audium 0.9.0

A terminal music app
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>audium — terminal music player</title>
  <meta name="description" content="A keyboard-driven music player that lives in your terminal. No Electron. No cloud. Just audio." />
  <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=DM+Mono:ital,wght@0,300;0,400;0,500;1,300;1,400&family=Instrument+Serif:ital@0;1&display=swap" rel="stylesheet" />
  <style>
    *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
    html { scroll-behavior: smooth; }
    :root {
      --bg:      #0e0e0e;
      --surface: #161616;
      --border:  #242424;
      --accent:  #64b4ff;
      --text:    #f0f0f0;
      --muted:   #555;
      --dimmed:  #999;
      --mono:    'DM Mono', monospace;
      --serif:   'Instrument Serif', Georgia, serif;
      --radius:  4px;
      --max:     900px;
    }
    body { background: var(--bg); color: var(--text); font-family: var(--mono); font-size: 14px; line-height: 1.7; -webkit-font-smoothing: antialiased; overflow-x: hidden; }
    a { color: var(--accent); text-decoration: none; }
    a:hover { text-decoration: underline; }
    body::before { content:''; position:fixed; inset:0; background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E"); opacity:0.025; pointer-events:none; z-index:1000; }
    .wrap { max-width: var(--max); margin: 0 auto; padding: 0 32px; }
    nav { position:fixed; top:0; left:0; right:0; z-index:100; border-bottom:1px solid var(--border); background:rgba(14,14,14,0.88); backdrop-filter:blur(12px); }
    nav .wrap { display:flex; align-items:center; justify-content:space-between; height:52px; }
    .nav-logo { font-size:15px; font-weight:500; color:var(--text); letter-spacing:0.04em; }
    .nav-logo span { color:var(--accent); }
    .nav-links { display:flex; gap:28px; list-style:none; }
    .nav-links a { color:var(--dimmed); font-size:12px; letter-spacing:0.06em; text-transform:uppercase; transition:color 0.15s; }
    .nav-links a:hover { color:var(--text); text-decoration:none; }
    .hero { padding:160px 0 100px; position:relative; }
    .hero::after { content:''; position:absolute; inset:0; background:repeating-linear-gradient(to bottom,transparent,transparent 3px,rgba(255,255,255,0.012) 3px,rgba(255,255,255,0.012) 4px); pointer-events:none; }
    .hero-eyebrow { font-size:11px; letter-spacing:0.14em; text-transform:uppercase; color:var(--accent); margin-bottom:20px; opacity:0; animation:fade-up 0.6s 0.1s forwards; }
    .hero h1 { font-family:var(--serif); font-size:clamp(52px,8vw,88px); font-weight:400; line-height:1.0; letter-spacing:-0.02em; color:var(--text); opacity:0; animation:fade-up 0.6s 0.2s forwards; }
    .hero h1 em { font-style:italic; color:var(--accent); }
    .hero-sub { margin-top:28px; max-width:520px; color:var(--dimmed); font-size:14px; line-height:1.8; opacity:0; animation:fade-up 0.6s 0.35s forwards; }
    .hero-actions { display:flex; flex-wrap:wrap; gap:12px; margin-top:44px; opacity:0; animation:fade-up 0.6s 0.5s forwards; }
    .btn { display:inline-flex; align-items:center; gap:8px; padding:10px 20px; border-radius:var(--radius); font-family:var(--mono); font-size:13px; font-weight:500; cursor:pointer; border:1px solid transparent; transition:all 0.15s; }
    .btn-primary { background:var(--accent); color:#000; }
    .btn-primary:hover { background:#90caff; text-decoration:none; }
    .btn-ghost { background:transparent; color:var(--dimmed); border-color:var(--border); }
    .btn-ghost:hover { color:var(--text); border-color:var(--dimmed); text-decoration:none; }
    .terminal-wrap { margin-top:72px; opacity:0; animation:fade-up 0.7s 0.65s forwards; }
    .terminal { background:var(--surface); border:1px solid var(--border); border-radius:8px; overflow:hidden; box-shadow:0 40px 80px rgba(0,0,0,0.5); }
    .term-bar { display:flex; align-items:center; gap:7px; padding:12px 16px; border-bottom:1px solid var(--border); background:#111; }
    .dot { width:11px; height:11px; border-radius:50%; }
    .dot-r { background:#ff5f57; } .dot-y { background:#febc2e; } .dot-g { background:#28c840; }
    .term-title { flex:1; text-align:center; color:var(--muted); font-size:11px; letter-spacing:0.04em; }
    .section { padding:80px 0; border-top:1px solid var(--border); }
    .section-label { font-size:11px; letter-spacing:0.14em; text-transform:uppercase; color:var(--muted); margin-bottom:12px; }
    .section h2 { font-family:var(--serif); font-size:clamp(28px,4vw,42px); font-weight:400; line-height:1.15; margin-bottom:40px; }
    .install-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:16px; }
    .install-card { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); padding:24px; transition:border-color 0.15s; }
    .install-card:hover { border-color:var(--accent); }
    .install-card-title { font-size:11px; letter-spacing:0.1em; text-transform:uppercase; color:var(--muted); margin-bottom:12px; }
    .install-card code { display:block; color:var(--accent); font-size:12.5px; word-break:break-all; }
    .install-card .comment { margin-top:8px; color:var(--muted); font-size:11px; }
    .features-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(260px,1fr)); gap:1px; background:var(--border); border:1px solid var(--border); border-radius:var(--radius); overflow:hidden; }
    .feature { background:var(--surface); padding:32px 28px; }
    .feature-icon { font-size:22px; margin-bottom:14px; display:block; }
    .feature h3 { font-size:14px; font-weight:500; color:var(--text); margin-bottom:8px; }
    .feature p { color:var(--dimmed); font-size:12.5px; line-height:1.75; }
    .keybind-table { width:100%; border-collapse:collapse; }
    .keybind-table tr { border-bottom:1px solid var(--border); }
    .keybind-table tr:last-child { border-bottom:none; }
    .keybind-table td { padding:10px 0; font-size:13px; color:var(--dimmed); }
    .keybind-table td:first-child { width:160px; padding-right:24px; }
    .keybind-table td:first-child kbd { display:inline-block; background:var(--surface); border:1px solid var(--border); border-radius:3px; padding:2px 8px; font-family:var(--mono); font-size:12px; color:var(--accent); }
    footer { border-top:1px solid var(--border); padding:40px 0; }
    footer .wrap { display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:16px; }
    footer p { color:var(--muted); font-size:12px; }
    .footer-links { display:flex; gap:20px; list-style:none; }
    .footer-links a { color:var(--muted); font-size:12px; }
    .footer-links a:hover { color:var(--dimmed); }
    @keyframes fade-up { from { opacity:0; transform:translateY(16px); } to { opacity:1; transform:translateY(0); } }
    @media (max-width:600px) { .wrap { padding:0 20px; } .hero { padding:120px 0 64px; } .nav-links { display:none; } }
  </style>
</head>
<body>

<nav>
  <div class="wrap">
    <span class="nav-logo">aud<span>i</span>um</span>
    <ul class="nav-links">
      <li><a href="#install">Install</a></li>
      <li><a href="#features">Features</a></li>
      <li><a href="#keybinds">Keybinds</a></li>
      <li><a href="https://github.com/takashialpha/audium">GitHub</a></li>
      <li><a href="https://crates.io/crates/audium">crates.io</a></li>
    </ul>
  </div>
</nav>

<section class="hero">
  <div class="wrap">
    <p class="hero-eyebrow">Apache-2.0 — available on crates.io &amp; AUR</p>
    <h1>Music that lives<br>in your <em>terminal.</em></h1>
    <p class="hero-sub">
      audium is a keyboard-driven music player for people who live in the terminal.
      No Electron. No cloud sync. No background daemons.
      Just your files, a queue, and a progress bar.
    </p>
    <div class="hero-actions">
      <a class="btn btn-primary" href="#install">Get started</a>
      <a class="btn btn-ghost" href="https://github.com/takashialpha/audium">View source →</a>
    </div>

    <div class="terminal-wrap">
      <div class="terminal">
        <div class="term-bar">
          <div class="dot dot-r"></div>
          <div class="dot dot-y"></div>
          <div class="dot dot-g"></div>
          <span class="term-title">audium</span>
        </div>
        <canvas id="tui" style="display:block;width:100%;background:#161616;border-radius:0 0 8px 8px;"></canvas>
        <script>
        (function() {
          const FONT_SIZE = 12;
          const FONT = FONT_SIZE + 'px "DM Mono",monospace';
          const PAD_X = 16, PAD_Y = 20, LINE_H = 19;
          const A = '#64b4ff', D = '#2e2e2e', M = '#606060', W = '#f0f0f0';

          // Each row is an array of [text, colour] segments drawn left-to-right.
          // The canvas measures each segment's advance width so columns are exact.
          const ROWS = [
            [[' audium', A], ['  \u2014  terminal music player      [?] help  [f] file picker  [c] playlist  [q] quit', M]],
            [['\u250c' + '\u2500'.repeat(25) + '\u252c' + '\u2500'.repeat(54) + '\u2510', D]],
            [['', D], [' Playlists               ', A], ['', D], [' All Tracks                                           ', A], ['', D]],
            [['│                         │', D], ['  #    Title                                          ', M], ['', D]],
            [['', D], [' > All Tracks (4)        ', A], ['', D], ['    1  04 - Neon Arpeggio                             ', M], ['', D]],
            [['', D], ['   Late Night (2)        ', M], ['', D], ['    2  Drift                                          ', M], ['', D]],
            [['', D], ['   Focus (2)             ', M], ['', D], [' >  3  Weightless (feat. Macarena)                    ', A], ['', D]],
            [['│                         │', D], ['    4  The River Calls                                ', M], ['', D]],
            [['│                         │                                                      │', D]],
            [['\u251c' + '\u2500'.repeat(54) + '\u2524', D]],
            [['│                         │', D], [' Queue                                                ', A], ['', D]],
            [['│                         │', D], ['    1  04 - Neon Arpeggio                             ', M], ['', D]],
            [['│                         │', D], ['    2  Drift                                          ', M], ['', D]],
            [['│                         │', D], [' >  3  Weightless (feat. Macarena)                    ', A], ['', D]],
            [['\u2514' + '\u2500'.repeat(25) + '\u2534' + '\u2500'.repeat(54) + '\u2518', D]],
            [['', A], ['Weightless (feat. Macarena)', W]],
            [[''.repeat(20), A], [''.repeat(41), D], ['  2:14 / 5:02', M]],
          ];

          function draw() {
            const canvas = document.getElementById('tui');
            const dpr = window.devicePixelRatio || 1;
            const cssW = canvas.parentElement.clientWidth;
            const cssH = PAD_Y * 2 + LINE_H * ROWS.length;
            canvas.style.height = cssH + 'px';
            canvas.width  = cssW * dpr;
            canvas.height = cssH * dpr;
            const ctx = canvas.getContext('2d');
            ctx.scale(dpr, dpr);
            ctx.font = FONT;
            ROWS.forEach((segs, row) => {
              let x = PAD_X;
              const y = PAD_Y + row * LINE_H;
              segs.forEach(([text, colour]) => {
                ctx.fillStyle = colour;
                ctx.fillText(text, x, y);
                x += ctx.measureText(text).width;
              });
            });
          }

          // Draw after fonts load for accurate metrics.
          if (document.fonts && document.fonts.ready) {
            document.fonts.ready.then(draw);
          } else {
            window.addEventListener('load', draw);
          }
          window.addEventListener('resize', draw);
        })();
        </script>
      </div>
    </div>
  </div>
</section>

<section class="section" id="install">
  <div class="wrap">
    <p class="section-label">Installation</p>
    <h2>Pick your platform.</h2>
    <div class="install-grid">
      <div class="install-card">
        <p class="install-card-title">Cargo (all platforms)</p>
        <code>cargo install audium</code>
        <p class="comment"># requires Rust 1.85+</p>
      </div>
      <div class="install-card">
        <p class="install-card-title">AUR (Arch Linux)</p>
        <code>paru -S audium</code>
        <p class="comment"># or yay, or manually with makepkg</p>
      </div>
    </div>
  </div>
</section>

<section class="section" id="features">
  <div class="wrap">
    <p class="section-label">Features</p>
    <h2>Everything you need.<br><em style="font-style:italic;color:var(--accent);">Nothing you don't.</em></h2>
    <div class="features-grid" style="margin-top:40px;">
      <div class="feature"><span class="feature-icon"></span><h3>Format agnostic</h3><p>Plays MP3, FLAC, OGG, WAV, AAC, M4A, Opus, and more via Symphonia — no FFmpeg required.</p></div>
      <div class="feature"><span class="feature-icon"></span><h3>Fully keyboard-driven</h3><p>Every action is a single keypress. Tab between panels, j/k to navigate, space to play.</p></div>
      <div class="feature"><span class="feature-icon">📂</span><h3>Built-in file picker</h3><p>Browse your filesystem without leaving audium. Only audio files are shown.</p></div>
      <div class="feature"><span class="feature-icon">🎵</span><h3>Playlists</h3><p>Create, rename, and delete playlists. The All Tracks view is always one keypress away.</p></div>
      <div class="feature"><span class="feature-icon">🔊</span><h3>Threaded audio</h3><p>Playback runs on a dedicated thread. Heavy I/O will never stutter your music.</p></div>
      <div class="feature"><span class="feature-icon">💾</span><h3>Persistent library</h3><p>Stored at <code style="color:var(--accent)">~/.audium/library.json</code>. Plain JSON, portable, easy to back up.</p></div>
    </div>
  </div>
</section>

<section class="section" id="keybinds">
  <div class="wrap">
    <p class="section-label">Keybindings</p>
    <h2>Muscle memory<br>over menus.</h2>
    <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:48px;margin-top:40px;">
      <div>
        <p style="color:var(--muted);font-size:11px;letter-spacing:0.1em;text-transform:uppercase;margin-bottom:16px;">Playback</p>
        <table class="keybind-table">
          <tr><td><kbd>Space</kbd></td><td>Play / Pause</td></tr>
          <tr><td><kbd>n</kbd></td><td>Next track</td></tr>
          <tr><td><kbd>N</kbd></td><td>Previous track</td></tr>
          <tr><td><kbd></kbd> / <kbd></kbd></td><td>Seek backward / forward</td></tr>
          <tr><td><kbd>+</kbd> / <kbd>=</kbd></td><td>Volume up</td></tr>
          <tr><td><kbd>-</kbd></td><td>Volume down</td></tr>
        </table>
      </div>
      <div>
        <p style="color:var(--muted);font-size:11px;letter-spacing:0.1em;text-transform:uppercase;margin-bottom:16px;">Navigation</p>
        <table class="keybind-table">
          <tr><td><kbd>Tab</kbd></td><td>Cycle panel focus</td></tr>
          <tr><td><kbd>j</kbd> / <kbd></kbd></td><td>Move down</td></tr>
          <tr><td><kbd>k</kbd> / <kbd></kbd></td><td>Move up</td></tr>
          <tr><td><kbd>Enter</kbd></td><td>Play selected</td></tr>
          <tr><td><kbd>?</kbd></td><td>Help overlay</td></tr>
        </table>
      </div>
      <div>
        <p style="color:var(--muted);font-size:11px;letter-spacing:0.1em;text-transform:uppercase;margin-bottom:16px;">Library &amp; Queue</p>
        <table class="keybind-table">
          <tr><td><kbd>f</kbd></td><td>Open file picker</td></tr>
          <tr><td><kbd>a</kbd></td><td>Add to queue</td></tr>
          <tr><td><kbd>p</kbd></td><td>Add to playlist</td></tr>
          <tr><td><kbd>c</kbd></td><td>New playlist</td></tr>
          <tr><td><kbd>d</kbd></td><td>Remove</td></tr>
          <tr><td><kbd>r</kbd></td><td>Rename</td></tr>
          <tr><td><kbd>x</kbd></td><td>Remove from queue</td></tr>
          <tr><td><kbd>q</kbd></td><td>Quit</td></tr>
        </table>
      </div>
    </div>
  </div>
</section>

<footer>
  <div class="wrap">
    <p>audium — <a href="https://github.com/takashialpha/audium/blob/main/LICENSE">Apache-2.0</a></p>
    <ul class="footer-links">
      <li><a href="https://github.com/takashialpha/audium">GitHub</a></li>
      <li><a href="https://crates.io/crates/audium">crates.io</a></li>
      <li><a href="https://aur.archlinux.org/packages/audium">AUR</a></li>
    </ul>
  </div>
</footer>

</body>
</html>