diff --git a/history.css b/history.css index b2f69fb..9d4f774 100644 --- a/history.css +++ b/history.css @@ -1,12 +1,12 @@ body { margin: 0; - font-family: Arial, sans-serif; - background: #0e1117; - color: #e6edf7; + font-family: Inter, "Segoe UI", Arial, sans-serif; + background: #070909; + color: #e7f0e9; } .history-page { - max-width: 960px; + max-width: 1000px; margin: 0 auto; padding: 24px; } @@ -16,7 +16,7 @@ h1 { } p { - color: #aeb7cc; + color: #8ea292; } #historyList { @@ -25,43 +25,98 @@ p { padding: 0; display: flex; flex-direction: column; - gap: 12px; + gap: 14px; } .history-item { - border: 1px solid #2a2f40; - border-radius: 8px; - padding: 12px; - background: #151a26; + border: 1px solid #1d2620; + border-radius: 12px; + padding: 10px; + background: #0d1210; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.01); +} + +.history-head { + display: flex; + gap: 10px; + align-items: center; +} + +.history-info { + min-width: 0; + flex: 1; } .history-cover-image { - width: 48px; - height: 48px; - border-radius: 6px; + width: 44px; + height: 44px; + border-radius: 7px; object-fit: cover; - display: block; - margin-bottom: 10px; + border: 1px solid #243027; + flex-shrink: 0; } -.history-url { - margin: 0 0 8px; - word-break: break-all; - color: #d2d9ea; +.history-title { + margin: 0; + color: #eff8f1; font-size: 13px; + font-weight: 600; + word-break: break-word; + line-height: 1.3; } -.history-meta { - margin: 0 0 10px; - color: #98a5c0; - font-size: 12px; +.history-subtitle { + margin: 3px 0 0; + color: #94a89a; + font-size: 11px; } -button { - border: 1px solid #2f66ff; - background: #2f66ff; - color: #fff; - border-radius: 6px; +.history-badges { + margin-top: 8px; + display: flex; + gap: 6px; +} + +.badge { + border: 1px solid #2d4f35; + background: #142219; + color: #bde6c6; + border-radius: 999px; + padding: 2px 8px; + font-size: 10px; + letter-spacing: 0.01em; +} + +.history-tags { + margin: 4px 0 0; + color: #b3c9b8; + font-size: 11px; + line-height: 1.3; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.download-icon-btn { + width: 44px; + height: 44px; + border: 1px solid #2f9e5a; + background: #1b7f45; + color: #f1fff4; + border-radius: 10px; + display: inline-flex; + align-items: center; + justify-content: center; cursor: pointer; - padding: 6px 10px; + flex-shrink: 0; + transition: background 120ms ease, transform 120ms ease; +} + +.download-icon-btn:hover { + background: #249452; + transform: translateY(-1px); +} + +.download-icon-btn:active { + transform: translateY(0); } diff --git a/history.html b/history.html index 62946e9..8b112b0 100644 --- a/history.html +++ b/history.html @@ -12,7 +12,6 @@

S3 Audio Capture History

-

All captured Splice S3 URLs.

Loading history...

diff --git a/history.js b/history.js index 1353122..01f89a8 100644 --- a/history.js +++ b/history.js @@ -3,51 +3,101 @@ const statusEl = document.getElementById("status"); const listEl = document.getElementById("historyList"); -function formatDate(ms) { - try { - return new Date(ms).toLocaleString(); - } catch (error) { - return "unknown time"; - } -} - function formatMeta(entry) { const packName = entry && entry.packName ? entry.packName : "n/a"; const key = entry && entry.key ? entry.key : "n/a"; const bpm = entry && entry.bpm ? entry.bpm : "n/a"; - const tags = entry && Array.isArray(entry.tags) && entry.tags.length ? entry.tags.join(", ") : "n/a"; - return `Pack: ${packName} | Key: ${key} | BPM: ${bpm} | Tags: ${tags}`; + return { packName, key, bpm }; +} + +function formatTags(entry) { + if (!entry || !Array.isArray(entry.tags) || !entry.tags.length) { + return "No tags"; + } + return entry.tags.join(" • "); +} + +function createDownloadIcon() { + const ns = "http://www.w3.org/2000/svg"; + const svg = document.createElementNS(ns, "svg"); + svg.setAttribute("viewBox", "0 0 24 24"); + svg.setAttribute("width", "22"); + svg.setAttribute("height", "22"); + svg.setAttribute("aria-hidden", "true"); + + const arrow = document.createElementNS(ns, "path"); + arrow.setAttribute("d", "M12 4v10m0 0l4-4m-4 4l-4-4"); + arrow.setAttribute("fill", "none"); + arrow.setAttribute("stroke", "currentColor"); + arrow.setAttribute("stroke-width", "2"); + arrow.setAttribute("stroke-linecap", "round"); + arrow.setAttribute("stroke-linejoin", "round"); + + const tray = document.createElementNS(ns, "path"); + tray.setAttribute("d", "M4 18v1a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-1"); + tray.setAttribute("fill", "none"); + tray.setAttribute("stroke", "currentColor"); + tray.setAttribute("stroke-width", "2"); + tray.setAttribute("stroke-linecap", "round"); + tray.setAttribute("stroke-linejoin", "round"); + + svg.appendChild(arrow); + svg.appendChild(tray); + return svg; } function buildItem(entry) { const li = document.createElement("li"); li.className = "history-item"; + const meta = formatMeta(entry); - if (entry.coverImageUrl) { - const cover = document.createElement("img"); - cover.className = "history-cover-image"; - cover.src = entry.coverImageUrl; - cover.alt = "Cover art"; - cover.loading = "lazy"; - li.appendChild(cover); - } + const head = document.createElement("div"); + head.className = "history-head"; - const url = document.createElement("p"); - url.className = "history-url"; - url.textContent = entry.fileName || entry.sampleId || "(unknown filename)"; + const cover = document.createElement("img"); + cover.className = "history-cover-image"; + cover.src = entry.coverImageUrl || "icons/icon-48.png"; + cover.alt = "Cover art"; + cover.loading = "lazy"; - const meta = document.createElement("p"); - meta.className = "history-meta"; + const info = document.createElement("div"); + info.className = "history-info"; + + const title = document.createElement("p"); + title.className = "history-title"; + title.textContent = entry.fileName || entry.sampleId || "(unknown filename)"; + + const subtitle = document.createElement("p"); + subtitle.className = "history-subtitle"; const mode = entry.mode === "full" || entry.fullUrl ? "full candidate" : "preview fallback"; - meta.textContent = `Captured: ${formatDate(entry.capturedAt)} | Mode: ${mode} | Source: ${entry.source || "unknown"} | ID: ${entry.sampleId || "n/a"}`; + subtitle.textContent = `${meta.packName} • ${mode}`; - const details = document.createElement("p"); - details.className = "history-meta"; - details.textContent = formatMeta(entry); + const badges = document.createElement("div"); + badges.className = "history-badges"; + const keyBadge = document.createElement("span"); + keyBadge.className = "badge"; + keyBadge.textContent = `Key ${meta.key}`; + const bpmBadge = document.createElement("span"); + bpmBadge.className = "badge"; + bpmBadge.textContent = `BPM ${meta.bpm}`; + badges.appendChild(keyBadge); + badges.appendChild(bpmBadge); + + const tags = document.createElement("p"); + tags.className = "history-tags"; + tags.textContent = formatTags(entry); + + info.appendChild(title); + info.appendChild(subtitle); + info.appendChild(badges); + info.appendChild(tags); const download = document.createElement("button"); download.type = "button"; - download.textContent = "Download"; + download.className = "download-icon-btn"; + download.setAttribute("aria-label", "Download sample"); + download.setAttribute("title", "Download sample"); + download.appendChild(createDownloadIcon()); download.addEventListener("click", async () => { const result = await browser.runtime.sendMessage({ type: "DOWNLOAD_ENTRY", @@ -60,10 +110,10 @@ function buildItem(entry) { statusEl.textContent = result.mode === "full" ? "Downloaded full file URL." : "Downloaded decoded preview fallback."; }); - li.appendChild(url); - li.appendChild(meta); - li.appendChild(details); - li.appendChild(download); + head.appendChild(cover); + head.appendChild(info); + head.appendChild(download); + li.appendChild(head); return li; } diff --git a/popup.css b/popup.css index 54f5605..03bfb3e 100644 --- a/popup.css +++ b/popup.css @@ -1,14 +1,14 @@ body { margin: 0; - font-family: Arial, sans-serif; - background: #111319; - color: #e9ecf1; + font-family: Inter, "Segoe UI", Arial, sans-serif; + background: #070909; + color: #e7f0e9; } .popup { - width: 380px; - max-height: 520px; - padding: 12px; + width: 420px; + max-height: 560px; + padding: 14px; box-sizing: border-box; } @@ -16,32 +16,37 @@ body { display: flex; align-items: center; justify-content: space-between; - gap: 8px; + gap: 10px; + margin-bottom: 10px; } .popup-header h1 { margin: 0; font-size: 15px; + font-weight: 600; + letter-spacing: 0.01em; } -button { - border: 1px solid #2f66ff; - background: #2f66ff; - color: white; - border-radius: 6px; +#openHistoryBtn { + border: 1px solid #263329; + background: #101513; + color: #b7cbbd; + border-radius: 999px; cursor: pointer; - padding: 6px 9px; + padding: 6px 10px; font-size: 12px; + transition: all 120ms ease; } -button.secondary { - border-color: #445; - background: #1e2230; +#openHistoryBtn:hover { + border-color: #3c5342; + color: #e3f3e7; + background: #151d18; } .status { - margin: 12px 0; - color: #adb6cb; + margin: 8px 0 12px; + color: #829589; font-size: 12px; } @@ -51,33 +56,98 @@ button.secondary { margin: 0; display: flex; flex-direction: column; - gap: 8px; + gap: 10px; } .url-item { - border: 1px solid #2a2f40; - border-radius: 8px; - background: #171b26; - padding: 8px; + border: 1px solid #1d2620; + border-radius: 12px; + background: #0d1210; + padding: 10px; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.01); +} + +.item-head { + display: flex; + gap: 10px; + align-items: center; +} + +.item-info { + min-width: 0; + flex: 1; } .cover-image { - width: 36px; - height: 36px; - border-radius: 4px; + width: 44px; + height: 44px; + border-radius: 7px; object-fit: cover; - display: block; - margin-bottom: 8px; + flex-shrink: 0; + border: 1px solid #243027; } -.url-text { - margin: 0 0 8px; +.item-title { + margin: 0; + color: #eff8f1; + font-size: 13px; + font-weight: 600; + line-height: 1.3; + word-break: break-word; +} + +.item-sub { + margin: 3px 0 0; + color: #94a89a; font-size: 11px; - word-break: break-all; - color: #c6d0e5; } -.actions { - display: flex; - gap: 8px; +.item-tags { + margin: 4px 0 0; + color: #b3c9b8; + font-size: 11px; + line-height: 1.3; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.item-badges { + margin-top: 8px; + display: flex; + gap: 6px; +} + +.badge { + border: 1px solid #2d4f35; + background: #142219; + color: #bde6c6; + border-radius: 999px; + padding: 2px 8px; + font-size: 10px; + letter-spacing: 0.01em; +} + +.download-icon-btn { + width: 44px; + height: 44px; + border: 1px solid #2f9e5a; + background: #1b7f45; + color: #f1fff4; + border-radius: 10px; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + flex-shrink: 0; + transition: background 120ms ease, transform 120ms ease; +} + +.download-icon-btn:hover { + background: #249452; + transform: translateY(-1px); +} + +.download-icon-btn:active { + transform: translateY(0); } diff --git a/popup.js b/popup.js index 69a6556..a270dc5 100644 --- a/popup.js +++ b/popup.js @@ -16,41 +16,101 @@ function formatMeta(entry) { const packName = entry && entry.packName ? entry.packName : "n/a"; const key = entry && entry.key ? entry.key : "n/a"; const bpm = entry && entry.bpm ? entry.bpm : "n/a"; - const tags = entry && Array.isArray(entry.tags) && entry.tags.length ? entry.tags.join(", ") : "n/a"; - return `Pack: ${packName} | Key: ${key} | BPM: ${bpm} | Tags: ${tags}`; + return { packName, key, bpm }; +} + +function formatTags(entry) { + if (!entry || !Array.isArray(entry.tags) || !entry.tags.length) { + return "No tags"; + } + const maxTags = 5; + const tags = entry.tags.slice(0, maxTags); + const suffix = entry.tags.length > maxTags ? ` +${entry.tags.length - maxTags}` : ""; + return tags.join(" • ") + suffix; +} + +function createDownloadIcon() { + const ns = "http://www.w3.org/2000/svg"; + const svg = document.createElementNS(ns, "svg"); + svg.setAttribute("viewBox", "0 0 24 24"); + svg.setAttribute("width", "22"); + svg.setAttribute("height", "22"); + svg.setAttribute("aria-hidden", "true"); + + const arrow = document.createElementNS(ns, "path"); + arrow.setAttribute("d", "M12 4v10m0 0l4-4m-4 4l-4-4"); + arrow.setAttribute("fill", "none"); + arrow.setAttribute("stroke", "currentColor"); + arrow.setAttribute("stroke-width", "2"); + arrow.setAttribute("stroke-linecap", "round"); + arrow.setAttribute("stroke-linejoin", "round"); + + const tray = document.createElementNS(ns, "path"); + tray.setAttribute("d", "M4 18v1a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-1"); + tray.setAttribute("fill", "none"); + tray.setAttribute("stroke", "currentColor"); + tray.setAttribute("stroke-width", "2"); + tray.setAttribute("stroke-linecap", "round"); + tray.setAttribute("stroke-linejoin", "round"); + + svg.appendChild(arrow); + svg.appendChild(tray); + return svg; } function buildRow(entry) { const li = document.createElement("li"); li.className = "url-item"; + const meta = formatMeta(entry); - if (entry.coverImageUrl) { - const cover = document.createElement("img"); - cover.className = "cover-image"; - cover.src = entry.coverImageUrl; - cover.alt = "Cover art"; - cover.loading = "lazy"; - li.appendChild(cover); - } + const head = document.createElement("div"); + head.className = "item-head"; - const p = document.createElement("p"); - p.className = "url-text"; - p.textContent = entry.fileName || entry.sampleId || "(unknown filename)"; + const cover = document.createElement("img"); + cover.className = "cover-image"; + cover.src = entry.coverImageUrl || "icons/icon-48.png"; + cover.alt = "Cover art"; + cover.loading = "lazy"; - const status = document.createElement("p"); - status.className = "status"; - status.textContent = `Source: ${entry.source || "unknown"} | Mode: ${entryLabel(entry)} | ID: ${entry.sampleId || "n/a"}`; + const info = document.createElement("div"); + info.className = "item-info"; - const meta = document.createElement("p"); - meta.className = "status"; - meta.textContent = formatMeta(entry); + const title = document.createElement("p"); + title.className = "item-title"; + title.textContent = entry.fileName || entry.sampleId || "(unknown filename)"; - const actions = document.createElement("div"); - actions.className = "actions"; + const sub = document.createElement("p"); + sub.className = "item-sub"; + sub.textContent = `${meta.packName} • ${entryLabel(entry)}`; + + const tags = document.createElement("p"); + tags.className = "item-tags"; + tags.textContent = formatTags(entry); + + const badges = document.createElement("div"); + badges.className = "item-badges"; + + const keyBadge = document.createElement("span"); + keyBadge.className = "badge"; + keyBadge.textContent = `Key ${meta.key}`; + + const bpmBadge = document.createElement("span"); + bpmBadge.className = "badge"; + bpmBadge.textContent = `BPM ${meta.bpm}`; + + badges.appendChild(keyBadge); + badges.appendChild(bpmBadge); + info.appendChild(title); + info.appendChild(sub); + info.appendChild(tags); + info.appendChild(badges); const download = document.createElement("button"); download.type = "button"; - download.textContent = "Download"; + download.className = "download-icon-btn"; + download.setAttribute("aria-label", "Download sample"); + download.setAttribute("title", "Download sample"); + download.appendChild(createDownloadIcon()); download.addEventListener("click", async () => { const result = await browser.runtime.sendMessage({ type: "DOWNLOAD_ENTRY", entry }); if (!result || !result.ok) { @@ -60,21 +120,10 @@ function buildRow(entry) { setStatus(result.mode === "full" ? "Downloaded full file URL." : "Downloaded decoded preview fallback."); }); - const copy = document.createElement("button"); - copy.type = "button"; - copy.className = "secondary"; - copy.textContent = "Copy URL"; - copy.addEventListener("click", async () => { - const url = entry.fullUrl || entry.previewUrl || ""; - await navigator.clipboard.writeText(url); - }); - - actions.appendChild(download); - actions.appendChild(copy); - li.appendChild(p); - li.appendChild(status); - li.appendChild(meta); - li.appendChild(actions); + head.appendChild(cover); + head.appendChild(info); + head.appendChild(download); + li.appendChild(head); return li; }