/* global React, ReactDOM */
const { useState, useEffect, useRef, useMemo, useCallback } = React;

// ============ src/icons.jsx ============
// =============== ICONS ===============
// Stroke-based, all 18px, currentColor — keep them quiet.
const Icon = ({ d, size = 18, fill = "none", strokeWidth = 1.5, children, style }) =>
<svg width={size} height={size} viewBox="0 0 24 24" fill={fill} stroke="currentColor"
strokeWidth={strokeWidth} strokeLinecap="round" strokeLinejoin="round" style={style}>
    {d ? <path d={d} /> : children}
  </svg>;


const Icons = {
  Cursor: (p) => <Icon {...p}><path d="M5 3l14 8-7 2-2 7L5 3z" /></Icon>,
  Pen: (p) => <Icon {...p}><path d="M3 21l4-1 11-11-3-3L4 17l-1 4z" /><path d="M14 6l3 3" /></Icon>,
  Eraser: (p) => <Icon {...p}><path d="M3 17l9-9 6 6-7 7H5l-2-4z" /><path d="M9 11l6 6" /></Icon>,
  Hand: (p) => <Icon {...p}><path d="M9 11V5a1.5 1.5 0 013 0v6" /><path d="M12 11V4a1.5 1.5 0 013 0v7" /><path d="M15 11V6a1.5 1.5 0 013 0v8a6 6 0 01-12 0V9a1.5 1.5 0 013 0v2" /></Icon>,
  Text: (p) => <Icon {...p}><path d="M5 6h14M12 6v13M9 19h6" /></Icon>,
  Bubble: (p) => <Icon {...p}><path d="M4 5h16v11H10l-4 4v-4H4z" /></Icon>,
  Sparkle: (p) => <Icon {...p}><path d="M12 3l1.8 5.2L19 10l-5.2 1.8L12 17l-1.8-5.2L5 10l5.2-1.8L12 3z" /><path d="M19 17l.7 2 2 .7-2 .7-.7 2-.7-2-2-.7 2-.7.7-2z" /></Icon>,
  Wand: (p) => <Icon {...p}><path d="M3 21l11-11" /><path d="M14 4l1 2 2 1-2 1-1 2-1-2-2-1 2-1 1-2z" /><path d="M19 13l.6 1.4 1.4.6-1.4.6L19 17l-.6-1.4-1.4-.6 1.4-.6.6-1.4z" /></Icon>,
  Layers: (p) => <Icon {...p}><path d="M12 3l9 5-9 5-9-5 9-5z" /><path d="M3 13l9 5 9-5" /><path d="M3 18l9 5 9-5" /></Icon>,
  Plus: (p) => <Icon {...p}><path d="M12 5v14M5 12h14" /></Icon>,
  Minus: (p) => <Icon {...p}><path d="M5 12h14" /></Icon>,
  Undo: (p) => <Icon {...p}><path d="M9 14L4 9l5-5" /><path d="M4 9h11a5 5 0 010 10h-3" /></Icon>,
  Redo: (p) => <Icon {...p}><path d="M15 14l5-5-5-5" /><path d="M20 9H9a5 5 0 000 10h3" /></Icon>,
  Eye: (p) => <Icon {...p}><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z" /><circle cx="12" cy="12" r="3" /></Icon>,
  Lock: (p) => <Icon {...p}><rect x="5" y="11" width="14" height="9" rx="1.5" /><path d="M8 11V8a4 4 0 018 0v3" /></Icon>,
  Search: (p) => <Icon {...p}><circle cx="11" cy="11" r="6" /><path d="M20 20l-4-4" /></Icon>,
  Folder: (p) => <Icon {...p}><path d="M3 7a2 2 0 012-2h4l2 2h8a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2V7z" /></Icon>,
  User: (p) => <Icon {...p}><circle cx="12" cy="8" r="4" /><path d="M4 21a8 8 0 0116 0" /></Icon>,
  Image: (p) => <Icon {...p}><rect x="3" y="4" width="18" height="16" rx="2" /><circle cx="9" cy="10" r="2" /><path d="M3 17l5-5 4 4 3-3 6 6" /></Icon>,
  Settings: (p) => <Icon {...p}><circle cx="12" cy="12" r="3" /><path d="M19.4 15a1.7 1.7 0 00.3 1.8l.1.1a2 2 0 11-2.8 2.8l-.1-.1a1.7 1.7 0 00-1.8-.3 1.7 1.7 0 00-1 1.5V21a2 2 0 11-4 0v-.1a1.7 1.7 0 00-1-1.5 1.7 1.7 0 00-1.8.3l-.1.1a2 2 0 11-2.8-2.8l.1-.1a1.7 1.7 0 00.3-1.8 1.7 1.7 0 00-1.5-1H3a2 2 0 110-4h.1a1.7 1.7 0 001.5-1 1.7 1.7 0 00-.3-1.8l-.1-.1a2 2 0 112.8-2.8l.1.1a1.7 1.7 0 001.8.3h0a1.7 1.7 0 001-1.5V3a2 2 0 114 0v.1a1.7 1.7 0 001 1.5h0a1.7 1.7 0 001.8-.3l.1-.1a2 2 0 112.8 2.8l-.1.1a1.7 1.7 0 00-.3 1.8v0a1.7 1.7 0 001.5 1H21a2 2 0 110 4h-.1a1.7 1.7 0 00-1.5 1z" /></Icon>,
  Share: (p) => <Icon {...p}><path d="M4 12v7a1 1 0 001 1h14a1 1 0 001-1v-7" /><path d="M16 6l-4-4-4 4" /><path d="M12 2v13" /></Icon>,
  Play: (p) => <Icon {...p}><path d="M6 4l14 8-14 8V4z" /></Icon>,
  ChevronLeft: (p) => <Icon {...p}><path d="M15 6l-6 6 6 6" /></Icon>,
  ChevronRight: (p) => <Icon {...p}><path d="M9 6l6 6-6 6" /></Icon>,
  ChevronDown: (p) => <Icon {...p}><path d="M6 9l6 6 6-6" /></Icon>,
  Dot: (p) => <Icon {...p}><circle cx="12" cy="12" r="3" fill="currentColor" /></Icon>,
  Dice: (p) => <Icon {...p}><rect x="4" y="4" width="16" height="16" rx="2" /><circle cx="9" cy="9" r="1" fill="currentColor" /><circle cx="15" cy="15" r="1" fill="currentColor" /><circle cx="15" cy="9" r="1" fill="currentColor" /><circle cx="9" cy="15" r="1" fill="currentColor" /></Icon>,
  Download: (p) => <Icon {...p}><path d="M12 4v12" /><path d="M7 11l5 5 5-5" /><path d="M4 20h16" /></Icon>,
  X: (p) => <Icon {...p}><path d="M6 6l12 12M18 6L6 18" /></Icon>,
  Grip: (p) => <Icon {...p}><circle cx="9" cy="6" r="1" fill="currentColor" /><circle cx="9" cy="12" r="1" fill="currentColor" /><circle cx="9" cy="18" r="1" fill="currentColor" /><circle cx="15" cy="6" r="1" fill="currentColor" /><circle cx="15" cy="12" r="1" fill="currentColor" /><circle cx="15" cy="18" r="1" fill="currentColor" /></Icon>,
  Crop: (p) => <Icon {...p}><path d="M6 2v16h16" /><path d="M2 6h16v16" /></Icon>,
  BgGen: (p) => <Icon {...p}><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 16l4-6 4 5 3-3.5 6 5.5"/><path d="M19 5l.5 1.5 1.5.5-1.5.5-.5 1.5-.5-1.5L17 7l1.5-.5.5-1.5z"/></Icon>,
  Angle: (p) => <Icon {...p}><path d="M4 9l8-4 8 4v7l-8 4-8-4V9z"/><path d="M4 9l8 4 8-4"/><path d="M12 13v7"/><path d="M18 7l2.5-2M18 7h2.5M18 7v2.5"/></Icon>
};

window.Icons = Icons;
window.Icon = Icon;


// ============ src/data.jsx ============
// Sample data for the editor demo.

const PAGES = [
{ id: 'p1', title: '第1話 — 月夜', titleEn: 'Ch.1 — Moonlit Night', panels: 6 },
{ id: 'p2', title: '第1話 — 出会い', titleEn: 'Ch.1 — Encounter', panels: 5 },
{ id: 'p3', title: '第1話 — 約束', titleEn: 'Ch.1 — The Promise', panels: 7 },
{ id: 'p4', title: '第2話 — 旅立ち', titleEn: 'Ch.2 — Departure', panels: 4 }];


// Panel layout for the active page (page 2). Coordinates in 100-unit grid.
// Each panel: x, y, w, h, status, painting (eyecatch as placeholder), label
const PANELS = [
{ id: 'pn1', x: 0, y: 0, w: 60, h: 28, status: 'done', painting: 'manifest', label: '導入' },
{ id: 'pn2', x: 60, y: 0, w: 40, h: 28, status: 'sketch', painting: null, label: 'リアクション' },
{ id: 'pn3', x: 0, y: 28, w: 35, h: 24, status: 'done', painting: 'product', label: '近景' },
{ id: 'pn4', x: 35, y: 28, w: 65, h: 24, status: 'selected', painting: null, label: '主人公・発話' },
{ id: 'pn5', x: 0, y: 52, w: 100, h: 28, status: 'sketch', painting: null, label: '回想' },
{ id: 'pn6', x: 0, y: 80, w: 50, h: 20, status: 'empty', painting: null, label: '' },
{ id: 'pn7', x: 50, y: 80, w: 50, h: 20, status: 'done', painting: 'news', label: '余韻' }];


const CHARACTERS = [
{ id: 'c1', name: '蓮 / Ren', role: 'Protagonist', lora: 'ren_v3.safetensors', strength: 0.82, painting: 'manifest' },
{ id: 'c2', name: '茜 / Akane', role: 'Heroine', lora: 'akane_v2.safetensors', strength: 0.75, painting: 'product' },
{ id: 'c3', name: '師匠 / Shishō', role: 'Mentor', lora: 'shisho_v1.safetensors', strength: 0.68, painting: 'news' },
{ id: 'c4', name: '影 / Kage', role: 'Antagonist', lora: 'kage_v1.safetensors', strength: 0.70, painting: null }];


const STYLES = [
{ id: 's1', name: 'Mincho Ink', sub: '墨絵 / sumi-e', active: true },
{ id: 's2', name: 'Shōnen Crisp', sub: '少年クリスプ' },
{ id: 's3', name: 'Watercolour', sub: '水彩' },
{ id: 's4', name: 'Halftone Noir', sub: 'ノワール' }];


const ASSETS = [
{ id: 'a1', name: 'Forest path', kind: 'BG', painting: 'manifest' },
{ id: 'a2', name: 'Edo townscape', kind: 'BG', painting: 'product' },
{ id: 'a3', name: 'Moon over rooftops', kind: 'BG', painting: 'news' },
{ id: 'a4', name: 'Katana — sheathed', kind: 'PROP' },
{ id: 'a5', name: 'Lantern, paper', kind: 'PROP' },
{ id: 'a6', name: 'Sliding door', kind: 'PROP' },
{ id: 'a7', name: 'ザッ', kind: 'SFX' },
{ id: 'a8', name: 'ドクン', kind: 'SFX' }];


window.DATA = { PAGES, PANELS, CHARACTERS, STYLES, ASSETS };


// ============ src/atoms.jsx ============
// =============== ATOMS ===============
// Small chrome elements shared across editor panels.

// The Holonica bezel logo — using the actual logo.svg asset.
const BezelLogo = ({ size = 22 }) =>
<img src="assets/logo.svg" width={size} height={size} alt="Holonica" style={{ display: 'block', filter: 'var(--logo-filter, none)' }} />;


// Editorial header — full-width chrome with bezel logo + project breadcrumb.
const EditorialHeader = ({ title, titleEn, projectName, onTweaks }) =>
<header style={{
  height: 'var(--header-h)',
  display: 'flex', alignItems: 'center',
  padding: '0 16px',
  background: 'var(--panel-bg)',
  borderBottom: '1px solid var(--panel-border)',
  flexShrink: 0,
  position: 'relative',
  zIndex: 10
}}>
    <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
      <BezelLogo size={22} />
      <button 
        style={{
          padding: '4px 12px',
          borderRadius: 6,
          border: '1px solid transparent',
          background: 'transparent',
          fontSize: 13,
          fontWeight: 600,
          color: 'var(--ink-900)',
          letterSpacing: '-0.01em',
          fontFamily: 'var(--font-sans)',
          cursor: 'default',
          transition: 'border-color 160ms',
        }}
        onMouseEnter={e => {
          e.currentTarget.style.borderColor = 'var(--ink-300)';
        }}
        onMouseLeave={e => {
          e.currentTarget.style.borderColor = 'transparent';
        }}
      >
        プロジェクト名
      </button>
    </div>

    <div style={{ flex: 1 }} />

    {/* Right side — share, status, avatar */}
    <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
      <span style={{ fontSize: 11, color: 'var(--ink-500)', display: 'flex', alignItems: 'center', gap: 6 }}>
        <span style={{ width: 6, height: 6, borderRadius: '50%', background: '#10B981' }}></span>
        自動保存中 <span style={{ fontFamily: 'var(--font-display)', fontStyle: 'italic' }}>· Auto-saved</span>
      </span>
      <button style={{ ...pillBtn, background: 'var(--ink-900)', color: 'var(--ink-0)' }}>
        <Icons.Download size={14} /> エクスポート
      </button>
      <div style={{
      width: 28, height: 28, borderRadius: '50%',
      background: 'linear-gradient(135deg, #B8941F, var(--accent))',
      border: '1px solid var(--ink-200)'
    }} />
    </div>
  </header>;


const iconBtn = {
  width: 32, height: 32, borderRadius: 8,
  display: 'flex', alignItems: 'center', justifyContent: 'center',
  color: 'var(--ink-700)',
  transition: 'background 160ms'
};
const iconBtnActive = { ...iconBtn, background: 'var(--ink-100)', color: 'var(--ink-900)' };

const pillBtn = {
  height: 30, padding: '0 14px', borderRadius: 100,
  display: 'inline-flex', alignItems: 'center', gap: 6,
  fontSize: 12, fontWeight: 500
};

// Eyebrow label — small uppercase tracker, used for section heads in rails.
const Eyebrow = ({ children, right }) =>
<div style={{
  display: 'flex', alignItems: 'center', justifyContent: 'space-between',
  fontSize: 10, fontWeight: 600, letterSpacing: '0.12em',
  textTransform: 'uppercase', color: 'var(--ink-500)',
  padding: '14px 14px 8px'
}}>
    <span>{children}</span>
    {right}
  </div>;


// A small editorial section title — serif + italic en-subtitle.
const SectionTitle = ({ jp, en, level = 2 }) =>
<div style={{ display: 'flex', alignItems: 'baseline', gap: 8 }}>
    <span style={{
    fontFamily: 'var(--font-display)', fontWeight: 700,
    fontSize: level === 1 ? 18 : 14,
    letterSpacing: '-0.01em'
  }}>{jp}</span>
    {en && <span style={{
    fontFamily: 'var(--font-display)', fontStyle: 'italic',
    fontSize: 11, color: 'var(--ink-400)'
  }}>{en}</span>}
  </div>;


// Painterly thumbnail — uses one of the eyecatch paintings, with a soft frame.
const PaintingTile = ({ name, w = '100%', h = 80, label, sublabel, halo }) => {
  const src = name ? `assets/paintings/${name}.png` : null;
  return (
    <div style={{
      position: 'relative', width: w, height: h, overflow: 'hidden',
      background: src ? `center/cover no-repeat url(${src}), var(--ink-100)` : 'var(--ink-100)',
      borderRadius: 4,
      border: '1px solid var(--panel-border)'
    }}>
      {!src &&
      <div style={{
        position: 'absolute', inset: 0,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        color: 'var(--ink-400)'
      }}><Icons.Image size={20} /></div>
      }
      {halo && <div style={{
        position: 'absolute', inset: 0,
        background: 'radial-gradient(circle at 30% 30%, rgba(244,208,63,.35), transparent 60%)',
        pointerEvents: 'none'
      }} />}
      {label &&
      <div style={{
        position: 'absolute', left: 6, bottom: 6, right: 6,
        fontSize: 10, color: '#fff', textShadow: '0 1px 3px rgba(0,0,0,.6)',
        fontWeight: 500, lineHeight: 1.2
      }}>
          {label}
          {sublabel && <div style={{ fontStyle: 'italic', fontFamily: 'var(--font-display)', opacity: 0.85 }}>{sublabel}</div>}
        </div>
      }
    </div>);

};

// Tag pill
const Tag = ({ children, tone = 'default', onClick, removable }) => {
  const tones = {
    default: { bg: 'var(--ink-100)', fg: 'var(--ink-700)', bd: 'transparent' },
    accent: { bg: 'rgba(244,208,63,.15)', fg: 'var(--selected-ink)', bd: 'rgba(244,208,63,.5)' },
    outline: { bg: 'transparent', fg: 'var(--ink-700)', bd: 'var(--ink-300)' },
    dark: { bg: 'var(--ink-900)', fg: 'var(--ink-0)', bd: 'var(--ink-900)' }
  };
  const t = tones[tone];
  return (
    <button onClick={onClick} style={{
      height: 22, padding: '0 8px', borderRadius: 100,
      background: t.bg, color: t.fg, border: `1px solid ${t.bd}`,
      fontSize: 11, fontWeight: 500,
      display: 'inline-flex', alignItems: 'center', gap: 4
    }}>
      {children}
      {removable && <Icons.X size={10} />}
    </button>);

};

// Slider with label + value
const LabeledSlider = ({ label, value, onChange, min = 0, max = 100, step = 1, suffix }) =>
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
    <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11, color: 'var(--ink-600)' }}>
      <span>{label}</span>
      <span style={{ fontFamily: 'var(--font-mono)', color: 'var(--ink-900)' }}>{typeof value === 'number' ? value.toFixed(2) : value}{suffix}</span>
    </div>
    <input type="range" min={min} max={max} step={step} value={value} onChange={(e) => onChange(parseFloat(e.target.value))}
  style={{ width: '100%', accentColor: 'var(--ink-900)' }} />
  </div>;


window.Atoms = {
  BezelLogo, EditorialHeader, Eyebrow, SectionTitle, PaintingTile, Tag, LabeledSlider,
  iconBtn, iconBtnActive, pillBtn
};
window.iconBtn = iconBtn;
window.iconBtnActive = iconBtnActive;
window.pillBtn = pillBtn;


// ============ src/canvas.jsx ============
// =============== CANVAS ===============
// The page canvas — manga-style multi-panel layout.
// Each panel is clickable; selected panel reveals AI controls in inspector.

const CANVAS_W = 720; // unscaled page width
const CANVAS_H = 1020; // ~A4-ish ratio

// Renders a single panel with its content state.
const Panel = ({ panel, selected, onSelect, panelTextBoxes = [], selectedTextBoxId, onSelectTextBox }) => {
  const isSelected = selected === panel.id;

  return (
    <div
      onClick={(e) => { e.stopPropagation(); onSelect(panel.id); if (onSelectTextBox) onSelectTextBox(null); }}
      style={{
        position: 'absolute',
        left: panel.x + '%',
        top: panel.y + '%',
        width: panel.w + '%',
        height: panel.h + '%',
        padding: 3,
        cursor: 'pointer'
      }}>

      <div style={{
        position: 'relative', width: '100%', height: '100%',
        background: '#fff',
        border: `2px solid ${isSelected ? 'var(--accent)' : '#1a1a1a'}`,
        boxShadow: isSelected ? '0 0 0 4px rgba(244,208,63,.25)' : 'none',
        overflow: 'hidden',
        transition: 'box-shadow 200ms, border-color 200ms'
      }}>
        {/* Text boxes */}
        {panelTextBoxes.map((tb, i) => (
          <TextBoxOverlay
            key={tb.id} tb={tb} index={i}
            selected={selectedTextBoxId === tb.id}
            onSelect={id => { if (onSelectTextBox) onSelectTextBox(id); }}
          />
        ))}
      </div>
    </div>);

};

// Text box overlay rendered inside a panel
const TextBoxOverlay = ({ tb, selected, onSelect, index }) => (
  <div
    onClick={e => { e.stopPropagation(); onSelect(tb.id); }}
    style={{
      position: 'absolute',
      left: '6%', top: `${8 + index * 26}%`,
      width: '88%',
      padding: '3px 6px',
      border: `1.5px ${selected ? 'solid' : 'dashed'} ${selected ? 'var(--accent)' : 'rgba(80,120,220,0.55)'}`,
      background: selected ? 'rgba(244,208,63,.09)' : 'rgba(255,255,255,.65)',
      cursor: 'pointer', borderRadius: 2,
      fontFamily: tb.font || 'sans-serif',
      fontSize: `${Math.max(7, (tb.size || 14) * 0.52)}px`,
      fontWeight: tb.bold ? 700 : 400,
      fontStyle: tb.italic ? 'italic' : 'normal',
      color: tb.color || '#1a1a1a',
      lineHeight: tb.lineHeight || 1.6,
      writingMode: tb.vertical ? 'vertical-rl' : 'horizontal-tb',
      boxShadow: selected ? '0 0 0 3px rgba(244,208,63,.22)' : 'none',
      transition: 'border-color 120ms, box-shadow 120ms, background 120ms',
      zIndex: 3,
      userSelect: 'none',
    }}
  >
    {tb.text || 'テキスト'}
    {selected && (
      <div style={{
        position: 'absolute', bottom: -4, right: -4,
        width: 8, height: 8, borderRadius: 2,
        background: 'var(--accent)', border: '1px solid #fff',
      }} />
    )}
  </div>
);

// SVG scribble — represents user's rough sketch (the "scribble" input)
const ScribbleSketch = () =>
<svg viewBox="0 0 100 60" preserveAspectRatio="none" style={{ position: 'absolute', inset: 0, width: '100%', height: '100%' }}>
    <g stroke="#1a1a1a" strokeWidth="0.7" fill="none" strokeLinecap="round" opacity="0.7">
      {/* Head */}
      <ellipse cx="40" cy="22" rx="9" ry="11" />
      {/* Hair */}
      <path d="M31 16 Q33 8 42 8 Q50 9 49 17 Q47 13 42 14 Q36 14 31 16Z" />
      {/* Body */}
      <path d="M32 33 Q30 45 28 58 M48 33 Q50 45 52 58" />
      {/* Arm */}
      <path d="M48 36 Q60 38 64 30" />
      {/* Sword line */}
      <path d="M64 30 L82 18" />
      {/* Background ground */}
      <path d="M0 50 Q25 48 50 51 T100 49" />
    </g>
  </svg>;


// Hint overlay when panel selected — suggests AI is "watching"
const SelectedScribbleHint = () =>
<>
    <ScribbleSketch />
    <div style={{
    position: 'absolute', left: 0, right: 0, top: 0, bottom: 0,
    background: 'linear-gradient(135deg, rgba(244,208,63,.08), transparent 50%)',
    pointerEvents: 'none'
  }} />
    <div style={{
    position: 'absolute', left: 8, top: 8,
    background: 'var(--accent)', color: '#1a1a1a',
    padding: '3px 8px', borderRadius: 100,
    fontSize: 10, fontWeight: 600,
    display: 'flex', alignItems: 'center', gap: 4,
    letterSpacing: '0.05em', textTransform: 'uppercase'
  }}>
      <Icons.Sparkle size={10} /> 選択中
    </div>
  </>;


