<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>EmbyToolkit</title>
<link rel="stylesheet" href="/static/app-theme.css">
<style>
  :root {
    --bg: #0d0f12;
    --surface: #13171f;
    --surface2: #1a2030;
    --surface3: #222840;
    --border: #252d3d;
    --border-active: #14b8a6;
    --text: #e8ecf4;
    --text-2: #8892a8;
    --text-3: #4e5a70;
    --accent: #14b8a6;
    --accent-h: #2dd4bf;
    --accent-glow: rgba(20,184,166,0.12);
    --green: #34d399;
    --green-bg: rgba(52,211,153,0.08);
    --red: #f87171;
    --r: 10px;
    --r-lg: 14px;
  }

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

  body {
    font-family: 'Inter', system-ui, sans-serif;
    background: var(--bg);
    color: var(--text);
    display: grid;
    grid-template-columns: 220px 1fr;
    height: 100vh;
    height: 100svh;
    overflow: hidden;
  }

  /* ── App Nav Sidebar ── */
  .app-nav {
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border-right: 1px solid var(--border);
    overflow: hidden;
  }
  .app-nav-brand {
    padding: 18px 16px;
    display: flex;
    align-items: center;
    gap: 10px;
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
  }
  .app-nav-logo {
    width: 32px; height: 32px;
    background: var(--accent);
    border-radius: 8px;
    display: grid; place-items: center;
    color: #fff;
    flex-shrink: 0;
  }
  .app-nav-name {
    font-size: 15px;
    font-weight: 700;
    letter-spacing: -0.03em;
  }
  .app-nav-items {
    list-style: none;
    padding: 8px;
    flex: 1;
    overflow-y: auto;
    margin: 0;
  }
  .app-nav-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 12px;
    border-radius: 8px;
    color: var(--text-2);
    text-decoration: none;
    font-size: 13px;
    font-weight: 500;
    transition: background 0.12s, color 0.12s;
    margin-bottom: 1px;
  }
  .app-nav-item:hover { background: var(--surface2); color: var(--text); }
  .app-nav-item.active { background: var(--accent); color: #fff; }
  .app-nav-footer {
    padding: 14px 16px;
    border-top: 1px solid var(--border);
    display: flex;
    align-items: center;
    gap: 7px;
    font-size: 12px;
    color: var(--text-2);
    flex-shrink: 0;
  }
  .dot {
    width: 7px; height: 7px; border-radius: 50%;
    background: var(--green); box-shadow: 0 0 6px var(--green);
  }
  .dot.off { background: var(--red); box-shadow: 0 0 6px var(--red); }

  /* ── Body ── */
  .body {
    display: grid;
    grid-template-columns: 320px 1fr;
    min-height: 0;
    overflow: hidden;
  }

  /* ── Sidebar ── */
  .sidebar {
    border-right: 1px solid var(--border);
    background: var(--surface);
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
  }

  .search-wrap {
    padding: 14px;
    border-bottom: 1px solid var(--border);
    flex-shrink: 0;
  }
  .studio-bulk-banner {
    display: none;
    margin-top: 10px;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: var(--r);
    padding: 8px 10px;
  }
  .studio-bulk-banner.visible { display: flex; }
  .studio-bulk-label {
    font-size: 12px;
    color: var(--text-2);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .studio-bulk-btn {
    flex-shrink: 0;
    padding: 5px 12px;
    background: var(--accent);
    color: #fff;
    border: none;
    border-radius: 6px;
    font-size: 12px;
    font-weight: 600;
    font-family: inherit;
    cursor: pointer;
    transition: background 0.12s, opacity 0.12s;
  }
  .studio-bulk-btn:hover { background: var(--accent-h); }
  .studio-bulk-btn:disabled { opacity: 0.55; cursor: not-allowed; }
  .studio-reset-btn {
    flex-shrink: 0;
    padding: 5px 10px;
    background: transparent;
    color: var(--red);
    border: 1px solid var(--red);
    border-radius: 6px;
    font-size: 12px;
    font-weight: 600;
    font-family: inherit;
    cursor: pointer;
    transition: background 0.12s, opacity 0.12s;
  }
  .studio-reset-btn:hover { background: rgba(248,113,113,0.1); }
  .studio-reset-btn:disabled { opacity: 0.55; cursor: not-allowed; }
  .search-inner { position: relative; }
  .search-inner svg {
    position: absolute; left: 11px; top: 50%;
    transform: translateY(-50%);
    color: var(--text-3); pointer-events: none;
  }
  .search-input {
    width: 100%;
    padding: 9px 12px 9px 36px;
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: var(--r);
    color: var(--text);
    font-family: inherit; font-size: 13px;
    outline: none;
    transition: border-color 0.15s, box-shadow 0.15s;
  }
  .search-input::placeholder { color: var(--text-3); }
  .search-input:focus {
    border-color: var(--border-active);
    box-shadow: 0 0 0 3px var(--accent-glow);
  }

  .results {
    flex: 1; min-height: 0; overflow-y: auto; padding: 6px;
  }
  .results::-webkit-scrollbar { width: 4px; }
  .results::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
  .results-toolbar,
  .results-footer {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 14px;
    background: var(--surface);
    flex-shrink: 0;
  }
  .results-toolbar {
    min-height: 38px;
    border-bottom: 1px solid var(--border);
    justify-content: space-between;
  }
  .results-count,
  .results-page {
    font-size: 11px;
    color: var(--text-2);
    font-family: inherit;
    font-variant-numeric: tabular-nums;
  }
  .results-footer {
    justify-content: flex-end;
    border-top: 1px solid var(--border);
  }
  .pager-btn {
    min-width: 84px;
    padding: 7px 10px;
    border-radius: 8px;
    border: 1px solid var(--border);
    background: var(--surface2);
    color: var(--text);
    font-family: inherit;
    font-size: 12px;
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s, opacity 0.12s;
  }
  .pager-btn:hover:not(:disabled) { background: var(--surface3); }
  .pager-btn:disabled { opacity: 0.35; cursor: not-allowed; }

  .result-item {
    display: flex; align-items: center; gap: 11px;
    padding: 9px 10px;
    border-radius: 8px;
    cursor: pointer;
    border: 1px solid transparent;
    transition: background 0.12s, border-color 0.12s;
  }
  .result-item:hover { background: var(--surface2); }
  .result-item.active { background: var(--accent-glow); border-color: var(--accent); }

  .result-poster {
    width: 36px; height: 54px;
    border-radius: 5px; object-fit: cover;
    background: var(--surface2); flex-shrink: 0;
  }
  .result-info { flex: 1; min-width: 0; }
  .result-name {
    font-size: 13px; font-weight: 600;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .result-sub {
    font-size: 11px; color: var(--text-2); margin-top: 2px;
    display: flex; gap: 5px; flex-wrap: wrap;
  }
  .result-sub .tag {
    background: var(--surface3);
    padding: 1px 6px; border-radius: 3px;
    font-size: 10px; font-weight: 600; text-transform: uppercase;
    letter-spacing: 0.04em; color: var(--text-2);
  }
  .result-sub .badge {
    color: var(--green); font-size: 10px;
  }

  .empty {
    padding: 48px 20px; text-align: center;
    color: var(--text-3); font-size: 13px;
  }
  .empty svg { opacity: 0.2; margin-bottom: 12px; display: block; margin-left: auto; margin-right: auto; }

  /* ── Main ── */
  .main {
    display: flex; flex-direction: column; min-height: 0; overflow: auto;
  }

  .preview-area {
    flex: 1;
    display: flex; align-items: center; justify-content: center;
    min-height: 280px;
    padding: clamp(16px, 2.4vw, 28px);
    overflow: hidden;
    background: var(--bg);
  }

  .preview-stack {
    width: min(100%, 1180px);
    display: flex;
    align-items: flex-start;
    justify-content: center;
    gap: 18px;
    flex-wrap: wrap;
  }

  .preview-shell {
    width: min(100%, 820px);
  }

  .primary-preview-shell {
    width: min(100%, 290px);
    display: none;
    flex-direction: column;
    gap: 8px;
  }
  .primary-preview-shell.visible { display: flex; }
  .primary-preview-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    color: var(--text-2);
    font-size: 11px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
  }
  .primary-preview-note {
    font-family: inherit;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0;
    text-transform: none;
    font-size: 11px;
  }
  .primary-preview-tools {
    display: flex;
    align-items: center;
    gap: 10px;
  }
  .mini-link {
    border: 0;
    background: transparent;
    color: var(--accent-h);
    font: inherit;
    font-size: 11px;
    font-weight: 700;
    cursor: pointer;
    padding: 0;
  }
  .mini-link:hover { color: #8fa4ff; }
  .primary-preview-frame {
    position: relative;
    aspect-ratio: 2 / 3;
    border-radius: var(--r-lg);
    overflow: hidden;
    background: var(--surface2);
    box-shadow: 0 8px 40px rgba(0,0,0,0.45);
    cursor: grab;
    touch-action: none;
  }
  .primary-preview-frame.dragging { cursor: grabbing; }
  .primary-preview-frame img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
  }
  .primary-preview-zoom {
    position: absolute;
    top: 12px;
    right: 12px;
    display: flex;
    gap: 8px;
    z-index: 2;
  }
  .primary-preview-zoom-btn {
    width: 32px;
    height: 32px;
    border: 1px solid rgba(255,255,255,0.14);
    border-radius: 999px;
    background: rgba(0,0,0,0.58);
    color: rgba(255,255,255,0.92);
    font: inherit;
    font-size: 18px;
    line-height: 1;
    cursor: pointer;
    backdrop-filter: blur(8px);
  }
  .primary-preview-zoom-btn:hover {
    background: rgba(0,0,0,0.72);
  }
  .primary-preview-empty {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    color: var(--text-3);
    font-size: 12px;
    text-align: center;
    padding: 16px;
  }
  .primary-preview-hint {
    position: absolute;
    left: 50%;
    bottom: 12px;
    transform: translateX(-50%);
    padding: 7px 10px;
    border-radius: 999px;
    background: rgba(0,0,0,0.58);
    border: 1px solid rgba(255,255,255,0.1);
    color: rgba(255,255,255,0.88);
    font-size: 11px;
    font-weight: 600;
    pointer-events: none;
    backdrop-filter: blur(8px);
    white-space: nowrap;
  }

  .upload-bg-group {
    display: none;
    gap: 6px;
    align-items: center;
    margin-top: 5px;
    padding: 5px 8px;
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: 8px;
  }
  .upload-bg-group.visible { display: flex; }
  .upload-bg-name {
    font-size: 11px;
    color: var(--green);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    flex: 1;
    min-width: 0;
  }

  .preview-placeholder {
    aspect-ratio: 16/9;
    border-radius: var(--r-lg);
    border: 1px dashed var(--border);
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    gap: 12px; color: var(--text-3); font-size: 13px;
  }
  .preview-placeholder svg { opacity: 0.15; }

  .preview-frame {
    position: relative;
    aspect-ratio: 16/9;
    border-radius: var(--r-lg);
    overflow: hidden;
    background: var(--surface2);
    box-shadow: 0 8px 40px rgba(0,0,0,0.5);
    display: none;
    cursor: grab;
    touch-action: none;
  }
  .preview-frame.visible { display: block; }
  .preview-frame.dragging { cursor: grabbing; }
  .preview-frame img { width: 100%; height: 100%; object-fit: cover; display: block; }

  .thumb-preview-tools {
    position: absolute;
    top: 10px;
    left: 10px;
    display: flex;
    align-items: center;
    gap: 10px;
    z-index: 2;
    padding: 6px 9px;
    border-radius: 999px;
    background: rgba(0,0,0,0.58);
    border: 1px solid rgba(255,255,255,0.1);
    backdrop-filter: blur(8px);
  }
  .thumb-preview-meta {
    color: rgba(255,255,255,0.88);
    font-family: inherit;
    font-variant-numeric: tabular-nums;
    font-size: 11px;
    white-space: nowrap;
  }
  .thumb-preview-zoom {
    position: absolute;
    top: 10px;
    right: 10px;
    display: flex;
    gap: 8px;
    z-index: 2;
  }
  .thumb-preview-hint {
    position: absolute;
    left: 50%;
    bottom: 10px;
    transform: translateX(-50%);
    padding: 7px 10px;
    border-radius: 999px;
    background: rgba(0,0,0,0.58);
    border: 1px solid rgba(255,255,255,0.1);
    color: rgba(255,255,255,0.88);
    font-size: 11px;
    font-weight: 600;
    pointer-events: none;
    backdrop-filter: blur(8px);
    white-space: nowrap;
    z-index: 2;
  }
  .bd-counter + .thumb-preview-hint {
    bottom: 38px;
  }

  /* Backdrop navigation overlaid on the frame */
  .bd-nav {
    position: absolute; inset: 0;
    display: flex; align-items: center; justify-content: space-between;
    padding: 0 12px;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.2s;
  }
  .preview-frame:hover .bd-nav { opacity: 1; }
  .bd-nav.always { opacity: 1; pointer-events: auto; }

  .bd-btn {
    width: 36px; height: 36px;
    border-radius: 50%;
    background: rgba(0,0,0,0.55);
    border: 1px solid rgba(255,255,255,0.15);
    color: #fff; font-size: 16px;
    display: grid; place-items: center;
    cursor: pointer;
    pointer-events: auto;
    backdrop-filter: blur(4px);
    transition: background 0.15s;
    flex-shrink: 0;
  }
  .bd-btn:hover { background: rgba(0,0,0,0.8); }
  .bd-btn:disabled { opacity: 0.2; cursor: not-allowed; }

  .bd-counter {
    position: absolute; bottom: 10px; left: 50%;
    transform: translateX(-50%);
    background: rgba(0,0,0,0.55);
    backdrop-filter: blur(4px);
    border: 1px solid rgba(255,255,255,0.12);
    border-radius: 20px;
    padding: 3px 12px;
    font-size: 11px; color: rgba(255,255,255,0.8);
    pointer-events: none;
    white-space: nowrap;
  }

  .loading-cover {
    position: absolute; inset: 0;
    background: rgba(13,15,18,0.8);
    display: none; align-items: center; justify-content: center;
    z-index: 4;
  }
  .loading-cover.on { display: flex; }

  .spinner {
    width: 32px; height: 32px;
    border: 2.5px solid var(--border);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: spin 0.7s linear infinite;
  }
  @keyframes spin { to { transform: rotate(360deg); } }

  /* ── Controls ── */
  .controls {
    flex-shrink: 0;
    border-top: 1px solid var(--border);
    background: var(--surface);
    padding: 14px 20px;
    display: none;
    gap: 14px;
    flex-direction: column;
  }
  .controls.visible { display: flex; }

  .ctrl-row {
    display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap;
  }

  .ctrl-group { display: flex; flex-direction: column; gap: 5px; }
  .ctrl-group label {
    font-size: 10px; font-weight: 600;
    text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--text-2);
  }
  .ctrl-hint {
    font-size: 10px;
    color: var(--text-3);
    max-width: 180px;
    line-height: 1.35;
  }

  .ctrl-select {
    padding: 7px 10px;
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: 8px;
    color: var(--text);
    font-family: inherit; font-size: 12px;
    outline: none;
    transition: border-color 0.15s;
    cursor: pointer;
  }
  .ctrl-select:focus { border-color: var(--border-active); }
  .ctrl-select option { background: var(--surface); }

  /* Logo position visual picker */
  .pos-grid {
    display: grid;
    grid-template-columns: repeat(3, 30px);
    grid-template-rows: repeat(2, 24px);
    gap: 3px;
  }
  .pos-btn {
    border-radius: 5px;
    border: 1px solid var(--border);
    background: var(--surface2);
    cursor: pointer;
    display: grid; place-items: center;
    transition: background 0.12s, border-color 0.12s;
    padding: 0;
  }
  .pos-btn svg { width: 12px; height: 12px; opacity: 0.4; transition: opacity 0.12s; }
  .pos-btn:hover { background: var(--surface3); }
  .pos-btn.active {
    background: var(--accent-glow);
    border-color: var(--accent);
  }
  .pos-btn.active svg { opacity: 1; }

  /* Color swatch */
  .color-wrap { display: flex; gap: 6px; align-items: center; }
  .color-wrap input[type="color"] {
    width: 34px; height: 33px;
    border: 1px solid var(--border); border-radius: 7px;
    background: var(--surface2); cursor: pointer; padding: 2px;
  }
  .color-wrap input[type="text"] {
    width: 80px;
    padding: 7px 8px;
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: 7px;
    color: var(--text);
    font-family: inherit; font-size: 12px; font-variant-numeric: tabular-nums;
    outline: none;
  }
  .color-wrap input[type="text"]:focus { border-color: var(--border-active); }

  .slider-wrap {
    display: flex; align-items: center; gap: 8px;
  }
  .slider-step {
    width: 24px;
    height: 24px;
    border-radius: 7px;
    border: 1px solid var(--border);
    background: var(--surface2);
    color: var(--text);
    font-family: inherit;
    font-size: 15px;
    font-weight: 700;
    line-height: 1;
    display: grid;
    place-items: center;
    cursor: pointer;
    flex-shrink: 0;
    transition: background 0.12s, border-color 0.12s;
  }
  .slider-step:hover { background: var(--surface3); border-color: var(--border-active); }
  .slider-wrap input[type="range"] {
    width: 100px;
    accent-color: var(--accent);
    cursor: pointer;
  }
  .slider-val {
    font-size: 11px; color: var(--text-2);
    font-family: inherit;
    font-variant-numeric: tabular-nums;
    min-width: 28px; text-align: right;
  }
  .asset-picker {
    display: flex;
    align-items: center;
    gap: 6px;
    min-height: 33px;
  }
  .asset-btn {
    width: 28px;
    height: 28px;
    border-radius: 7px;
    border: 1px solid var(--border);
    background: var(--surface2);
    color: var(--text);
    cursor: pointer;
    display: grid;
    place-items: center;
    font-size: 14px;
    transition: background 0.12s, border-color 0.12s, opacity 0.12s;
  }
  .asset-btn:hover:not(:disabled) { background: var(--surface3); }
  .asset-btn:disabled { opacity: 0.35; cursor: not-allowed; }
  .asset-count {
    min-width: 40px;
    font-size: 11px;
    color: var(--text-2);
    font-family: inherit;
    font-variant-numeric: tabular-nums;
    text-align: center;
  }
  .logo-swatch {
    width: 88px;
    height: 30px;
    border-radius: 7px;
    border: 1px solid var(--border);
    background: var(--surface2);
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    padding: 4px 6px;
  }
  .logo-swatch img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    display: block;
  }
  .logo-swatch.empty {
    color: var(--text-3);
    font-size: 10px;
    letter-spacing: 0.04em;
  }

  .toggle-chip {
    min-height: 33px;
    padding: 0 10px;
    border-radius: 8px;
    border: 1px solid var(--border);
    background: var(--surface2);
    display: inline-flex;
    align-items: center;
    gap: 8px;
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
  }
  .toggle-chip input {
    appearance: none;
    width: 14px;
    height: 14px;
    border-radius: 4px;
    border: 1px solid var(--border);
    background: var(--bg);
    display: inline-block;
    position: relative;
    flex-shrink: 0;
  }
  .toggle-chip input:checked {
    background: #e50914;
    border-color: #e50914;
  }
  .toggle-chip input:checked::after {
    content: "";
    position: absolute;
    inset: 2px;
    background: #fff;
    clip-path: polygon(14% 52%, 0 68%, 42% 100%, 100% 18%, 84% 4%, 40% 68%);
  }
  .toggle-chip span {
    font-size: 12px;
    color: var(--text);
  }
  .tag-preview {
    display: inline-flex;
    align-items: center;
    padding: 4px 7px;
    border-radius: 4px;
    background: #e50914;
    color: #fff;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.06em;
  }
  .tag-preview-dark {
    background: #111111;
  }

  .ctrl-spacer { flex: 1; }

  .studio-grid {
    display: flex; flex-wrap: wrap; gap: 4px;
    max-width: 200px;
  }
  .studio-btn {
    padding: 4px 8px;
    border-radius: 5px;
    border: 1px solid var(--border);
    background: var(--surface2);
    color: var(--text-2);
    font-family: inherit; font-size: 11px; font-weight: 700;
    cursor: pointer; white-space: nowrap;
    transition: background 0.12s, border-color 0.12s;
    letter-spacing: 0.03em;
  }
  .studio-btn:hover { background: var(--surface3); }
  .studio-btn.active { background: var(--accent-glow); border-color: var(--accent); color: var(--text); }

  .corner-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 3px; width: 62px;
  }
  .corner-btn {
    height: 26px;
    border-radius: 5px;
    border: 1px solid var(--border);
    background: var(--surface2);
    color: var(--text-2); font-size: 13px;
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s;
    display: grid; place-items: center;
  }
  .corner-btn:hover { background: var(--surface3); }
  .corner-btn.active { background: var(--accent-glow); border-color: var(--accent); color: var(--text); }

  /* Buttons */
  .btn {
    padding: 8px 18px;
    border-radius: 8px; border: none;
    font-family: inherit; font-size: 13px; font-weight: 600;
    cursor: pointer; display: inline-flex; align-items: center; gap: 7px;
    transition: all 0.13s; white-space: nowrap;
  }
  .btn:disabled { opacity: 0.35; cursor: not-allowed; }
  .btn-ghost {
    background: transparent; color: var(--text-2);
    border: 1px solid var(--border);
  }
  .btn-ghost:hover:not(:disabled) { background: var(--surface2); color: var(--text); }
  .btn-primary { background: var(--accent); color: #fff; }
  .btn-primary:hover:not(:disabled) { background: var(--accent-h); }
  .btn-green { background: var(--green); color: #0d0f12; }
  .btn-green:hover:not(:disabled) { filter: brightness(1.08); }

  /* Toast */
  .toast {
    position: fixed; bottom: 20px; right: 20px;
    padding: 11px 18px; border-radius: var(--r);
    font-size: 13px; font-weight: 500; z-index: 200;
    transform: translateY(80px); opacity: 0;
    transition: all 0.25s cubic-bezier(0.4,0,0.2,1);
    pointer-events: none;
  }
  .toast.show { transform: translateY(0); opacity: 1; }
  .toast.ok { background: var(--green-bg); border: 1px solid var(--green); color: var(--green); }
  .toast.err { background: rgba(248,113,113,.1); border: 1px solid var(--red); color: var(--red); }

  @media (max-width: 1400px) {
    .body { grid-template-columns: 260px minmax(0, 1fr); }
    .preview-shell { width: min(100%, 760px); }
  }

  @media (max-width: 1200px) {
    .body { grid-template-columns: 230px minmax(0, 1fr); }
    .search-wrap,
    .results-toolbar,
    .results-footer,
    .controls { padding-left: 12px; padding-right: 12px; }
    .preview-area { padding: 16px; }
    .preview-shell { width: min(100%, 680px); }
    .primary-preview-shell { width: min(100%, 240px); }
    .slider-wrap input[type="range"] { width: 80px; }
    .studio-grid { max-width: 160px; }
    .result-item { gap: 9px; padding: 8px; }
    .result-name { font-size: 12px; }
  }

  @media (max-width: 980px) {
    .body {
      grid-template-columns: 1fr;
      grid-template-rows: minmax(220px, 32vh) minmax(0, 1fr);
      overflow: auto;
    }
    .sidebar {
      border-right: none;
      border-bottom: 1px solid var(--border);
    }
    .main { overflow: visible; }
  }

  @media (max-height: 860px) {
    .preview-area {
      align-items: flex-start;
      padding: 16px;
    }
    .preview-shell { width: min(100%, 680px); }
    .primary-preview-shell { width: min(100%, 220px); }
    .controls { padding: 12px 16px; }
  }

  @media (min-width: 981px) and (max-width: 1440px) and (max-height: 900px) {
    .main {
      justify-content: flex-start;
      overflow: auto;
    }
    .preview-area {
      flex: 0 1 auto;
      align-items: flex-start;
      padding: 12px 16px 8px;
    }
    .preview-shell { width: min(100%, 600px); }
    .primary-preview-shell { width: min(100%, 220px); }
    .controls {
      padding: 10px 14px 12px;
      gap: 10px;
    }
    .ctrl-row {
      gap: 10px;
      align-items: flex-start;
    }
  }
</style>
</head>
<body>

<nav class="app-nav">
  <div class="app-nav-brand">
    <div class="app-nav-logo">
      <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.29 7 12 12 20.71 7"/><line x1="12" y1="22" x2="12" y2="12"/></svg>
    </div>
    <span class="app-nav-name">EmbyToolkit</span>
  </div>
  <ul class="app-nav-items">
    <li><a class="app-nav-item active" href="/">
      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="m21 15-5-5L5 21"/></svg>
      <span>Generator</span>
    </a></li>
    <li><a class="app-nav-item" href="/collections">
      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
      <span>Collections</span>
    </a></li>
    <li><a class="app-nav-item" href="/airing">
      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="7" width="20" height="15" rx="2"/><polyline points="17 2 12 7 7 2"/></svg>
      <span>Airing</span>
    </a></li>
    <li><a class="app-nav-item" href="/bulk-assign">
      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"/></svg>
      <span>Bulk Assign</span>
    </a></li>
    <li><a class="app-nav-item" href="/favorites">
      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 17.27 18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>
      <span>User Favourites</span>
    </a></li>
  </ul>
  <div class="app-nav-foot">
    <div class="app-nav-status">
      <span class="dot" id="statusDot"></span>
      <span id="statusText">Checking…</span>
    </div>
    <div class="app-nav-version">EmbyToolkit · v1.0</div>
  </div>
</nav>

<div class="body">

  <!-- Sidebar -->
  <aside class="sidebar">
    <div class="search-wrap">
      <div class="search-inner">
        <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
        <input class="search-input" id="searchInput" type="text" placeholder="Search movies & shows…" autocomplete="off">
      </div>
      <div class="studio-bulk-banner" id="studioBulkBanner">
        <span class="studio-bulk-label" id="studioBulkLabel">0 shows</span>
        <div style="display:flex;gap:6px;flex-shrink:0">
          <button class="studio-reset-btn" id="studioResetBtn">Reset All</button>
          <button class="studio-bulk-btn" id="studioBulkBtn">Bulk Assign All</button>
        </div>
      </div>
    </div>
    <div class="results-toolbar" id="resultsToolbar" style="display:none">
      <div class="results-count" id="resultsCount">0 results</div>
      <div class="results-page" id="resultsPage">Page 1</div>
    </div>
    <div class="results" id="resultsList">
      <div class="empty">
        <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2"><rect x="2" y="2" width="20" height="20" rx="3"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="m21 15-5-5L5 21"/></svg>
        Search your Emby library
      </div>
    </div>
    <div class="results-footer" id="resultsFooter" style="display:none">
      <button class="pager-btn" id="btnPrevPage" disabled>Previous</button>
      <button class="pager-btn" id="btnNextPage" disabled>Next</button>
    </div>
  </aside>

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

    <div class="preview-area">
      <div class="preview-stack">
        <div class="preview-shell">

          <!-- Placeholder -->
          <div class="preview-placeholder" id="placeholder">
            <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="m9 9 6 6m0-6-6 6"/></svg>
            Select a title to get started
          </div>

          <!-- Preview frame -->
          <div class="preview-frame" id="previewFrame" title="Drag to position the thumb crop. Use the mouse wheel to zoom.">
            <img id="thumbImg" src="" alt="">
            <div class="thumb-preview-tools">
              <button class="mini-link" id="btnThumbReset" type="button">Reset</button>
              <span class="thumb-preview-meta" id="thumbPreviewMeta">100% · X 0 · Y 0</span>
            </div>
            <div class="thumb-preview-zoom">
              <button class="primary-preview-zoom-btn" id="btnThumbPreviewZoomOut" type="button" aria-label="Zoom out">-</button>
              <button class="primary-preview-zoom-btn" id="btnThumbPreviewZoomIn" type="button" aria-label="Zoom in">+</button>
            </div>
            <div class="bd-nav" id="bdNav">
              <button class="bd-btn" id="btnBdPrev" disabled>&#8592;</button>
              <button class="bd-btn" id="btnBdNext" disabled>&#8594;</button>
            </div>
            <div class="bd-counter" id="bdCounter" style="display:none"></div>
            <div class="thumb-preview-hint">Drag to position • Wheel to zoom</div>
            <div class="loading-cover" id="loadingCover"><div class="spinner"></div></div>
          </div>
        </div>

        <div class="primary-preview-shell" id="primaryPreviewShell">
          <div class="primary-preview-head">
            <span>Primary Poster</span>
            <div class="primary-preview-tools" id="primaryPreviewTools">
              <button class="mini-link" id="btnPrimaryReset" type="button">Reset</button>
              <span class="primary-preview-note" id="primaryPreviewMeta">100% · X 0 · Y -16</span>
            </div>
          </div>
          <div class="primary-preview-frame" id="primaryPreviewFrame" title="This applies the selected studio logo to the existing primary poster.">
            <img id="primaryPreviewImg" src="" alt="">
            <div class="primary-preview-zoom" id="primaryPreviewZoom">
              <button class="primary-preview-zoom-btn" id="btnPrimaryPreviewZoomOut" type="button" aria-label="Zoom out">-</button>
              <button class="primary-preview-zoom-btn" id="btnPrimaryPreviewZoomIn" type="button" aria-label="Zoom in">+</button>
            </div>
            <div class="primary-preview-empty" id="primaryPreviewEmpty">Enable this to stamp the selected studio logo onto the existing primary poster.</div>
            <div class="primary-preview-hint" id="primaryPreviewHint">No separate poster is generated.</div>
          </div>
        </div>

      </div>
    </div>

    <!-- Controls bar -->
    <div class="controls" id="controlsBar">
      <div class="ctrl-row">

        <!-- Logo position -->
        <div class="ctrl-group">
          <label>Logo position</label>
          <div class="pos-grid" id="posGrid">
            <!-- Row 1: left / center / right (vertically centred) -->
            <button class="pos-btn" data-pos="left" title="Left centre">
              <svg viewBox="0 0 12 12" fill="currentColor"><rect x="1" y="4" width="5" height="4" rx="1"/></svg>
            </button>
            <button class="pos-btn" data-pos="center" title="Centre">
              <svg viewBox="0 0 12 12" fill="currentColor"><rect x="3.5" y="4" width="5" height="4" rx="1"/></svg>
            </button>
            <button class="pos-btn" data-pos="right" title="Right centre" style="display:none">
              <!-- hidden - not implemented yet -->
            </button>
            <!-- Row 2: bottom-left / bottom-center / bottom-right -->
            <button class="pos-btn" data-pos="bottom-left" title="Bottom left">
              <svg viewBox="0 0 12 12" fill="currentColor"><rect x="1" y="7" width="5" height="4" rx="1"/></svg>
            </button>
            <button class="pos-btn active" data-pos="bottom-center" title="Bottom centre">
              <svg viewBox="0 0 12 12" fill="currentColor"><rect x="3.5" y="7" width="5" height="4" rx="1"/></svg>
            </button>
            <button class="pos-btn" data-pos="bottom-right" title="Bottom right">
              <svg viewBox="0 0 12 12" fill="currentColor"><rect x="6" y="7" width="5" height="4" rx="1"/></svg>
            </button>
          </div>
        </div>

        <div class="ctrl-group" id="logoAssetGroup" style="display:none">
          <label>Logo asset</label>
          <div class="asset-picker">
            <button class="asset-btn" id="btnLogoPrev" disabled>&#8592;</button>
            <div class="logo-swatch empty" id="logoAssetPreview">No logo</div>
            <button class="asset-btn" id="btnLogoNext" disabled>&#8594;</button>
            <span class="asset-count" id="logoAssetCount">0/0</span>
          </div>
        </div>

        <div class="ctrl-group" id="backdropAssetGroup" style="display:none">
          <label>Backdrop asset</label>
          <div class="asset-picker">
            <button class="asset-btn" id="btnBackdropPrev" disabled>&#8592;</button>
            <span class="asset-count" id="backdropAssetCount">0/0</span>
            <button class="asset-btn" id="btnBackdropNext" disabled>&#8594;</button>
          </div>
        </div>

        <!-- Background -->
        <div class="ctrl-group">
          <label>Background</label>
          <select class="ctrl-select" id="bgSelect">
            <option value="backdrop" selected>Backdrop</option>
            <option value="blur">Blurred poster</option>
            <option value="upload">↑ Upload photo…</option>
          </select>
          <input type="file" id="uploadBgInput" accept="image/*" style="display:none">
          <div class="upload-bg-group" id="uploadBgGroup">
            <span class="upload-bg-name" id="uploadBgName"></span>
            <button class="btn btn-ghost" id="btnUploadBg" type="button" style="font-size:10px;padding:3px 8px;margin-left:auto">Change</button>
          </div>
        </div>

        <!-- Logo size -->
        <div class="ctrl-group">
          <label>Logo size</label>
          <div class="slider-wrap">
            <input type="range" id="logoScaleSlider" min="40" max="220" value="130">
            <span class="slider-val" id="logoScaleVal">130%</span>
          </div>
        </div>

        <!-- Darkness -->
        <div class="ctrl-group">
          <label>Darkness</label>
          <div class="slider-wrap">
            <input type="range" id="darknessSlider" min="0" max="100" value="0">
            <span class="slider-val" id="darknessVal">0%</span>
          </div>
        </div>

        <!-- Studio logo -->
        <div class="ctrl-group">
          <label>Studio</label>
          <div class="studio-grid" id="studioGrid">
            <button class="studio-btn active" data-studio="none">None</button>
            <button class="studio-btn" data-studio="netflix" style="color:#e50914">NETFLIX</button>
            <button class="studio-btn" data-studio="hulu" style="color:#1ce783">hulu</button>
            <button class="studio-btn" data-studio="appletv">Apple TV+</button>
            <button class="studio-btn" data-studio="disney" style="color:#6fa7e8">DISNEY+</button>
            <button class="studio-btn" data-studio="hbo">HBO</button>
            <button class="studio-btn" data-studio="prime" style="color:#00a8e1">prime</button>
            <button class="studio-btn" data-studio="paramount" style="color:#6a90d4">PARAM+</button>
          </div>
        </div>

        <!-- Studio position -->
        <div class="ctrl-group" id="studioPosGroup" style="display:none">
          <label>Studio pos</label>
          <div class="corner-grid" id="studioCornerGrid">
            <button class="corner-btn active" data-pos="top-left" title="Top left">↖</button>
            <button class="corner-btn" data-pos="top-right" title="Top right">↗</button>
            <button class="corner-btn" data-pos="bottom-right" title="Bottom right">↘</button>
            <button class="corner-btn" data-pos="bottom-left" title="Bottom left">↙</button>
          </div>
        </div>

        <div class="ctrl-group" id="seriesTagGroup" style="display:none">
          <label>Series tag</label>
          <label class="toggle-chip" for="newEpisodesToggle">
            <input type="checkbox" id="newEpisodesToggle">
            <span>New episodes</span>
            <span class="tag-preview">New Season</span>
          </label>
          <label class="toggle-chip" for="seasonFinaleToggle">
            <input type="checkbox" id="seasonFinaleToggle">
            <span>Season finale</span>
            <span class="tag-preview tag-preview-dark">Season Finale</span>
          </label>
        </div>

        <div class="ctrl-group">
          <label>Primary poster</label>
          <label class="toggle-chip" for="matchPrimaryToggle">
            <input type="checkbox" id="matchPrimaryToggle">
            <span>Stamp studio logo onto existing poster</span>
          </label>
        </div>

        <!-- Thumb crop -->
        <div class="ctrl-group" id="thumbZoomGroup">
          <label>Thumb zoom</label>
          <div class="slider-wrap">
            <button class="slider-step" id="btnThumbZoomOut" type="button" aria-label="Zoom out">-</button>
            <input type="range" id="thumbZoomSlider" min="100" max="260" value="100">
            <button class="slider-step" id="btnThumbZoomIn" type="button" aria-label="Zoom in">+</button>
            <span class="slider-val" id="thumbZoomVal">100%</span>
          </div>
        </div>

        <div class="ctrl-group" id="thumbPanXGroup">
          <label>Thumb X</label>
          <div class="slider-wrap">
            <input type="range" id="thumbPanXSlider" min="-100" max="100" value="0">
            <span class="slider-val" id="thumbPanXVal">0</span>
          </div>
        </div>

        <div class="ctrl-group" id="thumbPanYGroup">
          <label>Thumb Y</label>
          <div class="slider-wrap">
            <input type="range" id="thumbPanYSlider" min="-100" max="100" value="0">
            <span class="slider-val" id="thumbPanYVal">0</span>
          </div>
        </div>

        <div class="ctrl-group" id="primaryZoomGroup" style="display:none">
          <label>Poster zoom</label>
          <div class="slider-wrap">
            <button class="slider-step" id="btnPrimaryZoomOut" type="button" aria-label="Zoom out">-</button>
            <input type="range" id="primaryZoomSlider" min="100" max="260" value="100">
            <button class="slider-step" id="btnPrimaryZoomIn" type="button" aria-label="Zoom in">+</button>
            <span class="slider-val" id="primaryZoomVal">100%</span>
          </div>
        </div>

        <div class="ctrl-group" id="primaryPanXGroup" style="display:none">
          <label>Poster X</label>
          <div class="slider-wrap">
            <input type="range" id="primaryPanXSlider" min="-100" max="100" value="0">
            <span class="slider-val" id="primaryPanXVal">0</span>
          </div>
        </div>

        <div class="ctrl-group" id="primaryPanYGroup" style="display:none">
          <label>Poster Y</label>
          <div class="slider-wrap">
            <input type="range" id="primaryPanYSlider" min="-100" max="100" value="-16">
            <span class="slider-val" id="primaryPanYVal">-16</span>
          </div>
        </div>

        <!-- Text colour -->
        <div class="ctrl-group">
          <label>Text colour</label>
          <div class="color-wrap">
            <input type="color" id="textColorPicker" value="#FFFFFF">
            <input type="text" id="textColorText" value="#FFFFFF" maxlength="7">
          </div>
        </div>

        <div class="ctrl-group">
          <label>Bulk category</label>
          <select class="ctrl-select" id="bulkCategorySelect">
            <option value="">Choose category…</option>
          </select>
          <div class="ctrl-hint">Uses the current settings. Only items that already have an Emby logo will be updated.</div>
        </div>

        <div class="ctrl-spacer"></div>

        <!-- Actions -->
        <button class="btn btn-ghost" id="btnRegenerate" disabled>
          <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>
          Regenerate
        </button>
        <button class="btn btn-primary" id="btnGenerate" disabled>Generate</button>
        <button class="btn btn-green" id="btnApply" disabled>Apply to Emby</button>
        <button class="btn btn-ghost" id="btnBulkApply" disabled>Bulk Apply Category</button>
      </div>
    </div>

  </main>
</div>

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

<script>
const state = {
  item: null,
  backdropIndex: 0,
  logoIndex: 0,
  imageInfo: { logos: [], backdrops: [], primaries: [] },
  generated: false,
  cacheKey: null,
  primaryGenerated: false,
  uploadBgId: null,
  searchQuery: '',
  searchStart: 0,
  searchLimit: 12,
  searchTotal: 0,
  searchController: null,
  studioKey: null,
  categoriesLoaded: false,
};

const $ = s => document.querySelector(s);
const searchInput  = $('#searchInput');
const resultsToolbar = $('#resultsToolbar');
const resultsCount = $('#resultsCount');
const resultsPage = $('#resultsPage');
const resultsList  = $('#resultsList');
const resultsFooter = $('#resultsFooter');
const btnPrevPage = $('#btnPrevPage');
const btnNextPage = $('#btnNextPage');
const studioBulkBanner = $('#studioBulkBanner');
const studioBulkLabel  = $('#studioBulkLabel');
const studioBulkBtn    = $('#studioBulkBtn');
const studioResetBtn   = $('#studioResetBtn');
const placeholder  = $('#placeholder');
const previewFrame = $('#previewFrame');
const thumbImg     = $('#thumbImg');
const primaryPreviewShell = $('#primaryPreviewShell');
const primaryPreviewFrame = $('#primaryPreviewFrame');
const primaryPreviewImg = $('#primaryPreviewImg');
const primaryPreviewEmpty = $('#primaryPreviewEmpty');
const primaryPreviewMeta = $('#primaryPreviewMeta');
const primaryPreviewTools = $('#primaryPreviewTools');
const primaryPreviewZoom = $('#primaryPreviewZoom');
const primaryPreviewHint = $('#primaryPreviewHint');
const btnPrimaryReset = $('#btnPrimaryReset');
const btnPrimaryPreviewZoomOut = $('#btnPrimaryPreviewZoomOut');
const btnPrimaryPreviewZoomIn = $('#btnPrimaryPreviewZoomIn');
const uploadBgGroup = $('#uploadBgGroup');
const uploadBgInput = $('#uploadBgInput');
const btnUploadBg = $('#btnUploadBg');
const uploadBgName = $('#uploadBgName');
const loadingCover = $('#loadingCover');
const bdNav        = $('#bdNav');
const bdCounter    = $('#bdCounter');
const logoAssetGroup = $('#logoAssetGroup');
const btnLogoPrev = $('#btnLogoPrev');
const btnLogoNext = $('#btnLogoNext');
const logoAssetPreview = $('#logoAssetPreview');
const logoAssetCount = $('#logoAssetCount');
const backdropAssetGroup = $('#backdropAssetGroup');
const btnBackdropPrev = $('#btnBackdropPrev');
const btnBackdropNext = $('#btnBackdropNext');
const backdropAssetCount = $('#backdropAssetCount');
const btnBdPrev    = $('#btnBdPrev');
const btnBdNext    = $('#btnBdNext');
const controlsBar  = $('#controlsBar');
const posGrid      = $('#posGrid');
const bgSelect     = $('#bgSelect');
const textColorPicker=$('#textColorPicker');
const textColorText= $('#textColorText');
const logoScaleSlider  = $('#logoScaleSlider');
const logoScaleVal     = $('#logoScaleVal');
const darknessSlider   = $('#darknessSlider');
const darknessVal      = $('#darknessVal');
const studioGrid       = $('#studioGrid');
const studioPosGroup   = $('#studioPosGroup');
const studioCornerGrid = $('#studioCornerGrid');
const seriesTagGroup   = $('#seriesTagGroup');
const newEpisodesToggle= $('#newEpisodesToggle');
const seasonFinaleToggle = $('#seasonFinaleToggle');
const matchPrimaryToggle = $('#matchPrimaryToggle');
const thumbZoomGroup = $('#thumbZoomGroup');
const thumbPanXGroup = $('#thumbPanXGroup');
const thumbPanYGroup = $('#thumbPanYGroup');
const btnThumbZoomOut = $('#btnThumbZoomOut');
const btnThumbZoomIn = $('#btnThumbZoomIn');
const btnThumbPreviewZoomOut = $('#btnThumbPreviewZoomOut');
const btnThumbPreviewZoomIn = $('#btnThumbPreviewZoomIn');
const btnThumbReset = $('#btnThumbReset');
const thumbPreviewMeta = $('#thumbPreviewMeta');
const thumbZoomSlider = $('#thumbZoomSlider');
const thumbZoomVal = $('#thumbZoomVal');
const thumbPanXSlider = $('#thumbPanXSlider');
const thumbPanXVal = $('#thumbPanXVal');
const thumbPanYSlider = $('#thumbPanYSlider');
const thumbPanYVal = $('#thumbPanYVal');
const primaryZoomGroup = $('#primaryZoomGroup');
const primaryPanXGroup = $('#primaryPanXGroup');
const primaryPanYGroup = $('#primaryPanYGroup');
const btnPrimaryZoomOut = $('#btnPrimaryZoomOut');
const btnPrimaryZoomIn = $('#btnPrimaryZoomIn');
const primaryZoomSlider = $('#primaryZoomSlider');
const primaryZoomVal = $('#primaryZoomVal');
const primaryPanXSlider = $('#primaryPanXSlider');
const primaryPanXVal = $('#primaryPanXVal');
const primaryPanYSlider = $('#primaryPanYSlider');
const primaryPanYVal = $('#primaryPanYVal');
const btnGenerate  = $('#btnGenerate');
const btnRegenerate= $('#btnRegenerate');
const btnApply     = $('#btnApply');
const bulkCategorySelect = $('#bulkCategorySelect');
const btnBulkApply = $('#btnBulkApply');
const toast        = $('#toast');

// ── Connection check ──
(async () => {
  try {
    const d = await (await fetch('/api/config')).json();
    if (d.connected) {
      $('#statusText').textContent = 'Connected to Emby';
    } else {
      $('#statusDot').classList.add('off');
      $('#statusText').textContent = 'No API key';
    }
  } catch {
    $('#statusDot').classList.add('off');
    $('#statusText').textContent = 'Connection error';
  }
})();

async function readErrorMessage(response) {
  try {
    const data = await response.json();
    if (typeof data?.detail === 'string' && data.detail.trim()) return data.detail;
    if (Array.isArray(data?.detail)) return data.detail.map(entry => entry.msg || entry.message || String(entry)).join(', ');
  } catch {}
  return `${response.status}`;
}

async function loadCategories() {
  if (state.categoriesLoaded) return;
  try {
    const response = await fetch('/api/categories');
    if (!response.ok) throw new Error(await readErrorMessage(response));
    const data = await response.json();
    const options = (data.items || []).map(item =>
      `<option value="${item.id}">${esc(item.name)}${item.item_count ? ` (${item.item_count})` : ''}</option>`
    ).join('');
    bulkCategorySelect.innerHTML = `<option value="">Choose category…</option>${options}`;
    state.categoriesLoaded = true;
  } catch (error) {
    bulkCategorySelect.innerHTML = '<option value="">Categories unavailable</option>';
    showToast(`Category load failed: ${error.message}`, 'err');
  }
}

function currentCategoryLabel() {
  return bulkCategorySelect.options[bulkCategorySelect.selectedIndex]?.text || '';
}

function updateBulkApplyState() {
  btnBulkApply.disabled = !state.item || !bulkCategorySelect.value;
}

// ── Search ──
let searchTimer;
searchInput.addEventListener('input', () => {
  clearTimeout(searchTimer);
  const q = searchInput.value.trim();
  state.searchQuery = q;
  state.searchStart = 0;
  if (state.searchController) state.searchController.abort();
  if (q.length < 2) {
    resultsToolbar.style.display = 'none';
    resultsFooter.style.display = 'none';
    resultsList.innerHTML = '<div class="empty"><p>Type at least 2 characters</p></div>';
    return;
  }
  searchTimer = setTimeout(() => doSearch(q, 0), 180);
});

function updateSearchPager(total, start, limit) {
  if (!state.searchQuery || total <= 0) {
    resultsToolbar.style.display = 'none';
    resultsFooter.style.display = 'none';
    return;
  }
  resultsToolbar.style.display = 'flex';
  resultsFooter.style.display = total > limit ? 'flex' : 'none';
  resultsCount.textContent = `${Math.min(start + 1, total)}-${Math.min(start + limit, total)} of ${total}`;
  resultsPage.textContent = `Page ${Math.floor(start / limit) + 1}`;
  btnPrevPage.disabled = start === 0;
  btnNextPage.disabled = start + limit >= total;
}

function currentLogoAsset() {
  if (state.logoIndex < 0) return null;
  return state.imageInfo.logos[state.logoIndex] ?? null;
}

function currentBackdropAsset() {
  return state.imageInfo.backdrops[state.backdropIndex] ?? null;
}

function updateAssetPickers() {
  const logos = state.imageInfo.logos ?? [];
  const backdrops = state.imageInfo.backdrops ?? [];
  const totalLogoChoices = logos.length + 1;
  const activeLogoChoice = state.logoIndex < 0 ? 1 : state.logoIndex + 2;

  logoAssetGroup.style.display = state.item ? '' : 'none';
  logoAssetCount.textContent = `${activeLogoChoice}/${totalLogoChoices}`;
  btnLogoPrev.disabled = totalLogoChoices <= 1 || state.logoIndex < 0;
  btnLogoNext.disabled = totalLogoChoices <= 1 || state.logoIndex >= logos.length - 1;
  if (state.logoIndex < 0 || !logos.length) {
    logoAssetPreview.classList.add('empty');
    logoAssetPreview.textContent = 'Text';
  } else {
    logoAssetPreview.classList.remove('empty');
    logoAssetPreview.innerHTML = `<img src="${logos[state.logoIndex].url}" alt="" loading="lazy" decoding="async">`;
  }

  backdropAssetGroup.style.display = backdrops.length > 1 ? '' : 'none';
  backdropAssetCount.textContent = backdrops.length ? `${state.backdropIndex + 1}/${backdrops.length}` : '0/0';
  btnBackdropPrev.disabled = backdrops.length <= 1 || state.backdropIndex === 0;
  btnBackdropNext.disabled = backdrops.length <= 1 || state.backdropIndex >= backdrops.length - 1;
}

function syncSeriesTagToggles(changed) {
  if (changed === 'new' && newEpisodesToggle.checked) seasonFinaleToggle.checked = false;
  if (changed === 'finale' && seasonFinaleToggle.checked) newEpisodesToggle.checked = false;
}

function getSeriesTagFlags() {
  const isSeries = state.item?.type === 'Series';
  const seasonFinale = isSeries && seasonFinaleToggle.checked;
  return {
    newEpisodes: isSeries && newEpisodesToggle.checked && !seasonFinale,
    seasonFinale,
  };
}

function updateThumbPreviewMeta() {
  thumbZoomVal.textContent = thumbZoomSlider.value + '%';
  thumbPanXVal.textContent = thumbPanXSlider.value;
  thumbPanYVal.textContent = thumbPanYSlider.value;
  thumbPreviewMeta.textContent = `${thumbZoomSlider.value}% · X ${thumbPanXSlider.value} · Y ${thumbPanYSlider.value}`;
}

function updatePrimaryPreviewMeta() {
  primaryZoomVal.textContent = primaryZoomSlider.value + '%';
  primaryPanXVal.textContent = primaryPanXSlider.value;
  primaryPanYVal.textContent = primaryPanYSlider.value;
  primaryPreviewMeta.textContent = `${primaryZoomSlider.value}% · X ${primaryPanXSlider.value} · Y ${primaryPanYSlider.value}`;
}

function setPrimaryPreviewVisible(visible) {
  primaryPreviewShell.classList.toggle('visible', visible);
  primaryZoomGroup.style.display = 'none';
  primaryPanXGroup.style.display = 'none';
  primaryPanYGroup.style.display = 'none';
  primaryPreviewTools.style.display = 'none';
  primaryPreviewZoom.style.display = 'none';
  primaryPreviewHint.style.display = visible ? '' : 'none';
}

function syncPrimaryPreviewState() {
  const show = Boolean(state.item && matchPrimaryToggle.checked);
  setPrimaryPreviewVisible(show);
  if (!show) {
    primaryPreviewImg.removeAttribute('src');
    primaryPreviewEmpty.textContent = 'Enable this to stamp the selected studio logo onto the existing primary poster.';
    primaryPreviewEmpty.style.display = '';
    return;
  }
  primaryPreviewImg.removeAttribute('src');
  primaryPreviewEmpty.textContent = selectedStudio === 'none'
    ? 'Select a studio to stamp its logo onto the existing primary poster. No separate poster is generated.'
    : 'Applying will stamp the selected studio logo onto the existing primary poster. No separate poster is generated.';
  primaryPreviewEmpty.style.display = '';
}

let primaryPreviewTimer;
let thumbPreviewTimer;
let generateController = null;
let generateRequestId = 0;
let primaryDragState = null;
let thumbDragState = null;

function clamp(n, min, max) {
  return Math.max(min, Math.min(max, n));
}

function setThumbCropValues({ zoom, panX, panY }, { regenerate = false } = {}) {
  if (zoom != null) thumbZoomSlider.value = String(clamp(Math.round(zoom), 100, 260));
  if (panX != null) thumbPanXSlider.value = String(clamp(Math.round(panX), -100, 100));
  if (panY != null) thumbPanYSlider.value = String(clamp(Math.round(panY), -100, 100));
  updateThumbPreviewMeta();
  if (regenerate) {
    state.generated = false;
    btnApply.disabled = true;
    btnRegenerate.disabled = true;
    scheduleThumbPreviewRegenerate();
  }
}

function setPrimaryCropValues({ zoom, panX, panY }, { regenerate = false } = {}) {
  if (zoom != null) primaryZoomSlider.value = String(clamp(Math.round(zoom), 100, 260));
  if (panX != null) primaryPanXSlider.value = String(clamp(Math.round(panX), -100, 100));
  if (panY != null) primaryPanYSlider.value = String(clamp(Math.round(panY), -100, 100));
  updatePrimaryPreviewMeta();
  if (regenerate) {
    state.generated = false;
    btnApply.disabled = true;
    btnRegenerate.disabled = true;
    schedulePrimaryPreviewRegenerate();
  }
}

function scheduleThumbPreviewRegenerate() {
  updateThumbPreviewMeta();
  if (!state.item) return;
  clearTimeout(thumbPreviewTimer);
  thumbPreviewTimer = setTimeout(() => generateThumb({ silent: true }), 180);
}

function schedulePrimaryPreviewRegenerate() {
  updatePrimaryPreviewMeta();
  if (!state.item || !matchPrimaryToggle.checked) return;
  clearTimeout(primaryPreviewTimer);
  primaryPreviewTimer = setTimeout(() => generateThumb({ silent: true }), 180);
}

function finishPrimaryDrag(pointerId = null) {
  if (!primaryDragState) return;
  if (pointerId != null && primaryDragState.pointerId !== pointerId) return;
  try {
    primaryPreviewFrame.releasePointerCapture(primaryDragState.pointerId);
  } catch {}
  primaryPreviewFrame.classList.remove('dragging');
  primaryDragState = null;
}

function finishThumbDrag(pointerId = null) {
  if (!thumbDragState) return;
  if (pointerId != null && thumbDragState.pointerId !== pointerId) return;
  try {
    previewFrame.releasePointerCapture(thumbDragState.pointerId);
  } catch {}
  previewFrame.classList.remove('dragging');
  thumbDragState = null;
}

function nudgeThumbZoom(delta) {
  if (!state.item) return;
  setThumbCropValues({
    zoom: Number(thumbZoomSlider.value) + delta,
  }, { regenerate: true });
}

function nudgePrimaryZoom(delta) {
  if (!state.item || !matchPrimaryToggle.checked) return;
  setPrimaryCropValues({
    zoom: Number(primaryZoomSlider.value) + delta,
  }, { regenerate: true });
}

async function doSearch(q, start = 0) {
  if (state.searchController) state.searchController.abort();
  const controller = new AbortController();
  state.searchController = controller;
  state.searchQuery = q;
  state.searchStart = start;
  resultsList.innerHTML = '<div class="empty"><div class="spinner" style="margin:0 auto 0"></div></div>';
  resultsToolbar.style.display = 'flex';
  resultsFooter.style.display = 'none';
  studioBulkBanner.classList.remove('visible');
  studioBulkBtn.disabled = false;
  studioBulkBtn.textContent = 'Bulk Assign All';
  studioResetBtn.disabled = false;
  studioResetBtn.textContent = 'Reset All';
  resultsCount.textContent = 'Searching...';
  resultsPage.textContent = '';
  try {
    const r = await fetch(
      `/api/search?q=${encodeURIComponent(q)}&start=${start}&limit=${state.searchLimit}`,
      { signal: controller.signal },
    );
    const data = await r.json();
    if (!r.ok) throw new Error(typeof data?.detail === 'string' ? data.detail : `${r.status}`);
    if (state.searchController !== controller) return;
    state.searchTotal = data.total;
    state.searchStart = data.start;
    state.studioKey = data.studio_key || null;
    if (state.studioKey && data.total > 0) {
      studioBulkLabel.textContent = `${data.total} show${data.total !== 1 ? 's' : ''} — apply studio thumb to all`;
      studioBulkBanner.classList.add('visible');
    } else {
      studioBulkBanner.classList.remove('visible');
    }
    if (!data.items.length) {
      resultsList.innerHTML = '<div class="empty">No results</div>';
      resultsCount.textContent = '0 results';
      resultsPage.textContent = '';
      resultsFooter.style.display = 'none';
      return;
    }
    resultsList.innerHTML = data.items.map(it => `
      <div class="result-item ${state.item?.id === it.id ? 'active' : ''}"
           data-item='${JSON.stringify(it).replace(/'/g,"&#39;")}'>
        <img class="result-poster" src="${it.poster_url}" alt="" loading="lazy" decoding="async" fetchpriority="low" width="36" height="54">
        <div class="result-info">
          <div class="result-name">${esc(it.name)}</div>
          <div class="result-sub">
            <span>${it.year || '—'}</span>
            <span class="tag">${it.type}</span>
            ${it.has_logo ? '<span class="badge">logo ✓</span>' : ''}
            ${it.backdrop_count > 0 ? `<span class="badge">${it.backdrop_count} backdrop${it.backdrop_count>1?'s':''}</span>` : ''}
          </div>
        </div>
      </div>`).join('');
    updateSearchPager(data.total, data.start, data.limit);
    resultsList.querySelectorAll('.result-item').forEach(el =>
      el.addEventListener('click', () => selectItem(JSON.parse(el.dataset.item))));
  } catch (e) {
    if (e.name === 'AbortError') return;
    resultsToolbar.style.display = 'none';
    resultsFooter.style.display = 'none';
    studioBulkBanner.classList.remove('visible');
    resultsList.innerHTML = `<div class="empty">Error: ${esc(e.message)}</div>`;
  } finally {
    if (state.searchController === controller) state.searchController = null;
  }
}

btnPrevPage.addEventListener('click', () => {
  if (!state.searchQuery || state.searchStart === 0) return;
  doSearch(state.searchQuery, Math.max(0, state.searchStart - state.searchLimit));
});

btnNextPage.addEventListener('click', () => {
  if (!state.searchQuery || state.searchStart + state.searchLimit >= state.searchTotal) return;
  doSearch(state.searchQuery, state.searchStart + state.searchLimit);
});

studioResetBtn.addEventListener('click', async () => {
  if (!state.studioKey) return;
  if (!confirm(`Reset all ${state.searchQuery} thumbs and posters? This deletes custom images and reverts to Emby originals.`)) return;
  studioResetBtn.disabled = true;
  studioBulkBtn.disabled = true;
  studioResetBtn.textContent = 'Resetting…';
  try {
    const r = await fetch('/api/bulk-reset/studio', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({ studio_key: state.studioKey }),
    });
    const result = await r.json();
    if (!r.ok) throw new Error(result?.detail || `${r.status}`);
    studioBulkLabel.textContent = `✓ Reset ${result.reset} shows${result.skipped ? `, ${result.skipped} skipped` : ''}`;
    studioResetBtn.textContent = 'Done';
  } catch (e) {
    studioBulkLabel.textContent = `Error: ${esc(e.message)}`;
    studioResetBtn.textContent = 'Reset All';
    studioResetBtn.disabled = false;
    studioBulkBtn.disabled = false;
  }
});

studioBulkBtn.addEventListener('click', async () => {
  if (!state.studioKey) return;
  studioBulkBtn.disabled = true;
  studioBulkBtn.textContent = 'Fetching…';
  try {
    const r = await fetch(`/api/search?q=${encodeURIComponent(state.searchQuery)}&type=Series,Movie&start=0&limit=500`);
    const data = await r.json();
    if (!r.ok) throw new Error(data?.detail || `${r.status}`);
    const ids = data.items.map(it => it.id);
    if (!ids.length) throw new Error('No items found.');
    studioBulkBtn.textContent = `Applying to ${ids.length}…`;
    const ar = await fetch('/api/bulk-assign/apply', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({ item_ids: ids }),
    });
    const result = await ar.json();
    if (!ar.ok) throw new Error(result?.detail || `${ar.status}`);
    const n = result.applied_count ?? (result.applied || []).length;
    const sk = result.skipped_missing_assets_count ?? (result.skipped_missing_assets || []).length;
    studioBulkLabel.textContent = `✓ Applied to ${n}${sk ? `, skipped ${sk}` : ''}`;
    studioBulkBtn.textContent = 'Done';
  } catch (e) {
    studioBulkLabel.textContent = `Error: ${esc(e.message)}`;
    studioBulkBtn.textContent = 'Bulk Assign All';
    studioBulkBtn.disabled = false;
  }
});

