<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PornCode Font Converter</title>
<style>
  :root {
    --bg:      #0f0f13;
    --panel:   #1a1a24;
    --border:  #2d2d40;
    --accent:  #7c5cfc;
    --accent2: #a084fd;
    --text:    #e2e2f0;
    --muted:   #7a7a9a;
    --green:   #4ade80;
    --red:     #f87171;
    --card:    #13131d;
  }

  * { box-sizing: border-box; margin: 0; padding: 0; }

  body {
    background: var(--bg);
    color: var(--text);
    font-family: 'SF Mono', 'Fira Mono', monospace;
    font-size: 13px;
    min-height: 100vh;
  }

  /* ── Layout ── */
  .layout {
    display: grid;
    grid-template-columns: 300px 1fr;
    grid-template-rows: 56px 1fr;
    height: 100vh;
  }

  header {
    grid-column: 1 / -1;
    background: var(--panel);
    border-bottom: 1px solid var(--border);
    display: flex;
    align-items: center;
    padding: 0 20px;
    gap: 16px;
  }

  header h1 {
    font-size: 15px;
    font-weight: 600;
    letter-spacing: 0.05em;
    color: var(--accent2);
  }

  header .badge {
    font-size: 11px;
    background: var(--accent);
    color: #fff;
    padding: 2px 8px;
    border-radius: 10px;
    opacity: 0.85;
  }

  .header-right {
    margin-left: auto;
    display: flex;
    gap: 10px;
    align-items: center;
  }

  /* ── Sidebar ── */
  .sidebar {
    background: var(--panel);
    border-right: 1px solid var(--border);
    overflow-y: auto;
    padding: 16px;
    display: flex;
    flex-direction: column;
    gap: 16px;
  }

  .section-title {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--muted);
    margin-bottom: 8px;
  }

  .field-group { display: flex; flex-direction: column; gap: 8px; }

  .field {
    display: flex;
    flex-direction: column;
    gap: 4px;
  }

  .field label {
    font-size: 11px;
    color: var(--muted);
  }

  .field input {
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 6px;
    color: var(--text);
    padding: 6px 10px;
    font-size: 13px;
    font-family: inherit;
    width: 100%;
    outline: none;
    transition: border-color .15s;
  }

  .field input:focus { border-color: var(--accent); }

  .field input[type=number] { -moz-appearance: textfield; }

  .metrics-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
  }

  /* ── Buttons ── */
  .btn {
    border: none;
    border-radius: 8px;
    padding: 9px 16px;
    font-size: 13px;
    font-family: inherit;
    cursor: pointer;
    font-weight: 600;
    transition: opacity .15s, transform .08s;
    display: flex;
    align-items: center;
    gap: 6px;
  }

  .btn:hover  { opacity: 0.88; }
  .btn:active { transform: scale(0.97); }

  .btn-primary { background: var(--accent); color: #fff; width: 100%; justify-content: center; }
  .btn-ghost   { background: transparent; color: var(--accent2); border: 1px solid var(--border); }
  .btn-sm      { padding: 5px 12px; font-size: 12px; }

  .btn-generate {
    background: linear-gradient(135deg, #7c5cfc, #a855f7);
    color: #fff;
    width: 100%;
    justify-content: center;
    padding: 12px;
    font-size: 14px;
    border-radius: 10px;
  }

  /* ── Download bar ── */
  #download-bar {
    display: none;
    background: #0d1f0d;
    border: 1px solid #2a4a2a;
    border-radius: 8px;
    padding: 10px 12px;
    gap: 8px;
    flex-direction: column;
  }
  #download-bar.show { display: flex; }

  .dl-row { display: flex; gap: 8px; }

  .btn-dl {
    flex: 1;
    background: #1a2e1a;
    color: var(--green);
    border: 1px solid #2a4a2a;
    border-radius: 6px;
    padding: 7px 10px;
    font-size: 12px;
    font-family: inherit;
    cursor: pointer;
    font-weight: 600;
    text-align: center;
    text-decoration: none;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
  }
  .btn-dl:hover { background: #223322; }

  /* ── Main content ── */
  .main {
    overflow-y: auto;
    padding: 20px;
    display: flex;
    flex-direction: column;
    gap: 16px;
  }

  /* ── Stats bar ── */
  .stats-bar {
    display: flex;
    gap: 20px;
    align-items: center;
    padding: 10px 16px;
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: 10px;
  }

  .stat { display: flex; flex-direction: column; gap: 2px; }
  .stat-value { font-size: 18px; font-weight: 700; color: var(--accent2); }
  .stat-label { font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: .08em; }

  .filter-row {
    margin-left: auto;
    display: flex;
    gap: 6px;
  }

  .filter-btn {
    background: transparent;
    border: 1px solid var(--border);
    color: var(--muted);
    border-radius: 6px;
    padding: 4px 12px;
    font-size: 12px;
    font-family: inherit;
    cursor: pointer;
    transition: all .15s;
  }
  .filter-btn.active, .filter-btn:hover {
    border-color: var(--accent);
    color: var(--accent2);
    background: rgba(124,92,252,.12);
  }

  /* ── Glyph grid ── */
  #glyph-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
    gap: 10px;
  }

  .glyph-card {
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 8px;
    cursor: pointer;
    transition: border-color .15s, transform .1s;
    position: relative;
  }

  .glyph-card:hover {
    border-color: var(--accent);
    transform: translateY(-2px);
  }

  .glyph-card.selected {
    border-color: var(--accent2);
    box-shadow: 0 0 0 2px rgba(160,132,253,.25);
  }

  .glyph-preview {
    width: 100%;
    aspect-ratio: 1;
    display: block;
  }

  .glyph-label {
    text-align: center;
    margin-top: 4px;
    font-size: 16px;
    color: var(--text);
    line-height: 1;
  }

  .glyph-meta {
    text-align: center;
    font-size: 10px;
    color: var(--muted);
    margin-top: 2px;
  }

  .row-badge {
    position: absolute;
    top: 4px;
    right: 4px;
    font-size: 9px;
    background: rgba(124,92,252,.25);
    color: var(--accent2);
    border-radius: 4px;
    padding: 1px 4px;
  }

  /* ── Detail panel ── */
  #detail-panel {
    display: none;
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 16px;
    gap: 16px;
    align-items: flex-start;
  }
  #detail-panel.show { display: flex; }

  .detail-preview {
    width: 200px;
    height: 140px;
    flex-shrink: 0;
    background: var(--card);
    border-radius: 8px;
    border: 1px solid var(--border);
  }

  .detail-info { flex: 1; }
  .detail-info h3 { font-size: 32px; line-height: 1; color: var(--accent2); }
  .glyph-adjust {
    margin-top: 8px;
    display: grid;
    grid-template-columns: repeat(3, minmax(180px, 1fr));
    gap: 10px;
  }
  .adjust-group {
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 8px;
    background: var(--card);
  }
  .adjust-title {
    font-size: 10px;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: .08em;
    margin-bottom: 6px;
  }
  .adjust-row {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 6px;
  }
  .adjust-row:last-child { margin-bottom: 0; }
  .adjust-row label {
    width: 72px;
    font-size: 11px;
    color: var(--muted);
  }
  .adjust-row input {
    flex: 1;
    min-width: 0;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 6px;
    color: var(--text);
    padding: 5px 8px;
    font-family: inherit;
  }
  .arrow-pad {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: repeat(3, 1fr);
    gap: 4px;
    width: 108px;
  }
  .arrow-pad .btn { justify-content: center; padding: 5px 0; }
  .arrow-pad .empty { visibility: hidden; }
  .detail-actions {
    margin-top: 8px;
    display: flex;
    gap: 8px;
    align-items: center;
  }
  .detail-grid {
    margin-top: 10px;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6px 16px;
  }
  .detail-row { font-size: 11px; }
  .detail-row span { color: var(--muted); }

  /* ── Status / log ── */
  #status {
    padding: 8px 12px;
    border-radius: 8px;
    font-size: 12px;
    display: none;
  }
  #status.show { display: block; }
  #status.ok  { background: #0d1f0d; color: var(--green); border: 1px solid #2a4a2a; }
  #status.err { background: #1f0d0d; color: var(--red);   border: 1px solid #4a2a2a; }
  #status.info { background: var(--panel); color: var(--muted); border: 1px solid var(--border); }

  /* ── Divider ── */
  .divider { border: none; border-top: 1px solid var(--border); }

  /* ── Spinner ── */
  .spinner {
    width: 14px; height: 14px;
    border: 2px solid rgba(255,255,255,.3);
    border-top-color: #fff;
    border-radius: 50%;
    animation: spin .7s linear infinite;
    display: none;
  }
  .spinning .spinner { display: block; }
  .spinning .btn-text { display: none; }
  @keyframes spin { to { transform: rotate(360deg); } }

  /* ── Preview text ── */
  #preview-section {
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 16px;
  }

  #preview-section h3 {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: .12em;
    color: var(--muted);
    margin-bottom: 10px;
  }

  #preview-input {
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 8px;
    color: var(--text);
    padding: 8px 12px;
    font-size: 14px;
    font-family: inherit;
    width: 100%;
    outline: none;
    margin-bottom: 10px;
    resize: vertical;
  }
  #preview-input:focus { border-color: var(--accent); }

  #preview-render {
    background: var(--card);
    border-radius: 8px;
    padding: 12px;
    min-height: 60px;
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    align-items: flex-start;
    flex-direction: column;
  }

  .preview-glyph {
    display: inline-block;
    width: 42px;
    height: 56px;
    vertical-align: middle;
    flex: 0 0 auto;
    overflow: visible;
  }

  .glyph-adjust {
    margin-top: 10px;
    display: flex;
    gap: 8px;
    align-items: center;
    flex-wrap: wrap;
  }

  .glyph-adjust input {
    width: 72px;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 6px;
    color: var(--text);
    padding: 5px 8px;
    font-family: inherit;
  }