const PageCanvas = ({ pages, activePageId, panels, selectedPanel, onSelectPanel, zoom = 1, textBoxes = {}, selectedTextBoxId, onSelectTextBox }) => {
  const activePage = pages.find((p) => p.id === activePageId) || pages[0];
  const indexed = panels.map((p, i) => ({ ...p, idx: i + 1 }));

  return (
    <div
      onClick={() => { onSelectPanel(null); if (onSelectTextBox) onSelectTextBox(null); }}
      style={{
        flex: 1, position: 'relative', overflow: 'auto',
        background: 'var(--canvas-bg)',
        backgroundImage: 'radial-gradient(circle, rgba(0,0,0,.04) 1px, transparent 1px)',
        backgroundSize: '20px 20px',
        display: 'flex', alignItems: 'flex-start', justifyContent: 'center',
        padding: '40px'
      }}>
      
      {/* The page */}
      <div style={{
        width: CANVAS_W * zoom,
        flexShrink: 0
      }}>
        {/* Caption removed */}

        <div style={{
          position: 'relative',
          width: '100%',
          paddingBottom: '141.4%', /* B5-ish */
          background: '#fdfcf8',
          boxShadow: '0 30px 80px rgba(0,0,0,.12), 0 6px 16px rgba(0,0,0,.06)',
          border: '1px solid var(--ink-200)'
        }}>
          {indexed.map((p) =>
          <Panel key={p.id} panel={p} selected={selectedPanel}
            onSelect={onSelectPanel}
            panelTextBoxes={(textBoxes || {})[p.id] || []}
            selectedTextBoxId={selectedTextBoxId}
            onSelectTextBox={onSelectTextBox}
          />
          )}
        </div>

        {/* Page footer — page number, editorial */}
        <div style={{
          marginTop: 12, textAlign: 'center',
          fontFamily: 'var(--font-display)', fontStyle: 'italic',
          fontSize: 11, color: 'var(--ink-400)'
        }}>
          — page {pages.findIndex((p) => p.id === activePageId) + 1} of {pages.length} —
        </div>
      </div>
    </div>);

};

window.PageCanvas = PageCanvas;


// ============ src/rails.jsx ============
// =============== RAILS ===============
// Left rail: pages navigator.
// Right rail (when no panel selected): asset library tabs.

// ----- LEFT RAIL: Pages -----
const TOOLS_DEF = [
  { Icon: Icons.Pen,     label: 'キャラデザイン' },
  { Icon: Icons.Sparkle, label: 'キャラ生成' },
  { Icon: Icons.Image,   label: '背景生成' },
  { Icon: Icons.Angle,   label: 'アングル変換', ai: true },
  { Icon: Icons.Crop,    label: 'コマ割り' },
  { Icon: Icons.Folder,  label: 'テンプレ' },
  { Icon: Icons.Text,    label: 'テキスト' },
  { Icon: Icons.Layers,  label: 'ライブラリ' },
];

const PagesRail = ({ pages, activePageId, onSelectPage, narrow, projectName, aiPanel, activeTool, onToolChange }) => {
  if (narrow) {
    return (
      <div style={railNarrowStyle}>
        {pages.map((p, i) =>
        <button key={p.id} onClick={() => onSelectPage(p.id)}
        style={{
          ...iconBtn,
          width: 36, height: 48, borderRadius: 4,
          fontFamily: 'var(--font-display)', fontSize: 14,
          background: activePageId === p.id ? 'var(--ink-900)' : 'var(--ink-100)',
          color: activePageId === p.id ? 'var(--ink-0)' : 'var(--ink-700)',
          flexDirection: 'column', gap: 2,
        }}>
          {i + 1}
        </button>
        )}
        <button style={{ ...iconBtn, width: 36, height: 36, border: '1px dashed var(--ink-300)' }}>
          <Icons.Plus size={14} />
        </button>
      </div>
    );
  }

  return (
    <aside style={railStyle}>
      {/* Logo + project name header */}
      <div style={{
        height: 'var(--header-h)', flexShrink: 0,
        display: 'flex', alignItems: 'center',
        padding: '0 14px',
        borderBottom: '1px solid var(--panel-border)',
        gap: 10,
      }}>
        <BezelLogo size={22} />
        <button
          style={{
            padding: '4px 12px', borderRadius: 6,
            border: '1px solid transparent', background: 'transparent',
            fontSize: 13, fontWeight: 600, color: 'var(--ink-900)',
            letterSpacing: '-0.01em', fontFamily: 'var(--font-sans)',
            cursor: 'default', transition: 'border-color 160ms',
          }}
          onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--ink-300)'; }}
          onMouseLeave={e => { e.currentTarget.style.borderColor = 'transparent'; }}
        >
          プロジェクト名
        </button>
      </div>

      {/* Toolbar + properties panel — side by side */}
      <div style={{ flex: 1, display: 'flex', minHeight: 0, overflow: 'hidden' }}>

        {/* Vertical toolbar — left side */}
        <div style={{
          width: 48, flexShrink: 0,
          borderRight: '1px solid var(--panel-border)',
          display: 'flex', flexDirection: 'column', alignItems: 'center',
          padding: '8px 0', gap: 2,
          overflowY: 'auto',
        }}>
          {TOOLS_DEF.map((t, i) => {
            const active = activeTool === i;
            return (
              <button key={i} title={t.label} onClick={() => onToolChange(i)} style={{
                display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
                gap: 3, width: 40, padding: '7px 4px', borderRadius: 6,
                border: 'none', cursor: 'pointer',
                background: active ? 'var(--ink-900)' : 'transparent',
                color: active ? 'var(--ink-0)' : 'var(--ink-700)',
                transition: 'background 120ms, color 120ms',
              }}
                onMouseEnter={e => { if (!active) e.currentTarget.style.background = 'var(--ink-100)'; }}
                onMouseLeave={e => { if (!active) e.currentTarget.style.background = 'transparent'; }}
              >
                <t.Icon size={16} />
                <span style={{
                  fontSize: 8, fontWeight: 500, textAlign: 'center',
                  fontFamily: 'var(--font-sans)', lineHeight: 1.3,
                  whiteSpace: 'pre-line',
                  color: active ? 'var(--ink-0)' : 'var(--ink-600)',
                }}>{t.label}</span>
              </button>
            );
          })}
        </div>

        {/* Tool properties panel — right side */}
        {aiPanel && (
          <div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden', minHeight: 0 }}>
            {aiPanel}
          </div>
        )}
      </div>
    </aside>
  );
};

// ----- FLOATING PAGE STRIP: bottom of canvas -----
const FloatingPageStrip = ({ pages, activePageId, onSelectPage }) => {
  const [rtl, setRtl] = useState(true);
  const displayed = rtl ? [...pages].reverse() : pages;
  const actualIdx = (p) => pages.indexOf(p);

  const dirToggle = (
    <button
      key="toggle"
      onClick={() => setRtl(r => !r)}
      title={rtl ? '左→右に切り替え' : '右→左に切り替え'}
      style={{
        pointerEvents: 'all',
        height: 28, padding: '0 8px', borderRadius: 6,
        background: '#ffffff', border: 'none',
        boxShadow: '0 1px 4px rgba(0,0,0,.15)',
        display: 'flex', alignItems: 'center', gap: 4,
        fontSize: 10, fontWeight: 600, color: '#444',
        cursor: 'pointer', flexShrink: 0, alignSelf: 'flex-end', marginBottom: 7,
      }}
    >
      {rtl ? 'RTL' : 'LTR'}
    </button>
  );

  const addBtn = (
    <button key="add" style={{
      pointerEvents: 'all',
      display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
      width: 30, height: 43,
      border: '1.5px dashed #c8c8c8', borderRadius: 3,
      background: '#ffffff',
      boxShadow: '0 2px 6px rgba(0,0,0,.08)',
      cursor: 'pointer', color: '#aaa', flexShrink: 0,
    }}>
      <Icons.Plus size={11}/>
    </button>
  );

  const thumbs = (
    <div key="thumbs" style={{
      pointerEvents: 'all',
      display: 'flex', flexDirection: 'row', gap: 5, alignItems: 'flex-end',
    }}>
      {displayed.map((p) => {
        const i = actualIdx(p);
        const active = activePageId === p.id;
        return (
          <button key={p.id} onClick={() => onSelectPage(p.id)} style={{
            display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 3,
            background: 'none', border: 'none', cursor: 'pointer', padding: 0,
          }}>
            <div style={{
              width: 30, height: 43,
              background: '#fdfcf8',
              border: `1.5px solid ${active ? 'var(--accent)' : '#c8c8c8'}`,
              outline: 'none',
              borderRadius: 3,
              boxShadow: active
                ? '0 0 0 3px rgba(244,208,63,.3), 0 2px 8px rgba(0,0,0,.15)'
                : '0 2px 6px rgba(0,0,0,.12)',
              overflow: 'hidden',
              transition: 'border-color 150ms, box-shadow 150ms',
            }}>
              <MiniPageDiagram pageIdx={i}/>
            </div>
            <span style={{
              fontSize: 9, fontFamily: 'var(--font-mono)',
              color: active ? 'var(--ink-900)' : 'var(--ink-500)',
              fontWeight: active ? 700 : 400,
            }}>{i + 1}</span>
          </button>
        );
      })}
    </div>
  );

  return (
    <div style={{
      position: 'absolute',
      bottom: 12,
      left: rtl ? 'auto' : 12,
      right: rtl ? 12 : 'auto',
      zIndex: 20,
      display: 'flex',
      flexDirection: 'row',
      alignItems: 'flex-end',
      gap: 6,
      pointerEvents: 'none',
    }}>
      {rtl
        ? <>{dirToggle}{addBtn}{thumbs}</>
        : <>{thumbs}{addBtn}{dirToggle}</>
      }
    </div>
  );
};

// ----- TEMPLATE PANEL -----
const TEMPLATE_DATA = {
  'コマ割り': [
    // 2コマ
    { id: 't1',  label: '上下2等分',   panels: 2, layout: [[0,0,1,.5],[0,.5,1,.5]] },
    { id: 't5',  label: '左右見開き',  panels: 2, layout: [[0,0,.5,1],[.5,0,.5,1]] },
    { id: 't7',  label: '上大下小',    panels: 2, layout: [[0,0,1,.65],[0,.65,1,.35]] },
    // 3コマ
    { id: 't2',  label: '3段均等',     panels: 3, layout: [[0,0,1,.33],[0,.33,1,.33],[0,.66,1,.34]] },
    { id: 't3',  label: 'L字型',       panels: 3, layout: [[0,0,.6,1],[.6,0,.4,.5],[.6,.5,.4,.5]] },
    { id: 't8',  label: '上+下2',      panels: 3, layout: [[0,0,1,.5],[0,.5,.5,.5],[.5,.5,.5,.5]] },
    { id: 't6',  label: '斜め分割',    panels: 3, layout: [[0,0,1,.4],[0,.4,.45,.6],[.45,.4,.55,.6]] },
    // 4コマ
    { id: 't4',  label: '4コマ',       panels: 4, layout: [[0,0,.5,.5],[.5,0,.5,.5],[0,.5,.5,.5],[.5,.5,.5,.5]] },
    { id: 't9',  label: '4段均等',     panels: 4, layout: [[0,0,1,.25],[0,.25,1,.25],[0,.5,1,.25],[0,.75,1,.25]] },
    { id: 't10', label: '上大+下3',    panels: 4, layout: [[0,0,1,.5],[0,.5,.33,.5],[.33,.5,.34,.5],[.67,.5,.33,.5]] },
    { id: 't11', label: '左大+右3',    panels: 4, layout: [[0,0,.6,1],[.6,0,.4,.33],[.6,.33,.4,.34],[.6,.67,.4,.33]] },
    // 5コマ
    { id: 't12', label: '上+2+下2',    panels: 5, layout: [[0,0,1,.33],[0,.33,.5,.34],[.5,.33,.5,.34],[0,.67,.5,.33],[.5,.67,.5,.33]] },
    { id: 't13', label: '左2+右3',     panels: 5, layout: [[0,0,.5,.5],[0,.5,.5,.5],[.5,0,.5,.33],[.5,.33,.5,.34],[.5,.67,.5,.33]] },
    { id: 't14', label: '上3+下2',     panels: 5, layout: [[0,0,.33,.5],[.33,0,.34,.5],[.67,0,.33,.5],[0,.5,.5,.5],[.5,.5,.5,.5]] },
    // 6コマ
    { id: 't15', label: '2×3グリッド', panels: 6, layout: [[0,0,.5,.33],[.5,0,.5,.33],[0,.33,.5,.34],[.5,.33,.5,.34],[0,.67,.5,.33],[.5,.67,.5,.33]] },
    { id: 't16', label: '3×2グリッド', panels: 6, layout: [[0,0,.33,.5],[.33,0,.34,.5],[.67,0,.33,.5],[0,.5,.33,.5],[.33,.5,.34,.5],[.67,.5,.33,.5]] },
    { id: 't17', label: '大+小5',      panels: 6, layout: [[0,0,.6,.6],[.6,0,.4,.3],[.6,.3,.4,.3],[0,.6,.33,.4],[.33,.6,.34,.4],[.67,.6,.33,.4]] },
  ],
  '吹き出し': [
    { id: 'b1', label: '丸形', shape: 'ellipse' },
    { id: 'b2', label: '角丸', shape: 'rounded' },
    { id: 'b3', label: '叫び', shape: 'spiky' },
    { id: 'b4', label: 'ふわ', shape: 'cloud' },
    { id: 'b5', label: '四角', shape: 'rect' },
    { id: 'b6', label: 'モノローグ', shape: 'mono' },
  ],
  'オノマトペ': [
    { id: 'o1', label: 'ドカン', style: 'impact' },
    { id: 'o2', label: 'ザッ', style: 'slash' },
    { id: 'o3', label: 'ドクン', style: 'pulse' },
    { id: 'o4', label: 'シーン', style: 'quiet' },
    { id: 'o5', label: 'バキ', style: 'crack' },
    { id: 'o6', label: 'フワ', style: 'float' },
  ],
  '漫符': [
    { id: 'm1', label: '汗' },
    { id: 'm2', label: '怒りマーク' },
    { id: 'm3', label: '電球' },
    { id: 'm4', label: 'ハート' },
    { id: 'm5', label: 'zzz' },
    { id: 'm6', label: '疑問符' },
  ],
};

const TemplateThumbnail = ({ category, item }) => {
  if (category === 'コマ割り') {
    return (
      <svg viewBox="0 0 60 80" style={{ width: '100%', height: '100%' }}>
        {item.layout.map((r, i) => (
          <rect key={i} x={r[0]*60+1} y={r[1]*80+1} width={r[2]*60-2} height={r[3]*80-2}
            fill="#fafafa" stroke="#1a1a1a" strokeWidth="1.5"/>
        ))}
      </svg>
    );
  }
  if (category === '吹き出し') {
    const shapes = {
      ellipse:  <><ellipse cx="30" cy="34" rx="24" ry="18" fill="#fff" stroke="#1a1a1a" strokeWidth="1.5"/><polygon points="28,52 22,66 36,54" fill="#fff" stroke="#1a1a1a" strokeWidth="1.5"/></>,
      rounded:  <><rect x="6" y="20" width="48" height="32" rx="8" fill="#fff" stroke="#1a1a1a" strokeWidth="1.5"/><polygon points="20,52 14,66 30,53" fill="#fff" stroke="#1a1a1a" strokeWidth="1.5"/></>,
      spiky:    <><polygon points="30,8 36,22 52,18 44,30 58,36 44,40 50,54 36,48 30,62 24,48 10,54 16,40 2,36 16,30 8,18 24,22" fill="#fff" stroke="#1a1a1a" strokeWidth="1.5"/></>,
      cloud:    <><ellipse cx="20" cy="36" rx="12" ry="10" fill="#fff" stroke="#1a1a1a" strokeWidth="1.5"/><ellipse cx="30" cy="30" rx="14" ry="12" fill="#fff" stroke="#1a1a1a" strokeWidth="1.5"/><ellipse cx="42" cy="36" rx="11" ry="9" fill="#fff" stroke="#1a1a1a" strokeWidth="1.5"/><rect x="10" y="36" width="40" height="12" fill="#fff" stroke="none"/><line x1="10" y1="48" x2="50" y2="48" stroke="#1a1a1a" strokeWidth="1.5"/><polygon points="22,48 18,62 30,50" fill="#fff" stroke="#1a1a1a" strokeWidth="1.5"/></>,
      rect:     <><rect x="6" y="20" width="48" height="32" fill="#fff" stroke="#1a1a1a" strokeWidth="1.5"/><polygon points="20,52 14,66 30,53" fill="#fff" stroke="#1a1a1a" strokeWidth="1.5"/></>,
      mono:     <><rect x="6" y="20" width="48" height="32" rx="4" fill="#fff" stroke="#1a1a1a" strokeWidth="1.5" strokeDasharray="3,2"/></>,
    };
    return <svg viewBox="0 0 60 80" style={{ width: '100%', height: '100%' }}>{shapes[item.shape]}</svg>;
  }
  if (category === 'オノマトペ') {
    const colors = { impact:'#e53e3e', slash:'#2b6cb0', pulse:'#9b2c2c', quiet:'#4a5568', crack:'#744210', float:'#2c7a7b' };
    return (
      <svg viewBox="0 0 60 80" style={{ width: '100%', height: '100%' }}>
        <text x="30" y="50" textAnchor="middle" fontFamily="serif" fontWeight="900"
          fontSize="22" fill={colors[item.style] || '#1a1a1a'}>{item.label}</text>
      </svg>
    );
  }
  // 漫符
  const manpu = { '汗':'💧', '怒りマーク':'💢', '電球':'💡', 'ハート':'❤️', 'zzz':'💤', '疑問符':'❓' };
  return (
    <svg viewBox="0 0 60 80" style={{ width: '100%', height: '100%' }}>
      <text x="30" y="50" textAnchor="middle" fontSize="28">{manpu[item.label] || item.label}</text>
    </svg>
  );
};

const TemplatePanel = () => {
  const categories = Object.keys(TEMPLATE_DATA);
  const [category, setCategory] = useState(categories[0]);
  const [panelFilter, setPanelFilter] = useState('all');

  const allItems = TEMPLATE_DATA[category];
  const isKoma = category === 'コマ割り';

  // コマ割りのコマ数一覧（昇順・重複なし）
  const panelCounts = isKoma
    ? [...new Set(allItems.map(i => i.panels))].sort((a, b) => a - b)
    : [];

  const items = isKoma && panelFilter !== 'all'
    ? allItems.filter(i => i.panels === Number(panelFilter))
    : allItems;

  const selectStyle = {
    fontFamily: 'var(--font-sans)',
    fontSize: 11,
    fontWeight: 500,
    color: 'var(--ink-900)',
    background: 'var(--ink-100)',
    border: '1px solid var(--ink-300)',
    borderRadius: 6,
    padding: '4px 24px 4px 8px',
    cursor: 'pointer',
    outline: 'none',
    appearance: 'none',
    WebkitAppearance: 'none',
    backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%23737373' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")`,
    backgroundRepeat: 'no-repeat',
    backgroundPosition: 'right 7px center',
  };

  return (
    <div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden', minHeight: 0 }}>

      {/* Category selector */}
      <div style={{ padding: '12px 14px 10px', borderBottom: '1px solid var(--ink-200)', flexShrink: 0 }}>
        <div style={{
          fontSize: 10, fontWeight: 500, letterSpacing: '0.08em', textTransform: 'uppercase',
          color: 'var(--ink-400)', fontFamily: 'var(--font-sans)', marginBottom: 10,
        }}>テンプレート · Templates</div>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 5 }}>
          {categories.map(c => {
            const active = category === c;
            return (
              <button key={c} onClick={() => { setCategory(c); setPanelFilter('all'); }} style={{
                padding: '4px 12px', borderRadius: 'var(--r-pill)',
                border: active ? 'none' : '1px solid var(--ink-300)',
                background: active ? 'var(--ink-900)' : 'transparent',
                color: active ? '#fff' : 'var(--ink-600)',
                fontSize: 11, fontWeight: 500,
                fontFamily: 'var(--font-sans)',
                cursor: 'pointer',
                transition: 'background var(--d-fast) var(--ease-out), color var(--d-fast) var(--ease-out)',
              }}>
                {c}
              </button>
            );
          })}
        </div>
      </div>

      {/* コマ割り: panel-count filter */}
      {isKoma && (
        <div style={{
          padding: '8px 14px',
          borderBottom: '1px solid var(--ink-200)',
          flexShrink: 0,
          display: 'flex', alignItems: 'center', gap: 8,
        }}>
          <span style={{
            fontSize: 10, fontWeight: 500, color: 'var(--ink-500)',
            fontFamily: 'var(--font-sans)', whiteSpace: 'nowrap',
          }}>コマ数</span>
          <div style={{ position: 'relative', flex: 1 }}>
            <select
              value={panelFilter}
              onChange={e => setPanelFilter(e.target.value)}
              style={selectStyle}
            >
              <option value="all">すべて ({allItems.length})</option>
              {panelCounts.map(n => (
                <option key={n} value={n}>{n}コマ</option>
              ))}
            </select>
          </div>
          {panelFilter !== 'all' && (
            <button
              onClick={() => setPanelFilter('all')}
              style={{
                fontFamily: 'var(--font-sans)', fontSize: 10,
                color: 'var(--ink-500)', padding: '3px 8px',
                border: '1px solid var(--ink-300)', borderRadius: 6,
                cursor: 'pointer', whiteSpace: 'nowrap',
              }}>
              クリア
            </button>
          )}
        </div>
      )}

      {/* Template grid */}
      <div style={{ flex: 1, overflow: 'auto', padding: '14px 14px 20px' }}>
        {items.length === 0 ? (
          <div style={{
            textAlign: 'center', padding: '32px 0',
            fontFamily: 'var(--font-sans)', fontSize: 11, color: 'var(--ink-400)',
          }}>該当なし</div>
        ) : (
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
            {items.map(item => (
              <button key={item.id} style={{
                display: 'flex', flexDirection: 'column', alignItems: 'stretch',
                background: 'var(--background)', border: '1px solid var(--ink-200)',
                borderRadius: 4, padding: 0, cursor: 'grab',
                overflow: 'hidden', textAlign: 'left',
                transition: 'border-color var(--d-fast) var(--ease-out), box-shadow var(--d-fast) var(--ease-out)',
              }}
                onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--ink-900)'; e.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,.08)'; }}
                onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--ink-200)'; e.currentTarget.style.boxShadow = 'none'; }}
              >
                <div style={{ width: '100%', aspectRatio: '3/4', background: '#fafafa' }}>
                  <TemplateThumbnail category={category} item={item}/>
                </div>
              </button>
            ))}
          </div>
        )}
      </div>
    </div>
  );
};

// ----- TEXT TOOL PANEL -----
const MANGA_FONTS = [
  { group: 'ゴシック系', fonts: [
    { label: 'ゴシック体',    value: '"Noto Sans JP", sans-serif' },
    { label: '丸ゴシック',    value: '"M PLUS Rounded 1c", sans-serif' },
    { label: 'ドットゴシック', value: '"DotGothic16", sans-serif' },
    { label: 'ポップ体',      value: '"RocknRoll One", sans-serif' },
  ]},
  { group: '明朝系', fonts: [
    { label: '明朝体',        value: '"Noto Serif JP", serif' },
    { label: '細明朝',        value: '"Hina Mincho", serif' },
    { label: 'アンティーク',  value: '"Kaisei Decol", serif' },
  ]},
  { group: '手書き・毛筆', fonts: [
    { label: '手書き',        value: '"Zen Kurenaido", cursive' },
    { label: '毛筆',          value: '"Yuji Mai", serif' },
    { label: 'スティック体',  value: '"Stick", sans-serif' },
  ]},
];

const TEXT_COLORS = [
  { label: '黒',    value: '#1a1a1a' },
  { label: '白',    value: '#ffffff' },
  { label: 'グレー', value: '#737373' },
  { label: '赤',    value: '#dc2626' },
  { label: '青',    value: '#2563eb' },
];