// ── Select item ──
async function selectItem(item) {
  // Don't re-select the same item
  if (state.item?.id === item.id) return;

  state.item = item;
  state.backdropIndex = 0;
  state.logoIndex = item.has_logo ? 0 : -1;
  state.imageInfo = { logos: [], backdrops: [], primaries: [] };
  state.generated = false;
  state.cacheKey = null;
  state.primaryGenerated = false;
  primaryZoomSlider.value = '100';
  primaryPanXSlider.value = '0';
  primaryPanYSlider.value = '-16';
  updateThumbPreviewMeta();
  updatePrimaryPreviewMeta();
  syncPrimaryPreviewState();

  resultsList.querySelectorAll('.result-item').forEach(el => el.classList.remove('active'));
  resultsList.querySelector(`[data-item*='"id":"${item.id}"']`)?.classList.add('active');

  placeholder.style.display = 'none';
  controlsBar.classList.add('visible');
  btnRegenerate.disabled = true;
  btnApply.disabled = true;
  updateBulkApplyState();
  bdCounter.style.display = 'none';
  btnBdPrev.disabled = true;
  btnBdNext.disabled = true;
  updateAssetPickers();
  seriesTagGroup.style.display = item.type === 'Series' ? '' : 'none';
  if (item.type !== 'Series') {
    newEpisodesToggle.checked = false;
    seasonFinaleToggle.checked = false;
  }
  setStudioSelection(item.auto_studio || 'none', 'top-left');

  // Clear stale image and start generating immediately
  thumbImg.src = '';
  previewFrame.classList.add('visible');
  generateThumb();

  // Fetch accurate backdrop count in parallel
  fetch(`/api/images/${item.id}`)
    .then(r => r.json())
    .then(info => {
      state.imageInfo = {
        logos: info.logos ?? [],
        backdrops: info.backdrops ?? [],
        primaries: info.primaries ?? [],
      };
      state.item.backdrop_count = info.backdrop_count;
      if (!state.imageInfo.logos.length) {
        state.logoIndex = -1;
      } else if (state.logoIndex > state.imageInfo.logos.length - 1) {
        state.logoIndex = 0;
      }
      state.backdropIndex = 0;
      updateAssetPickers();
      updateBdUI();
    }).catch(() => {});
}