</style>
</head>
<body>
<div class="layout">

  <!-- ── Header ── -->
  <header>
    <h1>⬡ PornCode Font Converter</h1>
    <a href="https://porncode.ru/" target="_blank" rel="noopener" class="btn btn-ghost btn-sm">porncode.ru</a>
    <span class="badge" id="header-glyph-count">…</span>
    <div class="header-right">
      <button class="btn btn-ghost btn-sm" onclick="reloadGlyphs()">↺ Reload SVG</button>
    </div>
  </header>

  <!-- ── Sidebar ── -->
  <aside class="sidebar">

    <!-- Metadata -->
    <div>
      <div class="section-title">Метаданные шрифта</div>
      <div class="field-group">
        <div class="field">
          <label>Семейство (Family Name)</label>
          <input type="text" id="family_name" placeholder="PM Fake Font">
        </div>
        <div class="field">
          <label>Начертание (Style)</label>
          <input type="text" id="style_name" placeholder="Regular">
        </div>
        <div class="field">
          <label>Версия</label>
          <input type="text" id="version" placeholder="1.0.0">
        </div>
        <div class="field">
          <label>Copyright</label>
          <input type="text" id="copyright" placeholder="Copyright 2026">
        </div>
        <div class="field">
          <label>Дизайнер</label>
          <input type="text" id="designer" placeholder="">
        </div>
      </div>
    </div>

    <hr class="divider">

    <!-- SVG source -->
    <div>
      <div class="section-title">Источник SVG</div>
      <div class="field-group">
        <button class="btn btn-ghost btn-sm" onclick="pickSvgFolderDialog()">📁 Выбрать папку (диалог)</button>
        <div id="svg-source-info" style="font-size:11px;color:var(--muted);line-height:1.4;"></div>
      </div>
    </div>

    <hr class="divider">

    <!-- Metrics -->
    <div>
      <div class="section-title">Метрики (font units)</div>
      <div class="metrics-grid">
        <div class="field">
          <label>UPM (единиц в em-квадрате шрифта)</label>
          <input type="number" id="upm" value="1000">
        </div>
        <div class="field">
          <label>Cap Height (высота заглавных букв)</label>
          <input type="number" id="cap_height" value="700">
        </div>
        <div class="field">
          <label>Ascender (верхняя граница строки)</label>
          <input type="number" id="ascender" value="780">
        </div>
        <div class="field">
          <label>Descender (нижняя граница строки)</label>
          <input type="number" id="descender" value="-220">
        </div>
        <div class="field">
          <label>x-Height (высота строчных букв)</label>
          <input type="number" id="x_height" value="500">
        </div>
        <div class="field">
          <label>Line Gap (межстрочное расстояние)</label>
          <input type="number" id="line_gap" value="0">
        </div>
        <div class="field">
          <label>Left Bearing (левый отступ глифа)</label>
          <input type="number" id="left_bearing" value="40">
        </div>
        <div class="field">
          <label>Right Bearing (правый отступ глифа)</label>
          <input type="number" id="right_bearing" value="40">
        </div>
        <div class="field">
          <label>Tracking (доп. межбуквенный интервал)</label>
          <input type="number" id="tracking" value="0">
        </div>
        <div class="field">
          <label>Space Width (ширина пробела)</label>
          <input type="number" id="space_width" value="300">
        </div>
      </div>
    </div>

    <hr class="divider">

    <!-- Save config -->
    <button class="btn btn-ghost btn-sm" onclick="saveConfig()">💾 Сохранить настройки</button>
    <button class="btn btn-ghost btn-sm" onclick="resetGlyphTransforms()">↺ Сбросить правки глифов</button>
    <button class="btn btn-ghost btn-sm" onclick="applyRecommendedMetrics()">✓ Рекоменд. метрики</button>

    <hr class="divider">

    <!-- Generate -->
    <button class="btn btn-generate" id="btn-generate" onclick="generateFont()">
      <div class="spinner"></div>
      <span class="btn-text">⚡ Генерировать шрифт</span>
    </button>

    <!-- Download links -->
    <div id="download-bar">
      <div class="section-title" style="margin:0;">Скачать</div>
      <div class="dl-row">
        <a class="btn-dl" id="dl-otf" href="/api/download/otf">↓ OTF</a>
        <a class="btn-dl" id="dl-ttf" href="/api/download/ttf">↓ TTF</a>
        <a class="btn-dl" id="dl-woff2" href="/api/download/woff2">↓ WOFF2</a>
      </div>
    </div>

    <!-- Status -->
    <div id="status"></div>

  </aside>

  <!-- ── Main ── -->
  <main class="main">

    <!-- Stats -->
    <div class="stats-bar">
      <div class="stat">
        <div class="stat-value" id="stat-total">–</div>
        <div class="stat-label">Глифов</div>
      </div>
      <div class="stat">
        <div class="stat-value" id="stat-latin">–</div>
        <div class="stat-label">Латиница</div>
      </div>
      <div class="stat">
        <div class="stat-value" id="stat-cyrillic">–</div>
        <div class="stat-label">Кириллица</div>
      </div>
      <div class="stat">
        <div class="stat-value" id="stat-scale">–</div>
        <div class="stat-label">Scale</div>
      </div>
      <div class="filter-row">
        <button class="filter-btn active" onclick="filterGlyphs('all', this)">Все</button>
        <button class="filter-btn" onclick="filterGlyphs('latin', this)">Latin</button>
        <button class="filter-btn" onclick="filterGlyphs('cyrillic', this)">Кирилл.</button>
        <button class="filter-btn" onclick="filterGlyphs('row0', this)">Строка 1</button>
        <button class="filter-btn" onclick="filterGlyphs('row1', this)">Строка 2</button>
      </div>
    </div>

    <!-- Detail panel (shown when glyph selected) -->
    <div id="detail-panel">
      <svg class="detail-preview" id="detail-svg" viewBox="0 0 100 100"></svg>
      <div class="detail-info">
        <h3 id="detail-char">–</h3>
        <div class="glyph-adjust">
          <div class="adjust-group">
            <div class="adjust-title">Положение</div>
            <div class="adjust-row">
              <div class="arrow-pad">
                <div class="empty"></div>
                <button class="btn btn-ghost btn-sm" onclick="bumpGlyphTransform(0,8,0)">↑</button>
                <div class="empty"></div>
                <button class="btn btn-ghost btn-sm" onclick="bumpGlyphTransform(-8,0,0)">←</button>
                <div class="empty"></div>
                <button class="btn btn-ghost btn-sm" onclick="bumpGlyphTransform(8,0,0)">→</button>
                <div class="empty"></div>
                <button class="btn btn-ghost btn-sm" onclick="bumpGlyphTransform(0,-8,0)">↓</button>
                <div class="empty"></div>
              </div>
            </div>
            <div class="adjust-row"><label>X сдвиг</label><input type="number" id="glyph-x-shift" step="1" value="0"></div>
            <div class="adjust-row"><label>Y сдвиг</label><input type="number" id="glyph-y-shift" step="1" value="0"></div>
          </div>
          <div class="adjust-group">
            <div class="adjust-title">Размер</div>
            <div class="adjust-row"><label>Масштаб</label><input type="number" id="glyph-scale" step="0.01" min="0.1" value="1"></div>
            <div class="adjust-row">
              <label></label>
              <button class="btn btn-ghost btn-sm" onclick="bumpGlyphTransform(0,0,0.05)">Крупнее +</button>
              <button class="btn btn-ghost btn-sm" onclick="bumpGlyphTransform(0,0,-0.05)">Меньше -</button>
            </div>
          </div>
          <div class="adjust-group">
            <div class="adjust-title">Поля Глифа</div>
            <div class="adjust-row">
              <label>Левое (LSB)</label>
              <button class="btn btn-ghost btn-sm" onclick="bumpGlyphBearings(-8,0)">-</button>
              <button class="btn btn-ghost btn-sm" onclick="bumpGlyphBearings(8,0)">+</button>
              <input type="number" id="glyph-lsb-delta" step="1" value="0">
            </div>
            <div class="adjust-row">
              <label>Правое (RSB)</label>
              <button class="btn btn-ghost btn-sm" onclick="bumpGlyphBearings(0,-8)">-</button>
              <button class="btn btn-ghost btn-sm" onclick="bumpGlyphBearings(0,8)">+</button>
              <input type="number" id="glyph-rsb-delta" step="1" value="0">
            </div>
          </div>
        </div>
        <div class="detail-actions">
          <button class="btn btn-ghost btn-sm" onclick="saveGlyphTransform()">Применить</button>
          <span style="font-size:11px;color:var(--muted)">Сдвиг/масштаб внутри ячейки глифа + отступы слева/справа.</span>
        </div>
        <div id="selected-usage" style="margin-top:6px;font-size:11px;color:var(--muted);"></div>
        <div class="detail-grid" id="detail-grid"></div>
      </div>
    </div>

    <!-- Preview text box -->
    <div id="preview-section">
      <h3>Предпросмотр текста</h3>
      <div style="display:flex;gap:8px;align-items:center;margin-bottom:10px;">
        <span style="font-size:11px;color:var(--muted)">Размер предпросмотра:</span>
        <input type="range" id="preview-size" min="48" max="220" step="2" value="120" oninput="renderPreview()">
        <span id="preview-size-value" style="font-size:11px;color:var(--text)">120 px</span>
      </div>
      <textarea id="preview-input" rows="3" placeholder="Введите текст для предпросмотра..." oninput="renderPreview()"></textarea>
      <div id="preview-render"></div>
    </div>

    <!-- Glyph grid -->
    <div id="glyph-grid"></div>

  </main>
