Malits/Av1api
0
1<!DOCTYPE html>2<html lang="en">3<head>4<meta charset="UTF-8">5<meta name="viewport" content="width=device-width, initial-scale=1.0">6<title>AV1Encodes · API Tester</title>7<style>8 @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700&family=Syne:wght@400;700;800&display=swap');9 10 :root {11 --bg: #0a0a0d;12 --surface: #111118;13 --border: #1c1c2a;14 --accent: #6c63ff;15 --accent2: #ff7b54;16 --green: #3dffa0;17 --red: #ff4f6e;18 --yellow: #ffcb57;19 --cyan: #38d9f5;20 --text: #dde1f0;21 --muted: #52526e;22 --mono: 'JetBrains Mono', monospace;23 --sans: 'Syne', sans-serif;24 }25 26 *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }27 body {28 background: var(--bg);29 color: var(--text);30 font-family: var(--mono);31 font-size: 13px;32 min-height: 100vh;33 display: flex;34 flex-direction: column;35 }36 37 /* ── HEADER ─────────────────────────────────── */38 header {39 padding: 12px 28px;40 border-bottom: 1px solid var(--border);41 display: flex;42 align-items: center;43 gap: 14px;44 background: var(--surface);45 position: sticky;46 top: 0;47 z-index: 100;48 }49 .logo {50 font-family: var(--sans);51 font-size: 18px;52 font-weight: 800;53 color: #fff;54 display: flex;55 align-items: center;56 gap: 8px;57 letter-spacing: -0.5px;58 }59 .logo-icon {60 width: 28px; height: 28px;61 background: var(--accent);62 border-radius: 6px;63 display: flex; align-items: center; justify-content: center;64 font-size: 14px;65 }66 .logo span { color: var(--accent); }67 .base-url-pill {68 background: var(--bg);69 border: 1px solid var(--border);70 border-radius: 6px;71 padding: 4px 12px;72 font-size: 11px;73 color: var(--muted);74 display: flex;75 align-items: center;76 gap: 8px;77 }78 .base-url-pill input {79 background: transparent;80 border: none;81 color: var(--cyan);82 font-family: var(--mono);83 font-size: 11px;84 outline: none;85 width: 240px;86 }87 .status-dot {88 width: 7px; height: 7px;89 border-radius: 50%;90 background: var(--muted);91 transition: background .3s;92 }93 .status-dot.online { background: var(--green); animation: pulse 2s infinite; }94 .status-dot.offline { background: var(--red); }95 @keyframes pulse { 0%,100%{opacity:1} 50%{opacity:.3} }96 .kbd-hint {97 margin-left: auto;98 font-size: 10px;99 color: var(--muted);100 display: flex;101 align-items: center;102 gap: 5px;103 }104 .kbd {105 background: var(--border);106 border: 1px solid #2a2a40;107 border-radius: 3px;108 padding: 1px 5px;109 font-size: 9px;110 }111 112 /* ── LAYOUT ──────────────────────────────────── */113 .layout { display: flex; flex: 1; overflow: hidden; height: calc(100vh - 53px); }114 115 /* ── SIDEBAR ─────────────────────────────────── */116 aside {117 width: 270px;118 min-width: 270px;119 background: var(--surface);120 border-right: 1px solid var(--border);121 display: flex;122 flex-direction: column;123 overflow: hidden;124 }125 .sidebar-tabs {126 display: flex;127 border-bottom: 1px solid var(--border);128 flex-shrink: 0;129 }130 .stab {131 flex: 1;132 padding: 9px 4px;133 text-align: center;134 font-size: 10px;135 text-transform: uppercase;136 letter-spacing: 1px;137 color: var(--muted);138 cursor: pointer;139 transition: color .2s, background .2s;140 }141 .stab.active { color: var(--accent); background: #6c63ff10; border-bottom: 2px solid var(--accent); }142 .sidebar-panel { flex: 1; overflow-y: auto; padding: 6px 0; display: none; }143 .sidebar-panel.active { display: block; }144 145 /* ── ROUTE LIST ──────────────────────────────── */146 .group-label {147 font-size: 9px;148 text-transform: uppercase;149 letter-spacing: 2px;150 color: var(--muted);151 padding: 14px 16px 5px;152 font-weight: 700;153 }154 .route-item {155 display: flex;156 align-items: center;157 gap: 9px;158 padding: 7px 16px;159 cursor: pointer;160 border-left: 2px solid transparent;161 transition: background .12s;162 user-select: none;163 }164 .route-item:hover { background: #1e1e2e; }165 .route-item.active { background: #6c63ff0e; border-left-color: var(--accent); }166 .method-badge {167 font-size: 9px;168 font-weight: 700;169 padding: 2px 5px;170 border-radius: 3px;171 min-width: 42px;172 text-align: center;173 letter-spacing: .5px;174 }175 .m-GET { background: #3dffa018; color: var(--green); }176 .m-POST { background: #ff7b5418; color: var(--accent2); }177 .m-PATCH { background: #ffcb5718; color: var(--yellow); }178 .m-DELETE { background: #ff4f6e18; color: var(--red); }179 .route-path {180 font-size: 11px;181 color: var(--text);182 white-space: nowrap;183 overflow: hidden;184 text-overflow: ellipsis;185 flex: 1;186 }187 .divider { height: 1px; background: var(--border); margin: 4px 0; }188 189 /* ── HISTORY ─────────────────────────────────── */190 .hist-empty { padding: 28px 16px; font-size: 11px; color: var(--muted); text-align: center; }191 .hist-item {192 padding: 8px 14px;193 cursor: pointer;194 border-left: 2px solid transparent;195 transition: background .12s;196 }197 .hist-item:hover { background: #1e1e2e; }198 .hist-top { display: flex; align-items: center; gap: 6px; margin-bottom: 3px; }199 .hist-path { font-size: 10px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }200 .hist-time { font-size: 10px; color: var(--muted); margin-left: auto; white-space: nowrap; }201 .hist-actions { display: flex; gap: 8px; padding: 8px 14px 4px; }202 .hist-btn {203 flex: 1;204 background: transparent;205 border: 1px solid var(--border);206 color: var(--muted);207 font-family: var(--mono);208 font-size: 10px;209 padding: 4px 8px;210 border-radius: 4px;211 cursor: pointer;212 transition: color .2s, border-color .2s;213 }214 .hist-btn:hover { border-color: var(--accent); color: var(--accent); }215 .hist-btn.danger:hover { border-color: var(--red); color: var(--red); }216 217 /* ── MAIN ────────────────────────────────────── */218 main { flex: 1; display: flex; flex-direction: column; overflow: hidden; }219 220 /* ── REQUEST PANEL ───────────────────────────── */221 .request-panel {222 padding: 20px 28px 16px;223 border-bottom: 1px solid var(--border);224 background: var(--bg);225 flex-shrink: 0;226 max-height: 55%;227 overflow-y: auto;228 }229 .route-title {230 font-family: var(--sans);231 font-size: 16px;232 font-weight: 700;233 color: #fff;234 margin-bottom: 3px;235 }236 .route-desc { color: var(--muted); font-size: 11px; margin-bottom: 14px; }237 .url-row { display: flex; gap: 8px; align-items: center; margin-bottom: 14px; flex-wrap: wrap; }238 .method-pill {239 font-size: 11px; font-weight: 700;240 padding: 7px 13px; border-radius: 5px;241 min-width: 66px; text-align: center;242 flex-shrink: 0;243 }244 .url-display {245 flex: 1;246 background: var(--surface);247 border: 1px solid var(--border);248 color: var(--cyan);249 font-family: var(--mono);250 font-size: 12px;251 padding: 7px 12px;252 border-radius: 5px;253 word-break: break-all;254 min-height: 34px;255 line-height: 1.5;256 min-width: 0;257 }258 .send-btn {259 background: var(--accent);260 color: #fff;261 border: none;262 font-family: var(--mono);263 font-size: 12px;264 font-weight: 700;265 padding: 7px 22px;266 border-radius: 5px;267 cursor: pointer;268 transition: opacity .2s, transform .1s;269 white-space: nowrap;270 flex-shrink: 0;271 }272 .send-btn:hover { opacity: .85; }273 .send-btn:active { transform: scale(.97); }274 .send-btn:disabled { opacity: .4; cursor: not-allowed; }275 276 .section-title {277 font-size: 10px;278 text-transform: uppercase;279 letter-spacing: 1px;280 color: var(--muted);281 margin: 12px 0 7px;282 }283 .params-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }284 .param-group { display: flex; flex-direction: column; gap: 5px; }285 .param-group label {286 font-size: 10px;287 text-transform: uppercase;288 letter-spacing: 1px;289 color: var(--muted);290 display: flex;291 align-items: center;292 gap: 6px;293 }294 .param-group input, .param-group textarea, .param-group select {295 background: var(--surface);296 border: 1px solid var(--border);297 color: var(--text);298 font-family: var(--mono);299 font-size: 12px;300 padding: 7px 10px;301 border-radius: 5px;302 outline: none;303 transition: border-color .2s;304 resize: vertical;305 }306 .param-group select { appearance: none; cursor: pointer; }307 .param-group input:focus, .param-group textarea:focus, .param-group select:focus {308 border-color: var(--accent);309 }310 .param-tag-path { color: var(--accent2); }311 .param-tag-query { color: var(--cyan); }312 313 /* ── RESPONSE PANEL ──────────────────────────── */314 .response-panel {315 flex: 1;316 overflow: auto;317 padding: 20px 28px;318 display: flex;319 flex-direction: column;320 gap: 12px;321 min-height: 0;322 }323 .response-header { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }324 .response-label {325 font-size: 10px;326 text-transform: uppercase;327 letter-spacing: 1.5px;328 color: var(--muted);329 font-weight: 700;330 }331 .status-pill {332 font-size: 11px;333 font-weight: 700;334 padding: 2px 11px;335 border-radius: 20px;336 }337 .s2xx { background: #3dffa018; color: var(--green); }338 .s4xx, .s5xx { background: #ff4f6e18; color: var(--red); }339 .spnd { background: #ffcb5718; color: var(--yellow); }340 .time-pill { font-size: 11px; color: var(--muted); margin-left: auto; }341 pre.response-body {342 background: var(--surface);343 border: 1px solid var(--border);344 border-radius: 7px;345 padding: 16px;346 font-size: 12px;347 line-height: 1.7;348 overflow: auto;349 white-space: pre-wrap;350 word-break: break-word;351 flex: 1;352 min-height: 120px;353 }354 .resp-actions { display: flex; gap: 8px; }355 .action-btn {356 background: var(--border);357 border: none;358 color: var(--muted);359 font-family: var(--mono);360 font-size: 11px;361 padding: 4px 12px;362 border-radius: 4px;363 cursor: pointer;364 transition: color .2s;365 }366 .action-btn:hover { color: var(--text); }367 368 .empty-state {369 flex: 1;370 display: flex;371 align-items: center;372 justify-content: center;373 color: var(--muted);374 font-size: 12px;375 flex-direction: column;376 gap: 10px;377 }378 .empty-icon { font-size: 36px; opacity: .25; }379 380 /* ── JSON HIGHLIGHT ──────────────────────────── */381 .jk { color: #a89df7; } /* key */382 .js { color: var(--green); } /* string */383 .jn { color: var(--accent2); } /* number */384 .jb { color: var(--yellow); } /* bool */385 .jl { color: var(--red); } /* null */386 387 /* ── SCROLLBAR ───────────────────────────────── */388 ::-webkit-scrollbar { width: 5px; height: 5px; }389 ::-webkit-scrollbar-track { background: transparent; }390 ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }391 392 /* ── QUICK-COPY TOAST ────────────────────────── */393 #toast {394 position: fixed;395 bottom: 24px;396 right: 24px;397 background: var(--accent);398 color: #fff;399 font-family: var(--mono);400 font-size: 12px;401 padding: 8px 18px;402 border-radius: 6px;403 opacity: 0;404 pointer-events: none;405 transition: opacity .3s;406 z-index: 9999;407 }408 #toast.show { opacity: 1; }409 410 /* ── ADMIN INTERFACE ────────────────────────── */411 .admin-panel {412 display: none;413 flex-direction: column;414 gap: 16px;415 height: 100%;416 overflow-y: auto;417 padding: 20px;418 }419 .admin-panel.active {420 display: flex;421 }422 423 /* Lock Screen */424 .admin-lock-screen {425 flex: 1;426 display: flex;427 align-items: center;428 justify-content: center;429 background: radial-gradient(circle at center, #181824 0%, #0a0a0d 100%);430 }431 .lock-card {432 background: rgba(17, 17, 24, 0.7);433 backdrop-filter: blur(12px);434 -webkit-backdrop-filter: blur(12px);435 border: 1px solid var(--border);436 border-radius: 12px;437 padding: 32px;438 width: 100%;439 max-width: 400px;440 box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);441 text-align: center;442 display: flex;443 flex-direction: column;444 gap: 20px;445 transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);446 }447 .lock-card:hover {448 transform: translateY(-4px);449 border-color: rgba(108, 99, 255, 0.3);450 }451 .lock-icon {452 font-size: 40px;453 background: rgba(108, 99, 255, 0.1);454 color: var(--accent);455 width: 70px;456 height: 70px;457 border-radius: 50%;458 display: flex;459 align-items: center;460 justify-content: center;461 margin: 0 auto;462 border: 1px solid rgba(108, 99, 255, 0.2);463 }464 .lock-title {465 font-family: var(--sans);466 font-size: 20px;467 font-weight: 700;468 color: #fff;469 }470 .lock-desc {471 color: var(--muted);472 font-size: 11px;473 line-height: 1.5;474 }475 .lock-input-group {476 display: flex;477 flex-direction: column;478 gap: 8px;479 text-align: left;480 }481 .lock-input-group label {482 font-size: 10px;483 text-transform: uppercase;484 letter-spacing: 1px;485 color: var(--muted);486 }487 .lock-input {488 background: var(--bg);489 border: 1px solid var(--border);490 color: var(--text);491 font-family: var(--mono);492 font-size: 13px;493 padding: 10px 14px;494 border-radius: 6px;495 outline: none;496 transition: border-color 0.2s, box-shadow 0.2s;497 }498 .lock-input:focus {499 border-color: var(--accent);500 box-shadow: 0 0 0 2px rgba(108, 99, 255, 0.2);501 }502 .lock-btn {503 background: var(--accent);504 color: #fff;505 border: none;506 font-family: var(--mono);507 font-size: 12px;508 font-weight: 700;509 padding: 12px;510 border-radius: 6px;511 cursor: pointer;512 transition: opacity 0.2s, transform 0.1s;513 }514 .lock-btn:hover { opacity: 0.9; }515 .lock-btn:active { transform: scale(0.98); }516 517 /* Dashboard Stats Grid */518 .stats-grid {519 display: grid;520 grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));521 gap: 12px;522 }523 .stat-card {524 background: var(--surface);525 border: 1px solid var(--border);526 border-radius: 8px;527 padding: 16px;528 display: flex;529 flex-direction: column;530 gap: 6px;531 position: relative;532 overflow: hidden;533 }534 .stat-card::after {535 content: '';536 position: absolute;537 top: 0; left: 0; right: 0; height: 2px;538 background: transparent;539 }540 .stat-card.accent::after { background: var(--accent); }541 .stat-card.cyan::after { background: var(--cyan); }542 .stat-card.green::after { background: var(--green); }543 .stat-card.yellow::after { background: var(--yellow); }544 545 .stat-card.clickable {546 cursor: pointer;547 transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);548 }549 .stat-card.clickable:hover {550 background: rgba(255, 255, 255, 0.05);551 border-color: rgba(108, 99, 255, 0.35);552 transform: translateY(-2px);553 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);554 }555 .stat-card.clickable:active {556 transform: translateY(0);557 }558 559 .stat-label {560 font-size: 10px;561 color: var(--muted);562 text-transform: uppercase;563 letter-spacing: 1px;564 }565 .stat-value {566 font-family: var(--sans);567 font-size: 22px;568 font-weight: 700;569 color: #fff;570 }571 .stat-desc {572 font-size: 10px;573 color: var(--muted);574 }575 576 /* Admin Toolbar */577 .admin-toolbar {578 display: flex;579 justify-content: space-between;580 align-items: center;581 background: var(--surface);582 border: 1px solid var(--border);583 border-radius: 8px;584 padding: 12px 18px;585 gap: 16px;586 flex-wrap: wrap;587 }588 .toolbar-actions {589 display: flex;590 gap: 8px;591 flex-wrap: wrap;592 }593 .btn {594 background: var(--border);595 color: var(--text);596 border: 1px solid transparent;597 font-family: var(--mono);598 font-size: 11px;599 font-weight: 600;600 padding: 8px 14px;601 border-radius: 5px;602 cursor: pointer;603 display: inline-flex;604 align-items: center;605 gap: 6px;606 transition: background 0.2s, color 0.2s, border-color 0.2s;607 }608 .btn:hover { background: #232336; color: #fff; }609 .btn.primary { background: var(--accent); color: #fff; }610 .btn.primary:hover { opacity: 0.9; }611 .btn.danger { background: rgba(255, 79, 110, 0.1); color: var(--red); border-color: rgba(255, 79, 110, 0.2); }612 .btn.danger:hover { background: var(--red); color: #fff; }613 .btn.success { background: rgba(61, 255, 160, 0.1); color: var(--green); border-color: rgba(61, 255, 160, 0.2); }614 .btn.success:hover { background: var(--green); color: var(--bg); }615 616 /* Sync Info / Progress */617 .sync-status-bar {618 background: rgba(17, 17, 24, 0.5);619 border: 1px solid var(--border);620 border-radius: 6px;621 padding: 10px 14px;622 display: flex;623 flex-direction: column;624 gap: 6px;625 width: 100%;626 }627 .sync-status-header {628 display: flex;629 justify-content: space-between;630 font-size: 11px;631 }632 .progress-bg {633 background: var(--bg);634 border-radius: 4px;635 height: 6px;636 width: 100%;637 overflow: hidden;638 }639 .progress-bar {640 background: linear-gradient(90deg, var(--accent) 0%, var(--cyan) 100%);641 height: 100%;642 width: 0%;643 transition: width 0.3s ease;644 position: relative;645 }646 .progress-bar.animated::after {647 content: '';648 position: absolute;649 top: 0; left: 0; bottom: 0; right: 0;650 background: linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.2) 50%, rgba(255,255,255,0) 100%);651 animation: shine 1.5s infinite;652 }653 @keyframes shine {654 0% { transform: translateX(-100%); }655 100% { transform: translateX(100%); }656 }657 658 /* Search & Filter Controls */659 .admin-controls {660 display: flex;661 gap: 10px;662 align-items: center;663 flex-wrap: wrap;664 }665 .admin-search-input {666 flex: 1;667 min-width: 200px;668 background: var(--surface);669 border: 1px solid var(--border);670 color: var(--text);671 font-family: var(--mono);672 font-size: 12px;673 padding: 8px 12px;674 border-radius: 6px;675 outline: none;676 transition: border-color 0.2s;677 }678 .admin-search-input:focus { border-color: var(--accent); }679 .admin-select {680 background: var(--surface);681 border: 1px solid var(--border);682 color: var(--text);683 font-family: var(--mono);684 font-size: 12px;685 padding: 8px 12px;686 border-radius: 6px;687 outline: none;688 cursor: pointer;689 }690 691 /* Table Grid */692 .table-wrapper {693 background: var(--surface);694 border: 1px solid var(--border);695 border-radius: 8px;696 overflow: hidden;697 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);698 }699 .admin-table {700 width: 100%;701 border-collapse: collapse;702 text-align: left;703 font-size: 12px;704 }705 .admin-table th, .admin-table td {706 padding: 10px 14px;707 border-bottom: 1px solid var(--border);708 }709 .admin-table th {710 background: rgba(28, 28, 42, 0.4);711 color: var(--muted);712 font-weight: 600;713 text-transform: uppercase;714 font-size: 10px;715 letter-spacing: 1px;716 user-select: none;717 }718 .admin-table tbody tr {719 transition: background 0.15s;720 }721 .admin-table tbody tr:hover {722 background: rgba(108, 99, 255, 0.03);723 }724 .admin-table td img {725 border-radius: 4px;726 object-fit: cover;727 background: var(--bg);728 }729 730 .badge {731 display: inline-block;732 padding: 2px 6px;733 border-radius: 4px;734 font-size: 10px;735 font-weight: 600;736 }737 .badge.success { background: rgba(61, 255, 160, 0.1); color: var(--green); }738 .badge.warning { background: rgba(255, 203, 87, 0.1); color: var(--yellow); }739 .badge.danger { background: rgba(255, 79, 110, 0.1); color: var(--red); }740 .badge.info { background: rgba(56, 217, 245, 0.1); color: var(--cyan); }741 742 .table-actions {743 display: flex;744 gap: 6px;745 }746 .action-btn-sm {747 background: transparent;748 border: 1px solid var(--border);749 color: var(--muted);750 font-family: var(--mono);751 font-size: 10px;752 padding: 4px 8px;753 border-radius: 4px;754 cursor: pointer;755 transition: all 0.2s;756 }757 .action-btn-sm:hover { border-color: var(--accent); color: var(--accent); }758 .action-btn-sm.danger:hover { border-color: var(--red); color: var(--red); background: rgba(255, 79, 110, 0.05); }759 .action-btn-sm.success:hover { border-color: var(--green); color: var(--green); background: rgba(61, 255, 160, 0.05); }760 761 /* Pagination */762 .table-footer {763 display: flex;764 justify-content: space-between;765 align-items: center;766 padding: 10px 14px;767 background: rgba(28, 28, 42, 0.2);768 font-size: 11px;769 color: var(--muted);770 }771 .pagination-controls {772 display: flex;773 gap: 6px;774 }775 776 /* Bulk Downloader Styles */777 .bulk-filter-bar {778 background: rgba(28, 28, 42, 0.4);779 border: 1px solid var(--border);780 border-radius: 8px;781 padding: 12px 16px;782 display: flex;783 flex-wrap: wrap;784 gap: 16px;785 align-items: center;786 justify-content: space-between;787 }788 .bulk-table-container {789 max-height: 380px;790 overflow-y: auto;791 border: 1px solid var(--border);792 border-radius: 8px;793 background: var(--surface);794 }795 .bulk-action-bar {796 background: rgba(28, 28, 42, 0.4);797 border: 1px solid var(--border);798 border-radius: 8px;799 padding: 12px 16px;800 display: flex;801 align-items: center;802 justify-content: space-between;803 flex-wrap: wrap;804 gap: 12px;805 }806 .bulk-checkbox-cell {807 text-align: center;808 cursor: pointer;809 }810 .bulk-checkbox {811 width: 16px;812 height: 16px;813 cursor: pointer;814 accent-color: var(--accent);815 }816 .action-btn-sm.info {817 border-color: var(--cyan);818 color: var(--cyan);819 }820 .action-btn-sm.info:hover {821 background: rgba(0, 242, 254, 0.08);822 border-color: var(--cyan);823 color: var(--cyan);824 }825 .action-btn-sm.primary {826 border-color: var(--accent);827 color: var(--accent);828 }829 .action-btn-sm.primary:hover {830 background: rgba(255, 79, 110, 0.08);831 border-color: var(--accent);832 color: var(--accent);833 }834 835 /* Modals */836 .admin-modal-overlay {837 position: fixed;838 top: 0; left: 0; right: 0; bottom: 0;839 background: rgba(5, 5, 8, 0.85);840 backdrop-filter: blur(8px);841 -webkit-backdrop-filter: blur(8px);842 z-index: 1000;843 display: none;844 align-items: center;845 justify-content: center;846 padding: 20px;847 overflow-y: auto;848 }849 .admin-modal-overlay.active {850 display: flex;851 }852 .admin-modal-card {853 background: var(--surface);854 border: 1px solid var(--border);855 border-radius: 12px;856 width: 100%;857 max-width: 800px;858 box-shadow: 0 10px 40px rgba(0, 0, 0, 0.6);859 display: flex;860 flex-direction: column;861 max-height: 90vh;862 animation: modalSlideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1);863 }864 @keyframes modalSlideIn {865 from { transform: translateY(20px); opacity: 0; }866 to { transform: translateY(0); opacity: 1; }867 }868 .modal-header {869 padding: 16px 20px;870 border-bottom: 1px solid var(--border);871 display: flex;872 justify-content: space-between;873 align-items: center;874 }875 .modal-title {876 font-family: var(--sans);877 font-size: 16px;878 font-weight: 700;879 color: #fff;880 }881 .modal-close {882 background: transparent;883 border: none;884 color: var(--muted);885 font-size: 20px;886 cursor: pointer;887 transition: color 0.2s;888 }889 .modal-close:hover { color: #fff; }890 891 .modal-body {892 padding: 20px;893 overflow-y: auto;894 flex: 1;895 }896 .modal-footer {897 padding: 14px 20px;898 border-top: 1px solid var(--border);899 display: flex;900 justify-content: flex-end;901 gap: 10px;902 }903 904 /* Form Layout */905 .form-grid {906 display: grid;907 grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));908 gap: 16px;909 }910 .form-group-full {911 grid-column: 1 / -1;912 }913 914 /* Quick Form Tools */915 .enrich-box {916 background: rgba(108, 99, 255, 0.05);917 border: 1px dashed rgba(108, 99, 255, 0.3);918 border-radius: 6px;919 padding: 12px;920 margin-bottom: 16px;921 display: flex;922 align-items: center;923 justify-content: space-between;924 gap: 12px;925 }926 927 /* Modal Tabs */928 .modal-tabs {929 display: flex;930 border-bottom: 1px solid var(--border);931 flex-shrink: 0;932 }933 .mtab {934 padding: 12px 20px;935 font-size: 11px;936 font-weight: bold;937 cursor: pointer;938 color: var(--muted);939 transition: all 0.2s;940 border-bottom: 2px solid transparent;941 font-family: var(--sans);942 text-transform: uppercase;943 letter-spacing: 0.5px;944 }945 .mtab:hover { color: var(--text); }946 .mtab.active { color: var(--accent) !important; border-bottom-color: var(--accent) !important; background: rgba(108, 99, 255, 0.05); }947</style>948</head>949<body>950 951<header>952 <div class="logo">953 <div class="logo-icon">▶</div>954 AV1<span>Encodes</span>955 </div>956 <div class="base-url-pill">957 <div class="status-dot" id="statusDot"></div>958 <input id="baseUrlInput" value="http://localhost:8000" placeholder="http://localhost:8000" />959 </div>960 <span style="font-size:10px;color:var(--muted)">Python API Tester</span>961 <span class="kbd-hint">Send <span class="kbd">Ctrl</span><span class="kbd">↵</span></span>962</header>963 964<div class="layout">965 <aside>966 <div class="sidebar-tabs">967 <div class="stab active" onclick="switchTab('routes',this)">Routes</div>968 <div class="stab" onclick="switchTab('history',this)">History</div>969 <div class="stab" onclick="switchTab('admin',this)">Admin</div>970 </div>971 <div class="sidebar-panel active" id="tab-routes"></div>972 <div class="sidebar-panel" id="tab-history">973 <div id="histList"></div>974 </div>975 <div class="sidebar-panel" id="tab-admin">976 <div style="padding:14px 16px; font-size:11px; color:var(--muted); line-height: 1.5;">977 <strong>AV1 Headless CMS</strong><br>978 Add, edit, or delete anime and trigger fast background crawls directly from the dashboard.979 </div>980 <div class="divider"></div>981 <div id="adminSidebarContent" style="padding: 10px 16px; display: flex; flex-direction: column; gap: 8px;">982 <div style="font-size:10px; color:var(--muted)">Authenticate to view sidebar operations.</div>983 </div>984 </div>985 </aside>986 987 <main>988 <div class="request-panel" id="requestPanel">989 <div class="empty-state" style="padding:60px 0">990 <div class="empty-icon">⚡</div>991 <div>Pick a route from the sidebar to begin</div>992 </div>993 </div>994 <div class="response-panel" id="responsePanel">995 <div class="empty-state">996 <div class="empty-icon">◎</div>997 <div>Response will appear here</div>998 </div>999 </div>1000 <div class="admin-panel" id="adminPanel">1001 <!-- Loaded dynamically via javascript based on authentication state -->1002 </div>1003 </main>1004</div>1005 1006<div id="toast">Copied!</div>1007 1008<!-- ── ADMIN BULK DOWNLOAD MODAL ──────────────── -->1009<div class="admin-modal-overlay" id="adminBulkDownloadModal">1010 <div class="admin-modal-card" style="max-width: 850px; width: 95%;">1011 <div class="modal-header">1012 <div class="modal-title" id="bulkModalTitle">Bulk Episode Stream Links</div>1013 <button class="modal-close" onclick="closeBulkDownloadModal()">×</button>1014 </div>1015 1016 <div class="modal-body" style="display: flex; flex-direction: column; gap: 16px;">1017 <!-- Controls -->1018 <div class="bulk-filter-bar">1019 <div style="display: flex; align-items: center; gap: 12px; flex-wrap: wrap;">1020 <div style="display: flex; align-items: center; gap: 8px;">1021 <label style="font-size: 11px; font-weight: bold; color: var(--muted); margin: 0; white-space: nowrap;">Quality:</label>1022 <select id="bulkQualityFilter" class="admin-select" style="padding: 6px 12px; font-size: 11px; width: 130px; border-radius: 4px;">1023 <option value="1920 x 1080">1080p</option>1024 <option value="1280 x 720">720p</option>1025 <option value="854 x 480">480p</option>1026 </select>1027 </div>1028 <div style="display: flex; align-items: center; gap: 8px;">1029 <label style="font-size: 11px; font-weight: bold; color: var(--muted); margin: 0; white-space: nowrap;">Link Type:</label>1030 <select id="bulkLinkTypeFilter" class="admin-select" style="padding: 6px 12px; font-size: 11px; width: 130px; border-radius: 4px;" onchange="renderBulkTable()">1031 <option value="download">Download</option>1032 <option value="stream">Stream</option>1033 <option value="watch">Watch</option>1034 <option value="torrent">Torrent</option>1035 </select>1036 </div>1037 <button class="btn primary" type="button" onclick="bulkResolveLinks()" id="bulkResolveBtn" style="font-size: 11px; padding: 6px 14px;">Resolve Links 🔗</button>1038 </div>1039 1040 <div style="display: flex; gap: 8px;">1041 <button class="action-btn-sm success" onclick="toggleSelectAllBulk(true)">Select All</button>1042 <button class="action-btn-sm" onclick="toggleSelectAllBulk(false)">Deselect All</button>1043 </div>1044 </div>1045 1046 <!-- Progress bar (hidden by default) -->1047 <div id="bulkProgressContainer" style="display: none;">1048 <div style="font-size: 11px; color: var(--muted); margin-bottom: 4px;">Resolving video links: <span id="bulkProgressText">0 / 0</span></div>1049 <div style="width: 100%; height: 6px; background: var(--border); border-radius: 3px; overflow: hidden;">1050 <div id="bulkProgressBar" style="width: 0%; height: 100%; background: linear-gradient(90deg, var(--accent), var(--accent2)); border-radius: 3px; transition: width 0.3s;"></div>1051 </div>1052 </div>1053 1054 <!-- Table/Tree list of Episodes -->1055 <div class="bulk-table-container">1056 <table class="admin-table" style="font-size: 11px; margin: 0;">1057 <thead style="position: sticky; top: 0; background: var(--surface); z-index: 10; border-bottom: 2px solid var(--border);">1058 <tr>1059 <th style="width: 40px; text-align: center;">✓</th>1060 <th style="width: 70px;">Episode</th>1061 <th>File Name</th>1062 <th style="width: 80px;">Size</th>1063 <th style="width: 70px;">Type</th>1064 <th style="width: 100px; text-align: right;">Action</th>1065 </tr>1066 </thead>1067 <tbody id="bulkTableBody">1068 <tr>1069 <td colspan="6" style="text-align: center; color: var(--muted); padding: 30px;">Select quality and click <strong>Resolve Links 🔗</strong> to fetch episode links.</td>1070 </tr>1071 </tbody>1072 </table>1073 </div>1074 1075 <!-- Selection Summary and Download Actions -->1076 <div class="bulk-action-bar">1077 <div style="font-size: 12px; color: var(--foreground);">1078 Selected: <strong id="bulkSelectedCount" style="color: var(--accent);">0</strong> item(s)1079 </div>1080 <div style="display: flex; gap: 10px;">1081 <button class="btn success" type="button" onclick="bulkCopyAllLinks()">Copy Selected Links 📋</button>1082 <button class="btn primary" type="button" onclick="bulkDownloadAllSelected()">Download Selected ⬇</button>1083 </div>1084 </div>1085 1086 </div>1087 <div class="modal-footer">1088 <button class="btn" onclick="closeBulkDownloadModal()">Close</button>1089 </div>1090 </div>1091</div>1092 1093<!-- ── ADMIN MODAL ────────────────────────────── -->1094<div class="admin-modal-overlay" id="adminModal">1095 <div class="admin-modal-card">1096 <div class="modal-header">1097 <div class="modal-title" id="modalTitle">Add New Anime</div>1098 <button class="modal-close" onclick="closeModal()">×</button>1099 </div>1100 1101 <!-- Modal Tabs (only visible when editing) -->1102 <div class="modal-tabs" id="modalTabsContainer" style="display: none; border-bottom: 1px solid var(--border); background: rgba(28,28,42,0.2);">1103 <div class="mtab active" onclick="switchModalTab('metadata')">Anime Metadata</div>1104 <div class="mtab" onclick="switchModalTab('streams')">Episode Stream Mirrors 📺</div>1105 </div>1106 1107 <div class="modal-body">1108 <!-- AniList Live Explorer -->1109 <div class="enrich-box" id="modalEnrichBox" style="flex-direction: column; align-items: stretch; gap: 10px; border-style: solid; border-color: rgba(108, 99, 255, 0.2); background: rgba(17, 17, 24, 0.4); padding: 16px;">1110 <div style="display: flex; justify-content: space-between; align-items: center;">1111 <div style="font-size:11px; color:var(--text)">1112 <strong style="color: var(--accent);">AniList Live Explorer 🔍</strong><br>1113 <span style="color:var(--muted)">Search the global AniList database to automatically populate this form.</span>1114 </div>1115 </div>1116 <div style="display: flex; gap: 8px; margin-top: 4px;">1117 <input type="text" id="anilistSearchInput" placeholder="Type anime name (e.g. Frieren, Chainsaw Man)..." style="flex: 1; background: var(--bg); border: 1px solid var(--border); padding: 8px 12px; border-radius: 5px; color: #fff; font-family: var(--sans); font-size: 12px; outline: none; transition: border-color 0.2s;" onkeydown="if(event.key === 'Enter') { event.preventDefault(); searchAniListLive(); }" />1118 <button class="btn success" type="button" style="padding:0 16px; font-size:11px; font-weight:700; height: 33px;" onclick="searchAniListLive()">Search 🌐</button>1119 </div>1120 <!-- Search Results Grid -->1121 <div id="anilistSearchResults" style="display: none; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 12px; max-height: 240px; overflow-y: auto; margin-top: 10px; padding: 4px; border-top: 1px dashed var(--border); padding-top: 10px;"></div>1122 </div>1123 1124 <form id="animeForm" onsubmit="submitAnimeForm(event)">1125 <div class="form-grid">1126 <div class="param-group">1127 <label>Slug <span style="color:var(--red)">*</span></label>1128 <input type="text" id="form_slug" required placeholder="e.g. spy-x-family" />1129 </div>1130 <div class="param-group">1131 <label>Master Title <span style="color:var(--red)">*</span></label>1132 <input type="text" id="form_title" required placeholder="e.g. Spy x Family" />1133 </div>1134 <div class="param-group">1135 <label>Romaji Title</label>1136 <input type="text" id="form_romaji_title" placeholder="e.g. SPY×FAMILY" />1137 </div>1138 <div class="param-group">1139 <label>English Title</label>1140 <input type="text" id="form_english_title" placeholder="e.g. SPY x FAMILY" />1141 </div>1142 <div class="param-group">1143 <label>Native Title</label>1144 <input type="text" id="form_native_title" placeholder="e.g. SPY×FAMILY" />1145 </div>1146 <div class="param-group">1147 <label>Format</label>1148 <select id="form_format" class="admin-select">1149 <option value="">Select format...</option>1150 <option value="TV">TV</option>1151 <option value="MOVIE">MOVIE</option>1152 <option value="OVA">OVA</option>1153 <option value="ONA">ONA</option>1154 <option value="SPECIAL">SPECIAL</option>1155 </select>1156 </div>1157 <div class="param-group">1158 <label>Season</label>1159 <select id="form_season" class="admin-select">1160 <option value="">Select season...</option>1161 <option value="WINTER">WINTER</option>1162 <option value="SPRING">SPRING</option>1163 <option value="SUMMER">SUMMER</option>1164 <option value="FALL">FALL</option>1165 </select>1166 </div>1167 <div class="param-group">1168 <label>Year</label>1169 <input type="number" id="form_year" placeholder="e.g. 2022" />1170 </div>1171 <div class="param-group">1172 <label>Episodes Count</label>1173 <input type="number" id="form_episodes_count" placeholder="e.g. 12" />1174 </div>1175 <div class="param-group">1176 <label>Season Count</label>1177 <input type="number" id="form_season_count" value="1" placeholder="e.g. 1" />1178 </div>1179 <div class="param-group">1180 <label>Status</label>1181 <select id="form_status" class="admin-select">1182 <option value="">Select status...</option>1183 <option value="FINISHED">FINISHED</option>1184 <option value="RELEASING">RELEASING</option>1185 <option value="NOT_YET_RELEASED">NOT_YET_RELEASED</option>1186 <option value="CANCELLED">CANCELLED</option>1187 <option value="HIATUS">HIATUS</option>1188 </select>1189 </div>1190 <div class="param-group">1191 <label>Average Score (0-100)</label>1192 <input type="number" id="form_average_score" min="0" max="100" placeholder="e.g. 85" />1193 </div>1194 <div class="param-group">1195 <label>Popularity</label>1196 <input type="number" id="form_popularity" placeholder="e.g. 145000" />1197 </div>1198 <div class="param-group-full param-group">1199 <label>Genres (comma separated)</label>1200 <input type="text" id="form_genres" placeholder="e.g. Action, Comedy, Supernatural" />