function updateBdUI() {
  const n = state.imageInfo.backdrops.length || state.item?.backdrop_count || 0;
  if (n > 1) {
    bdCounter.textContent = `Backdrop ${state.backdropIndex + 1} / ${n}`;
    bdCounter.style.display = '';
    btnBdPrev.disabled = state.backdropIndex === 0;
    btnBdNext.disabled = state.backdropIndex >= n - 1;
  } else {
    bdCounter.style.display = 'none';
    btnBdPrev.disabled = true;
    btnBdNext.disabled = true;
  }
}

function stepBackdrop(delta) {
  const max = (state.imageInfo.backdrops.length || state.item?.backdrop_count || 0) - 1;
  if (max < 0) return;
  const nextIndex = Math.max(0, Math.min(max, state.backdropIndex + delta));
  if (nextIndex === state.backdropIndex) return;
  state.backdropIndex = nextIndex;
  updateAssetPickers();
  updateBdUI();
  generateThumb();
}

function stepLogo(delta) {
  const max = (state.imageInfo.logos.length || 0) - 1;
  const nextIndex = Math.max(-1, Math.min(max, state.logoIndex + delta));
  if (nextIndex === state.logoIndex) return;
  state.logoIndex = nextIndex;
  updateAssetPickers();
  generateThumb();
}

