Files
adminandClaude Opus 5.5 2a19515c27 feat: split the web UI into modules and rework it for screen readers
webui.html becomes webui/ (index.html, style.css, ES modules in js/, no
build step); web.py serves it under / and /ui/<path> with the same
containment check as recordings.

How it feels with a screen reader:
- Days read "Today, Saturday 17 October"; recordings are named by their
  wall-clock span ("22:00 to 23:00"), which is the row header and what
  every button says. The filename moves to the last column.
- Focus is never dropped: busy buttons stay focused (aria-disabled),
  finished analyses focus their first section, delete moves to the next
  row, closing the clip bar (x or Escape) returns to where you were.
- Reloads rebuild only the days whose content changed; DOM ids are stable
  per filename, so a rollover no longer resets the reading position.
- Only user-initiated actions are announced; highlights completion is
  spoken (the progress area was stuck aria-busy before).
- Media Session previous/next track step sections like J/K (AirPods,
  lock screen, media keys).
- Row layout: Time | Length | Loudness | Player | Options | File. The
  player opens inside its cell; Download/Cut/Delete are always there,
  and Cut toggles its start/end fields in place.
- Settings tucked behind a <details>; pre-roll and auto-advance are
  remembered per browser; no CSS uppercase on headers or badges.

Fixes the REC badge showing on every row: its inline-flex display beat
the UA [hidden] rule. One global [hidden]{display:none !important} now
covers every case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 01:05:15 +02:00

136 lines
7.4 KiB
CSS