</div>

<script>
// ── State ──────────────────────────────────────────────────────────────────
let allGlyphs = [];
let currentFilter = 'all';
let selectedChar  = null;
const glyphMap    = {};   // char → glyph data
let liveMetrics = {
  upm: 1000,
  ascender: 780,
  descender: -220,
  line_gap: 0,
  left_bearing: 40,
  right_bearing: 40,
  tracking: 0,
  space_width: 300,
};

// ── Boot ───────────────────────────────────────────────────────────────────
window.addEventListener('DOMContentLoaded', () => {
  loadConfig();
  loadGlyphs();
});

// ── Config ─────────────────────────────────────────────────────────────────
function loadConfig() {
  fetch('/api/config').then(r => r.json()).then(cfg => {
    const fields = ['family_name','style_name','version','copyright','designer',
                    'upm','ascender','descender','line_gap','cap_height',
                    'x_height','left_bearing','right_bearing','tracking','space_width'];
    fields.forEach(f => {
      const el = document.getElementById(f);
      if (el && cfg[f] !== undefined) el.value = cfg[f];
    });
    updateSvgSourceInfo(cfg);
  });
}

function updateSvgSourceInfo(cfg) {
  const el = document.getElementById('svg-source-info');
  if (!el) return;
  const en = cfg?.svg_en || '—';
  const ru = cfg?.svg_ru || '—';
  el.innerHTML = `EN: <code>${en}</code><br>RU: <code>${ru}</code>`;
}