btnBdPrev.addEventListener('click', () => stepBackdrop(-1));
btnBdNext.addEventListener('click', () => stepBackdrop(1));
btnBackdropPrev.addEventListener('click', () => stepBackdrop(-1));
btnBackdropNext.addEventListener('click', () => stepBackdrop(1));
btnLogoPrev.addEventListener('click', () => stepLogo(-1));
btnLogoNext.addEventListener('click', () => stepLogo(1));

// ── Logo position picker ──
let logoAlign = 'bottom-center';
posGrid.querySelectorAll('.pos-btn[data-pos]').forEach(btn => {
  if (btn.style.display === 'none') return;
  btn.addEventListener('click', () => {
    posGrid.querySelectorAll('.pos-btn').forEach(b => b.classList.remove('active'));
    btn.classList.add('active');
    logoAlign = btn.dataset.pos;
  });
});

logoScaleSlider.addEventListener('input', () => logoScaleVal.textContent = logoScaleSlider.value + '%');
darknessSlider.addEventListener('input', () => darknessVal.textContent = darknessSlider.value + '%');

// Studio selector
let selectedStudio = 'none';
let studioPos = 'top-left';

function setStudioSelection(studio, position = 'top-left') {
  selectedStudio = studio;
  studioPos = studio === 'appletv' ? 'top-right' : position;
  studioGrid.querySelectorAll('.studio-btn').forEach(btn => {
    btn.classList.toggle('active', btn.dataset.studio === selectedStudio);
  });
  studioPosGroup.style.display = selectedStudio === 'none' ? 'none' : '';
  studioCornerGrid.querySelectorAll('.corner-btn').forEach(btn => {
    btn.classList.toggle('active', btn.dataset.pos === studioPos);
  });
  syncPrimaryPreviewState();
}

