Add Music section: hub, template, build pipeline, and homepage wire
Music/build.py parses *-music.md files and generates collection pages supporting YouTube video embeds (lazy IntersectionObserver) and FileBrowser audio cards with floating media player (prev/next/auto-advance). Music star node wired in index.html. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -45,3 +45,6 @@
|
|||||||
- **Shortstraw \- Cold Shoulder \[Music Video 720p\] \- YouTube**
|
- **Shortstraw \- Cold Shoulder \[Music Video 720p\] \- YouTube**
|
||||||
- URL: https://www.youtube.com/watch?v=pIGIW3h9LpU
|
- URL: https://www.youtube.com/watch?v=pIGIW3h9LpU
|
||||||
|
|
||||||
|
- **Dammit - Dwonlaod \ - Free On File Browser**
|
||||||
|
- URL: https://files.exopraxist.org/share/KeQTyi9r
|
||||||
|
|
||||||
|
|||||||
769
Music/build.py
Normal file
769
Music/build.py
Normal file
@@ -0,0 +1,769 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Music build script.
|
||||||
|
|
||||||
|
For each *-music.md in this folder:
|
||||||
|
- Parse title, optional first-load lightbox, and track entries
|
||||||
|
- Two entry types detected by URL:
|
||||||
|
YouTube → youtube.com/watch?v=VIDEO_ID → video embed card
|
||||||
|
Audio → files.exopraxist.org/share/TOKEN → audio card + floating player
|
||||||
|
- Generate a self-contained HTML page
|
||||||
|
|
||||||
|
Then update music-hub.html subtitle with current totals.
|
||||||
|
|
||||||
|
Output filenames: strip '-music' suffix, lowercase the stem.
|
||||||
|
OldMusicandMVs-music.md → oldmusicandmvs.html
|
||||||
|
|
||||||
|
Run after adding or editing any *-music.md file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
from html import escape
|
||||||
|
|
||||||
|
HERE = Path(__file__).parent
|
||||||
|
HUB = HERE / "music-hub.html"
|
||||||
|
|
||||||
|
ALLOW = (
|
||||||
|
"accelerometer; autoplay; clipboard-write; "
|
||||||
|
"encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ── MD parsing ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def slug_from_path(path: Path) -> str:
|
||||||
|
stem = path.stem
|
||||||
|
stem = re.sub(r'-music$', '', stem, flags=re.IGNORECASE)
|
||||||
|
return stem.lower().replace('-', '').replace('_', '').replace(' ', '')
|
||||||
|
|
||||||
|
|
||||||
|
def parse_md(path: Path) -> dict:
|
||||||
|
text = path.read_text(encoding='utf-8')
|
||||||
|
|
||||||
|
m = re.search(r'^#\s+(.+)$', text, re.MULTILINE)
|
||||||
|
title = m.group(1).strip() if m else path.stem
|
||||||
|
|
||||||
|
m = re.search(r'>>on-first-load\s+-\s+lightbox\s+-\s+"([^"]+)"', text)
|
||||||
|
lightbox = m.group(1).strip() if m else None
|
||||||
|
|
||||||
|
entries = []
|
||||||
|
for m in re.finditer(
|
||||||
|
r'-\s+\*\*(.+?)\*\*\s*\n\s+-\s+URL:\s+(https://[^\s]+)',
|
||||||
|
text
|
||||||
|
):
|
||||||
|
name = m.group(1).strip()
|
||||||
|
# Strip trailing \- Source label from name (e.g. "Title \- YouTube")
|
||||||
|
name = re.sub(r'\s*\\-\s*.+$', '', name).strip()
|
||||||
|
url = m.group(2).strip()
|
||||||
|
|
||||||
|
if 'youtube.com/watch' in url:
|
||||||
|
vid = re.search(r'[?&]v=([A-Za-z0-9_-]+)', url)
|
||||||
|
if vid:
|
||||||
|
entries.append({'type': 'youtube', 'name': name,
|
||||||
|
'video_id': vid.group(1)})
|
||||||
|
elif 'files.exopraxist.org/share/' in url:
|
||||||
|
token = url.rstrip('/').split('/')[-1]
|
||||||
|
entries.append({'type': 'audio', 'name': name,
|
||||||
|
'share_url': url,
|
||||||
|
'direct_url': f"https://files.exopraxist.org/api/public/dl/{token}"})
|
||||||
|
|
||||||
|
return {'title': title, 'lightbox': lightbox, 'entries': entries}
|
||||||
|
|
||||||
|
|
||||||
|
# ── HTML blocks ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def build_lightbox_html(text: str, slug: str) -> tuple[str, str]:
|
||||||
|
if not text:
|
||||||
|
return '', ''
|
||||||
|
|
||||||
|
# Preserve — JL attribution if present at the end
|
||||||
|
body = text
|
||||||
|
attribution = ''
|
||||||
|
m = re.search(r'\s*-\s*JL\s*$', text)
|
||||||
|
if m:
|
||||||
|
body = text[:m.start()].strip()
|
||||||
|
attribution = '<p class="lightbox-attribution">— JL</p>'
|
||||||
|
|
||||||
|
safe_body = escape(body)
|
||||||
|
|
||||||
|
div = f"""
|
||||||
|
<div id="lightbox">
|
||||||
|
<div class="lightbox-content">
|
||||||
|
<span class="lightbox-close">×</span>
|
||||||
|
<p class="lightbox-quote">{safe_body}</p>
|
||||||
|
{attribution}
|
||||||
|
<span class="lightbox-hint">CLICK ANYWHERE TO DISMISS</span>
|
||||||
|
</div>
|
||||||
|
</div>"""
|
||||||
|
|
||||||
|
js = f"""
|
||||||
|
const lightbox = document.getElementById('lightbox');
|
||||||
|
if (!localStorage.getItem('music-{slug}-seen')) {{
|
||||||
|
lightbox.style.display = 'flex';
|
||||||
|
localStorage.setItem('music-{slug}-seen', '1');
|
||||||
|
}}
|
||||||
|
const closeLightbox = () => {{ lightbox.style.display = 'none'; }};
|
||||||
|
lightbox.addEventListener('click', (e) => {{
|
||||||
|
if (e.target === lightbox) closeLightbox();
|
||||||
|
}});
|
||||||
|
document.querySelector('.lightbox-close').addEventListener('click', closeLightbox);
|
||||||
|
document.addEventListener('keydown', (e) => {{
|
||||||
|
if (e.key === 'Escape') closeLightbox();
|
||||||
|
}});
|
||||||
|
"""
|
||||||
|
return div, js
|
||||||
|
|
||||||
|
|
||||||
|
def build_video_card(entry: dict) -> str:
|
||||||
|
embed_url = f"https://www.youtube.com/embed/{entry['video_id']}"
|
||||||
|
title_esc = escape(entry['name'])
|
||||||
|
return f"""
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">{title_esc}</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe
|
||||||
|
data-src="{embed_url}"
|
||||||
|
allow="{ALLOW}"
|
||||||
|
allowfullscreen>
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
</div>"""
|
||||||
|
|
||||||
|
|
||||||
|
def build_audio_card(entry: dict, audio_index: int) -> str:
|
||||||
|
title_esc = escape(entry['name'])
|
||||||
|
direct_url = entry['direct_url']
|
||||||
|
share_url = entry['share_url']
|
||||||
|
return f"""
|
||||||
|
<div class="card" id="track-{audio_index}">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2 class="card-title">{title_esc}</h2>
|
||||||
|
</div>
|
||||||
|
<div class="audio-card">
|
||||||
|
<div class="audio-controls">
|
||||||
|
<button class="play-btn"
|
||||||
|
data-src="{direct_url}"
|
||||||
|
data-title="{title_esc}">▶</button>
|
||||||
|
<div class="audio-info">
|
||||||
|
<a href="{share_url}" target="_blank" rel="noopener"
|
||||||
|
class="download-link">⬇ DOWNLOAD</a>
|
||||||
|
<div class="progress-container">
|
||||||
|
<div class="progress-bar"></div>
|
||||||
|
</div>
|
||||||
|
<div class="duration-meta">
|
||||||
|
<span class="current-time">0:00</span>
|
||||||
|
<span class="total-time">--:--</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>"""
|
||||||
|
|
||||||
|
|
||||||
|
def build_entries_html(entries: list) -> str:
|
||||||
|
parts = []
|
||||||
|
audio_idx = 1
|
||||||
|
for e in entries:
|
||||||
|
if e['type'] == 'youtube':
|
||||||
|
parts.append(build_video_card(e))
|
||||||
|
else:
|
||||||
|
parts.append(build_audio_card(e, audio_idx))
|
||||||
|
audio_idx += 1
|
||||||
|
return '\n'.join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
def build_stagger_css(count: int) -> str:
|
||||||
|
lines = []
|
||||||
|
for i in range(1, min(count, 12) + 1):
|
||||||
|
lines.append(
|
||||||
|
f" .card:nth-child({i}) {{ animation-delay: {(i-1)*50}ms; }}"
|
||||||
|
)
|
||||||
|
return '\n'.join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Full page generation ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def generate_html(data: dict, slug: str) -> str:
|
||||||
|
title = data['title']
|
||||||
|
entries = data['entries']
|
||||||
|
count = len(entries)
|
||||||
|
has_audio = any(e['type'] == 'audio' for e in entries)
|
||||||
|
title_upper = title.upper()
|
||||||
|
count_label = f"{count} TRACK{'S' if count != 1 else ''}"
|
||||||
|
|
||||||
|
lightbox_div, lightbox_js = build_lightbox_html(data['lightbox'], slug)
|
||||||
|
entries_html = build_entries_html(entries)
|
||||||
|
stagger_css = build_stagger_css(count)
|
||||||
|
|
||||||
|
lightbox_css = """
|
||||||
|
/* --- Lightbox --- */
|
||||||
|
#lightbox {
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0;
|
||||||
|
width: 100%; height: 100%;
|
||||||
|
background: rgba(4, 6, 11, 0.9);
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
padding: 2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.lightbox-content {
|
||||||
|
background: #0c0a08;
|
||||||
|
border: 2px solid var(--mu-red);
|
||||||
|
border-top: 8px solid var(--mu-orange);
|
||||||
|
max-width: 600px;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 2.5rem;
|
||||||
|
position: relative;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.lightbox-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5rem; right: 0.75rem;
|
||||||
|
font-size: 2rem;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.lightbox-quote {
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: var(--text-warm);
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
padding-right: 1.5rem;
|
||||||
|
}
|
||||||
|
.lightbox-attribution {
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.lightbox-hint {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
color: rgba(232, 213, 184, 0.4);
|
||||||
|
}
|
||||||
|
""" if data['lightbox'] else ''
|
||||||
|
|
||||||
|
audio_css = """
|
||||||
|
/* --- Audio Card --- */
|
||||||
|
.audio-card {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
.audio-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
.play-btn {
|
||||||
|
background: none;
|
||||||
|
border: 2px solid var(--mu-orange);
|
||||||
|
color: var(--mu-orange);
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition);
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
.play-btn:hover {
|
||||||
|
background: var(--mu-orange);
|
||||||
|
color: var(--bg-void);
|
||||||
|
}
|
||||||
|
.audio-info { flex-grow: 1; }
|
||||||
|
.download-link {
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
.download-link:hover { text-decoration: underline; }
|
||||||
|
.progress-container {
|
||||||
|
height: 4px;
|
||||||
|
background: var(--mu-red);
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.progress-bar {
|
||||||
|
height: 100%;
|
||||||
|
background: var(--mu-orange);
|
||||||
|
width: 0%;
|
||||||
|
transition: width 100ms linear;
|
||||||
|
}
|
||||||
|
.duration-meta {
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: rgba(232, 213, 184, 0.5);
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Floating Player --- */
|
||||||
|
.floating-player {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0; left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 64px;
|
||||||
|
background: var(--bg-void);
|
||||||
|
border-top: 2px solid var(--mu-red);
|
||||||
|
z-index: 500;
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 1rem;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
.floating-player.active { display: flex; }
|
||||||
|
.fp-info { flex: 1; min-width: 0; }
|
||||||
|
.fp-title {
|
||||||
|
font-family: 'Rancho', cursive;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.fp-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
.fp-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
font-family: 'Rambla', sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
.fp-btn:hover { color: var(--text-warm); }
|
||||||
|
.fp-progress-wrap {
|
||||||
|
position: absolute;
|
||||||
|
top: -2px; left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--mu-red);
|
||||||
|
}
|
||||||
|
.fp-progress-fill {
|
||||||
|
height: 100%;
|
||||||
|
background: var(--mu-orange);
|
||||||
|
width: 0%;
|
||||||
|
transition: width 100ms linear;
|
||||||
|
}
|
||||||
|
""" if has_audio else ''
|
||||||
|
|
||||||
|
floating_player_html = """
|
||||||
|
<div class="floating-player">
|
||||||
|
<div class="fp-progress-wrap"><div class="fp-progress-fill"></div></div>
|
||||||
|
<div class="fp-info"><div class="fp-title">No Track Selected</div></div>
|
||||||
|
<div class="fp-controls">
|
||||||
|
<button class="fp-btn" id="fp-prev">◀◀</button>
|
||||||
|
<button class="fp-btn" id="fp-play">▶</button>
|
||||||
|
<button class="fp-btn" id="fp-next">▶▶</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<audio id="main-audio"></audio>""" if has_audio else ''
|
||||||
|
|
||||||
|
audio_js = """
|
||||||
|
// --- Audio Logic ---
|
||||||
|
const audio = document.getElementById('main-audio');
|
||||||
|
const floatingPlayer = document.querySelector('.floating-player');
|
||||||
|
const fpPlayBtn = document.getElementById('fp-play');
|
||||||
|
const fpTitle = document.querySelector('.fp-title');
|
||||||
|
const fpFill = document.querySelector('.fp-progress-fill');
|
||||||
|
let currentTrackBtn = null;
|
||||||
|
|
||||||
|
const formatTime = (t) => {
|
||||||
|
const m = Math.floor(t / 60);
|
||||||
|
const s = Math.floor(t % 60);
|
||||||
|
return `${m}:${s.toString().padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateUI = () => {
|
||||||
|
const playing = !audio.paused;
|
||||||
|
const icon = playing ? '▐▐' : '▶';
|
||||||
|
if (currentTrackBtn) currentTrackBtn.textContent = icon;
|
||||||
|
fpPlayBtn.textContent = icon;
|
||||||
|
if (playing) {
|
||||||
|
floatingPlayer.classList.add('active');
|
||||||
|
document.body.classList.add('player-active');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.querySelectorAll('.play-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
const src = btn.dataset.src;
|
||||||
|
const title = btn.dataset.title;
|
||||||
|
if (audio.getAttribute('data-loaded') === src) {
|
||||||
|
if (audio.paused) audio.play(); else audio.pause();
|
||||||
|
} else {
|
||||||
|
if (currentTrackBtn) currentTrackBtn.textContent = '▶';
|
||||||
|
audio.src = src;
|
||||||
|
audio.setAttribute('data-loaded', src);
|
||||||
|
fpTitle.textContent = title;
|
||||||
|
currentTrackBtn = btn;
|
||||||
|
audio.play();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
fpPlayBtn.addEventListener('click', () => {
|
||||||
|
if (audio.paused) audio.play(); else audio.pause();
|
||||||
|
});
|
||||||
|
|
||||||
|
audio.addEventListener('play', updateUI);
|
||||||
|
audio.addEventListener('pause', updateUI);
|
||||||
|
|
||||||
|
audio.addEventListener('timeupdate', () => {
|
||||||
|
if (!audio.duration) return;
|
||||||
|
const pct = (audio.currentTime / audio.duration) * 100;
|
||||||
|
if (fpFill) fpFill.style.width = pct + '%';
|
||||||
|
if (currentTrackBtn) {
|
||||||
|
const card = currentTrackBtn.closest('.audio-card');
|
||||||
|
const bar = card.querySelector('.progress-bar');
|
||||||
|
const cur = card.querySelector('.current-time');
|
||||||
|
if (bar) bar.style.width = pct + '%';
|
||||||
|
if (cur) cur.textContent = formatTime(audio.currentTime);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
audio.addEventListener('loadedmetadata', () => {
|
||||||
|
if (currentTrackBtn) {
|
||||||
|
const card = currentTrackBtn.closest('.audio-card');
|
||||||
|
const tot = card.querySelector('.total-time');
|
||||||
|
if (tot) tot.textContent = formatTime(audio.duration);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
audio.addEventListener('ended', () => {
|
||||||
|
const allBtns = [...document.querySelectorAll('.play-btn')];
|
||||||
|
const idx = allBtns.indexOf(currentTrackBtn);
|
||||||
|
const next = allBtns[idx + 1];
|
||||||
|
if (next) {
|
||||||
|
next.click();
|
||||||
|
} else {
|
||||||
|
audio.pause();
|
||||||
|
if (currentTrackBtn) currentTrackBtn.textContent = '▶';
|
||||||
|
fpPlayBtn.textContent = '▶';
|
||||||
|
floatingPlayer.classList.remove('active');
|
||||||
|
document.body.classList.remove('player-active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('fp-prev').addEventListener('click', () => {
|
||||||
|
const all = [...document.querySelectorAll('.play-btn')];
|
||||||
|
const prev = all[all.indexOf(currentTrackBtn) - 1];
|
||||||
|
if (prev) prev.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('fp-next').addEventListener('click', () => {
|
||||||
|
const all = [...document.querySelectorAll('.play-btn')];
|
||||||
|
const next = all[all.indexOf(currentTrackBtn) + 1];
|
||||||
|
if (next) next.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.progress-container').forEach(bar => {
|
||||||
|
bar.addEventListener('click', (e) => {
|
||||||
|
const rect = bar.getBoundingClientRect();
|
||||||
|
audio.currentTime = ((e.clientX - rect.left) / rect.width) * audio.duration;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
""" if has_audio else ''
|
||||||
|
|
||||||
|
return f"""<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{escape(title)} | MUSIC</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Rancho&family=Rambla:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
:root {{
|
||||||
|
--bg-void: #04060b;
|
||||||
|
--text-warm: #e8d5b8;
|
||||||
|
--mu-red: #8b2020;
|
||||||
|
--mu-orange: #ff7f3f;
|
||||||
|
--mu-red-glow: rgba(139, 32, 32, 0.25);
|
||||||
|
--mu-orange-glow: rgba(255, 127, 63, 0.25);
|
||||||
|
--transition: 100ms ease;
|
||||||
|
}}
|
||||||
|
|
||||||
|
* {{ box-sizing: border-box; margin: 0; padding: 0; border-radius: 0; }}
|
||||||
|
|
||||||
|
body {{
|
||||||
|
background-color: var(--bg-void);
|
||||||
|
color: var(--text-warm);
|
||||||
|
font-family: 'Rambla', sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
padding-top: 64px;
|
||||||
|
}}
|
||||||
|
|
||||||
|
body.player-active {{ padding-bottom: 64px; }}
|
||||||
|
|
||||||
|
/* --- Sticky header --- */
|
||||||
|
.sticky-header {{
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0; right: 0;
|
||||||
|
z-index: 100;
|
||||||
|
background: var(--bg-void);
|
||||||
|
border-bottom: 2px solid var(--mu-red);
|
||||||
|
padding: 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.header-title {{
|
||||||
|
font-family: 'Rancho', cursive;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.header-nav {{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.nav-link {{
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: var(--transition);
|
||||||
|
}}
|
||||||
|
|
||||||
|
.nav-link:hover {{ color: #ff9f6f; }}
|
||||||
|
|
||||||
|
.count {{
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
color: rgba(232, 213, 184, 0.5);
|
||||||
|
}}
|
||||||
|
|
||||||
|
.menu-toggle {{
|
||||||
|
display: none;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
}}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {{
|
||||||
|
.menu-toggle {{ display: block; }}
|
||||||
|
.header-nav {{
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%; right: 0;
|
||||||
|
background: var(--bg-void);
|
||||||
|
border: 2px solid var(--mu-red);
|
||||||
|
border-top: none;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 1rem;
|
||||||
|
gap: 1rem;
|
||||||
|
width: 200px;
|
||||||
|
}}
|
||||||
|
.header-nav.open {{ display: flex; }}
|
||||||
|
}}
|
||||||
|
{lightbox_css}{audio_css}
|
||||||
|
/* --- Cards --- */
|
||||||
|
@keyframes cardIn {{
|
||||||
|
from {{ opacity: 0; transform: translateY(16px); }}
|
||||||
|
to {{ opacity: 1; transform: translateY(0); }}
|
||||||
|
}}
|
||||||
|
|
||||||
|
main {{
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 0 1rem 4rem;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.section-title {{
|
||||||
|
font-family: 'Rancho', cursive;
|
||||||
|
font-size: 3rem;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.card {{
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
border: 2px solid var(--mu-red);
|
||||||
|
background: rgba(139, 32, 32, 0.05);
|
||||||
|
transition: border-color var(--transition), box-shadow var(--transition);
|
||||||
|
animation: cardIn 320ms ease both;
|
||||||
|
}}
|
||||||
|
|
||||||
|
{stagger_css}
|
||||||
|
|
||||||
|
.card:hover {{
|
||||||
|
border-color: var(--mu-orange);
|
||||||
|
box-shadow: 0 0 8px 2px var(--mu-orange-glow);
|
||||||
|
}}
|
||||||
|
|
||||||
|
.card-header {{
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border-bottom: 1px solid var(--mu-red);
|
||||||
|
}}
|
||||||
|
|
||||||
|
.card-title {{
|
||||||
|
font-family: 'Rancho', cursive;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
}}
|
||||||
|
|
||||||
|
.video-container {{
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 56.25%;
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #000;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.video-container iframe {{
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: 0;
|
||||||
|
width: 100%; height: 100%;
|
||||||
|
border: 0;
|
||||||
|
}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{lightbox_div}
|
||||||
|
<header class="sticky-header">
|
||||||
|
<div class="header-title">{escape(title_upper)}</div>
|
||||||
|
<button class="menu-toggle">☰</button>
|
||||||
|
<nav class="header-nav">
|
||||||
|
<a href="music-hub.html" class="nav-link">← MUSIC</a>
|
||||||
|
<span class="count">{count_label}</span>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<h1 class="section-title">{escape(title.upper())}</h1>
|
||||||
|
{entries_html}
|
||||||
|
</main>
|
||||||
|
{floating_player_html}
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {{
|
||||||
|
{lightbox_js}
|
||||||
|
// --- Mobile nav ---
|
||||||
|
const menuToggle = document.querySelector('.menu-toggle');
|
||||||
|
const nav = document.querySelector('.header-nav');
|
||||||
|
menuToggle.addEventListener('click', (e) => {{
|
||||||
|
e.stopPropagation();
|
||||||
|
nav.classList.toggle('open');
|
||||||
|
}});
|
||||||
|
document.addEventListener('click', (e) => {{
|
||||||
|
if (!nav.contains(e.target) && e.target !== menuToggle)
|
||||||
|
nav.classList.remove('open');
|
||||||
|
}});
|
||||||
|
|
||||||
|
// --- Lazy-load iframes ---
|
||||||
|
const observer = new IntersectionObserver((entries, obs) => {{
|
||||||
|
entries.forEach(entry => {{
|
||||||
|
if (entry.isIntersecting) {{
|
||||||
|
const iframe = entry.target;
|
||||||
|
if (iframe.dataset.src) {{
|
||||||
|
iframe.src = iframe.dataset.src;
|
||||||
|
iframe.removeAttribute('data-src');
|
||||||
|
obs.unobserve(iframe);
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
}});
|
||||||
|
}}, {{ rootMargin: '200px' }});
|
||||||
|
document.querySelectorAll('iframe[data-src]').forEach(f => observer.observe(f));
|
||||||
|
{audio_js}
|
||||||
|
}});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# ── Hub subtitle update ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def update_hub_subtitle(collection_count: int, entry_total: int):
|
||||||
|
hub_text = HUB.read_text(encoding='utf-8')
|
||||||
|
subtitle = (
|
||||||
|
f'<span class="subtitle">'
|
||||||
|
f'{collection_count} COLLECTION{"S" if collection_count != 1 else ""}'
|
||||||
|
f' · PERSONAL ARCHIVE'
|
||||||
|
f' · {entry_total} TRACK{"S" if entry_total != 1 else ""}'
|
||||||
|
f'</span>'
|
||||||
|
)
|
||||||
|
hub_text = re.sub(
|
||||||
|
r'<!-- SUBTITLE-START -->.*?<!-- SUBTITLE-END -->',
|
||||||
|
f'<!-- SUBTITLE-START -->{subtitle}<!-- SUBTITLE-END -->',
|
||||||
|
hub_text,
|
||||||
|
flags=re.DOTALL
|
||||||
|
)
|
||||||
|
HUB.write_text(hub_text, encoding='utf-8')
|
||||||
|
|
||||||
|
|
||||||
|
# ── Main ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def main():
|
||||||
|
md_files = sorted(HERE.glob('*-music.md'))
|
||||||
|
if not md_files:
|
||||||
|
print("No *-music.md files found.")
|
||||||
|
return
|
||||||
|
|
||||||
|
total_entries = 0
|
||||||
|
|
||||||
|
for md_path in md_files:
|
||||||
|
slug = slug_from_path(md_path)
|
||||||
|
out = HERE / f"{slug}.html"
|
||||||
|
data = parse_md(md_path)
|
||||||
|
count = len(data['entries'])
|
||||||
|
|
||||||
|
if count == 0:
|
||||||
|
print(f" SKIP {md_path.name} — no entries found")
|
||||||
|
continue
|
||||||
|
|
||||||
|
audio_count = sum(1 for e in data['entries'] if e['type'] == 'audio')
|
||||||
|
video_count = count - audio_count
|
||||||
|
|
||||||
|
html = generate_html(data, slug)
|
||||||
|
out.write_text(html, encoding='utf-8')
|
||||||
|
total_entries += count
|
||||||
|
print(f" BUILD {out.name} — {data['title']} ({video_count} videos, {audio_count} audio)")
|
||||||
|
|
||||||
|
update_hub_subtitle(len(md_files), total_entries)
|
||||||
|
print(f"\nUpdated hub — {len(md_files)} collections, {total_entries} tracks total")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
186
Music/music-hub.html
Normal file
186
Music/music-hub.html
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Music Hub — Singular Particular Space</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Rancho&family=Rambla:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg-void: #04060b;
|
||||||
|
--text-warm: #e8d5b8;
|
||||||
|
--mu-red: #8b2020;
|
||||||
|
--mu-orange: #ff7f3f;
|
||||||
|
--mu-red-glow: rgba(139, 32, 32, 0.25);
|
||||||
|
--mu-orange-glow: rgba(255, 127, 63, 0.25);
|
||||||
|
--transition: 100ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--bg-void);
|
||||||
|
color: var(--text-warm);
|
||||||
|
font-family: 'Rambla', sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
border-bottom: 2px solid var(--mu-red);
|
||||||
|
padding-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-family: 'Rancho', cursive;
|
||||||
|
font-size: clamp(3rem, 10vw, 5rem);
|
||||||
|
color: var(--mu-orange);
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
color: rgba(232, 213, 184, 0.6);
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-link {
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-link:hover {
|
||||||
|
color: #ff9f6f; /* lighter tint of mu-orange */
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
display: block;
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
border: 2px solid var(--mu-red);
|
||||||
|
transition: var(--transition);
|
||||||
|
background: rgba(139, 32, 32, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
border-color: var(--mu-orange);
|
||||||
|
box-shadow: 0 0 8px var(--mu-orange-glow);
|
||||||
|
background: rgba(255, 127, 63, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
background: var(--mu-red);
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover .card-header {
|
||||||
|
background: var(--mu-orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-family: 'Rancho', cursive;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--text-warm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover .card-title {
|
||||||
|
color: var(--bg-void);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body {
|
||||||
|
padding: 1.5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-meta {
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
color: rgba(232, 213, 184, 0.6);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-desc {
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-warm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<a href="../index.html" class="back-link">← SPACE</a>
|
||||||
|
<h1>MUSIC</h1>
|
||||||
|
<!-- SUBTITLE-START --><span class="subtitle">1 COLLECTION · PERSONAL ARCHIVE · 14 TRACKS</span><!-- SUBTITLE-END -->
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="grid">
|
||||||
|
<!-- Real Card -->
|
||||||
|
<a href="music-template.html" class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2 class="card-title">Old Music and Music Videos</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<span class="card-meta">14 ENTRIES</span>
|
||||||
|
<p class="card-desc">An archive of old recordings and music videos directed by JL.</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Placeholder 1 -->
|
||||||
|
<a href="#" class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2 class="card-title">Live Sessions</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<span class="card-meta">0 ENTRIES</span>
|
||||||
|
<p class="card-desc">Upcoming recordings from live performances and studio sessions.</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Placeholder 2 -->
|
||||||
|
<a href="#" class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2 class="card-title">Experimental Tapes</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<span class="card-meta">0 ENTRIES</span>
|
||||||
|
<p class="card-desc">Found sounds, field recordings, and sonic experiments.</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
703
Music/music-template.html
Normal file
703
Music/music-template.html
Normal file
@@ -0,0 +1,703 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Old Music and Music Videos — Singular Particular Space</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Rancho&family=Rambla:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg-void: #04060b;
|
||||||
|
--text-warm: #e8d5b8;
|
||||||
|
--mu-red: #8b2020;
|
||||||
|
--mu-orange: #ff7f3f;
|
||||||
|
--mu-red-glow: rgba(139, 32, 32, 0.25);
|
||||||
|
--mu-orange-glow: rgba(255, 127, 63, 0.25);
|
||||||
|
--transition: 100ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--bg-void);
|
||||||
|
color: var(--text-warm);
|
||||||
|
font-family: 'Rambla', sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 0;
|
||||||
|
padding-top: 64px;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.player-active {
|
||||||
|
padding-bottom: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Header --- */
|
||||||
|
.sticky-header {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 100;
|
||||||
|
background: var(--bg-void);
|
||||||
|
border-bottom: 2px solid var(--mu-red);
|
||||||
|
padding: 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
font-family: 'Rancho', cursive;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-nav {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link {
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link:hover {
|
||||||
|
color: #ff9f6f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.count {
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
color: rgba(232, 213, 184, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-toggle {
|
||||||
|
display: none;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Main Layout --- */
|
||||||
|
main {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 3rem auto;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-family: 'Rancho', cursive;
|
||||||
|
font-size: 3rem;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Cards --- */
|
||||||
|
.card {
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
border: 2px solid var(--mu-red);
|
||||||
|
background: rgba(139, 32, 32, 0.05);
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
border-color: var(--mu-orange);
|
||||||
|
box-shadow: 0 0 8px var(--mu-orange-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
padding: 1rem;
|
||||||
|
border-bottom: 1px solid var(--mu-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-family: 'Rancho', cursive;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container {
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 56.25%; /* 16:9 */
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container iframe {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Audio Card --- */
|
||||||
|
.audio-card {
|
||||||
|
padding: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-btn {
|
||||||
|
background: none;
|
||||||
|
border: 2px solid var(--mu-orange);
|
||||||
|
color: var(--mu-orange);
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-btn:hover {
|
||||||
|
background: var(--mu-orange);
|
||||||
|
color: var(--bg-void);
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio-info {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-link {
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-container {
|
||||||
|
height: 4px;
|
||||||
|
background: var(--mu-red);
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
height: 100%;
|
||||||
|
background: var(--mu-orange);
|
||||||
|
width: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.duration-meta {
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: rgba(232, 213, 184, 0.5);
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Lightbox --- */
|
||||||
|
#lightbox {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(4, 6, 11, 0.9);
|
||||||
|
display: none; /* JS toggles flex */
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-content {
|
||||||
|
background: #0c0a08;
|
||||||
|
border: 2px solid var(--mu-red);
|
||||||
|
border-top: 8px solid var(--mu-orange);
|
||||||
|
max-width: 600px;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 2.5rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5rem;
|
||||||
|
right: 0.75rem;
|
||||||
|
font-size: 2rem;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-quote {
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: var(--text-warm);
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-attribution {
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox-hint {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
color: rgba(232, 213, 184, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Floating Player --- */
|
||||||
|
.floating-player {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 64px;
|
||||||
|
background: var(--bg-void);
|
||||||
|
border-top: 2px solid var(--mu-red);
|
||||||
|
z-index: 500;
|
||||||
|
display: none; /* JS toggles flex */
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 1rem;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-player.active {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fp-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fp-title {
|
||||||
|
font-family: 'Rancho', cursive;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fp-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fp-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
font-family: 'Rambla', sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fp-progress-wrap {
|
||||||
|
position: absolute;
|
||||||
|
top: -2px;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Mobile --- */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.menu-toggle { display: block; }
|
||||||
|
.header-nav {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
right: 0;
|
||||||
|
background: var(--bg-void);
|
||||||
|
border: 2px solid var(--mu-red);
|
||||||
|
border-top: none;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 1rem;
|
||||||
|
gap: 1rem;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
.header-nav.open { display: flex; }
|
||||||
|
.section-title { font-size: 2.2rem; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="lightbox">
|
||||||
|
<div class="lightbox-content">
|
||||||
|
<span class="lightbox-close">×</span>
|
||||||
|
<p class="lightbox-quote">
|
||||||
|
I much prefer making music live. But here are some old recordings. Oh and some music videos that I made for better recording artists than me at the time. At time of writing, there are inklings of thinkings about returning to making music. I'm considering it. We'll see how it plays out.
|
||||||
|
</p>
|
||||||
|
<p class="lightbox-attribution">— JL</p>
|
||||||
|
<span class="lightbox-hint">CLICK ANYWHERE TO DISMISS</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<header class="sticky-header">
|
||||||
|
<div class="header-title">OLD MUSIC AND MUSIC VIDEOS — THE ARCHIVE</div>
|
||||||
|
<button class="menu-toggle">☰</button>
|
||||||
|
<nav class="header-nav">
|
||||||
|
<a href="music-hub.html" class="nav-link">← MUSIC</a>
|
||||||
|
<span class="count">14 TRACKS</span>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<h1 class="section-title">THE ARCHIVE</h1>
|
||||||
|
|
||||||
|
<!-- Audio Card -->
|
||||||
|
<div class="card" id="track-1">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2 class="card-title">Dammit (Download — Free)</h2>
|
||||||
|
</div>
|
||||||
|
<div class="audio-card">
|
||||||
|
<div class="audio-controls">
|
||||||
|
<button class="play-btn" data-track-id="1" data-src="https://files.exopraxist.org/api/public/dl/KeQTyi9r" data-title="Dammit (Blink 182 Cover)">▶</button>
|
||||||
|
<div class="audio-info">
|
||||||
|
<a href="https://files.exopraxist.org/share/KeQTyi9r" target="_blank" class="download-link">⬇ DOWNLOAD</a>
|
||||||
|
<div class="progress-container">
|
||||||
|
<div class="progress-bar"></div>
|
||||||
|
</div>
|
||||||
|
<div class="duration-meta">
|
||||||
|
<span class="current-time">0:00</span>
|
||||||
|
<span class="total-time">--:--</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Video Cards -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">Sixteen by Melanie Kerr — A Music Video</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe data-src="https://www.youtube.com/embed/zmwv8NXMO6E" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">Good Girl by Melanie Kerr — A Music Video</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe data-src="https://www.youtube.com/embed/M4KCZr7Xx7A" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">042 Letter to My Sunrise (Demo Tape)</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe data-src="https://www.youtube.com/embed/lbhFd6I_CII" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">038 Trouble Done Bore Me Down</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe data-src="https://www.youtube.com/embed/fpm0nfMjm7M" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">036 That's All Right Mama</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe data-src="https://www.youtube.com/embed/N_8WL1GhW0s" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">035 Psycho Bch</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe data-src="https://www.youtube.com/embed/fsE03yW7M9Y" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">034 On the Curious Creatures Called People</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe data-src="https://www.youtube.com/embed/JJtnygYcs5o" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">033 Ode to Truth Kind of</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe data-src="https://www.youtube.com/embed/QVQl2uyUBGg" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">032 iYeza</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe data-src="https://www.youtube.com/embed/1rN4C828vR4" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">031 Dammit (Blink 182 Cover)</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe data-src="https://www.youtube.com/embed/YTZqMbIuGKk" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">030 An Irish Airman Forsees His Death</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe data-src="https://www.youtube.com/embed/wOe-KTlWX1Y" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">Trouble Done Bore Me Down</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe data-src="https://www.youtube.com/embed/BWJrOOYqRuM" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">Shortstraw — Cold Shoulder (Music Video 720p)</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe data-src="https://www.youtube.com/embed/pIGIW3h9LpU" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Floating Player -->
|
||||||
|
<div class="floating-player">
|
||||||
|
<div class="fp-progress-wrap">
|
||||||
|
<div class="progress-container">
|
||||||
|
<div class="progress-bar"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="fp-info">
|
||||||
|
<div class="fp-title">No Track Selected</div>
|
||||||
|
</div>
|
||||||
|
<div class="fp-controls">
|
||||||
|
<button class="fp-btn" id="fp-prev">◀◀</button>
|
||||||
|
<button class="fp-btn" id="fp-play">▶</button>
|
||||||
|
<button class="fp-btn" id="fp-next">▶▶</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<audio id="main-audio"></audio>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// --- Lightbox ---
|
||||||
|
const lightbox = document.getElementById('lightbox');
|
||||||
|
if (!localStorage.getItem('music-oldmusicandmvs-seen')) {
|
||||||
|
lightbox.style.display = 'flex';
|
||||||
|
localStorage.setItem('music-oldmusicandmvs-seen', '1');
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeLightbox = () => { lightbox.style.display = 'none'; };
|
||||||
|
|
||||||
|
lightbox.addEventListener('click', (e) => {
|
||||||
|
if (e.target === lightbox) closeLightbox();
|
||||||
|
});
|
||||||
|
document.querySelector('.lightbox-close').addEventListener('click', closeLightbox);
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Escape') closeLightbox();
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Mobile Menu ---
|
||||||
|
const menuToggle = document.querySelector('.menu-toggle');
|
||||||
|
const nav = document.querySelector('.header-nav');
|
||||||
|
menuToggle.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
nav.classList.toggle('open');
|
||||||
|
});
|
||||||
|
document.addEventListener('click', (e) => {
|
||||||
|
if (!nav.contains(e.target) && e.target !== menuToggle) {
|
||||||
|
nav.classList.remove('open');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Lazy Load Iframes ---
|
||||||
|
const iframeObserver = new IntersectionObserver((entries, obs) => {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
const iframe = entry.target;
|
||||||
|
if (iframe.dataset.src) {
|
||||||
|
iframe.src = iframe.dataset.src;
|
||||||
|
iframe.removeAttribute('data-src');
|
||||||
|
obs.unobserve(iframe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, { rootMargin: '200px' });
|
||||||
|
|
||||||
|
document.querySelectorAll('iframe').forEach(iframe => {
|
||||||
|
iframeObserver.observe(iframe);
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Audio Logic ---
|
||||||
|
const audio = document.getElementById('main-audio');
|
||||||
|
const floatingPlayer = document.querySelector('.floating-player');
|
||||||
|
const fpPlayBtn = document.getElementById('fp-play');
|
||||||
|
const fpTitle = document.querySelector('.fp-title');
|
||||||
|
const fpProgressBar = floatingPlayer.querySelector('.progress-bar');
|
||||||
|
|
||||||
|
let currentTrackBtn = null;
|
||||||
|
|
||||||
|
const updateUI = () => {
|
||||||
|
const isPlaying = !audio.paused;
|
||||||
|
const icon = isPlaying ? '▐▐' : '▶';
|
||||||
|
|
||||||
|
if (currentTrackBtn) currentTrackBtn.textContent = icon;
|
||||||
|
fpPlayBtn.textContent = icon;
|
||||||
|
|
||||||
|
if (isPlaying) {
|
||||||
|
floatingPlayer.classList.add('active');
|
||||||
|
document.body.classList.add('player-active');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatTime = (time) => {
|
||||||
|
const min = Math.floor(time / 60);
|
||||||
|
const sec = Math.floor(time % 60);
|
||||||
|
return `${min}:${sec.toString().padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
document.querySelectorAll('.play-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
const src = btn.dataset.src;
|
||||||
|
const title = btn.dataset.title;
|
||||||
|
|
||||||
|
if (audio.src === src) {
|
||||||
|
if (audio.paused) audio.play();
|
||||||
|
else audio.pause();
|
||||||
|
} else {
|
||||||
|
// Reset previous btn if any
|
||||||
|
if (currentTrackBtn) currentTrackBtn.textContent = '▶';
|
||||||
|
|
||||||
|
audio.src = src;
|
||||||
|
fpTitle.textContent = title;
|
||||||
|
currentTrackBtn = btn;
|
||||||
|
audio.play();
|
||||||
|
}
|
||||||
|
updateUI();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
fpPlayBtn.addEventListener('click', () => {
|
||||||
|
if (audio.paused) audio.play();
|
||||||
|
else audio.pause();
|
||||||
|
updateUI();
|
||||||
|
});
|
||||||
|
|
||||||
|
audio.addEventListener('timeupdate', () => {
|
||||||
|
const percent = (audio.currentTime / audio.duration) * 100;
|
||||||
|
document.querySelectorAll('.progress-bar').forEach(bar => {
|
||||||
|
bar.style.width = percent + '%';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update local time display if card is visible
|
||||||
|
if (currentTrackBtn) {
|
||||||
|
const card = currentTrackBtn.closest('.audio-card');
|
||||||
|
card.querySelector('.current-time').textContent = formatTime(audio.currentTime);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
audio.addEventListener('loadedmetadata', () => {
|
||||||
|
if (currentTrackBtn) {
|
||||||
|
const card = currentTrackBtn.closest('.audio-card');
|
||||||
|
card.querySelector('.total-time').textContent = formatTime(audio.duration);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sync floating player button on native play/pause events
|
||||||
|
audio.addEventListener('play', updateUI);
|
||||||
|
audio.addEventListener('pause', updateUI);
|
||||||
|
|
||||||
|
audio.addEventListener('ended', () => {
|
||||||
|
const allBtns = [...document.querySelectorAll('.play-btn')];
|
||||||
|
const idx = allBtns.indexOf(currentTrackBtn);
|
||||||
|
const next = allBtns[idx + 1];
|
||||||
|
if (next) {
|
||||||
|
next.click();
|
||||||
|
} else {
|
||||||
|
audio.pause();
|
||||||
|
if (currentTrackBtn) currentTrackBtn.textContent = '▶';
|
||||||
|
fpPlayBtn.textContent = '▶';
|
||||||
|
floatingPlayer.classList.remove('active');
|
||||||
|
document.body.classList.remove('player-active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('fp-prev').addEventListener('click', () => {
|
||||||
|
const allBtns = [...document.querySelectorAll('.play-btn')];
|
||||||
|
const idx = allBtns.indexOf(currentTrackBtn);
|
||||||
|
const prev = allBtns[idx - 1];
|
||||||
|
if (prev) prev.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('fp-next').addEventListener('click', () => {
|
||||||
|
const allBtns = [...document.querySelectorAll('.play-btn')];
|
||||||
|
const idx = allBtns.indexOf(currentTrackBtn);
|
||||||
|
const next = allBtns[idx + 1];
|
||||||
|
if (next) next.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Seek logic
|
||||||
|
document.querySelectorAll('.progress-container').forEach(container => {
|
||||||
|
container.addEventListener('click', (e) => {
|
||||||
|
const rect = container.getBoundingClientRect();
|
||||||
|
const pos = (e.clientX - rect.left) / rect.width;
|
||||||
|
audio.currentTime = pos * audio.duration;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
705
Music/oldmusicandmvs.html
Normal file
705
Music/oldmusicandmvs.html
Normal file
@@ -0,0 +1,705 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Old Music and Music Videos - The Archive | MUSIC</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Rancho&family=Rambla:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg-void: #04060b;
|
||||||
|
--text-warm: #e8d5b8;
|
||||||
|
--mu-red: #8b2020;
|
||||||
|
--mu-orange: #ff7f3f;
|
||||||
|
--mu-red-glow: rgba(139, 32, 32, 0.25);
|
||||||
|
--mu-orange-glow: rgba(255, 127, 63, 0.25);
|
||||||
|
--transition: 100ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; border-radius: 0; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--bg-void);
|
||||||
|
color: var(--text-warm);
|
||||||
|
font-family: 'Rambla', sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
padding-top: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.player-active { padding-bottom: 64px; }
|
||||||
|
|
||||||
|
/* --- Sticky header --- */
|
||||||
|
.sticky-header {
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0; right: 0;
|
||||||
|
z-index: 100;
|
||||||
|
background: var(--bg-void);
|
||||||
|
border-bottom: 2px solid var(--mu-red);
|
||||||
|
padding: 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
font-family: 'Rancho', cursive;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-nav {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link {
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link:hover { color: #ff9f6f; }
|
||||||
|
|
||||||
|
.count {
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
color: rgba(232, 213, 184, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-toggle {
|
||||||
|
display: none;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.menu-toggle { display: block; }
|
||||||
|
.header-nav {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%; right: 0;
|
||||||
|
background: var(--bg-void);
|
||||||
|
border: 2px solid var(--mu-red);
|
||||||
|
border-top: none;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 1rem;
|
||||||
|
gap: 1rem;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
.header-nav.open { display: flex; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Lightbox --- */
|
||||||
|
#lightbox {
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0;
|
||||||
|
width: 100%; height: 100%;
|
||||||
|
background: rgba(4, 6, 11, 0.9);
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
padding: 2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.lightbox-content {
|
||||||
|
background: #0c0a08;
|
||||||
|
border: 2px solid var(--mu-red);
|
||||||
|
border-top: 8px solid var(--mu-orange);
|
||||||
|
max-width: 600px;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 2.5rem;
|
||||||
|
position: relative;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.lightbox-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5rem; right: 0.75rem;
|
||||||
|
font-size: 2rem;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.lightbox-quote {
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: var(--text-warm);
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
padding-right: 1.5rem;
|
||||||
|
}
|
||||||
|
.lightbox-attribution {
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.lightbox-hint {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
color: rgba(232, 213, 184, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Audio Card --- */
|
||||||
|
.audio-card {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
.audio-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
.play-btn {
|
||||||
|
background: none;
|
||||||
|
border: 2px solid var(--mu-orange);
|
||||||
|
color: var(--mu-orange);
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition);
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
.play-btn:hover {
|
||||||
|
background: var(--mu-orange);
|
||||||
|
color: var(--bg-void);
|
||||||
|
}
|
||||||
|
.audio-info { flex-grow: 1; }
|
||||||
|
.download-link {
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
.download-link:hover { text-decoration: underline; }
|
||||||
|
.progress-container {
|
||||||
|
height: 4px;
|
||||||
|
background: var(--mu-red);
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.progress-bar {
|
||||||
|
height: 100%;
|
||||||
|
background: var(--mu-orange);
|
||||||
|
width: 0%;
|
||||||
|
transition: width 100ms linear;
|
||||||
|
}
|
||||||
|
.duration-meta {
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: rgba(232, 213, 184, 0.5);
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Floating Player --- */
|
||||||
|
.floating-player {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0; left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 64px;
|
||||||
|
background: var(--bg-void);
|
||||||
|
border-top: 2px solid var(--mu-red);
|
||||||
|
z-index: 500;
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 1rem;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
.floating-player.active { display: flex; }
|
||||||
|
.fp-info { flex: 1; min-width: 0; }
|
||||||
|
.fp-title {
|
||||||
|
font-family: 'Rancho', cursive;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.fp-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
.fp-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
font-family: 'Rambla', sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
.fp-btn:hover { color: var(--text-warm); }
|
||||||
|
.fp-progress-wrap {
|
||||||
|
position: absolute;
|
||||||
|
top: -2px; left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--mu-red);
|
||||||
|
}
|
||||||
|
.fp-progress-fill {
|
||||||
|
height: 100%;
|
||||||
|
background: var(--mu-orange);
|
||||||
|
width: 0%;
|
||||||
|
transition: width 100ms linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Cards --- */
|
||||||
|
@keyframes cardIn {
|
||||||
|
from { opacity: 0; transform: translateY(16px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 0 1rem 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-family: 'Rancho', cursive;
|
||||||
|
font-size: 3rem;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
border: 2px solid var(--mu-red);
|
||||||
|
background: rgba(139, 32, 32, 0.05);
|
||||||
|
transition: border-color var(--transition), box-shadow var(--transition);
|
||||||
|
animation: cardIn 320ms ease both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:nth-child(1) { animation-delay: 0ms; }
|
||||||
|
.card:nth-child(2) { animation-delay: 50ms; }
|
||||||
|
.card:nth-child(3) { animation-delay: 100ms; }
|
||||||
|
.card:nth-child(4) { animation-delay: 150ms; }
|
||||||
|
.card:nth-child(5) { animation-delay: 200ms; }
|
||||||
|
.card:nth-child(6) { animation-delay: 250ms; }
|
||||||
|
.card:nth-child(7) { animation-delay: 300ms; }
|
||||||
|
.card:nth-child(8) { animation-delay: 350ms; }
|
||||||
|
.card:nth-child(9) { animation-delay: 400ms; }
|
||||||
|
.card:nth-child(10) { animation-delay: 450ms; }
|
||||||
|
.card:nth-child(11) { animation-delay: 500ms; }
|
||||||
|
.card:nth-child(12) { animation-delay: 550ms; }
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
border-color: var(--mu-orange);
|
||||||
|
box-shadow: 0 0 8px 2px var(--mu-orange-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border-bottom: 1px solid var(--mu-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-family: 'Rancho', cursive;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
color: var(--mu-orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container {
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 56.25%;
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container iframe {
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: 0;
|
||||||
|
width: 100%; height: 100%;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="lightbox">
|
||||||
|
<div class="lightbox-content">
|
||||||
|
<span class="lightbox-close">×</span>
|
||||||
|
<p class="lightbox-quote">I much prefer making music live. But here are some old recordings. Oh and some music videos that I made for better recording artists than me at the time. At time of writing, there are inklings of thinkings about returning to making music. I'm considering it. We'll see how it plays out.</p>
|
||||||
|
<p class="lightbox-attribution">— JL</p>
|
||||||
|
<span class="lightbox-hint">CLICK ANYWHERE TO DISMISS</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<header class="sticky-header">
|
||||||
|
<div class="header-title">OLD MUSIC AND MUSIC VIDEOS - THE ARCHIVE</div>
|
||||||
|
<button class="menu-toggle">☰</button>
|
||||||
|
<nav class="header-nav">
|
||||||
|
<a href="music-hub.html" class="nav-link">← MUSIC</a>
|
||||||
|
<span class="count">14 TRACKS</span>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<h1 class="section-title">OLD MUSIC AND MUSIC VIDEOS - THE ARCHIVE</h1>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">Sixteen by Melanie Kerr</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe
|
||||||
|
data-src="https://www.youtube.com/embed/zmwv8NXMO6E"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen>
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">Good Girl by Melanie Kerr</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe
|
||||||
|
data-src="https://www.youtube.com/embed/M4KCZr7Xx7A"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen>
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">042 Letter to My Sunrise \(Demo Tape\)</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe
|
||||||
|
data-src="https://www.youtube.com/embed/lbhFd6I_CII"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen>
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">038 Trouble Done Bore Me Down</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe
|
||||||
|
data-src="https://www.youtube.com/embed/fpm0nfMjm7M"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen>
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">036 That's All Right Mama</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe
|
||||||
|
data-src="https://www.youtube.com/embed/N_8WL1GhW0s"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen>
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">035 Psycho Bch</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe
|
||||||
|
data-src="https://www.youtube.com/embed/fsE03yW7M9Y"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen>
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">034 On the Curious Creatures Called People</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe
|
||||||
|
data-src="https://www.youtube.com/embed/JJtnygYcs5o"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen>
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">033 Ode to Truth Kind of</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe
|
||||||
|
data-src="https://www.youtube.com/embed/QVQl2uyUBGg"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen>
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">032 iYeza</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe
|
||||||
|
data-src="https://www.youtube.com/embed/1rN4C828vR4"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen>
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">031 Dammit Blink 182 Cover</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe
|
||||||
|
data-src="https://www.youtube.com/embed/YTZqMbIuGKk"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen>
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">030 An Irish Airman Forsees his Death</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe
|
||||||
|
data-src="https://www.youtube.com/embed/wOe-KTlWX1Y"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen>
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">Trouble Done Bore Me Down</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe
|
||||||
|
data-src="https://www.youtube.com/embed/BWJrOOYqRuM"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen>
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header"><h2 class="card-title">Shortstraw</h2></div>
|
||||||
|
<div class="video-container">
|
||||||
|
<iframe
|
||||||
|
data-src="https://www.youtube.com/embed/pIGIW3h9LpU"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen>
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" id="track-1">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2 class="card-title">Dammit - Dwonlaod \ - Free On File Browser</h2>
|
||||||
|
</div>
|
||||||
|
<div class="audio-card">
|
||||||
|
<div class="audio-controls">
|
||||||
|
<button class="play-btn"
|
||||||
|
data-src="https://files.exopraxist.org/api/public/dl/KeQTyi9r"
|
||||||
|
data-title="Dammit - Dwonlaod \ - Free On File Browser">▶</button>
|
||||||
|
<div class="audio-info">
|
||||||
|
<a href="https://files.exopraxist.org/share/KeQTyi9r" target="_blank" rel="noopener"
|
||||||
|
class="download-link">⬇ DOWNLOAD</a>
|
||||||
|
<div class="progress-container">
|
||||||
|
<div class="progress-bar"></div>
|
||||||
|
</div>
|
||||||
|
<div class="duration-meta">
|
||||||
|
<span class="current-time">0:00</span>
|
||||||
|
<span class="total-time">--:--</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<div class="floating-player">
|
||||||
|
<div class="fp-progress-wrap"><div class="fp-progress-fill"></div></div>
|
||||||
|
<div class="fp-info"><div class="fp-title">No Track Selected</div></div>
|
||||||
|
<div class="fp-controls">
|
||||||
|
<button class="fp-btn" id="fp-prev">◀◀</button>
|
||||||
|
<button class="fp-btn" id="fp-play">▶</button>
|
||||||
|
<button class="fp-btn" id="fp-next">▶▶</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<audio id="main-audio"></audio>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
||||||
|
const lightbox = document.getElementById('lightbox');
|
||||||
|
if (!localStorage.getItem('music-oldmusicandmvs-seen')) {
|
||||||
|
lightbox.style.display = 'flex';
|
||||||
|
localStorage.setItem('music-oldmusicandmvs-seen', '1');
|
||||||
|
}
|
||||||
|
const closeLightbox = () => { lightbox.style.display = 'none'; };
|
||||||
|
lightbox.addEventListener('click', (e) => {
|
||||||
|
if (e.target === lightbox) closeLightbox();
|
||||||
|
});
|
||||||
|
document.querySelector('.lightbox-close').addEventListener('click', closeLightbox);
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Escape') closeLightbox();
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Mobile nav ---
|
||||||
|
const menuToggle = document.querySelector('.menu-toggle');
|
||||||
|
const nav = document.querySelector('.header-nav');
|
||||||
|
menuToggle.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
nav.classList.toggle('open');
|
||||||
|
});
|
||||||
|
document.addEventListener('click', (e) => {
|
||||||
|
if (!nav.contains(e.target) && e.target !== menuToggle)
|
||||||
|
nav.classList.remove('open');
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Lazy-load iframes ---
|
||||||
|
const observer = new IntersectionObserver((entries, obs) => {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
const iframe = entry.target;
|
||||||
|
if (iframe.dataset.src) {
|
||||||
|
iframe.src = iframe.dataset.src;
|
||||||
|
iframe.removeAttribute('data-src');
|
||||||
|
obs.unobserve(iframe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, { rootMargin: '200px' });
|
||||||
|
document.querySelectorAll('iframe[data-src]').forEach(f => observer.observe(f));
|
||||||
|
|
||||||
|
// --- Audio Logic ---
|
||||||
|
const audio = document.getElementById('main-audio');
|
||||||
|
const floatingPlayer = document.querySelector('.floating-player');
|
||||||
|
const fpPlayBtn = document.getElementById('fp-play');
|
||||||
|
const fpTitle = document.querySelector('.fp-title');
|
||||||
|
const fpFill = document.querySelector('.fp-progress-fill');
|
||||||
|
let currentTrackBtn = null;
|
||||||
|
|
||||||
|
const formatTime = (t) => {
|
||||||
|
const m = Math.floor(t / 60);
|
||||||
|
const s = Math.floor(t % 60);
|
||||||
|
return `${m}:${s.toString().padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateUI = () => {
|
||||||
|
const playing = !audio.paused;
|
||||||
|
const icon = playing ? '▐▐' : '▶';
|
||||||
|
if (currentTrackBtn) currentTrackBtn.textContent = icon;
|
||||||
|
fpPlayBtn.textContent = icon;
|
||||||
|
if (playing) {
|
||||||
|
floatingPlayer.classList.add('active');
|
||||||
|
document.body.classList.add('player-active');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.querySelectorAll('.play-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
const src = btn.dataset.src;
|
||||||
|
const title = btn.dataset.title;
|
||||||
|
if (audio.getAttribute('data-loaded') === src) {
|
||||||
|
if (audio.paused) audio.play(); else audio.pause();
|
||||||
|
} else {
|
||||||
|
if (currentTrackBtn) currentTrackBtn.textContent = '▶';
|
||||||
|
audio.src = src;
|
||||||
|
audio.setAttribute('data-loaded', src);
|
||||||
|
fpTitle.textContent = title;
|
||||||
|
currentTrackBtn = btn;
|
||||||
|
audio.play();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
fpPlayBtn.addEventListener('click', () => {
|
||||||
|
if (audio.paused) audio.play(); else audio.pause();
|
||||||
|
});
|
||||||
|
|
||||||
|
audio.addEventListener('play', updateUI);
|
||||||
|
audio.addEventListener('pause', updateUI);
|
||||||
|
|
||||||
|
audio.addEventListener('timeupdate', () => {
|
||||||
|
if (!audio.duration) return;
|
||||||
|
const pct = (audio.currentTime / audio.duration) * 100;
|
||||||
|
if (fpFill) fpFill.style.width = pct + '%';
|
||||||
|
if (currentTrackBtn) {
|
||||||
|
const card = currentTrackBtn.closest('.audio-card');
|
||||||
|
const bar = card.querySelector('.progress-bar');
|
||||||
|
const cur = card.querySelector('.current-time');
|
||||||
|
if (bar) bar.style.width = pct + '%';
|
||||||
|
if (cur) cur.textContent = formatTime(audio.currentTime);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
audio.addEventListener('loadedmetadata', () => {
|
||||||
|
if (currentTrackBtn) {
|
||||||
|
const card = currentTrackBtn.closest('.audio-card');
|
||||||
|
const tot = card.querySelector('.total-time');
|
||||||
|
if (tot) tot.textContent = formatTime(audio.duration);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
audio.addEventListener('ended', () => {
|
||||||
|
const allBtns = [...document.querySelectorAll('.play-btn')];
|
||||||
|
const idx = allBtns.indexOf(currentTrackBtn);
|
||||||
|
const next = allBtns[idx + 1];
|
||||||
|
if (next) {
|
||||||
|
next.click();
|
||||||
|
} else {
|
||||||
|
audio.pause();
|
||||||
|
if (currentTrackBtn) currentTrackBtn.textContent = '▶';
|
||||||
|
fpPlayBtn.textContent = '▶';
|
||||||
|
floatingPlayer.classList.remove('active');
|
||||||
|
document.body.classList.remove('player-active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('fp-prev').addEventListener('click', () => {
|
||||||
|
const all = [...document.querySelectorAll('.play-btn')];
|
||||||
|
const prev = all[all.indexOf(currentTrackBtn) - 1];
|
||||||
|
if (prev) prev.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('fp-next').addEventListener('click', () => {
|
||||||
|
const all = [...document.querySelectorAll('.play-btn')];
|
||||||
|
const next = all[all.indexOf(currentTrackBtn) + 1];
|
||||||
|
if (next) next.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.progress-container').forEach(bar => {
|
||||||
|
bar.addEventListener('click', (e) => {
|
||||||
|
const rect = bar.getBoundingClientRect();
|
||||||
|
audio.currentTime = ((e.clientX - rect.left) / rect.width) * audio.duration;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -691,7 +691,7 @@ body::after {
|
|||||||
var STARS = [
|
var STARS = [
|
||||||
{ id: 'writings', label: 'Writings', sign: '文', x: 25, y: 22, href: '' },
|
{ id: 'writings', label: 'Writings', sign: '文', x: 25, y: 22, href: '' },
|
||||||
{ id: 'videos', label: 'Videos', sign: '映', x: 68, y: 18, href: 'Videos/index.html' },
|
{ id: 'videos', label: 'Videos', sign: '映', x: 68, y: 18, href: 'Videos/index.html' },
|
||||||
{ id: 'music', label: 'Music', sign: '♬', x: 12, y: 44, href: '' },
|
{ id: 'music', label: 'Music', sign: '♬', x: 12, y: 44, href: 'Music/music-hub.html' },
|
||||||
{ id: 'images', label: 'Images', sign: '絵', x: 55, y: 35, href: 'Images/images.html' },
|
{ id: 'images', label: 'Images', sign: '絵', x: 55, y: 35, href: 'Images/images.html' },
|
||||||
{ id: 'playlists', label: 'Playlists', sign: '≡', x: 78, y: 50, href: 'Playlists/playlists.html' },
|
{ id: 'playlists', label: 'Playlists', sign: '≡', x: 78, y: 50, href: 'Playlists/playlists.html' },
|
||||||
{ id: 'watchlists', label: 'Watchlists', sign: '視', x: 22, y: 66, href: 'Watchlists/watchlists-hub.html' },
|
{ id: 'watchlists', label: 'Watchlists', sign: '視', x: 22, y: 66, href: 'Watchlists/watchlists-hub.html' },
|
||||||
|
|||||||
Reference in New Issue
Block a user