function pickSvgFolderDialog() {
  fetch(`${window.location.origin}/api/svg-folder-dialog`, { method: 'POST' })
    .then(async (r) => {
      const text = await r.text();
      let data = {};
      if (text) {
        try {
          data = JSON.parse(text);
        } catch (_) {
          throw new Error(`Некорректный ответ сервера (${r.status})`);
        }
      }
      if (!r.ok || !data.ok) {
        throw new Error(data.error || `HTTP ${r.status}`);
      }
      return data;
    })
    .then(d => {
      showStatus('Папка SVG применена', 'ok');
      updateSvgSourceInfo(d);
      loadGlyphs(true);
    })
    .catch(e => {
      const msg = (e && e.message) ? e.message : String(e);
      showStatus('Ошибка папки SVG: ' + msg, 'err');
    });
}

function saveConfig() {
  const cfg = {};
  ['family_name','style_name','version','copyright','designer',
   'upm','ascender','descender','line_gap','cap_height',
   'x_height','left_bearing','right_bearing','tracking','space_width'].forEach(f => {
    const el = document.getElementById(f);
    if (el) cfg[f] = el.value;
  });

  fetch('/api/config', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify(cfg),
  }).then(r => r.json()).then(d => {
    showStatus('Настройки сохранены', 'ok');
    // Reload glyph preview with new metrics
    loadGlyphs();
  });
}