studioGrid.querySelectorAll('.studio-btn').forEach(btn => {
  btn.addEventListener('click', () => {
    setStudioSelection(btn.dataset.studio, studioPos);
  });
});

studioCornerGrid.querySelectorAll('.corner-btn').forEach(btn => {
  btn.addEventListener('click', () => {
    if (selectedStudio === 'appletv' && btn.dataset.pos !== 'top-right') return;
    studioCornerGrid.querySelectorAll('.corner-btn').forEach(b => b.classList.remove('active'));
    btn.classList.add('active');
    studioPos = btn.dataset.pos;
    syncPrimaryPreviewState();
  });
});
textColorPicker.addEventListener('input', () => textColorText.value = textColorPicker.value);
textColorText.addEventListener('input', () => { if (/^#[0-9a-f]{6}$/i.test(textColorText.value)) textColorPicker.value = textColorText.value; });
newEpisodesToggle.addEventListener('change', () => syncSeriesTagToggles('new'));
seasonFinaleToggle.addEventListener('change', () => syncSeriesTagToggles('finale'));
matchPrimaryToggle.addEventListener('change', () => {
  syncPrimaryPreviewState();
});
primaryZoomSlider.addEventListener('input', schedulePrimaryPreviewRegenerate);
primaryPanXSlider.addEventListener('input', schedulePrimaryPreviewRegenerate);
primaryPanYSlider.addEventListener('input', schedulePrimaryPreviewRegenerate);

// Thumb crop sliders
thumbZoomSlider.addEventListener('input', () => {
  setThumbCropValues({ zoom: Number(thumbZoomSlider.value) }, { regenerate: true });
});
thumbPanXSlider.addEventListener('input', () => {
  setThumbCropValues({ panX: Number(thumbPanXSlider.value) }, { regenerate: true });
});
thumbPanYSlider.addEventListener('input', () => {
  setThumbCropValues({ panY: Number(thumbPanYSlider.value) }, { regenerate: true });
});
btnThumbZoomOut.addEventListener('click', () => nudgeThumbZoom(-10));
btnThumbZoomIn.addEventListener('click', () => nudgeThumbZoom(10));
btnThumbPreviewZoomOut.addEventListener('click', () => nudgeThumbZoom(-10));
btnThumbPreviewZoomIn.addEventListener('click', () => nudgeThumbZoom(10));
btnThumbReset.addEventListener('click', () => {
  setThumbCropValues({ zoom: 100, panX: 0, panY: 0 }, { regenerate: true });
});
previewFrame.addEventListener('pointerdown', event => {
  if (!state.item || event.target.closest('button')) return;
  event.preventDefault();
  thumbDragState = {
    pointerId: event.pointerId,
    startX: event.clientX,
    startY: event.clientY,
    startPanX: Number(thumbPanXSlider.value),
    startPanY: Number(thumbPanYSlider.value),
  };
  previewFrame.classList.add('dragging');
  previewFrame.setPointerCapture(event.pointerId);
});
previewFrame.addEventListener('pointermove', event => {
  if (!thumbDragState || thumbDragState.pointerId !== event.pointerId) return;
  const rect = previewFrame.getBoundingClientRect();
  const deltaX = event.clientX - thumbDragState.startX;
  const deltaY = event.clientY - thumbDragState.startY;
  setThumbCropValues({
    panX: thumbDragState.startPanX - (deltaX / Math.max(1, rect.width)) * 200,
    panY: thumbDragState.startPanY - (deltaY / Math.max(1, rect.height)) * 200,
  }, { regenerate: true });
});
previewFrame.addEventListener('pointerup', event => finishThumbDrag(event.pointerId));
previewFrame.addEventListener('pointercancel', event => finishThumbDrag(event.pointerId));
previewFrame.addEventListener('lostpointercapture', () => finishThumbDrag());
previewFrame.addEventListener('wheel', event => {
  if (!state.item) return;
  event.preventDefault();
  const delta = event.deltaY < 0 ? 8 : -8;
  nudgeThumbZoom(delta);
}, { passive: false });

// ── Background select / upload ──
bgSelect.addEventListener('change', () => {
  if (bgSelect.value === 'upload') {
    // Immediately open file picker when this option is chosen
    uploadBgInput.click();
  } else {
    uploadBgGroup.classList.remove('visible');
    state.uploadBgId = null;
  }
});
btnUploadBg.addEventListener('click', () => uploadBgInput.click());
uploadBgInput.addEventListener('change', async () => {
  const file = uploadBgInput.files[0];
  if (!file) {
    // User cancelled — revert to backdrop if no file was previously uploaded
    if (!state.uploadBgId) bgSelect.value = 'backdrop';
    return;
  }
  uploadBgName.textContent = file.name;
  uploadBgGroup.classList.add('visible');
  btnUploadBg.disabled = true;
  try {
    const fd = new FormData();
    fd.append('file', file);
    const r = await fetch('/api/upload-background', { method: 'POST', body: fd });
    if (!r.ok) throw new Error(`${r.status}`);
    const data = await r.json();
    state.uploadBgId = data.upload_id;
    showToast(`Photo ready (${data.width}×${data.height})`, 'ok');
    if (state.item) generateThumb();
  } catch (e) {
    showToast(`Upload failed: ${e.message}`, 'err');
    state.uploadBgId = null;
    bgSelect.value = 'backdrop';
    uploadBgGroup.classList.remove('visible');
  } finally {
    btnUploadBg.disabled = false;
  }
});

btnPrimaryZoomOut.addEventListener('click', () => {
  nudgePrimaryZoom(-10);
});
btnPrimaryZoomIn.addEventListener('click', () => {
  nudgePrimaryZoom(10);
});
btnPrimaryPreviewZoomOut.addEventListener('click', () => nudgePrimaryZoom(-10));
btnPrimaryPreviewZoomIn.addEventListener('click', () => nudgePrimaryZoom(10));
btnPrimaryReset.addEventListener('click', () => {
  setPrimaryCropValues({ zoom: 100, panX: 0, panY: -16 }, { regenerate: true });
});
primaryPreviewFrame.addEventListener('pointerdown', event => {
  if (!state.item || !matchPrimaryToggle.checked || !state.primaryGenerated) return;
  event.preventDefault();
  primaryDragState = {
    pointerId: event.pointerId,
    startX: event.clientX,
    startY: event.clientY,
    startPanX: Number(primaryPanXSlider.value),
    startPanY: Number(primaryPanYSlider.value),
  };
  primaryPreviewFrame.classList.add('dragging');
  primaryPreviewFrame.setPointerCapture(event.pointerId);
});
primaryPreviewFrame.addEventListener('pointermove', event => {
  if (!primaryDragState || primaryDragState.pointerId !== event.pointerId) return;
  const rect = primaryPreviewFrame.getBoundingClientRect();
  const deltaX = event.clientX - primaryDragState.startX;
  const deltaY = event.clientY - primaryDragState.startY;
  setPrimaryCropValues({
    panX: primaryDragState.startPanX - (deltaX / Math.max(1, rect.width)) * 200,
    panY: primaryDragState.startPanY - (deltaY / Math.max(1, rect.height)) * 200,
  }, { regenerate: true });
});
primaryPreviewFrame.addEventListener('pointerup', event => finishPrimaryDrag(event.pointerId));
primaryPreviewFrame.addEventListener('pointercancel', event => finishPrimaryDrag(event.pointerId));
primaryPreviewFrame.addEventListener('lostpointercapture', () => finishPrimaryDrag());
primaryPreviewFrame.addEventListener('wheel', event => {
  if (!state.item || !matchPrimaryToggle.checked) return;
  event.preventDefault();
  const delta = event.deltaY < 0 ? 8 : -8;
  nudgePrimaryZoom(delta);
}, { passive: false });
updateThumbPreviewMeta();
updatePrimaryPreviewMeta();

// ── Generate ──
async function generateThumb({ silent = false } = {}) {
  if (!state.item) return;
  clearTimeout(primaryPreviewTimer);
  clearTimeout(thumbPreviewTimer);
  if (generateController) generateController.abort();
  const controller = new AbortController();
  generateController = controller;
  const requestId = ++generateRequestId;
  previewFrame.classList.add('visible');
  loadingCover.classList.add('on');
  btnGenerate.disabled = true;
  btnRegenerate.disabled = true;

  try {
    const seriesTagFlags = getSeriesTagFlags();
    const r = await fetch('/api/generate', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      signal: controller.signal,
      body: JSON.stringify({
        item_id: state.item.id,
        title: state.item.name,
        bg_mode: bgSelect.value,
        upload_bg_id: bgSelect.value === 'upload' ? state.uploadBgId : null,
        text_color: textColorText.value,
        logo_align: logoAlign,
        logo_index: currentLogoAsset()?.index ?? null,
        logo_scale: logoScaleSlider.value / 100,
        darkness: darknessSlider.value / 100,
        studio: selectedStudio,
        studio_position: studioPos,
        new_episodes_tag: seriesTagFlags.newEpisodes,
        season_finale_tag: seriesTagFlags.seasonFinale,
        generate_primary: matchPrimaryToggle.checked,
        primary_zoom: primaryZoomSlider.value / 100,
        primary_pan_x: primaryPanXSlider.value / 100,
        primary_pan_y: primaryPanYSlider.value / 100,
        thumb_zoom: thumbZoomSlider.value / 100,
        thumb_pan_x: thumbPanXSlider.value / 100,
        thumb_pan_y: thumbPanYSlider.value / 100,
        backdrop_index: currentBackdropAsset()?.index ?? state.backdropIndex,
      }),
    });
    if (!r.ok) throw new Error(await readErrorMessage(r));
    if (requestId !== generateRequestId) return;
    const primaryGenerated = r.headers.get('X-Primary-Generated') === '1';
    state.cacheKey = r.headers.get('X-Cache-Key');
    state.primaryGenerated = primaryGenerated;
    const blob = await r.blob();
    if (requestId !== generateRequestId) return;
    // Revoke old URL to avoid memory leak
    if (thumbImg.src.startsWith('blob:')) URL.revokeObjectURL(thumbImg.src);
    thumbImg.src = URL.createObjectURL(blob);
    syncPrimaryPreviewState();
    state.generated = true;
    btnApply.disabled = false;
    btnRegenerate.disabled = false;
    updateBdUI();
    if (!silent) {
      showToast('Generated thumb', 'ok');
    }
  } catch (e) {
    if (e.name === 'AbortError') return;
    if (requestId !== generateRequestId) return;
    state.cacheKey = null;
    state.primaryGenerated = false;
    syncPrimaryPreviewState();
    showToast(`Failed: ${e.message}`, 'err');
    btnRegenerate.disabled = !state.generated;
  } finally {
    if (generateController === controller) generateController = null;
    if (requestId === generateRequestId) {
      loadingCover.classList.remove('on');
      btnGenerate.disabled = false;
    }
  }
}

btnGenerate.addEventListener('click', generateThumb);
btnRegenerate.addEventListener('click', generateThumb);
bulkCategorySelect.addEventListener('change', updateBulkApplyState);

// ── Apply ──
btnApply.addEventListener('click', async () => {
  if (!state.generated) return;
  btnApply.disabled = true;
  try {
    const seriesTagFlags = getSeriesTagFlags();
    const r = await fetch('/api/apply', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        item_id: state.item.id,
        title: state.item.name,
        bg_mode: bgSelect.value,
        upload_bg_id: bgSelect.value === 'upload' ? state.uploadBgId : null,
        text_color: textColorText.value,
        logo_align: logoAlign,
        logo_index: currentLogoAsset()?.index ?? null,
        logo_scale: logoScaleSlider.value / 100,
        darkness: darknessSlider.value / 100,
        studio: selectedStudio,
        studio_position: studioPos,
        new_episodes_tag: seriesTagFlags.newEpisodes,
        season_finale_tag: seriesTagFlags.seasonFinale,
        generate_primary: matchPrimaryToggle.checked,
        primary_zoom: primaryZoomSlider.value / 100,
        primary_pan_x: primaryPanXSlider.value / 100,
        primary_pan_y: primaryPanYSlider.value / 100,
        thumb_zoom: thumbZoomSlider.value / 100,
        thumb_pan_x: thumbPanXSlider.value / 100,
        thumb_pan_y: thumbPanYSlider.value / 100,
        backdrop_index: currentBackdropAsset()?.index ?? state.backdropIndex,
      }),
    });
    if (!r.ok) throw new Error(await readErrorMessage(r));
    const data = await r.json();
    if (data.primary_attempted && data.primary_code) {
      showToast('Applied thumb + studio logo to primary', 'ok');
    } else if (data.primary_attempted && data.primary_error) {
      showToast('Applied thumb only', 'ok');
    } else {
      showToast('Applied to Emby!', 'ok');
    }
  } catch (e) {
    showToast(`Failed: ${e.message}`, 'err');
  } finally {
    btnApply.disabled = false;
  }
});