:root{--bg:#0f1117;--surf:#1a1d27;--brd:#2c3040;--txt:#e2e8f0;--muted:#8b93ad;
--accent:#4f9cf9;--orange:#f97316;--green:#22c55e;--red:#ef4444;
--gutter:28px}
@media (max-width:640px){:root{--gutter:16px}}
*{box-sizing:border-box;margin:0;padding:0}
/* One rule for every [hidden] element. Author display rules (flex chips, the
inline-flex REC badge, the flex clip bar) beat the UA's [hidden] otherwise —
the REC badge used to show on every row that way. */
[hidden]{display:none !important}
body{background:var(--bg);color:var(--txt);font:15px/1.5 system-ui,sans-serif}
.sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;
overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
.skip{position:absolute;left:-999px;top:0;padding:8px 14px;background:var(--accent);
color:#000;border-radius:0 0 4px 4px;text-decoration:none;font-weight:600;z-index:30}
.skip:focus{left:0}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
#page-title:focus,#days:focus{outline:none}
.muted{color:var(--muted)}
.nowrap{white-space:nowrap}
header{padding:16px var(--gutter);border-bottom:1px solid var(--brd);
display:flex;align-items:baseline;gap:6px 14px;flex-wrap:wrap}
header h1{font-size:20px;font-weight:600}
#subtitle{color:var(--muted)}
#storage-info{color:var(--muted);font-size:13px;margin-right:auto}
button{cursor:pointer;border:1px solid var(--brd);background:var(--surf);color:var(--txt);
padding:6px 12px;border-radius:6px;font:inherit;font-size:13px;white-space:nowrap;min-height:32px}
button:hover{background:var(--brd)}
button:disabled,button[aria-disabled=true]{opacity:.55;cursor:default}
input{font:inherit;font-size:14px;background:var(--bg);border:1px solid var(--brd);
color:var(--txt);padding:5px 8px;border-radius:5px;min-height:32px}
input[type=date]{color-scheme:dark}
input[type=radio]{min-height:0;accent-color:var(--accent)}
/* settings */
#settings{border-bottom:1px solid var(--brd);background:var(--surf)}
#settings summary{padding:10px var(--gutter);cursor:pointer;color:var(--muted);font-size:14px}
#settings summary:hover{color:var(--txt)}
.settings-grid{display:grid;grid-template-columns:max-content 90px 1fr;gap:8px 12px;
align-items:center;padding:4px var(--gutter) 14px}
.settings-grid label{font-size:14px}
.hint{font-size:12px;color:var(--muted)}
@media (max-width:640px){
.settings-grid{grid-template-columns:1fr 90px}
.settings-grid .hint{grid-column:1 / -1;margin-top:-6px}
}
/* filters */
.filter-bar{display:flex;align-items:center;gap:8px 10px;padding:10px var(--gutter);
border-bottom:1px solid var(--brd);flex-wrap:wrap}
.filter-bar label{font-size:14px;color:var(--muted)}
#filter-name{width:200px}
/* days */
#days{padding:20px var(--gutter)}
.day-section{margin-bottom:18px}
.day-heading-bar{background:var(--surf);padding:6px 10px;border:1px solid var(--brd);
border-radius:8px;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.day-heading-bar.open{border-radius:8px 8px 0 0}
h2.day-heading{font-size:inherit;font-weight:inherit;flex:1 1 auto}
.day-toggle{background:none;border:none;color:var(--txt);font-size:15px;font-weight:600;
padding:4px 0;display:inline-flex;align-items:baseline;gap:4px 10px;flex-wrap:wrap;text-align:left;white-space:normal}
.day-toggle:hover{background:none;color:var(--accent)}
.day-meta{color:var(--muted);font-size:13px;font-weight:400}
.day-arrow{font-size:10px}
button.day-hl{color:var(--green);border-color:#166534;background:#052e16}
button.day-hl:hover{background:#0a3d1f}
.day-hl-status{opacity:.8}
.day-hl-container{border:1px solid var(--brd);border-top:none;padding:10px 12px 12px}
/* recordings table; scrolls sideways on narrow screens instead of the page */
.table-wrap{overflow-x:auto;border:1px solid var(--brd);border-top:none}
table{width:100%;border-collapse:collapse}
th,td{padding:9px 10px;border-bottom:1px solid var(--brd);vertical-align:middle;text-align:left}
thead th{color:var(--muted);font-weight:500;font-size:13px;white-space:nowrap}
th.when{font-weight:600;white-space:nowrap}
tr.data-row:hover>*{background:var(--surf)}
td.file{color:var(--muted);font-size:12px;white-space:nowrap}
.fn{font-family:ui-monospace,monospace}
.badge{display:inline-block;padding:1px 6px;border-radius:3px;font-size:10px;
font-weight:700;border:1px solid}
.badge-wav{color:var(--green);border-color:#166534;background:#052e16}
.badge-mp3{color:var(--accent);border-color:#1e40af;background:#0c1a40}
.badge-ogg{color:#c084fc;border-color:#6b21a8;background:#2d1157}
.badge-flac{color:#fb923c;border-color:#7c2d12;background:#2c0e04}
.badge-aac,.badge-opus{color:var(--muted);border-color:var(--brd);background:var(--surf)}
.badge-rec{display:inline-flex;align-items:center;padding:1px 6px;border-radius:3px;
font-size:11px;font-weight:700;color:var(--red);border:1px solid #7f1d1d;background:#2d0808;
animation:pulse 1.5s ease-in-out infinite}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.45}}
@media (prefers-reduced-motion:reduce){.badge-rec{animation:none}}
.actions{display:flex;gap:6px;align-items:center}
a.dl{color:var(--accent);text-decoration:none;font-size:13px;padding:6px 4px}
a.dl:hover{text-decoration:underline}
button.del{color:var(--red);border-color:#7f1d1d}
button.del:hover:not(:disabled){background:#2d0808}
button.cut{color:var(--accent);border-color:#1e40af;background:#0c1a40}
button.cut:hover:not(:disabled){background:#1e3a8a}
/* analysis */
td.loudness{min-width:200px}
.wbox{background:var(--surf);border:1px solid var(--brd);border-radius:6px;padding:8px 10px}
.analysis-meta{font-size:12px;color:var(--muted)}
.chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
.chip{background:#431407;color:var(--orange);border:1px solid #7c2d12;border-radius:5px;
padding:4px 9px;font-size:12px;font-family:ui-monospace,monospace;min-height:30px}
.chip:hover{background:#6c1f08;border-color:#9a3412}
.chip-toggle{margin-top:8px}
button.reanalyse-btn{margin-top:8px;font-size:12px;color:var(--muted);background:transparent}
.quiet{color:var(--muted);font-size:13px}
.quiet+.quiet{margin-top:6px}
.prog-bar{height:4px;background:var(--brd);border-radius:2px;margin:6px 0 4px;overflow:hidden}
.prog-fill{height:100%;width:0;background:var(--accent);transition:width .15s ease}
@media (prefers-reduced-motion:reduce){.prog-fill{transition:none}}
.prog-file{font-size:12px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.empty{text-align:center;padding:60px 16px;color:var(--muted)}
/* full-file player cell, options cell */
audio{width:100%;height:40px;display:block;color-scheme:dark;accent-color:var(--accent)}
td.player audio{width:300px;margin-top:6px}
.cut-panel{display:flex;align-items:center;gap:8px 12px;margin-top:8px;flex-wrap:wrap}
.cut-field{display:flex;align-items:center;gap:6px;font-size:13px;color:var(--muted)}
.cut-time{width:110px;font-family:ui-monospace,monospace}
/* clip player bar */
#clip-bar{position:fixed;bottom:0;left:0;right:0;z-index:20;background:var(--surf);
border-top:1px solid var(--brd);padding:8px var(--gutter);display:flex;flex-direction:column;gap:6px}
.clip-row{display:flex;align-items:center;gap:8px 10px;flex-wrap:wrap}
#clip-label{flex:1 1 240px;font-family:ui-monospace,monospace;font-size:13px;min-width:0}
#clip-audio{flex:1 1 260px;min-width:200px;width:auto}
#clip-adv{border:none;display:flex;align-items:center;gap:4px 12px;flex-wrap:wrap}
#clip-adv legend{float:left;font-size:12px;color:var(--muted);margin-right:4px}
#clip-adv label{font-size:13px;display:flex;align-items:center;gap:5px;cursor:pointer}
body.clip-open{padding-bottom:130px}