function resetGlyphTransforms() {
  fetch('/api/reset-glyph-transforms', { method: 'POST' })
    .then(r => r.json())
    .then(d => {
      if (!d.ok) throw new Error('Reset failed');
      showStatus('Правки глифов сброшены', 'ok');
      selectedChar = null;
      loadGlyphs(true);
    })
    .catch(e => showStatus('Ошибка сброса: ' + e, 'err'));
}

function applyRecommendedMetrics() {
  fetch('/api/apply-recommended-metrics', { method: 'POST' })
    .then(r => r.json())
    .then(d => {
      if (!d.ok) throw new Error('Apply failed');
      loadConfig();
      showStatus('Применены рекомендованные метрики', 'ok');
      loadGlyphs(true);
    })
    .catch(e => showStatus('Ошибка метрик: ' + e, 'err'));
}

// ── Glyphs ─────────────────────────────────────────────────────────────────
function loadGlyphs(forceReload = false) {
  showStatus('Загрузка глифов…', 'info');
  const url = forceReload ? '/api/glyphs?reload=1' : '/api/glyphs';
  fetch(url).then(r => r.json()).then(data => {
    allGlyphs = data.glyphs;
    allGlyphs.forEach(g => { glyphMap[g.char] = g; });
    liveMetrics = data.metrics || liveMetrics;

    // Stats
    const latin    = allGlyphs.filter(g => g.unicode_val >= 0x61 && g.unicode_val <= 0x7a).length;
    const cyrillic = allGlyphs.filter(g => g.unicode_val >= 0x400 && g.unicode_val <= 0x4ff).length;
    document.getElementById('stat-total').textContent    = data.total;
    document.getElementById('stat-latin').textContent    = latin;
    document.getElementById('stat-cyrillic').textContent = cyrillic;
    document.getElementById('stat-scale').textContent    = data.scale;
    document.getElementById('header-glyph-count').textContent = data.total + ' глифов';

    renderGrid(allGlyphs);
    if (selectedChar && glyphMap[selectedChar]) {
      selectGlyph(selectedChar);
    }
    renderPreview();
    hideStatus();
  }).catch(e => {
    showStatus('Ошибка загрузки: ' + e, 'err');
  });
}