// ----- CUSTOM FONT DROPDOWN -----
const FontSelect = ({ value, onChange, groups }) => {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);

  // Find label for current value
  const currentFont = groups.flatMap(g => g.fonts).find(f => f.value === value);

  // Close on outside click
  useEffect(() => {
    if (!open) return;
    const handler = (e) => {
      if (ref.current && !ref.current.contains(e.target)) setOpen(false);
    };
    document.addEventListener('mousedown', handler);
    return () => document.removeEventListener('mousedown', handler);
  }, [open]);

  return (
    <div ref={ref} style={{ position: 'relative' }}>
      {/* Trigger */}
      <button
        onClick={() => setOpen(o => !o)}
        style={{
          width: '100%', height: 36, padding: '0 10px 0 12px',
          borderRadius: 7, border: '1px solid var(--ink-300)',
          background: open ? 'var(--ink-100)' : 'var(--background)',
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          cursor: 'pointer', gap: 8,
          transition: 'border-color 120ms, background 120ms',
        }}
        onMouseEnter={e => { if (!open) e.currentTarget.style.borderColor = 'var(--ink-500)'; }}
        onMouseLeave={e => { if (!open) e.currentTarget.style.borderColor = 'var(--ink-300)'; }}
      >
        {/* Left: label */}
        <span style={{
          fontFamily: 'var(--font-sans)', fontSize: 12, fontWeight: 600,
          color: 'var(--ink-800)', whiteSpace: 'nowrap', flex: 1, textAlign: 'left',
        }}>
          {currentFont?.label || 'フォントを選択'}
        </span>
        {/* Chevron */}
        <svg width="10" height="6" viewBox="0 0 10 6" fill="none" stroke="var(--ink-500)"
          strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"
          style={{ flexShrink: 0, transform: open ? 'rotate(180deg)' : 'rotate(0)', transition: 'transform 160ms' }}>
          <path d="M1 1l4 4 4-4"/>
        </svg>
      </button>

      {/* Dropdown panel */}
      {open && (
        <div style={{
          position: 'absolute', top: 'calc(100% + 4px)', left: 0, right: 0,
          background: 'var(--background)',
          border: '1px solid var(--ink-200)',
          borderRadius: 9,
          boxShadow: '0 8px 32px rgba(0,0,0,.12), 0 2px 8px rgba(0,0,0,.08)',
          zIndex: 50, overflow: 'hidden',
          animation: 'dropIn 120ms ease',
        }}>
          <style>{`@keyframes dropIn { from { opacity:0; transform:translateY(-4px); } to { opacity:1; transform:translateY(0); } }`}</style>
          <div style={{ maxHeight: 280, overflow: 'auto' }}>
            {groups.map((g, gi) => (
              <div key={g.group}>
                {/* Group header */}
                <div style={{
                  padding: '8px 12px 4px',
                  fontSize: 9, fontWeight: 700, letterSpacing: '0.1em',
                  textTransform: 'uppercase', color: 'var(--ink-400)',
                  fontFamily: 'var(--font-sans)',
                  borderTop: gi > 0 ? '1px solid var(--ink-100)' : 'none',
                  marginTop: gi > 0 ? 4 : 0,
                }}>{g.group}</div>

                {/* Font options */}
                {g.fonts.map(f => {
                  const selected = f.value === value;
                  return (
                    <button
                      key={f.value}
                      onClick={() => { onChange(f.value); setOpen(false); }}
                      style={{
                        width: '100%', padding: '7px 12px',
                        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                        gap: 10, border: 'none', cursor: 'pointer', textAlign: 'left',
                        background: selected ? 'var(--ink-900)' : 'transparent',
                        transition: 'background 80ms',
                      }}
                      onMouseEnter={e => { if (!selected) e.currentTarget.style.background = 'var(--ink-100)'; }}
                      onMouseLeave={e => { if (!selected) e.currentTarget.style.background = 'transparent'; }}
                    >
                      {/* Font label */}
                      <span style={{
                        fontFamily: 'var(--font-sans)', fontSize: 11, fontWeight: 600,
                        color: selected ? '#fff' : 'var(--ink-700)',
                        whiteSpace: 'nowrap', flex: 1,
                      }}>{f.label}</span>
                      {/* Check */}
                      {selected && (
                        <svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="#fff" strokeWidth="2" strokeLinecap="round">
                          <path d="M2 6l3 3 5-5"/>
                        </svg>
                      )}
                    </button>
                  );
                })}
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
};

const TextPanel = ({ selectedPanel, onAddTextBox }) => {
  const [font,        setFont]        = useState(MANGA_FONTS[0].fonts[0].value);
  const [size,        setSize]        = useState(16);
  const [bold,        setBold]        = useState(false);
  const [italic,      setItalic]      = useState(false);
  const [vertical,    setVertical]    = useState(false);
  const [align,       setAlign]       = useState('left');
  const [color,       setColor]       = useState('#1a1a1a');
  const [lineHeight,  setLineHeight]  = useState(1.6);
  const [letterSpace, setLetterSpace] = useState(0);



  const sans = 'var(--font-sans)';

  const sectionLabel = (text) => (
    <div style={{
      fontSize: 9, fontWeight: 600, letterSpacing: '0.08em', textTransform: 'uppercase',
      color: 'var(--ink-400)', fontFamily: sans, marginBottom: 6,
    }}>{text}</div>
  );

  const toggleBtn = (active, onClick, children, title) => (
    <button title={title} onClick={onClick} style={{
      width: 28, height: 28, borderRadius: 5, border: 'none', cursor: 'pointer',
      fontFamily: sans, fontSize: 12, fontWeight: 600,
      background: active ? 'var(--ink-900)' : 'var(--ink-100)',
      color: active ? 'var(--ink-0)' : 'var(--ink-600)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      transition: 'background var(--d-fast), color var(--d-fast)',
    }}>{children}</button>
  );

  const selectStyle = {
    width: '100%', fontFamily: sans, fontSize: 11, fontWeight: 500,
    color: 'var(--ink-900)', background: 'var(--ink-100)',
    border: '1px solid var(--ink-300)', borderRadius: 6,
    padding: '5px 24px 5px 8px', cursor: 'pointer', outline: 'none',
    appearance: 'none', WebkitAppearance: 'none',
    backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%23737373' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")`,
    backgroundRepeat: 'no-repeat', backgroundPosition: 'right 7px center',
  };

  const row = { display: 'flex', alignItems: 'center', gap: 6 };

  return (
    <div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden', minHeight: 0 }}>

      {/* Header */}
      <div style={{ padding: '12px 14px 10px', borderBottom: '1px solid var(--ink-200)', flexShrink: 0 }}>
        <div style={{
          fontSize: 10, fontWeight: 500, letterSpacing: '0.08em', textTransform: 'uppercase',
          color: 'var(--ink-400)', fontFamily: sans, marginBottom: 10,
        }}>テキスト · Text</div>

        {/* No panel selected hint */}
        {!selectedPanel && (
          <div style={{
            padding: '6px 10px', borderRadius: 6,
            background: 'var(--ink-50)', border: '1px solid var(--ink-200)',
            fontFamily: sans, fontSize: 11, color: 'var(--ink-400)',
            marginBottom: 8, textAlign: 'center',
          }}>
            コマを選択してください
          </div>
        )}

        {/* Add text box button */}
        <button
          disabled={!selectedPanel}
          onClick={() => onAddTextBox && onAddTextBox({ font, size, bold, italic, vertical, align, color, lineHeight, letterSpace })}
          style={{
            width: '100%', height: 36, borderRadius: 7,
            border: `1.5px dashed ${selectedPanel ? 'var(--ink-400)' : 'var(--ink-200)'}`,
            background: 'transparent',
            color: selectedPanel ? 'var(--ink-700)' : 'var(--ink-300)',
            fontFamily: sans, fontSize: 12, fontWeight: 600,
            display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 7,
            cursor: selectedPanel ? 'pointer' : 'not-allowed',
            opacity: selectedPanel ? 1 : 0.5,
            transition: 'background var(--d-fast), border-color var(--d-fast), color var(--d-fast)',
          }}
          onMouseEnter={e => {
            if (!selectedPanel) return;
            e.currentTarget.style.background = 'var(--ink-900)';
            e.currentTarget.style.borderColor = 'var(--ink-900)';
            e.currentTarget.style.color = '#fff';
          }}
          onMouseLeave={e => {
            if (!selectedPanel) return;
            e.currentTarget.style.background = 'transparent';
            e.currentTarget.style.borderColor = 'var(--ink-400)';
            e.currentTarget.style.color = 'var(--ink-700)';
          }}
        >
          <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round">
            <rect x="1" y="3" width="12" height="8" rx="1.5"/>
            <path d="M4 6h6M4 8.5h4"/>
          </svg>
          テキストボックスを追加
        </button>
      </div>

      {/* Body */}
      <div style={{ flex: 1, overflow: 'auto', padding: '14px 14px 20px', display: 'flex', flexDirection: 'column', gap: 16 }}>

        {/* フォント */}
        <div>
          {sectionLabel('フォント · Font')}
          <FontSelect value={font} onChange={setFont} groups={MANGA_FONTS} />
          {/* Preview */}
          <div style={{
            marginTop: 6, padding: '6px 8px', background: 'var(--ink-50)',
            border: '1px solid var(--ink-200)', borderRadius: 5,
            fontFamily: font, fontSize: 13, color: color,
            letterSpacing: `${letterSpace}em`, lineHeight,
            fontWeight: bold ? 700 : 400, fontStyle: italic ? 'italic' : 'normal',
            textAlign: align,
          }}>あいうえおABCabc</div>
        </div>

        {/* サイズ */}
        <div>
          {sectionLabel('サイズ · Size')}
          <div style={row}>
            <input
              type="number" min={6} max={120} value={size}
              onChange={e => setSize(Number(e.target.value))}
              style={{
                width: 56, padding: '4px 6px', borderRadius: 5,
                border: '1px solid var(--ink-300)', background: 'var(--ink-100)',
                fontFamily: sans, fontSize: 12, color: 'var(--ink-900)',
                outline: 'none', textAlign: 'right',
              }}
            />
            <span style={{ fontFamily: sans, fontSize: 10, color: 'var(--ink-500)' }}>px</span>
            <input
              type="range" min={6} max={72} step={1} value={size}
              onChange={e => setSize(Number(e.target.value))}
              style={{ flex: 1, accentColor: 'var(--ink-900)', cursor: 'pointer' }}
            />
          </div>
        </div>

        {/* スタイル */}
        <div>
          {sectionLabel('スタイル · Style')}
          <div style={{ ...row, gap: 5 }}>
            {toggleBtn(bold,   () => setBold(!bold),     'B', '太字')}
            {toggleBtn(italic, () => setItalic(!italic), 'I', 'イタリック')}
            <div style={{ width: 1, height: 20, background: 'var(--ink-200)', margin: '0 2px' }}/>
            {/* 縦書きトグル */}
            <button
              title={vertical ? '縦書き ON' : '縦書き OFF'}
              onClick={() => setVertical(!vertical)}
              style={{
                display: 'flex', alignItems: 'center', gap: 5,
                padding: '4px 8px', borderRadius: 5, border: 'none', cursor: 'pointer',
                fontFamily: sans, fontSize: 10, fontWeight: 600,
                background: vertical ? 'var(--ink-900)' : 'var(--ink-100)',
                color: vertical ? 'var(--ink-0)' : 'var(--ink-500)',
                transition: 'background var(--d-fast), color var(--d-fast)',
              }}
            >
              <svg width="12" height="12" viewBox="0 0 14 14" fill="currentColor">
                <rect x="11" y="0" width="2" height="14" rx="1"/>
                <rect x="6"  y="0" width="2" height="10" rx="1"/>
                <rect x="1"  y="0" width="2" height="12" rx="1"/>
              </svg>
              縦書き
              {/* pill indicator */}
              <span style={{
                width: 24, height: 13, borderRadius: 99,
                background: vertical ? 'rgba(255,255,255,.25)' : 'var(--ink-300)',
                position: 'relative', flexShrink: 0,
                transition: 'background var(--d-fast)',
              }}>
                <span style={{
                  position: 'absolute', top: 2, left: vertical ? 13 : 2,
                  width: 9, height: 9, borderRadius: '50%',
                  background: vertical ? '#fff' : 'var(--ink-500)',
                  transition: 'left var(--d-fast), background var(--d-fast)',
                }}/>
              </span>
            </button>
          </div>
        </div>

        {/* 揃え */}
        <div>
          {sectionLabel('揃え · Align')}
          <div style={{ ...row, gap: 5 }}>
            {[
              { id: 'left',    icon: '⬛⬛⬛\n⬛⬛□\n⬛⬛□', svg: 'left'    },
              { id: 'center',  icon: 'center',  svg: 'center'  },
              { id: 'right',   icon: 'right',   svg: 'right'   },
              { id: 'justify', icon: 'justify', svg: 'justify' },
            ].map(a => (
              <button key={a.id} title={a.id} onClick={() => setAlign(a.id)} style={{
                width: 28, height: 28, borderRadius: 5, border: 'none', cursor: 'pointer',
                background: align === a.id ? 'var(--ink-900)' : 'var(--ink-100)',
                color: align === a.id ? 'var(--ink-0)' : 'var(--ink-600)',
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                transition: 'background var(--d-fast), color var(--d-fast)',
              }}>
                <svg width="14" height="12" viewBox="0 0 14 12" fill="currentColor">
                  {a.id === 'left'    && <><rect x="0" y="0" width="14" height="2" rx="1"/><rect x="0" y="5" width="9"  height="2" rx="1"/><rect x="0" y="10" width="11" height="2" rx="1"/></>}
                  {a.id === 'center'  && <><rect x="0" y="0" width="14" height="2" rx="1"/><rect x="3" y="5" width="8"  height="2" rx="1"/><rect x="1" y="10" width="12" height="2" rx="1"/></>}
                  {a.id === 'right'   && <><rect x="0" y="0" width="14" height="2" rx="1"/><rect x="5" y="5" width="9"  height="2" rx="1"/><rect x="3" y="10" width="11" height="2" rx="1"/></>}
                  {a.id === 'justify' && <><rect x="0" y="0" width="14" height="2" rx="1"/><rect x="0" y="5" width="14" height="2" rx="1"/><rect x="0" y="10" width="14" height="2" rx="1"/></>}
                </svg>
              </button>
            ))}
          </div>
        </div>

        {/* カラー */}
        <div>
          {sectionLabel('カラー · Color')}
          <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            {/* Color picker trigger */}
            <div style={{ position: 'relative', flexShrink: 0 }}>
              <div style={{
                width: 36, height: 36, borderRadius: 8,
                background: color,
                border: '1px solid var(--ink-200)',
                cursor: 'pointer',
                boxShadow: '0 1px 4px rgba(0,0,0,.10)',
                transition: 'border-color 120ms',
              }} />
              <input
                type="color"
                value={color}
                onChange={e => setColor(e.target.value)}
                style={{
                  position: 'absolute', inset: 0,
                  opacity: 0, cursor: 'pointer', width: '100%', height: '100%',
                }}
              />
            </div>
            {/* Hex value */}
            <div style={{
              flex: 1, display: 'flex', alignItems: 'center',
              gap: 6, padding: '0 8px',
              height: 36, borderRadius: 7,
              border: '1px solid var(--ink-200)',
              background: 'var(--ink-50)',
            }}>
              <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--ink-400)' }}>#</span>
              <input
                type="text"
                value={color.replace('#', '')}
                onChange={e => {
                  const v = e.target.value.replace(/[^0-9a-fA-F]/g, '').slice(0, 6);
                  if (v.length === 6) setColor('#' + v);
                }}
                style={{
                  flex: 1, border: 'none', background: 'transparent',
                  fontFamily: 'var(--font-mono)', fontSize: 12, fontWeight: 500,
                  color: 'var(--ink-900)', outline: 'none', width: 0,
                }}
              />
            </div>

          </div>
        </div>

        {/* 行間 */}
        <div>
          {sectionLabel('行間 · Line Height')}
          <div style={row}>
            <input
              type="range" min={1} max={3} step={0.1} value={lineHeight}
              onChange={e => setLineHeight(Number(e.target.value))}
              style={{ flex: 1, accentColor: 'var(--ink-900)', cursor: 'pointer' }}
            />
            <span style={{ fontFamily: sans, fontSize: 11, color: 'var(--ink-600)', minWidth: 28, textAlign: 'right' }}>
              {lineHeight.toFixed(1)}
            </span>
          </div>
        </div>

        {/* 字間 */}
        <div>
          {sectionLabel('字間 · Letter Spacing')}
          <div style={row}>
            <input
              type="range" min={-0.1} max={0.5} step={0.01} value={letterSpace}
              onChange={e => setLetterSpace(Number(e.target.value))}
              style={{ flex: 1, accentColor: 'var(--ink-900)', cursor: 'pointer' }}
            />
            <span style={{ fontFamily: sans, fontSize: 11, color: 'var(--ink-600)', minWidth: 36, textAlign: 'right' }}>
              {letterSpace >= 0 ? '+' : ''}{letterSpace.toFixed(2)}
            </span>
          </div>
        </div>

      </div>
    </div>
  );
};

// ----- KOMAWARI PANEL -----
const KomawariPanel = () => {
  const [lineWidth,   setLineWidth]   = useState(3);
  const [gutterH,     setGutterH]     = useState(4);
  const [gutterV,     setGutterV]     = useState(4);

  const sans = 'var(--font-sans)';

  const sectionLabel = (text) => (
    <div style={{
      fontSize: 9, fontWeight: 600, letterSpacing: '0.08em', textTransform: 'uppercase',
      color: 'var(--ink-400)', fontFamily: sans, marginBottom: 6,
    }}>{text}</div>
  );

  const sliderRow = (value, setValue, min, max, step, unit) => (
    <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
      <input type="range" min={min} max={max} step={step} value={value}
        onChange={e => setValue(Number(e.target.value))}
        style={{ flex: 1, accentColor: 'var(--ink-900)', cursor: 'pointer' }}
      />
      <div style={{ display: 'flex', alignItems: 'center', gap: 3, flexShrink: 0 }}>
        <input type="number" min={min} max={max} step={step} value={value}
          onChange={e => setValue(Math.min(max, Math.max(min, Number(e.target.value))))}
          style={{
            width: 44, padding: '3px 5px', borderRadius: 5, textAlign: 'right',
            border: '1px solid var(--ink-300)', background: 'var(--ink-100)',
            fontFamily: sans, fontSize: 11, color: 'var(--ink-900)', outline: 'none',
          }}
        />
        <span style={{ fontFamily: sans, fontSize: 10, color: 'var(--ink-500)', minWidth: 16 }}>{unit}</span>
      </div>
    </div>
  );

  // Preview: a 2×2 grid showing gutters visually
  const pw = 180, ph = 120;
  const glH = Math.round(gutterH * 0.6);   // scale mm → preview px (rough)
  const glV = Math.round(gutterV * 0.6);
  const lw  = Math.max(1, Math.round(lineWidth * 0.5));

  return (
    <div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden', minHeight: 0 }}>

      {/* Header */}
      <div style={{ padding: '12px 14px 10px', borderBottom: '1px solid var(--ink-200)', flexShrink: 0 }}>
        <div style={{
          fontSize: 10, fontWeight: 500, letterSpacing: '0.08em', textTransform: 'uppercase',
          color: 'var(--ink-400)', fontFamily: sans,
        }}>コマ割り · Panel Division</div>
      </div>

      {/* Body */}
      <div style={{ flex: 1, overflow: 'auto', padding: '16px 14px 20px', display: 'flex', flexDirection: 'column', gap: 18 }}>

        {/* Preview */}
        <div>
          {sectionLabel('プレビュー · Preview')}
          <div style={{
            background: 'var(--ink-50)', border: '1px solid var(--ink-200)',
            borderRadius: 5, padding: 10, display: 'flex', justifyContent: 'center',
          }}>
            <svg width={pw} height={ph} viewBox={`0 0 ${pw} ${ph}`} style={{ display: 'block' }}>
              {/* Background */}
              <rect width={pw} height={ph} fill="#fafafa" stroke="#ccc" strokeWidth="1"/>
              {/* Left-right gutter (vertical line) */}
              <rect
                x={(pw - glH) / 2} y={0}
                width={glH} height={ph}
                fill="var(--ink-200)"
              />
              {/* Top-bottom gutter (horizontal line) */}
              <rect
                x={0} y={(ph - glV) / 2}
                width={pw} height={glV}
                fill="var(--ink-200)"
              />
              {/* Division lines */}
              <line x1={pw/2} y1={0} x2={pw/2} y2={ph} stroke="#1a1a1a" strokeWidth={lw}/>
              <line x1={0} y1={ph/2} x2={pw} y2={ph/2} stroke="#1a1a1a" strokeWidth={lw}/>
              {/* Gutter labels */}
              <text x={pw/2 + glH/2 + 3} y={ph/2 - 4} fontSize="8" fill="#999" fontFamily="monospace">←{gutterH}mm→</text>
              <text x={4} y={ph/2 + glV/2 + 9} fontSize="8" fill="#999" fontFamily="monospace">↕{gutterV}mm</text>
            </svg>
          </div>
        </div>

        {/* 線幅 */}
        <div>
          {sectionLabel('線幅 · Line Width')}
          {sliderRow(lineWidth, setLineWidth, 1, 50, 1, 'px')}
        </div>

        {/* 左右余白 */}
        <div>
          {sectionLabel('左右余白 · Horizontal Gutter')}
          {sliderRow(gutterH, setGutterH, 1, 100, 1, 'mm')}
        </div>

        {/* 上下余白 */}
        <div>
          {sectionLabel('上下余白 · Vertical Gutter')}
          {sliderRow(gutterV, setGutterV, 1, 100, 1, 'mm')}
        </div>

      </div>
    </div>
  );
};

// ----- CHAR GEN PANEL -----
// キャラ生成ツール: キャラ選択 → ポーズ選択 → 生成
const POSE_CATEGORIES = {
  '立ちポーズ': [
    { id: 'st01', label: '直立',       hint: '正面・直立' },
    { id: 'st02', label: '腕組み',     hint: '腕を組んで立つ' },
    { id: 'st03', label: '片手腰',     hint: '片手を腰に当てる' },
    { id: 'st04', label: '振り返り',   hint: '肩越しに振り返る' },
    { id: 'st05', label: '背中向き',   hint: '後ろ向きで立つ' },
    { id: 'st06', label: 'Tポーズ',    hint: '腕を水平に広げる' },
    { id: 'st07', label: '歩き',       hint: '歩いている途中' },
    { id: 'st08', label: '片膝立て',   hint: '片膝を軽く上げる' },
  ],
  '戦闘・アクション': [
    { id: 'ac01', label: '刀を構える', hint: '正眼の構え' },
    { id: 'ac02', label: '抜刀',       hint: '刀を抜く瞬間' },
    { id: 'ac03', label: '斬り込み',   hint: '前方へ跳び込む' },
    { id: 'ac04', label: '防御',       hint: '刀で受け止める' },
    { id: 'ac05', label: '回し蹴り',   hint: '高い回し蹴り' },
    { id: 'ac06', label: '着地',       hint: '高所から着地' },
    { id: 'ac07', label: '走り',       hint: '全力疾走' },
    { id: 'ac08', label: '倒れ込み',   hint: 'ダメージを受けて倒れる' },
    { id: 'ac09', label: '気を溜める', hint: '両手を広げてチャージ' },
    { id: 'ac10', label: '投擲',       hint: '物を投げつける' },
  ],
  '感情表現': [
    { id: 'em01', label: '驚き',       hint: '後ずさりして驚く' },
    { id: 'em02', label: '怒り',       hint: '前のめりで怒鳴る' },
    { id: 'em03', label: '喜び',       hint: '両手を上げて喜ぶ' },
    { id: 'em04', label: '悲しみ',     hint: '膝をついて泣く' },
    { id: 'em05', label: '困惑',       hint: '頭を掻いて困る' },
    { id: 'em06', label: '笑い',       hint: '口を押さえて笑う' },
    { id: 'em07', label: '睨み',       hint: '上目遣いで睨む' },
    { id: 'em08', label: '照れ',       hint: '顔を背けて照れる' },
  ],
  '座り・屈み': [
    { id: 'si01', label: '正座',       hint: '床に正座する' },
    { id: 'si02', label: 'あぐら',     hint: 'あぐらをかく' },
    { id: 'si03', label: '椅子座り',   hint: '椅子に座る' },
    { id: 'si04', label: 'しゃがみ',   hint: 'しゃがんで何かを見る' },
    { id: 'si05', label: '体育座り',   hint: '膝を抱えて座る' },
    { id: 'si06', label: '寝転び',     hint: '横になる' },
    { id: 'si07', label: '膝つき',     hint: '片膝をついて礼をする' },
    { id: 'si08', label: '前屈み',     hint: '机や地面に前屈み' },
  ],
  '会話・仕草': [
    { id: 'ge01', label: '指差し',     hint: '前方を指差す' },
    { id: 'ge02', label: '手を差し伸べ', hint: '手を差し出す' },
    { id: 'ge03', label: '腕を伸ばす', hint: '両腕を前に伸ばす' },
    { id: 'ge04', label: '敬礼',       hint: '軍式敬礼' },
    { id: 'ge05', label: 'おじぎ',     hint: '深くお辞儀をする' },
    { id: 'ge06', label: '手を振る',   hint: '片手を振る' },
    { id: 'ge07', label: '考え込む',   hint: '顎に手を当てて考える' },
    { id: 'ge08', label: '肩をすくめ', hint: '両肩をすくめる' },
  ],
};

// SVG sketches for poses (simplified stick-figure thumbnails)
const PoseSVG = ({ poseId }) => {
  // Minimal silhouettes per category prefix
  const prefix = poseId.slice(0, 2);
  const stColor = '#1a1a1a';
  const figures = {
    st: ( // standing
      <g stroke={stColor} strokeWidth="1.5" strokeLinecap="round" fill="none">
        <circle cx="30" cy="14" r="6" fill={stColor} opacity=".12" stroke={stColor}/>
        <line x1="30" y1="20" x2="30" y2="44"/>
        <line x1="18" y1="28" x2="42" y2="28"/>
        <line x1="30" y1="44" x2="20" y2="60"/>
        <line x1="30" y1="44" x2="40" y2="60"/>
      </g>
    ),
    ac: ( // action
      <g stroke={stColor} strokeWidth="1.5" strokeLinecap="round" fill="none">
        <circle cx="30" cy="12" r="6" fill={stColor} opacity=".12" stroke={stColor}/>
        <line x1="30" y1="18" x2="28" y2="40"/>
        <line x1="16" y1="24" x2="36" y2="32"/>
        <line x1="36" y1="32" x2="46" y2="22"/>
        <line x1="28" y1="40" x2="16" y2="56"/>
        <line x1="28" y1="40" x2="38" y2="58"/>
      </g>
    ),
    em: ( // emotion
      <g stroke={stColor} strokeWidth="1.5" strokeLinecap="round" fill="none">
        <circle cx="30" cy="14" r="6" fill={stColor} opacity=".12" stroke={stColor}/>
        <line x1="30" y1="20" x2="30" y2="42"/>
        <line x1="16" y1="26" x2="30" y2="32"/>
        <line x1="30" y1="32" x2="44" y2="26"/>
        <line x1="30" y1="42" x2="22" y2="60"/>
        <line x1="30" y1="42" x2="38" y2="60"/>
      </g>
    ),
    si: ( // sitting
      <g stroke={stColor} strokeWidth="1.5" strokeLinecap="round" fill="none">
        <circle cx="30" cy="14" r="6" fill={stColor} opacity=".12" stroke={stColor}/>
        <line x1="30" y1="20" x2="30" y2="38"/>
        <line x1="18" y1="26" x2="42" y2="26"/>
        <line x1="30" y1="38" x2="16" y2="50"/>
        <line x1="16" y1="50" x2="16" y2="62"/>
        <line x1="30" y1="38" x2="44" y2="50"/>
        <line x1="44" y1="50" x2="44" y2="62"/>
      </g>
    ),
    ge: ( // gesture
      <g stroke={stColor} strokeWidth="1.5" strokeLinecap="round" fill="none">
        <circle cx="30" cy="14" r="6" fill={stColor} opacity=".12" stroke={stColor}/>
        <line x1="30" y1="20" x2="30" y2="42"/>
        <line x1="18" y1="26" x2="30" y2="32"/>
        <line x1="30" y1="32" x2="48" y2="20"/>
        <line x1="30" y1="42" x2="22" y2="60"/>
        <line x1="30" y1="42" x2="38" y2="60"/>
      </g>
    ),
  };
  return (
    <svg viewBox="0 0 60 72" style={{ width: '100%', height: '100%' }}>
      <rect width="60" height="72" fill="#fafafa"/>
      {figures[prefix] || figures.st}
    </svg>
  );
};

// ============ CharGenEditModal ============
// 編集モーダル: 生成結果を採用前にブラシで修正できる
const CharGenEditModal = ({ result, onClose, onAccept, sans }) => {
  const canvasRef = useRef(null);
  const [brushMode, setBrushMode] = useState(false);
  const [isDrawing, setIsDrawing] = useState(false);
  const [history, setHistory] = useState([]);
  const [historyIdx, setHistoryIdx] = useState(-1);

  // ブラシ: Holonica黄 + 透明
  const BRUSH_COLOR = '#F4D03F';
  const BRUSH_OPACITY = 0.45;
  const BRUSH_SIZE = 24;

  // Canvas初期化
  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    // プレースホルダー画像を描画
    ctx.fillStyle = '#f5f4f0';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    // キャラシルエット
    ctx.fillStyle = '#e0ddd5';
    ctx.beginPath(); ctx.ellipse(200, 100, 52, 62, 0, 0, Math.PI * 2); ctx.fill();
    ctx.fillRect(148, 158, 104, 130);
    ctx.fillRect(112, 288, 54, 110);
    ctx.fillRect(234, 288, 54, 110);
    // テキスト
    ctx.fillStyle = '#aaa';
    ctx.font = '11px monospace';
    ctx.textAlign = 'center';
    ctx.fillText(`生成結果 · ${result.char}`, 200, 420);
    // 初期historyを保存
    const snap = canvas.toDataURL();
    setHistory([snap]);
    setHistoryIdx(0);
  }, []);

  const getPos = (e, canvas) => {
    const rect = canvas.getBoundingClientRect();
    const scaleX = canvas.width / rect.width;
    const scaleY = canvas.height / rect.height;
    if (e.touches) {
      return {
        x: (e.touches[0].clientX - rect.left) * scaleX,
        y: (e.touches[0].clientY - rect.top) * scaleY,
      };
    }
    return {
      x: (e.clientX - rect.left) * scaleX,
      y: (e.clientY - rect.top) * scaleY,
    };
  };

  const startDraw = (e) => {
    if (!brushMode) return;
    e.preventDefault();
    const canvas = canvasRef.current;
    const ctx = canvas.getContext('2d');
    const { x, y } = getPos(e, canvas);
    ctx.beginPath();
    ctx.moveTo(x, y);
    setIsDrawing(true);
  };

  const draw = (e) => {
    if (!brushMode || !isDrawing) return;
    e.preventDefault();
    const canvas = canvasRef.current;
    const ctx = canvas.getContext('2d');
    const { x, y } = getPos(e, canvas);
    ctx.lineTo(x, y);
    ctx.strokeStyle = BRUSH_COLOR;
    ctx.globalAlpha = BRUSH_OPACITY;
    ctx.lineWidth = BRUSH_SIZE;
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';
    ctx.stroke();
    ctx.globalAlpha = 1;
  };

  const endDraw = () => {
    if (!isDrawing) return;
    setIsDrawing(false);
    // スナップショットをhistoryに保存
    const canvas = canvasRef.current;
    const snap = canvas.toDataURL();
    setHistory(h => {
      const next = h.slice(0, historyIdx + 1);
      next.push(snap);
      setHistoryIdx(next.length - 1);
      return next;
    });
  };

  const undo = () => {
    if (historyIdx <= 0) return;
    const newIdx = historyIdx - 1;
    const canvas = canvasRef.current;
    const ctx = canvas.getContext('2d');
    const img = new Image();
    img.onload = () => { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0); };
    img.src = history[newIdx];
    setHistoryIdx(newIdx);
  };

  const redo = () => {
    if (historyIdx >= history.length - 1) return;
    const newIdx = historyIdx + 1;
    const canvas = canvasRef.current;
    const ctx = canvas.getContext('2d');
    const img = new Image();
    img.onload = () => { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0); };
    img.src = history[newIdx];
    setHistoryIdx(newIdx);
  };

  const canUndo = historyIdx > 0;
  const canRedo = historyIdx < history.length - 1;

  return (
    <div style={{
      position: 'fixed', inset: 0, zIndex: 110,
      background: 'rgba(0,0,0,.6)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      backdropFilter: 'blur(6px)',
    }}>
      <div
        onClick={e => e.stopPropagation()}
        style={{
          background: 'var(--panel-bg)',
          border: '1px solid var(--panel-border)',
          borderRadius: 14,
          width: 400, maxWidth: '92vw',
          overflow: 'hidden',
          boxShadow: '0 32px 80px rgba(0,0,0,.3)',
          display: 'flex', flexDirection: 'column',
        }}
      >
        {/* Header */}
        <div style={{
          padding: '14px 16px',
          borderBottom: '1px solid var(--panel-border)',
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        }}>
          <div>
            <div style={{ fontFamily: sans, fontSize: 13, fontWeight: 700, color: 'var(--ink-900)' }}>
              キャラを編集する
            </div>
            <div style={{
              fontFamily: 'var(--font-display)', fontStyle: 'italic',
              fontSize: 11, color: 'var(--ink-400)', marginTop: 2,
            }}>
              Edit before adopting
            </div>
          </div>
          <button onClick={onClose} style={{
            width: 30, height: 30, borderRadius: 7, border: 'none',
            background: 'var(--ink-100)', cursor: 'pointer',
            display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--ink-600)',
          }}>
            <Icons.X size={14} />
          </button>
        </div>

        {/* Canvas */}
        <div style={{
          position: 'relative',
          background: '#f5f4f0',
          lineHeight: 0,
        }}>
          <canvas
            ref={canvasRef}
            width={400}
            height={440}
            onMouseDown={startDraw}
            onMouseMove={draw}
            onMouseUp={endDraw}
            onMouseLeave={endDraw}
            onTouchStart={startDraw}
            onTouchMove={draw}
            onTouchEnd={endDraw}
            style={{
              display: 'block',
              width: '100%',
              cursor: brushMode ? 'crosshair' : 'default',
              touchAction: brushMode ? 'none' : 'auto',
            }}
          />
          {/* ブラシモード中のインジケーター */}
          {brushMode && (
            <div style={{
              position: 'absolute', top: 10, left: 10,
              background: 'var(--accent)', color: '#1a1a1a',
              padding: '3px 10px', borderRadius: 100,
              fontSize: 10, fontWeight: 600,
              display: 'flex', alignItems: 'center', gap: 5,
              pointerEvents: 'none',
              letterSpacing: '0.05em',
            }}>
              <span style={{
                width: 8, height: 8, borderRadius: '50%',
                background: '#1a1a1a', opacity: 0.5,
              }} />
              ブラシ編集中
            </div>
          )}
        </div>

        {/* Footer */}
        <div style={{
          padding: '12px 14px',
          borderTop: '1px solid var(--panel-border)',
          display: 'flex', alignItems: 'center', gap: 8,
        }}>
          {!brushMode ? (
            <>
              {/* 選択するボタン → ブラシモードへ */}
              <button
                onClick={() => setBrushMode(true)}
                style={{
                  height: 38, padding: '0 16px', borderRadius: 8,
                  border: '1.5px solid var(--ink-300)', cursor: 'pointer',
                  background: 'transparent', color: 'var(--ink-700)',
                  fontFamily: sans, fontSize: 12, fontWeight: 600,
                  display: 'flex', alignItems: 'center', gap: 6,
                  transition: 'border-color 120ms, background 120ms',
                }}
                onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--ink-600)'; e.currentTarget.style.background = 'var(--ink-50)'; }}
                onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--ink-300)'; e.currentTarget.style.background = 'transparent'; }}
              >
                <Icons.Pen size={13} /> 選択する
              </button>
              <div style={{ flex: 1 }} />
              <button
                onClick={onAccept}
                style={{
                  height: 38, padding: '0 18px', borderRadius: 8,
                  border: 'none', cursor: 'pointer',
                  background: 'var(--ink-900)', color: '#fff',
                  fontFamily: sans, fontSize: 12, fontWeight: 600,
                  display: 'flex', alignItems: 'center', gap: 6,
                }}
              >
                <Icons.Pen size={13} /> 編集する
              </button>
            </>
          ) : (
            <>
              {/* ブラシモード中: Undo / Redo / キャンセル */}
              <button
                onClick={undo}
                disabled={!canUndo}
                style={{
                  height: 36, padding: '0 12px', borderRadius: 7,
                  border: '1.5px solid var(--ink-200)', cursor: canUndo ? 'pointer' : 'not-allowed',
                  background: canUndo ? 'var(--ink-50)' : 'transparent',
                  color: canUndo ? 'var(--ink-900)' : 'var(--ink-300)',
                  fontFamily: sans, fontSize: 11, fontWeight: 600,
                  display: 'flex', alignItems: 'center', gap: 5,
                  transition: 'background 100ms, color 100ms',
                }}
              >
                <Icons.Undo size={13} /> Undo
              </button>
              <button
                onClick={redo}
                disabled={!canRedo}
                style={{
                  height: 36, padding: '0 12px', borderRadius: 7,
                  border: '1.5px solid var(--ink-200)', cursor: canRedo ? 'pointer' : 'not-allowed',
                  background: canRedo ? 'var(--ink-50)' : 'transparent',
                  color: canRedo ? 'var(--ink-900)' : 'var(--ink-300)',
                  fontFamily: sans, fontSize: 11, fontWeight: 600,
                  display: 'flex', alignItems: 'center', gap: 5,
                  transition: 'background 100ms, color 100ms',
                }}
              >
                <Icons.Redo size={13} /> Redo
              </button>
              <button
                onClick={() => setBrushMode(false)}
                style={{
                  height: 36, padding: '0 12px', borderRadius: 7,
                  border: '1.5px solid var(--ink-300)', cursor: 'pointer',
                  background: 'transparent', color: 'var(--ink-600)',
                  fontFamily: sans, fontSize: 11, fontWeight: 600,
                  display: 'flex', alignItems: 'center', gap: 5,
                  transition: 'border-color 120ms, background 120ms',
                }}
                onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--ink-500)'; e.currentTarget.style.background = 'var(--ink-50)'; }}
                onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--ink-300)'; e.currentTarget.style.background = 'transparent'; }}
              >
                キャンセル
              </button>
              <div style={{ flex: 1 }} />
              <button
                onClick={onAccept}
                style={{
                  height: 36, padding: '0 18px', borderRadius: 7,
                  border: 'none', cursor: 'pointer',
                  background: 'var(--ink-900)', color: '#fff',
                  fontFamily: sans, fontSize: 12, fontWeight: 600,
                  display: 'flex', alignItems: 'center', gap: 6,
                }}
              >
                <Icons.Pen size={13} /> 編集する
              </button>
            </>
          )}
        </div>
      </div>
    </div>
  );
};

