<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>xeen — Screenshot → Video</title>
<style>
:root {
  --bg: #0f0f13;
  --surface: #1a1a24;
  --surface2: #24243a;
  --border: #2d2d44;
  --text: #e4e4ef;
  --text2: #8888aa;
  --accent: #7c5cfc;
  --accent2: #5c9cfc;
  --green: #4cda8a;
  --red: #fc5c7c;
  --orange: #fcac5c;
  --radius: 10px;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Inter', -apple-system, sans-serif; background: var(--bg); color: var(--text); min-height: 100vh; }

/* ─── Header ─────── */
.header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 16px 24px; border-bottom: 1px solid var(--border);
}
.header h1 { font-size: 20px; font-weight: 700; }
.header h1 span { color: var(--accent); }
.header .session-info { color: var(--text2); font-size: 13px; }
.session-select { background: var(--surface2); color: var(--text); border: 1px solid var(--border); padding: 6px 12px; border-radius: 6px; font-size: 13px; }

/* ─── Tabs ─────── */
.tabs {
  display: flex; gap: 0; border-bottom: 1px solid var(--border);
  padding: 0 24px; background: var(--surface);
}
.tab {
  padding: 12px 20px; cursor: pointer; color: var(--text2);
  font-size: 13px; font-weight: 500; border-bottom: 2px solid transparent;
  transition: all 0.2s; user-select: none;
}
.tab:hover { color: var(--text); }
.tab.active { color: var(--accent); border-bottom-color: var(--accent); }
.tab .num {
  display: inline-block; width: 20px; height: 20px; line-height: 20px;
  text-align: center; border-radius: 50%; background: var(--surface2);
  font-size: 11px; margin-right: 6px;
}
.tab.active .num { background: var(--accent); color: white; }

/* ─── Content ─────── */
.content { padding: 24px; max-width: 1400px; margin: 0 auto; }
.panel { display: none; }
.panel.active { display: block; }
.panel-header { margin-bottom: 20px; }
.panel-header h2 { font-size: 18px; margin-bottom: 4px; }
.panel-header p { color: var(--text2); font-size: 13px; }

/* ─── Grid ─────── */
.frame-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 12px;
}
.frame-card {
  background: var(--surface); border: 2px solid var(--border);
  border-radius: var(--radius); overflow: hidden; cursor: pointer;
  transition: all 0.2s; position: relative;
}
.frame-card:hover { border-color: var(--accent); transform: translateY(-2px); }
.frame-card.selected { border-color: var(--green); }
.frame-card img { width: 100%; display: block; }
.frame-card .meta {
  padding: 8px 12px; font-size: 12px; color: var(--text2);
  display: flex; justify-content: space-between;
}
.frame-card .check {
  position: absolute; top: 8px; right: 8px;
  width: 24px; height: 24px; border-radius: 50%;
  background: var(--border); display: flex; align-items: center;
  justify-content: center; font-size: 14px;
}
.frame-card.selected .check { background: var(--green); color: white; }
.frame-card .del-frame {
  position: absolute; top: 8px; left: 8px;
  width: 24px; height: 24px; border-radius: 50%;
  background: rgba(252,92,124,0.0); border: none; color: transparent;
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; cursor: pointer; transition: all 0.15s; line-height: 1;
}
.frame-card:hover .del-frame {
  background: rgba(252,92,124,0.85); color: white;
}
.frame-card .del-frame:hover { background: var(--red) !important; }
.frame-card.frame-missing { border-color: var(--red); opacity: 0.75; }
.frame-missing-overlay {
  background: rgba(252,92,124,0.12);
  min-height: 120px;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  font-size: 13px; color: var(--red); text-align: center; padding: 12px;
}
.frame-missing-overlay small { color: var(--text2); font-size: 10px; margin-top: 4px; word-break: break-all; }
.missing-frames-banner {
  display: none;
  align-items: flex-start;
  gap: 8px;
  background: rgba(252,172,92,0.12);
  border: 1px solid var(--orange);
  border-radius: var(--radius);
  padding: 12px 16px;
  margin-bottom: 16px;
  font-size: 13px;
  color: var(--orange);
  line-height: 1.5;
}

/* ─── Trash Sidebar ─────── */
.trash-sidebar {
  position: fixed;
  right: 0; top: 0; bottom: 0;
  width: 260px;
  background: var(--surface);
  border-left: 1px solid var(--border);
  display: flex; flex-direction: column;
  transform: translateX(100%);
  transition: transform 0.3s;
  z-index: 200;
}
.trash-sidebar.open { transform: translateX(0); }
.trash-toggle {
  position: fixed;
  right: 16px; bottom: 80px;
  width: 48px; height: 48px;
  border-radius: 50%;
  background: var(--surface2);
  border: 1px solid var(--border);
  color: var(--text2);
  font-size: 22px;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  z-index: 201;
  transition: all 0.2s;
  box-shadow: 0 4px 12px rgba(0,0,0,.4);
}
.trash-toggle:hover { border-color: var(--red); color: var(--red); }
.trash-toggle .badge {
  position: absolute; top: -4px; right: -4px;
  background: var(--red); color: white;
  font-size: 10px; font-weight: 700;
  width: 18px; height: 18px; border-radius: 50%;
  display: none; align-items: center; justify-content: center;
}
.trash-toggle .badge.show { display: flex; }
.trash-header {
  padding: 16px;
  border-bottom: 1px solid var(--border);
  display: flex; align-items: center; justify-content: space-between;
}
.trash-header h3 { font-size: 14px; font-weight: 600; }
.trash-close {
  background: none; border: none; color: var(--text2);
  cursor: pointer; font-size: 18px; line-height: 1;
}
.trash-close:hover { color: var(--text); }
.trash-list {
  flex: 1; overflow-y: auto;
  padding: 8px;
  display: flex; flex-direction: column; gap: 6px;
}
.trash-item {
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 10px;
  display: flex; align-items: center; gap: 8px;
}
.trash-item img {
  width: 40px; height: 40px; object-fit: cover;
  border-radius: 4px; flex-shrink: 0;
}
.trash-item-info {
  flex: 1; overflow: hidden;
}
.trash-item-name {
  font-size: 11px; color: var(--text2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.trash-item-actions { display: flex; gap: 4px; }
.trash-item-restore {
  background: none; border: 1px solid var(--border);
  color: var(--text2); border-radius: 6px;
  padding: 2px 6px; font-size: 10px; cursor: pointer;
  white-space: nowrap;
}
.trash-item-restore:hover { border-color: var(--green); color: var(--green); }
.trash-item-purge {
  background: none; border: none;
  color: var(--text2); cursor: pointer; font-size: 13px;
}
.trash-item-purge:hover { color: var(--red); }
.trash-empty-msg {
  text-align: center; padding: 40px 16px;
  color: var(--text2); font-size: 13px;
}
.trash-footer {
  padding: 12px 16px;
  border-top: 1px solid var(--border);
}
.btn-danger { background: var(--red); color: white; width: 100%; }
.btn-danger:hover { background: #e04060; }
.tab-sessions {
  padding: 12px 20px; color: var(--text2);
  font-size: 13px; font-weight: 500;
  text-decoration: none; border-bottom: 2px solid transparent;
  transition: color 0.2s; user-select: none;
  margin-right: 8px; border-right: 1px solid var(--border);
}
.tab-sessions:hover { color: var(--text); }

/* ─── Tools Toolbar ─────── */
.tools-bar {
  display: flex; flex-wrap: wrap; gap: 6px;
  padding: 10px 0; margin-bottom: 12px;
  border-bottom: 1px solid var(--border);
}
.tool-btn {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 6px 12px; border-radius: 8px;
  background: var(--surface2); border: 1px solid var(--border);
  color: var(--text2); font-size: 12px; cursor: pointer;
  transition: all 0.15s; white-space: nowrap;
}
.tool-btn:hover { border-color: var(--accent); color: var(--text); }
.tool-btn.danger:hover { border-color: var(--red); color: var(--red); }
.tool-btn.success:hover { border-color: var(--green); color: var(--green); }
.tool-btn .icon { font-size: 14px; }
.tool-btn .badge-count {
  background: var(--red); color: white;
  font-size: 10px; font-weight: 700;
  padding: 1px 5px; border-radius: 10px;
  min-width: 16px; text-align: center;
}
.tool-btn.advanced { border-style: dashed; opacity: 0.6; }
.tool-btn.advanced:hover { opacity: 1; }
.tools-divider {
  width: 1px; background: var(--border);
  margin: 0 4px; align-self: stretch;
}

/* ─── Export Controls ─────── */
.export-options {
  display: grid; gap: 16px; margin-bottom: 20px;
}
.export-row {
  display: grid; grid-template-columns: 120px 1fr; align-items: center; gap: 12px;
}
.export-row label {
  font-size: 13px; font-weight: 500; color: var(--text);
}
.export-row input[type="range"] {
  width: 100%; height: 6px; border-radius: 3px;
  background: var(--surface2); outline: none; -webkit-appearance: none;
}
.export-row input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; width: 18px; height: 18px;
  border-radius: 50%; background: var(--accent); cursor: pointer;
  border: 2px solid var(--bg);
}
.export-row input[type="range"]::-moz-range-thumb {
  width: 18px; height: 18px; border-radius: 50%; background: var(--accent);
  cursor: pointer; border: 2px solid var(--bg);
}
.range-value {
  font-size: 14px; font-weight: 600; color: var(--accent);
  min-width: 40px; text-align: center;
}
.range-labels {
  display: flex; justify-content: space-between; font-size: 11px; color: var(--text2);
  margin-top: 4px;
}

.format-buttons {
  display: flex; gap: 12px; margin: 20px 0; flex-wrap: wrap;
}
.format-btn {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 16px 20px; border: 2px solid var(--border); border-radius: 12px;
  background: var(--surface1); color: var(--text); cursor: pointer;
  transition: all 0.2s; min-width: 80px;
}
.format-btn:hover {
  border-color: var(--accent); background: var(--surface2); transform: translateY(-2px);
}
.format-btn.active {
  border-color: var(--accent); background: var(--accent); color: var(--bg);
}
.format-icon {
  font-size: 24px;
}
.format-name {
  font-size: 12px; font-weight: 500;
}

.export-links {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 16px;
  margin-top: 12px;
}
.export-links a {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 12px; background: var(--surface1); border: 1px solid var(--border);
  border-radius: 6px; color: var(--text); text-decoration: none;
  font-size: 13px; transition: all 0.2s;
}
.export-links a:hover {
  border-color: var(--accent); background: var(--surface2);
}
.export-error {
  padding: 8px 12px; background: var(--red); color: white;
  border-radius: 6px; font-size: 13px; margin-bottom: 8px;
}

.export-preview {
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface1); overflow: hidden;
  display: flex; flex-direction: column;
  height: 100%;
}
.preview-header {
  display: flex; justify-content: space-between; align-items: center;
  padding: 8px 12px; background: var(--surface2);
  border-bottom: 1px solid var(--border);
}
.format-badge {
  background: var(--accent); color: var(--bg);
  padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600;
}
.download-link {
  color: var(--text); text-decoration: none; font-size: 12px;
  transition: color 0.2s;
}
.download-link:hover {
  color: var(--accent);
}
.preview-content {
  padding: 12px; display: flex; justify-content: center;
  background: var(--bg); flex: 1;
  min-height: 200px;
}
.preview-media {
  width: 100%; height: 100%;
  max-height: 200px;
  object-fit: contain;
  border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.preview-icon {
  font-size: 48px; opacity: 0.5;
}

/* ─── Focus Mode Controls ─────── */
.focus-option {
  display: flex; align-items: center; gap: 6px;
  padding: 8px 12px; border: 1px solid var(--border); border-radius: 6px;
  background: var(--surface2); cursor: pointer; transition: all 0.2s;
  font-size: 12px; color: var(--text2);
}
.focus-option:hover {
  border-color: var(--accent); color: var(--text);
}
.focus-option input[type="radio"] {
  accent-color: var(--accent);
}
.focus-option input[type="radio"]:checked + span {
  color: var(--accent); font-weight: 500;
}
.focus-option:has(input:checked) {
  border-color: var(--accent); background: rgba(124,92,252,.1);
}

#zoomSlider::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; width: 18px; height: 18px;
  border-radius: 50%; background: var(--accent); cursor: pointer;
  border: 2px solid var(--bg);
}
#zoomSlider::-moz-range-thumb {
  width: 18px; height: 18px; border-radius: 50%; background: var(--accent);
  cursor: pointer; border: 2px solid var(--bg);
}
#paddingSlider::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; width: 18px; height: 18px;
  border-radius: 50%; background: var(--accent); cursor: pointer;
  border: 2px solid var(--bg);
}
#paddingSlider::-moz-range-thumb {
  width: 18px; height: 18px; border-radius: 50%; background: var(--accent);
  cursor: pointer; border: 2px solid var(--bg);
}
.mode-toggle {
  margin-left: auto;
  display: flex; align-items: center; gap: 6px;
  font-size: 11px; color: var(--text2);
}
.mode-toggle input { accent-color: var(--accent); }

/* ─── Duplicate markers ─────── */
.frame-card.duplicate { border-color: var(--orange); }
.frame-card.duplicate::after {
  content: 'duplikat'; position: absolute;
  bottom: 32px; left: 50%; transform: translateX(-50%);
  background: var(--orange); color: #000;
  font-size: 10px; font-weight: 700;
  padding: 2px 8px; border-radius: 4px;
  pointer-events: none;
}
.dup-similarity {
  position: absolute; top: 36px; right: 8px;
  background: rgba(252,172,92,0.9); color: #000;
  font-size: 10px; font-weight: 600;
  padding: 1px 5px; border-radius: 4px;
}

/* ─── Annotate ─────── */
.annotate-workspace {
  display: flex; gap: 16px;
}
.annotate-frame-list {
  width: 120px; flex-shrink: 0;
  display: flex; flex-direction: column; gap: 6px;
  max-height: 70vh; overflow-y: auto;
}
.annotate-thumb {
  border: 2px solid var(--border); border-radius: 6px;
  cursor: pointer; overflow: hidden; position: relative;
}
.annotate-thumb.active { border-color: var(--accent); }
.annotate-thumb img { width: 100%; display: block; }
.annotate-thumb .idx {
  position: absolute; top: 2px; left: 4px;
  font-size: 10px; color: white; text-shadow: 0 1px 3px rgba(0,0,0,.8);
}
.annotate-canvas-wrap {
  flex: 1; position: relative; background: var(--surface);
  border-radius: var(--radius); overflow: hidden;
  display: flex; align-items: flex-start; justify-content: center;
}
.annotate-canvas-wrap canvas {
  max-width: 100%; max-height: 70vh; cursor: crosshair; display: block;
}
.annotate-color-btn {
  width: 24px; height: 24px; border-radius: 50%;
  border: 2px solid var(--border); cursor: pointer;
}
.annotate-color-btn.active { border-color: white; box-shadow: 0 0 0 2px var(--accent); }
.annotate-size-range { width: 80px; vertical-align: middle; }

/* ─── Center Marker ─────── */
.center-workspace {
  display: flex; gap: 20px;
}
.center-frame-list {
  width: 160px; flex-shrink: 0;
  display: flex; flex-direction: column; gap: 6px;
  max-height: 70vh; overflow-y: auto;
}
.center-thumb {
  border: 2px solid var(--border); border-radius: 6px;
  cursor: pointer; overflow: hidden; position: relative;
}
.center-thumb.active { border-color: var(--accent); }
.center-thumb.marked { border-color: var(--green); }
.center-thumb img { width: 100%; display: block; }
.center-thumb .idx { position: absolute; top: 2px; left: 4px; font-size: 10px; color: white; text-shadow: 0 1px 3px rgba(0,0,0,.8); }