function reloadGlyphs() { loadGlyphs(true); }

function metricValue(id, fallback = 0) {
  const v = parseInt(document.getElementById(id)?.value, 10);
  return Number.isFinite(v) ? v : fallback;
}

function calcLiveScale() {
  const capHeight = metricValue('cap_height', 700);
  let maxSvgHeight = 1;
  for (const g of allGlyphs) {
    const svgH = Math.max(1, g.baseline - g.bbox[1]);
    if (svgH > maxSvgHeight) maxSvgHeight = svgH;
  }
  return capHeight / maxSvgHeight;
}

function resolvePreviewGlyph(ch) {
  // Prefer exact codepoint first; fallback to lowercase only if exact missing.
  return glyphMap[ch] || glyphMap[ch.toLowerCase()] || null;
}

// ── Grid ───────────────────────────────────────────────────────────────────
function filterGlyphs(type, btn) {
  currentFilter = type;
  document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
  btn.classList.add('active');

  let filtered;
  if      (type === 'all')      filtered = allGlyphs;
  else if (type === 'latin')    filtered = allGlyphs.filter(g => g.unicode_val >= 0x61 && g.unicode_val <= 0x7a);
  else if (type === 'cyrillic') filtered = allGlyphs.filter(g => g.unicode_val >= 0x400 && g.unicode_val <= 0x4ff);
  else if (type === 'row0')     filtered = allGlyphs.filter(g => g.row === 0);
  else if (type === 'row1')     filtered = allGlyphs.filter(g => g.row === 1);
  else filtered = allGlyphs;

  renderGrid(filtered);
}

function renderGrid(glyphs) {
  const grid = document.getElementById('glyph-grid');
  grid.innerHTML = '';

  glyphs.forEach(g => {
    const card = document.createElement('div');
    card.className = 'glyph-card' + (g.char === selectedChar ? ' selected' : '');
    card.dataset.char = g.char;
    card.onclick = () => selectGlyph(g.char);

    card.innerHTML = `
      <span class="row-badge">R${g.row}</span>
      <svg class="glyph-preview" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
        <path d="${g.preview_d}" fill="#a084fd"/>
      </svg>
      <div class="glyph-label">${g.char}</div>
      <div class="glyph-meta">${g.unicode}</div>
    `;
    grid.appendChild(card);
  });
}

// ── Detail panel ───────────────────────────────────────────────────────────
function selectGlyph(char) {
  selectedChar = char;
  const g = glyphMap[char];
  if (!g) return;

  // Update selection highlight
  document.querySelectorAll('.glyph-card').forEach(c => {
    c.classList.toggle('selected', c.dataset.char === char);
  });

  // Show detail panel
  const panel = document.getElementById('detail-panel');
  panel.classList.add('show');

  document.getElementById('detail-char').textContent = char;
  document.getElementById('glyph-x-shift').value = g.x_shift_svg || 0;
  document.getElementById('glyph-y-shift').value = g.y_shift_svg || 0;
  document.getElementById('glyph-scale').value = g.scale_mul || 1;
  document.getElementById('glyph-lsb-delta').value = g.lsb_delta || 0;
  document.getElementById('glyph-rsb-delta').value = g.rsb_delta || 0;

  // Preview SVG
  const svg = document.getElementById('detail-svg');
  const asc = metricValue('ascender', liveMetrics.ascender);
  const desc = metricValue('descender', liveMetrics.descender);
  const totalH = Math.max(1, asc - desc);
  const lsb = metricValue('left_bearing', liveMetrics.left_bearing) + (g.lsb_delta || 0);
  const rsb = metricValue('right_bearing', liveMetrics.right_bearing) + (g.rsb_delta || 0);
  // In a single-glyph metric view, tracking should NOT be part of glyph cell width.
  const adv = Math.max(1, g.font_width + lsb + rsb);
  const baselineY = asc;
  const lsbX = Math.max(0, lsb);
  const rsbX = Math.max(0, adv - rsb);
  svg.setAttribute('viewBox', `0 0 ${adv} ${totalH}`);
  svg.innerHTML = `
    <rect x="0" y="0" width="${adv}" height="${totalH}" fill="none" stroke="#3b3b52" stroke-dasharray="6 4"/>
    <line x1="0" y1="${baselineY}" x2="${adv}" y2="${baselineY}" stroke="#355f9f" stroke-dasharray="4 4"/>
    <line x1="${lsbX}" y1="0" x2="${lsbX}" y2="${totalH}" stroke="#6d8cff"/>
    <line x1="${rsbX}" y1="0" x2="${rsbX}" y2="${totalH}" stroke="#6d8cff"/>
    <path d="${g.font_preview_d}" fill="#a084fd"/>
  `;

  // Info grid
  const grid = document.getElementById('detail-grid');
  grid.innerHTML = `
    <div class="detail-row"><span>Unicode:</span> ${g.unicode}</div>
    <div class="detail-row"><span>Строка:</span> ${g.row}</div>
    <div class="detail-row"><span>Ширина (font):</span> ${g.font_width} ед.</div>
    <div class="detail-row"><span>Высота (font):</span> ${g.font_height} ед.</div>
    <div class="detail-row"><span>Advance width (без tracking):</span> ${adv.toFixed(0)} ед.</div>
    <div class="detail-row"><span>Baseline SVG:</span> ${g.baseline}</div>
    <div class="detail-row"><span>SVG bbox X:</span> ${g.bbox[0].toFixed(0)}–${g.bbox[2].toFixed(0)}</div>
    <div class="detail-row"><span>SVG bbox Y:</span> ${g.bbox[1].toFixed(0)}–${g.bbox[3].toFixed(0)}</div>
  `;

  updateSelectedUsageHint();
}