const CharGenPanel = () => {
  const { CHARACTERS } = window.DATA;
  const sans = 'var(--font-sans)';

  const [selectedCharId, setSelectedCharId] = useState(CHARACTERS[0].id);
  const [poseMode, setPoseMode]             = useState('template'); // 'template' | 'prompt'
  const [poseCategory, setPoseCategory]     = useState(Object.keys(POSE_CATEGORIES)[0]);
  const [selectedPoseId, setSelectedPoseId] = useState(null);
  const [posePrompt, setPosePrompt]         = useState('');
  const [generateCount, setGenerateCount]   = useState(1); // 1-3
  const [generating, setGenerating]         = useState(false);
  const [progress, setProgress]             = useState(0);
  const [results, setResults]               = useState([]); // Generated images
  const [selectedResultIdx, setSelectedResultIdx] = useState(null);
  const [modalResultIdx, setModalResultIdx] = useState(null); // Preview modal
  const [editModalResultIdx, setEditModalResultIdx] = useState(null); // Edit modal

  const poses = POSE_CATEGORIES[poseCategory];
  const categories = Object.keys(POSE_CATEGORIES);

  // Fake generation progress
  useEffect(() => {
    if (!generating) return;
    setProgress(0);
    setResults([]); // Clear previous results
    setSelectedResultIdx(null);
    
    const t = setInterval(() => {
      setProgress(p => {
        const next = p + Math.random() * 8 + 3;
        if (next >= 100) { 
          clearInterval(t); 
          // Simulate generating images
          const fakeResults = Array.from({ length: generateCount }, (_, i) => ({
            id: `result-${Date.now()}-${i}`,
            char: selectedChar.name.split(' / ')[0],
            pose: poseMode === 'template' ? selectedPose?.label : posePrompt.slice(0, 20),
          }));
          setResults(fakeResults);
          setSelectedResultIdx(0);
          setGenerating(false);
          return 100; 
        }
        return next;
      });
    }, 100);
    return () => clearInterval(t);
  }, [generating, generateCount, selectedChar, poseMode, selectedPose, posePrompt]);

  const selectedChar = CHARACTERS.find(c => c.id === selectedCharId);
  const selectedPose = selectedPoseId ? poses.find(p => p.id === selectedPoseId) : null;

  const canGenerate = !!selectedCharId && (
    poseMode === 'template' ? !!selectedPoseId : posePrompt.trim().length > 0
  );

  const selectStyle = {
    width: '100%',
    fontFamily: sans, fontSize: 12, fontWeight: 500,
    color: 'var(--ink-900)', background: 'var(--ink-50)',
    border: '1px solid var(--ink-300)', borderRadius: 6,
    padding: '6px 28px 6px 10px',
    cursor: 'pointer', outline: 'none',
    appearance: 'none', WebkitAppearance: 'none',
    backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%23737373' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")`,
    backgroundRepeat: 'no-repeat', backgroundPosition: 'right 9px center',
  };

  const sectionLabel = (text, sub) => (
    <div style={{
      display: 'flex', alignItems: 'baseline', gap: 6,
      marginBottom: 10,
    }}>
      <span style={{ fontFamily: sans, fontSize: 11, fontWeight: 600, color: 'var(--ink-900)' }}>{text}</span>
      {sub && <span style={{ fontFamily: sans, fontSize: 10, color: 'var(--ink-400)' }}>{sub}</span>}
    </div>
  );

  return (
    <div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden', minHeight: 0, fontFamily: sans }}>

      {/* Header */}
      <div style={{ padding: '12px 14px 10px', borderBottom: '1px solid var(--ink-200)', flexShrink: 0 }}>
        <div style={{ fontSize: 10, fontWeight: 500, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--ink-400)', fontFamily: sans }}>
          キャラ生成 · Character
        </div>
      </div>

      {/* Body — scrollable */}
      <div style={{ flex: 1, overflow: 'auto', display: 'flex', flexDirection: 'column' }}>

        {/* ① キャラクター選択 */}
        <div style={{ padding: '14px 14px 12px', borderBottom: '1px solid var(--ink-100)', flexShrink: 0 }}>
          {sectionLabel('キャラクター', 'Character')}
          <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            {CHARACTERS.map(char => {
              const sel = selectedCharId === char.id;
              return (
                <button key={char.id} onClick={() => setSelectedCharId(char.id)} style={{
                  display: 'flex', alignItems: 'center', gap: 10,
                  padding: '7px 10px', borderRadius: 8,
                  border: `1.5px solid ${sel ? 'var(--ink-900)' : 'var(--ink-200)'}`,
                  background: sel ? 'var(--ink-50)' : 'transparent',
                  cursor: 'pointer', textAlign: 'left',
                  transition: 'border-color 120ms, background 120ms',
                }}>
                  {/* Thumbnail */}
                  <div style={{
                    width: 38, height: 38, flexShrink: 0, borderRadius: 4,
                    background: char.painting
                      ? `center/cover no-repeat url(assets/paintings/${char.painting}.png), var(--ink-100)`
                      : 'var(--ink-100)',
                    border: '1px solid var(--ink-200)',
                    display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--ink-400)',
                  }}>
                    {!char.painting && <Icons.User size={16} />}
                  </div>
                  {/* Info */}
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontFamily: sans, fontWeight: 600, fontSize: 12, color: 'var(--ink-900)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                      {char.name}
                    </div>
                    <div style={{ fontFamily: sans, fontSize: 10, color: 'var(--ink-500)', marginTop: 1 }}>
                      {char.role}
                    </div>
                  </div>
                  {/* Selected indicator */}
                  {sel && (
                    <div style={{ width: 6, height: 6, borderRadius: '50%', background: 'var(--ink-900)', flexShrink: 0 }} />
                  )}
                </button>
              );
            })}
          </div>
        </div>

        {/* ② ポーズ選択 */}
        <div style={{ padding: '14px 14px 12px', borderBottom: '1px solid var(--ink-100)', flexShrink: 0 }}>

          {/* Section label + tabs */}
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12 }}>
            <div style={{ display: 'flex', alignItems: 'baseline', gap: 6 }}>
              <span style={{ fontFamily: sans, fontSize: 11, fontWeight: 600, color: 'var(--ink-900)' }}>ポーズ</span>
              <span style={{ fontFamily: sans, fontSize: 10, color: 'var(--ink-400)' }}>Pose</span>
            </div>
            {/* Tab toggle */}
            <div style={{
              display: 'flex', gap: 2,
              background: 'var(--ink-100)', borderRadius: 6, padding: 2,
            }}>
              {[{ id: 'template', label: 'テンプレ' }, { id: 'prompt', label: 'プロンプト' }].map(t => (
                <button key={t.id} onClick={() => setPoseMode(t.id)} style={{
                  padding: '3px 8px', borderRadius: 4,
                  border: 'none', cursor: 'pointer',
                  fontFamily: sans, fontSize: 10, fontWeight: 500,
                  background: poseMode === t.id ? 'var(--panel-bg)' : 'transparent',
                  color: poseMode === t.id ? 'var(--ink-900)' : 'var(--ink-500)',
                  boxShadow: poseMode === t.id ? '0 1px 3px rgba(0,0,0,.08)' : 'none',
                  transition: 'background 120ms, color 120ms',
                }}>
                  {t.label}
                </button>
              ))}
            </div>
          </div>

          {/* Template mode */}
          {poseMode === 'template' && (
            <>
              {/* Category dropdown */}
              <div style={{ position: 'relative', marginBottom: 12 }}>
                <select value={poseCategory} onChange={e => { setPoseCategory(e.target.value); setSelectedPoseId(null); }} style={selectStyle}>
                  {categories.map(cat => (
                    <option key={cat} value={cat}>{cat}</option>
                  ))}
                </select>
              </div>

              {/* Pose grid */}
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 6 }}>
                {poses.map(pose => {
                  const sel = selectedPoseId === pose.id;
                  return (
                    <button key={pose.id} onClick={() => setSelectedPoseId(pose.id)} title={pose.hint} style={{
                      display: 'flex', flexDirection: 'column', alignItems: 'stretch',
                      padding: 0, border: `1.5px solid ${sel ? 'var(--ink-900)' : 'var(--ink-200)'}`,
                      borderRadius: 6, overflow: 'hidden',
                      background: sel ? 'var(--ink-50)' : 'var(--background)',
                      cursor: 'pointer',
                      transition: 'border-color 120ms, background 120ms',
                      boxShadow: sel ? '0 0 0 2px rgba(0,0,0,.06)' : 'none',
                    }}>
                      <div style={{ width: '100%', aspectRatio: '5/6', background: '#fafafa' }}>
                        <PoseSVG poseId={pose.id} />
                      </div>
                      <div style={{
                        padding: '3px 4px 4px',
                        fontFamily: sans, fontSize: 9, fontWeight: sel ? 600 : 400,
                        color: sel ? 'var(--ink-900)' : 'var(--ink-600)',
                        textAlign: 'center', lineHeight: 1.2,
                        borderTop: '1px solid var(--ink-100)',
                        background: sel ? 'var(--ink-100)' : 'transparent',
                      }}>
                        {pose.label}
                      </div>
                    </button>
                  );
                })}
              </div>
            </>
          )}

          {/* Prompt mode */}
          {poseMode === 'prompt' && (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
              <textarea
                value={posePrompt}
                onChange={e => setPosePrompt(e.target.value)}
                placeholder="例: 刀を構えて正面を向いている、片膝をついて礼をしている…"
                rows={5}
                style={{
                  width: '100%', boxSizing: 'border-box',
                  fontFamily: sans, fontSize: 11, color: 'var(--ink-900)',
                  background: 'var(--ink-50)',
                  border: '1px solid var(--ink-300)', borderRadius: 6,
                  padding: '8px 10px', resize: 'vertical',
                  outline: 'none', lineHeight: 1.6,
                  transition: 'border-color 120ms',
                }}
                onFocus={e => e.currentTarget.style.borderColor = 'var(--ink-500)'}
                onBlur={e => e.currentTarget.style.borderColor = 'var(--ink-300)'}
              />
            </div>
          )}
        </div>

      {/* ③ 生成枚数 */}
      <div style={{ padding: '14px 14px 12px', borderBottom: '1px solid var(--ink-100)', flexShrink: 0 }}>
        {sectionLabel('生成枚数', 'Generate')}
        <div style={{ display: 'flex', gap: 6 }}>
          {[1, 2, 3].map(n => (
            <button key={n} onClick={() => setGenerateCount(n)} style={{
              flex: 1, padding: '8px 0', borderRadius: 6,
              border: `1.5px solid ${generateCount === n ? 'var(--ink-900)' : 'var(--ink-200)'}`,
              background: generateCount === n ? 'var(--ink-900)' : 'transparent',
              color: generateCount === n ? '#fff' : 'var(--ink-900)',
              fontFamily: sans, fontSize: 12, fontWeight: 600,
              cursor: 'pointer',
              transition: 'border-color 120ms, background 120ms, color 120ms',
            }}>
              {n}枚
            </button>
          ))}
        </div>
      </div>
      </div>

      {/* ③ 生成ボタン — fixed at bottom */}
      <div style={{
        padding: '12px 14px 14px',
        borderTop: '1px solid var(--ink-200)',
        flexShrink: 0,
        background: 'var(--panel-bg)',
      }}>
        {/* Selected summary */}
        {(selectedChar || (poseMode === 'template' && selectedPose) || (poseMode === 'prompt' && posePrompt.trim())) && !results.length && (
          <div style={{
            display: 'flex', gap: 6, marginBottom: 10, flexWrap: 'wrap',
          }}>
            {selectedChar && (
              <span style={{
                fontFamily: sans, fontSize: 10, fontWeight: 500,
                color: 'var(--ink-700)', background: 'var(--ink-100)',
                padding: '2px 8px', borderRadius: 100,
              }}>
                {selectedChar.name.split(' / ')[0]}
              </span>
            )}
            {poseMode === 'template' && selectedPose && (
              <span style={{
                fontFamily: sans, fontSize: 10, fontWeight: 500,
                color: 'var(--ink-700)', background: 'var(--ink-100)',
                padding: '2px 8px', borderRadius: 100,
              }}>
                {selectedPose.label}
              </span>
            )}
            {poseMode === 'prompt' && posePrompt.trim() && (
              <span style={{
                fontFamily: sans, fontSize: 10, fontWeight: 500,
                color: 'var(--ink-700)', background: 'var(--ink-100)',
                padding: '2px 8px', borderRadius: 100,
                maxWidth: '100%', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
              }}>
                {posePrompt.trim().slice(0, 24)}{posePrompt.trim().length > 24 ? '…' : ''}
              </span>
            )}
          </div>
        )}

        {/* Progress bar */}
        {generating && (
          <div style={{
            height: 3, borderRadius: 2, background: 'var(--ink-100)',
            marginBottom: 10, overflow: 'hidden',
          }}>
            <div style={{
              height: '100%', borderRadius: 2,
              background: 'var(--ink-900)',
              width: `${progress}%`,
              transition: 'width 100ms linear',
            }} />
          </div>
        )}

        {/* Generate button */}
        {!results.length && (
          <>
            <button
              disabled={!canGenerate || generating}
              onClick={() => setGenerating(true)}
              style={{
                width: '100%', height: 40, borderRadius: 8,
                border: 'none', cursor: canGenerate && !generating ? 'pointer' : 'not-allowed',
                background: canGenerate && !generating ? 'var(--ink-900)' : 'var(--ink-200)',
                color: canGenerate && !generating ? '#fff' : 'var(--ink-400)',
                fontFamily: sans, fontSize: 13, fontWeight: 600,
                display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
                transition: 'background 160ms, color 160ms',
              }}
            >
              {generating
                ? <><Icons.Sparkle size={14} /> 生成中… {Math.floor(progress)}%</>
                : <><Icons.Sparkle size={14} /> {generateCount}枚生成</>
              }
            </button>
            {!canGenerate && !generating && (
              <div style={{ fontFamily: sans, fontSize: 10, color: 'var(--ink-400)', textAlign: 'center', marginTop: 6 }}>
                {!selectedCharId ? 'キャラクターを選んでください' : 'ポーズを選んでください'}
              </div>
            )}
          </>
        )}

        {/* Results display */}
        {results.length > 0 && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>

            {/* Grid */}
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6 }}>
              {results.map((result, idx) => {
                const sel = selectedResultIdx === idx;
                return (
                  <button key={result.id} onClick={() => { setSelectedResultIdx(idx); setModalResultIdx(idx); }} style={{
                    display: 'flex', flexDirection: 'column', alignItems: 'stretch',
                    padding: 0, border: `1.5px solid ${sel ? 'var(--ink-900)' : 'var(--ink-200)'}`,
                    borderRadius: 6, overflow: 'hidden',
                    background: 'transparent', cursor: 'pointer',
                    transition: 'border-color 120ms, box-shadow 120ms',
                    boxShadow: sel ? '0 2px 8px rgba(0,0,0,.1)' : 'none',
                  }}
                    onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--ink-600)'; }}
                    onMouseLeave={e => { e.currentTarget.style.borderColor = sel ? 'var(--ink-900)' : 'var(--ink-200)'; }}
                  >
                    {/* Placeholder image */}
                    <div style={{
                      width: '100%', aspectRatio: '3/4',
                      background: `linear-gradient(135deg, #f5f4f0 0%, #ebe9e2 100%)`,
                      display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
                      gap: 4,
                    }}>
                      <Icons.Image size={20} style={{ color: 'var(--ink-300)' }} />
                      <span style={{ fontFamily: sans, fontSize: 9, color: 'var(--ink-400)' }}>生成 {idx + 1}</span>
                    </div>
                    {/* Bottom bar */}
                    {sel && (
                      <div style={{
                        padding: '3px 6px', background: 'var(--ink-900)',
                        fontFamily: sans, fontSize: 9, color: '#fff', textAlign: 'center',
                      }}>選択中</div>
                    )}
                  </button>
                );
              })}
            </div>

            {/* Adopt / Cancel buttons */}
            <div style={{ display: 'flex', gap: 6 }}>
              <button
                disabled={selectedResultIdx === null}
                onClick={() => { setResults([]); setSelectedResultIdx(null); setModalResultIdx(null); }}
                style={{
                  flex: 1, height: 40, borderRadius: 8,
                  border: 'none', cursor: selectedResultIdx !== null ? 'pointer' : 'not-allowed',
                  background: selectedResultIdx !== null ? 'var(--ink-900)' : 'var(--ink-200)',
                  color: selectedResultIdx !== null ? '#fff' : 'var(--ink-400)',
                  fontFamily: sans, fontSize: 12, fontWeight: 600,
                  display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
                  transition: 'background 160ms, color 160ms',
                }}
              >
                <Icons.Plus size={14} /> 採用
              </button>
              <button
                onClick={() => { setResults([]); setSelectedResultIdx(null); setModalResultIdx(null); }}
                style={{
                  flex: 1, height: 40, borderRadius: 8,
                  border: '1.5px solid var(--ink-300)', cursor: 'pointer',
                  background: 'transparent', color: 'var(--ink-600)',
                  fontFamily: sans, fontSize: 12, fontWeight: 600,
                  display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
                  transition: 'border-color 160ms',
                }}
              >
                <Icons.X size={14} /> キャンセル
              </button>
            </div>
          </div>
        )}

        {/* Modal overlay */}
        {modalResultIdx !== null && results[modalResultIdx] && (
          <div
            onClick={() => setModalResultIdx(null)}
            style={{
              position: 'fixed', inset: 0, zIndex: 100,
              background: 'rgba(0,0,0,.55)',
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              backdropFilter: 'blur(4px)',
            }}
          >
            <div
              onClick={e => e.stopPropagation()}
              style={{
                background: 'var(--panel-bg)',
                border: '1px solid var(--panel-border)',
                borderRadius: 12,
                width: 340, maxWidth: '90vw',
                overflow: 'hidden',
                boxShadow: '0 24px 64px rgba(0,0,0,.24)',
              }}
            >
              {/* Modal image */}
              <div style={{
                width: '100%', aspectRatio: '3/4',
                background: 'linear-gradient(135deg, #f5f4f0 0%, #ebe9e2 100%)',
                display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
                gap: 8,
              }}>
                <Icons.Image size={40} style={{ color: 'var(--ink-300)' }} />
                <span style={{ fontFamily: sans, fontSize: 12, color: 'var(--ink-400)', fontWeight: 500 }}>
                  生成結果 {modalResultIdx + 1}
                </span>
              </div>

              {/* Modal footer */}
              <div style={{ padding: '14px 16px', borderTop: '1px solid var(--panel-border)' }}>
                <div style={{ fontFamily: sans, fontSize: 11, color: 'var(--ink-600)', marginBottom: 12 }}>
                  {results[modalResultIdx].char} · {results[modalResultIdx].pose}
                </div>
                <div style={{ display: 'flex', gap: 8 }}>
                  <button
                    onClick={() => {
                      setEditModalResultIdx(modalResultIdx);
                      setModalResultIdx(null);
                    }}
                    style={{
                      height: 38, padding: '0 14px', borderRadius: 8,
                      border: '1.5px solid var(--ink-300)', cursor: 'pointer',
                      background: 'transparent', color: 'var(--ink-700)',
                      fontFamily: sans, fontSize: 12, fontWeight: 600,
                      display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
                      transition: 'border-color 120ms, background 120ms',
                    }}
                    onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--ink-600)'; e.currentTarget.style.background = 'var(--ink-50)'; }}
                    onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--ink-300)'; e.currentTarget.style.background = 'transparent'; }}
                  >
                    <Icons.Pen size={13} /> 編集する
                  </button>
                  <button
                    onClick={() => {
                      setSelectedResultIdx(modalResultIdx);
                      setModalResultIdx(null);
                      setResults([]);
                    }}
                    style={{
                      flex: 1, height: 38, borderRadius: 8,
                      border: 'none', cursor: 'pointer',
                      background: 'var(--ink-900)', color: '#fff',
                      fontFamily: sans, fontSize: 12, fontWeight: 600,
                      display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
                    }}
                  >
                    <Icons.Plus size={13} /> このコマに採用
                  </button>
                  <button
                    onClick={() => setModalResultIdx(null)}
                    style={{
                      width: 38, height: 38, borderRadius: 8,
                      border: '1.5px solid var(--ink-300)', cursor: 'pointer',
                      background: 'transparent', color: 'var(--ink-600)',
                      display: 'flex', alignItems: 'center', justifyContent: 'center',
                    }}
                  >
                    <Icons.X size={14} />
                  </button>
                </div>
              </div>
            </div>
          </div>
        )}

        {/* Edit modal */}
        {editModalResultIdx !== null && results[editModalResultIdx] && (
          <CharGenEditModal
            result={results[editModalResultIdx]}
            onClose={() => setEditModalResultIdx(null)}
            onAccept={() => {
              setSelectedResultIdx(editModalResultIdx);
              setEditModalResultIdx(null);
              setResults([]);
            }}
            sans={sans}
          />
        )}
      </div>
    </div>
  );
};