.center-canvas-wrap {
  flex: 1; position: relative; background: var(--surface);
  border-radius: var(--radius); overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
.center-canvas-wrap img {
  max-width: 100%; max-height: 70vh; cursor: crosshair; display: block;
}
.crosshair {
  position: absolute; pointer-events: none;
}
.crosshair-h, .crosshair-v {
  position: absolute; background: var(--red); opacity: 0.7;
}
.crosshair-h { height: 1px; width: 100%; left: 0; }
.crosshair-v { width: 1px; height: 100%; top: 0; }
.center-dot {
  position: absolute; width: 16px; height: 16px; border-radius: 50%;
  background: var(--red); border: 2px solid white;
  transform: translate(-50%, -50%); pointer-events: none;
  box-shadow: 0 0 8px rgba(252,92,124,.5);
}
.center-info {
  position: absolute; bottom: 12px; left: 12px; background: rgba(0,0,0,.7);
  padding: 6px 12px; border-radius: 6px; font-size: 12px;
}

/* ─── Mouse Position Matrix ─────── */
.mouse-matrix-container {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 16px;
}
.mouse-matrix {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  max-height: 400px;
  overflow-y: auto;
}
.mouse-matrix-item {
  position: relative;
  background: var(--surface2);
  border: 2px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  cursor: pointer;
  transition: all 0.2s;
  aspect-ratio: 16/9;
}
.mouse-matrix-item:hover {
  border-color: var(--accent);
  transform: scale(1.02);
}
.mouse-matrix-item.active {
  border-color: var(--green);
  box-shadow: 0 0 0 2px rgba(76,218,138,0.3);
}
.mouse-matrix-item.marked {
  border-color: var(--orange);
}
.mouse-matrix-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.mouse-matrix-item .frame-number {
  position: absolute;
  top: 4px;
  left: 4px;
  background: rgba(0,0,0,.8);
  color: white;
  font-size: 10px;
  font-weight: 600;
  padding: 2px 6px;
  border-radius: 4px;
}
.mouse-matrix-item .mouse-position {
  position: absolute;
  bottom: 4px;
  right: 4px;
  background: rgba(252,92,124,.9);
  color: white;
  font-size: 9px;
  font-weight: 600;
  padding: 2px 4px;
  border-radius: 3px;
  pointer-events: none;
}
.mouse-matrix-item .center-mark {
  position: absolute;
  width: 12px;
  height: 12px;
  border: 2px solid var(--green);
  border-radius: 50%;
  background: rgba(76,218,138,.3);
  transform: translate(-50%, -50%);
  pointer-events: none;
}
.mouse-matrix-item .suggested-center {
  position: absolute;
  width: 8px;
  height: 8px;
  background: var(--red);
  border-radius: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
}

/* ─── Crop Preview ─────── */
.preset-bar {
  display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px;
}
.preset-btn {
  padding: 8px 14px; border: 1px solid var(--border); background: var(--surface);
  color: var(--text); border-radius: 6px; cursor: pointer; font-size: 12px;
  transition: all 0.2s;
}
.preset-btn:hover { border-color: var(--accent); }
.preset-btn.active { background: var(--accent); border-color: var(--accent); color: white; }

.crop-preview-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 12px;
}
.crop-card {
  background: var(--surface); border-radius: var(--radius);
  overflow: hidden; border: 1px solid var(--border);
}
.crop-card img { width: 100%; display: block; }
.crop-card .info { padding: 8px; font-size: 11px; color: var(--text2); }

/* ─── Captions / Napisy ─────── */
.captions-layout {
  display: grid;
  grid-template-columns: 1fr 380px;
  gap: 20px;
  height: calc(100vh - 280px);
  min-height: 500px;
}
.captions-timeline {
  display: flex; flex-direction: column; gap: 0; overflow-y: auto;
  background: var(--surface); border-radius: var(--radius);
  border: 1px solid var(--border);
}
.caption-row {
  display: grid;
  grid-template-columns: 56px 1fr 80px;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  transition: background 0.15s;
  position: relative;
}
.caption-row:hover { background: var(--surface2); }
.caption-row.active { background: rgba(124,92,252,.15); border-left: 3px solid var(--accent); }
.caption-row .cap-thumb {
  width: 50px; height: 32px; object-fit: cover;
  border-radius: 4px; background: var(--bg);
  border: 1px solid var(--border);
}
.caption-row .cap-text {
  font-size: 12px; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.caption-row .cap-frames {
  font-size: 10px; color: var(--text2); text-align: right;
}
.caption-row .cap-del {
  position: absolute; right: 6px; top: 6px;
  background: none; border: none; color: var(--text2); cursor: pointer; font-size: 13px;
  display: none;
}
.caption-row:hover .cap-del { display: block; }
.caption-row .cap-del:hover { color: var(--red); }

.caption-editor {
  display: flex; flex-direction: column; gap: 12px;
  background: var(--surface); border-radius: var(--radius);
  border: 1px solid var(--border); padding: 16px; overflow-y: auto;
}
.cap-preview-wrap {
  position: relative; background: #000; border-radius: 6px;
  overflow: hidden; flex-shrink: 0;
}
.cap-preview-wrap img { width: 100%; display: block; }
.cap-overlay-text {
  position: absolute; left: 50%; transform: translateX(-50%);
  padding: 4px 12px; border-radius: 4px;
  pointer-events: none; white-space: pre-wrap; text-align: center;
  max-width: 90%;
}
.cap-field { display: flex; flex-direction: column; gap: 4px; }
.cap-field label { font-size: 11px; color: var(--text2); font-weight: 500; }
.cap-field input[type="text"],
.cap-field textarea,
.cap-field select,
.cap-field input[type="number"] {
  background: var(--surface2); color: var(--text);
  border: 1px solid var(--border); border-radius: 6px;
  padding: 6px 8px; font-size: 13px; width: 100%;
}
.cap-field textarea { resize: vertical; min-height: 64px; font-family: inherit; }
.cap-row-inline { display: flex; gap: 8px; }
.cap-row-inline .cap-field { flex: 1; }

.llm-panel {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 16px; margin-bottom: 16px;
}
.llm-panel h4 { font-size: 13px; margin: 0 0 12px 0; color: var(--text); }
.llm-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 12px; }
.llm-fields label { font-size: 11px; color: var(--text2); display: block; margin-bottom: 4px; }
.llm-fields select, .llm-fields input {
  width: 100%; background: var(--surface2); color: var(--text);
  border: 1px solid var(--border); border-radius: 6px;
  padding: 6px 8px; font-size: 12px;
}
.llm-api-row {
  display: flex; gap: 8px; align-items: center; margin-bottom: 10px;
}
.llm-api-row input {
  flex: 1; background: var(--surface2); color: var(--text);
  border: 1px solid var(--border); border-radius: 6px;
  padding: 6px 8px; font-size: 12px; font-family: monospace;
}