function bumpGlyphTransform(dx, dy, dScale) {
  const xInput = document.getElementById('glyph-x-shift');
  const yInput = document.getElementById('glyph-y-shift');
  const sInput = document.getElementById('glyph-scale');
  xInput.value = (parseFloat(xInput.value || '0') + dx).toFixed(0);
  yInput.value = (parseFloat(yInput.value || '0') + dy).toFixed(0);
  sInput.value = Math.max(0.1, parseFloat(sInput.value || '1') + dScale).toFixed(2);
  saveGlyphTransform();
}

function bumpGlyphBearings(dLsb, dRsb) {
  const lsbInput = document.getElementById('glyph-lsb-delta');
  const rsbInput = document.getElementById('glyph-rsb-delta');
  lsbInput.value = (parseFloat(lsbInput.value || '0') + dLsb).toFixed(0);
  rsbInput.value = (parseFloat(rsbInput.value || '0') + dRsb).toFixed(0);
  saveGlyphTransform();
}

function saveGlyphTransform() {
  if (!selectedChar) return;
  const x_shift = parseFloat(document.getElementById('glyph-x-shift').value || '0');
  const y_shift = parseFloat(document.getElementById('glyph-y-shift').value || '0');
  const scale = parseFloat(document.getElementById('glyph-scale').value || '1');
  const lsb_delta = parseFloat(document.getElementById('glyph-lsb-delta').value || '0');
  const rsb_delta = parseFloat(document.getElementById('glyph-rsb-delta').value || '0');
  fetch('/api/glyph-transform', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({ char: selectedChar, x_shift, y_shift, scale, lsb_delta, rsb_delta }),
  }).then(r => r.json()).then((data) => {
    if (!data.ok) throw new Error(data.error || 'Не удалось сохранить трансформацию');
    showStatus(`Глиф "${selectedChar}": x=${x_shift}, y=${y_shift}, scale=${scale}, L=${lsb_delta}, R=${rsb_delta}`, 'ok');
    loadGlyphs();
  }).catch((e) => showStatus('Ошибка трансформации: ' + e, 'err'));
}