// ----- ANGLE PANEL -----
const ANGLE_PRESETS = [
  { id: 'eye',   label: 'アイレベル', en: 'Eye Level',   rotX: 0,   rotY: 0   },
  { id: 'bird',  label: '俯瞰',       en: "Bird's Eye",  rotX: -42, rotY: 18  },
  { id: 'worm',  label: '仰瞰',       en: "Worm's Eye",  rotX: 42,  rotY: -12 },
  { id: 'dutch', label: '斜め',       en: 'Dutch',       rotX: -18, rotY: 28  },
  { id: 'side',  label: '横',         en: 'Side View',   rotX: 0,   rotY: 85  },
  { id: 'back',  label: '背面',       en: 'Back View',   rotX: 0,   rotY: 175 },
];

const AnglePanel = () => {
  const [hasImage, setHasImage]   = useState(false);
  const [isDragOver, setIsDragOver] = useState(false);
  const [hAngle, setHAngle]       = useState(0);
  const [vAngle, setVAngle]       = useState(0);
  const [zoom, setZoom]           = useState(5.0);
  const [generating, setGenerating] = useState(false);
  const [progress, setProgress]   = useState(0);
  const sans = 'var(--font-sans)';

  const sliderRow = (label, value, setValue, min, max, step, unit) => (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
        <span style={{ fontSize: 10, fontWeight: 600, color: 'var(--ink-700)', fontFamily: sans }}>{label}</span>
        <span style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--ink-500)' }}>
          {value}{unit}
        </span>
      </div>
      <input type="range" min={min} max={max} step={step} value={value}
        onChange={e => setValue(Number(e.target.value))}
        style={{ width: '100%', accentColor: 'var(--ink-900)', cursor: 'pointer', height: 4 }}
      />
    </div>
  );

  const sectionLabel = (text, sub) => (
    <div style={{ display: 'flex', alignItems: 'baseline', gap: 6, marginBottom: 10 }}>
      <span style={{ fontFamily: sans, fontSize: 11, fontWeight: 600, color: 'var(--ink-900)' }}>{text}</span>
      {sub && <span style={{ fontFamily: sans, fontSize: 10, color: 'var(--ink-400)' }}>{sub}</span>}
    </div>
  );

  useEffect(() => {
    if (!generating) return;
    setProgress(0);
    const t = setInterval(() => {
      setProgress(p => {
        const next = p + Math.random() * 8 + 3;
        if (next >= 100) { clearInterval(t); setGenerating(false); return 100; }
        return next;
      });
    }, 100);
    return () => clearInterval(t);
  }, [generating]);

  return (
    <div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden', minHeight: 0, fontFamily: sans }}>

      {/* Header */}
      <div style={{ padding: '12px 14px 10px', borderBottom: '1px solid var(--ink-200)', flexShrink: 0 }}>
        <div style={{ fontSize: 10, fontWeight: 500, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--ink-400)', fontFamily: sans }}>
          アングル変換 · Angle Convert
        </div>
      </div>

      {/* Body */}
      <div style={{ flex: 1, overflow: 'auto', display: 'flex', flexDirection: 'column' }}>

        {/* ① 入力画像 */}
        <div style={{ padding: '14px 14px 12px', borderBottom: '1px solid var(--ink-100)', flexShrink: 0 }}>
          {sectionLabel('入力画像', 'Source Image')}
          {hasImage ? (
            <div style={{ position: 'relative' }}>
              <div style={{
                width: '100%', aspectRatio: '16/9',
                background: '#fdfcf8', border: '1.5px solid var(--ink-200)',
                borderRadius: 4, overflow: 'hidden',
              }}>
                <AngleSourcePreview/>
              </div>
              <button onClick={() => setHasImage(false)} style={{
                position: 'absolute', top: 6, right: 6,
                width: 20, height: 20, borderRadius: '50%',
                background: 'rgba(0,0,0,.6)', border: 'none',
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                cursor: 'pointer', color: '#fff',
              }}>
                <Icons.X size={9}/>
              </button>
              <div style={{ marginTop: 6, display: 'flex', gap: 5 }}>
                <button style={anglePanelChip} onClick={() => setHasImage(false)}>差し替え</button>
                <button style={anglePanelChip}><Icons.Image size={10}/> 別のコマ</button>
              </div>
            </div>
          ) : (
            <div
              onClick={() => setHasImage(true)}
              onDragOver={e => { e.preventDefault(); setIsDragOver(true); }}
              onDragLeave={() => setIsDragOver(false)}
              onDrop={e => { e.preventDefault(); setIsDragOver(false); setHasImage(true); }}
              style={{
                width: '100%', aspectRatio: '16/9',
                border: `1.5px dashed ${isDragOver ? 'var(--ink-900)' : 'var(--ink-300)'}`,
                borderRadius: 6, cursor: 'pointer',
                display: 'flex', flexDirection: 'column',
                alignItems: 'center', justifyContent: 'center', gap: 7,
                background: isDragOver ? 'var(--ink-100)' : 'var(--ink-50)',
                transition: 'border-color 160ms, background 160ms',
              }}>
              <Icons.Image size={20} style={{ color: 'var(--ink-400)' }}/>
              <div style={{ textAlign: 'center' }}>
                <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--ink-700)', fontFamily: sans }}>画像をドロップ</div>
                <div style={{ fontSize: 10, color: 'var(--ink-400)', marginTop: 2 }}>Drop image or click to upload</div>
              </div>
              <div style={{ fontSize: 9, color: 'var(--ink-400)', fontFamily: 'var(--font-mono)', letterSpacing: '0.06em' }}>PNG · JPG · WEBP</div>
            </div>
          )}
        </div>

        {/* ② 3D アングル設定 */}
        <div style={{ padding: '14px 14px 12px', borderBottom: '1px solid var(--ink-100)' }}>
          <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 10 }}>
            <div style={{ display: 'flex', alignItems: 'baseline', gap: 6 }}>
              <span style={{ fontFamily: sans, fontSize: 11, fontWeight: 600, color: 'var(--ink-900)' }}>アングル設定</span>
              <span style={{ fontFamily: sans, fontSize: 10, color: 'var(--ink-400)' }}>3D Camera Angle</span>
            </div>
            <button
              title="リセット"
              onClick={() => { setHAngle(0); setVAngle(0); setZoom(5.0); }}
              style={{
                fontSize: 16, fontWeight: 400, fontFamily: sans,
                color: 'var(--ink-500)', background: 'none', border: 'none',
                cursor: 'pointer', padding: '4px 6px',
                borderRadius: 4,
                display: 'flex', alignItems: 'center',
              }}
              onMouseEnter={e => e.currentTarget.style.background = 'var(--ink-100)'}
              onMouseLeave={e => e.currentTarget.style.background = 'none'}
            >
              ↺
            </button>
          </div>
          <AngleViewer3D rotX={-vAngle} rotY={hAngle} zoom={zoom}/>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 12, marginTop: 12 }}>
            {sliderRow('水平角度', hAngle, setHAngle, 0, 360, 1, '°')}
            {sliderRow('垂直角度', vAngle, setVAngle, -30, 60, 1, '°')}
            {sliderRow('ズーム', zoom, setZoom, 0, 10, 0.1, 'x')}
          </div>
        </div>
      </div>

      {/* ③ 生成ボタン */}
      <div style={{ padding: 12, borderTop: '1px solid var(--ink-200)', background: 'var(--panel-bg)', flexShrink: 0 }}>
        {generating && (
          <div style={{ marginBottom: 10 }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 10, color: 'var(--ink-500)', marginBottom: 4, fontFamily: 'var(--font-mono)' }}>
              <span>アングル変換中…</span><span>{progress.toFixed(0)}%</span>
            </div>
            <div style={{ height: 3, background: 'var(--ink-200)', borderRadius: 100, overflow: 'hidden' }}>
              <div style={{ width: `${progress}%`, height: '100%', background: 'var(--ink-900)', transition: 'width 120ms' }}/>
            </div>
          </div>
        )}
        <button
          onClick={() => setGenerating(true)}
          disabled={generating || !hasImage}
          style={{
            width: '100%', height: 38, borderRadius: 100, border: 'none',
            background: generating ? 'var(--ink-300)' : !hasImage ? 'var(--ink-200)' : 'var(--ink-900)',
            color: !hasImage && !generating ? 'var(--ink-400)' : 'var(--ink-0)',
            fontWeight: 600, fontSize: 13, fontFamily: sans,
            display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 8,
            cursor: generating || !hasImage ? 'not-allowed' : 'pointer',
            transition: 'background 160ms',
          }}>
          <Icons.Angle size={14}/>
          {generating ? '変換中…' : 'アングルを変換する'}
        </button>
        {!hasImage && !generating && (
          <div style={{ textAlign: 'center', marginTop: 5, fontSize: 10, color: 'var(--ink-400)' }}>
            画像を入力してください
          </div>
        )}
      </div>
    </div>
  );
};



/* Source image placeholder shown after "upload" */
const AngleSourcePreview = () => (
  <svg viewBox="0 0 220 124" style={{ width: '100%', height: '100%' }}>
    <defs>
      <pattern id="asp" width="6" height="6" patternUnits="userSpaceOnUse">
        <circle cx="3" cy="3" r="0.4" fill="#d4d4d4"/>
      </pattern>
    </defs>
    <rect width="220" height="124" fill="url(#asp)"/>
    <g stroke="#1a1a1a" strokeWidth="1" fill="none" strokeLinecap="round">
      <ellipse cx="110" cy="36" rx="14" ry="17"/>
      <line x1="110" y1="53" x2="110" y2="88"/>
      <line x1="110" y1="65" x2="90" y2="80"/>
      <line x1="110" y1="65" x2="130" y2="80"/>
      <line x1="110" y1="88" x2="98" y2="112"/>
      <line x1="110" y1="88" x2="122" y2="112"/>
      <line x1="20" y1="96" x2="200" y2="96" stroke="#bbb" strokeWidth="0.6" strokeDasharray="6,4"/>
    </g>
    <text x="8" y="120" fontFamily="ui-monospace" fontSize="7" fill="#9ca3af">source.png · 1280×720</text>
  </svg>
);

/* 3D CSS panel viewer */
const AngleViewer3D = ({ rotX, rotY, zoom = 5 }) => {
  const W = 66, H = 84, D = 10;
  const scale = Math.max(0.1, zoom / 5);
  const f = (extra) => ({ position: 'absolute', boxSizing: 'border-box', backfaceVisibility: 'hidden', ...extra });

  return (
    <div style={{
      width: '100%', height: 152,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      background: 'linear-gradient(160deg, #f5f3ef 0%, #eae6df 100%)',
      border: '1px solid var(--panel-border)', borderRadius: 6,
      overflow: 'hidden', position: 'relative',
    }}>
      {/* Subtle dot grid */}
      <svg style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', opacity: 0.18 }}>
        <defs><pattern id="vgrid" x="0" y="0" width="14" height="14" patternUnits="userSpaceOnUse"><circle cx="7" cy="7" r="0.7" fill="#666"/></pattern></defs>
        <rect width="100%" height="100%" fill="url(#vgrid)"/>
      </svg>

      {/* Axis indicators */}
      <div style={{ position: 'absolute', bottom: 8, right: 10, display: 'flex', gap: 6, zIndex: 1 }}>
        {[['X','#ef4444'],['Y','#22c55e'],['Z','#3b82f6']].map(([ax, col]) => (
          <div key={ax} style={{ display: 'flex', alignItems: 'center', gap: 3 }}>
            <div style={{ width: 10, height: 2, background: col, borderRadius: 1 }}/>
            <span style={{ fontSize: 8, fontFamily: 'var(--font-mono)', color: 'var(--ink-500)' }}>{ax}</span>
          </div>
        ))}
      </div>

      {/* 3D box */}
      <div style={{ perspective: '500px' }}>
        <div style={{
          width: W, height: H,
          position: 'relative',
          transformStyle: 'preserve-3d',
          transform: `scale(${scale}) rotateX(${rotX}deg) rotateY(${rotY}deg)`,
          transition: 'transform 300ms cubic-bezier(0.4,0,0.2,1)',
        }}>
          {/* Front face — manga panel */}
          <div style={f({ width: W, height: H, background: '#fdfcf8', border: '2px solid #1a1a1a', transform: `translateZ(${D/2}px)`, overflow: 'hidden' })}>
            <svg viewBox="0 0 66 84" style={{ width: '100%', height: '100%' }}>
              <g stroke="#1a1a1a" strokeWidth="1.1" fill="none" strokeLinecap="round">
                <ellipse cx="33" cy="17" rx="8" ry="10"/>
                <line x1="33" y1="27" x2="33" y2="46"/>
                <line x1="33" y1="34" x2="22" y2="43"/>
                <line x1="33" y1="34" x2="44" y2="43"/>
                <line x1="33" y1="46" x2="26" y2="62"/>
                <line x1="33" y1="46" x2="40" y2="62"/>
                <line x1="6" y1="55" x2="60" y2="55" stroke="#ccc" strokeWidth="0.5" strokeDasharray="3,3"/>
              </g>
            </svg>
          </div>
          {/* Back */}
          <div style={f({ width: W, height: H, background: '#cec9c1', border: '1px solid #888', transform: `rotateY(180deg) translateZ(${D/2}px)` })}/>
          {/* Right */}
          <div style={f({ width: D, height: H, background: '#bdb9b1', border: '1px solid #888', left: W, transformOrigin: 'left center', transform: 'rotateY(90deg)' })}/>
          {/* Left */}
          <div style={f({ width: D, height: H, background: '#c8c4bc', border: '1px solid #888', right: W, transformOrigin: 'right center', transform: 'rotateY(-90deg)' })}/>
          {/* Top */}
          <div style={f({ width: W, height: D, background: '#d4d0c8', border: '1px solid #888', top: -D, transformOrigin: 'bottom center', transform: 'rotateX(-90deg)' })}/>
          {/* Bottom */}
          <div style={f({ width: W, height: D, background: '#b8b4ac', border: '1px solid #888', top: H, transformOrigin: 'top center', transform: 'rotateX(90deg)' })}/>
        </div>
      </div>
    </div>
  );
};

