diff --git a/index.html b/index.html
index f92c588..681ec30 100644
--- a/index.html
+++ b/index.html
@@ -17,7 +17,7 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
@keyframes spin { to { transform: rotate(360deg); } }
.brand { font-size: 15px; font-weight: 700; color: #6b7089; letter-spacing: 1px; user-select: none; }
.brand span { color: #6b7089; }
-footer { display: flex !important; align-items: center; gap: 16px; padding: 10px 24px; background: linear-gradient(to bottom, #1e2132, #161821); border-top: 1px solid #272c42; font-size: 14px; color: #6b7089; flex-shrink: 0; }
+footer { display: flex !important; align-items: center; gap: 16px; padding: 10px 24px; background: linear-gradient(to bottom, #1e2132, #161821); border-top: 1px solid #272c42; font-size: 14px; color: #6b7089; flex-shrink: 0; position: relative; }
footer .brand { margin-left: auto; }
.content { flex: 1; overflow: hidden; display: flex; flex-direction: column; }
.pinned { flex-shrink: 0; background: linear-gradient(to bottom, #1e2132, #161821); padding: 16px 24px; display: flex; flex-direction: row; gap: 20px; align-items: center; }
@@ -38,6 +38,14 @@ canvas { width: 100%; height: 100px; border-radius: 6px; display: block; }
#noiz-overlay { position: fixed; opacity: 0.75; inset: 0; display: flex; justify-content: center; align-items: center; background: transparent; pointer-events: none; z-index: 9999; animation: fadeout 750ms ease 750ms forwards; }
#noiz-overlay img { max-width: 50vw; max-height: 50vh; filter: drop-shadow(0 8px 16px rgba(0,0,0,0.5)); }
@keyframes fadeout { from { opacity: 0.75; transform: scale(1); } to { opacity: 0; transform: scale(1.75); visibility: hidden; } }
+#tagEditorOverlay { position: fixed; inset: 0; display: none; align-items: center; justify-content: center; background: rgba(22,24,33,0.85); z-index: 10000; }
+#tagEditorModal { background: #1e2132; border: 1px solid #6b7089; border-radius: 8px; padding: 24px; min-width: 640px; max-width: 700px; box-shadow: 0 8px 32px rgba(0,0,0,0.5); }
+#tagEditorModal h3 { margin-bottom: 16px; color: #c6c8d1; font-size: 14px; font-weight: 600; }
+.tag-editor-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
+.tag-editor-fields label { display: flex; flex-direction: column; gap: 2px; margin-bottom: 0; font-size: 11px; color: #6b7089; text-transform: uppercase; letter-spacing: .4px; }
+#tagEditorModal input, #tagEditorModal select { background: #161821; border: 1px solid #272c42; color: #c6c8d1; padding: 6px 8px; border-radius: 4px; font-size: 12px; outline: none; }
+#tagEditorModal input:focus, #tagEditorModal select:focus { border-color: #84a0c6; }
+.tag-editor-buttons { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; }
#timeRemaining { color: #6b7089; }
#vuMeter { width: 100%; height: 20px; border-radius: 2px; display: block; }
.controls { display: flex; align-items: center; gap: 16px; align-self: flex-end; }
@@ -95,6 +103,9 @@ canvas { width: 100%; height: 100px; border-radius: 6px; display: block; }
.track-item .col-time { color: #6b7089; font-size: 11px; font-variant-numeric: tabular-nums; overflow: hidden; white-space: nowrap; }
.track-item .col-genre { color: #6b7089; font-size: 10px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
footer .footer-track { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+#footerStatus { font-size: 12px; font-weight: 600; white-space: nowrap; position: absolute; left: 50%; transform: translateX(-50%); pointer-events: none; }
+#footerStatus.success { color: #87be82; }
+#footerStatus.error { color: #e27878; }
#error { display: none; background: rgba(226,120,120,0.15); color: #e27878; padding: 8px 16px; font-size: 13px; text-align: center; border-bottom: 1px solid rgba(226,120,120,0.3); }
@media (max-width: 700px) {
.player { flex-direction: column; align-items: stretch; }
@@ -232,12 +243,36 @@ footer .footer-track { flex: 1; min-width: 0; white-space: nowrap; overflow: hid
+
+
@@ -566,6 +601,7 @@ const timeRemaining = document.getElementById('timeRemaining');
const progressRow = document.getElementById('progressRow');
const footerTrack = document.getElementById('footerTrack');
const footerTime = document.getElementById('footerTime');
+const footerStatus = document.getElementById('footerStatus');
const volumeSlider = document.getElementById('volumeSlider');
const volDisplay = document.getElementById('volDisplay');
const audioInfo = document.getElementById('audioInfo');
@@ -574,6 +610,21 @@ const windowSelect = document.getElementById('windowSelect');
const decaySelect = document.getElementById('decaySelect');
const barDecaySelect = document.getElementById('barDecaySelect');
const crossfadeSelect = document.getElementById('crossfadeSelect');
+const infoBtn = document.getElementById('infoBtn');
+const tagEditorOverlay = document.getElementById('tagEditorOverlay');
+const tagName = document.getElementById('tagName');
+const tagArtist = document.getElementById('tagArtist');
+const tagAlbum = document.getElementById('tagAlbum');
+const tagGenre = document.getElementById('tagGenre');
+const tagYear = document.getElementById('tagYear');
+const tagTrack = document.getElementById('tagTrack');
+const tagComment = document.getElementById('tagComment');
+const tagComposer = document.getElementById('tagComposer');
+const tagUrl = document.getElementById('tagUrl');
+const tagEncodedBy = document.getElementById('tagEncodedBy');
+const tagSaveBtn = document.getElementById('tagSaveBtn');
+const tagCancelBtn = document.getElementById('tagCancelBtn');
+const genreList = document.getElementById('genreList');
const AUDIO_EXTS = new Set(['.mp3','.wav','.ogg','.flac','.m4a','.aac','.wma','.aiff','.alac','.opus']);
const GENRES = [
'Blues','Classic Rock','Country','Dance','Disco','Funk','Grunge','Hip-Hop','Jazz','Metal',
@@ -887,6 +938,10 @@ async function processFiles(files, paths) {
genre: tags.genre || '',
year: tags.year || '',
track: tags.track || '',
+ comment: tags.comment || '',
+ composer: tags.composer || '',
+ urlTag: tags.urlTag || '',
+ encodedBy: tags.encodedBy || '',
filename: file.name,
url,
file,
@@ -1006,6 +1061,59 @@ function deleteTrack() {
renderTracks();
}
}
+function openTagEditor() {
+ let idx = -1;
+ if (selectedIndices.size > 0) {
+ idx = [...selectedIndices][0];
+ } else if (currentIndex >= 0) {
+ idx = currentIndex;
+ }
+ if (idx < 0) { showStatus('No track selected.', 'error'); return; }
+ const t = tracks[idx];
+ tagEditorOverlay.dataset.idx = idx;
+ tagName.value = t.name || '';
+ tagArtist.value = t.artist || '';
+ tagAlbum.value = t.album || '';
+ tagGenre.value = t.genre || '';
+ tagYear.value = t.year || '';
+ tagTrack.value = t.track || '';
+ tagComment.value = t.comment || '';
+ tagComposer.value = t.composer || '';
+ tagUrl.value = t.urlTag || '';
+ tagEncodedBy.value = t.encodedBy || '';
+ tagEditorOverlay.style.display = 'flex';
+}
+function showStatus(msg, type) {
+ footerStatus.textContent = msg;
+ footerStatus.className = type;
+ clearTimeout(footerStatus._timer);
+ footerStatus._timer = setTimeout(() => { footerStatus.textContent = ''; footerStatus.className = ''; }, 2000);
+}
+function saveTagEditor() {
+ const idx = parseInt(tagEditorOverlay.dataset.idx, 10);
+ if (isNaN(idx) || idx < 0 || idx >= tracks.length) {
+ showStatus('Error saving tags', 'error');
+ return;
+ }
+ const t = tracks[idx];
+ t.name = tagName.value.trim();
+ t.artist = tagArtist.value.trim();
+ t.album = tagAlbum.value.trim();
+ t.genre = tagGenre.value.trim();
+ t.year = tagYear.value.trim();
+ t.track = tagTrack.value.trim();
+ t.comment = tagComment.value.trim();
+ t.composer = tagComposer.value.trim();
+ t.urlTag = tagUrl.value.trim();
+ t.encodedBy = tagEncodedBy.value.trim();
+ if (idx === currentIndex) updateUI();
+ renderTracks();
+ tagEditorOverlay.style.display = 'none';
+ showStatus('Tag information saved.', 'success');
+}
+function cancelTagEditor() {
+ tagEditorOverlay.style.display = 'none';
+}
function getSortFn(field) {
switch (field) {
case 'idx': return (a, b) => tracks.indexOf(a) - tracks.indexOf(b);
@@ -1300,6 +1408,23 @@ addFolderBtn.addEventListener('click', addFolder);
addFilesBtn.addEventListener('click', addFiles);
isolateBtn.addEventListener('click', isolateTracks);
deleteBtn.addEventListener('click', deleteTrack);
+infoBtn.addEventListener('click', openTagEditor);
+tagSaveBtn.addEventListener('click', saveTagEditor);
+tagCancelBtn.addEventListener('click', cancelTagEditor);
+tagEditorOverlay.addEventListener('click', (e) => {
+ if (e.target === tagEditorOverlay) cancelTagEditor();
+});
+tagName.addEventListener('keydown', (e) => { if (e.key === 'Enter') saveTagEditor(); });
+tagArtist.addEventListener('keydown', (e) => { if (e.key === 'Enter') saveTagEditor(); });
+tagAlbum.addEventListener('keydown', (e) => { if (e.key === 'Enter') saveTagEditor(); });
+tagGenre.addEventListener('keydown', (e) => { if (e.key === 'Enter') saveTagEditor(); });
+tagYear.addEventListener('keydown', (e) => { if (e.key === 'Enter') saveTagEditor(); });
+tagTrack.addEventListener('keydown', (e) => { if (e.key === 'Enter') saveTagEditor(); });
+tagComment.addEventListener('keydown', (e) => { if (e.key === 'Enter') saveTagEditor(); });
+tagComposer.addEventListener('keydown', (e) => { if (e.key === 'Enter') saveTagEditor(); });
+tagUrl.addEventListener('keydown', (e) => { if (e.key === 'Enter') saveTagEditor(); });
+tagEncodedBy.addEventListener('keydown', (e) => { if (e.key === 'Enter') saveTagEditor(); });
+GENRES.forEach(g => { const opt = document.createElement('option'); opt.value = g; genreList.appendChild(opt); });
sortSelect.addEventListener('change', () => {
if (sortSelect.value === sortField) { sortAsc = !sortAsc; }
else { sortField = sortSelect.value; sortAsc = true; }