btnBulkApply.addEventListener('click', async () => {
  if (!state.item || !bulkCategorySelect.value) return;
  const categoryLabel = currentCategoryLabel();
  const confirmed = window.confirm(`Bulk apply the current settings to "${categoryLabel}"?\n\nOnly items with Emby logos will be updated.`);
  if (!confirmed) return;
  btnBulkApply.disabled = true;
  try {
    const seriesTagFlags = getSeriesTagFlags();
    const response = await fetch('/api/bulk-apply/category', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        category_id: bulkCategorySelect.value,
        category_name: categoryLabel,
        item_id: state.item.id,
        title: state.item.name,
        bg_mode: bgSelect.value,
        upload_bg_id: bgSelect.value === 'upload' ? state.uploadBgId : null,
        text_color: textColorText.value,
        logo_align: logoAlign,
        logo_index: currentLogoAsset()?.index ?? null,
        logo_scale: logoScaleSlider.value / 100,
        darkness: darknessSlider.value / 100,
        studio: selectedStudio,
        studio_position: studioPos,
        new_episodes_tag: seriesTagFlags.newEpisodes,
        season_finale_tag: seriesTagFlags.seasonFinale,
        generate_primary: matchPrimaryToggle.checked,
        primary_zoom: primaryZoomSlider.value / 100,
        primary_pan_x: primaryPanXSlider.value / 100,
        primary_pan_y: primaryPanYSlider.value / 100,
        thumb_zoom: thumbZoomSlider.value / 100,
        thumb_pan_x: thumbPanXSlider.value / 100,
        thumb_pan_y: thumbPanYSlider.value / 100,
        backdrop_index: currentBackdropAsset()?.index ?? state.backdropIndex,
      }),
    });
    if (!response.ok) throw new Error(await readErrorMessage(response));
    const data = await response.json();
    showToast(
      `Bulk complete: ${data.applied_count}/${data.eligible_count} applied, ${data.skipped_without_logo_count} skipped without logo, ${data.failed_count} failed`,
      data.failed_count ? 'err' : 'ok',
    );
  } catch (error) {
    showToast(`Bulk apply failed: ${error.message}`, 'err');
  } finally {
    updateBulkApplyState();
  }
});

// ── Utils ──
function esc(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
function showToast(msg, type) {
  toast.textContent = msg;
  toast.className = `toast ${type} show`;
  setTimeout(() => toast.classList.remove('show'), 2800);
}

loadCategories();
</script>
</body>
</html>