const anglePanelChip = {
  height: 24, padding: '0 10px', borderRadius: 100,
  background: 'var(--ink-100)', fontSize: 11, fontWeight: 500, border: 'none', cursor: 'pointer',
  display: 'inline-flex', alignItems: 'center', gap: 4,
};

// ----- LIBRARY PANEL -----
const LibraryPanel = () => {
  const [tab, setTab] = useState('characters');
  const { CHARACTERS, ASSETS } = window.DATA;

  const sans = 'var(--font-sans)';

  // Group assets by kind
  const assetsByKind = {};
  ASSETS.forEach(a => {
    if (!assetsByKind[a.kind]) assetsByKind[a.kind] = [];
    assetsByKind[a.kind].push(a);
  });

  const kinds = Object.keys(assetsByKind).sort();

  return (
    <div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden', minHeight: 0 }}>
      
      {/* Header */}
      <div style={{ padding: '12px 14px 10px', borderBottom: '1px solid var(--ink-200)', flexShrink: 0 }}>
        <div style={{
          fontSize: 10, fontWeight: 500, letterSpacing: '0.08em', textTransform: 'uppercase',
          color: 'var(--ink-400)', fontFamily: sans, marginBottom: 10,
        }}>ライブラリ · Library</div>
        
        {/* Tabs */}
        <div style={{ display: 'flex', gap: 4 }}>
          {['all', 'characters', 'assets'].map(t => (
            <button key={t} onClick={() => setTab(t)} style={{
              padding: '4px 12px', borderRadius: 6,
              border: tab === t ? 'none' : '1px solid var(--ink-300)',
              background: tab === t ? 'var(--ink-900)' : 'transparent',
              color: tab === t ? '#fff' : 'var(--ink-600)',
              fontSize: 11, fontWeight: 500,
              fontFamily: sans,
              cursor: 'pointer',
              transition: 'background var(--d-fast), color var(--d-fast)',
            }}>
              {t === 'all' ? 'すべて' : t === 'characters' ? 'キャラ' : 'アセット'}
            </button>
          ))}
        </div>
      </div>

      {/* Body */}
      <div style={{ flex: 1, overflow: 'auto', padding: '14px 14px 20px' }}>
        
        {/* All tab */}
        {tab === 'all' && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
            {/* Characters section */}
            <div>
              <div style={{
                fontSize: 10, fontWeight: 600, letterSpacing: '0.08em', textTransform: 'uppercase',
                color: 'var(--ink-500)', fontFamily: sans, marginBottom: 8, paddingBottom: 6,
                borderBottom: '1px solid var(--ink-200)',
              }}>
                キャラクター · Characters
              </div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
                {CHARACTERS.map(char => (
                  <div key={char.id} style={{
                    padding: 8,
                    border: '1px solid var(--ink-200)',
                    borderRadius: 6,
                    cursor: 'grab',
                    transition: 'border-color var(--d-fast), box-shadow var(--d-fast)',
                  }}
                    onMouseEnter={e => {
                      e.currentTarget.style.borderColor = 'var(--ink-900)';
                      e.currentTarget.style.boxShadow = '0 2px 8px rgba(0,0,0,.08)';
                    }}
                    onMouseLeave={e => {
                      e.currentTarget.style.borderColor = 'var(--ink-200)';
                      e.currentTarget.style.boxShadow = 'none';
                    }}
                  >
                    <div style={{ display: 'flex', gap: 8, alignItems: 'flex-start' }}>
                      <div style={{
                        width: 50, height: 50, flexShrink: 0,
                        background: char.painting ? `center/cover no-repeat url(assets/paintings/${char.painting}.png), var(--ink-100)` : 'var(--ink-100)',
                        borderRadius: 4,
                        border: '1px solid var(--ink-200)',
                        display: 'flex', alignItems: 'center', justifyContent: 'center',
                        color: 'var(--ink-400)',
                      }}>
                        {!char.painting && <Icons.Image size={18} />}
                      </div>
                      <div style={{ flex: 1, minWidth: 0 }}>
                        <div style={{
                          fontWeight: 600, fontSize: 12, color: 'var(--ink-900)',
                          overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
                        }}>
                          {char.name}
                        </div>
                        <div style={{
                          fontSize: 10, color: 'var(--ink-500)', marginTop: 2,
                          overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
                        }}>
                          {char.role}
                        </div>
                        <div style={{
                          fontSize: 9, fontFamily: 'var(--font-mono)', color: 'var(--ink-400)', marginTop: 3,
                        }}>
                          {char.lora}
                        </div>
                        <div style={{
                          fontSize: 8, color: 'var(--ink-400)', marginTop: 2,
                        }}>
                          Strength: {(char.strength * 100).toFixed(0)}%
                        </div>
                      </div>
                    </div>
                  </div>
                ))}
              </div>
            </div>

            {/* Assets section */}
            <div>
              <div style={{
                fontSize: 10, fontWeight: 600, letterSpacing: '0.08em', textTransform: 'uppercase',
                color: 'var(--ink-500)', fontFamily: sans, marginBottom: 8, paddingBottom: 6,
                borderBottom: '1px solid var(--ink-200)',
              }}>
                アセット · Assets
              </div>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
                {ASSETS.map(asset => (
                  <div key={asset.id} style={{
                    padding: 8,
                    border: '1px solid var(--ink-200)',
                    borderRadius: 4,
                    cursor: 'grab',
                    transition: 'border-color var(--d-fast), box-shadow var(--d-fast)',
                    display: 'flex', alignItems: 'center', gap: 8,
                  }}
                    onMouseEnter={e => {
                      e.currentTarget.style.borderColor = 'var(--ink-900)';
                      e.currentTarget.style.boxShadow = '0 2px 8px rgba(0,0,0,.08)';
                    }}
                    onMouseLeave={e => {
                      e.currentTarget.style.borderColor = 'var(--ink-200)';
                      e.currentTarget.style.boxShadow = 'none';
                    }}
                  >
                    <div style={{
                      width: 40, height: 40, flexShrink: 0,
                      background: asset.painting ? `center/cover no-repeat url(assets/paintings/${asset.painting}.png), var(--ink-100)` : 'var(--ink-100)',
                      borderRadius: 3,
                      border: '1px solid var(--ink-200)',
                      display: 'flex', alignItems: 'center', justifyContent: 'center',
                      color: 'var(--ink-400)',
                    }}>
                      {!asset.painting && <Icons.Image size={14} />}
                    </div>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{
                        fontWeight: 500, fontSize: 11, color: 'var(--ink-900)',
                        overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
                      }}>
                        {asset.name}
                      </div>
                      <div style={{
                        fontSize: 9, fontFamily: 'var(--font-mono)', color: 'var(--ink-400)', marginTop: 1,
                      }}>
                        {asset.kind} · {asset.id}
                      </div>
                    </div>
                  </div>
                ))}
              </div>
            </div>
          </div>
        )}

        {/* Characters tab */}
        {tab === 'characters' && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
            {CHARACTERS.map(char => (
              <div key={char.id} style={{
                padding: 8,
                border: '1px solid var(--ink-200)',
                borderRadius: 6,
                cursor: 'grab',
                transition: 'border-color var(--d-fast), box-shadow var(--d-fast)',
              }}
                onMouseEnter={e => {
                  e.currentTarget.style.borderColor = 'var(--ink-900)';
                  e.currentTarget.style.boxShadow = '0 2px 8px rgba(0,0,0,.08)';
                }}
                onMouseLeave={e => {
                  e.currentTarget.style.borderColor = 'var(--ink-200)';
                  e.currentTarget.style.boxShadow = 'none';
                }}
              >
                {/* Character thumbnail + info */}
                <div style={{ display: 'flex', gap: 8, alignItems: 'flex-start' }}>
                  <div style={{
                    width: 50, height: 50, flexShrink: 0,
                    background: char.painting ? `center/cover no-repeat url(assets/paintings/${char.painting}.png), var(--ink-100)` : 'var(--ink-100)',
                    borderRadius: 4,
                    border: '1px solid var(--ink-200)',
                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                    color: 'var(--ink-400)',
                  }}>
                    {!char.painting && <Icons.Image size={18} />}
                  </div>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{
                      fontWeight: 600, fontSize: 12, color: 'var(--ink-900)',
                      overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
                    }}>
                      {char.name}
                    </div>
                    <div style={{
                      fontSize: 10, color: 'var(--ink-500)', marginTop: 2,
                      overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
                    }}>
                      {char.role}
                    </div>
                    <div style={{
                      fontSize: 9, fontFamily: 'var(--font-mono)', color: 'var(--ink-400)', marginTop: 3,
                    }}>
                      {char.lora}
                    </div>
                    <div style={{
                      fontSize: 8, color: 'var(--ink-400)', marginTop: 2,
                    }}>
                      Strength: {(char.strength * 100).toFixed(0)}%
                    </div>
                  </div>
                </div>
              </div>
            ))}
          </div>
        )}

        {/* Assets tab */}
        {tab === 'assets' && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
            {kinds.map(kind => (
              <div key={kind}>
                <div style={{
                  fontSize: 10, fontWeight: 600, letterSpacing: '0.08em', textTransform: 'uppercase',
                  color: 'var(--ink-500)', fontFamily: sans, marginBottom: 8, paddingBottom: 6,
                  borderBottom: '1px solid var(--ink-200)',
                }}>
                  {kind}
                </div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
                  {assetsByKind[kind].map(asset => (
                    <div key={asset.id} style={{
                      padding: 8,
                      border: '1px solid var(--ink-200)',
                      borderRadius: 4,
                      cursor: 'grab',
                      transition: 'border-color var(--d-fast), box-shadow var(--d-fast)',
                      display: 'flex', alignItems: 'center', gap: 8,
                    }}
                      onMouseEnter={e => {
                        e.currentTarget.style.borderColor = 'var(--ink-900)';
                        e.currentTarget.style.boxShadow = '0 2px 8px rgba(0,0,0,.08)';
                      }}
                      onMouseLeave={e => {
                        e.currentTarget.style.borderColor = 'var(--ink-200)';
                        e.currentTarget.style.boxShadow = 'none';
                      }}
                    >
                      {/* Asset thumbnail */}
                      <div style={{
                        width: 40, height: 40, flexShrink: 0,
                        background: asset.painting ? `center/cover no-repeat url(assets/paintings/${asset.painting}.png), var(--ink-100)` : 'var(--ink-100)',
                        borderRadius: 3,
                        border: '1px solid var(--ink-200)',
                        display: 'flex', alignItems: 'center', justifyContent: 'center',
                        color: 'var(--ink-400)',
                      }}>
                        {!asset.painting && <Icons.Image size={14} />}
                      </div>
                      <div style={{ flex: 1, minWidth: 0 }}>
                        <div style={{
                          fontWeight: 500, fontSize: 11, color: 'var(--ink-900)',
                          overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
                        }}>
                          {asset.name}
                        </div>
                        <div style={{
                          fontSize: 9, fontFamily: 'var(--font-mono)', color: 'var(--ink-400)', marginTop: 1,
                        }}>
                          {asset.id}
                        </div>
                      </div>
                    </div>
                  ))}
                </div>
              </div>
            ))}
          </div>
        )}
      </div>
    </div>
  );
};

// ----- TOOLS RAIL: vertical toolbar alongside left rail -----
const ToolsRail = ({ activeTool, onToolChange }) => {
  const tools = [
    { Icon: Icons.Cursor,  label: '選択' },
    { Icon: Icons.Pen,     label: 'キャラ\nデザイン' },
    { Icon: Icons.Sparkle, label: 'キャラ\n生成' },
    { Icon: Icons.Image,   label: '背景\n生成' },
    { Icon: Icons.Angle,   label: 'アングル\n変換', ai: true },
    { Icon: Icons.Crop,    label: 'コマ割り' },
    { Icon: Icons.Folder,  label: 'テンプレ' },
    { Icon: Icons.Text,    label: 'テキスト' },
    { Icon: Icons.Layers,  label: 'ライブラリ' },
  ];

  return (
    <div style={{
      width: 48, flexShrink: 0,
      background: 'var(--panel-bg)',
      borderRight: '1px solid var(--panel-border)',
      display: 'flex', flexDirection: 'column', alignItems: 'center',
      padding: '10px 0', gap: 2,
      height: '100%', overflow: 'hidden'
    }}>
      {tools.map((t, i) =>
      <button key={i} title={t.label} onClick={() => onToolChange(i)} style={{
        display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
        gap: 3, width: 40, padding: '7px 4px', borderRadius: 6,
        border: 'none', cursor: 'pointer',
        position: 'relative',
        background: activeTool === i ? 'var(--ink-900)' : 'transparent',
        color: activeTool === i ? 'var(--ink-0)' : 'var(--ink-700)'
      }}>
          <t.Icon size={16} />
          <span style={{ fontSize: 8, fontWeight: 500, letterSpacing: '0.02em', textAlign: 'center', whiteSpace: 'pre-line', lineHeight: 1.3 }}>{t.label}</span>
          {t.ai && <span style={{ position: 'absolute', top: 3, right: 3, fontSize: 7, fontWeight: 700, color: 'var(--accent)', letterSpacing: '0.06em' }}>AI</span>}
        </button>
      )}
    </div>);

};

// Tiny SVG diagram showing rough panel structure of a page
const MiniPageDiagram = ({ pageIdx }) => {
  const layouts = [
  [[0, 0, 1, .4], [0, .4, .5, .3], [.5, .4, .5, .3], [0, .7, 1, .3]],
  [[0, 0, .6, .5], [.6, 0, .4, .5], [0, .5, 1, .5]],
  [[0, 0, 1, .3], [0, .3, .5, .4], [.5, .3, .5, .4], [0, .7, .5, .3], [.5, .7, .5, .3]],
  [[0, 0, 1, .5], [0, .5, 1, .5]]];

  const l = layouts[pageIdx % layouts.length];
  return (
    <svg viewBox="0 0 100 100" preserveAspectRatio="none" style={{ width: '100%', height: '100%' }}>
      {l.map((r, i) =>
      <rect key={i} x={r[0] * 100 + 2} y={r[1] * 100 + 2} width={r[2] * 100 - 4} height={r[3] * 100 - 4}
      fill="none" stroke="#737373" strokeWidth="2" />
      )}
    </svg>);

};

// ----- RIGHT RAIL: Library (when nothing selected) -----
const LibraryRail = ({ onSelectAsset, narrow }) => {
  const [tab, setTab] = useState('chars');
  const { CHARACTERS, ASSETS, STYLES } = window.DATA;

  if (narrow) {
    return (
      <div style={railNarrowStyle}>
        {[
        { id: 'chars', Icon: Icons.User, label: 'Characters' },
        { id: 'assets', Icon: Icons.Image, label: 'Assets' },
        { id: 'styles', Icon: Icons.Wand, label: 'Styles' }].
        map((t) =>
        <button key={t.id} title={t.label} style={{
          ...iconBtn, width: 36, height: 36,
          background: tab === t.id ? 'var(--ink-100)' : 'transparent'
        }} onClick={() => setTab(t.id)}>
            <t.Icon size={16} />
          </button>
        )}
      </div>);

  }

  return (
    <aside style={railStyle}>
      {/* Editorial section title */}
      <div style={{ padding: '14px 14px 4px', borderBottom: '1px solid var(--panel-border)' }}>
        <Atoms.SectionTitle jp="ライブラリ" en="Library" level={1} />
      </div>

      {/* Tabs */}
      <div style={{
        display: 'flex', borderBottom: '1px solid var(--panel-border)',
        padding: '0 8px'
      }}>
        {[
        { id: 'chars', jp: 'キャラ', en: 'Characters' },
        { id: 'assets', jp: '素材', en: 'Assets' },
        { id: 'styles', jp: 'スタイル', en: 'Styles' }].
        map((t) => {
          const active = tab === t.id;
          return (
            <button key={t.id} onClick={() => setTab(t.id)}
            style={{
              padding: '10px 8px', fontSize: 12, fontWeight: 500,
              color: active ? 'var(--ink-900)' : 'var(--ink-500)',
              borderBottom: `2px solid ${active ? 'var(--ink-900)' : 'transparent'}`,
              position: 'relative', marginBottom: -1
            }}>
              {t.jp} <span style={{ fontFamily: 'var(--font-display)', fontStyle: 'italic', fontSize: 10, color: 'var(--ink-400)' }}>{t.en}</span>
            </button>);

        })}
      </div>

      {/* Search */}
      <div style={{ padding: '10px 12px' }}>
        <div style={{
          display: 'flex', alignItems: 'center', gap: 6,
          padding: '6px 10px', background: 'var(--ink-100)', borderRadius: 6
        }}>
          <Icons.Search size={13} style={{ color: 'var(--ink-500)' }} />
          <input placeholder="検索 / Search" style={{
            flex: 1, border: 0, background: 'transparent', outline: 'none', fontSize: 12
          }} />
        </div>
      </div>

      {/* Tab content */}
      <div style={{ flex: 1, overflow: 'auto', padding: '0 12px 12px' }}>
        {tab === 'chars' &&
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            {CHARACTERS.map((c) =>
          <div key={c.id} style={{
            display: 'flex', gap: 10, padding: 8, borderRadius: 6,
            background: 'var(--ink-50)', border: '1px solid var(--panel-border)',
            cursor: 'grab'
          }}>
                <Atoms.PaintingTile name={c.painting} w={44} h={56} halo />
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 12, fontWeight: 500 }}>{c.name}</div>
                  <div style={{ fontFamily: 'var(--font-display)', fontStyle: 'italic', fontSize: 10, color: 'var(--ink-500)' }}>
                    {c.role}
                  </div>
                  <div style={{ marginTop: 4, display: 'flex', gap: 4, flexWrap: 'wrap' }}>
                    <Atoms.Tag tone="outline">LoRA</Atoms.Tag>
                    <Atoms.Tag tone="default">w {c.strength.toFixed(2)}</Atoms.Tag>
                  </div>
                </div>
              </div>
          )}
            <button style={{
            padding: 10, border: '1px dashed var(--ink-300)', borderRadius: 6,
            fontSize: 12, color: 'var(--ink-500)',
            display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6
          }}>
              <Icons.Plus size={14} /> 新しいキャラクターを学習 <span style={{ fontFamily: 'var(--font-display)', fontStyle: 'italic' }}>train new</span>
            </button>
          </div>
        }

        {tab === 'assets' &&
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
            {ASSETS.map((a) =>
          <div key={a.id} style={{ cursor: 'grab' }}>
                <Atoms.PaintingTile name={a.painting} h={70} />
                <div style={{ fontSize: 11, marginTop: 4, fontWeight: 500 }}>{a.name}</div>
                <div style={{ fontFamily: 'var(--font-mono)', fontSize: 9, color: 'var(--ink-400)', textTransform: 'uppercase' }}>{a.kind}</div>
              </div>
          )}
          </div>
        }

        {tab === 'styles' &&
        <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            {STYLES.map((s) =>
          <div key={s.id} style={{
            padding: 10, borderRadius: 6,
            background: s.active ? 'rgba(244,208,63,.12)' : 'var(--ink-50)',
            border: `1px solid ${s.active ? 'rgba(244,208,63,.5)' : 'var(--panel-border)'}`,
            display: 'flex', alignItems: 'center', justifyContent: 'space-between'
          }}>
                <div>
                  <div style={{ fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 14 }}>{s.name}</div>
                  <div style={{ fontFamily: 'var(--font-jp)', fontSize: 11, color: 'var(--ink-500)' }}>{s.sub}</div>
                </div>
                {s.active && <Atoms.Tag tone="accent">適用中</Atoms.Tag>}
              </div>
          )}
          </div>
        }
      </div>
    </aside>);

};

const railStyle = {
  width: 'var(--rail-w)',
  background: 'var(--panel-bg)',
  borderRight: '1px solid var(--panel-border)',
  display: 'flex', flexDirection: 'column',
  flexShrink: 0,
  height: '100%',
  overflow: 'hidden'
};

const railRightStyle = {
  width: 'var(--rail-w-right, var(--rail-w))',
  background: 'var(--panel-bg)',
  borderLeft: '1px solid var(--panel-border)',
  display: 'flex', flexDirection: 'column',
  flexShrink: 0,
  height: '100%',
  overflow: 'hidden'
};

const railNarrowStyle = {
  width: 'var(--rail-w-narrow)',
  background: 'var(--panel-bg)',
  borderRight: '1px solid var(--panel-border)',
  display: 'flex', flexDirection: 'column', alignItems: 'center',
  gap: 6, padding: '12px 0',
  flexShrink: 0, height: '100%'
};

window.PagesRail = PagesRail;
window.LibraryRail = LibraryRail;


// ============ src/ai-panel.jsx ============
// =============== AI PANEL ===============
// The heart of the Holonica magic — when a panel is selected, this shows
// scribble preview + character LoRA picker + style + sliders + generate.
// GUI-first; prompt is a small accent at the bottom, not the protagonist.