/* ─── Publish ─────── */
.publish-section { max-width: 640px; }
.export-options { display: flex; flex-direction: column; gap: 12px; margin-bottom: 20px; }
.export-row { display: flex; gap: 12px; align-items: center; }
.export-row label { width: 120px; font-size: 13px; color: var(--text2); }
.export-row select, .export-row input {
  flex: 1; padding: 8px; background: var(--surface2); color: var(--text);
  border: 1px solid var(--border); border-radius: 6px; font-size: 13px;
}
.social-links { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 16px; }
.social-btn {
  padding: 10px 18px; border-radius: 8px; font-size: 13px; font-weight: 500;
  cursor: pointer; border: none; color: white; transition: opacity 0.2s;
}
.social-btn:hover { opacity: 0.85; }
.social-btn.twitter { background: #1da1f2; }
.social-btn.linkedin { background: #0077b5; }
.social-btn.facebook { background: #4267b2; }
.social-btn.reddit { background: #ff4500; }
.social-btn.telegram { background: #0088cc; }

/* ─── Buttons ─────── */
.btn {
  padding: 10px 20px; border: none; border-radius: 8px;
  font-size: 13px; font-weight: 600; cursor: pointer; transition: all 0.2s;
}
.btn-primary { background: var(--accent); color: white; }
.btn-primary:hover { background: #6b4ce8; }
.btn-success { background: var(--green); color: #111; }
.btn-outline { background: transparent; border: 1px solid var(--border); color: var(--text); }
.btn-outline:hover { border-color: var(--accent); }
.btn-group { display: flex; gap: 8px; margin-top: 16px; }

/* ─── Upload zone ─────── */
.upload-zone {
  border: 2px dashed var(--border); border-radius: var(--radius);
  padding: 48px; text-align: center; color: var(--text2);
  cursor: pointer; transition: all 0.2s; margin-bottom: 20px;
}
.upload-zone:hover, .upload-zone.dragover {
  border-color: var(--accent); background: rgba(124,92,252,.05);
}
.upload-zone h3 { margin-bottom: 8px; color: var(--text); }

/* ─── Empty state ─────── */
.empty-state { text-align: center; padding: 60px; color: var(--text2); }
.empty-state h3 { color: var(--text); margin-bottom: 8px; }

/* ─── Toast ─────── */
.toast {
  position: fixed; bottom: 24px; right: 24px;
  background: var(--surface2); border: 1px solid var(--green);
  padding: 12px 20px; border-radius: 8px; font-size: 13px;
  transform: translateY(80px); opacity: 0; transition: all 0.3s;
  z-index: 1000;
}
.toast.show { transform: translateY(0); opacity: 1; }

/* ─── Spinner ─────── */
.spinner { display: inline-block; width: 16px; height: 16px; border: 2px solid var(--border); border-top-color: var(--accent); border-radius: 50%; animation: spin 0.6s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
</style>
</head>
<body>

<div class="header">
  <h1><span>xeen</span> capture</h1>
  <div style="display:flex;align-items:center;gap:12px;">
    <span class="session-info" id="sessionInfo">Brak sesji</span>
    <select class="session-select" id="sessionSelect" onchange="loadSession(this.value)">
      <option value="">— wybierz sesję —</option>
    </select>
  </div>
</div>

<div class="tabs" id="tabs">
  <a class="tab tab-sessions" href="/" title="Powrót do listy sesji">← Sesje</a>
  <div class="tab active" data-tab="select">
    <span class="num">1</span>Wybór klatek
  </div>
  <div class="tab" data-tab="annotate">
    <span class="num">2</span>Adnotacje
  </div>
  <div class="tab" data-tab="center">
    <span class="num">3</span>Środek
  </div>
  <div class="tab" data-tab="crop">
    <span class="num">4</span>Przycinanie
  </div>
  <div class="tab" data-tab="captions">
    <span class="num">5</span>Napisy
  </div>
  <div class="tab" data-tab="publish">
    <span class="num">6</span>Publikacja
  </div>
</div>

<!-- Trash toggle -->
<button class="trash-toggle" onclick="toggleTrash()" title="Kosz">
  🗑
  <span class="badge" id="trashBadge"></span>
</button>

<!-- Trash sidebar -->
<div class="trash-sidebar" id="trashSidebar">
  <div class="trash-header">
    <h3>🗑 Kosz klatek</h3>
    <button class="trash-close" onclick="toggleTrash()">✕</button>
  </div>
  <div class="trash-list" id="trashList">
    <div class="trash-empty-msg">Kosz jest pusty</div>
  </div>
  <div class="trash-footer">
    <button class="btn btn-danger" onclick="emptyFrameTrash()">Opróżnij kosz</button>
  </div>
</div>

<div class="content">

  <!-- ═══ Tab 1: Frame Selection ═══ -->
  <div class="panel active" id="panel-select">
    <div class="panel-header">
      <h2>Wybór klatek</h2>
      <p>Kliknij aby zaznaczyć/odznaczyć klatki. Zaznaczone przejdą do dalszej edycji.</p>
    </div>

    <div class="tools-bar" id="toolsSelect">
      <button class="tool-btn danger" onclick="detectDuplicates()" title="Znajdź klatki podobne >90%">
        <span class="icon">🔍</span> Znajdź duplikaty <span class="badge-count" id="dupCount" style="display:none"></span>
      </button>
      <button class="tool-btn danger" id="btnRemoveDups" onclick="removeDuplicates()" style="display:none" title="Usuń oznaczone duplikaty">
        <span class="icon">🗑</span> Usuń duplikaty
      </button>
      <button class="tool-btn" onclick="sortByChange()" title="Sortuj wg % zmiany">
        <span class="icon">📊</span> Sortuj wg zmiany
      </button>
      <button class="tool-btn" onclick="invertSelection()" title="Odwróć zaznaczenie">
        <span class="icon">🔄</span> Odwróć
      </button>
      <button class="tool-btn" onclick="selectEveryNth()" title="Zaznacz co N-tą klatkę">
        <span class="icon">#️⃣</span> Co N-ta
      </button>
      <button class="tool-btn success" onclick="selectMostDifferent()" title="Zaznacz klatki z największymi zmianami">
        <span class="icon">⚡</span> Największe zmiany
      </button>
      <div class="tools-divider"></div>
      <button class="tool-btn advanced" onclick="advancedSimilarity()" title="Zaawansowana analiza (wymaga dodatkowych pakietów)">
        <span class="icon">🧠</span> AI detekcja
      </button>
      <div class="mode-toggle">
        <input type="checkbox" id="advancedMode" onchange="toggleAdvancedMode(this.checked)">
        <label for="advancedMode">Zaawansowane</label>
      </div>
    </div>

    <div class="upload-zone" id="uploadZone" onclick="document.getElementById('fileInput').click()">
      <h3>📸 Przeciągnij screenshoty lub kliknij aby wybrać</h3>
      <p>Lub uruchom <code>xeen capture</code> w terminalu (wymaga GUI)</p>
      <input type="file" id="fileInput" multiple accept="image/*" style="display:none" onchange="uploadFiles(this.files)">
    </div>

    <div class="missing-frames-banner" id="missingFramesBanner"></div>
    <div id="frameGrid" class="frame-grid"></div>

    <div class="btn-group">
      <button class="btn btn-outline" onclick="selectAllFrames()">Zaznacz wszystkie</button>
      <button class="btn btn-outline" onclick="deselectAllFrames()">Odznacz wszystkie</button>
      <button class="btn btn-primary" onclick="saveSelection(); navigateToTab('annotate')">Dalej → Adnotacje</button>
    </div>
  </div>

  <!-- ═══ Tab 2: Annotate ═══ -->
  <div class="panel" id="panel-annotate">
    <div class="panel-header">
      <h2>Adnotacje</h2>
      <p>Dodaj strzałki, prostokąty i tekst na klatkach. Pomiń jeśli nie potrzebujesz.</p>
    </div>

    <div class="tools-bar">
      <button class="tool-btn success" id="annToolArrow" onclick="setAnnotateTool('arrow')" title="Strzałka">
        <span class="icon">↗️</span> Strzałka
      </button>
      <button class="tool-btn" id="annToolRect" onclick="setAnnotateTool('rect')" title="Prostokąt / highlight">
        <span class="icon">⬜</span> Prostokąt
      </button>
      <button class="tool-btn" id="annToolText" onclick="setAnnotateTool('text')" title="Tekst">
        <span class="icon">🅰</span> Tekst
      </button>
      <div class="tools-divider"></div>
      <button class="annotate-color-btn active" style="background:#fc5c7c" onclick="setAnnotateColor('#fc5c7c',this)" title="Czerwony"></button>
      <button class="annotate-color-btn" style="background:#fcac5c" onclick="setAnnotateColor('#fcac5c',this)" title="Pomarańczowy"></button>
      <button class="annotate-color-btn" style="background:#5cfc7c" onclick="setAnnotateColor('#5cfc7c',this)" title="Zielony"></button>
      <button class="annotate-color-btn" style="background:#5cacfc" onclick="setAnnotateColor('#5cacfc',this)" title="Niebieski"></button>
      <button class="annotate-color-btn" style="background:#ffffff" onclick="setAnnotateColor('#ffffff',this)" title="Biały"></button>
      <div class="tools-divider"></div>
      <label style="font-size:11px;color:var(--text2)">Grubość:</label>
      <input type="range" class="annotate-size-range" min="2" max="10" value="3" id="annLineWidth">
      <div class="tools-divider"></div>
      <button class="tool-btn" onclick="undoAnnotation()" title="Cofnij">
        <span class="icon">↩️</span> Cofnij
      </button>
      <button class="tool-btn danger" onclick="clearAnnotations()" title="Wyczyść">
        <span class="icon">🗑</span> Wyczyść
      </button>
    </div>

    <div class="annotate-workspace">
      <div class="annotate-frame-list" id="annotateFrameList"></div>
      <div class="annotate-canvas-wrap" id="annotateCanvasWrap">
        <canvas id="annotateCanvas" width="800" height="600"></canvas>
      </div>
    </div>

    <div class="btn-group">
      <button class="btn btn-outline" onclick="navigateToTab('select')">← Wstecz</button>
      <button class="btn btn-outline" onclick="navigateToTab('center')">Pomiń adnotacje →</button>
      <button class="btn btn-primary" onclick="saveAnnotations(); navigateToTab('center')">Dalej → Środek</button>
    </div>
  </div>

  <!-- ═══ Tab 3: Center Marking ═══ -->
  <div class="panel" id="panel-center">
    <div class="panel-header">
      <h2>Oznacz środek</h2>
      <p>Kliknij na obrazie aby wyznaczyć punkt centralny. 🔴 = kursor myszy z nagrania (sugestia).</p>
      <p style="font-size:11px;color:var(--text2);margin-top:4px">
        ⌨️ <b>←/→</b> kolejna klatka &nbsp;|&nbsp; <b>Spacja</b> zatwierdź sugestię + dalej &nbsp;|&nbsp; <b>C</b> kopiuj z poprzedniej &nbsp;|&nbsp; <b>Enter</b> dalej → Crop
      </p>
    </div>

    <div class="tools-bar">
      <button class="tool-btn success" onclick="autoCenters()" title="Użyj pozycji kursora z nagrania">
        <span class="icon">🎯</span> Auto: kursor myszy
      </button>
      <button class="tool-btn" onclick="centerToMiddle()" title="Ustaw środek na środek obrazu">
        <span class="icon">➕</span> Środek obrazu
      </button>
      <button class="tool-btn" onclick="copyCenterToAll()" title="Kopiuj bieżący środek do wszystkich klatek">
        <span class="icon">📋</span> Kopiuj do wszystkich
      </button>
      <div class="tools-divider"></div>
      <button class="tool-btn advanced" onclick="advancedAutoCenter()" title="AI detekcja punktu zainteresowania">
        <span class="icon">🧠</span> AI auto-center
      </button>
    </div>

    <div class="center-workspace">
      <div class="center-frame-list" id="centerFrameList"></div>
      <div class="center-canvas-wrap" id="centerCanvasWrap">
        <img id="centerImage" src="" alt="">
        <div class="crosshair-h" id="crossH" style="display:none"></div>
        <div class="crosshair-v" id="crossV" style="display:none"></div>
        <div class="center-dot" id="centerDot" style="display:none"></div>
        <div class="center-info" id="centerInfo">Kliknij na obrazie</div>
      </div>
    </div>
    
    <!-- Mouse Position Matrix -->
    <div class="mouse-matrix-container" style="margin-top:20px">
      <h4 style="margin:0 0 12px 0;font-size:14px;color:var(--text)">🖱️ Pozycje myszy (matrix 3x3)</h4>
      <div id="mouseMatrix" class="mouse-matrix">
        <div style="grid-column:1/-1;text-align:center;color:var(--text2);font-size:12px;padding:8px">
          ⏳ Ładowanie pozycji myszy...
        </div>
      </div>
    </div>
    <div class="btn-group">
      <button class="btn btn-outline" onclick="navigateToTab('select')">← Wstecz</button>
      <button class="btn btn-outline" onclick="autoCenters()">Auto (kursor myszy)</button>
      <button class="btn btn-primary" onclick="saveCenters(); navigateToTab('crop')">Dalej → Przycinanie</button>
    </div>
  </div>

  <!-- ═══ Tab 3: Crop Preview ═══ -->
  <div class="panel" id="panel-crop">
    <div class="panel-header">
      <h2>Przycinanie</h2>
      <p>Wybierz format docelowy — podgląd przyciętych klatek wg oznaczonego środka.</p>
    </div>

    <div class="tools-bar">
      <button class="tool-btn" onclick="previewAllPresets()" title="Podgląd wszystkich formatów">
        <span class="icon">🖼</span> Podgląd wszystkich
      </button>
      <button class="tool-btn" onclick="fitToContent()" title="Dopasuj kadrowanie do treści">
        <span class="icon">✂️</span> Dopasuj do treści
      </button>
      <button class="tool-btn" onclick="addCustomPreset()" title="Dodaj własny format">
        <span class="icon">➕</span> Własny format
      </button>
      <div class="tools-divider"></div>
      <button class="tool-btn advanced" onclick="advancedSmartCrop()" title="AI smart crop (wymaga opencv)">
        <span class="icon">🧠</span> AI smart crop
      </button>
    </div>

    <!-- Focus Mode Controls -->
    <div class="focus-controls" style="margin-bottom:20px;padding:16px;background:var(--surface);border-radius:8px;border:1px solid var(--border)">
      <h4 style="margin:0 0 12px 0;font-size:14px;color:var(--text)">🎯 Tryb focusu</h4>
      <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:8px;margin-bottom:16px">
        <label class="focus-option">
          <input type="radio" name="focusMode" value="screen" checked onchange="updateFocusMode('screen')">
          <span>🖥️ Ekran</span>
        </label>
        <label class="focus-option">
          <input type="radio" name="focusMode" value="mouse" onchange="updateFocusMode('mouse')">
          <span>🖱️ Mysz</span>
        </label>
        <label class="focus-option">
          <input type="radio" name="focusMode" value="keyboard" onchange="updateFocusMode('keyboard')">
          <span>⌨️ Klawiatura</span>
        </label>
        <label class="focus-option">
          <input type="radio" name="focusMode" value="application" onchange="updateFocusMode('application')">
          <span>🪬 Aplikacja</span>
        </label>
      </div>
      
      <!-- Zoom Control -->
      <div id="zoomControls" style="display:none">
        <label style="font-size:13px;color:var(--text2);display:block;margin-bottom:8px">
          🔍 Zoom myszy: <span id="zoomValue">2.0x</span>
        </label>
        <input type="range" id="zoomSlider" min="1" max="10" value="2" step="0.5" 
               oninput="updateZoom(this.value)"
               style="width:100%;height:6px;border-radius:3px;background:var(--surface2);outline:none;-webkit-appearance:none">
        <div style="display:flex;justify-content:space-between;font-size:11px;color:var(--text2);margin-top:4px">
          <span>1x</span><span>5x</span><span>10x</span>
        </div>
        
        <label style="font-size:13px;color:var(--text2);display:block;margin:12px 0 8px 0">
          📏 Obszar wokół myszy: <span id="paddingValue">100px</span>
        </label>
        <input type="range" id="paddingSlider" min="50" max="300" value="100" step="10" 
               oninput="updatePadding(this.value)"
               style="width:100%;height:6px;border-radius:3px;background:var(--surface2);outline:none;-webkit-appearance:none">
        <div style="display:flex;justify-content:space-between;font-size:11px;color:var(--text2);margin-top:4px">
          <span>50px</span><span>175px</span><span>300px</span>
        </div>
      </div>
      
      <!-- Video Preview -->
      <div id="videoPreviewContainer" style="margin-top:16px">
        <h5 style="margin:0 0 8px 0;font-size:13px;color:var(--text)">🎬 Podgląd pierwszej klatki wideo</h5>
        <div id="videoPreview" style="background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:8px;min-height:120px;display:flex;align-items:center;justify-content:center;position:relative">
          <div style="color:var(--text2);font-size:12px;text-align:center">
            <div>⏳ Generowanie podglądu...</div>
            <small style="display:block;margin-top:4px">Zmień ustawienia aby zobaczyć efekt</small>
          </div>
        </div>
      </div>
    </div>

    <div class="preset-bar" id="presetBar"></div>
    <div id="cropPreviewGrid" class="crop-preview-grid"></div>
    <div class="btn-group">
      <button class="btn btn-outline" onclick="navigateToTab('center')">← Wstecz</button>
      <button class="btn btn-primary" onclick="navigateToTab('captions')">Dalej → Napisy</button>
    </div>
  </div>

  <!-- ═══ Tab 5: Captions / Napisy ═══ -->
  <div class="panel" id="panel-captions">
    <div class="panel-header">
      <h2>Napisy</h2>
      <p>Dodaj opisy do klatek — ręcznie lub automatycznie przez AI. Pozycjonuj tekst jak w Canva.</p>
    </div>

    <div class="tools-bar">
      <button class="tool-btn success" onclick="addCaption()" title="Dodaj nowy napis">
        <span class="icon">➕</span> Dodaj napis
      </button>
      <button class="tool-btn" onclick="autoCaptionsFromFrames()" title="Auto: utwórz napis dla każdej klatki">
        <span class="icon">🔄</span> Auto (1 napis/klatka)
      </button>
      <button class="tool-btn" onclick="saveCaptions()" title="Zapisz napisy">
        <span class="icon">💾</span> Zapisz
      </button>
      <button class="tool-btn danger" onclick="clearAllCaptions()" title="Usuń wszystkie napisy">
        <span class="icon">🗑</span> Wyczyść
      </button>
    </div>

    <!-- LLM Panel -->
    <div class="llm-panel">
      <h4>🤖 Generowanie przez AI (LLM)</h4>
      <div class="llm-api-row">
        <span style="font-size:11px;color:var(--text2);white-space:nowrap">Klucz API:</span>
        <input type="password" id="llmApiKey" placeholder="sk-... (lub ustaw zmienną OPENAI_API_KEY)" 
               title="Zostaw puste jeśli masz OPENAI_API_KEY w środowisku">
      </div>
      <div class="llm-fields">
        <div>
          <label>Provider</label>
          <select id="llmProvider" onchange="updateLlmModelPlaceholder()">
            <option value="openai">OpenAI</option>
            <option value="anthropic">Anthropic</option>
            <option value="ollama">Ollama (lokalny)</option>
            <option value="gemini">Google Gemini</option>
          </select>
        </div>
        <div>
          <label>Model</label>
          <input type="text" id="llmModel" value="gpt-4o-mini" placeholder="gpt-4o-mini">
        </div>
        <div>
          <label>Język</label>
          <select id="llmLanguage">
            <option value="pl">Polski</option>
            <option value="en">English</option>
            <option value="de">Deutsch</option>
          </select>
        </div>
        <div>
          <label>Styl napisów</label>
          <select id="llmStyle">
            <option value="tutorial">Tutorial (krok po kroku)</option>
            <option value="social">Social media</option>
            <option value="minimal">Minimalny</option>
            <option value="descriptive">Opisowy</option>
          </select>
        </div>
      </div>
      <button class="btn btn-primary" onclick="generateCaptionsLLM()" id="llmGenerateBtn">
        🤖 Generuj napisy AI
      </button>
      <span id="llmStatus" style="font-size:12px;color:var(--text2);margin-left:12px"></span>
    </div>

    <!-- Main editor layout -->
    <div class="captions-layout">
      <!-- Left: timeline/list -->
      <div>
        <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
          <span style="font-size:13px;font-weight:600;color:var(--text)">
            Lista napisów <span id="captionCount" style="color:var(--text2);font-weight:400">(0)</span>
          </span>
          <button class="tool-btn" style="padding:4px 10px;font-size:11px" onclick="addCaption()">+ Nowy</button>
        </div>
        <div class="captions-timeline" id="captionsList">
          <div style="text-align:center;padding:40px;color:var(--text2);font-size:13px">
            Brak napisów — dodaj ręcznie lub wygeneruj AI
          </div>
        </div>
      </div>

      <!-- Right: editor -->
      <div class="caption-editor" id="captionEditor">
        <div style="text-align:center;padding:20px;color:var(--text2);font-size:13px">
          Kliknij napis na liście aby edytować
        </div>
      </div>
    </div>

    <div class="btn-group" style="margin-top:16px">
      <button class="btn btn-outline" onclick="navigateToTab('crop')">← Wstecz</button>
      <button class="btn btn-success" onclick="saveCaptions(); navigateToTab('publish')">Zapisz i dalej → Publikacja</button>
    </div>
  </div>

  <!-- ═══ Tab 6: Publish ═══ -->
  <div class="panel" id="panel-publish">
    <div class="panel-header">
      <h2>Eksport i publikacja</h2>
      <p>Eksportuj jako wideo, GIF lub paczkę PNG, i udostępnij.</p>
    </div>

    <div class="panel-subtitle" id="publishPresetInfo">Wybrany format: —</div>

    <div class="tools-bar">
      <button class="tool-btn success" onclick="exportSession()" title="Eksportuj z aktualnymi ustawieniami">
        <span class="icon">📦</span> Szybki eksport
      </button>
      <button class="tool-btn" onclick="exportAllFormats()" title="Eksportuj we wszystkich formatach">
        <span class="icon">📁</span> Eksport wszystkich
      </button>
      <button class="tool-btn" onclick="addWatermark()" title="Dodaj znak wodny">
        <span class="icon">🌐</span> Znak wodny
      </button>
      <div class="tools-divider"></div>
      <button class="tool-btn advanced" onclick="advancedVideoExport()" title="FFmpeg zaawansowany (wymaga ffmpeg)">
        <span class="icon">🧠</span> Zaawansowany eksport
      </button>
    </div>

    <div class="publish-section">

      <!-- Animated frame preview loop -->
      <div id="publishAnimWrap" style="background:var(--bg);border:1px solid var(--border);border-radius:10px;padding:12px;margin-bottom:20px;text-align:center">
        <div style="font-size:12px;color:var(--text2);margin-bottom:8px">
          🎥 Podgląd animacji &mdash; <span id="publishAnimLabel">pierwsze 3 klatki, loop</span>
        </div>
        <div style="position:relative;display:inline-block;background:#000;border-radius:6px;overflow:hidden;min-width:180px;min-height:100px">
          <img id="publishAnimImg" src="" alt="" style="max-width:100%;max-height:240px;display:block;object-fit:contain">
          <div id="publishAnimSpinner" style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.5)">
            <div class="spinner"></div>
          </div>
        </div>
        <div id="publishAnimDots" style="margin-top:8px;display:flex;justify-content:center;gap:6px"></div>
      </div>

      <div class="export-options">
        <div class="export-row">
          <label>Czas/klatka:</label>
          <input type="range" id="exportDuration" min="0.1" max="5" value="0.5" step="0.1"
                 oninput="document.getElementById('durationValue').textContent = this.value + 's'; syncConfigToURL(); restartPublishPreview();">
          <span id="durationValue" class="range-value">0.5s</span>
          <div class="range-labels">
            <span>0.1s</span><span>2.5s</span><span>5s</span>
          </div>
        </div>
        <div class="export-row">
          <label>Jakość:</label>
          <input type="range" id="exportQuality" min="10" max="100" value="70" step="5" 
                 oninput="document.getElementById('qualityValue').textContent = this.value + '%'; syncConfigToURL();">
          <span id="qualityValue" class="range-value">70%</span>
          <div class="range-labels">
            <span>Niska</span><span>Średnia</span><span>Wysoka</span>
          </div>
        </div>
        <div class="export-row" style="display:none;">
          <label>FPS:</label>
          <input type="range" id="exportFps" min="1" max="10" value="2" step="1" 
                 oninput="document.getElementById('fpsValue').textContent = this.value">
          <span id="fpsValue" class="range-value">2</span>
          <div class="range-labels">
            <span>1</span><span>5</span><span>10</span>
          </div>
        </div>
      </div>

      <!-- hidden select kept for JS compatibility -->
      <select id="exportFormat" style="display:none"><option value="video">MP4</option></select>

      <button class="btn btn-success" onclick="exportAllFormats()" id="exportBtn">
        ⚡ Generuj wszystkie formaty (MP4, GIF, WebM, ZIP)
      </button>

      <div id="exportResults" style="margin-top:16px;display:none;">
        <h4>Gotowe pliki:</h4>
        <div id="exportLinks" class="export-links"></div>
      </div>

      <h3 style="margin-top:32px; font-size:15px;">Udostępnij</h3>
      <div class="social-links" id="socialLinks"></div>
    </div>
  </div>

</div>

<div class="toast" id="toast"></div>

<script>
// ─── State ──────────────────────────────────────────────────────────────────
let currentSession = null;
let sessionData = null;
let selectedFrames = new Set();
let centerMarks = {};  // frameIndex -> {x, y}
let currentCenterFrame = 0;
let activePreset = 'twitter_post';
let presets = {};
let duplicateIndices = new Set();  // indices marked as duplicates
let duplicatePairs = [];           // [{frame_a, frame_b, similarity}]
let advancedModeOn = false;
let currentFocusMode = 'screen';
let currentZoomLevel = 2.0;
let currentMousePadding = 100;

// ─── URL Management ───────────────────────────────────────────────────────────
function parseURL() {
  const path = window.location.pathname;
  const params = new URLSearchParams(window.location.search);
  
  // Parse session and tab from URL
  if (path.startsWith('/session/')) {
    const parts = path.split('/');
    const sessionName = parts[2];
    const tabName = parts[4] || 'select'; // default to select tab
    
    return { sessionName, tabName };
  }
  
  return { sessionName: null, tabName: 'select' };
}

function updateURL(sessionName = null, tabName = null) {
  let url = '/';
  
  if (sessionName) {
    const params = new URLSearchParams();
    params.set('preset', activePreset || '');
    params.set('focus', currentFocusMode);
    params.set('zoom', String(currentZoomLevel));
    params.set('pad', String(currentMousePadding));
    params.set('dur', document.getElementById('exportDuration')?.value || '0.5');
    params.set('quality', document.getElementById('exportQuality')?.value || '70');
    url = `/session/${sessionName}/tab/${tabName || 'select'}?${params.toString()}`;
  }
  
  window.history.pushState({}, '', url);
}

function syncConfigToURL() {
  if (!currentSession) return;
  const { tabName } = parseURL();
  const params = new URLSearchParams();
  params.set('preset', activePreset || '');
  params.set('focus', currentFocusMode);
  params.set('zoom', String(currentZoomLevel));
  params.set('pad', String(currentMousePadding));
  params.set('dur', document.getElementById('exportDuration')?.value || '0.5');
  params.set('quality', document.getElementById('exportQuality')?.value || '70');
  const url = `/session/${currentSession}/tab/${tabName || 'select'}?${params.toString()}`;
  window.history.replaceState({}, '', url);
}

function navigateToSession(sessionName, tabName = 'select') {
  updateURL(sessionName, tabName);
  loadSession(sessionName);
  if (tabName !== 'select') {
    switchTab(tabName);
  }
}

function navigateToTab(tabName) {
  if (currentSession) {
    updateURL(currentSession, tabName);
  }
  switchTab(tabName);
}

function applyConfigFromURL() {
  const params = new URLSearchParams(window.location.search);
  const preset = params.get('preset');
  const focus = params.get('focus');
  const zoom = params.get('zoom');
  const pad = params.get('pad');
  const dur = params.get('dur');
  const quality = params.get('quality');

  if (preset && presets[preset]) {
    activePreset = preset;
    updatePresetBtns();
  }
  if (focus) updateFocusMode(focus);
  if (zoom) updateZoom(parseFloat(zoom));
  if (pad) updatePadding(parseInt(pad));
  if (dur) {
    const durationEl = document.getElementById('exportDuration');
    const durationValue = document.getElementById('durationValue');
    if (durationEl && durationValue) {
      durationEl.value = dur;
      durationValue.textContent = `${dur}s`;
    }
  }
  if (quality) {
    const qualityEl = document.getElementById('exportQuality');
    const qualityValue = document.getElementById('qualityValue');
    if (qualityEl && qualityValue) {
      qualityEl.value = quality;
      qualityValue.textContent = `${quality}%`;
    }
  }
  updatePublishPresetInfo();
}

// ─── Init ───────────────────────────────────────────────────────────────────
document.addEventListener('DOMContentLoaded', async () => {
  await loadSessions();
  await loadPresets();
  applyConfigFromURL();
  setupDragDrop();
  setupCenterCanvas();
  buildSocialLinks();
  updateTabHandlers();

  document.getElementById('exportDuration')?.addEventListener('input', syncConfigToURL);
  document.getElementById('exportQuality')?.addEventListener('input', syncConfigToURL);
  
  // Handle browser back/forward
  window.addEventListener('popstate', () => {
    const { sessionName, tabName } = parseURL();
    if (sessionName && sessionName !== currentSession) {
      loadSession(sessionName);
    }
    if (tabName) {
      switchTab(tabName);
    }
  });
  
  // Load initial state from URL
  const { sessionName, tabName } = parseURL();
  if (sessionName) {
    await loadSession(sessionName);
    if (tabName !== 'select') {
      switchTab(tabName);
    }
  }
});

async function api(path, opts = {}) {
  const res = await fetch('/api' + path, {
    headers: { 'Content-Type': 'application/json' },
    ...opts,
    body: opts.body ? JSON.stringify(opts.body) : undefined,
  });
  if (!res.ok) throw new Error(`API error: ${res.status}`);
  return res.json();
}

// ─── Sessions ───────────────────────────────────────────────────────────────
async function loadSessions() {
  const sessions = await api('/sessions');
  const sel = document.getElementById('sessionSelect');
  sel.innerHTML = '<option value="">— wybierz sesję —</option>';
  sessions.forEach(s => {
    const opt = document.createElement('option');
    opt.value = s.name;
    opt.textContent = `${s.name} (${s.frame_count} kl.)`;
    sel.appendChild(opt);
  });
  
  // Auto-load first session if no URL session specified
  const { sessionName } = parseURL();
  if (!sessionName && sessions.length > 0) {
    sel.value = sessions[0].name;
    loadSession(sessions[0].name);
  } else if (sessionName) {
    sel.value = sessionName;
  }
}

async function loadSession(name) {
  if (!name) return;
  currentSession = name;
  sessionData = await api(`/sessions/${name}`);
  document.getElementById('sessionInfo').textContent =
    `${sessionData.frame_count} klatek • ${sessionData.duration.toFixed(1)}s`;

  // Update URL to reflect current session
  updateURL(name, 'select');

  // Show warning if frames are missing on disk
  const missingBanner = document.getElementById('missingFramesBanner');
  const missing = sessionData._missing_frames || [];
  if (missing.length > 0) {
    missingBanner.style.display = 'flex';
    missingBanner.innerHTML = `
      <span>⚠️ <strong>${missing.length} klatek brakuje na dysku</strong> — pliki nie zostały zapisane podczas nagrywania:<br>
      <small style="font-family:monospace;opacity:.8">${missing.join(', ')}</small></span>
      <button onclick="document.getElementById('missingFramesBanner').style.display='none'" style="background:none;border:none;color:inherit;cursor:pointer;font-size:18px;margin-left:12px">✕</button>
    `;
  } else {
    missingBanner.style.display = 'none';
  }

  // Załaduj zaznaczenie
  const prev = sessionData.selected_frames;
  selectedFrames = new Set(prev || sessionData.frames.map((_, i) => i));

  // Załaduj centra
  centerMarks = {};
  const cc = sessionData.custom_centers || {};
  for (const [k, v] of Object.entries(cc)) {
    centerMarks[parseInt(k)] = v;
  }

  renderFrameGrid();
}

// ─── Tab 1: Frame Selection ─────────────────────────────────────────────────
function renderFrameGrid() {
  const grid = document.getElementById('frameGrid');
  grid.innerHTML = '';
  if (!sessionData) return;

  const missingSet = new Set(sessionData._missing_frames || []);

  sessionData.frames.forEach((f, i) => {
    const isMissing = missingSet.has(f.filename);
    const isDup = duplicateIndices.has(i);
    const card = document.createElement('div');
    card.className = `frame-card${selectedFrames.has(i) ? ' selected' : ''}${isMissing ? ' frame-missing' : ''}${isDup ? ' duplicate' : ''}`;
    card.onclick = () => toggleFrame(i, card);

    // Find best similarity match for this duplicate
    let simLabel = '';
    if (isDup) {
      const pair = duplicatePairs.find(p => p.frame_b === i);
      if (pair) simLabel = `<span class="dup-similarity">${pair.similarity}%</span>`;
    }

    card.innerHTML = isMissing
      ? `
        <div class="frame-missing-overlay">❌ Brak pliku<br><small>${f.filename}</small></div>
        <div class="check">${selectedFrames.has(i) ? '✓' : ''}</div>
        <button class="del-frame" title="Usuń klatkę" onclick="event.stopPropagation(); deleteFrame(${i})">✕</button>
        <div class="meta"><span>#${i+1} • ${f.timestamp.toFixed(1)}s</span><span style="color:var(--red)">⚠ brak pliku</span></div>
      `
      : `
        <img src="/api/sessions/${currentSession}/frames/${f.filename}" loading="lazy">
        <div class="check">${selectedFrames.has(i) ? '✓' : ''}</div>
        ${simLabel}
        <button class="del-frame" title="Usuń klatkę" onclick="event.stopPropagation(); deleteFrame(${i})">✕</button>
        <div class="meta">
          <span>#${i+1} • ${f.timestamp.toFixed(1)}s</span>
          <span>Δ${f.change_pct.toFixed(0)}% • 🖱(${f.mouse_x},${f.mouse_y})</span>
        </div>
      `;
    grid.appendChild(card);
  });
}

function toggleFrame(idx, card) {
  if (selectedFrames.has(idx)) {
    selectedFrames.delete(idx);
    card.classList.remove('selected');
    card.querySelector('.check').textContent = '';
  } else {
    selectedFrames.add(idx);
    card.classList.add('selected');
    card.querySelector('.check').textContent = '✓';
  }
}

function selectAllFrames() {
  sessionData.frames.forEach((_, i) => selectedFrames.add(i));
  renderFrameGrid();
}

function deselectAllFrames() {
  selectedFrames.clear();
  renderFrameGrid();
}

// ─── Frame Trash ───────────────────────────────────────────────────────────────
const frameTrash = [];  // { sessionName, frame, origIdx }

function toggleTrash() {
  document.getElementById('trashSidebar').classList.toggle('open');
}

function updateTrashBadge() {
  const badge = document.getElementById('trashBadge');
  badge.textContent = frameTrash.length;
  badge.classList.toggle('show', frameTrash.length > 0);
}

function renderTrashList() {
  const list = document.getElementById('trashList');
  if (frameTrash.length === 0) {
    list.innerHTML = '<div class="trash-empty-msg">Kosz jest pusty</div>';
    return;
  }
  list.innerHTML = '';
  frameTrash.forEach((item, i) => {
    const el = document.createElement('div');
    el.className = 'trash-item';
    el.innerHTML = `
      <img src="/api/sessions/${item.sessionName}/frames/${item.frame.filename}" onerror="this.style.display='none'">
      <div class="trash-item-info">
        <div class="trash-item-name">#${item.origIdx + 1} • ${item.frame.filename}</div>
        <div class="trash-item-actions">
          <button class="trash-item-restore" onclick="restoreFrame(${i})">↩ Przywróć</button>
          <button class="trash-item-purge" onclick="purgeFrame(${i})" title="Usuń trwale">✕</button>
        </div>
      </div>
    `;
    list.appendChild(el);
  });
}

function deleteFrame(idx) {
  if (!currentSession || !sessionData) return;
  const f = sessionData.frames[idx];
  if (!f) return;

  // Move to trash (soft delete)
  frameTrash.unshift({ sessionName: currentSession, frame: { ...f }, origIdx: idx });
  updateTrashBadge();

  // Remove from local state
  sessionData.frames.splice(idx, 1);
  sessionData.frame_count = sessionData.frames.length;
  sessionData.frames.forEach((fr, i) => { fr.index = i; });
  selectedFrames = new Set([...selectedFrames].filter(x => x !== idx).map(x => x > idx ? x - 1 : x));
  renderFrameGrid();
  renderTrashList();
  toast(`Klatka #${idx + 1} w koszu — ↩ przywróć w koszu`);
}

async function restoreFrame(i) {
  const item = frameTrash.splice(i, 1)[0];
  // Re-insert at original position (or end)
  const insertAt = Math.min(item.origIdx, sessionData.frames.length);
  sessionData.frames.splice(insertAt, 0, item.frame);
  sessionData.frame_count = sessionData.frames.length;
  sessionData.frames.forEach((fr, j) => { fr.index = j; });
  selectedFrames = new Set([...selectedFrames].map(x => x >= insertAt ? x + 1 : x));
  selectedFrames.add(insertAt);
  renderFrameGrid();
  updateTrashBadge();
  renderTrashList();
  toast(`Klatka przywrócona na pozycję #${insertAt + 1}`);
}

async function purgeFrame(i) {
  const item = frameTrash.splice(i, 1)[0];
  try {
    await fetch(`/api/sessions/${item.sessionName}/frames/${item.frame.filename}`, { method: 'DELETE' });
  } catch (e) { /* ignore */ }
  updateTrashBadge();
  renderTrashList();
  toast(`Klatka trwale usunięta`);
}

async function emptyFrameTrash() {
  await Promise.all(frameTrash.map(item =>
    fetch(`/api/sessions/${item.sessionName}/frames/${item.frame.filename}`, { method: 'DELETE' }).catch(() => {})
  ));
  frameTrash.length = 0;
  updateTrashBadge();
  renderTrashList();
  toast('Kosz opróżniony');
}

async function saveSelection() {
  if (!currentSession) return;
  await api(`/sessions/${currentSession}/select`, {
    method: 'POST',
    body: { selected_indices: [...selectedFrames].sort() },
  });
  toast('Zapisano wybór klatek');
}

// ─── Tools: Tab 1 (Select) ──────────────────────────────────────────────────

async function detectDuplicates() {
  if (!currentSession) return;
  toast('Szukam duplikatów...');
  try {
    const res = await api(`/sessions/${currentSession}/similarity?threshold=90`);
    duplicatePairs = res.duplicate_pairs || [];
    duplicateIndices = new Set(res.duplicate_indices || []);

    const badge = document.getElementById('dupCount');
    const btn = document.getElementById('btnRemoveDups');
    if (duplicateIndices.size > 0) {
      badge.textContent = duplicateIndices.size;
      badge.style.display = 'inline-block';
      btn.style.display = '';
      toast(`Znaleziono ${duplicateIndices.size} duplikatów (${duplicatePairs.length} par ≥90%)`);
    } else {
      badge.style.display = 'none';
      btn.style.display = 'none';
      toast('Brak duplikatów — wszystkie klatki są unikalne ✅');
    }
    renderFrameGrid();
  } catch (e) {
    toast('Błąd analizy podobieństwa: ' + e.message);
  }
}

function removeDuplicates() {
  if (duplicateIndices.size === 0) return;
  const toRemove = [...duplicateIndices].sort((a, b) => b - a);
  toRemove.forEach(idx => deleteFrame(idx));
  duplicateIndices.clear();
  duplicatePairs = [];
  document.getElementById('dupCount').style.display = 'none';
  document.getElementById('btnRemoveDups').style.display = 'none';
  toast(`Usunięto ${toRemove.length} duplikatów do kosza`);
}

function sortByChange() {
  if (!sessionData) return;
  const sorted = sessionData.frames.slice().sort((a, b) => b.change_pct - a.change_pct);
  const origOrder = sessionData.frames.map(f => f.filename);
  sessionData.frames = sorted;
  sessionData.frames.forEach((f, i) => { f.index = i; });
  selectedFrames = new Set(sessionData.frames.map((_, i) => i));
  renderFrameGrid();
  toast('Posortowano wg % zmiany (malejąco)');
}

function invertSelection() {
  if (!sessionData) return;
  const newSel = new Set();
  sessionData.frames.forEach((_, i) => {
    if (!selectedFrames.has(i)) newSel.add(i);
  });
  selectedFrames = newSel;
  renderFrameGrid();
  toast(`Odwrócono zaznaczenie — ${selectedFrames.size} zaznaczonych`);
}

function selectEveryNth() {
  const n = parseInt(prompt('Zaznacz co którą klatkę? (np. 2 = co drugą)', '2'));
  if (!n || n < 1 || !sessionData) return;
  selectedFrames.clear();
  sessionData.frames.forEach((_, i) => {
    if (i % n === 0) selectedFrames.add(i);
  });
  renderFrameGrid();
  toast(`Zaznaczono co ${n}. klatkę — ${selectedFrames.size} zaznaczonych`);
}

function selectMostDifferent() {
  if (!sessionData) return;
  
  // Sort frames by change_pct (descending)
  const framesWithIndex = sessionData.frames.map((frame, index) => ({
    index,
    change_pct: frame.change_pct || 0
  }));
  
  framesWithIndex.sort((a, b) => b.change_pct - a.change_pct);
  
  // Calculate threshold based on distribution
  const changes = framesWithIndex.map(f => f.change_pct);
  const avgChange = changes.reduce((a, b) => a + b, 0) / changes.length;
  const maxChange = Math.max(...changes);
  
  // Select frames with change > average + 25% of (max - average)
  // This captures the top ~25% most changing frames
  const threshold = avgChange + (maxChange - avgChange) * 0.25;
  
  selectedFrames.clear();
  framesWithIndex.forEach(frame => {
    if (frame.change_pct >= threshold) {
      selectedFrames.add(frame.index);
    }
  });
  
  // Always include first frame (usually important)
  selectedFrames.add(0);
  
  renderFrameGrid();
  toast(`Zaznaczono ${selectedFrames.size} klatek z największymi zmianami (>${threshold.toFixed(1)}%)`);
}

// ─── Tab 2: Annotations ─────────────────────────────────────────────────────
let annTool = 'arrow';       // 'arrow' | 'rect' | 'text'
let annColor = '#fc5c7c';
let annDrawing = false;
let annStartX = 0, annStartY = 0;
let annCurrentFrame = null;
// annotations[frameIndex] = [ {type, color, lineWidth, x1,y1,x2,y2, text?} ]
let annotations = {};
let annBgImage = null;       // current background Image object

function setAnnotateTool(tool) {
  annTool = tool;
  ['annToolArrow','annToolRect','annToolText'].forEach(id => {
    const el = document.getElementById(id);
    if (el) el.classList.toggle('success', id === 'annTool' + tool.charAt(0).toUpperCase() + tool.slice(1));
  });
}

function setAnnotateColor(color, btn) {
  annColor = color;
  document.querySelectorAll('.annotate-color-btn').forEach(b => b.classList.remove('active'));
  if (btn) btn.classList.add('active');
}

function renderAnnotatePanel() {
  if (!sessionData) return;
  const list = document.getElementById('annotateFrameList');
  list.innerHTML = '';
  const selected = [...selectedFrames].sort();

  selected.forEach((idx, i) => {
    const f = sessionData.frames[idx];
    const thumb = document.createElement('div');
    const hasAnn = annotations[idx] && annotations[idx].length > 0;
    thumb.className = `annotate-thumb${i === 0 && annCurrentFrame === null ? ' active' : ''}${annCurrentFrame === idx ? ' active' : ''}`;
    thumb.onclick = () => showAnnotateFrame(idx);
    thumb.innerHTML = `
      <img src="/api/sessions/${currentSession}/frames/${f.filename}">
      <span class="idx">#${idx+1} ${hasAnn ? '✏️' : ''}</span>
    `;
    list.appendChild(thumb);
  });

  if (selected.length > 0 && annCurrentFrame === null) showAnnotateFrame(selected[0]);
}

function showAnnotateFrame(idx) {
  annCurrentFrame = idx;
  const f = sessionData.frames[idx];
  const canvas = document.getElementById('annotateCanvas');
  const ctx = canvas.getContext('2d');

  const img = new Image();
  img.crossOrigin = 'anonymous';
  img.onload = () => {
    canvas.width = img.naturalWidth;
    canvas.height = img.naturalHeight;
    annBgImage = img;
    redrawAnnotations();
  };
  img.src = `/api/sessions/${currentSession}/frames/${f.filename}`;

  // Highlight thumb
  document.querySelectorAll('.annotate-thumb').forEach(t => t.classList.remove('active'));
  const thumbs = document.querySelectorAll('.annotate-thumb');
  const selected = [...selectedFrames].sort();
  const ti = selected.indexOf(idx);
  if (ti >= 0 && thumbs[ti]) thumbs[ti].classList.add('active');
}

function redrawAnnotations() {
  const canvas = document.getElementById('annotateCanvas');
  const ctx = canvas.getContext('2d');
  if (annBgImage) {
    ctx.drawImage(annBgImage, 0, 0);
  }
  const items = annotations[annCurrentFrame] || [];
  items.forEach(a => drawAnnotation(ctx, a));
}

function drawAnnotation(ctx, a) {
  ctx.strokeStyle = a.color;
  ctx.fillStyle = a.color;
  ctx.lineWidth = a.lineWidth;
  ctx.lineCap = 'round';

  if (a.type === 'arrow') {
    // Line
    ctx.beginPath();
    ctx.moveTo(a.x1, a.y1);
    ctx.lineTo(a.x2, a.y2);
    ctx.stroke();
    // Arrowhead
    const angle = Math.atan2(a.y2 - a.y1, a.x2 - a.x1);
    const headLen = Math.max(a.lineWidth * 5, 16);
    ctx.beginPath();
    ctx.moveTo(a.x2, a.y2);
    ctx.lineTo(a.x2 - headLen * Math.cos(angle - 0.4), a.y2 - headLen * Math.sin(angle - 0.4));
    ctx.lineTo(a.x2 - headLen * Math.cos(angle + 0.4), a.y2 - headLen * Math.sin(angle + 0.4));
    ctx.closePath();
    ctx.fill();
  } else if (a.type === 'rect') {
    ctx.lineWidth = a.lineWidth;
    ctx.strokeRect(a.x1, a.y1, a.x2 - a.x1, a.y2 - a.y1);
    // Light fill for highlight effect
    ctx.fillStyle = a.color + '18';
    ctx.fillRect(a.x1, a.y1, a.x2 - a.x1, a.y2 - a.y1);
  } else if (a.type === 'text') {
    const fontSize = Math.max(a.lineWidth * 8, 20);
    ctx.font = `bold ${fontSize}px sans-serif`;
    ctx.fillStyle = a.color;
    ctx.shadowColor = 'rgba(0,0,0,0.6)';
    ctx.shadowBlur = 4;
    ctx.fillText(a.text, a.x1, a.y1);
    ctx.shadowBlur = 0;
  }
}

function getCanvasCoords(e) {
  const canvas = document.getElementById('annotateCanvas');
  const rect = canvas.getBoundingClientRect();
  const scaleX = canvas.width / rect.width;
  const scaleY = canvas.height / rect.height;
  return {
    x: Math.round((e.clientX - rect.left) * scaleX),
    y: Math.round((e.clientY - rect.top) * scaleY),
  };
}

function setupAnnotateCanvas() {
  const canvas = document.getElementById('annotateCanvas');

  canvas.addEventListener('mousedown', (e) => {
    if (annCurrentFrame === null) return;
    const { x, y } = getCanvasCoords(e);
    annDrawing = true;
    annStartX = x;
    annStartY = y;

    if (annTool === 'text') {
      annDrawing = false;
      const text = prompt('Wpisz tekst:', '');
      if (!text) return;
      if (!annotations[annCurrentFrame]) annotations[annCurrentFrame] = [];
      annotations[annCurrentFrame].push({
        type: 'text', color: annColor,
        lineWidth: parseInt(document.getElementById('annLineWidth').value),
        x1: x, y1: y, x2: x, y2: y, text,
      });
      redrawAnnotations();
    }
  });

  canvas.addEventListener('mousemove', (e) => {
    if (!annDrawing || annTool === 'text') return;
    const { x, y } = getCanvasCoords(e);
    // Live preview
    redrawAnnotations();
    const ctx = canvas.getContext('2d');
    const lw = parseInt(document.getElementById('annLineWidth').value);
    drawAnnotation(ctx, {
      type: annTool, color: annColor, lineWidth: lw,
      x1: annStartX, y1: annStartY, x2: x, y2: y,
    });
  });

  canvas.addEventListener('mouseup', (e) => {
    if (!annDrawing || annTool === 'text') return;
    annDrawing = false;
    const { x, y } = getCanvasCoords(e);
    const lw = parseInt(document.getElementById('annLineWidth').value);
    if (!annotations[annCurrentFrame]) annotations[annCurrentFrame] = [];
    annotations[annCurrentFrame].push({
      type: annTool, color: annColor, lineWidth: lw,
      x1: annStartX, y1: annStartY, x2: x, y2: y,
    });
    redrawAnnotations();
  });

  canvas.addEventListener('mouseleave', () => {
    if (annDrawing && annTool !== 'text') {
      annDrawing = false;
      redrawAnnotations();
    }
  });
}

function undoAnnotation() {
  if (annCurrentFrame === null) return;
  const items = annotations[annCurrentFrame];
  if (!items || items.length === 0) { toast('Brak adnotacji do cofnięcia'); return; }
  items.pop();
  redrawAnnotations();
  toast('Cofnięto adnotację');
}

function clearAnnotations() {
  if (annCurrentFrame === null) return;
  annotations[annCurrentFrame] = [];
  redrawAnnotations();
  toast('Wyczyszczono adnotacje na tej klatce');
}

function saveAnnotations() {
  // Annotations are saved as rendered PNGs onto the frame images
  // For now, store in memory — actual baking happens at export
  toast(`Zapisano adnotacje dla ${Object.keys(annotations).filter(k => annotations[k]?.length > 0).length} klatek`);
}

// Init annotation canvas on DOMContentLoaded
document.addEventListener('DOMContentLoaded', () => {
  setupAnnotateCanvas();
});

// ─── Tools: Tab 3 (Center) ──────────────────────────────────────────────────

function centerToMiddle() {
  if (!sessionData) return;
  [...selectedFrames].forEach(idx => {
    const f = sessionData.frames[idx];
    if (f) centerMarks[idx] = { x: Math.round(f.width / 2), y: Math.round(f.height / 2) };
  });
  renderCenterPanel();
  toast('Środek ustawiony na środek obrazu dla zaznaczonych klatek');
}

function copyCenterToAll() {
  if (!sessionData) return;
  const current = centerMarks[currentCenterFrame];
  if (!current) { toast('Najpierw oznacz środek na bieżącej klatce'); return; }
  [...selectedFrames].forEach(idx => {
    centerMarks[idx] = { ...current };
  });
  renderCenterPanel();
  toast(`Skopiowano środek (${current.x}, ${current.y}) do ${selectedFrames.size} klatek`);
}

// ─── Export Focus Mode Functions ───────────────────────────────────────────
function updateExportFocusMode(mode) {
  currentFocusMode = mode;
  const zoomControls = document.getElementById('exportZoomControls');
  zoomControls.style.display = mode === 'mouse' ? 'block' : 'none';
  
  // Sync with crop tab
  document.querySelector('input[name="focusMode"][value="' + mode + '"]').checked = true;
  
  // Update video preview
  updateVideoPreview();
  
  toast(`Tryb focusu eksportu: ${mode === 'screen' ? 'Ekran' : mode === 'mouse' ? 'Mysz' : mode === 'keyboard' ? 'Klawiatura' : 'Aplikacja'}`);
}

function updateExportZoom(value) {
  currentZoomLevel = parseFloat(value);
  document.getElementById('exportZoomValue').textContent = currentZoomLevel.toFixed(1) + 'x';
  
  // Sync with crop tab
  document.getElementById('zoomSlider').value = value;
  document.getElementById('zoomValue').textContent = currentZoomLevel.toFixed(1) + 'x';
  
  // Update video preview
  updateVideoPreview();
}

function updateExportPadding(value) {
  currentMousePadding = parseInt(value);
  document.getElementById('exportPaddingValue').textContent = currentMousePadding + 'px';
  
  // Sync with crop tab
  document.getElementById('paddingSlider').value = value;
  document.getElementById('paddingValue').textContent = currentMousePadding + 'px';
  
  // Update video preview
  updateVideoPreview();
}

// ─── Video Preview Functions ───────────────────────────────────────────
let videoPreviewTimeout = null;

function updateVideoPreview() {
  // Clear previous timeout
  if (videoPreviewTimeout) {
    clearTimeout(videoPreviewTimeout);
  }
  
  // Debounce preview generation
  videoPreviewTimeout = setTimeout(() => {
    generateVideoPreview('crop');
  }, 500);
}

async function generateVideoPreview(context) {
  if (!currentSession || !sessionData) return;
  
  const previewId = 'videoPreview';
  const previewEl = document.getElementById(previewId);
  
  if (!previewEl) return;
  
  // Show loading state
  previewEl.innerHTML = `
    <div style="color:var(--text2);font-size:12px;text-align:center">
      <div class="spinner" style="margin:0 auto 8px auto"></div>
      <div>Generowanie podglądu...</div>
      <small style="display:block;margin-top:4px">${currentFocusMode === 'mouse' ? `Zoom ${currentZoomLevel}x • obszar ${currentMousePadding}px` : currentFocusMode}</small>
    </div>
  `;
  
  try {
    const result = await api(`/sessions/${currentSession}/video-preview`, {
      method: 'POST',
      body: {
        preset: activePreset,
        frame_indices: [...selectedFrames].sort(),
        focus_mode: currentFocusMode,
        zoom_level: currentZoomLevel,
        mouse_padding: currentMousePadding,
      },
    });
    
    const focusIcon = result.focus_mode === 'mouse' ? '🖱️' : 
                      result.focus_mode === 'keyboard' ? '⌨️' : 
                      result.focus_mode === 'application' ? '🪬' : '🖥️';
    
    previewEl.innerHTML = `
      <div style="display:flex;flex-direction:column;align-items:center;gap:8px;width:100%">
        <img src="${result.preview_url}" style="max-width:100%;max-height:200px;border-radius:4px;box-shadow:0 2px 8px rgba(0,0,0,0.1)">
        <div style="font-size:11px;color:var(--text2);text-align:center;line-height:1.4">
          <div><strong>Klatka #${result.frame_index + 1}</strong> • ${focusIcon} ${result.focus_mode}</div>
          ${result.focus_mode === 'mouse' ? `<div>Zoom ${result.zoom_level}x • obszar ${currentMousePadding}px</div>` : ''}
          <div>Wycięto: ${result.crop.w}×${result.crop.h}px → ${result.target.w}×${result.target.h}px</div>
          <div>Środek: (${result.center.x}, ${result.center.y})</div>
        </div>
      </div>
    `;
  } catch (e) {
    previewEl.innerHTML = `
      <div style="color:var(--red);font-size:12px;text-align:center">
        <div>❌ Błąd generowania podglądu</div>
        <small style="display:block;margin-top:4px">${e.message}</small>
      </div>
    `;
  }
}

// ─── Focus Mode & Zoom Functions ───────────────────────────────────────────
function updateFocusMode(mode) {
  currentFocusMode = mode;
  const zoomControls = document.getElementById('zoomControls');
  zoomControls.style.display = mode === 'mouse' ? 'block' : 'none';
  
  // Sync with export tab
  document.querySelector('input[name="exportFocusMode"][value="' + mode + '"]').checked = true;
  
  // Auto-refresh crop preview if on crop tab
  const cropPanel = document.getElementById('panel-crop');
  if (cropPanel && cropPanel.classList.contains('active')) {
    loadCropPreview();
  }
  
  // Update video preview
  updateVideoPreview();
  
  toast(`Tryb focusu: ${mode === 'screen' ? 'Ekran' : mode === 'mouse' ? 'Mysz' : mode === 'keyboard' ? 'Klawiatura' : 'Aplikacja'}`);
}

function updateZoom(value) {
  currentZoomLevel = parseFloat(value);
  document.getElementById('zoomValue').textContent = currentZoomLevel.toFixed(1) + 'x';
  
  // Sync with export tab
  document.getElementById('exportZoomSlider').value = value;
  document.getElementById('exportZoomValue').textContent = currentZoomLevel.toFixed(1) + 'x';
  
  // Auto-refresh crop preview
  const cropPanel = document.getElementById('panel-crop');
  if (cropPanel && cropPanel.classList.contains('active')) {
    loadCropPreview();
  }
  
  // Update video preview
  updateVideoPreview();
}

function updatePadding(value) {
  currentMousePadding = parseInt(value);
  document.getElementById('paddingValue').textContent = currentMousePadding + 'px';
  
  // Sync with export tab
  document.getElementById('exportPaddingSlider').value = value;
  document.getElementById('exportPaddingValue').textContent = currentMousePadding + 'px';
  
  // Auto-refresh crop preview
  const cropPanel = document.getElementById('panel-crop');
  if (cropPanel && cropPanel.classList.contains('active')) {
    loadCropPreview();
  }
  
  // Update video preview
  updateVideoPreview();
}

// ─── Tools: Tab 3 (Crop) ────────────────────────────────────────────────────

function previewAllPresets() {
  toast('Podgląd wszystkich formatów — wkrótce');
}

function fitToContent() {
  toast('Dopasowanie do treści — wymaga analizy OCR zawartości klatek');
}

function addCustomPreset() {
  const w = parseInt(prompt('Szerokość (px):', '1080'));
  const h = parseInt(prompt('Wysokość (px):', '1080'));
  if (!w || !h) return;
  const name = prompt('Nazwa formatu:', `custom_${w}x${h}`) || `custom_${w}x${h}`;
  presets[name] = { width: w, height: h, label: name };
  toast(`Dodano format ${name} (${w}×${h})`);
}

// ─── Tools: Tab 4 (Versions) ────────────────────────────────────────────────

function compareVersionsSideBySide() {
  toast('Porównanie bok-do-boku — wkrótce');
}

function downloadAllVersions() {
  toast('Pobieranie wszystkich wersji — wkrótce');
}

// ─── Tools: Tab 5 (Publish) ─────────────────────────────────────────────────

function exportAllFormats() {
  toast('Eksport we wszystkich formatach — wkrótce');
}

function addWatermark() {
  toast('Znak wodny — wkrótce');
}

// ─── Advanced Mode ──────────────────────────────────────────────────────────

function toggleAdvancedMode(on) {
  advancedModeOn = on;
  document.querySelectorAll('.tool-btn.advanced').forEach(btn => {
    btn.style.display = on ? '' : 'none';
  });
}

function advancedSimilarity() {
  toast('🧠 AI detekcja wymaga: pip install torch torchvision\nUżyj "make install" aby zainstalować');
}
function advancedAutoCenter() {
  toast('🧠 AI auto-center wymaga: pip install torch torchvision\nUżyj "make install" aby zainstalować');
}
function advancedSmartCrop() {
  toast('🧠 AI smart crop wymaga: pip install opencv-python-headless\nUżyj "make install" aby zainstalować');
}
function advancedUpscale() {
  toast('🧠 AI upscale wymaga: pip install realesrgan\nUżyj "make install" aby zainstalować');
}
function advancedVideoExport() {
  toast('🧠 Zaawansowany eksport wymaga: sudo apt install ffmpeg\nUżyj "make install" aby zainstalować');
}

// ─── Init advanced mode ─────────────────────────────────────────────────────
document.addEventListener('DOMContentLoaded', () => {
  toggleAdvancedMode(false);
});

// ─── Mouse Position Matrix ─────────────────────────────────────────────
function renderMouseMatrix() {
  if (!sessionData) return;
  
  const matrix = document.getElementById('mouseMatrix');
  const selected = [...selectedFrames].sort();
  
  if (selected.length === 0) {
    matrix.innerHTML = '<div style="grid-column:1/-1;text-align:center;color:var(--text2);font-size:12px;padding:16px">Brak zaznaczonych klatek</div>';
    return;
  }
  
  matrix.innerHTML = '';
  
  selected.forEach((frameIdx, i) => {
    if (i >= 9) return; // Max 9 frames in 3x3 grid
    
    const frame = sessionData.frames[frameIdx];
    if (!frame) return;
    
    const item = document.createElement('div');
    item.className = 'mouse-matrix-item';
    if (frameIdx === currentCenterFrame) item.classList.add('active');
    if (centerMarks[frameIdx]) item.classList.add('marked');
    
    item.onclick = () => showCenterFrame(frameIdx);
    
    // Calculate positions for visual markers
    const centerX = centerMarks[frameIdx]?.x || frame.suggested_center_x || frame.width / 2;
    const centerY = centerMarks[frameIdx]?.y || frame.suggested_center_y || frame.height / 2;
    const mouseX = frame.mouse_x || frame.width / 2;
    const mouseY = frame.mouse_y || frame.height / 2;
    
    // Convert to percentages for positioning
    const centerXPct = (centerX / frame.width) * 100;
    const centerYPct = (centerY / frame.height) * 100;
    const mouseXPct = (mouseX / frame.width) * 100;
    const mouseYPct = (mouseY / frame.height) * 100;
    
    item.innerHTML = `
      <img src="/api/sessions/${currentSession}/frames/${frame.filename}" loading="lazy">
      <div class="frame-number">#${frameIdx + 1}</div>
      <div class="mouse-position">🖱️ ${Math.round(mouseX)},${Math.round(mouseY)}</div>
      ${centerMarks[frameIdx] ? `<div class="center-mark" style="left:${centerXPct}%;top:${centerYPct}%"></div>` : ''}
      <div class="suggested-center" style="left:${(frame.suggested_center_x / frame.width) * 100}%;top:${(frame.suggested_center_y / frame.height) * 100}%"></div>
    `;
    
    matrix.appendChild(item);
  });
  
  // If more than 9 frames, show indicator
  if (selected.length > 9) {
    const more = document.createElement('div');
    more.style.cssText = 'grid-column:1/-1;text-align:center;color:var(--text2);font-size:11px;padding:8px';
    more.textContent = `... i ${selected.length - 9} więcej klatek`;
    matrix.appendChild(more);
  }
}

// ─── Tab 2: Center Marking ──────────────────────────────────────────────────
function setupCenterCanvas() {
  const wrap = document.getElementById('centerCanvasWrap');
  const img = document.getElementById('centerImage');

  img.addEventListener('click', (e) => {
    const rect = img.getBoundingClientRect();
    const scaleX = img.naturalWidth / rect.width;
    const scaleY = img.naturalHeight / rect.height;
    const x = Math.round((e.clientX - rect.left) * scaleX);
    const y = Math.round((e.clientY - rect.top) * scaleY);
    setCenter(currentCenterFrame, x, y);
  });

  img.addEventListener('mousemove', (e) => {
    const rect = img.getBoundingClientRect();
    const px = e.clientX - rect.left;
    const py = e.clientY - rect.top;
    document.getElementById('crossH').style.cssText = `display:block;top:${py + rect.top - wrap.getBoundingClientRect().top}px`;
    document.getElementById('crossV').style.cssText = `display:block;left:${px + rect.left - wrap.getBoundingClientRect().left}px`;
  });

  // ── Keyboard shortcuts ──
  document.addEventListener('keydown', (e) => {
    // Only active when center panel is visible
    const panel = document.getElementById('panel-center');
    if (!panel || !panel.classList.contains('active')) return;
    if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT') return;

    const selected = [...selectedFrames].sort();
    const curIdx = selected.indexOf(currentCenterFrame);

    switch (e.key) {
      case 'ArrowRight':
      case 'ArrowDown':
        e.preventDefault();
        if (curIdx < selected.length - 1) showCenterFrame(selected[curIdx + 1]);
        break;
      case 'ArrowLeft':
      case 'ArrowUp':
        e.preventDefault();
        if (curIdx > 0) showCenterFrame(selected[curIdx - 1]);
        break;
      case ' ':  // Space = accept suggestion (mouse cursor position)
        e.preventDefault();
        {
          const f = sessionData.frames[currentCenterFrame];
          const sx = f.suggested_center_x || Math.round(f.width / 2);
          const sy = f.suggested_center_y || Math.round(f.height / 2);
          setCenter(currentCenterFrame, sx, sy);
          // Auto-advance to next frame
          if (curIdx < selected.length - 1) {
            setTimeout(() => showCenterFrame(selected[curIdx + 1]), 150);
          }
        }
        break;
      case 'c':
      case 'C':  // C = copy center from previous frame
        e.preventDefault();
        if (curIdx > 0) {
          const prev = centerMarks[selected[curIdx - 1]];
          if (prev) {
            setCenter(currentCenterFrame, prev.x, prev.y);
            // Auto-advance
            if (curIdx < selected.length - 1) {
              setTimeout(() => showCenterFrame(selected[curIdx + 1]), 150);
            }
          } else {
            toast('Poprzednia klatka nie ma oznaczonego środka');
          }
        }
        break;
      case 'Enter':  // Enter = next tab
        e.preventDefault();
        saveCenters();
        navigateToTab('crop');
        break;
      case 'Escape':
        e.preventDefault();
        navigateToTab('select');
        break;
    }
  });
}

function renderCenterPanel() {
  if (!sessionData) return;
  const list = document.getElementById('centerFrameList');
  list.innerHTML = '';
  const selected = [...selectedFrames].sort();

  selected.forEach((idx, i) => {
    const f = sessionData.frames[idx];
    const thumb = document.createElement('div');
    const isMarked = centerMarks[idx] != null;
    thumb.className = `center-thumb${i === 0 ? ' active' : ''}${isMarked ? ' marked' : ''}`;
    thumb.onclick = () => showCenterFrame(idx);
    thumb.innerHTML = `
      <img src="/api/sessions/${currentSession}/frames/${f.filename}">
      <span class="idx">#${idx+1} ${isMarked ? '✓' : ''}</span>
    `;
    list.appendChild(thumb);
  });

  if (selected.length > 0) showCenterFrame(selected[0]);
  
  // Render mouse position matrix
  renderMouseMatrix();
}

function showCenterFrame(idx) {
  currentCenterFrame = idx;
  const f = sessionData.frames[idx];
  const img = document.getElementById('centerImage');
  img.src = `/api/sessions/${currentSession}/frames/${f.filename}`;

  // Pokaż zaznaczony środek lub sugestię
  const center = centerMarks[idx] || {
    x: f.suggested_center_x,
    y: f.suggested_center_y,
  };
  img.onload = () => updateCenterDot(center.x, center.y);

  // Podświetl thumb
  document.querySelectorAll('.center-thumb').forEach(t => t.classList.remove('active'));
  const thumbs = document.querySelectorAll('.center-thumb');
  const selected = [...selectedFrames].sort();
  const thumbIdx = selected.indexOf(idx);
  if (thumbIdx >= 0 && thumbs[thumbIdx]) thumbs[thumbIdx].classList.add('active');

  // Update matrix active state
  document.querySelectorAll('.mouse-matrix-item').forEach(item => item.classList.remove('active'));
  const matrixItems = document.querySelectorAll('.mouse-matrix-item');
  if (matrixItems[thumbIdx]) matrixItems[thumbIdx].classList.add('active');

  document.getElementById('centerInfo').textContent =
    centerMarks[idx]
      ? `Środek: (${center.x}, ${center.y})`
      : `Sugestia (kursor): (${center.x}, ${center.y}) — kliknij aby ustawić`;
}

function setCenter(idx, x, y) {
  centerMarks[idx] = { x, y };
  updateCenterDot(x, y);
  document.getElementById('centerInfo').textContent = `Środek: (${x}, ${y})`;

  // Update thumb
  const thumbs = document.querySelectorAll('.center-thumb');
  const selected = [...selectedFrames].sort();
  const thumbIdx = selected.indexOf(idx);
  if (thumbIdx >= 0 && thumbs[thumbIdx]) {
    thumbs[thumbIdx].classList.add('marked');
    thumbs[thumbIdx].querySelector('.idx').textContent = `#${idx+1} ✓`;
  }

  // Update matrix
  const matrixItems = document.querySelectorAll('.mouse-matrix-item');
  if (matrixItems[thumbIdx]) {
    matrixItems[thumbIdx].classList.add('marked');
    // Re-render the specific matrix item to show the new center mark
    renderMouseMatrix();
  }

  toast(`Środek klatki #${idx+1}: (${x}, ${y})`);
}

function updateCenterDot(cx, cy) {
  const img = document.getElementById('centerImage');
  const dot = document.getElementById('centerDot');
  const rect = img.getBoundingClientRect();
  const wrap = document.getElementById('centerCanvasWrap').getBoundingClientRect();

  const px = (cx / img.naturalWidth) * rect.width + (rect.left - wrap.left);
  const py = (cy / img.naturalHeight) * rect.height + (rect.top - wrap.top);

  dot.style.display = 'block';
  dot.style.left = px + 'px';
  dot.style.top = py + 'px';
}

function autoCenters() {
  [...selectedFrames].forEach(idx => {
    const f = sessionData.frames[idx];
    if (!centerMarks[idx]) {
      centerMarks[idx] = {
        x: f.suggested_center_x || f.mouse_x || Math.round(f.width / 2),
        y: f.suggested_center_y || f.mouse_y || Math.round(f.height / 2),
      };
    }
  });
  renderCenterPanel();
  renderMouseMatrix();
  toast('Auto-środki ustawione na pozycji kursora');
}

async function saveCenters() {
  if (!currentSession) return;
  const marks = Object.entries(centerMarks).map(([k, v]) => ({
    frame_index: parseInt(k), center_x: v.x, center_y: v.y,
  }));
  await api(`/sessions/${currentSession}/centers`, {
    method: 'POST',
    body: { marks },
  });
  toast('Zapisano środki');
}

// ─── Tab 3: Crop Preview ────────────────────────────────────────────────────
async function loadPresets() {
  presets = await api('/presets');
  renderPresetBars();
  updatePublishPresetInfo();
}

function renderPresetBars() {
  const bar = document.getElementById('presetBar');
  bar.innerHTML = '';
  for (const [key, p] of Object.entries(presets)) {
    const btn = document.createElement('button');
    btn.className = `preset-btn${key === activePreset ? ' active' : ''}`;
    btn.textContent = p.label;
    btn.dataset.preset = key;
    btn.onclick = () => { activePreset = key; updatePresetBtns(); loadCropPreview(); };
    bar.appendChild(btn);
  }
}

function updatePresetBtns() {
  document.querySelectorAll('#presetBar .preset-btn').forEach(b => {
    b.classList.toggle('active', b.dataset.preset === activePreset);
  });
  updatePublishPresetInfo();
}

function updatePublishPresetInfo() {
  const info = document.getElementById('publishPresetInfo');
  if (!info || !presets[activePreset]) return;
  const preset = presets[activePreset];
  info.textContent = `Wybrany format: ${preset.label} (${preset.w}×${preset.h})`;
}

async function loadCropPreview() {
  if (!currentSession) return;
  const grid = document.getElementById('cropPreviewGrid');
  grid.innerHTML = '<div class="spinner"></div>';

  const result = await api(`/sessions/${currentSession}/crop-preview`, {
    method: 'POST',
    body: {
      preset: activePreset,
      frame_indices: [...selectedFrames].sort(),
      focus_mode: currentFocusMode,
      zoom_level: currentZoomLevel,
      mouse_padding: currentMousePadding,
    },
  });

  grid.innerHTML = '';
  result.previews.forEach(p => {
    const card = document.createElement('div');
    card.className = 'crop-card';
    
    const focusIcon = p.focus_mode === 'mouse' ? '🖱️' : 
                      p.focus_mode === 'keyboard' ? '⌨️' : 
                      p.focus_mode === 'application' ? '🪬' : '🖥️';
    
    card.innerHTML = `
      <img src="/api/sessions/${currentSession}/preview/${p.filename}" loading="lazy">
      <div class="info">
        Klatka #${p.index+1} • ${focusIcon} ${p.focus_mode} • zoom: ${p.zoom_level}x<br>
        środek: (${p.center.x}, ${p.center.y}) • ${p.target.w}×${p.target.h}
      </div>
    `;
    grid.appendChild(card);
  });
}

async function exportSession() {
  if (!currentSession) return;
  
  const format = document.getElementById('exportFormat').value;
  const btn = document.getElementById('exportBtn');
  const originalHtml = btn.innerHTML;
  btn.innerHTML = '<span class="spinner"></span>Generuję...';
  btn.disabled = true;

  try {
    const result = await api(`/sessions/${currentSession}/export`, {
      method: 'POST',
      body: {
        preset: activePreset,
        format: format,
        duration_per_frame: parseFloat(document.getElementById('exportDuration').value),
        fps: Math.round(1 / parseFloat(document.getElementById('exportDuration').value)),
        quality: parseInt(document.getElementById('exportQuality').value),
        frame_indices: [...selectedFrames].sort(),
        focus_mode: currentFocusMode,
        zoom_level: currentZoomLevel,
        mouse_padding: currentMousePadding,
      },
    });

    // Show result in export results section
    document.getElementById('exportResults').style.display = 'block';
    const linksContainer = document.getElementById('exportLinks');
    
    // Check if preview already exists
    let existingPreview = document.querySelector(`[data-format-link="${format}"]`);
    if (!existingPreview) {
      existingPreview = document.createElement('div');
      existingPreview.className = 'export-preview';
      existingPreview.dataset.formatLink = format;
      linksContainer.appendChild(existingPreview);
    }
    
    // Create preview element based on format
    let previewElement = '';
    if (format === 'zip') {
      previewElement = `<div class="preview-icon">📁</div>`;
    } else {
      const isVideo = ['video', 'webm'].includes(format);
      const tag = isVideo ? 'video' : 'img';
      const extraAttrs = isVideo ? 'controls muted loop autoplay' : '';
      previewElement = `<${tag} src="${result.download_url}" ${extraAttrs} class="preview-media"></${tag}>`;
    }
    
    existingPreview.innerHTML = `
      <div class="preview-header">
        <span class="format-badge">${format.toUpperCase()}</span>
        <a href="${result.download_url}" download class="download-link">⬇ ${result.filename} (${result.size_mb} MB)</a>
      </div>
      <div class="preview-content">
        ${previewElement}
      </div>
    `;
    
    toast(`Eksport ${format.toUpperCase()} gotowy!`);
  } catch (e) {
    toast('Błąd eksportu: ' + e.message);
  } finally {
    btn.innerHTML = originalHtml;
    btn.disabled = false;
  }
}

// ─── Publish: Animated Frame Preview Loop ───────────────────────────────────
let _publishAnimFrames = [];   // array of img src URLs (crop previews)
let _publishAnimIdx = 0;
let _publishAnimTimer = null;

async function initPublishPreview() {
  if (!currentSession || !sessionData) return;

  const img = document.getElementById('publishAnimImg');
  const spinner = document.getElementById('publishAnimSpinner');
  const dots = document.getElementById('publishAnimDots');
  const label = document.getElementById('publishAnimLabel');

  spinner.style.display = 'flex';
  _publishAnimFrames = [];
  stopPublishPreview();

  // Pick up to 3 selected frames
  const sel = [...selectedFrames].sort().slice(0, 3);
  if (sel.length === 0) { spinner.style.display = 'none'; return; }

  label.textContent = `${sel.length} klatek, loop`;

  try {
    const result = await api(`/sessions/${currentSession}/crop-preview`, {
      method: 'POST',
      body: {
        preset: activePreset,
        frame_indices: sel,
        focus_mode: currentFocusMode,
        zoom_level: currentZoomLevel,
        mouse_padding: currentMousePadding,
      },
    });

    _publishAnimFrames = result.previews.map(p =>
      `/api/sessions/${currentSession}/preview/${p.filename}`
    );
  } catch (e) {
    // fallback: use raw frame thumbnails
    sel.forEach(idx => {
      const f = sessionData.frames[idx];
      if (f) _publishAnimFrames.push(`/api/sessions/${currentSession}/frames/${f.filename}`);
    });
  }

  if (_publishAnimFrames.length === 0) { spinner.style.display = 'none'; return; }

  // Build dot indicators
  dots.innerHTML = _publishAnimFrames.map((_, i) =>
    `<div class="pub-anim-dot" id="pubDot${i}" style="width:8px;height:8px;border-radius:50%;background:var(--border);transition:background .2s"></div>`
  ).join('');

  // Show first frame, hide spinner
  img.src = _publishAnimFrames[0];
  img.onload = () => { spinner.style.display = 'none'; };
  _publishAnimIdx = 0;
  updatePublishAnimDot(0);
  startPublishPreview();
}

function startPublishPreview() {
  stopPublishPreview();
  const dur = parseFloat(document.getElementById('exportDuration')?.value || '0.5') * 1000;
  _publishAnimTimer = setInterval(() => {
    if (_publishAnimFrames.length === 0) return;
    _publishAnimIdx = (_publishAnimIdx + 1) % _publishAnimFrames.length;
    const img = document.getElementById('publishAnimImg');
    if (img) img.src = _publishAnimFrames[_publishAnimIdx];
    updatePublishAnimDot(_publishAnimIdx);
  }, Math.max(100, dur));
}

function stopPublishPreview() {
  if (_publishAnimTimer) { clearInterval(_publishAnimTimer); _publishAnimTimer = null; }
}

function restartPublishPreview() {
  stopPublishPreview();
  startPublishPreview();
}

function updatePublishAnimDot(activeIdx) {
  document.querySelectorAll('.pub-anim-dot').forEach((d, i) => {
    d.style.background = i === activeIdx ? 'var(--accent)' : 'var(--border)';
  });
}

async function exportAllFormats() {
  if (!currentSession) return;
  
  const btn = document.getElementById('exportBtn');
  const originalHtml = btn.innerHTML;
  btn.innerHTML = '<span class="spinner"></span> Generuję wszystkie formaty...';
  btn.disabled = true;
  
  const formats = ['video', 'gif', 'webm', 'zip'];
  const results = {};
  
  try {
    // Export all formats in parallel
    const exportPromises = formats.map(format => 
      api(`/sessions/${currentSession}/export`, {
        method: 'POST',
        body: {
          preset: activePreset,
          format: format,
          duration_per_frame: parseFloat(document.getElementById('exportDuration').value),
          fps: Math.round(1 / parseFloat(document.getElementById('exportDuration').value)),
          quality: parseInt(document.getElementById('exportQuality').value),
          frame_indices: [...selectedFrames].sort(),
          focus_mode: currentFocusMode,
          zoom_level: currentZoomLevel,
          mouse_padding: currentMousePadding,
        },
      }).then(result => ({ format, result }))
      .catch(error => ({ format, error }))
    );
    
    const exportResults = await Promise.all(exportPromises);
    
    // Show results
    document.getElementById('exportResults').style.display = 'block';
    const linksContainer = document.getElementById('exportLinks');
    linksContainer.innerHTML = '';
    
    exportResults.forEach(({ format, result, error }) => {
      if (error) {
        const errorDiv = document.createElement('div');
        errorDiv.className = 'export-error';
        errorDiv.textContent = `❌ ${format.toUpperCase()}: ${error.message}`;
        linksContainer.appendChild(errorDiv);
      } else {
        const previewDiv = document.createElement('div');
        previewDiv.className = 'export-preview';
        previewDiv.dataset.formatLink = format;
        
        // Create preview element based on format
        let previewElement = '';
        if (format === 'zip') {
          previewElement = `<div class="preview-icon">📁</div>`;
        } else {
          const isVideo = ['video', 'webm'].includes(format);
          const tag = isVideo ? 'video' : 'img';
          const extraAttrs = isVideo ? 'controls muted loop autoplay' : '';
          previewElement = `<${tag} src="${result.download_url}" ${extraAttrs} class="preview-media"></${tag}>`;
        }
        
        previewDiv.innerHTML = `
          <div class="preview-header">
            <span class="format-badge">${format.toUpperCase()}</span>
            <a href="${result.download_url}" download class="download-link">⬇ ${result.filename} (${result.size_mb} MB)</a>
          </div>
          <div class="preview-content">
            ${previewElement}
          </div>
        `;
        
        linksContainer.appendChild(previewDiv);
      }
    });
    
    toast('Wszystkie formaty wygenerowane!');
  } catch (e) {
    toast('Błąd eksportu: ' + e.message);
  } finally {
    btn.innerHTML = originalHtml;
    btn.disabled = false;
  }
}

function buildSocialLinks() {
  const container = document.getElementById('socialLinks');
  const links = {
    twitter: { label: 'Twitter / X', class: 'twitter' },
    linkedin: { label: 'LinkedIn', class: 'linkedin' },
    facebook: { label: 'Facebook', class: 'facebook' },
    reddit: { label: 'Reddit', class: 'reddit' },
    telegram: { label: 'Telegram', class: 'telegram' },
  };
  for (const [key, cfg] of Object.entries(links)) {
    const btn = document.createElement('button');
    btn.className = `social-btn ${cfg.class}`;
    btn.textContent = cfg.label;
    btn.onclick = () => {
      const text = encodeURIComponent(`Check out this demo!`);
      const url = encodeURIComponent(window.location.href);
      // Will be populated from social links API
      window.open(`https://${key}.com`, '_blank');
    };
    container.appendChild(btn);
  }
}

// ─── Upload ─────────────────────────────────────────────────────────────────
function setupDragDrop() {
  const zone = document.getElementById('uploadZone');
  zone.addEventListener('dragover', (e) => { e.preventDefault(); zone.classList.add('dragover'); });
  zone.addEventListener('dragleave', () => zone.classList.remove('dragover'));
  zone.addEventListener('drop', (e) => {
    e.preventDefault();
    zone.classList.remove('dragover');
    uploadFiles(e.dataTransfer.files);
  });
}

async function uploadFiles(files) {
  if (!files.length) return;
  const fd = new FormData();
  for (const f of files) fd.append('files', f);

  const res = await fetch('/api/sessions/upload', { method: 'POST', body: fd });
  const data = await res.json();

  toast(`Załadowano ${data.frame_count} obrazów`);
  await loadSessions();
  document.getElementById('sessionSelect').value = data.name;
  await loadSession(data.name);
}

// ─── Tab Switching ──────────────────────────────────────────────────────────
function switchTab(name) {
  document.querySelectorAll('.tab').forEach(t => t.classList.toggle('active', t.dataset.tab === name));
  document.querySelectorAll('.panel').forEach(p => p.classList.toggle('active', p.id === `panel-${name}`));

  // Lazy load tab content
  if (name === 'select') {
    renderFrameGrid();
    // Auto-detect duplicates when switching to select tab
    if (sessionData && sessionData.frame_count > 1) {
      detectDuplicates();
    }
  }
  if (name === 'annotate') renderAnnotatePanel();
  if (name === 'center') renderCenterPanel();
  if (name === 'crop') {
    loadCropPreview();
    // Generate video preview when switching to crop tab
    setTimeout(() => updateVideoPreview(), 100);
  }
  if (name === 'captions') {
    loadCaptionsPanel();
  }
  if (name === 'publish') {
    initPublishPreview();
  } else {
    stopPublishPreview();
  }
}

// Update tab click handlers to use navigation
function updateTabHandlers() {
  document.querySelectorAll('.tab').forEach(tab => {
    const tabName = tab.dataset.tab;
    tab.onclick = () => navigateToTab(tabName);
  });
}

// ─── Tab 5: Captions / Napisy ────────────────────────────────────────────────
let captions = [];           // array of caption objects
let activeCaptionId = null;  // currently selected caption id
let capDragActive = false;   // drag state for position
let capDragCapId = null;

function capUUID() {
  return 'cap_' + Math.random().toString(36).slice(2, 9);
}

async function loadCaptionsPanel() {
  if (!currentSession) return;
  try {
    const res = await api(`/sessions/${currentSession}/captions`);
    captions = res.captions || [];
  } catch(e) {
    captions = [];
  }
  renderCaptionsList();
  if (captions.length > 0) selectCaption(captions[0].id);
}

function renderCaptionsList() {
  const list = document.getElementById('captionsList');
  const count = document.getElementById('captionCount');
  if (!list) return;
  count.textContent = `(${captions.length})`;

  if (captions.length === 0) {
    list.innerHTML = '<div style="text-align:center;padding:40px;color:var(--text2);font-size:13px">Brak napisów — dodaj ręcznie lub wygeneruj AI</div>';
    return;
  }

  list.innerHTML = '';
  captions.forEach(cap => {
    const row = document.createElement('div');
    row.className = `caption-row${cap.id === activeCaptionId ? ' active' : ''}`;
    row.onclick = () => selectCaption(cap.id);

    const frames = sessionData?.frames || [];
    const thumbSrc = frames[cap.frame_start]
      ? `/api/sessions/${currentSession}/frames/${frames[cap.frame_start].filename}`
      : '';

    const rangeLabel = cap.frame_start === cap.frame_end
      ? `#${cap.frame_start + 1}`
      : `#${cap.frame_start + 1}–#${cap.frame_end + 1}`;

    row.innerHTML = `
      <img class="cap-thumb" src="${thumbSrc}" onerror="this.style.opacity=0.2">
      <span class="cap-text" title="${cap.text}">${cap.text || '(pusty)'}</span>
      <span class="cap-frames">${rangeLabel}</span>
      <button class="cap-del" title="Usuń" onclick="event.stopPropagation(); deleteCaption('${cap.id}')">✕</button>
    `;
    list.appendChild(row);
  });
}

function selectCaption(id) {
  activeCaptionId = id;
  renderCaptionsList();
  renderCaptionEditor();
}

function renderCaptionEditor() {
  const editor = document.getElementById('captionEditor');
  if (!editor) return;
  const cap = captions.find(c => c.id === activeCaptionId);
  if (!cap) {
    editor.innerHTML = '<div style="text-align:center;padding:20px;color:var(--text2);font-size:13px">Kliknij napis na liście aby edytować</div>';
    return;
  }

  const frames = sessionData?.frames || [];
  const thumbSrc = frames[cap.frame_start]
    ? `/api/sessions/${currentSession}/frames/${frames[cap.frame_start].filename}`
    : '';

  const maxFrame = Math.max(0, frames.length - 1);

  editor.innerHTML = `
    <!-- Preview with overlay -->
    <div class="cap-preview-wrap" id="capPreviewWrap">
      <img id="capPreviewImg" src="${thumbSrc}" alt="">
      <div class="cap-overlay-text" id="capOverlayText"
           style="top:${cap.y}%;left:${cap.x}%;
                  font-size:${cap.font_size * 0.3}px;
                  color:${cap.color};
                  background:${hexToRgba(cap.bg_color, cap.bg_opacity)};
                  font-weight:${cap.bold ? 'bold' : 'normal'};
                  font-style:${cap.italic ? 'italic' : 'normal'};
                  text-align:${cap.align}">
        ${cap.text || ''}
      </div>
    </div>
    <p style="font-size:10px;color:var(--text2);margin:0;text-align:center">⬆ Przeciągnij tekst aby zmienić pozycję</p>

    <!-- Text content -->
    <div class="cap-field">
      <label>Tekst napisu</label>
      <textarea id="capText" oninput="updateCaptionField('text', this.value)">${cap.text}</textarea>
    </div>

    <!-- Frame range -->
    <div class="cap-row-inline">
      <div class="cap-field">
        <label>Klatka od (#)</label>
        <input type="number" id="capFrameStart" min="1" max="${maxFrame + 1}" value="${cap.frame_start + 1}"
               oninput="updateCaptionField('frame_start', parseInt(this.value)-1); updatePreviewThumb()">
      </div>
      <div class="cap-field">
        <label>Klatka do (#)</label>
        <input type="number" id="capFrameEnd" min="1" max="${maxFrame + 1}" value="${cap.frame_end + 1}"
               oninput="updateCaptionField('frame_end', parseInt(this.value)-1)">
      </div>
    </div>

    <!-- Position -->
    <div class="cap-row-inline">
      <div class="cap-field">
        <label>Pozycja X (%)</label>
        <input type="range" min="0" max="100" value="${cap.x}" step="1"
               oninput="updateCaptionField('x', parseFloat(this.value)); updateOverlay()">
      </div>
      <div class="cap-field">
        <label>Pozycja Y (%)</label>
        <input type="range" min="0" max="100" value="${cap.y}" step="1"
               oninput="updateCaptionField('y', parseFloat(this.value)); updateOverlay()">
      </div>
    </div>

    <!-- Style -->
    <div class="cap-row-inline">
      <div class="cap-field">
        <label>Rozmiar czcionki</label>
        <input type="range" min="12" max="120" value="${cap.font_size}" step="2"
               oninput="updateCaptionField('font_size', parseInt(this.value)); updateOverlay(); this.nextElementSibling.textContent=this.value+'px'">
        <span style="font-size:11px;color:var(--text2)">${cap.font_size}px</span>
      </div>
      <div class="cap-field">
        <label>Wyrównanie</label>
        <select oninput="updateCaptionField('align', this.value); updateOverlay()">
          <option value="left" ${cap.align==='left'?'selected':''}>Lewo</option>
          <option value="center" ${cap.align==='center'?'selected':''}>Środek</option>
          <option value="right" ${cap.align==='right'?'selected':''}>Prawo</option>
        </select>
      </div>
    </div>

    <!-- Colors -->
    <div class="cap-row-inline">
      <div class="cap-field">
        <label>Kolor tekstu</label>
        <input type="color" value="${cap.color}"
               oninput="updateCaptionField('color', this.value); updateOverlay()"
               style="width:100%;height:36px;border-radius:6px;border:1px solid var(--border);cursor:pointer">
      </div>
      <div class="cap-field">
        <label>Kolor tła</label>
        <input type="color" value="${cap.bg_color}"
               oninput="updateCaptionField('bg_color', this.value); updateOverlay()"
               style="width:100%;height:36px;border-radius:6px;border:1px solid var(--border);cursor:pointer">
      </div>
      <div class="cap-field">
        <label>Krycie tła</label>
        <input type="range" min="0" max="1" step="0.05" value="${cap.bg_opacity}"
               oninput="updateCaptionField('bg_opacity', parseFloat(this.value)); updateOverlay(); this.nextElementSibling.textContent=Math.round(this.value*100)+'%'">
        <span style="font-size:11px;color:var(--text2)">${Math.round(cap.bg_opacity*100)}%</span>
      </div>
    </div>

    <!-- Bold/Italic -->
    <div class="cap-row-inline">
      <div class="cap-field">
        <label>Pogrubienie</label>
        <button class="btn ${cap.bold ? 'btn-primary' : 'btn-outline'}" id="capBoldBtn"
                onclick="updateCaptionField('bold', !${cap.bold}); renderCaptionEditor()">
          <b>B</b>
        </button>
      </div>
      <div class="cap-field">
        <label>Kursywa</label>
        <button class="btn ${cap.italic ? 'btn-primary' : 'btn-outline'}" id="capItalicBtn"
                onclick="updateCaptionField('italic', !${cap.italic}); renderCaptionEditor()">
          <i>I</i>
        </button>
      </div>
    </div>
  `;

  // Enable drag on overlay text
  setupCaptionDrag();
}

function hexToRgba(hex, opacity) {
  const r = parseInt(hex.slice(1,3), 16);
  const g = parseInt(hex.slice(3,5), 16);
  const b = parseInt(hex.slice(5,7), 16);
  return `rgba(${r},${g},${b},${opacity})`;
}

function updateCaptionField(field, value) {
  const cap = captions.find(c => c.id === activeCaptionId);
  if (!cap) return;
  cap[field] = value;
}

function updateOverlay() {
  const cap = captions.find(c => c.id === activeCaptionId);
  const overlay = document.getElementById('capOverlayText');
  if (!cap || !overlay) return;
  overlay.style.top = cap.y + '%';
  overlay.style.left = cap.x + '%';
  overlay.style.fontSize = (cap.font_size * 0.3) + 'px';
  overlay.style.color = cap.color;
  overlay.style.background = hexToRgba(cap.bg_color, cap.bg_opacity);
  overlay.style.fontWeight = cap.bold ? 'bold' : 'normal';
  overlay.style.fontStyle = cap.italic ? 'italic' : 'normal';
  overlay.style.textAlign = cap.align;
  overlay.textContent = cap.text;
}

function updatePreviewThumb() {
  const cap = captions.find(c => c.id === activeCaptionId);
  const img = document.getElementById('capPreviewImg');
  if (!cap || !img || !sessionData) return;
  const frames = sessionData.frames || [];
  if (frames[cap.frame_start]) {
    img.src = `/api/sessions/${currentSession}/frames/${frames[cap.frame_start].filename}`;
  }
}

function setupCaptionDrag() {
  const overlay = document.getElementById('capOverlayText');
  const wrap = document.getElementById('capPreviewWrap');
  if (!overlay || !wrap) return;

  overlay.style.cursor = 'move';
  overlay.style.userSelect = 'none';

  let dragging = false, startX, startY, startPctX, startPctY;

  overlay.addEventListener('mousedown', e => {
    e.preventDefault();
    dragging = true;
    startX = e.clientX;
    startY = e.clientY;
    const cap = captions.find(c => c.id === activeCaptionId);
    startPctX = cap.x;
    startPctY = cap.y;
  });

  window.addEventListener('mousemove', e => {
    if (!dragging) return;
    const rect = wrap.getBoundingClientRect();
    const dx = ((e.clientX - startX) / rect.width) * 100;
    const dy = ((e.clientY - startY) / rect.height) * 100;
    const cap = captions.find(c => c.id === activeCaptionId);
    if (!cap) return;
    cap.x = Math.max(0, Math.min(100, startPctX + dx));
    cap.y = Math.max(0, Math.min(100, startPctY + dy));
    updateOverlay();
  });

  window.addEventListener('mouseup', () => { dragging = false; });
}

function addCaption() {
  const frames = sessionData?.frames || [];
  const firstSelected = [...selectedFrames].sort()[0] ?? 0;
  const newCap = {
    id: capUUID(),
    frame_start: firstSelected,
    frame_end: firstSelected,
    text: 'Nowy napis',
    x: 50.0, y: 85.0,
    font_size: 32,
    color: '#ffffff',
    bg_color: '#000000',
    bg_opacity: 0.5,
    bold: false,
    italic: false,
    align: 'center',
  };
  captions.push(newCap);
  renderCaptionsList();
  selectCaption(newCap.id);
}

function deleteCaption(id) {
  captions = captions.filter(c => c.id !== id);
  if (activeCaptionId === id) {
    activeCaptionId = captions.length > 0 ? captions[0].id : null;
  }
  renderCaptionsList();
  if (activeCaptionId) renderCaptionEditor();
  else document.getElementById('captionEditor').innerHTML = '<div style="text-align:center;padding:20px;color:var(--text2)">Brak napisów</div>';
}

function autoCaptionsFromFrames() {
  const selected = [...selectedFrames].sort();
  if (selected.length === 0) { toast('Zaznacz klatki w zakładce Wybór klatek'); return; }
  captions = selected.map(idx => ({
    id: capUUID(),
    frame_start: idx,
    frame_end: idx,
    text: `Klatka #${idx + 1}`,
    x: 50.0, y: 85.0,
    font_size: 32,
    color: '#ffffff',
    bg_color: '#000000',
    bg_opacity: 0.5,
    bold: false,
    italic: false,
    align: 'center',
  }));
  renderCaptionsList();
  if (captions.length > 0) selectCaption(captions[0].id);
  toast(`Utworzono ${captions.length} napisów`);
}

async function saveCaptions() {
  if (!currentSession) return;
  try {
    await api(`/sessions/${currentSession}/captions`, {
      method: 'POST',
      body: { captions },
    });
    toast(`Zapisano ${captions.length} napisów`);
  } catch(e) {
    toast('Błąd zapisu: ' + e.message);
  }
}

function clearAllCaptions() {
  if (!confirm('Usunąć wszystkie napisy?')) return;
  captions = [];
  activeCaptionId = null;
  renderCaptionsList();
  document.getElementById('captionEditor').innerHTML = '<div style="text-align:center;padding:20px;color:var(--text2)">Brak napisów</div>';
}

function updateLlmModelPlaceholder() {
  const provider = document.getElementById('llmProvider').value;
  const modelInput = document.getElementById('llmModel');
  const defaults = {
    openai: 'gpt-4o-mini',
    anthropic: 'claude-3-haiku-20240307',
    ollama: 'llava',
    gemini: 'gemini-1.5-flash',
  };
  modelInput.value = defaults[provider] || 'gpt-4o-mini';
  modelInput.placeholder = defaults[provider] || '';
}

async function generateCaptionsLLM() {
  if (!currentSession) return;
  const apiKey = document.getElementById('llmApiKey').value.trim();
  const provider = document.getElementById('llmProvider').value;
  const model = document.getElementById('llmModel').value.trim();
  const language = document.getElementById('llmLanguage').value;
  const style = document.getElementById('llmStyle').value;

  // Pass API key as env hint for liteLLM
  if (apiKey) {
    const envVarMap = { openai: 'OPENAI_API_KEY', anthropic: 'ANTHROPIC_API_KEY', gemini: 'GEMINI_API_KEY', ollama: '' };
    const envVar = envVarMap[provider];
    if (envVar) {
      toast(`Uwaga: klucz API przesyłany przez UI — ustaw zmienną ${envVar} w środowisku dla bezpieczeństwa`);
    }
  }

  const btn = document.getElementById('llmGenerateBtn');
  const status = document.getElementById('llmStatus');
  btn.disabled = true;
  btn.textContent = '⏳ Generuję...';
  status.textContent = 'Wysyłam obrazy do modelu...';

  try {
    const frameIndices = [...selectedFrames].sort();
    const result = await fetch(`/api/sessions/${currentSession}/captions/generate`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        ...(apiKey ? { 'X-LLM-API-Key': apiKey } : {}),
      },
      body: JSON.stringify({
        frame_indices: frameIndices.length > 0 ? frameIndices : null,
        provider,
        model,
        language,
        style,
      }),
    });

    if (!result.ok) {
      const err = await result.json().catch(() => ({ detail: result.statusText }));
      throw new Error(err.detail || result.statusText);
    }

    const data = await result.json();
    captions = data.captions;
    renderCaptionsList();
    if (captions.length > 0) selectCaption(captions[0].id);
    status.textContent = `✅ Wygenerowano ${captions.length} napisów (${data.model})`;
    toast(`✅ ${captions.length} napisów przez AI`);
  } catch(e) {
    status.textContent = `❌ ${e.message}`;
    toast('Błąd LLM: ' + e.message);
  } finally {
    btn.disabled = false;
    btn.textContent = '🤖 Generuj napisy AI';
  }
}

// ─── Toast ──────────────────────────────────────────────────────────────────
function toast(msg) {
  const t = document.getElementById('toast');
  t.textContent = msg;
  t.classList.add('show');
  setTimeout(() => t.classList.remove('show'), 2500);
}
</script>
</body>
</html>
