| 1 | <div class="container ax-page">
|
|---|
| 2 |
|
|---|
| 3 | <p><a href="/admin" class="btn">← <%= t('nav.admin') %></a></p>
|
|---|
| 4 | <%- include('../partials/media-tabs', { active: 'audio', audioOn: true }) %>
|
|---|
| 5 | <header class="ax-header">
|
|---|
| 6 | <div>
|
|---|
| 7 | <h1><%= t('aaud.title') %></h1>
|
|---|
| 8 | <p class="ax-tagline"><%= t('aaud.tagline_pre') %> <code>[[track:<id>]]</code> <%= t('aaud.tagline_post') %></p>
|
|---|
| 9 | </div>
|
|---|
| 10 | </header>
|
|---|
| 11 |
|
|---|
| 12 | <% if (success) { %><div class="ax-flash ax-flash-ok"><%= success %></div><% } %>
|
|---|
| 13 | <% if (error) { %><div class="ax-flash ax-flash-err"><%= error %></div><% } %>
|
|---|
| 14 |
|
|---|
| 15 | <%# ── UPLOAD ──────────────────────────────────────────────── %>
|
|---|
| 16 | <section class="ax-card">
|
|---|
| 17 | <div class="ax-card-title"><%= t('aaud.upload') %></div>
|
|---|
| 18 |
|
|---|
| 19 | <%# Bulk drag-drop zone. Multiple files at once, transcoded sequentially.
|
|---|
| 20 | Title is auto-derived from each filename. Artist/album/cover are
|
|---|
| 21 | SHARED across the batch (set once, applied to every track) — same
|
|---|
| 22 | pattern as iTunes/Plex bulk-add. %>
|
|---|
| 23 | <div class="ax-form">
|
|---|
| 24 |
|
|---|
| 25 | <%# Shared metadata fields (apply to ALL files in the batch). %>
|
|---|
| 26 | <div class="ax-row ax-row-2">
|
|---|
| 27 | <label class="ax-field">
|
|---|
| 28 | <span><%= t('aaud.artist') %> <small><%= t('aaud.applied_all') %></small></span>
|
|---|
| 29 | <input type="text" id="batch-artist" placeholder="<%= t('aaud.optional') %>">
|
|---|
| 30 | </label>
|
|---|
| 31 | <label class="ax-field">
|
|---|
| 32 | <span><%= t('aaud.album') %> <small><%= t('aaud.applied_all') %></small></span>
|
|---|
| 33 | <input type="text" id="batch-album" placeholder="<%= t('aaud.optional') %>">
|
|---|
| 34 | </label>
|
|---|
| 35 | </div>
|
|---|
| 36 | <label class="ax-field ax-file">
|
|---|
| 37 | <span><%= t('aaud.cover') %> <small><%= t('aaud.cover_hint') %></small></span>
|
|---|
| 38 | <span class="ax-file-control">
|
|---|
| 39 | <span class="ax-btn ax-btn-secondary ax-file-btn">🖼 <%= t('aaud.choose_cover') %></span>
|
|---|
| 40 | <span class="ax-file-name" data-empty><%= t('aaud.no_file') %></span>
|
|---|
| 41 | <input type="file" id="batch-cover" accept="image/jpeg,image/png,image/webp,image/gif">
|
|---|
| 42 | </span>
|
|---|
| 43 | </label>
|
|---|
| 44 |
|
|---|
| 45 | <%# The drop-zone itself. Click anywhere to open file picker. %>
|
|---|
| 46 | <label class="ax-dropzone" id="audio-dropzone" tabindex="0">
|
|---|
| 47 | <span class="ax-dropzone-icon">🎵</span>
|
|---|
| 48 | <strong class="ax-dropzone-title"><%= t('aaud.drag_here') %></strong>
|
|---|
| 49 | <span class="ax-dropzone-sub"><%= t('aaud.or_click') %><br><small>mp3, m4a, ogg, opus, flac · max <%= maxBytesMb %> MB · wav max <%= maxWavMb %> MB</small></span>
|
|---|
| 50 | <input type="file" id="audio-files" accept="audio/*" multiple>
|
|---|
| 51 | </label>
|
|---|
| 52 |
|
|---|
| 53 | <%# Per-file progress list. Populated by JS as files are queued. %>
|
|---|
| 54 | <ul class="ax-upload-queue" id="upload-queue" hidden></ul>
|
|---|
| 55 |
|
|---|
| 56 | <div class="ax-form-actions" id="upload-actions" hidden>
|
|---|
| 57 | <button type="button" class="ax-btn ax-btn-primary" id="start-upload-btn">⬆ <%= t('aaud.start_upload') %></button>
|
|---|
| 58 | <button type="button" class="ax-btn" id="clear-queue-btn"><%= t('aaud.clear_list') %></button>
|
|---|
| 59 | </div>
|
|---|
| 60 | </div>
|
|---|
| 61 | </section>
|
|---|
| 62 |
|
|---|
| 63 | <%# ── TRACK LIST ─────────────────────────────────────────── %>
|
|---|
| 64 | <section class="ax-card">
|
|---|
| 65 | <div class="ax-card-title" style="display:flex;align-items:center;gap:.5rem">
|
|---|
| 66 | <%= t('aaud.tracks') %> <span class="ax-count"><%= tracks.length %></span>
|
|---|
| 67 | <button type="button" class="ax-btn ax-btn-secondary" id="add-link-track-btn"
|
|---|
| 68 | style="margin-left:auto;font-size:.85rem" title="<%= t('aaud.add_link_track_title') %>">
|
|---|
| 69 | + <%= t('aaud.add_link_track') %>
|
|---|
| 70 | </button>
|
|---|
| 71 | </div>
|
|---|
| 72 |
|
|---|
| 73 | <% if (!tracks.length) { %>
|
|---|
| 74 | <div class="ax-empty">
|
|---|
| 75 | <div class="ax-empty-icon">♫</div>
|
|---|
| 76 | <p><%= t('aaud.no_tracks') %></p>
|
|---|
| 77 | </div>
|
|---|
| 78 | <% } else { %>
|
|---|
| 79 | <%
|
|---|
| 80 | // Pre-fetch loop labels — `t` is shadowed by the loop var below.
|
|---|
| 81 | var _aaudUntitled = t('aaud.untitled');
|
|---|
| 82 | var _aaudPlay = t('aaud.play');
|
|---|
| 83 | var _aaudEdit = t('aaud.edit');
|
|---|
| 84 | var _aaudDlOn = t('aaud.dl_on');
|
|---|
| 85 | var _aaudDlOff = t('aaud.dl_off');
|
|---|
| 86 | var _aaudDelete = t('aaud.delete');
|
|---|
| 87 | var _aaudCopy = t('aaud.copy_click');
|
|---|
| 88 | var _aaudDelConfirm = t('aaud.delete_confirm');
|
|---|
| 89 | %>
|
|---|
| 90 | <ul class="ax-list">
|
|---|
| 91 | <% tracks.forEach(function(t) { %>
|
|---|
| 92 | <li class="ax-track" data-track-id="<%= t.id %>">
|
|---|
| 93 | <% if (t.cover_url) { %>
|
|---|
| 94 | <img class="ax-track-cover" src="<%= t.cover_url %>" alt="" data-cover-thumb>
|
|---|
| 95 | <% } else { %>
|
|---|
| 96 | <span class="ax-track-cover ax-track-cover-empty" data-cover-thumb>♫</span>
|
|---|
| 97 | <% } %>
|
|---|
| 98 |
|
|---|
| 99 | <div class="ax-track-meta">
|
|---|
| 100 | <div class="ax-track-title" data-cell="title"><%= t.title || _aaudUntitled %></div>
|
|---|
| 101 | <div class="ax-track-sub">
|
|---|
| 102 | <span data-cell="artist"><%= t.artist || '—' %></span>
|
|---|
| 103 | <% if (t.album) { %><span class="ax-track-sep">·</span><span data-cell="album"><%= t.album %></span><% } else { %><span data-cell="album" hidden></span><% } %>
|
|---|
| 104 | <span class="ax-track-sep">·</span><span><%= t.size ? Math.round(t.size/1024) + ' KB' : '—' %></span>
|
|---|
| 105 | </div>
|
|---|
| 106 | <code class="ax-embed" data-copy="[[track:<%= t.id %>]]" title="<%= _aaudCopy %>">[[track:<%= t.id %>]]</code>
|
|---|
| 107 | </div>
|
|---|
| 108 |
|
|---|
| 109 | <div class="ax-track-actions">
|
|---|
| 110 | <% if (t.stream_url) { %>
|
|---|
| 111 | <button type="button" class="ax-icon-btn ax-track-play"
|
|---|
| 112 | data-stream-url="<%= t.stream_url %>"
|
|---|
| 113 | data-track-id="<%= t.id %>"
|
|---|
| 114 | aria-label="<%= _aaudPlay %>" title="<%= _aaudPlay %>">
|
|---|
| 115 | <span class="ax-track-play-icon" aria-hidden="true">▶</span>
|
|---|
| 116 | </button>
|
|---|
| 117 | <% } %>
|
|---|
| 118 | <button type="button" class="ax-icon-btn" data-track-edit data-id="<%= t.id %>" aria-label="<%= _aaudEdit %>" title="<%= _aaudEdit %>">✎</button>
|
|---|
| 119 | <% if (typeof premiumUnlocked === 'undefined' || premiumUnlocked) { %>
|
|---|
| 120 | <button type="button" class="ax-icon-btn" data-track-dl data-id="<%= t.id %>" data-on="<%= t.downloadable ? '1' : '0' %>"
|
|---|
| 121 | aria-label="<%= t.downloadable ? _aaudDlOn : _aaudDlOff %>" title="<%= t.downloadable ? _aaudDlOn : _aaudDlOff %>"
|
|---|
| 122 | style="<%= t.downloadable ? 'color:var(--accent,#6b8f71)' : 'opacity:.5' %>">⬇</button>
|
|---|
| 123 | <% } %>
|
|---|
| 124 | <form action="/admin/audio/<%= t.id %>/delete" method="post" data-confirm="<%= _aaudDelConfirm %>" class="ax-track-delete">
|
|---|
| 125 | <button type="submit" class="ax-icon-btn ax-icon-btn-danger" aria-label="<%= _aaudDelete %>" title="<%= _aaudDelete %>">🗑</button>
|
|---|
| 126 | </form>
|
|---|
| 127 | </div>
|
|---|
| 128 | </li>
|
|---|
| 129 | <% }); %>
|
|---|
| 130 | </ul>
|
|---|
| 131 | <% } %>
|
|---|
| 132 | </section>
|
|---|
| 133 |
|
|---|
| 134 | <% if ((typeof premiumUnlocked === 'undefined' || premiumUnlocked) && typeof embedUrl !== 'undefined') { %>
|
|---|
| 135 | <section class="ax-section" style="margin-top:1.5rem">
|
|---|
| 136 | <h2 style="margin:0 0 .4rem">🔊 <%= t('aaud.embed_player') %></h2>
|
|---|
| 137 | <p style="opacity:.75;font-size:.9rem;margin:0 0 .6rem"><%= t('aaud.embed_hint') %></p>
|
|---|
| 138 | <textarea readonly rows="3" data-selectall style="width:100%;font-family:monospace;font-size:12px;padding:10px;border-radius:8px;border:1px solid var(--rule);background:var(--paper-2);color:inherit;"><iframe src="<%= embedUrl %>" width="100%" height="420" frameborder="0" loading="lazy" style="border-radius:12px"></iframe></textarea>
|
|---|
| 139 | <p style="margin:.6rem 0 0"><a class="btn" href="<%= embedUrl %>" target="_blank">▶ <%= t('aaud.preview_player') %></a></p>
|
|---|
| 140 | </section>
|
|---|
| 141 | <% } %>
|
|---|
| 142 |
|
|---|
| 143 | </div>
|
|---|
| 144 |
|
|---|
| 145 | <style>
|
|---|
| 146 | /* ─── Page wrapper ──────────────────────────────────────────────── */
|
|---|
| 147 | .ax-page {
|
|---|
| 148 | max-width: 880px;
|
|---|
| 149 | margin: 1.5rem auto 4rem;
|
|---|
| 150 | padding: 0 1rem;
|
|---|
| 151 | }
|
|---|
| 152 | .ax-header {
|
|---|
| 153 | display: flex; align-items: flex-start; gap: 0.75rem;
|
|---|
| 154 | margin-bottom: 1.5rem;
|
|---|
| 155 | }
|
|---|
| 156 | .ax-header h1 {
|
|---|
| 157 | font-family: var(--font-display, serif);
|
|---|
| 158 | font-size: 1.75rem;
|
|---|
| 159 | margin: 0 0 0.25rem;
|
|---|
| 160 | }
|
|---|
| 161 | .ax-tagline {
|
|---|
| 162 | color: var(--ink-muted, var(--ink-soft));
|
|---|
| 163 | margin: 0;
|
|---|
| 164 | font-size: 0.9rem;
|
|---|
| 165 | line-height: 1.5;
|
|---|
| 166 | }
|
|---|
| 167 | .ax-tagline code {
|
|---|
| 168 | background: var(--paper-2);
|
|---|
| 169 | padding: 0.1em 0.4em;
|
|---|
| 170 | border-radius: 4px;
|
|---|
| 171 | font-size: 0.9em;
|
|---|
| 172 | }
|
|---|
| 173 | .ax-back {
|
|---|
| 174 | display: inline-flex; align-items: center; justify-content: center;
|
|---|
| 175 | width: 40px; height: 40px;
|
|---|
| 176 | border: 1px solid var(--rule);
|
|---|
| 177 | border-radius: 8px;
|
|---|
| 178 | background: var(--paper);
|
|---|
| 179 | color: var(--ink);
|
|---|
| 180 | text-decoration: none;
|
|---|
| 181 | font-size: 1.1rem;
|
|---|
| 182 | flex-shrink: 0;
|
|---|
| 183 | transition: border-color 120ms;
|
|---|
| 184 | }
|
|---|
| 185 | .ax-back:hover { border-color: var(--accent); }
|
|---|
| 186 |
|
|---|
| 187 | /* ─── Flash messages ───────────────────────────────────────────── */
|
|---|
| 188 | .ax-flash {
|
|---|
| 189 | padding: 0.75rem 1rem;
|
|---|
| 190 | border-radius: 8px;
|
|---|
| 191 | margin-bottom: 1rem;
|
|---|
| 192 | font-size: 0.9rem;
|
|---|
| 193 | }
|
|---|
| 194 | .ax-flash-ok { background: rgba(40,160,90,.15); color: #2a9d5e; border: 1px solid rgba(40,160,90,.3); }
|
|---|
| 195 | .ax-flash-err { background: rgba(200,60,60,.15); color: #c33; border: 1px solid rgba(200,60,60,.3); }
|
|---|
| 196 |
|
|---|
| 197 | /* ─── Card sections ────────────────────────────────────────────── */
|
|---|
| 198 | .ax-card {
|
|---|
| 199 | background: var(--paper);
|
|---|
| 200 | border: 1px solid var(--rule);
|
|---|
| 201 | border-radius: 12px;
|
|---|
| 202 | padding: 1.25rem;
|
|---|
| 203 | margin-bottom: 1rem;
|
|---|
| 204 | }
|
|---|
| 205 | .ax-card-title {
|
|---|
| 206 | font-family: var(--font-display, serif);
|
|---|
| 207 | font-size: 1.15rem;
|
|---|
| 208 | font-weight: 600;
|
|---|
| 209 | margin-bottom: 1rem;
|
|---|
| 210 | display: flex; align-items: baseline; gap: 0.5rem;
|
|---|
| 211 | }
|
|---|
| 212 | .ax-count {
|
|---|
| 213 | font-family: var(--font-ui, system-ui);
|
|---|
| 214 | font-size: 0.8rem;
|
|---|
| 215 | color: var(--ink-muted, var(--ink-soft));
|
|---|
| 216 | background: var(--paper-2);
|
|---|
| 217 | padding: 0.1em 0.55em;
|
|---|
| 218 | border-radius: 999px;
|
|---|
| 219 | font-weight: 500;
|
|---|
| 220 | }
|
|---|
| 221 |
|
|---|
| 222 | /* ─── Form fields ──────────────────────────────────────────────── */
|
|---|
| 223 | .ax-form {
|
|---|
| 224 | display: flex; flex-direction: column;
|
|---|
| 225 | gap: 0.85rem;
|
|---|
| 226 | }
|
|---|
| 227 | .ax-row {
|
|---|
| 228 | display: grid; gap: 0.85rem;
|
|---|
| 229 | }
|
|---|
| 230 | .ax-row-2 { grid-template-columns: 1fr 1fr; }
|
|---|
| 231 | @media (max-width: 600px) {
|
|---|
| 232 | .ax-row-2 { grid-template-columns: 1fr; }
|
|---|
| 233 | }
|
|---|
| 234 | .ax-field {
|
|---|
| 235 | display: flex; flex-direction: column;
|
|---|
| 236 | gap: 0.35rem;
|
|---|
| 237 | min-width: 0;
|
|---|
| 238 | }
|
|---|
| 239 | .ax-field > span {
|
|---|
| 240 | font-size: 0.8rem;
|
|---|
| 241 | font-weight: 600;
|
|---|
| 242 | color: var(--ink-soft);
|
|---|
| 243 | display: flex; align-items: baseline; gap: 0.4rem;
|
|---|
| 244 | flex-wrap: wrap;
|
|---|
| 245 | }
|
|---|
| 246 | .ax-field > span small {
|
|---|
| 247 | font-weight: 400; color: var(--ink-muted);
|
|---|
| 248 | font-size: 0.95em;
|
|---|
| 249 | }
|
|---|
| 250 | .ax-field input[type="text"],
|
|---|
| 251 | .ax-field input[type="url"],
|
|---|
| 252 | .ax-field input[type="email"],
|
|---|
| 253 | .ax-field select,
|
|---|
| 254 | .ax-field textarea {
|
|---|
| 255 | width: 100%;
|
|---|
| 256 | box-sizing: border-box;
|
|---|
| 257 | padding: 0.6rem 0.75rem;
|
|---|
| 258 | border: 1px solid var(--rule);
|
|---|
| 259 | border-radius: 6px;
|
|---|
| 260 | background: var(--paper-2);
|
|---|
| 261 | color: var(--ink);
|
|---|
| 262 | font-family: var(--font-ui, system-ui), sans-serif;
|
|---|
| 263 | font-size: 0.95rem;
|
|---|
| 264 | -webkit-appearance: none; appearance: none;
|
|---|
| 265 | transition: border-color 120ms;
|
|---|
| 266 | }
|
|---|
| 267 | .ax-field input:focus,
|
|---|
| 268 | .ax-field select:focus,
|
|---|
| 269 | .ax-field textarea:focus {
|
|---|
| 270 | outline: 2px solid var(--accent);
|
|---|
| 271 | outline-offset: -1px;
|
|---|
| 272 | border-color: var(--accent);
|
|---|
| 273 | }
|
|---|
| 274 |
|
|---|
| 275 | /* ─── File inputs (custom-styled wrapper) ─────────────────────── */
|
|---|
| 276 | .ax-file-control {
|
|---|
| 277 | display: flex; align-items: center; gap: 0.5rem;
|
|---|
| 278 | flex-wrap: wrap;
|
|---|
| 279 | position: relative;
|
|---|
| 280 | min-height: 40px;
|
|---|
| 281 | }
|
|---|
| 282 | .ax-file-control input[type="file"] {
|
|---|
| 283 | position: absolute; inset: 0;
|
|---|
| 284 | opacity: 0; cursor: pointer;
|
|---|
| 285 | z-index: 2;
|
|---|
| 286 | }
|
|---|
| 287 | .ax-file-btn {
|
|---|
| 288 | pointer-events: none; /* let clicks pass through to the file input */
|
|---|
| 289 | }
|
|---|
| 290 | .ax-file-name {
|
|---|
| 291 | font-size: 0.85rem;
|
|---|
| 292 | color: var(--ink);
|
|---|
| 293 | word-break: break-all;
|
|---|
| 294 | flex: 1;
|
|---|
| 295 | min-width: 0;
|
|---|
| 296 | }
|
|---|
| 297 | .ax-file-name[data-empty] { color: var(--ink-muted, var(--ink-soft)); }
|
|---|
| 298 |
|
|---|
| 299 | .ax-form-actions {
|
|---|
| 300 | display: flex; gap: 0.5rem;
|
|---|
| 301 | margin-top: 0.25rem;
|
|---|
| 302 | }
|
|---|
| 303 | .ax-form-actions[hidden] { display: none; }
|
|---|
| 304 |
|
|---|
| 305 | /* ─── Buttons ──────────────────────────────────────────────────── */
|
|---|
| 306 | .ax-btn {
|
|---|
| 307 | display: inline-flex; align-items: center; justify-content: center; gap: 0.4rem;
|
|---|
| 308 | padding: 0.55rem 1rem;
|
|---|
| 309 | border: 1px solid var(--rule);
|
|---|
| 310 | background: var(--paper-2);
|
|---|
| 311 | color: var(--ink);
|
|---|
| 312 | font-family: var(--font-ui, system-ui), sans-serif;
|
|---|
| 313 | font-size: 0.9rem; font-weight: 500;
|
|---|
| 314 | text-decoration: none;
|
|---|
| 315 | border-radius: 6px;
|
|---|
| 316 | cursor: pointer;
|
|---|
| 317 | min-height: 40px;
|
|---|
| 318 | transition: background 120ms, border-color 120ms;
|
|---|
| 319 | -webkit-tap-highlight-color: transparent;
|
|---|
| 320 | }
|
|---|
| 321 | .ax-btn:hover { border-color: var(--accent); }
|
|---|
| 322 | .ax-btn-secondary { background: var(--paper-2); }
|
|---|
| 323 | .ax-btn-primary {
|
|---|
| 324 | background: var(--accent); color: white;
|
|---|
| 325 | border-color: var(--accent);
|
|---|
| 326 | }
|
|---|
| 327 | .ax-btn-primary:hover { opacity: 0.92; border-color: var(--accent); }
|
|---|
| 328 |
|
|---|
| 329 | /* Tiny icon-only button for row actions */
|
|---|
| 330 | .ax-icon-btn {
|
|---|
| 331 | display: inline-flex; align-items: center; justify-content: center;
|
|---|
| 332 | width: 40px; height: 40px;
|
|---|
| 333 | border: 1px solid var(--rule);
|
|---|
| 334 | background: var(--paper-2);
|
|---|
| 335 | color: var(--ink);
|
|---|
| 336 | border-radius: 8px;
|
|---|
| 337 | cursor: pointer;
|
|---|
| 338 | font-size: 1rem;
|
|---|
| 339 | transition: background 120ms, border-color 120ms, color 120ms;
|
|---|
| 340 | }
|
|---|
| 341 | .ax-icon-btn:hover { border-color: var(--accent); }
|
|---|
| 342 | .ax-icon-btn-danger:hover { color: #dc2626; border-color: #dc2626; }
|
|---|
| 343 |
|
|---|
| 344 | .ax-track-play.is-playing {
|
|---|
| 345 | border-color: var(--accent);
|
|---|
| 346 | color: var(--accent);
|
|---|
| 347 | background: color-mix(in srgb, var(--accent) 12%, var(--paper-2));
|
|---|
| 348 | }
|
|---|
| 349 | .ax-track-play-icon { font-size: 0.85rem; line-height: 1; }
|
|---|
| 350 |
|
|---|
| 351 | /* ─── Empty state ──────────────────────────────────────────────── */
|
|---|
| 352 | .ax-empty {
|
|---|
| 353 | text-align: center;
|
|---|
| 354 | padding: 2rem 1rem;
|
|---|
| 355 | color: var(--ink-muted, var(--ink-soft));
|
|---|
| 356 | }
|
|---|
| 357 | .ax-empty-icon {
|
|---|
| 358 | font-size: 2.5rem;
|
|---|
| 359 | font-family: var(--font-display, serif);
|
|---|
| 360 | color: var(--ink-soft);
|
|---|
| 361 | opacity: 0.4;
|
|---|
| 362 | margin-bottom: 0.5rem;
|
|---|
| 363 | }
|
|---|
| 364 | .ax-empty p { margin: 0; }
|
|---|
| 365 |
|
|---|
| 366 | /* ─── Track list (card per row) ────────────────────────────────── */
|
|---|
| 367 | .ax-list {
|
|---|
| 368 | list-style: none; padding: 0; margin: 0;
|
|---|
| 369 | display: flex; flex-direction: column;
|
|---|
| 370 | gap: 0.6rem;
|
|---|
| 371 | }
|
|---|
| 372 | .ax-track {
|
|---|
| 373 | display: grid;
|
|---|
| 374 | grid-template-columns: 56px 1fr auto;
|
|---|
| 375 | gap: 0.85rem;
|
|---|
| 376 | align-items: center;
|
|---|
| 377 | padding: 0.75rem;
|
|---|
| 378 | background: var(--paper-2);
|
|---|
| 379 | border: 1px solid var(--rule);
|
|---|
| 380 | border-radius: 10px;
|
|---|
| 381 | transition: border-color 120ms;
|
|---|
| 382 | }
|
|---|
| 383 | .ax-track:hover { border-color: var(--accent); }
|
|---|
| 384 | .ax-track-cover {
|
|---|
| 385 | width: 56px; height: 56px;
|
|---|
| 386 | border-radius: 6px;
|
|---|
| 387 | object-fit: cover;
|
|---|
| 388 | background: var(--paper);
|
|---|
| 389 | display: flex; align-items: center; justify-content: center;
|
|---|
| 390 | font-family: var(--font-display, serif);
|
|---|
| 391 | font-size: 1.5rem;
|
|---|
| 392 | color: var(--accent);
|
|---|
| 393 | flex-shrink: 0;
|
|---|
| 394 | }
|
|---|
| 395 | .ax-track-cover-empty { /* span variant */ }
|
|---|
| 396 | .ax-track-meta {
|
|---|
| 397 | min-width: 0; /* allow text-overflow on long titles */
|
|---|
| 398 | display: flex; flex-direction: column; gap: 0.2rem;
|
|---|
| 399 | }
|
|---|
| 400 | .ax-track-title {
|
|---|
| 401 | font-weight: 600;
|
|---|
| 402 | font-size: 0.95rem;
|
|---|
| 403 | color: var(--ink);
|
|---|
| 404 | overflow: hidden;
|
|---|
| 405 | text-overflow: ellipsis;
|
|---|
| 406 | white-space: nowrap;
|
|---|
| 407 | }
|
|---|
| 408 | .ax-track-sub {
|
|---|
| 409 | font-size: 0.8rem;
|
|---|
| 410 | color: var(--ink-muted, var(--ink-soft));
|
|---|
| 411 | display: flex; flex-wrap: wrap; gap: 0.3rem; align-items: center;
|
|---|
| 412 | }
|
|---|
| 413 | .ax-track-sep { opacity: 0.5; }
|
|---|
| 414 | .ax-embed {
|
|---|
| 415 | display: inline-block;
|
|---|
| 416 | background: var(--paper);
|
|---|
| 417 | padding: 0.15rem 0.5rem;
|
|---|
| 418 | border-radius: 4px;
|
|---|
| 419 | font-family: var(--font-mono, ui-monospace, monospace);
|
|---|
| 420 | font-size: 0.75rem;
|
|---|
| 421 | color: var(--ink-soft);
|
|---|
| 422 | cursor: pointer;
|
|---|
| 423 | user-select: all;
|
|---|
| 424 | margin-top: 0.15rem;
|
|---|
| 425 | word-break: break-all;
|
|---|
| 426 | align-self: flex-start;
|
|---|
| 427 | transition: background 120ms;
|
|---|
| 428 | }
|
|---|
| 429 | .ax-embed:hover { background: var(--rule); }
|
|---|
| 430 | .ax-embed.is-copied { background: rgba(40,160,90,0.2); color: #2a9d5e; }
|
|---|
| 431 |
|
|---|
| 432 | .ax-track-actions {
|
|---|
| 433 | display: flex; gap: 0.4rem;
|
|---|
| 434 | flex-shrink: 0;
|
|---|
| 435 | }
|
|---|
| 436 | .ax-track-delete { display: inline; }
|
|---|
| 437 |
|
|---|
| 438 | /* ─── Mobile tweaks ───────────────────────────────────────────── */
|
|---|
| 439 | @media (max-width: 480px) {
|
|---|
| 440 | .ax-track {
|
|---|
| 441 | grid-template-columns: 48px 1fr;
|
|---|
| 442 | grid-template-areas: "cover meta" "actions actions";
|
|---|
| 443 | gap: 0.6rem;
|
|---|
| 444 | }
|
|---|
| 445 | .ax-track-cover { grid-area: cover; width: 48px; height: 48px; }
|
|---|
| 446 | .ax-track-meta { grid-area: meta; }
|
|---|
| 447 | .ax-track-actions {
|
|---|
| 448 | grid-area: actions;
|
|---|
| 449 | justify-content: flex-end;
|
|---|
| 450 | border-top: 1px solid var(--rule);
|
|---|
| 451 | padding-top: 0.5rem;
|
|---|
| 452 | margin-top: 0.25rem;
|
|---|
| 453 | }
|
|---|
| 454 | }
|
|---|
| 455 |
|
|---|
| 456 | /* ─── Bulk drop-zone ──────────────────────────────────────────── */
|
|---|
| 457 | .ax-dropzone {
|
|---|
| 458 | display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|---|
| 459 | gap: 0.4rem;
|
|---|
| 460 | padding: 2rem 1rem;
|
|---|
| 461 | border: 2px dashed var(--rule);
|
|---|
| 462 | border-radius: 12px;
|
|---|
| 463 | background: var(--paper-2);
|
|---|
| 464 | cursor: pointer;
|
|---|
| 465 | text-align: center;
|
|---|
| 466 | transition: border-color 150ms, background 150ms;
|
|---|
| 467 | position: relative;
|
|---|
| 468 | min-height: 160px;
|
|---|
| 469 | }
|
|---|
| 470 | .ax-dropzone:hover,
|
|---|
| 471 | .ax-dropzone:focus-within {
|
|---|
| 472 | border-color: var(--accent);
|
|---|
| 473 | }
|
|---|
| 474 | .ax-dropzone.is-dragover {
|
|---|
| 475 | border-color: var(--accent);
|
|---|
| 476 | background: color-mix(in srgb, var(--accent) 8%, var(--paper-2));
|
|---|
| 477 | }
|
|---|
| 478 | .ax-dropzone input[type="file"] {
|
|---|
| 479 | /* Cover the whole dropzone but invisible — clicks bubble through to it. */
|
|---|
| 480 | position: absolute; inset: 0;
|
|---|
| 481 | opacity: 0;
|
|---|
| 482 | cursor: pointer;
|
|---|
| 483 | }
|
|---|
| 484 | .ax-dropzone-icon {
|
|---|
| 485 | font-size: 2rem;
|
|---|
| 486 | opacity: 0.5;
|
|---|
| 487 | line-height: 1;
|
|---|
| 488 | }
|
|---|
| 489 | .ax-dropzone-title {
|
|---|
| 490 | font-size: 1rem;
|
|---|
| 491 | font-weight: 600;
|
|---|
| 492 | color: var(--ink);
|
|---|
| 493 | }
|
|---|
| 494 | .ax-dropzone-sub {
|
|---|
| 495 | font-size: 0.85rem;
|
|---|
| 496 | color: var(--ink-muted, var(--ink-soft));
|
|---|
| 497 | line-height: 1.4;
|
|---|
| 498 | }
|
|---|
| 499 | .ax-dropzone-sub small {
|
|---|
| 500 | font-size: 0.75rem;
|
|---|
| 501 | opacity: 0.8;
|
|---|
| 502 | }
|
|---|
| 503 |
|
|---|
| 504 | /* ─── Upload queue ────────────────────────────────────────────── */
|
|---|
| 505 | .ax-upload-queue {
|
|---|
| 506 | list-style: none;
|
|---|
| 507 | padding: 0; margin: 0.75rem 0 0;
|
|---|
| 508 | display: flex; flex-direction: column;
|
|---|
| 509 | gap: 0.4rem;
|
|---|
| 510 | }
|
|---|
| 511 | .ax-queue-item {
|
|---|
| 512 | display: grid;
|
|---|
| 513 | grid-template-columns: 1fr auto;
|
|---|
| 514 | gap: 0.75rem;
|
|---|
| 515 | align-items: center;
|
|---|
| 516 | padding: 0.6rem 0.85rem;
|
|---|
| 517 | background: var(--paper);
|
|---|
| 518 | border: 1px solid var(--rule);
|
|---|
| 519 | border-radius: 8px;
|
|---|
| 520 | font-size: 0.875rem;
|
|---|
| 521 | transition: border-color 150ms, opacity 150ms;
|
|---|
| 522 | }
|
|---|
| 523 | .ax-queue-item--active {
|
|---|
| 524 | border-color: var(--accent);
|
|---|
| 525 | }
|
|---|
| 526 | .ax-queue-item--done {
|
|---|
| 527 | border-color: rgba(40,160,90,0.4);
|
|---|
| 528 | opacity: 0.85;
|
|---|
| 529 | }
|
|---|
| 530 | .ax-queue-item--error {
|
|---|
| 531 | border-color: rgba(220,60,60,0.5);
|
|---|
| 532 | }
|
|---|
| 533 | .ax-queue-name {
|
|---|
| 534 | font-weight: 500;
|
|---|
| 535 | color: var(--ink);
|
|---|
| 536 | overflow: hidden;
|
|---|
| 537 | text-overflow: ellipsis;
|
|---|
| 538 | white-space: nowrap;
|
|---|
| 539 | min-width: 0;
|
|---|
| 540 | }
|
|---|
| 541 | .ax-queue-size {
|
|---|
| 542 | font-size: 0.75rem;
|
|---|
| 543 | color: var(--ink-muted, var(--ink-soft));
|
|---|
| 544 | margin-left: 0.5rem;
|
|---|
| 545 | }
|
|---|
| 546 | .ax-queue-status {
|
|---|
| 547 | font-size: 0.75rem;
|
|---|
| 548 | font-weight: 500;
|
|---|
| 549 | padding: 0.15em 0.6em;
|
|---|
| 550 | border-radius: 999px;
|
|---|
| 551 | background: var(--paper-2);
|
|---|
| 552 | color: var(--ink-soft);
|
|---|
| 553 | white-space: nowrap;
|
|---|
| 554 | flex-shrink: 0;
|
|---|
| 555 | }
|
|---|
| 556 | .ax-queue-status--queued { color: var(--ink-muted, var(--ink-soft)); }
|
|---|
| 557 | .ax-queue-status--uploading,
|
|---|
| 558 | .ax-queue-status--transcoding {
|
|---|
| 559 | color: var(--accent);
|
|---|
| 560 | background: color-mix(in srgb, var(--accent) 10%, var(--paper-2));
|
|---|
| 561 | }
|
|---|
| 562 | .ax-queue-status--done {
|
|---|
| 563 | color: #2a9d5e;
|
|---|
| 564 | background: rgba(40,160,90,0.12);
|
|---|
| 565 | }
|
|---|
| 566 | .ax-queue-status--error {
|
|---|
| 567 | color: #dc2626;
|
|---|
| 568 | background: rgba(220,60,60,0.12);
|
|---|
| 569 | }
|
|---|
| 570 | </style>
|
|---|
| 571 |
|
|---|
| 572 | <%# Track editor modal (exposes window.openTrackEditor) %>
|
|---|
| 573 | <%- include('../partials/track-editor', { csrfToken: (typeof csrfToken !== 'undefined' ? csrfToken : '') }) %>
|
|---|
| 574 |
|
|---|
| 575 | <%# Het script staat in assets/js/mod/admin-audio.js; de gegevens via partials/page-data.ejs (shaer-bqr). %>
|
|---|
| 576 | <%- include('../partials/page-data', { pageData: { change_failed: t('aaud.change_failed'), copied: t('aaud.copied'), create_failed: t('aaud.create_failed'), dl_off: t('aaud.dl_off'), dl_on: t('aaud.dl_on'), editor_not_loaded: t('aaud.editor_not_loaded'), err_unexpected: t('aaud.err_unexpected'), failed: t('aaud.failed'), new_track: t('aaud.new_track'), no_file: t('aaud.no_file'), pause: t('aaud.pause'), play: t('aaud.play'), st_done: t('aaud.st_done'), st_error: t('aaud.st_error'), st_queued: t('aaud.st_queued'), st_transcoding: t('aaud.st_transcoding'), st_uploading: t('aaud.st_uploading'), untitled: t('aaud.untitled') } }) %>
|
|---|