const AIPanel = ({ panel, panelIdx, onClose, embedded, dock, _noWrapper }) => {
  const { CHARACTERS, STYLES } = window.DATA;
  const [selectedChar, setSelectedChar] = useState('c1');
  const [selectedStyle, setSelectedStyle] = useState('s1');
  const [scribbleStrength, setScribbleStrength] = useState(0.85);
  const [loraStrength, setLoraStrength] = useState(0.78);
  const [seed, setSeed] = useState(847362);
  const [steps, setSteps] = useState(28);
  const [generating, setGenerating] = useState(false);
  const [progress, setProgress] = useState(0);
  const [streamed, setStreamed] = useState('');
  const [prompt, setPrompt] = useState('');

  // Fake AI streaming when generating
  useEffect(() => {
    if (!generating) return;
    setProgress(0);setStreamed('');
    const t = setInterval(() => {
      setProgress((p) => {
        const next = p + Math.random() * 6 + 2;
        if (next >= 100) {clearInterval(t);setGenerating(false);return 100;}
        return next;
      });
    }, 120);
    const fullText = '月明かりの下、刀を構える主人公。背景は江戸の町並み、霧の中に灯篭の光。Mincho ink style, 600dpi.';
    let i = 0;
    const t2 = setInterval(() => {
      if (i >= fullText.length) {clearInterval(t2);return;}
      setStreamed(fullText.slice(0, ++i));
    }, 35);
    return () => {clearInterval(t);clearInterval(t2);};
  }, [generating]);

  const wrapStyle = embedded ? embeddedStyle : dock === 'bottom' ? bottomDockStyle : floatingStyle;

  // When rendered inside RightPanel tab, skip the outer aside + header
  const inner =
  <>
      {/* Header — only shown when not inside a tab wrapper */}
      {!_noWrapper && <div style={{
      padding: '14px 14px 10px',
      borderBottom: '1px solid var(--panel-border)',
      display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between',
      gap: 12, flexShrink: 0
    }}>
        <div>
          <div style={{ fontSize: 10, fontWeight: 600, letterSpacing: '0.12em', textTransform: 'uppercase', color: 'var(--accent)' }}>
            ◇ Selected Panel · 第{panelIdx}コマ
          </div>
          <div style={{ marginTop: 4, display: 'flex', alignItems: 'baseline', gap: 8 }}>
            <span style={{ fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 18, letterSpacing: '-0.01em' }}>
              情景を生成する
            </span>
          </div>
          <div style={{ fontFamily: 'var(--font-display)', fontStyle: 'italic', fontSize: 12, color: 'var(--ink-500)', marginTop: 2 }}>
            Generate this scene from your scribble
          </div>
        </div>
        {onClose &&
      <button style={iconBtn} onClick={onClose}><Icons.X size={14} /></button>
      }
      </div>}

      {/* Body */}
      <div style={{
      flex: 1, overflow: 'auto',
      display: dock === 'bottom' ? 'grid' : 'flex',
      gridTemplateColumns: dock === 'bottom' ? '320px 1fr 1fr' : undefined,
      flexDirection: 'column',
      gap: 0
    }}>
        {/* --- Scribble preview --- */}
        <Section title="ラフ" subtitle="Scribble · ControlNet">
          <div style={{
          position: 'relative', aspectRatio: panel ? `${panel.w}/${panel.h}` : '4/3',
          background: '#fdfcf8', border: '1.5px solid var(--ink-300)', borderRadius: 4,
          overflow: 'hidden'
        }}>
            <ScribblePreviewSVG />
            <div style={{
            position: 'absolute', right: 6, top: 6,
            background: '#fff', border: '1px solid var(--ink-200)',
            borderRadius: 100, padding: '2px 8px',
            fontSize: 10, fontFamily: 'var(--font-mono)',
            display: 'flex', alignItems: 'center', gap: 4
          }}>
              <span style={{ width: 5, height: 5, borderRadius: '50%', background: '#10B981' }} />
              detected
            </div>
          </div>
          <div style={{ marginTop: 8, display: 'flex', gap: 6 }}>
            <button style={chipBtn}><Icons.Pen size={11} /> 編集</button>
            <button style={chipBtn}><Icons.Image size={11} /> 差し替え</button>
            <button style={chipBtn}>反転</button>
          </div>
          <div style={{ marginTop: 12 }}>
            <Atoms.LabeledSlider label="ラフ忠実度 / Scribble" value={scribbleStrength}
          min={0} max={1} step={0.01} onChange={setScribbleStrength} />
          </div>
        </Section>

        {/* --- Character LoRA --- */}
        <Section title="キャラ" subtitle="Character · LoRA">
          <div style={{ display: 'flex', gap: 6, overflow: 'auto', paddingBottom: 4 }}>
            {CHARACTERS.map((c) => {
            const sel = selectedChar === c.id;
            return (
              <button key={c.id} onClick={() => setSelectedChar(c.id)} style={{
                flexShrink: 0, width: 64, padding: 4,
                borderRadius: 6,
                background: sel ? 'rgba(244,208,63,.15)' : 'transparent',
                border: `1.5px solid ${sel ? 'var(--accent)' : 'transparent'}`,
                textAlign: 'center'
              }}>
                  <Atoms.PaintingTile name={c.painting} w="100%" h={64} halo={sel} />
                  <div style={{ fontSize: 10, marginTop: 4, fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                    {c.name.split(' / ')[0]}
                  </div>
                </button>);

          })}
            <button style={{
            flexShrink: 0, width: 64, height: 64, marginTop: 4,
            border: '1px dashed var(--ink-300)', borderRadius: 6,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            color: 'var(--ink-500)'
          }}><Icons.Plus size={16} /></button>
          </div>
          <div style={{ marginTop: 10, padding: 10, background: 'var(--ink-50)', borderRadius: 6, border: '1px solid var(--panel-border)' }}>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
              <span style={{ fontSize: 11, color: 'var(--ink-600)' }}>
                {CHARACTERS.find((c) => c.id === selectedChar)?.lora}
              </span>
              <Atoms.Tag tone="accent">active</Atoms.Tag>
            </div>
            <Atoms.LabeledSlider label="LoRA strength" value={loraStrength}
          min={0} max={1.5} step={0.01} onChange={setLoraStrength} />
          </div>
          <div style={{ marginTop: 8, display: 'flex', gap: 4, flexWrap: 'wrap' }}>
            <Atoms.Tag tone="outline" removable>怒り表情</Atoms.Tag>
            <Atoms.Tag tone="outline" removable>刀構え</Atoms.Tag>
            <Atoms.Tag tone="outline">+ 追加</Atoms.Tag>
          </div>
        </Section>

        {/* --- Style --- */}
        <Section title="画風" subtitle="Style · Mincho Ink">
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6 }}>
            {STYLES.map((s) => {
            const sel = selectedStyle === s.id;
            return (
              <button key={s.id} onClick={() => setSelectedStyle(s.id)} style={{
                padding: '8px 10px', borderRadius: 6,
                background: sel ? 'var(--ink-900)' : 'var(--ink-50)',
                color: sel ? 'var(--ink-0)' : 'var(--ink-900)',
                border: `1px solid ${sel ? 'var(--ink-900)' : 'var(--panel-border)'}`,
                textAlign: 'left'
              }}>
                  <div style={{ fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 13 }}>{s.name}</div>
                  <div style={{ fontFamily: 'var(--font-jp)', fontSize: 10, opacity: 0.7 }}>{s.sub}</div>
                </button>);

          })}
          </div>
        </Section>

        {/* --- Advanced (sd params) --- */}
        <Section title="詳細" subtitle="Advanced · SD params">
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            <Atoms.LabeledSlider label="Steps" value={steps} min={10} max={60} step={1} onChange={setSteps} />
            <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
              <span style={{ fontSize: 11, color: 'var(--ink-600)', flex: 1 }}>Seed</span>
              <input value={seed} onChange={(e) => setSeed(parseInt(e.target.value) || 0)} style={{
              width: 90, padding: '4px 8px', border: '1px solid var(--panel-border)',
              borderRadius: 4, fontFamily: 'var(--font-mono)', fontSize: 11, background: 'transparent'
            }} />
              <button style={iconBtn} onClick={() => setSeed(Math.floor(Math.random() * 9999999))}>
                <Icons.Dice size={14} />
              </button>
            </div>
            <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
              <Atoms.Tag tone="outline">DPM++ 2M</Atoms.Tag>
              <Atoms.Tag tone="outline">CFG 7.0</Atoms.Tag>
              <Atoms.Tag tone="outline">768×512</Atoms.Tag>
            </div>
          </div>
        </Section>

        {/* --- Prompt (de-emphasized — GUI-first promise) --- */}
        <Section title="補足" subtitle="Optional prompt">
          <textarea
          value={prompt}
          onChange={(e) => setPrompt(e.target.value)}
          placeholder="必要があれば補足を…例: 雨上がりの石畳"
          style={{
            width: '100%', minHeight: 56, padding: 8,
            border: '1px solid var(--panel-border)', borderRadius: 4,
            background: 'var(--ink-50)', resize: 'vertical',
            fontFamily: 'var(--font-jp)', fontSize: 12, lineHeight: 1.5,
            outline: 'none'
          }} />
        
        </Section>
      </div>

      {/* Generate button — sticky footer */}
      <div style={{
      padding: 12, borderTop: '1px solid var(--panel-border)',
      background: 'var(--panel-bg)', flexShrink: 0
    }}>
        {generating &&
      <div style={{ marginBottom: 10 }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 10, color: 'var(--ink-500)', marginBottom: 4, fontFamily: 'var(--font-mono)' }}>
              <span>step {Math.round(progress / 100 * steps)} / {steps}</span>
              <span>{progress.toFixed(0)}%</span>
            </div>
            <div style={{ height: 3, background: 'var(--ink-200)', borderRadius: 100, overflow: 'hidden' }}>
              <div style={{
            width: `${progress}%`, height: '100%',
            background: 'linear-gradient(90deg, var(--accent), #B8941F)',
            transition: 'width 120ms'
          }} />
            </div>
            {streamed &&
        <div style={{ marginTop: 8, fontFamily: 'var(--font-jp)', fontSize: 11, color: 'var(--ink-500)', fontStyle: 'italic', minHeight: 16 }}>
                <span style={{ color: 'var(--accent)' }}>◇</span> {streamed}<span style={{ animation: 'blink 1s infinite' }}>▍</span>
              </div>
        }
          </div>
      }
        <div style={{ display: 'flex', gap: 6 }}>
          <button onClick={() => setGenerating(true)} disabled={generating} style={{
          flex: 1, height: 40, borderRadius: 100,
          background: generating ? 'var(--ink-300)' : 'var(--ink-900)',
          color: 'var(--ink-0)',
          fontWeight: 600, fontSize: 13,
          display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 8,
          cursor: generating ? 'wait' : 'pointer'
        }}>
            <Icons.Sparkle size={14} />
            {generating ? '生成中…' : '生成する'}
            <span style={{ fontFamily: 'var(--font-display)', fontStyle: 'italic', fontWeight: 400, opacity: 0.7 }}>
              · Generate
            </span>
          </button>
          <button style={{ ...iconBtn, width: 40, height: 40, background: 'var(--ink-100)' }} title="4 variations">
            <Icons.Layers size={16} />
          </button>
        </div>
      </div>
      <style>{`@keyframes blink { 50% { opacity: 0; } }`}</style>
    </>;


  if (_noWrapper) return inner;
  return <aside style={wrapStyle}>{inner}</aside>;
};

const Section = ({ title, subtitle, children }) =>
<section style={{ padding: '14px 14px 4px', borderBottom: '1px solid var(--panel-border)' }}>
    <div style={{ marginBottom: 10 }}>
      <Atoms.SectionTitle jp={title} en={subtitle} />
    </div>
    {children}
  </section>;


const ScribblePreviewSVG = () =>
<svg viewBox="0 0 100 70" preserveAspectRatio="xMidYMid meet" style={{ width: '100%', height: '100%' }}>
    {/* Soft grid like sketch paper */}
    <defs>
      <pattern id="dots" width="6" height="6" patternUnits="userSpaceOnUse">
        <circle cx="3" cy="3" r="0.4" fill="#d4d4d4" />
      </pattern>
    </defs>
    <rect width="100" height="70" fill="url(#dots)" />
    <g stroke="#1a1a1a" strokeWidth="0.5" fill="none" strokeLinecap="round">
      <ellipse cx="42" cy="28" rx="9" ry="11" />
      <path d="M33 22 Q35 12 44 12 Q52 13 51 23 Q49 18 44 19 Q38 19 33 22Z" />
      <path d="M34 39 Q32 50 30 64 M50 39 Q52 50 54 64" />
      <path d="M50 42 Q62 44 66 36" />
      <path d="M66 36 L84 22" />
      <path d="M0 56 Q25 53 50 57 T100 55" />
    </g>
    <text x="93" y="66" fontFamily="ui-monospace" fontSize="3" fill="#737373" textAnchor="end">scribble.png · 768×512</text>
  </svg>;


const chipBtn = {
  height: 24, padding: '0 10px', borderRadius: 100,
  background: 'var(--ink-100)', fontSize: 11, fontWeight: 500,
  display: 'inline-flex', alignItems: 'center', gap: 4
};

const embeddedStyle = {
  width: 'var(--rail-w)', minWidth: 320,
  background: 'var(--panel-bg)',
  borderLeft: '1px solid var(--panel-border)',
  display: 'flex', flexDirection: 'column',
  flexShrink: 0, height: '100%', overflow: 'hidden'
};

const floatingStyle = {
  position: 'absolute',
  right: 24, top: 24, bottom: 24,
  width: 380,
  background: 'var(--panel-bg)',
  border: '1px solid var(--panel-border)',
  borderRadius: 12,
  boxShadow: '0 30px 80px rgba(0,0,0,.18), 0 6px 16px rgba(0,0,0,.08)',
  display: 'flex', flexDirection: 'column',
  zIndex: 50,
  overflow: 'hidden'
};

const bottomDockStyle = {
  width: '100%', height: 320,
  background: 'var(--panel-bg)',
  borderTop: '1px solid var(--panel-border)',
  display: 'flex', flexDirection: 'column',
  flexShrink: 0, overflow: 'hidden'
};

window.AIPanel = AIPanel;


// ============ LayersPanel ============
const LAYER_TYPE_META = {
  sketch:     { label: '下書き',   color: '#9CA3AF' },
  lineart:    { label: '線画',     color: '#1a1a1a' },
  color:      { label: '彩色',     color: '#F59E0B' },
  effects:    { label: '効果',     color: '#8B5CF6' },
  text:       { label: 'テキスト', color: '#3B82F6' },
  background: { label: '背景',     color: '#10B981' },
};

// Small SVG layer thumbnails — suggest layer content at a glance
const LAYER_TYPE_LABEL = {
  background: '背景',
  color:      '彩色',
  lineart:    '線画',
  sketch:     '下書き',
  effects:    '効果',
  text:       'テキスト',
};

const LayerThumb = ({ type, visible }) => {
  const W = 40, H = 32;
  const content = {
    background: (
      <svg width={W} height={H} viewBox={`0 0 ${W} ${H}`}>
        <rect width={W} height={H} fill="#daf0e8"/>
        <rect y={H * 0.52} width={W} height={H * 0.48} fill="#a8d5c2"/>
        <ellipse cx={W * 0.72} cy={H * 0.28} rx={7} ry={5} fill="#f5e8a0" opacity=".95"/>
        <rect x={2} y={H * 0.6} width={8} height={H * 0.28} fill="#6aab7e" rx="1.5"/>
        <rect x={13} y={H * 0.55} width={10} height={H * 0.32} fill="#5a9b6e" rx="1.5"/>
        <rect x={26} y={H * 0.58} width={7} height={H * 0.28} fill="#7abe8e" rx="1.5"/>
      </svg>
    ),
    color: (
      <svg width={W} height={H} viewBox={`0 0 ${W} ${H}`}>
        <rect width={W} height={H} fill="#fff"/>
        <path d={`M4,${H-2} Q10,4 18,7 Q26,10 ${W-2},${H-2} Z`} fill="#f9c784" opacity=".85"/>
        <ellipse cx="18" cy="9" rx="7" ry="6" fill="#f4a261" opacity=".9"/>
        <circle cx="28" cy="14" r="6" fill="#e07a5f" opacity=".8"/>
        <circle cx="10" cy="18" r="4" fill="#a8dadc" opacity=".7"/>
      </svg>
    ),
    lineart: (
      <svg width={W} height={H} viewBox={`0 0 ${W} ${H}`}>
        <rect width={W} height={H} fill="#fafafa"/>
        <path d="M4,26 Q10,6 18,8 Q26,10 36,22" stroke="#1a1a1a" strokeWidth="1.8" fill="none" strokeLinecap="round"/>
        <circle cx="18" cy="8" r="3.5" stroke="#1a1a1a" strokeWidth="1.5" fill="none"/>
        <path d="M14,20 Q18,13 22,14" stroke="#1a1a1a" strokeWidth="1.4" fill="none" strokeLinecap="round"/>
        <path d="M6,20 Q10,16 13,18" stroke="#1a1a1a" strokeWidth="1.2" fill="none" strokeLinecap="round"/>
      </svg>
    ),
    sketch: (
      <svg width={W} height={H} viewBox={`0 0 ${W} ${H}`}>
        <rect width={W} height={H} fill="#fafafa"/>
        <path d="M4,25 Q10,8 18,10 Q26,12 35,23" stroke="#bbb" strokeWidth="1.5" fill="none" strokeLinecap="round" strokeDasharray="2,1"/>
        <circle cx="18" cy="10" r="3.5" stroke="#ccc" strokeWidth="1.2" fill="none"/>
        <path d="M14,19 Q18,14 22,15" stroke="#ccc" strokeWidth="1.2" fill="none" strokeLinecap="round" strokeDasharray="1.5,1"/>
      </svg>
    ),
    effects: (
      <svg width={W} height={H} viewBox={`0 0 ${W} ${H}`}>
        <rect width={W} height={H} fill="#fafafa"/>
        <circle cx="20" cy="16" r="10" fill="none" stroke="#a0c4ff" strokeWidth="1" opacity=".6"/>
        <circle cx="20" cy="16" r="6" fill="none" stroke="#a0c4ff" strokeWidth="1.2" opacity=".8"/>
        {[0,45,90,135,180,225,270,315].map((deg, i) => {
          const r = deg * Math.PI / 180;
          const x1 = 20 + 7 * Math.cos(r);
          const y1 = 16 + 7 * Math.sin(r);
          const x2 = 20 + 11 * Math.cos(r);
          const y2 = 16 + 11 * Math.sin(r);
          return <line key={i} x1={x1} y1={y1} x2={x2} y2={y2} stroke="#7eb8ff" strokeWidth="1.2" opacity=".7"/>;
        })}
        <circle cx="20" cy="16" r="2" fill="#60a5fa" opacity=".9"/>
      </svg>
    ),
    text: (
      <svg width={W} height={H} viewBox={`0 0 ${W} ${H}`}>
        <rect width={W} height={H} fill="#fafafa"/>
        <rect x="4" y="4" width={W-8} height={H-10} rx="3" fill="none" stroke="#888" strokeWidth="1.2"/>
        <polygon points={`9,${H-6} 14,${H-10} 19,${H-6}`} fill="#fafafa" stroke="#888" strokeWidth="1.2"/>
        <line x1="8" y1="11" x2={W-8} y2="11" stroke="#999" strokeWidth="1.2" strokeLinecap="round"/>
        <line x1="8" y1="16" x2={W-12} y2="16" stroke="#ccc" strokeWidth="1" strokeLinecap="round"/>
        <line x1="8" y1="20" x2={W-10} y2="20" stroke="#ccc" strokeWidth="1" strokeLinecap="round"/>
      </svg>
    ),
  };

  return (
    <div style={{
      width: W, height: H, flexShrink: 0, borderRadius: 4, overflow: 'hidden',
      border: '1px solid var(--ink-200)',
      opacity: visible ? 1 : 0.3,
      transition: 'opacity var(--d-fast)',
    }}>
      {content[type] || content.lineart}
    </div>
  );
};

const PANEL_STATUS_COLOR = {
  done:     '#F4D03F',
  sketch:   '#9CA3AF',
  selected: '#3B82F6',
  empty:    '#D1D5DB',
};

const makeDefaultLayers = (panelId, status) => {
  const layers = [
    { id: `${panelId}_tx`, name: 'テキスト', type: 'text',       visible: true,  locked: false, opacity: 100 },
    { id: `${panelId}_fx`, name: '効果',     type: 'effects',    visible: status !== 'empty', locked: false, opacity: 80  },
    { id: `${panelId}_ln`, name: '線画',     type: 'lineart',    visible: true,  locked: false, opacity: 100 },
  ];
  if (status === 'sketch' || status === 'empty') {
    layers.push({ id: `${panelId}_sk`, name: '下書き', type: 'sketch', visible: true, locked: false, opacity: 55 });
  }
  layers.push({ id: `${panelId}_cl`, name: '彩色',  type: 'color',      visible: true,  locked: false, opacity: 100 });
  layers.push({ id: `${panelId}_bg`, name: '背景',   type: 'background', visible: true,  locked: true,  opacity: 100 });
  return layers;
};

const LayersPanel = ({ panels, selectedPanel, onSelectPanel }) => {
  const initMap = () => {
    const m = {};
    panels.forEach(p => { m[p.id] = makeDefaultLayers(p.id, p.status); });
    return m;
  };

  const [layerMap,     setLayerMap]     = useState(initMap);
  const [openPanels,   setOpenPanels]   = useState(() => new Set([panels[0]?.id]));
  const [activeLayerId, setActiveLayerId] = useState(null);
  const sans = 'var(--font-sans)';

  const togglePanel = (id) => setOpenPanels(s => {
    const next = new Set(s);
    if (next.has(id)) next.delete(id); else next.add(id);
    return next;
  });

  const toggleVisible = (panelId, lid) => setLayerMap(m => ({
    ...m, [panelId]: m[panelId].map(l => l.id === lid ? { ...l, visible: !l.visible } : l),
  }));
  const toggleLocked = (panelId, lid) => setLayerMap(m => ({
    ...m, [panelId]: m[panelId].map(l => l.id === lid ? { ...l, locked: !l.locked } : l),
  }));
  const addLayer = (panelId) => {
    const nl = { id: `${panelId}_new_${Date.now()}`, name: '新規レイヤー', type: 'lineart', visible: true, locked: false, opacity: 100 };
    setLayerMap(m => ({ ...m, [panelId]: [nl, ...m[panelId]] }));
    setActiveLayerId(nl.id);
  };

  return (
    <aside style={railRightStyle}>

      {/* Right rail header — auto-save + export + avatar */}
      <div style={{
        height: 'var(--header-h)', flexShrink: 0,
        display: 'flex', alignItems: 'center',
        padding: '0 12px',
        borderBottom: '1px solid var(--panel-border)',
        gap: 8,
      }}>
        <span style={{ fontSize: 11, color: 'var(--ink-500)', display: 'flex', alignItems: 'center', gap: 6, flex: 1 }}>
          <span style={{ width: 6, height: 6, borderRadius: '50%', background: '#10B981', flexShrink: 0 }}></span>
          <span style={{ fontFamily: 'var(--font-sans)' }}>自動保存中</span>
        </span>
        <button style={{ ...pillBtn, background: 'var(--ink-900)', color: 'var(--ink-0)', fontSize: 11, height: 28 }}>
          <Icons.Download size={13} /> エクスポート
        </button>
        <div style={{
          width: 28, height: 28, borderRadius: '50%',
          background: 'linear-gradient(135deg, #B8941F, var(--accent))',
          border: '1px solid var(--ink-200)', flexShrink: 0,
        }} />
      </div>

      {/* Layers label */}
      <div style={{
        padding: '10px 14px 8px', borderBottom: '1px solid var(--panel-border)', flexShrink: 0,
      }}>
        <div style={{
          fontSize: 9, fontWeight: 600, letterSpacing: '0.08em', textTransform: 'uppercase',
          color: 'var(--ink-400)', fontFamily: sans,
        }}>レイヤー · Layers</div>
      </div>

      {/* Accordion */}
      <div style={{ flex: 1, overflow: 'auto' }}>
        {panels.map((panel, pi) => {
          const isOpen   = openPanels.has(panel.id);
          const isSel    = selectedPanel === panel.id;
          const layers   = layerMap[panel.id] || [];

          return (
            <div key={panel.id} style={{ borderBottom: '1px solid var(--panel-border)' }}>

              {/* Panel header row */}
              <div
                onClick={() => { togglePanel(panel.id); onSelectPanel(panel.id); }}
                style={{
                  display: 'flex', alignItems: 'center', gap: 6,
                  padding: '7px 10px 7px 8px',
                  cursor: 'pointer',
                  background: isSel ? 'rgba(244,208,63,.07)' : 'transparent',
                  borderLeft: `2px solid ${isSel ? 'var(--accent)' : 'transparent'}`,
                  transition: 'background var(--d-fast)',
                  userSelect: 'none',
                }}>

                {/* Chevron */}
                <svg width="10" height="10" viewBox="0 0 10 10" fill="none"
                  style={{ flexShrink: 0, transition: 'transform var(--d-fast)', transform: isOpen ? 'rotate(90deg)' : 'rotate(0)' }}>
                  <path d="M3 2l4 3-4 3" stroke="var(--ink-500)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
                </svg>

                {/* コマ番号 */}
                <span style={{
                  fontFamily: sans, fontSize: 11, fontWeight: 600,
                  color: 'var(--ink-900)', flex: 1, minWidth: 0,
                }}>
                  コマ{pi + 1}
                </span>

                {/* Layer count badge */}
                <span style={{
                  fontFamily: sans, fontSize: 9, color: 'var(--ink-400)',
                  background: 'var(--ink-100)', borderRadius: 10,
                  padding: '1px 5px', flexShrink: 0,
                }}>{layers.length}</span>
              </div>

              {/* Layer rows */}
              {isOpen && (
                <div style={{ background: 'var(--ink-50)' }}>
                  {layers.map(layer => {
                    const isActiveLayer = layer.id === activeLayerId;
                    return (
                      <div key={layer.id}
                        onClick={() => setActiveLayerId(layer.id)}
                        style={{
                          display: 'flex', alignItems: 'center',
                          padding: '6px 10px 6px 20px',
                          background: isActiveLayer ? 'rgba(244,208,63,.12)' : 'transparent',
                          borderLeft: `2px solid ${isActiveLayer ? 'var(--accent)' : 'transparent'}`,
                          cursor: 'pointer',
                          opacity: layer.visible ? 1 : 0.38,
                          transition: 'background var(--d-fast), opacity var(--d-fast)',
                          gap: 8,
                        }}>

                        {/* Thumbnail */}
                        <div style={{ flexShrink: 0 }}>
                          <LayerThumb type={layer.type} visible={layer.visible}/>
                        </div>

                        {/* Name + category */}
                        <div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: 2 }}>
                          <div style={{
                            fontFamily: sans, fontSize: 10,
                            fontWeight: isActiveLayer ? 600 : 400,
                            color: 'var(--ink-900)',
                            overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
                          }}>{layer.name}</div>
                          <span style={{
                            fontFamily: sans, fontSize: 9, fontWeight: 500,
                            color: 'var(--ink-500)',
                            background: 'var(--ink-100)',
                            borderRadius: 3, padding: '1px 5px',
                            display: 'inline-block', alignSelf: 'flex-start',
                          }}>{LAYER_TYPE_LABEL[layer.type] || layer.type}</span>
                        </div>

                        {/* Opacity */}
                        <span style={{
                          fontFamily: sans, fontSize: 9, color: 'var(--ink-400)',
                          marginRight: 4, minWidth: 22, textAlign: 'right',
                        }}>{layer.opacity}%</span>

                        {/* Eye */}
                        <button onClick={e => { e.stopPropagation(); toggleVisible(panel.id, layer.id); }}
                          style={{ ...iconBtn, width: 18, height: 18, color: layer.visible ? 'var(--ink-600)' : 'var(--ink-300)' }}>
                          <Icons.Eye size={10}/>
                        </button>

                        {/* Lock */}
                        <button onClick={e => { e.stopPropagation(); toggleLocked(panel.id, layer.id); }}
                          style={{ ...iconBtn, width: 18, height: 18, color: layer.locked ? 'var(--ink-900)' : 'var(--ink-300)' }}>
                          <Icons.Lock size={10}/>
                        </button>
                      </div>
                    );
                  })}
                </div>
              )}
            </div>
          );
        })}
      </div>
    </aside>
  );
};