// ── Preview text ───────────────────────────────────────────────────────────
function renderPreview() {
  const text = document.getElementById('preview-input').value;
  const container = document.getElementById('preview-render');
  container.innerHTML = '';
  const previewEmPx = metricValue('preview-size', 120);
  document.getElementById('preview-size-value').textContent = `${previewEmPx} px`;
  const lb = metricValue('left_bearing', 40);
  const rb = metricValue('right_bearing', 40);
  const tracking = metricValue('tracking', 0);
  const upm = Math.max(1, metricValue('upm', 1000));
  const asc = metricValue('ascender', 780);
  const desc = metricValue('descender', -220);
  const lineGap = metricValue('line_gap', 0);
  const pxPerUnit = previewEmPx / upm;
  const glyphBoxPx = Math.max(18, (asc - desc) * pxPerUnit);
  const lineAdvancePx = (asc - desc + lineGap) * pxPerUnit;

  const lines = text.split('\n');
  lines.forEach((line, lineIndex) => {
    const row = document.createElement('div');
    row.style.display = 'flex';
    row.style.alignItems = 'center';
    row.style.width = '100%';
    row.style.flexWrap = 'nowrap';
    row.style.minHeight = `${glyphBoxPx}px`;
    if (lineIndex < lines.length - 1) row.style.marginBottom = `${lineAdvancePx - glyphBoxPx}px`;

    for (const ch of line) {
      const g = resolvePreviewGlyph(ch);
      if (g && g.font_preview_d) {
        const lsbLocal = lb + (g.lsb_delta || 0);
        const rsbLocal = rb + (g.rsb_delta || 0);
        // True glyph advance; tracking is an extra inter-glyph space applied outside advance.
        const advUnits = Math.max(1, g.font_width + lsbLocal + rsbLocal);
        const glyphPxWidth = Math.max(10, advUnits * pxPerUnit);
        const trackingPx = tracking * pxPerUnit;
        const div = document.createElement('div');
        div.className = 'preview-glyph';
        div.style.width = `${glyphPxWidth}px`;
        div.style.height = `${glyphBoxPx}px`;
        div.style.marginRight = `${trackingPx}px`;
        const ascLocal = asc;
        const descLocal = desc;
        const hLocal = Math.max(1, ascLocal - descLocal);
        div.innerHTML = `<svg width="100%" height="100%" viewBox="0 0 ${advUnits} ${hLocal}" style="overflow:visible">
          <path d="${g.font_preview_d}" fill="#e2e2f0"/>
        </svg>`;
        row.appendChild(div);
      } else if (ch === ' ') {
        const sp = document.createElement('div');
        const spaceUnits = metricValue('space_width', 300) + tracking;
        sp.style.width = `${Math.max(6, spaceUnits * pxPerUnit)}px`;
        row.appendChild(sp);
      }
    }
    container.appendChild(row);
  });

  updateSelectedUsageHint();
}

function updateSelectedUsageHint() {
  const el = document.getElementById('selected-usage');
  if (!el) return;
  if (!selectedChar) {
    el.textContent = '';
    return;
  }
  const text = document.getElementById('preview-input')?.value || '';
  let count = 0;
  for (const ch of text) {
    const g = resolvePreviewGlyph(ch);
    if (g && g.char === selectedChar) count += 1;
  }
  const cp = selectedChar.codePointAt(0).toString(16).toUpperCase().padStart(4, '0');
  if (count > 0) {
    el.textContent = `Редактируется "${selectedChar}" (U+${cp}). В тексте найдено: ${count}.`;
    el.style.color = 'var(--green)';
  } else {
    el.textContent = `Редактируется "${selectedChar}" (U+${cp}), но этого глифа нет в текущем тексте предпросмотра.`;
    el.style.color = 'var(--red)';
  }
}

function attachLiveMetricPreview() {
  [
    'upm','cap_height','ascender','descender','x_height',
    'line_gap','left_bearing','right_bearing','tracking','space_width'
  ].forEach(id => {
    const el = document.getElementById(id);
    if (!el) return;
    el.addEventListener('input', renderPreview);
  });
}

attachLiveMetricPreview();

// ── Generate ───────────────────────────────────────────────────────────────
function generateFont() {
  const btn = document.getElementById('btn-generate');
  btn.classList.add('spinning');
  btn.disabled = true;
  document.getElementById('download-bar').classList.remove('show');
  showStatus('Генерация шрифта…', 'info');

  fetch('/api/generate', { method: 'POST' })
    .then(async (r) => {
      const text = await r.text();
      let data = {};
      if (text) {
        try {
          data = JSON.parse(text);
        } catch (e) {
          throw new Error(`Некорректный ответ сервера (${r.status}): ${text.slice(0, 240)}`);
        }
      }
      if (!r.ok) {
        const msg = (data.errors && data.errors.length) ? data.errors.join('; ') : `HTTP ${r.status}`;
        throw new Error(msg);
      }
      return data;
    })
    .then(data => {
      btn.classList.remove('spinning');
      btn.disabled = false;

      if (data.ok) {
        const fam = document.getElementById('family_name').value.replace(/ /g,'');
        const sty = document.getElementById('style_name').value.replace(/ /g,'');
        const base = `${fam}-${sty}`;
        showStatus(`✓ Шрифт создан: ${base}.otf + ${base}.ttf`, 'ok');
        document.getElementById('download-bar').classList.add('show');
      } else {
        showStatus('Ошибки: ' + data.errors.join('; '), 'err');
      }
    })
    .catch(e => {
      btn.classList.remove('spinning');
      btn.disabled = false;
      showStatus('Ошибка: ' + e, 'err');
    });
}

// ── Helpers ────────────────────────────────────────────────────────────────
function showStatus(msg, type) {
  const el = document.getElementById('status');
  el.textContent = msg;
  el.className = 'show ' + type;
}
function hideStatus() {
  document.getElementById('status').className = '';
}
</script>
</body>
</html>