// ============ RightPanel — tabbed wrapper ============
const RightPanel = ({ panel, panelIdx, onClose, embedded, dock, panels, selectedPanel, onSelectPanel }) => {
  const [tab, setTab] = useState('ai');
  const tabs = [
  { id: 'ai', jp: 'AI生成', en: 'Generate' },
  { id: 'layers', jp: 'レイヤー', en: 'Layers' }];

  const wrapStyle = embedded ? embeddedStyle : dock === 'bottom' ? bottomDockStyle : dock === 'floating' ? floatingStyle : embeddedStyle;
  return (
    <aside style={wrapStyle}>
      {/* Tab bar */}
      <div style={{
        display: 'flex', borderBottom: '1px solid var(--panel-border)',
        padding: '0 8px', flexShrink: 0,
        background: 'var(--panel-bg)'
      }}>
        {tabs.map((t) => {
          const active = tab === t.id;
          return (
            <button key={t.id} onClick={() => setTab(t.id)} style={{
              padding: '11px 10px', fontSize: 12, fontWeight: 500, border: 'none', cursor: 'pointer',
              background: 'transparent',
              color: active ? 'var(--ink-900)' : 'var(--ink-500)',
              borderBottom: `2px solid ${active ? 'var(--ink-900)' : 'transparent'}`,
              marginBottom: -1,
              display: 'flex', alignItems: 'baseline', gap: 5
            }}>
              {t.jp}
              <span style={{ fontFamily: 'var(--font-display)', fontStyle: 'italic', fontSize: 10, color: active ? 'var(--ink-400)' : 'var(--ink-300)' }}>
                {t.en}
              </span>
            </button>);

        })}
        <div style={{ flex: 1 }} />
        {onClose &&
        <button style={{ ...iconBtn, width: 28, height: 28, alignSelf: 'center' }} onClick={onClose}>
            <Icons.X size={13} />
          </button>
        }
      </div>

      {/* Tab content */}
      {tab === 'ai' &&
      <AIPanel panel={panel} panelIdx={panelIdx} onClose={null} embedded={true}
      _noWrapper={true} />
      }
      {tab === 'layers' &&
      <LayersPanel panels={panels} selectedPanel={selectedPanel} onSelectPanel={onSelectPanel} />
      }
    </aside>);

};


// ============ src/editor.jsx ============
// =============== EDITOR ===============
// Composes header + rails + canvas + AI panel based on current layout.

const Editor = ({ tweaks, setTweak, layout, label, scaleHint }) => {
  const [activePageId, setActivePageId] = useState('p2');
  const [selectedPanel, setSelectedPanel] = useState('pn4');
  const [panelMeta, setPanelMeta] = useState(null);
  const [activeTool, setActiveTool] = useState(0);

  // ── Text box state ──────────────────────────────────────
  const [textBoxes, setTextBoxes] = useState({});
  const [selectedTextBoxId, setSelectedTextBoxId] = useState(null);

  const addTextBox = (panelId, defaults = {}) => {
    const id = 'tb_' + Date.now();
    const count = (textBoxes[panelId] || []).length;
    const newBox = {
      id, panelId,
      text: 'テキスト',
      font: defaults.font || MANGA_FONTS[0].fonts[0].value,
      size: defaults.size || 16,
      bold: defaults.bold || false,
      italic: defaults.italic || false,
      vertical: defaults.vertical || false,
      align: defaults.align || 'left',
      color: defaults.color || '#1a1a1a',
      lineHeight: defaults.lineHeight || 1.6,
      letterSpace: defaults.letterSpace || 0,
      _index: count,
    };
    setTextBoxes(prev => ({ ...prev, [panelId]: [...(prev[panelId] || []), newBox] }));
    setSelectedTextBoxId(id);
  };

  const updateTextBox = (id, props) => {
    setTextBoxes(prev => {
      const next = { ...prev };
      for (const pid in next) {
        next[pid] = next[pid].map(tb => tb.id === id ? { ...tb, ...props } : tb);
      }
      return next;
    });
  };

  const selectedTextBox = selectedTextBoxId
    ? Object.values(textBoxes).flat().find(tb => tb.id === selectedTextBoxId) || null
    : null;
  // ───────────────────────────────────────────────────────
  // Tools 0=キャラデザイン, 1=キャラ生成, 2=背景生成, 3=アングル変換, 4=コマ割り...
  const showLeftAI       = activeTool === 0 || activeTool === 2;
  const showLeftCharGen  = activeTool === 1;
  const showLeftAngle    = activeTool === 3;
  const showLeftKomawari = activeTool === 4;
  const showLeftTemplate = activeTool === 5;
  const showLeftText     = activeTool === 6;
  const showLeftLibrary  = activeTool === 7;

  const { PAGES, PANELS } = window.DATA;

  // Determine config from layout name
  const cfg = layoutConfig(layout || tweaks.layout, tweaks);

  const selectedPanelObj = PANELS.find((p) => p.id === selectedPanel);
  const selectedIdx = selectedPanelObj ? PANELS.indexOf(selectedPanelObj) + 1 : null;

  const activePage = PAGES.find((p) => p.id === activePageId);

  // --- LEFT and RIGHT rail content depends on selection ---
  const leftRail =
  <PagesRail
    pages={PAGES} activePageId={activePageId}
    onSelectPage={setActivePageId} narrow={cfg.leftNarrow}
    projectName="月の刀 / Tsuki no Katana"
    activeTool={activeTool}
    onToolChange={setActiveTool}
    aiPanel={
      showLeftAI ? <AIPanel panel={selectedPanelObj} panelIdx={selectedIdx} onClose={null} embedded={true} _noWrapper={true}/> :
      showLeftCharGen ? <CharGenPanel/> :
      showLeftAngle ? <AnglePanel/> :
      showLeftKomawari ? <KomawariPanel/> :
      showLeftTemplate ? <TemplatePanel/> :
      showLeftText ? <TextPanel
        selectedPanel={selectedPanel}
        onAddTextBox={(defaults) => selectedPanel && addTextBox(selectedPanel, defaults)}
      /> :
      showLeftLibrary ? <LibraryPanel/> :
      null
    }
  />;



  // Right rail: always LayersPanel only
  const rightContent = <LayersPanel panels={PANELS} selectedPanel={selectedPanel} onSelectPanel={setSelectedPanel}/>;
  const aiOpen = !!selectedPanel;
  const showBottomDock = false;
  const showFloating = false;
  const showSide = true;

  return (
    <div style={{
      display: 'flex', flexDirection: 'column',
      height: '100%', width: '100%', overflow: 'hidden',
      background: 'var(--ink-50)',
      position: 'relative'
    }}>
      {/* Optional layout label badge */}
      {label &&
      <div style={{
        position: 'absolute', top: 14, left: '50%', transform: 'translateX(-50%)',
        zIndex: 5, pointerEvents: 'none'
      }}>
          <div style={{
          padding: '4px 14px', borderRadius: 100,
          background: 'rgba(255,255,255,.85)', border: '1px solid var(--panel-border)',
          backdropFilter: 'blur(8px)',
          fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 12,
          display: 'flex', alignItems: 'baseline', gap: 6
        }}>
            <span>{label.jp}</span>
            <span style={{ fontStyle: 'italic', color: 'var(--ink-500)', fontWeight: 400 }}>{label.en}</span>
          </div>
        </div>
      }

      <div style={{
        flex: 1,
        display: 'flex',
        flexDirection: showBottomDock ? 'column' : 'row',
        overflow: 'hidden',
        minHeight: 0
      }}>
        {/* Top row (or full content if no bottom dock) */}
        <div style={{ flex: 1, display: 'flex', minHeight: 0, overflow: 'hidden' }}>
          {/* Left rail — includes header + toolbar + properties */}
          {tweaks.railPosition === 'left' && leftRail}

          {/* Center canvas */}
          <div style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0, position: 'relative' }}>
            {/* Canvas context bar — appears above canvas when text box selected */}
            {selectedTextBox && (
              <CanvasContextBar
                textBox={selectedTextBox}
                onUpdate={updateTextBox}
                onDeselect={() => setSelectedTextBoxId(null)}
              />
            )}
            <PageCanvas
              pages={PAGES}
              activePageId={activePageId}
              panels={PANELS}
              selectedPanel={selectedPanel}
              onSelectPanel={setSelectedPanel}
              zoom={cfg.canvasZoom || 1}
              textBoxes={textBoxes}
              selectedTextBoxId={selectedTextBoxId}
              onSelectTextBox={id => { setSelectedTextBoxId(id); if (id) setActiveTool(5); }} />

            {/* Floating page strip — bottom of canvas */}
            <FloatingPageStrip
              pages={PAGES}
              activePageId={activePageId}
              onSelectPage={setActivePageId}
            />

            {/* Canvas top-right controls — undo/redo/zoom */}
            <CanvasControls />

            {/* Floating AI panel (overlaid on canvas) */}
            {showFloating &&
            <RightPanel
              panel={selectedPanelObj} panelIdx={selectedIdx}
              onClose={() => setSelectedPanel(null)}
              panels={PANELS} selectedPanel={selectedPanel} onSelectPanel={setSelectedPanel}
              dock="floating" />

            }
          </div>

          {/* Right rail — always LayersPanel */}
          {rightContent}

          {/* Right rail when railPosition is right */}
          {tweaks.railPosition === 'right' && <ToolsRail />}
          {tweaks.railPosition === 'right' && leftRail}
        </div>

        {/* Bottom-dock AI panel */}
        {showBottomDock &&
        <RightPanel
          panel={selectedPanelObj} panelIdx={selectedIdx}
          onClose={() => setSelectedPanel(null)} dock="bottom"
          panels={PANELS} selectedPanel={selectedPanel} onSelectPanel={setSelectedPanel} />

        }
      </div>
    </div>);

};

const RailWrap = ({ children }) => <>{children}</>;

// Bottom toolbar — status bar only (undo/redo/zoom moved to canvas top-right)
const BottomToolbar = ({ selectedPanelIdx }) =>
<div style={{
  height: 'var(--footer-h)', flexShrink: 0,
  background: 'var(--panel-bg)', borderTop: '1px solid var(--panel-border)',
  display: 'flex', alignItems: 'center', padding: '0 12px', gap: 8,
  fontSize: 11, color: 'var(--ink-600)'
}}>
    <span></span>
    <Divider />
    <span>{selectedPanelIdx ? `` : 'コマ未選択 / no panel'}</span>

    <div style={{ flex: 1 }} />
  </div>;


// ── Canvas context bar — floats above canvas when a text box is selected ──
const CanvasContextBar = ({ textBox, onUpdate, onDeselect }) => {
  if (!textBox) return null;
  const sans = 'var(--font-sans)';
  const allFonts = MANGA_FONTS.flatMap(g => g.fonts);
  const sep = <div style={{ width: 1, height: 18, background: 'var(--ink-200)', margin: '0 2px', flexShrink: 0 }} />;

  return (
    <div style={{
      height: 40, flexShrink: 0,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      padding: '0 12px', gap: 4,
      background: 'var(--panel-bg)',
      borderBottom: '1px solid var(--panel-border)',
      animation: 'ctxIn 160ms cubic-bezier(.2,.8,.2,1)',
      overflow: 'hidden',
      zIndex: 5,
    }}>
      <style>{`@keyframes ctxIn { from { opacity:0; max-height:0; } to { opacity:1; max-height:40px; } }`}</style>

      {/* T icon */}
      <div style={{ color: 'var(--ink-400)', display: 'flex', alignItems: 'center', paddingRight: 4 }}>
        <Icons.Text size={14} />
      </div>
      {sep}

      {/* Font name */}
      <select
        value={textBox.font}
        onChange={e => onUpdate(textBox.id, { font: e.target.value })}
        style={{
          fontFamily: sans, fontSize: 11, fontWeight: 600,
          color: 'var(--ink-900)', background: 'transparent',
          border: 'none', outline: 'none', cursor: 'pointer',
          padding: '0 16px 0 4px', height: 28, borderRadius: 5,
          appearance: 'none', WebkitAppearance: 'none',
          maxWidth: 90,
          backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5' viewBox='0 0 8 5'%3E%3Cpath d='M1 1l3 3 3-3' stroke='%23999' stroke-width='1.3' fill='none' stroke-linecap='round'/%3E%3C/svg%3E")`,
          backgroundRepeat: 'no-repeat', backgroundPosition: 'right 3px center',
        }}
      >
        {allFonts.map(f => <option key={f.value} value={f.value}>{f.label}</option>)}
      </select>
      {sep}

      {/* Size − / input / + */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 2 }}>
        <button onClick={() => onUpdate(textBox.id, { size: Math.max(6, (textBox.size || 16) - 1) })}
          style={{ width: 20, height: 20, borderRadius: 4, border: 'none', background: 'transparent', cursor: 'pointer', color: 'var(--ink-500)', fontSize: 14, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>−</button>
        <input
          type="number" min={6} max={120} value={textBox.size || 16}
          onChange={e => onUpdate(textBox.id, { size: Number(e.target.value) })}
          style={{
            width: 34, height: 24, textAlign: 'center',
            fontFamily: 'var(--font-mono)', fontSize: 11, fontWeight: 600,
            border: '1px solid var(--ink-200)', borderRadius: 4,
            background: 'var(--ink-50)', color: 'var(--ink-900)', outline: 'none',
          }}
        />
        <button onClick={() => onUpdate(textBox.id, { size: Math.min(120, (textBox.size || 16) + 1) })}
          style={{ width: 20, height: 20, borderRadius: 4, border: 'none', background: 'transparent', cursor: 'pointer', color: 'var(--ink-500)', fontSize: 14, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>＋</button>
      </div>
      {sep}

      {/* Bold / Italic */}
      {[
        { label: 'B', key: 'bold',   btnStyle: { fontWeight: 800, fontFamily: sans } },
        { label: 'I', key: 'italic', btnStyle: { fontStyle: 'italic', fontFamily: 'serif' } },
      ].map(btn => {
        const active = textBox[btn.key];
        return (
          <button key={btn.key} onClick={() => onUpdate(textBox.id, { [btn.key]: !active })}
            style={{
              width: 26, height: 26, borderRadius: 5, border: 'none', cursor: 'pointer',
              fontSize: 12, ...btn.btnStyle,
              background: active ? 'var(--ink-900)' : 'transparent',
              color: active ? '#fff' : 'var(--ink-600)',
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              transition: 'background 100ms, color 100ms',
            }}>{btn.label}</button>
        );
      })}
      {sep}

      {/* Align */}
      {['left', 'center', 'right'].map(a => {
        const active = (textBox.align || 'left') === a;
        return (
          <button key={a} onClick={() => onUpdate(textBox.id, { align: a })}
            style={{
              width: 26, height: 26, borderRadius: 5, border: 'none', cursor: 'pointer',
              background: active ? 'var(--ink-900)' : 'transparent',
              color: active ? '#fff' : 'var(--ink-600)',
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              transition: 'background 100ms',
            }}>
            <svg width="13" height="11" viewBox="0 0 13 11" fill="currentColor">
              {a === 'left'   && <><rect x="0" y="0" width="13" height="1.8" rx=".9"/><rect x="0" y="4.6" width="8"  height="1.8" rx=".9"/><rect x="0" y="9.2" width="10" height="1.8" rx=".9"/></>}
              {a === 'center' && <><rect x="0" y="0" width="13" height="1.8" rx=".9"/><rect x="2.5" y="4.6" width="8"  height="1.8" rx=".9"/><rect x="1.5" y="9.2" width="10" height="1.8" rx=".9"/></>}
              {a === 'right'  && <><rect x="0" y="0" width="13" height="1.8" rx=".9"/><rect x="5" y="4.6" width="8"  height="1.8" rx=".9"/><rect x="3" y="9.2" width="10" height="1.8" rx=".9"/></>}
            </svg>
          </button>
        );
      })}
      {sep}

      {/* Color swatch */}
      <div style={{ position: 'relative', width: 22, height: 22, flexShrink: 0 }}>
        <div style={{
          width: 22, height: 22, borderRadius: 5,
          background: textBox.color || '#1a1a1a',
          border: '1.5px solid var(--ink-300)', cursor: 'pointer',
        }} />
        <input type="color" value={textBox.color || '#1a1a1a'}
          onChange={e => onUpdate(textBox.id, { color: e.target.value })}
          style={{ position: 'absolute', inset: 0, opacity: 0, cursor: 'pointer', width: '100%', height: '100%' }}
        />
      </div>
      {sep}

      {/* Deselect */}
      <button onClick={onDeselect}
        style={{
          width: 24, height: 24, borderRadius: 5, border: 'none',
          cursor: 'pointer', color: 'var(--ink-400)', background: 'transparent',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          transition: 'background 100ms',
        }}
        onMouseEnter={e => e.currentTarget.style.background = 'var(--ink-100)'}
        onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
      >
        <Icons.X size={12} />
      </button>
    </div>
  );
};

// Canvas top-right floating controls — undo/redo + zoom
const CanvasControls = () => {
  const [zoom, setZoom] = React.useState(72);
  return (
    <div style={{
      position: 'absolute', top: 12, right: 12,
      display: 'flex', alignItems: 'center', gap: 2,
      background: 'var(--panel-bg)',
      border: '1px solid var(--panel-border)',
      borderRadius: 8,
      boxShadow: '0 2px 8px rgba(0,0,0,.08)',
      padding: '2px 4px',
      zIndex: 10,
      fontSize: 11, color: 'var(--ink-600)'
    }}>
      <button style={iconBtn} title="Undo"><Icons.Undo size={14} /></button>
      <button style={iconBtn} title="Redo"><Icons.Redo size={14} /></button>
      <div style={{ width: 1, height: 16, background: 'var(--ink-200)', margin: '0 2px' }} />
      <button style={iconBtn} title="縮小" onClick={() => setZoom((z) => Math.max(10, z - 10))}><Icons.Minus size={14} /></button>
      <span style={{ fontFamily: 'var(--font-mono)', minWidth: 36, textAlign: 'center' }}>{zoom}%</span>
      <button style={iconBtn} title="拡大" onClick={() => setZoom((z) => Math.min(200, z + 10))}><Icons.Plus size={14} /></button>
    </div>);

};

const Divider = () => <span style={{ width: 1, height: 16, background: 'var(--ink-200)' }} />;

function layoutConfig(layout, tweaks) {
  // Three named layouts: atelier, focus, bench
  const aiPlacement = tweaks.aiPanelPlacement || 'side';
  switch (layout) {
    case 'focus':
      return { aiPlacement: 'floating', leftNarrow: true, rightNarrow: true, canvasZoom: 1 };
    case 'bench':
      return { aiPlacement: 'bottom', leftNarrow: false, rightNarrow: false, canvasZoom: 0.85 };
    case 'atelier':
    default:
      return { aiPlacement, leftNarrow: false, rightNarrow: false, canvasZoom: 1 };
  }
}

window.Editor = Editor;


// ============ src/app.jsx ============
// =============== APP ===============
// Top-level: hosts Editor and a design_canvas-mode for multiple variations.
// Tweaks panel toggles between single editor and canvas of variations.

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "layout": "atelier",
  "theme": "light",
  "railPosition": "left",
  "aiPanelPlacement": "side",
  "showHalos": true,
  "viewMode": "single"
} /*EDITMODE-END*/;

const App = () => {
  const [tweaks, setTweak] = window.useTweaks ? window.useTweaks(TWEAK_DEFAULTS) : useFallbackTweaks(TWEAK_DEFAULTS);

  // Apply theme to <html>
  useEffect(() => {
    document.documentElement.setAttribute('data-theme', tweaks.theme || 'light');
  }, [tweaks.theme]);

  // ---- Tweaks panel UI ----
  const TP = window.TweaksPanel;
  const TS = window.TweakSection;
  const TR = window.TweakRadio;
  const TT = window.TweakToggle;

  const tweaksPanel = TP &&
  <TP title="Tweaks">
      {TS && <TS label="View" />}
      {TR && <TR label="Mode" value={tweaks.viewMode}
    options={['single', 'canvas']}
    onChange={(v) => setTweak('viewMode', v)} />}
      {TS && <TS label="Theme" />}
      {TR && <TR label="Theme" value={tweaks.theme}
    options={['light', 'dark']}
    onChange={(v) => setTweak('theme', v)} />}
      {TS && <TS label="Layout" />}
      {TR && <TR label="Pages rail" value={tweaks.railPosition}
    options={['left', 'right']}
    onChange={(v) => setTweak('railPosition', v)} />}
      {TR && <TR label="AI panel" value={tweaks.aiPanelPlacement}
    options={['side', 'float', 'bottom']}
    onChange={(v) => setTweak('aiPanelPlacement', v === 'float' ? 'floating' : v)} />}
    </TP>;


  // Single mode
  if (tweaks.viewMode !== 'canvas') {
    return (
      <>
        <Editor tweaks={tweaks} setTweak={setTweak} />
        {tweaksPanel}
      </>);

  }

  // Canvas mode — three layout variations side-by-side
  const DC = window.DesignCanvas;
  const DCS = window.DCSection;
  const DCA = window.DCArtboard;
  if (!DC) return <div>Loading…</div>;

  return (
    <>
      <DC>
        <DCS id="layouts" title="Holonica Studio — Editor Layouts" subtitle="3 explorations · 1440×900">
          <DCA id="atelier" label="Atelier — classical: rails L+R, AI in side inspector" width={1440} height={900}>
            <Editor tweaks={{ ...tweaks, aiPanelPlacement: 'side' }} layout="atelier"
            label={{ jp: 'Atelier', en: 'classical · rails + side AI' }} />
          </DCA>
          <DCA id="focus" label="Focus — collapsed rails, floating AI overlay" width={1440} height={900}>
            <Editor tweaks={{ ...tweaks }} layout="focus"
            label={{ jp: 'Focus', en: 'collapsed rails · floating AI' }} />
          </DCA>
          <DCA id="bench" label="Bench — bottom-docked AI like a workbench drawer" width={1440} height={900}>
            <Editor tweaks={{ ...tweaks }} layout="bench"
            label={{ jp: 'Bench', en: 'workbench drawer' }} />
          </DCA>
        </DCS>
      </DC>
      {tweaksPanel}
    </>);

};

// Fallback if useTweaks helper isn't loaded
function useFallbackTweaks(defaults) {
  const [t, setT] = useState(defaults);
  const setTweak = (k, v) => {
    if (typeof k === 'object') setT((prev) => ({ ...prev, ...k }));else
    setT((prev) => ({ ...prev, [k]: v }));
  };
  return [t, setTweak];
}

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