diff --git a/.gitignore b/.gitignore index c000ddc..8ac896e 100644 --- a/.gitignore +++ b/.gitignore @@ -27,5 +27,18 @@ Thumbs.db # Direnv .direnv/ +# Source audio (large WAVs from concert tape transfers) +content/audio/*.wav + +# Built audio (regenerate from content/audio/ via `make audio`) +web/static/audio/*.opus + +# Built images (regenerate from content/images/ via `make images`) +web/static/images/*.webp + +# Nix build outputs +result +result-* + # Build artifacts from content pipeline content/build/ diff --git a/Makefile b/Makefile index 6ce7e03..3366425 100644 --- a/Makefile +++ b/Makefile @@ -2,10 +2,46 @@ SHELL := /usr/bin/env bash .PHONY: help help: - @echo "make build — build server + frontend locally (sanity check)" - @echo "make check — type-check + go vet" + @echo "make build — build server + frontend locally (sanity check)" + @echo "make check — type-check + go vet" + @echo "make audio-pull — copy source WAVs from Dropbox into content/audio/" + @echo "make audio — transcode each side WAV → Opus" + @echo "make images — convert content/images/*.jpg → WebP" + @echo "make artists — regenerate artists.json from audio.yaml" + @echo "make content — artists + images + audio" + @echo "make deploy-assets — rsync web/static/{audio,images}/ to sprade for production" @echo "make tag V=patch|minor|major — bump version tag and push" - @echo "make prefetch — print sha256 + npmDepsHash + vendorHash for the homelab module" + @echo "make prefetch — print sha256 + npmDepsHash + vendorHash for the homelab module" + +.PHONY: audio-pull +audio-pull: + bin/pull-audio + +.PHONY: audio +audio: + bin/build-audio + +.PHONY: images +images: + bin/build-images + +.PHONY: artists +artists: + bin/build-artists + +.PHONY: content +content: artists images audio + +.PHONY: deploy-assets +deploy-assets: + @if [ ! -d web/static/audio ] || [ -z "$$(ls web/static/audio 2>/dev/null)" ]; then \ + echo "error: web/static/audio is empty. Run \`make audio\` first." >&2; exit 1; \ + fi + @if [ ! -d web/static/images ] || [ -z "$$(ls web/static/images 2>/dev/null)" ]; then \ + echo "error: web/static/images is empty. Run \`make images\` first." >&2; exit 1; \ + fi + rsync -av --delete --progress web/static/audio/ deploy@sprade:/var/lib/docent/audio/ + rsync -av --delete --progress web/static/images/ deploy@sprade:/var/lib/docent/images/ .PHONY: build build: diff --git a/bin/build-artists b/bin/build-artists new file mode 100755 index 0000000..587d59d --- /dev/null +++ b/bin/build-artists @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +MANIFEST="${MANIFEST:-$REPO_ROOT/content/audio.yaml}" +OUT="${OUT:-$REPO_ROOT/web/src/lib/artists.json}" + +for tool in yq jq; do + if ! command -v "$tool" >/dev/null; then + echo "error: $tool not in PATH (try: nix develop)" >&2 + exit 1 + fi +done + +yq -o=json '.' "$MANIFEST" | jq ' + def render_artist: + . as $a | + { + id: $a.id, + name: $a.name, + slug: $a.slug, + description: ($a.description // "" | rtrimstr("\n")), + images: [ + ($a.images // []) | to_entries[] | + ("/images/" + $a.slug + "-" + ((.key + 1) | tostring) + ".webp") + ], + shows: [ + $a.shows[] | . as $s | { + caption: $s.caption, + slug: $s.slug, + sides: [ + $s.sides[] | { + id: .id, + label: .label, + audio: ("/audio/" + $a.slug + "-" + $s.slug + "-" + .id + ".opus") + } + ] + } + ] + }; + + { + exhibit: .exhibit, + artists: [ .artists[] | render_artist ] + } +' > "$OUT.tmp" + +mv "$OUT.tmp" "$OUT" +echo "wrote $OUT ($(jq '.artists | length' "$OUT") artists)" diff --git a/bin/build-audio b/bin/build-audio new file mode 100755 index 0000000..6022153 --- /dev/null +++ b/bin/build-audio @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +# Sides are encoded individually — no concatenation. Idempotent: a side is +# skipped if its output is newer than its source. +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +MANIFEST="${MANIFEST:-$REPO_ROOT/content/audio.yaml}" +RAW_DIR="${RAW_DIR:-$REPO_ROOT/content/audio}" +OUT_DIR="${OUT_DIR:-$REPO_ROOT/web/static/audio}" + +for tool in yq ffmpeg; do + if ! command -v "$tool" >/dev/null; then + echo "error: $tool not in PATH (try: nix develop)" >&2 + exit 1 + fi +done + +if [[ ! -f "$MANIFEST" ]]; then + echo "error: manifest not found: $MANIFEST" >&2 + exit 1 +fi + +mkdir -p "$OUT_DIR" + +DEFAULT_BITRATE=$(yq -r '.defaults.bitrate // "64k"' "$MANIFEST") +DEFAULT_CHANNELS=$(yq -r '.defaults.channels // "2"' "$MANIFEST") +DEFAULT_RATE=$(yq -r '.defaults.sample_rate // "48000"' "$MANIFEST") + +encoded=0 +skipped=0 +missing=0 +failed=0 + +NUM_ARTISTS=$(yq '.artists | length' "$MANIFEST") + +for ((a=0; a src_mt )); then + printf ' %-50s up to date\n' "$OUT_NAME" + skipped=$((skipped + 1)) + continue + fi + fi + + printf ' %-50s encoding ...\n' "$OUT_NAME" + + if ffmpeg \ + -i "$SRC_PATH" \ + -c:a libopus -b:a "$BITRATE" -ac "$CHANNELS" -ar "$RATE" \ + -application audio -vbr on -compression_level 10 \ + -y "$OUT_PATH" \ + -hide_banner -loglevel warning -nostdin &2 + failed=$((failed + 1)) + fi + done + done +done + +printf '\n%d encoded · %d up-to-date · %d missing sources · %d failed\n' \ + "$encoded" "$skipped" "$missing" "$failed" + +if (( failed > 0 )); then + exit 1 +fi diff --git a/bin/build-images b/bin/build-images new file mode 100755 index 0000000..be42374 --- /dev/null +++ b/bin/build-images @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +MANIFEST="${MANIFEST:-$REPO_ROOT/content/audio.yaml}" +SRC_DIR="${SRC_DIR:-$REPO_ROOT/content/images}" +OUT_DIR="${OUT_DIR:-$REPO_ROOT/web/static/images}" +MAX_SIZE="${MAX_SIZE:-1200}" +QUALITY="${QUALITY:-80}" + +for tool in yq magick; do + if ! command -v "$tool" >/dev/null; then + echo "error: $tool not in PATH (try: nix develop)" >&2 + exit 1 + fi +done + +mkdir -p "$OUT_DIR" + +encoded=0 +skipped=0 +missing=0 +failed=0 + +NUM_ARTISTS=$(yq '.artists | length' "$MANIFEST") + +for ((a=0; a src_mt )); then + printf ' %-45s up to date\n' "$OUT_NAME" + skipped=$((skipped + 1)) + continue + fi + fi + + printf ' %-45s converting ...\n' "$OUT_NAME" + + if magick "$SRC_PATH" \ + -auto-orient \ + -strip \ + -resize "${MAX_SIZE}x${MAX_SIZE}>" \ + -quality "$QUALITY" \ + "$OUT_PATH"; then + size=$(du -h "$OUT_PATH" | cut -f1) + printf ' %-45s done (%s)\n' "$OUT_NAME" "$size" + encoded=$((encoded + 1)) + else + printf ' %-45s FAILED\n' "$OUT_NAME" >&2 + failed=$((failed + 1)) + fi + done +done + +printf '\n%d converted · %d up-to-date · %d missing sources · %d failed\n' \ + "$encoded" "$skipped" "$missing" "$failed" + +if (( failed > 0 )); then + exit 1 +fi diff --git a/bin/pull-audio b/bin/pull-audio new file mode 100755 index 0000000..87c398c --- /dev/null +++ b/bin/pull-audio @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# Copy concert WAV files from the curator's Dropbox folder into content/audio/, +# stripping the ` Brianna` suffix that the source files carry. +# Idempotent: skips files that already exist at the destination. +set -euo pipefail + +SRC_DIR="${SRC_DIR:-$HOME/Dropbox/File requests/Mill Run Audio}" +DEST_DIR="${DEST_DIR:-$(cd "$(dirname "$0")/.." && pwd)/content/audio}" + +if [[ ! -d "$SRC_DIR" ]]; then + echo "error: source directory not found: $SRC_DIR" >&2 + exit 1 +fi + +mkdir -p "$DEST_DIR" + +count=0 +copied=0 +skipped=0 + +shopt -s nullglob +for src in "$SRC_DIR"/*.wav; do + count=$((count + 1)) + name=$(basename "$src") + # Strip trailing " Brianna.wav" → ".wav" + dest_name="${name% Brianna.wav}.wav" + dest="$DEST_DIR/$dest_name" + + if [[ -f "$dest" ]] && [[ $(stat -c %s "$src") -eq $(stat -c %s "$dest") ]]; then + skipped=$((skipped + 1)) + continue + fi + + printf ' [%2d] %s\n' "$count" "$dest_name" + cp -- "$src" "$dest" + copied=$((copied + 1)) +done + +printf '\n%d total · %d copied · %d skipped (already present)\n' "$count" "$copied" "$skipped" diff --git a/branding.png b/branding.png new file mode 100644 index 0000000..afb0fd3 Binary files /dev/null and b/branding.png differ diff --git a/content/audio.yaml b/content/audio.yaml new file mode 100644 index 0000000..20df964 --- /dev/null +++ b/content/audio.yaml @@ -0,0 +1,899 @@ +# Source of truth for the exhibit's audio collection. +# +# The Mill Run Playhouse-Theatre stop in the larger exhibit holds recordings +# from concerts at the venue, digitized side-by-side from cassette tapes. +# The data model here is artist → shows → sides. Each `side` produces one +# Opus file; sides are not concatenated. +# +# Filename convention (auto-derived): +# web/static/audio/--.opus +# +# Pipeline: +# bin/pull-audio — copy WAVs from Dropbox to content/audio/ +# bin/build-audio — encode each side as its own opus +# bin/build-images — convert content/images/*.jpg → web/static/images/*.webp +# bin/build-stops — regenerate web/src/lib/stops.json from this file + +defaults: + bitrate: 64k + channels: 2 + sample_rate: 48000 + +exhibit: + title: "Mill Run Theatre" + description: | + Listen to performances from the Mill Run + Select from the artists below + +artists: + - id: 1 + name: "Al Martino" + slug: al-martino + description: | + American singer of Italian descent best known for the ballad "Here + in My Heart" and the perennial "Spanish Eyes." Martino also played + the singer Johnny Fontane in The Godfather and its sequel. + images: + - "AL MARTINO001_74.jpg" + shows: + - caption: "1974" + slug: "1974" + sides: + - id: a + label: "Side A" + source: "Al Martino - 1974 (Side A) (No Side B).wav" + + - id: 2 + name: "Caterina Valente" + slug: caterina-valente + description: | + Italo-French entertainer who recorded in over a dozen languages across + a career spanning the 1950s through the 1980s. A jazz-trained singer, + she was a fixture of European and American television variety shows. + images: [] + shows: + - caption: "Aug 1976" + slug: "1976-aug" + sides: + - id: a + label: "Side A" + source: "Caterina Valente - August 1976 (2nd Show Sunday) (Side A) (No Side B).wav" + + - id: 3 + name: "Dionne Warwick" + slug: dionne-warwicke + description: | + American singer who became the definitive voice of the Burt Bacharach + and Hal David songbook in the 1960s: "Walk On By," "Anyone Who Had a + Heart," and "Do You Know the Way to San Jose." From 1971 to 1975 she + performed under the alternate spelling "Warwicke." + images: + - "Dionne Warwick 1974 from Don Leavitt.jpg" + shows: + - caption: "1974" + slug: "1974" + sides: + - id: a + label: "Side A" + source: "Dionne Warwicke - 1974 (Side A).wav" + - id: b + label: "Side B" + source: "Dionne Warwicke - 1974 (Side B).wav" + + - id: 4 + name: "Don Ho" + slug: don-ho + description: | + Hawaiian musician and entertainer whose 1966 single "Tiny Bubbles" + became his lifelong signature. He performed nightly in Waikiki for + decades and toured the mainland into the 2000s. + images: + - "Don Ho 001 1980.jpg" + shows: + - caption: "Jun 26, 1977 — 1st show" + slug: "1977-jun-1" + sides: + - id: a + label: "Side A" + source: "Don Ho - June 26th 1977 (Tape 1) (Side A).wav" + - id: b + label: "Side B" + source: "Don Ho - June 26th 1977 (Tape 1) (Side B).wav" + - caption: "Jun 26, 1977 — 2nd show" + slug: "1977-jun-2" + sides: + - id: a + label: "Side A" + source: "Don Ho - June 26th 1977 (Tape 2) (Side A).wav" + - id: b + label: "Side B" + source: "Don Ho - June 26th 1977 (Tape 2) (Side B).wav" + + - id: 5 + name: "Don Rickles" + slug: don-rickles + description: | + American stand-up comedian known for insult comedy aimed affectionately + at celebrities and audience members alike. A regular on television + variety and talk shows from the 1960s onward. + images: + - "Don Rickles 002 1975.jpg" + shows: + - caption: "May 25, 1975" + slug: "1975-may" + sides: + - id: a + label: "Side A" + source: "Don Rickles - May 25th 1975 (Side A).wav" + - id: b + label: "Side B" + source: "Don Rickles - May 25th 1975 (Side B).wav" + + - id: 6 + name: "Eddy Arnold" + slug: eddy-arnold + description: | + Tennessee-born country singer who placed more than 145 songs on the + country charts across five decades. Hits include "Make the World Go + Away" and "Cattle Call." + images: + - "Eddy Arnold 001 1975.jpg" + shows: + - caption: "1975" + slug: "1975" + sides: + - id: a + label: "Side A" + source: "Eddy Arnold - 1975 (Side A).wav" + - id: b + label: "Side B" + source: "Eddy Arnold - 1975 (Side B).wav" + - caption: "Nov 1977" + slug: "1977-nov" + sides: + - id: a + label: "Side A" + source: "Eddy Arnold - November 1977 (Side A).wav" + - id: b + label: "Side B" + source: "Eddy Arnold - November 1977 (Side B).wav" + + - id: 7 + name: "Engelbert Humperdinck" + slug: engelbert-humperdinck + description: | + British pop singer whose 1967 single "Release Me" famously kept the + Beatles' "Strawberry Fields Forever" from reaching number one in the + UK. His stage name was borrowed from a 19th-century German opera + composer. + images: + - "Engelbert Humperdinck 003 1975.jpg" + shows: + - caption: "Oct 1975" + slug: "1975-oct" + sides: + - id: a + label: "Side A" + source: "Engelebert Humperdinck - October 1975 (Side A).wav" + - id: b + label: "Side B" + source: "Engelebert Humperdinck - October 1975 (Side B).wav" + + - id: 8 + name: "Frankie Laine" + slug: frankie-laine + description: | + American crooner whose hits include "That's My Desire," "Mule Train," + "Jezebel," and the theme from the television series Rawhide. + images: + - "Frankie Laine 002 1975.jpg" + shows: + - caption: "Sep 1975" + slug: "1975-sep" + sides: + - id: a + label: "Side A" + source: "Frankie Laine - September 1975 (2nd Show Sunday) (Side A).wav" + - id: b + label: "Side B" + source: "Frankie Laine - September 1975 (2nd Show Sunday) (Side B).wav" + + - id: 9 + name: "Gladys Knight & the Pips" + slug: gladys-knight + description: | + American soul and funk group fronted by Gladys Knight with her brother + and two cousins as the Pips. Hits include "Midnight Train to Georgia," + "I Heard It Through the Grapevine," and "Neither One of Us." + images: + - "Gladys Knight and the Pips 001 1975.jpg" + - "Gladys Knight & the Pips 001 1975.jpg" + shows: + - caption: "Aug 1977 — 1st show" + slug: "1977-aug-1st" + sides: + - id: a + label: "Side A" + source: "Gladys Knight - August 1977 (1st Show Saturday) (Side A).wav" + - id: b + label: "Side B" + source: "Gladys Knight - August 1977 (1st Show Saturday) (Side B).wav" + - caption: "Aug 1977 — 2nd show" + slug: "1977-aug-2nd" + sides: + - id: a + label: "Side A" + source: "Gladys Knight - August 1977 (2nd Show Saturday) (Side A).wav" + - id: b + label: "Side B" + source: "Gladys Knight - August 1977 (2nd Show Saturday) (Side B).wav" + - caption: "Oct 1975 — 1st show" + slug: "1975-oct-fri" + sides: + - id: a + label: "Side A" + source: "Gladys Knight - October 1975 (2nd Show Friday) (Side A).wav" + - id: b + label: "Side B" + source: "Gladys Knight - October 1975 (2nd Show Friday) (Side B).wav" + - caption: "Oct 1975 — 2nd show" + slug: "1975-oct-sat" + sides: + - id: a + label: "Side A" + source: "Gladys Knight - October 1975 (2nd Show Saturday) (Side A).wav" + - id: b + label: "Side B" + source: "Gladys Knight - October 1975 (2nd Show Saturday) (Side B).wav" + + - id: 10 + name: "Glen Campbell & Jud Strunk" + slug: glen-campbell-jud-strunk + description: | + Country-pop superstar Glen Campbell ("Wichita Lineman," "Rhinestone + Cowboy") shared this bill with Jud Strunk, a Maine native who reached + the U.S. Top 20 in 1973 with the novelty hit "Daisy a Day." + images: + - "Glenn Campbell 006 1973.jpg" + shows: + - caption: "Dec 1973" + slug: "1973-dec" + sides: + - id: a + label: "Side A" + source: "Glen Campbell_Jud Strunk - December 1973 (Side A).wav" + - id: b + label: "Side B" + source: "Glen Campbell_Jud Strunk - December 1973 (Side B).wav" + + - id: 11 + name: "Glenn Yarbrough & The Limeliters" + slug: glenn-yarbrough-limeliters + description: | + Folk singer who rose to fame with the Limeliters, one of the leading + acts of the early-1960s folk revival, before launching a successful + solo career. He occasionally rejoined the group for reunion tours. + images: [] + shows: + - caption: "Oct 1977" + slug: "1977-oct" + sides: + - id: a + label: "Side A" + source: "Glenn Yarbrough & Limeliters - October 1977 (Side A).wav" + - id: b + label: "Side B" + source: "Glenn Yarbrough & Limeliters - October 1977 (Side B).wav" + + - id: 12 + name: "Harry Belafonte" + slug: harry-belafonte + description: | + American singer, actor, and civil rights activist who brought Caribbean + music to mainstream audiences with "Day-O (The Banana Boat Song)" and + "Jump in the Line." His 1956 album Calypso was the first by a solo + artist to sell a million copies in the United States. + images: [] + shows: + - caption: "May 1976" + slug: "1976-may" + sides: + - id: a + label: "Act I — Side A" + source: "Harry Belafonte - May 1976 (1st Half) (Act I) (Side A).wav" + - id: b + label: "Act I — Side B" + source: "Harry Belafonte - May 1976 (1st Half) (Act I) (Side B).wav" + + - id: 13 + name: "Helen Reddy" + slug: helen-reddy + description: | + Australian-American singer best known for "I Am Woman," an anthem + that became a touchstone of second-wave feminism. Other hits include + "Delta Dawn" and "Angie Baby." + images: + - "HelenReddy001_74.jpg" + shows: + - caption: "1974" + slug: "1974" + sides: + - id: a + label: "Side A" + source: "Helen Reddy - 1974 (Side A).wav" + - id: b + label: "Side B" + source: "Helen Reddy - 1974 (Side B).wav" + + - id: 14 + name: "Florence Henderson & Jim Nabors" + slug: henderson-nabors + description: | + Florence Henderson (Carol Brady of The Brady Bunch) shared this bill + with Jim Nabors (Gomer Pyle of The Andy Griffith Show). Both had + concert-singing careers alongside their television roles and were + regulars on variety programs. + images: + - "Florence Henderson 009 1975.jpg" + - "Jim Nabors 002 1975.jpg" + shows: + - caption: "Jul 1975" + slug: "1975-jul" + sides: + - id: a + label: "Side A" + source: "Henderson & Nabors - July 1975 (Side A).wav" + - id: b + label: "Side B" + source: "Henderson & Nabors - July 1975 (Side B).wav" + + - id: 15 + name: "The Hues Corporation & The 5th Dimension" + slug: hues-corp-5th-dimension + description: | + The Hues Corporation, who topped the charts in 1974 with "Rock the + Boat," shared this bill with The 5th Dimension, the late-1960s + sunshine-pop group behind "Up, Up and Away" and the medley + "Aquarius/Let the Sunshine In," drawn from the musical Hair. + images: + - "The 5th DimentionFD001_74.jpg" + shows: + - caption: "1974" + slug: "1974" + sides: + - id: a + label: "Side A" + source: "Hues Corp; 5th Dimension - 1974 (Side A).wav" + - id: b + label: "Side B" + source: "Hues Corp; 5th Dimension - 1974 (Side B).wav" + + - id: 16 + name: "John Davidson" + slug: john-davidson + description: | + American singer, actor, and television host with a clean-cut style. + A frequent variety-show guest in the 1960s and '70s, he later hosted + That's Incredible! and Hollywood Squares. + images: + - "JohnDavidson002_74 HQ.jpg" + shows: + - caption: "1974" + slug: "1974" + sides: + - id: a + label: "Side A" + source: "John Davidson - 1974 (Side A).wav" + - id: b + label: "Side B" + source: "John Davidson - 1974 (Side B).wav" + + - id: 17 + name: "Johnny Mathis" + slug: johnny-mathis + description: | + American singer whose smooth tenor on songs like "Chances Are," + "Misty," and "It's Not for Me to Say" made him one of the best-selling + recording artists of the twentieth century. + images: + - "Johnny Mathis 004 1975.jpg" + shows: + - caption: "Aug 1975 — 1st show" + slug: "1975-aug-sun-1st" + sides: + - id: a + label: "Side A" + source: "Johnny Mathis - August 1975 (1st Show Sunday) (Side A).wav" + - id: b + label: "Side B" + source: "Johnny Mathis - August 1975 (1st Show Sunday) (Side B).wav" + - caption: "Aug 1975 — 2nd show" + slug: "1975-aug-1st" + sides: + - id: a + label: "Side A" + source: "Johnny Mathis - August 1975 (1st) (Side A).wav" + - id: b + label: "Side B" + source: "Johnny Mathis - August 1975 (1st) (Side B).wav" + - caption: "Aug 1975 — 3rd show" + slug: "1975-aug-2nd-half" + sides: + - id: a + label: "Side A" + source: "Johnny Mathis - August 1975 (2nd Half) (Side A) (No Side B).wav" + - caption: "Aug 1975 — 4th show" + slug: "1975-aug-3rd" + sides: + - id: a + label: "Side A" + source: "Johnny Mathis - August 1975 (3rd) (Side A) (No Side B).wav" + - caption: "Aug 1976 — 1st show" + slug: "1976-aug-1" + sides: + - id: a + label: "Side A" + source: "Johnny Mathis - August 1976 (Side A) 1-13-2021 (tape with stickers on box).wav" + - id: b + label: "Side B" + source: "Johnny Mathis - August 1976 (Side B) 1-13-2021 (tape with stickers on box).wav" + - caption: "Aug 1976 — 2nd show" + slug: "1976-aug-2" + sides: + - id: a + label: "Side A" + source: "Johnny Mathis - August 1976 (Side A) 7-30-2021.wav" + - id: b + label: "Side B" + source: "Johnny Mathis - August 1976 (Side B) 7-30-2021.wav" + + - id: 18 + name: "Kate Smith" + slug: kate-smith + description: | + American singer with a 50-year career and a deep contralto voice, + famous for her 1939 recording of Irving Berlin's "God Bless America." + She hosted radio and television variety programs through the 1950s + and '60s. + images: + - "Kate Smith 006 1975.jpg" + shows: + - caption: "Nov 1975" + slug: "1975-nov" + sides: + - id: a + label: "Side A" + source: "Kate Smith - November 1975 (1st Show) (Side A).wav" + - id: b + label: "Side B" + source: "Kate Smith - November 1975 (1st Show) (Side B).wav" + + - id: 19 + name: "Lainie Kazan" + slug: lainie-kazan + description: | + American singer and actress with a powerful belt and Broadway roots. + As Barbra Streisand's understudy in Funny Girl, she famously stepped + in for one performance in 1964. She later became a fixture in supper + clubs and film comedies. + images: [] + shows: + - caption: "Aug 1975 — 1st show" + slug: "1975-aug-1" + sides: + - id: a + label: "Side A" + source: "Lainie Kazan - August 1975 (Side A).wav" + - id: b + label: "Side B" + source: "Lainie Kazan - August 1975 (Side B).wav" + - caption: "Aug 1975 — 2nd show" + slug: "1975-aug-2" + sides: + - id: a + label: "Side A" + source: "Lainie Kazan - August 1975 (Tape 2) (Side A).wav" + - id: b + label: "Side B" + source: "Lainie Kazan - August 1975 (Tape 2) (Side B).wav" + + - id: 20 + name: "The Love Machine" + slug: love-machine + description: | + International all-female funk and soul group active in the 1970s. + The seven-member ensemble, which included songwriter Sandra Sully, + toured Europe, Asia, and Africa and recorded for labels including + Buddah, Arista, and Motown. + images: [] + shows: + - caption: "Oct 1976" + slug: "1976-oct" + sides: + - id: a + label: "Side A" + source: "Love Machine - October 1976 (Side A).wav" + + - id: 21 + name: "Tony Martin & Cyd Charisse" + slug: martin-charisse + description: | + Husband-and-wife stage act. Tony Martin sang and Cyd Charisse danced; + both were veterans of Hollywood musicals, and they toured together + through the 1970s. + images: [] + shows: + - caption: "Aug 1975" + slug: "1975-aug" + sides: + - id: a + label: "Side A" + source: "Martin;Charisse - August 1975 (Side A).wav" + - id: b + label: "Side B" + source: "Martin;Charisse - August 1975 (Side B).wav" + + - id: 22 + name: "The Mills Brothers" + slug: mills-brothers + description: | + American close-harmony vocal quartet from Piqua, Ohio, active from + the 1920s through the 1980s. Best known for "Paper Doll" (1943), + "Glow Worm," and "You Always Hurt the One You Love." + images: + - "Mills Brothers 003 1975.jpg" + shows: + - caption: "Sep 1975" + slug: "1975-sep" + sides: + - id: a + label: "Side A" + source: "Mills Brothers - September 1975 (Side A).wav" + - id: b + label: "Side B" + source: "Mills Brothers - September 1975 (Side B).wav" + + - id: 23 + name: "Mitzi Gaynor" + slug: mitzi-gaynor + description: | + American actress, singer, and dancer who starred in the 1958 film of + South Pacific opposite Rossano Brazzi. Her touring revues throughout + the 1970s showcased her dance numbers and comic timing. + images: + - "Mitzi Gaynor 007 1976.jpg" + shows: + - caption: "Oct 2, 1977" + slug: "1977-oct" + sides: + - id: i-a + label: "Act I — Side A" + source: "Mitzi Gaynor - October 2nd 1977 (Act I) (Side A).wav" + - id: i-b + label: "Act I — Side B" + source: "Mitzi Gaynor - October 2nd 1977 (Act I) (Side B).wav" + - id: ii-a + label: "Act II — Side A" + source: "Mitzi Gaynor - October 2nd 1977 (Act II) (Side A) (No Side B).wav" + + - id: 24 + name: "Robert Goulet" + slug: robert-goulet + description: | + Canadian-American baritone who originated the role of Lancelot in + Lerner and Loewe's Camelot on Broadway in 1960. A long career on + television, in film, and on the Las Vegas circuit followed. + images: + - "Robert Goulet crop 008 1976.jpg" + shows: + - caption: "Jun 1975 — with Foster Brooks" + slug: "1975-jun" + sides: + - id: a + label: "Side A" + source: "Robert Goulet_Foster Brooks - June 1975 (Side A).wav" + - id: b + label: "Side B" + source: "Robert Goulet_Foster Brooks - June 1975 (Side B).wav" + - caption: "Oct 1976" + slug: "1976-oct" + sides: + - id: a + label: "Side A" + source: "Robert Goulet - October 1976 (Side A).wav" + - id: b + label: "Side B" + source: "Robert Goulet - October 1976 (Side B).wav" + + - id: 25 + name: "Roy Clark" + slug: roy-clark + description: | + American country musician and comedian, longtime co-host of the + syndicated television show Hee Haw. A virtuoso multi-instrumentalist + and a Country Music Hall of Fame inductee. + images: + - "Roy Clark 009 crop1976.jpg" + shows: + - caption: "Apr 1976" + slug: "1976-apr" + sides: + - id: a + label: "Side A" + source: "Roy Clark - April 1976 (Side A).wav" + - id: b + label: "Side B" + source: "Roy Clark - April 1976 (Side B).wav" + + - id: 26 + name: "Sammy Davis Jr." + slug: sammy-davis-jr + description: | + American singer, dancer, actor, and member of the Rat Pack, whose + career spanned more than six decades across Broadway, film, and + television. His signature hit was "The Candy Man." + images: + - "Sammy Davis Jr. 002 1975.jpg" + - "Sammy Davis Jr. 003 1976.jpg" + shows: + - caption: "1975" + slug: "1975" + sides: + - id: a + label: "Side A" + source: "Sammy Davis - 1975 (Side A).wav" + - id: b + label: "Side B" + source: "Sammy Davis - 1975 (Side B).wav" + - caption: "Nov 1976" + slug: "1976-nov" + sides: + - id: a + label: "Side A" + source: "Sammy Davis Jr - November 1976 (Side A).wav" + - id: b + label: "Side B" + source: "Sammy Davis Jr - November 1976 (Side B).wav" + + - id: 27 + name: "Sandler & Young" + slug: sandler-young + description: | + Singing duo Tony Sandler (Belgian) and Ralph Young (American), known + for their multilingual repertoire and tight harmony. A staple of + supper-club and television variety bookings from the late 1960s + onward. + images: + - "Sandler & Young 010 1975.jpg" + shows: + - caption: "Dec 1975 — 1st show" + slug: "1975-dec-mill-run" + sides: + - id: a + label: "Side A" + source: "Sandler_Young - December 1975 ('Mill Run' Label) (Side A).wav" + - id: b + label: "Side B" + source: "Sandler_Young - December 1975 ('Mill Run' Label) (Side B).wav" + - caption: "Dec 1975 — 2nd show" + slug: "1975-dec" + sides: + - id: a + label: "Side A" + source: "Sandler_Young - December 1975 (Side A).wav" + - id: b + label: "Side B" + source: "Sandler_Young - December 1975 (Side B).wav" + + - id: 28 + name: "Shirley Bassey" + slug: shirley-bassey + description: | + Welsh singer with a powerful contralto, internationally known for the + three James Bond film theme songs she recorded: "Goldfinger" (1964), + "Diamonds Are Forever" (1971), and "Moonraker" (1979). + images: + - "Shirley Bassey 011 1976.jpg" + shows: + - caption: "Sep 1976" + slug: "1976-sep" + sides: + - id: a + label: "Side A" + source: "Shirley Bassey - September 1976 (Side A).wav" + - id: b + label: "Side B" + source: "Shirley Bassey - September 1976 (Side B).wav" + + - id: 29 + name: "Steve Lawrence & Eydie Gormé" + slug: steve-lawrence-eydie-gorme + description: | + American husband-and-wife singing duo who met as Steve Allen's Tonight + Show regulars in the 1950s and remained inseparable performers until + Eydie's death in 2013. Each maintained a successful solo career + alongside the duo work. + images: + - "Steve Lawrence_EydieGorme001_73.jpg" + shows: + - caption: "Aug 1977 — 1st show" + slug: "1977-aug-1" + sides: + - id: a + label: "Side A" + source: "Steve & Eydie - August 1977 (Side A).wav" + - id: b + label: "Side B" + source: "Steve & Eydie - August 1977 (Side B).wav" + - caption: "Aug 1977 — 2nd show" + slug: "1977-aug-2" + sides: + - id: a + label: "Side A" + source: "Steve Lawrence & Eydie Gorme - August 1977 (1st Show Sunday) (Side A).wav" + - id: b + label: "Side B" + source: "Steve Lawrence & Eydie Gorme - August 1977 (1st Show Sunday) (Side B).wav" + + - id: 30 + name: "The Dells" + slug: the-dells + description: | + Chicago R&B vocal group whose career stretched from the 1950s + ("Oh What a Night") through the 1990s ("Stay in My Corner"). One of + the longest-lived doo-wop and soul ensembles. + images: [] + shows: + - caption: "1975" + slug: "1975" + sides: + - id: a + label: "Side A" + source: "The Dells - 1975 (Side A).wav" + - id: b + label: "Side B" + source: "The Dells - 1975 (Side B).wav" + + - id: 31 + name: "The Irish Rovers" + slug: irish-rovers + description: | + Canadian-Irish folk group founded in Toronto in the 1960s, best known + for "The Unicorn" (1968) and "Wasn't That a Party" (1981). + images: [] + shows: + - caption: "Jul 1977 — 1st show" + slug: "1977-jul-16" + sides: + - id: a + label: "Side A" + source: "The Irish Rovers - July 16th, 1977 (1st Show) (Side A).wav" + - id: b + label: "Side B" + source: "The Irish Rovers - July 16th, 1977 (1st Show) (Side B).wav" + - caption: "Jul 1977 — 2nd show" + slug: "1977-jul-17" + sides: + - id: a + label: "Side A" + source: "The Irish Rovers - July 17th, 1977 (2nd Show) (Side A).wav" + - id: b + label: "Side B" + source: "The Irish Rovers - July 17th, 1977 (2nd Show) (Side B).wav" + + - id: 32 + name: "The Kingston Trio" + slug: kingston-trio + description: | + American folk group from San Francisco, one of the most popular acts + of the late 1950s and early '60s and a catalyst for the folk revival. + Hits include "Tom Dooley" and "Where Have All the Flowers Gone." + images: [] + shows: + - caption: "Oct 1977" + slug: "1977-oct" + sides: + - id: a + label: "Side A" + source: "The Kingston Trio - October 1977 (Side A).wav" + - id: b + label: "Side B" + source: "The Kingston Trio - October 1977 (Side B).wav" + + - id: 33 + name: "The Temptations & Honey Cone" + slug: temptations-honey-cone + description: | + Motown soul giants The Temptations ("My Girl," "Just My Imagination") + shared this bill with Honey Cone, the trio whose 1971 single + "Want Ads" topped the Billboard Hot 100. + images: + - "TheTemptations002_74.jpg" + shows: + - caption: "Dec 30, 1972" + slug: "1972-dec" + sides: + - id: a + label: "Side A" + source: "The Temptations_Honeycone - December 30th 1972 (Side A).wav" + - id: b + label: "Side B" + source: "The Temptations_Honeycone - December 30th 1972 (Side B).wav" + + - id: 34 + name: "Tom Jones" + slug: tom-jones + description: | + Welsh singer with a powerhouse baritone who broke through with "It's + Not Unusual" in 1965 and went on to build a decades-long Las Vegas + residency career. His variety show This Is Tom Jones aired from 1969 + to 1971. + images: + - "TomJones005_74.jpg" + - "Tom Jones redo 008 1976.jpg" + shows: + - caption: "1975" + slug: "1975" + sides: + - id: a + label: "Side A" + source: "Tom Jones - 1975 (1st Half) (1st Show Sat) (Side A) (No Side B).wav" + - caption: "Sep 1976" + slug: "1976-sep" + sides: + - id: a + label: "Side A" + source: "Tom Jones - September 1976 (Side A).wav" + - id: b + label: "Side B" + source: "Tom Jones - September 1976 (Side B).wav" + + - id: 35 + name: "Vic Damone" + slug: vic-damone + description: | + American singer whose smooth baritone made him a fixture on television + variety shows and a Las Vegas mainstay. Hits include "You're Breaking + My Heart" and "On the Street Where You Live." + images: + - "VicDamone001_74 HQ.jpg" + shows: + - caption: "Dec 1974" + slug: "1974-dec" + sides: + - id: a + label: "Side A" + source: "Vic Damone - December 1974 (Side A).wav" + - id: b + label: "Side B" + source: "Vic Damone - December 1974 (Side B).wav" + + - id: 36 + name: "Vikki Carr" + slug: vikki-carr + description: | + American singer of Mexican-American heritage who scored a pop hit with + "It Must Be Him" in 1967 before pivoting to a Spanish-language career + that earned her multiple Grammy and Latin Grammy awards. + images: + - "Vikki Carr 004 1976.jpg" + - "Vikki Carr 006 1975.jpg" + shows: + - caption: "1975" + slug: "1975" + sides: + - id: a + label: "Side A" + source: "Vikki Carr - 1975 (Side A).wav" + - id: b + label: "Side B" + source: "Vikki Carr - 1975 (Side B).wav" + - caption: "May 1975" + slug: "1975-may" + sides: + - id: a + label: "Side A" + source: "Vikki Carr - Sunday May 1975 (Second Half) (Red by WJT) (Side A).wav" + - id: b + label: "Side B" + source: "Vikki Carr - Sunday May 1975 (Second Half) (Red by WJT) (Side B).wav" diff --git a/content/images/AL MARTINO001_74.jpg b/content/images/AL MARTINO001_74.jpg new file mode 100644 index 0000000..6a812a2 Binary files /dev/null and b/content/images/AL MARTINO001_74.jpg differ diff --git a/content/images/Dionne Warwick 1974 from Don Leavitt.jpg b/content/images/Dionne Warwick 1974 from Don Leavitt.jpg new file mode 100644 index 0000000..558d2b1 Binary files /dev/null and b/content/images/Dionne Warwick 1974 from Don Leavitt.jpg differ diff --git a/content/images/Don Ho 001 1980.jpg b/content/images/Don Ho 001 1980.jpg new file mode 100644 index 0000000..d91ab7b Binary files /dev/null and b/content/images/Don Ho 001 1980.jpg differ diff --git a/content/images/Don Rickles 002 1975.jpg b/content/images/Don Rickles 002 1975.jpg new file mode 100644 index 0000000..f53a81c Binary files /dev/null and b/content/images/Don Rickles 002 1975.jpg differ diff --git a/content/images/Eddy Arnold 001 1975.jpg b/content/images/Eddy Arnold 001 1975.jpg new file mode 100644 index 0000000..30be557 Binary files /dev/null and b/content/images/Eddy Arnold 001 1975.jpg differ diff --git a/content/images/Engelbert Humperdinck 003 1975.jpg b/content/images/Engelbert Humperdinck 003 1975.jpg new file mode 100644 index 0000000..d336e11 Binary files /dev/null and b/content/images/Engelbert Humperdinck 003 1975.jpg differ diff --git a/content/images/Florence Henderson 009 1975.jpg b/content/images/Florence Henderson 009 1975.jpg new file mode 100644 index 0000000..733dc8c Binary files /dev/null and b/content/images/Florence Henderson 009 1975.jpg differ diff --git a/content/images/Frankie Laine 002 1975.jpg b/content/images/Frankie Laine 002 1975.jpg new file mode 100644 index 0000000..28e87a6 Binary files /dev/null and b/content/images/Frankie Laine 002 1975.jpg differ diff --git a/content/images/Gladys Knight & the Pips 001 1975.jpg b/content/images/Gladys Knight & the Pips 001 1975.jpg new file mode 100644 index 0000000..41c6890 Binary files /dev/null and b/content/images/Gladys Knight & the Pips 001 1975.jpg differ diff --git a/content/images/Gladys Knight and the Pips 001 1975.jpg b/content/images/Gladys Knight and the Pips 001 1975.jpg new file mode 100644 index 0000000..585f1bb Binary files /dev/null and b/content/images/Gladys Knight and the Pips 001 1975.jpg differ diff --git a/content/images/Glenn Campbell 006 1973.jpg b/content/images/Glenn Campbell 006 1973.jpg new file mode 100644 index 0000000..fb83e3f Binary files /dev/null and b/content/images/Glenn Campbell 006 1973.jpg differ diff --git a/content/images/HelenReddy001_74.jpg b/content/images/HelenReddy001_74.jpg new file mode 100644 index 0000000..f625039 Binary files /dev/null and b/content/images/HelenReddy001_74.jpg differ diff --git a/content/images/Jim Nabors 002 1975.jpg b/content/images/Jim Nabors 002 1975.jpg new file mode 100644 index 0000000..a05e48d Binary files /dev/null and b/content/images/Jim Nabors 002 1975.jpg differ diff --git a/content/images/JohnDavidson002_74 HQ.jpg b/content/images/JohnDavidson002_74 HQ.jpg new file mode 100644 index 0000000..293b599 Binary files /dev/null and b/content/images/JohnDavidson002_74 HQ.jpg differ diff --git a/content/images/Johnny Mathis 004 1975.jpg b/content/images/Johnny Mathis 004 1975.jpg new file mode 100644 index 0000000..ee530ea Binary files /dev/null and b/content/images/Johnny Mathis 004 1975.jpg differ diff --git a/content/images/Kate Smith 006 1975.jpg b/content/images/Kate Smith 006 1975.jpg new file mode 100644 index 0000000..68509ae Binary files /dev/null and b/content/images/Kate Smith 006 1975.jpg differ diff --git a/content/images/Mills Brothers 003 1975.jpg b/content/images/Mills Brothers 003 1975.jpg new file mode 100644 index 0000000..b1d1b69 Binary files /dev/null and b/content/images/Mills Brothers 003 1975.jpg differ diff --git a/content/images/Mitzi Gaynor 007 1976.jpg b/content/images/Mitzi Gaynor 007 1976.jpg new file mode 100644 index 0000000..b61d41b Binary files /dev/null and b/content/images/Mitzi Gaynor 007 1976.jpg differ diff --git a/content/images/Robert Goulet crop 008 1976.jpg b/content/images/Robert Goulet crop 008 1976.jpg new file mode 100644 index 0000000..17dfc6f Binary files /dev/null and b/content/images/Robert Goulet crop 008 1976.jpg differ diff --git a/content/images/Roy Clark 009 crop1976.jpg b/content/images/Roy Clark 009 crop1976.jpg new file mode 100644 index 0000000..92b5cab Binary files /dev/null and b/content/images/Roy Clark 009 crop1976.jpg differ diff --git a/content/images/Sammy Davis Jr. 002 1975.jpg b/content/images/Sammy Davis Jr. 002 1975.jpg new file mode 100644 index 0000000..64210bb Binary files /dev/null and b/content/images/Sammy Davis Jr. 002 1975.jpg differ diff --git a/content/images/Sammy Davis Jr. 003 1976.jpg b/content/images/Sammy Davis Jr. 003 1976.jpg new file mode 100644 index 0000000..3939cc9 Binary files /dev/null and b/content/images/Sammy Davis Jr. 003 1976.jpg differ diff --git a/content/images/Sandler & Young 010 1975.jpg b/content/images/Sandler & Young 010 1975.jpg new file mode 100644 index 0000000..3b8b0ca Binary files /dev/null and b/content/images/Sandler & Young 010 1975.jpg differ diff --git a/content/images/Shirley Bassey 011 1976.jpg b/content/images/Shirley Bassey 011 1976.jpg new file mode 100644 index 0000000..f9da521 Binary files /dev/null and b/content/images/Shirley Bassey 011 1976.jpg differ diff --git a/content/images/Steve Lawrence_EydieGorme001_73.jpg b/content/images/Steve Lawrence_EydieGorme001_73.jpg new file mode 100644 index 0000000..596c928 Binary files /dev/null and b/content/images/Steve Lawrence_EydieGorme001_73.jpg differ diff --git a/content/images/The 5th DimentionFD001_74.jpg b/content/images/The 5th DimentionFD001_74.jpg new file mode 100644 index 0000000..53aade9 Binary files /dev/null and b/content/images/The 5th DimentionFD001_74.jpg differ diff --git a/content/images/TheTemptations002_74.jpg b/content/images/TheTemptations002_74.jpg new file mode 100644 index 0000000..ed56f86 Binary files /dev/null and b/content/images/TheTemptations002_74.jpg differ diff --git a/content/images/Tom Jones redo 008 1976.jpg b/content/images/Tom Jones redo 008 1976.jpg new file mode 100644 index 0000000..de353b7 Binary files /dev/null and b/content/images/Tom Jones redo 008 1976.jpg differ diff --git a/content/images/TomJones005_74.jpg b/content/images/TomJones005_74.jpg new file mode 100644 index 0000000..5e24858 Binary files /dev/null and b/content/images/TomJones005_74.jpg differ diff --git a/content/images/VicDamone001_74 HQ.jpg b/content/images/VicDamone001_74 HQ.jpg new file mode 100644 index 0000000..4d38b8f Binary files /dev/null and b/content/images/VicDamone001_74 HQ.jpg differ diff --git a/content/images/Vikki Carr 004 1976.jpg b/content/images/Vikki Carr 004 1976.jpg new file mode 100644 index 0000000..a6a94ab Binary files /dev/null and b/content/images/Vikki Carr 004 1976.jpg differ diff --git a/content/images/Vikki Carr 006 1975.jpg b/content/images/Vikki Carr 006 1975.jpg new file mode 100644 index 0000000..790e64a Binary files /dev/null and b/content/images/Vikki Carr 006 1975.jpg differ diff --git a/content/reference/reference-the_lettermen-mill_run.jpg b/content/reference/reference-the_lettermen-mill_run.jpg new file mode 100644 index 0000000..1e202be Binary files /dev/null and b/content/reference/reference-the_lettermen-mill_run.jpg differ diff --git a/content/stops.yaml b/content/stops.yaml index 136e373..56ae8cc 100644 --- a/content/stops.yaml +++ b/content/stops.yaml @@ -1,30 +1,324 @@ # Source of truth for exhibit content. -# A build step generates web/src/lib/stops.json from this file. +# A build step generates web/src/lib/stops.json from this file (and +# transcodes content/audio/*.wav → web/static/audio/*.opus, similarly for images). # -# Each stop has: -# id — integer, used in URLs and as the asset basename -# title — short label, shown in the grid and on the detail page -# audio — filename in content/audio/, transcoded to web/static/audio/ -# image — filename in content/images/, transcoded to web/static/images/ -# caption — short line shown beneath the title +# Each stop: +# id — integer, used in URLs and as the asset basename prefix +# title — performer(s) as printed on the exhibit +# audio — source filename in content/audio/ +# image — source filename in content/images/ +# caption — short line shown beneath the title (date / show info) # description — longer text shown below the audio player (markdown allowed) exhibit: - title: "TBD" - subtitle: "TBD" + title: "Mill Run Playhouse-Theatre" + subtitle: "" stops: - id: 1 - title: "Welcome" - audio: 01-welcome.wav - image: 01-welcome.png - caption: "An introduction to the exhibit." - description: | - Placeholder for the full description shown on the stop detail page. + title: "Al Martino" + audio: 01-al-martino.wav + image: 01-al-martino.png + caption: "1974" + description: "" - id: 2 - title: "Stop two placeholder" - audio: 02.wav - image: 02.png - caption: "" + title: "Caterina Valente" + audio: 02-caterina-valente.wav + image: 02-caterina-valente.png + caption: "Aug 1976" + description: "" + + - id: 3 + title: "Dionne Warwicke" + audio: 03-dionne-warwicke.wav + image: 03-dionne-warwicke.png + caption: "1974" + description: "" + + - id: 4 + title: "Don Ho" + audio: 04-don-ho.wav + image: 04-don-ho.png + caption: "Jun 26, 1977" + description: "" + + - id: 5 + title: "Don Rickles" + audio: 05-don-rickles.wav + image: 05-don-rickles.png + caption: "May 25, 1975" + description: "" + + - id: 6 + title: "Eddy Arnold" + audio: 06-eddy-arnold.wav + image: 06-eddy-arnold.png + caption: "1975" + description: "" + + - id: 7 + title: "Engelbert Humperdinck" + audio: 07-engelbert-humperdinck.wav + image: 07-engelbert-humperdinck.png + caption: "Oct 1975" + description: "" + + - id: 8 + title: "Frankie Laine" + audio: 08-frankie-laine.wav + image: 08-frankie-laine.png + caption: "Sun, Sep 1975 — 2nd show" + description: "" + + - id: 9 + title: "Gladys Knight" + audio: 09-gladys-knight-aug-1977.wav + image: 09-gladys-knight-aug-1977.png + caption: "Sat, Aug 1977 — 1st & 2nd show" + description: "" + + - id: 10 + title: "Gladys Knight" + audio: 10-gladys-knight-oct-1975-fri.wav + image: 10-gladys-knight-oct-1975-fri.png + caption: "Fri, Oct 1975 — 2nd show" + description: "" + + - id: 11 + title: "Gladys Knight" + audio: 11-gladys-knight-oct-1975-sat.wav + image: 11-gladys-knight-oct-1975-sat.png + caption: "Sat, Oct 1975 — 2nd show" + description: "" + + - id: 12 + title: "Glenn Campbell & Jud Strunk" + audio: 12-glenn-campbell-jud-strunk.wav + image: 12-glenn-campbell-jud-strunk.png + caption: "Dec 1973" + description: "" + + - id: 13 + title: "Glenn Yarbrough & The Limeliters" + audio: 13-glenn-yarbrough-limeliters.wav + image: 13-glenn-yarbrough-limeliters.png + caption: "Oct 1977" + description: "" + + - id: 14 + title: "Harry Belafonte" + audio: 14-harry-belafonte.wav + image: 14-harry-belafonte.png + caption: "May 1976" + description: "" + + - id: 15 + title: "Helen Reddy" + audio: 15-helen-reddy.wav + image: 15-helen-reddy.png + caption: "1974" + description: "" + + - id: 16 + title: "Henderson & Nabors" + audio: 16-henderson-nabors.wav + image: 16-henderson-nabors.png + caption: "Jul 1975" + description: "" + + - id: 17 + title: "Hues Corp & 5th Dimension" + audio: 17-hues-corp-5th-dimension.wav + image: 17-hues-corp-5th-dimension.png + caption: "1974" + description: "" + + - id: 18 + title: "John Davidson" + audio: 18-john-davidson.wav + image: 18-john-davidson.png + caption: "1974" + description: "" + + - id: 19 + title: "Johnny Mathis" + audio: 19-johnny-mathis-aug-1975.wav + image: 19-johnny-mathis-aug-1975.png + caption: "Sun, Aug 1975 — 1st show" + description: "" + + - id: 20 + title: "Johnny Mathis" + audio: 20-johnny-mathis-aug-1976.wav + image: 20-johnny-mathis-aug-1976.png + caption: "Aug 1976" + description: "" + + - id: 21 + title: "Kate Smith" + audio: 21-kate-smith.wav + image: 21-kate-smith.png + caption: "Nov 1975" + description: "" + + - id: 22 + title: "Lainie Kazan" + audio: 22-lainie-kazan.wav + image: 22-lainie-kazan.png + caption: "Aug 1975" + description: "" + + - id: 23 + title: "Love Machine" + audio: 23-love-machine.wav + image: 23-love-machine.png + caption: "Oct 1976" + description: "" + + - id: 24 + title: "Martin & Charisse" + audio: 24-martin-charisse.wav + image: 24-martin-charisse.png + caption: "Aug 1975" + description: "" + + - id: 25 + title: "Mill Bros" + audio: 25-mill-bros.wav + image: 25-mill-bros.png + caption: "Sep 1975" + description: "" + + - id: 26 + title: "Mitzi Gaynor" + audio: 26-mitzi-gaynor.wav + image: 26-mitzi-gaynor.png + caption: "Oct 2, 1977" + description: "" + + - id: 27 + title: "Robert Goulet" + audio: 27-robert-goulet.wav + image: 27-robert-goulet.png + caption: "Oct 1976" + description: "" + + - id: 28 + title: "Goulet & Foster Brooks" + audio: 28-goulet-foster-brooks.wav + image: 28-goulet-foster-brooks.png + caption: "Jun 1975" + description: "" + + - id: 29 + title: "Roy Clark" + audio: 29-roy-clark.wav + image: 29-roy-clark.png + caption: "Apr 1976" + description: "" + + - id: 30 + title: "Sammy Davis" + audio: 30-sammy-davis-1975.wav + image: 30-sammy-davis-1975.png + caption: "1975" + description: "" + + - id: 31 + title: "Sammy Davis Jr" + audio: 31-sammy-davis-jr-1976.wav + image: 31-sammy-davis-jr-1976.png + caption: "Nov 1976" + description: "" + + - id: 32 + title: "Sandler & Young" + audio: 32-sandler-young.wav + image: 32-sandler-young.png + caption: "Dec 1975" + description: "" + + - id: 33 + title: "Shirley Bassey" + audio: 33-shirley-bassey.wav + image: 33-shirley-bassey.png + caption: "Sep 1976" + description: "" + + - id: 34 + title: "Steve Lawrence & Eydie Gorme" + audio: 34-steve-lawrence-eydie-gorme.wav + image: 34-steve-lawrence-eydie-gorme.png + caption: "Aug 1977" + description: "" + + - id: 35 + title: "The Dells" + audio: 35-the-dells.wav + image: 35-the-dells.png + caption: "1975" + description: "" + + - id: 36 + title: "The Irish Rovers" + audio: 36-irish-rovers-jul16.wav + image: 36-irish-rovers-jul16.png + caption: "Jul 16, 1977 — 1st show" + description: "" + + - id: 37 + title: "The Irish Rovers" + audio: 37-irish-rovers-jul17.wav + image: 37-irish-rovers-jul17.png + caption: "Jul 17, 1977 — 2nd show" + description: "" + + - id: 38 + title: "The Kingston Trio" + audio: 38-kingston-trio.wav + image: 38-kingston-trio.png + caption: "Oct 1977" + description: "" + + - id: 39 + title: "The Temptations" + audio: 39-temptations.wav + image: 39-temptations.png + caption: "Dec 30, 1972" + description: "" + + - id: 40 + title: "Tom Jones" + audio: 40-tom-jones-1975.wav + image: 40-tom-jones-1975.png + caption: "Sat, 1975 — 1st show" + description: "" + + - id: 41 + title: "Tom Jones" + audio: 41-tom-jones-1976.wav + image: 41-tom-jones-1976.png + caption: "Sep 1976" + description: "" + + - id: 42 + title: "Vic Damone" + audio: 42-vic-damone.wav + image: 42-vic-damone.png + caption: "Dec 1974" + description: "" + + - id: 43 + title: "Vikki Carr" + audio: 43-vikki-carr-1975.wav + image: 43-vikki-carr-1975.png + caption: "1975" + description: "" + + - id: 44 + title: "Vikki Carr" + audio: 44-vikki-carr-may-1975.wav + image: 44-vikki-carr-may-1975.png + caption: "Sun, May 1975" description: "" diff --git a/flake.nix b/flake.nix index 14060c1..786a9d8 100644 --- a/flake.nix +++ b/flake.nix @@ -21,6 +21,7 @@ ffmpeg imagemagick yq-go + jq ]; }; } diff --git a/server/main.go b/server/main.go index d12c9e1..aebbc5d 100644 --- a/server/main.go +++ b/server/main.go @@ -16,7 +16,6 @@ import ( type server struct { staticDir string password string - qrKey string secret []byte secure bool loginLimiter *rateLimiter @@ -26,20 +25,13 @@ func main() { addr := flag.String("addr", ":8181", "listen address") staticDir := flag.String("static", "../web/build", "static asset directory") insecure := flag.Bool("insecure", false, "drop Secure flag from cookie (dev only)") - strict := flag.Bool("strict", false, "production mode: refuse to start without DOCENT_COOKIE_SECRET and DOCENT_QR_KEY") + strict := flag.Bool("strict", false, "production mode: refuse to start without DOCENT_COOKIE_SECRET") flag.Parse() password := os.Getenv("DOCENT_PASSWORD") if password == "" { log.Fatal("DOCENT_PASSWORD must be set") } - qrKey := os.Getenv("DOCENT_QR_KEY") - if qrKey == "" { - if *strict { - log.Fatal("DOCENT_QR_KEY must be set in -strict mode") - } - log.Println("warning: DOCENT_QR_KEY not set; QR-code auto-login disabled") - } cookieSecret := os.Getenv("DOCENT_COOKIE_SECRET") if *strict && cookieSecret == "" { @@ -61,7 +53,6 @@ func main() { s := &server{ staticDir: abs, password: password, - qrKey: qrKey, secret: secret, secure: !*insecure, loginLimiter: newRateLimiter(8, time.Minute), @@ -69,7 +60,6 @@ func main() { mux := http.NewServeMux() mux.HandleFunc("POST /api/login", s.handleLogin) - mux.HandleFunc("POST /api/key", s.handleKey) mux.HandleFunc("POST /api/logout", s.handleLogout) mux.HandleFunc("GET /api/me", s.handleMe) mux.Handle("GET /audio/", s.requireAuth(http.HandlerFunc(s.serveStatic))) @@ -126,26 +116,6 @@ func (s *server) handleLogin(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) } -func (s *server) handleKey(w http.ResponseWriter, r *http.Request) { - if !s.loginLimiter.allow(clientIP(r)) { - w.Header().Set("Retry-After", "60") - http.Error(w, "too many attempts", http.StatusTooManyRequests) - return - } - var body struct { - Key string `json:"key"` - } - err := json.NewDecoder(r.Body).Decode(&body) - // An empty qrKey means the QR auto-login is disabled — never accept any value. - if err != nil || s.qrKey == "" || !constantTimeEqualString(body.Key, s.qrKey) { - time.Sleep(250 * time.Millisecond) - http.Error(w, "unauthorized", http.StatusUnauthorized) - return - } - s.setSessionCookie(w) - w.WriteHeader(http.StatusNoContent) -} - func (s *server) handleLogout(w http.ResponseWriter, r *http.Request) { s.clearSessionCookie(w) w.WriteHeader(http.StatusNoContent) @@ -207,7 +177,6 @@ func (s *server) serveSPA(w http.ResponseWriter, r *http.Request) { return } - // Unknown path → SPA fallback. w.Header().Set("Cache-Control", "no-cache") http.ServeFile(w, r, filepath.Join(s.staticDir, "index.html")) } diff --git a/web/package-lock.json b/web/package-lock.json index 844eacd..ddd65f0 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -7,6 +7,14 @@ "": { "name": "docent-web", "version": "0.0.1", + "dependencies": { + "@fontsource/bebas-neue": "^5.1.0", + "@fontsource/cantarell": "^5.1.0", + "@fontsource/cormorant-garamond": "^5.1.0", + "@fontsource/inspiration": "^5.1.0", + "@fontsource/oswald": "^5.1.0", + "@fontsource/playwrite-de-sas": "^5.1.0" + }, "devDependencies": { "@sveltejs/adapter-static": "^3.0.5", "@sveltejs/kit": "^2.7.0", @@ -408,6 +416,60 @@ "node": ">=12" } }, + "node_modules/@fontsource/bebas-neue": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@fontsource/bebas-neue/-/bebas-neue-5.2.7.tgz", + "integrity": "sha512-DsmBrmq55d9BCU0mt4DT4RZDdH8vhWRKEUOfbuNB1EEjMuwbtFvM8N+3gIlkYSFbsb10P8Q19BV5OdpMu2h0fA==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/cantarell": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource/cantarell/-/cantarell-5.2.8.tgz", + "integrity": "sha512-MQKwrtpAmd5disYpoTcxliqQjzn/5msazpycrS57ppNNV8HKDP3RYpVTcEw8nYvWVoXwqSOkU2/RHmo3VqckkQ==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/cormorant-garamond": { + "version": "5.2.11", + "resolved": "https://registry.npmjs.org/@fontsource/cormorant-garamond/-/cormorant-garamond-5.2.11.tgz", + "integrity": "sha512-5JjpN023lhA5soijgVT0BdRGzmlijm402ppjccMd6h+vRE0mX2lJnE+41UPfnlidrkV9/rCo1mf58WZlHnB0CA==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/inspiration": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@fontsource/inspiration/-/inspiration-5.2.7.tgz", + "integrity": "sha512-Unh3kgWHn024288t5u8EiBgqR6i+PdHNCjrvPIhgCRS02XlgwoXQeWzUPCknWY/5WRsGJUsu3sJxo+0sMM7esQ==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/oswald": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource/oswald/-/oswald-5.2.8.tgz", + "integrity": "sha512-DFoPK1BqsFIQhVjs2xgO/oL9veF9oUJgFojEWIU+nazUrfqsBDtAMI4n9L9CZFX3kQNDLcCyzKx1xoEDvQT4WQ==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/playwrite-de-sas": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@fontsource/playwrite-de-sas/-/playwrite-de-sas-5.2.7.tgz", + "integrity": "sha512-ELyLve5ZbiEm3Ra2itvO4wjlqrjS6G/6SYTh7K8Mdbi/Ib/PEjZcyCzZChfw0lCx62AJWk5HYvGeXqS6KVjPaA==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", diff --git a/web/package.json b/web/package.json index ecc0606..2df743f 100644 --- a/web/package.json +++ b/web/package.json @@ -9,6 +9,14 @@ "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json" }, + "dependencies": { + "@fontsource/bebas-neue": "^5.1.0", + "@fontsource/cantarell": "^5.1.0", + "@fontsource/cormorant-garamond": "^5.1.0", + "@fontsource/inspiration": "^5.1.0", + "@fontsource/oswald": "^5.1.0", + "@fontsource/playwrite-de-sas": "^5.1.0" + }, "devDependencies": { "@sveltejs/adapter-static": "^3.0.5", "@sveltejs/kit": "^2.7.0", diff --git a/web/src/app.css b/web/src/app.css index 4c58f6c..5fb17d1 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -1,16 +1,54 @@ +@import '@fontsource/playwrite-de-sas/100.css'; +@import '@fontsource/cormorant-garamond/400.css'; +@import '@fontsource/cormorant-garamond/400-italic.css'; +@import '@fontsource/cormorant-garamond/600.css'; +@import '@fontsource/cantarell/400.css'; +@import '@fontsource/cantarell/700.css'; +@import '@fontsource/bebas-neue/400.css'; +@import '@fontsource/oswald/400.css'; +@import '@fontsource/oswald/500.css'; + +/* Inspiration is used only for the uppercase letters in script titles + * (matches the brand treatment where the leading caps are extra-flourished). + * The unicode-range restriction means the browser falls through to + * Playwrite DK Loopet for everything else automatically — no markup spans. + */ +@font-face { + font-family: 'Inspiration Caps'; + font-style: normal; + /* Inspiration is only published at weight 400; declare a wide range here + * so this face matches whatever weight the title-script class requests. */ + font-weight: 100 900; + font-display: swap; + src: + url('@fontsource/inspiration/files/inspiration-latin-400-normal.woff2') format('woff2'), + url('@fontsource/inspiration/files/inspiration-latin-400-normal.woff') format('woff'); + unicode-range: U+0041-005A; +} + :root { - --bg: #faf7f2; - --bg-elev: #ffffff; - --ink: #2a2520; - --ink-mute: #6b6157; - --accent: #6e3a1f; - --rule: #e8e0d4; + /* Mill Run brand palette */ + --bg: #b9cfba; /* sage — main background */ + --bg-card: #ffffff; /* card / tile background on the sage page */ + --bg-callout: #4b6c64; /* deep teal — callout panels */ + --bg-paper: #f6ecd6; /* warm ivory — Stagebill program paper */ + --ink: #4b6c64; /* primary body text */ + --ink-on-callout: #b9cfba; /* text on dark teal callouts */ + --ink-mute: #6b8a82; /* secondary teal for de-emphasis */ + --ink-deep: #11281f; /* very dark green for high contrast on paper/light bg */ + --title: #c33b23; /* red-orange — titles, focus, errors (darkened from #e1442b) */ + --highlight: #f1b416; /* yellow — accents, scrub thumb, indicators */ + --rule: #9bb59c; /* darker sage — borders/dividers */ + --shadow: 0 1px 2px rgba(75, 108, 100, 0.12), 0 8px 24px rgba(75, 108, 100, 0.18); + --radius: 14px; --radius-lg: 22px; - --shadow: 0 1px 2px rgba(42, 37, 32, 0.06), 0 8px 24px rgba(42, 37, 32, 0.08); --font-sans: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; --font-serif: ui-serif, Georgia, 'Times New Roman', serif; + --font-script: 'Inspiration Caps', 'Playwrite DE SAS', cursive; + --font-marquee: 'Bebas Neue', 'Anton', 'Oswald', 'Arial Narrow', sans-serif; + --font-editorial: 'Cormorant Garamond', 'Iowan Old Style', Cambria, Georgia, serif; color-scheme: light; } @@ -41,14 +79,18 @@ body { } h1, h2, h3 { - font-family: var(--font-serif); font-weight: 600; - letter-spacing: -0.01em; + letter-spacing: -0.005em; margin: 0; + color: var(--title); +} + +h2, h3 { + font-family: var(--font-serif); } a { - color: var(--accent); + color: var(--title); text-decoration: none; } @@ -70,3 +112,101 @@ img { display: block; max-width: 100%; } + +/* Title-style headings (the script-font, brand-red ones). + * Apply via `.title-script` to any heading we want to render in the + * exhibit's primary script-and-red treatment. + */ +.title-script { + font-family: var(--font-script); + color: var(--title); + font-weight: 100; + line-height: 1.25; + letter-spacing: 0; + /* Playwrite has long ascenders/descenders; allow them to breathe. */ + padding: 0.1em 0.05em; +} + +/* Marquee letter-board: white sign with bold black changeable-letter text. + * Modeled on the actual Mill Run marquee from the 1970s. */ +.marquee-panel { + position: relative; + display: inline-block; + max-width: 100%; + color: #111111; + /* Scale side padding with viewport so phone screens aren't doubly cramped. */ + padding: clamp(1.5rem, 5vw, 2.5rem) clamp(1.25rem, 5vw, 3rem) clamp(1.75rem, 5vw, 2.75rem); + border-radius: 4px; + border: 1px solid rgba(0, 0, 0, 0.08); + background: #ffffff; + box-shadow: + 0 1px 0 rgba(0, 0, 0, 0.08) inset, + 0 6px 24px rgba(75, 108, 100, 0.18); + text-align: center; +} + +/* Bulb ring: a single stroked rect with `stroke-dasharray: 0 N` and round + * caps renders evenly-spaced circular dots that traverse the corners cleanly. */ +.marquee-panel .bulbs { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + pointer-events: none; +} +.marquee-panel .bulbs rect { + x: 11px; + y: 11px; + width: calc(100% - 22px); + height: calc(100% - 22px); + rx: 3px; + fill: none; + stroke: var(--highlight); + stroke-width: 7; + /* pathLength is set on each rect in the template; dasharray "0 2" + * over a path of length N places N/2 evenly-spaced dots, so the first + * and last align exactly with no leftover corner gap. */ + stroke-dasharray: 0 2; + stroke-linecap: round; +} + +.title-marquee { + font-family: var(--font-marquee); + color: var(--title); + font-weight: 400; + line-height: 0.95; + letter-spacing: 0.04em; + text-transform: uppercase; + margin: 0; + text-align: center; +} + +.marquee-body { + font-family: var(--font-marquee); + color: #111111; + font-weight: 400; + font-size: clamp(0.95rem, 2.4vw, 1.25rem); + line-height: 1.15; + letter-spacing: 0.08em; + text-transform: uppercase; + margin: 0.4rem 0 0; + text-align: center; +} +.marquee-body + .marquee-body { + margin-top: 0.25rem; +} + +/* The yellow heading band that sits behind a script title. + * `max-width: 100%` keeps it inside narrow containers (login card on phones). + * `overflow-wrap: break-word` lets long titles wrap rather than overflow. + */ +.band { + display: inline-block; + max-width: 100%; + padding: 2rem 3.25rem 2.25rem; + background: var(--highlight); + border-radius: 2.5rem; + box-shadow: 0 1px 0 rgba(75, 108, 100, 0.08); + overflow-wrap: break-word; + hyphens: auto; +} diff --git a/web/src/app.html b/web/src/app.html index cdc6961..42415c6 100644 --- a/web/src/app.html +++ b/web/src/app.html @@ -11,7 +11,7 @@ - Mill Run + Mill Run Theatre %sveltekit.head% diff --git a/web/src/lib/artists.json b/web/src/lib/artists.json new file mode 100644 index 0000000..a460314 --- /dev/null +++ b/web/src/lib/artists.json @@ -0,0 +1,1230 @@ +{ + "exhibit": { + "title": "Mill Run Theatre", + "description": "Listen to performances from the Mill Run\nSelect from the artists below\n" + }, + "artists": [ + { + "id": 1, + "name": "Al Martino", + "slug": "al-martino", + "description": "American singer of Italian descent best known for the ballad \"Here\nin My Heart\" and the perennial \"Spanish Eyes.\" Martino also played\nthe singer Johnny Fontane in The Godfather and its sequel.", + "images": [ + "/images/al-martino-1.webp" + ], + "shows": [ + { + "caption": "1974", + "slug": "1974", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/al-martino-1974-a.opus" + } + ] + } + ] + }, + { + "id": 2, + "name": "Caterina Valente", + "slug": "caterina-valente", + "description": "Italo-French entertainer who recorded in over a dozen languages across\na career spanning the 1950s through the 1980s. A jazz-trained singer,\nshe was a fixture of European and American television variety shows.", + "images": [], + "shows": [ + { + "caption": "Aug 1976", + "slug": "1976-aug", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/caterina-valente-1976-aug-a.opus" + } + ] + } + ] + }, + { + "id": 3, + "name": "Dionne Warwick", + "slug": "dionne-warwicke", + "description": "American singer who became the definitive voice of the Burt Bacharach\nand Hal David songbook in the 1960s: \"Walk On By,\" \"Anyone Who Had a\nHeart,\" and \"Do You Know the Way to San Jose.\" From 1971 to 1975 she\nperformed under the alternate spelling \"Warwicke.\"", + "images": [ + "/images/dionne-warwicke-1.webp" + ], + "shows": [ + { + "caption": "1974", + "slug": "1974", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/dionne-warwicke-1974-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/dionne-warwicke-1974-b.opus" + } + ] + } + ] + }, + { + "id": 4, + "name": "Don Ho", + "slug": "don-ho", + "description": "Hawaiian musician and entertainer whose 1966 single \"Tiny Bubbles\"\nbecame his lifelong signature. He performed nightly in Waikiki for\ndecades and toured the mainland into the 2000s.", + "images": [ + "/images/don-ho-1.webp" + ], + "shows": [ + { + "caption": "Jun 26, 1977 — 1st show", + "slug": "1977-jun-1", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/don-ho-1977-jun-1-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/don-ho-1977-jun-1-b.opus" + } + ] + }, + { + "caption": "Jun 26, 1977 — 2nd show", + "slug": "1977-jun-2", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/don-ho-1977-jun-2-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/don-ho-1977-jun-2-b.opus" + } + ] + } + ] + }, + { + "id": 5, + "name": "Don Rickles", + "slug": "don-rickles", + "description": "American stand-up comedian known for insult comedy aimed affectionately\nat celebrities and audience members alike. A regular on television\nvariety and talk shows from the 1960s onward.", + "images": [ + "/images/don-rickles-1.webp" + ], + "shows": [ + { + "caption": "May 25, 1975", + "slug": "1975-may", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/don-rickles-1975-may-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/don-rickles-1975-may-b.opus" + } + ] + } + ] + }, + { + "id": 6, + "name": "Eddy Arnold", + "slug": "eddy-arnold", + "description": "Tennessee-born country singer who placed more than 145 songs on the\ncountry charts across five decades. Hits include \"Make the World Go\nAway\" and \"Cattle Call.\"", + "images": [ + "/images/eddy-arnold-1.webp" + ], + "shows": [ + { + "caption": "1975", + "slug": "1975", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/eddy-arnold-1975-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/eddy-arnold-1975-b.opus" + } + ] + }, + { + "caption": "Nov 1977", + "slug": "1977-nov", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/eddy-arnold-1977-nov-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/eddy-arnold-1977-nov-b.opus" + } + ] + } + ] + }, + { + "id": 7, + "name": "Engelbert Humperdinck", + "slug": "engelbert-humperdinck", + "description": "British pop singer whose 1967 single \"Release Me\" famously kept the\nBeatles' \"Strawberry Fields Forever\" from reaching number one in the\nUK. His stage name was borrowed from a 19th-century German opera\ncomposer.", + "images": [ + "/images/engelbert-humperdinck-1.webp" + ], + "shows": [ + { + "caption": "Oct 1975", + "slug": "1975-oct", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/engelbert-humperdinck-1975-oct-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/engelbert-humperdinck-1975-oct-b.opus" + } + ] + } + ] + }, + { + "id": 8, + "name": "Frankie Laine", + "slug": "frankie-laine", + "description": "American crooner whose hits include \"That's My Desire,\" \"Mule Train,\"\n\"Jezebel,\" and the theme from the television series Rawhide.", + "images": [ + "/images/frankie-laine-1.webp" + ], + "shows": [ + { + "caption": "Sep 1975", + "slug": "1975-sep", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/frankie-laine-1975-sep-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/frankie-laine-1975-sep-b.opus" + } + ] + } + ] + }, + { + "id": 9, + "name": "Gladys Knight & the Pips", + "slug": "gladys-knight", + "description": "American soul and funk group fronted by Gladys Knight with her brother\nand two cousins as the Pips. Hits include \"Midnight Train to Georgia,\"\n\"I Heard It Through the Grapevine,\" and \"Neither One of Us.\"", + "images": [ + "/images/gladys-knight-1.webp", + "/images/gladys-knight-2.webp" + ], + "shows": [ + { + "caption": "Aug 1977 — 1st show", + "slug": "1977-aug-1st", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/gladys-knight-1977-aug-1st-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/gladys-knight-1977-aug-1st-b.opus" + } + ] + }, + { + "caption": "Aug 1977 — 2nd show", + "slug": "1977-aug-2nd", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/gladys-knight-1977-aug-2nd-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/gladys-knight-1977-aug-2nd-b.opus" + } + ] + }, + { + "caption": "Oct 1975 — 1st show", + "slug": "1975-oct-fri", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/gladys-knight-1975-oct-fri-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/gladys-knight-1975-oct-fri-b.opus" + } + ] + }, + { + "caption": "Oct 1975 — 2nd show", + "slug": "1975-oct-sat", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/gladys-knight-1975-oct-sat-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/gladys-knight-1975-oct-sat-b.opus" + } + ] + } + ] + }, + { + "id": 10, + "name": "Glen Campbell & Jud Strunk", + "slug": "glen-campbell-jud-strunk", + "description": "Country-pop superstar Glen Campbell (\"Wichita Lineman,\" \"Rhinestone\nCowboy\") shared this bill with Jud Strunk, a Maine native who reached\nthe U.S. Top 20 in 1973 with the novelty hit \"Daisy a Day.\"", + "images": [ + "/images/glen-campbell-jud-strunk-1.webp" + ], + "shows": [ + { + "caption": "Dec 1973", + "slug": "1973-dec", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/glen-campbell-jud-strunk-1973-dec-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/glen-campbell-jud-strunk-1973-dec-b.opus" + } + ] + } + ] + }, + { + "id": 11, + "name": "Glenn Yarbrough & The Limeliters", + "slug": "glenn-yarbrough-limeliters", + "description": "Folk singer who rose to fame with the Limeliters, one of the leading\nacts of the early-1960s folk revival, before launching a successful\nsolo career. He occasionally rejoined the group for reunion tours.", + "images": [], + "shows": [ + { + "caption": "Oct 1977", + "slug": "1977-oct", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/glenn-yarbrough-limeliters-1977-oct-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/glenn-yarbrough-limeliters-1977-oct-b.opus" + } + ] + } + ] + }, + { + "id": 12, + "name": "Harry Belafonte", + "slug": "harry-belafonte", + "description": "American singer, actor, and civil rights activist who brought Caribbean\nmusic to mainstream audiences with \"Day-O (The Banana Boat Song)\" and\n\"Jump in the Line.\" His 1956 album Calypso was the first by a solo\nartist to sell a million copies in the United States.", + "images": [], + "shows": [ + { + "caption": "May 1976", + "slug": "1976-may", + "sides": [ + { + "id": "a", + "label": "Act I — Side A", + "audio": "/audio/harry-belafonte-1976-may-a.opus" + }, + { + "id": "b", + "label": "Act I — Side B", + "audio": "/audio/harry-belafonte-1976-may-b.opus" + } + ] + } + ] + }, + { + "id": 13, + "name": "Helen Reddy", + "slug": "helen-reddy", + "description": "Australian-American singer best known for \"I Am Woman,\" an anthem\nthat became a touchstone of second-wave feminism. Other hits include\n\"Delta Dawn\" and \"Angie Baby.\"", + "images": [ + "/images/helen-reddy-1.webp" + ], + "shows": [ + { + "caption": "1974", + "slug": "1974", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/helen-reddy-1974-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/helen-reddy-1974-b.opus" + } + ] + } + ] + }, + { + "id": 14, + "name": "Florence Henderson & Jim Nabors", + "slug": "henderson-nabors", + "description": "Florence Henderson (Carol Brady of The Brady Bunch) shared this bill\nwith Jim Nabors (Gomer Pyle of The Andy Griffith Show). Both had\nconcert-singing careers alongside their television roles and were\nregulars on variety programs.", + "images": [ + "/images/henderson-nabors-1.webp", + "/images/henderson-nabors-2.webp" + ], + "shows": [ + { + "caption": "Jul 1975", + "slug": "1975-jul", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/henderson-nabors-1975-jul-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/henderson-nabors-1975-jul-b.opus" + } + ] + } + ] + }, + { + "id": 15, + "name": "The Hues Corporation & The 5th Dimension", + "slug": "hues-corp-5th-dimension", + "description": "The Hues Corporation, who topped the charts in 1974 with \"Rock the\nBoat,\" shared this bill with The 5th Dimension, the late-1960s\nsunshine-pop group behind \"Up, Up and Away\" and the medley\n\"Aquarius/Let the Sunshine In,\" drawn from the musical Hair.", + "images": [ + "/images/hues-corp-5th-dimension-1.webp" + ], + "shows": [ + { + "caption": "1974", + "slug": "1974", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/hues-corp-5th-dimension-1974-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/hues-corp-5th-dimension-1974-b.opus" + } + ] + } + ] + }, + { + "id": 16, + "name": "John Davidson", + "slug": "john-davidson", + "description": "American singer, actor, and television host with a clean-cut style.\nA frequent variety-show guest in the 1960s and '70s, he later hosted\nThat's Incredible! and Hollywood Squares.", + "images": [ + "/images/john-davidson-1.webp" + ], + "shows": [ + { + "caption": "1974", + "slug": "1974", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/john-davidson-1974-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/john-davidson-1974-b.opus" + } + ] + } + ] + }, + { + "id": 17, + "name": "Johnny Mathis", + "slug": "johnny-mathis", + "description": "American singer whose smooth tenor on songs like \"Chances Are,\"\n\"Misty,\" and \"It's Not for Me to Say\" made him one of the best-selling\nrecording artists of the twentieth century.", + "images": [ + "/images/johnny-mathis-1.webp" + ], + "shows": [ + { + "caption": "Aug 1975 — 1st show", + "slug": "1975-aug-sun-1st", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/johnny-mathis-1975-aug-sun-1st-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/johnny-mathis-1975-aug-sun-1st-b.opus" + } + ] + }, + { + "caption": "Aug 1975 — 2nd show", + "slug": "1975-aug-1st", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/johnny-mathis-1975-aug-1st-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/johnny-mathis-1975-aug-1st-b.opus" + } + ] + }, + { + "caption": "Aug 1975 — 3rd show", + "slug": "1975-aug-2nd-half", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/johnny-mathis-1975-aug-2nd-half-a.opus" + } + ] + }, + { + "caption": "Aug 1975 — 4th show", + "slug": "1975-aug-3rd", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/johnny-mathis-1975-aug-3rd-a.opus" + } + ] + }, + { + "caption": "Aug 1976 — 1st show", + "slug": "1976-aug-1", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/johnny-mathis-1976-aug-1-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/johnny-mathis-1976-aug-1-b.opus" + } + ] + }, + { + "caption": "Aug 1976 — 2nd show", + "slug": "1976-aug-2", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/johnny-mathis-1976-aug-2-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/johnny-mathis-1976-aug-2-b.opus" + } + ] + } + ] + }, + { + "id": 18, + "name": "Kate Smith", + "slug": "kate-smith", + "description": "American singer with a 50-year career and a deep contralto voice,\nfamous for her 1939 recording of Irving Berlin's \"God Bless America.\"\nShe hosted radio and television variety programs through the 1950s\nand '60s.", + "images": [ + "/images/kate-smith-1.webp" + ], + "shows": [ + { + "caption": "Nov 1975", + "slug": "1975-nov", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/kate-smith-1975-nov-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/kate-smith-1975-nov-b.opus" + } + ] + } + ] + }, + { + "id": 19, + "name": "Lainie Kazan", + "slug": "lainie-kazan", + "description": "American singer and actress with a powerful belt and Broadway roots.\nAs Barbra Streisand's understudy in Funny Girl, she famously stepped\nin for one performance in 1964. She later became a fixture in supper\nclubs and film comedies.", + "images": [], + "shows": [ + { + "caption": "Aug 1975 — 1st show", + "slug": "1975-aug-1", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/lainie-kazan-1975-aug-1-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/lainie-kazan-1975-aug-1-b.opus" + } + ] + }, + { + "caption": "Aug 1975 — 2nd show", + "slug": "1975-aug-2", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/lainie-kazan-1975-aug-2-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/lainie-kazan-1975-aug-2-b.opus" + } + ] + } + ] + }, + { + "id": 20, + "name": "The Love Machine", + "slug": "love-machine", + "description": "International all-female funk and soul group active in the 1970s.\nThe seven-member ensemble, which included songwriter Sandra Sully,\ntoured Europe, Asia, and Africa and recorded for labels including\nBuddah, Arista, and Motown.", + "images": [], + "shows": [ + { + "caption": "Oct 1976", + "slug": "1976-oct", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/love-machine-1976-oct-a.opus" + } + ] + } + ] + }, + { + "id": 21, + "name": "Tony Martin & Cyd Charisse", + "slug": "martin-charisse", + "description": "Husband-and-wife stage act. Tony Martin sang and Cyd Charisse danced;\nboth were veterans of Hollywood musicals, and they toured together\nthrough the 1970s.", + "images": [], + "shows": [ + { + "caption": "Aug 1975", + "slug": "1975-aug", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/martin-charisse-1975-aug-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/martin-charisse-1975-aug-b.opus" + } + ] + } + ] + }, + { + "id": 22, + "name": "The Mills Brothers", + "slug": "mills-brothers", + "description": "American close-harmony vocal quartet from Piqua, Ohio, active from\nthe 1920s through the 1980s. Best known for \"Paper Doll\" (1943),\n\"Glow Worm,\" and \"You Always Hurt the One You Love.\"", + "images": [ + "/images/mills-brothers-1.webp" + ], + "shows": [ + { + "caption": "Sep 1975", + "slug": "1975-sep", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/mills-brothers-1975-sep-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/mills-brothers-1975-sep-b.opus" + } + ] + } + ] + }, + { + "id": 23, + "name": "Mitzi Gaynor", + "slug": "mitzi-gaynor", + "description": "American actress, singer, and dancer who starred in the 1958 film of\nSouth Pacific opposite Rossano Brazzi. Her touring revues throughout\nthe 1970s showcased her dance numbers and comic timing.", + "images": [ + "/images/mitzi-gaynor-1.webp" + ], + "shows": [ + { + "caption": "Oct 2, 1977", + "slug": "1977-oct", + "sides": [ + { + "id": "i-a", + "label": "Act I — Side A", + "audio": "/audio/mitzi-gaynor-1977-oct-i-a.opus" + }, + { + "id": "i-b", + "label": "Act I — Side B", + "audio": "/audio/mitzi-gaynor-1977-oct-i-b.opus" + }, + { + "id": "ii-a", + "label": "Act II — Side A", + "audio": "/audio/mitzi-gaynor-1977-oct-ii-a.opus" + } + ] + } + ] + }, + { + "id": 24, + "name": "Robert Goulet", + "slug": "robert-goulet", + "description": "Canadian-American baritone who originated the role of Lancelot in\nLerner and Loewe's Camelot on Broadway in 1960. A long career on\ntelevision, in film, and on the Las Vegas circuit followed.", + "images": [ + "/images/robert-goulet-1.webp" + ], + "shows": [ + { + "caption": "Jun 1975 — with Foster Brooks", + "slug": "1975-jun", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/robert-goulet-1975-jun-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/robert-goulet-1975-jun-b.opus" + } + ] + }, + { + "caption": "Oct 1976", + "slug": "1976-oct", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/robert-goulet-1976-oct-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/robert-goulet-1976-oct-b.opus" + } + ] + } + ] + }, + { + "id": 25, + "name": "Roy Clark", + "slug": "roy-clark", + "description": "American country musician and comedian, longtime co-host of the\nsyndicated television show Hee Haw. A virtuoso multi-instrumentalist\nand a Country Music Hall of Fame inductee.", + "images": [ + "/images/roy-clark-1.webp" + ], + "shows": [ + { + "caption": "Apr 1976", + "slug": "1976-apr", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/roy-clark-1976-apr-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/roy-clark-1976-apr-b.opus" + } + ] + } + ] + }, + { + "id": 26, + "name": "Sammy Davis Jr.", + "slug": "sammy-davis-jr", + "description": "American singer, dancer, actor, and member of the Rat Pack, whose\ncareer spanned more than six decades across Broadway, film, and\ntelevision. His signature hit was \"The Candy Man.\"", + "images": [ + "/images/sammy-davis-jr-1.webp", + "/images/sammy-davis-jr-2.webp" + ], + "shows": [ + { + "caption": "1975", + "slug": "1975", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/sammy-davis-jr-1975-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/sammy-davis-jr-1975-b.opus" + } + ] + }, + { + "caption": "Nov 1976", + "slug": "1976-nov", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/sammy-davis-jr-1976-nov-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/sammy-davis-jr-1976-nov-b.opus" + } + ] + } + ] + }, + { + "id": 27, + "name": "Sandler & Young", + "slug": "sandler-young", + "description": "Singing duo Tony Sandler (Belgian) and Ralph Young (American), known\nfor their multilingual repertoire and tight harmony. A staple of\nsupper-club and television variety bookings from the late 1960s\nonward.", + "images": [ + "/images/sandler-young-1.webp" + ], + "shows": [ + { + "caption": "Dec 1975 — 1st show", + "slug": "1975-dec-mill-run", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/sandler-young-1975-dec-mill-run-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/sandler-young-1975-dec-mill-run-b.opus" + } + ] + }, + { + "caption": "Dec 1975 — 2nd show", + "slug": "1975-dec", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/sandler-young-1975-dec-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/sandler-young-1975-dec-b.opus" + } + ] + } + ] + }, + { + "id": 28, + "name": "Shirley Bassey", + "slug": "shirley-bassey", + "description": "Welsh singer with a powerful contralto, internationally known for the\nthree James Bond film theme songs she recorded: \"Goldfinger\" (1964),\n\"Diamonds Are Forever\" (1971), and \"Moonraker\" (1979).", + "images": [ + "/images/shirley-bassey-1.webp" + ], + "shows": [ + { + "caption": "Sep 1976", + "slug": "1976-sep", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/shirley-bassey-1976-sep-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/shirley-bassey-1976-sep-b.opus" + } + ] + } + ] + }, + { + "id": 29, + "name": "Steve Lawrence & Eydie Gormé", + "slug": "steve-lawrence-eydie-gorme", + "description": "American husband-and-wife singing duo who met as Steve Allen's Tonight\nShow regulars in the 1950s and remained inseparable performers until\nEydie's death in 2013. Each maintained a successful solo career\nalongside the duo work.", + "images": [ + "/images/steve-lawrence-eydie-gorme-1.webp" + ], + "shows": [ + { + "caption": "Aug 1977 — 1st show", + "slug": "1977-aug-1", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/steve-lawrence-eydie-gorme-1977-aug-1-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/steve-lawrence-eydie-gorme-1977-aug-1-b.opus" + } + ] + }, + { + "caption": "Aug 1977 — 2nd show", + "slug": "1977-aug-2", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/steve-lawrence-eydie-gorme-1977-aug-2-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/steve-lawrence-eydie-gorme-1977-aug-2-b.opus" + } + ] + } + ] + }, + { + "id": 30, + "name": "The Dells", + "slug": "the-dells", + "description": "Chicago R&B vocal group whose career stretched from the 1950s\n(\"Oh What a Night\") through the 1990s (\"Stay in My Corner\"). One of\nthe longest-lived doo-wop and soul ensembles.", + "images": [], + "shows": [ + { + "caption": "1975", + "slug": "1975", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/the-dells-1975-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/the-dells-1975-b.opus" + } + ] + } + ] + }, + { + "id": 31, + "name": "The Irish Rovers", + "slug": "irish-rovers", + "description": "Canadian-Irish folk group founded in Toronto in the 1960s, best known\nfor \"The Unicorn\" (1968) and \"Wasn't That a Party\" (1981).", + "images": [], + "shows": [ + { + "caption": "Jul 1977 — 1st show", + "slug": "1977-jul-16", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/irish-rovers-1977-jul-16-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/irish-rovers-1977-jul-16-b.opus" + } + ] + }, + { + "caption": "Jul 1977 — 2nd show", + "slug": "1977-jul-17", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/irish-rovers-1977-jul-17-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/irish-rovers-1977-jul-17-b.opus" + } + ] + } + ] + }, + { + "id": 32, + "name": "The Kingston Trio", + "slug": "kingston-trio", + "description": "American folk group from San Francisco, one of the most popular acts\nof the late 1950s and early '60s and a catalyst for the folk revival.\nHits include \"Tom Dooley\" and \"Where Have All the Flowers Gone.\"", + "images": [], + "shows": [ + { + "caption": "Oct 1977", + "slug": "1977-oct", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/kingston-trio-1977-oct-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/kingston-trio-1977-oct-b.opus" + } + ] + } + ] + }, + { + "id": 33, + "name": "The Temptations & Honey Cone", + "slug": "temptations-honey-cone", + "description": "Motown soul giants The Temptations (\"My Girl,\" \"Just My Imagination\")\nshared this bill with Honey Cone, the trio whose 1971 single\n\"Want Ads\" topped the Billboard Hot 100.", + "images": [ + "/images/temptations-honey-cone-1.webp" + ], + "shows": [ + { + "caption": "Dec 30, 1972", + "slug": "1972-dec", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/temptations-honey-cone-1972-dec-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/temptations-honey-cone-1972-dec-b.opus" + } + ] + } + ] + }, + { + "id": 34, + "name": "Tom Jones", + "slug": "tom-jones", + "description": "Welsh singer with a powerhouse baritone who broke through with \"It's\nNot Unusual\" in 1965 and went on to build a decades-long Las Vegas\nresidency career. His variety show This Is Tom Jones aired from 1969\nto 1971.", + "images": [ + "/images/tom-jones-1.webp", + "/images/tom-jones-2.webp" + ], + "shows": [ + { + "caption": "1975", + "slug": "1975", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/tom-jones-1975-a.opus" + } + ] + }, + { + "caption": "Sep 1976", + "slug": "1976-sep", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/tom-jones-1976-sep-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/tom-jones-1976-sep-b.opus" + } + ] + } + ] + }, + { + "id": 35, + "name": "Vic Damone", + "slug": "vic-damone", + "description": "American singer whose smooth baritone made him a fixture on television\nvariety shows and a Las Vegas mainstay. Hits include \"You're Breaking\nMy Heart\" and \"On the Street Where You Live.\"", + "images": [ + "/images/vic-damone-1.webp" + ], + "shows": [ + { + "caption": "Dec 1974", + "slug": "1974-dec", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/vic-damone-1974-dec-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/vic-damone-1974-dec-b.opus" + } + ] + } + ] + }, + { + "id": 36, + "name": "Vikki Carr", + "slug": "vikki-carr", + "description": "American singer of Mexican-American heritage who scored a pop hit with\n\"It Must Be Him\" in 1967 before pivoting to a Spanish-language career\nthat earned her multiple Grammy and Latin Grammy awards.", + "images": [ + "/images/vikki-carr-1.webp", + "/images/vikki-carr-2.webp" + ], + "shows": [ + { + "caption": "1975", + "slug": "1975", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/vikki-carr-1975-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/vikki-carr-1975-b.opus" + } + ] + }, + { + "caption": "May 1975", + "slug": "1975-may", + "sides": [ + { + "id": "a", + "label": "Side A", + "audio": "/audio/vikki-carr-1975-may-a.opus" + }, + { + "id": "b", + "label": "Side B", + "audio": "/audio/vikki-carr-1975-may-b.opus" + } + ] + } + ] + } + ] +} diff --git a/web/src/lib/artists.ts b/web/src/lib/artists.ts new file mode 100644 index 0000000..542c677 --- /dev/null +++ b/web/src/lib/artists.ts @@ -0,0 +1,35 @@ +import data from './artists.json'; + +export type Side = { + id: string; + label: string; + audio: string; +}; + +export type Show = { + caption: string; + slug: string; + sides: Side[]; +}; + +export type Artist = { + id: number; + name: string; + slug: string; + description: string; + images: string[]; + shows: Show[]; +}; + +export type Exhibit = { + title: string; + description?: string; + subtitle?: string; +}; + +export const exhibit: Exhibit = data.exhibit; +export const artists: Artist[] = data.artists; + +export function artistById(id: number): Artist | undefined { + return artists.find((a) => a.id === id); +} diff --git a/web/src/lib/auth.svelte.ts b/web/src/lib/auth.svelte.ts index b1fc11f..bbe8a62 100644 --- a/web/src/lib/auth.svelte.ts +++ b/web/src/lib/auth.svelte.ts @@ -38,35 +38,8 @@ export async function login(password: string): Promise { return false; } -export async function loginWithKey(key: string): Promise { - const res = await fetch('/api/key', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ key }), - credentials: 'same-origin' - }); - if (res.ok) { - state = 'signed-in'; - return true; - } - return false; -} - export async function logout() { await fetch('/api/logout', { method: 'POST', credentials: 'same-origin' }); state = 'signed-out'; goto('/login'); } - -export type KeyResult = 'success' | 'failed' | 'absent'; - -export async function consumeKeyParam(): Promise { - if (!browser) return 'absent'; - const url = new URL(window.location.href); - const key = url.searchParams.get('key'); - if (!key) return 'absent'; - url.searchParams.delete('key'); - window.history.replaceState({}, '', url.toString()); - const ok = await loginWithKey(key); - return ok ? 'success' : 'failed'; -} diff --git a/web/src/lib/fonts.svelte.ts b/web/src/lib/fonts.svelte.ts new file mode 100644 index 0000000..5924d40 --- /dev/null +++ b/web/src/lib/fonts.svelte.ts @@ -0,0 +1,21 @@ +import { browser } from '$app/environment'; + +// Becomes true once all custom fonts have loaded. Components that contain +// SVG paths whose geometry depends on element size (e.g. the marquee bulb +// ring) can key off this so they remount after the font swap finishes +// resizing the panel. +let ready = $state(false); + +if (browser) { + if ('fonts' in document && typeof document.fonts.ready?.then === 'function') { + document.fonts.ready.then(() => { + ready = true; + }); + } else { + ready = true; + } +} + +export function fontsReady(): boolean { + return ready; +} diff --git a/web/src/lib/precache.svelte.ts b/web/src/lib/precache.svelte.ts index 22c281c..762ce12 100644 --- a/web/src/lib/precache.svelte.ts +++ b/web/src/lib/precache.svelte.ts @@ -1,16 +1,20 @@ import { browser } from '$app/environment'; -import { stops } from './stops'; +import { artists } from './artists'; export type PrecacheState = 'idle' | 'running' | 'complete' | 'unsupported'; let state: PrecacheState = $state('idle'); let done = $state(0); let total = $state(0); +let failed = $state(0); export function precacheState() { - return { state, done, total }; + return { state, done, total, failed }; } +// Precache every image and every audio side. ~1.6 GB at current Opus +// bitrate. Designed for the museum's single-kiosk tablet on a flaky wifi: +// staff hit the page once at setup; visitors thereafter hit cache only. export async function precacheAll(): Promise { if (!browser) return; if (!('serviceWorker' in navigator)) { @@ -26,7 +30,15 @@ export async function precacheAll(): Promise { return; } - const urls = stops.flatMap((s) => [s.audio, s.image]); + const urls = artists.flatMap((a) => [ + ...a.images, + ...a.shows.flatMap((s) => s.sides.map((side) => side.audio)) + ]); + + if (urls.length === 0) { + state = 'complete'; + return; + } state = 'running'; done = 0; @@ -38,14 +50,16 @@ export async function precacheAll(): Promise { if (data.type === 'precache-progress') { done = data.done; total = data.total; + failed = data.failed ?? 0; } else if (data.type === 'precache-done') { done = data.done; total = data.total; + failed = data.failed ?? 0; state = 'complete'; navigator.serviceWorker.removeEventListener('message', onMessage); } }; navigator.serviceWorker.addEventListener('message', onMessage); - target.postMessage({ type: 'precache-stops', urls }); + target.postMessage({ type: 'precache-artists', urls }); } diff --git a/web/src/lib/stops.json b/web/src/lib/stops.json deleted file mode 100644 index f1d313b..0000000 --- a/web/src/lib/stops.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "exhibit": { - "title": "Mill Run", - "subtitle": "" - }, - "stops": [ - { - "id": 1, - "title": "Welcome", - "audio": "/audio/01.opus", - "image": "/images/01.webp", - "caption": "An introduction to the exhibit.", - "description": "Placeholder description." - }, - { - "id": 2, - "title": "Stop two placeholder", - "audio": "/audio/02.opus", - "image": "/images/02.webp", - "caption": "", - "description": "" - } - ] -} diff --git a/web/src/lib/stops.ts b/web/src/lib/stops.ts deleted file mode 100644 index 99b9914..0000000 --- a/web/src/lib/stops.ts +++ /dev/null @@ -1,22 +0,0 @@ -import stopsJson from './stops.json'; - -export type Stop = { - id: number; - title: string; - audio: string; - image: string; - caption?: string; - description?: string; -}; - -export type Exhibit = { - title: string; - subtitle: string; -}; - -export const exhibit: Exhibit = stopsJson.exhibit; -export const stops: Stop[] = stopsJson.stops; - -export function stopById(id: number): Stop | undefined { - return stops.find((s) => s.id === id); -} diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte index 14b740b..5a231a7 100644 --- a/web/src/routes/+layout.svelte +++ b/web/src/routes/+layout.svelte @@ -4,7 +4,7 @@ import { page } from '$app/stores'; import { goto } from '$app/navigation'; import { dev } from '$app/environment'; - import { authState, checkAuth, consumeKeyParam } from '$lib/auth.svelte'; + import { authState, checkAuth } from '$lib/auth.svelte'; import { precacheAll } from '$lib/precache.svelte'; let { children } = $props(); @@ -24,18 +24,14 @@ } } - const keyResult = await consumeKeyParam(); - if (keyResult !== 'success') await checkAuth(); + await checkAuth(); const path = $page.url.pathname; if (authState() === 'signed-out' && path !== '/login') { const params = new URLSearchParams({ next: path }); - if (keyResult === 'failed') params.set('error', 'qr'); await goto('/login?' + params.toString(), { replaceState: true }); } - // Production-only: kick off audio/image bulk precache once authenticated. - // SW does the work; failures non-fatal. if (!dev && authState() === 'signed-in') { precacheAll().catch(() => undefined); } @@ -63,7 +59,7 @@ height: 32px; border-radius: 50%; border: 2px solid var(--rule); - border-top-color: var(--accent); + border-top-color: var(--title); animation: spin 0.8s linear infinite; } @keyframes spin { diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte index ea6ac8b..4363844 100644 --- a/web/src/routes/+page.svelte +++ b/web/src/routes/+page.svelte @@ -1,63 +1,118 @@
-

{exhibit.title}

- {#if exhibit.subtitle} -

{exhibit.subtitle}

- {/if} +
+ {#key fontsReady()} + + {/key} +

{exhibit.title}

+ {#if exhibit.description} + {#each exhibit.description.split('\n').filter(Boolean) as line} +

{line}

+ {/each} + {/if} +
-{#if pre.state === 'running'} +{#if pre.state === 'running' || (pre.state === 'complete' && pre.failed > 0)}
- - - Loading exhibit for offline listening… {pre.done}/{pre.total} - + {#if pre.state === 'running'} + + + Caching exhibit for offline listening… {pre.done} / {pre.total} + {#if pre.failed > 0}({pre.failed} retrying){/if} + + + {:else} + + {pre.total - pre.failed} of {pre.total} files cached. {pre.failed} + failed to download — they will stream over the network when played. + + {/if}
{/if} -
- {#each stops as stop (stop.id)} - +
+ {#each artists as artist (artist.id)} + {@const cover = artist.images[0]} +
- + {#if cover} + + {:else} + + {/if}
- {String(stop.id).padStart(2, '0')} - {stop.title} + {artist.name} + {artistYears(artist)}
{/each}
+ diff --git a/web/src/routes/artist/[id]/+page.svelte b/web/src/routes/artist/[id]/+page.svelte new file mode 100644 index 0000000..23fb1d8 --- /dev/null +++ b/web/src/routes/artist/[id]/+page.svelte @@ -0,0 +1,312 @@ + + +{#if !artist} +
+

Artist not found.

+ Back to all artists +
+{:else} +
+ + + Back to All Artists + +
+ +
+
+ {#key `${artist.id}-${fontsReady()}`} + + {/key} +

{artist.name}

+
+ + {#if artist.images.length > 0 || artist.description} +
+ {#if artist.images.length > 0} +
+ {#each artist.images as src, i (src)} +
+ +
+ {/each} +
+ {/if} + + {#if artist.description} +
+ {#each artist.description.split(/\n\n+/) as para} +

{para}

+ {/each} +
+ {/if} +
+ {/if} + + + +
+ {#each artist.shows as show, showIdx (show.slug)} +
+

{show.caption}

+
+ {#each show.sides as side, sideIdx (side.id)} + + {/each} +
+
+ {/each} +
+ +

+ Digitized from analog tapes in the Niles Historical Society archive. +

+
+ + +{/if} + + diff --git a/web/src/routes/artist/[id]/SidePlayer.svelte b/web/src/routes/artist/[id]/SidePlayer.svelte new file mode 100644 index 0000000..45c5414 --- /dev/null +++ b/web/src/routes/artist/[id]/SidePlayer.svelte @@ -0,0 +1,253 @@ + + +
+ + +
+ {label} + + {fmt(currentTime)} / {fmt(duration)} +
+ + +
+ + diff --git a/web/src/routes/login/+page.svelte b/web/src/routes/login/+page.svelte index e4e5da1..969208b 100644 --- a/web/src/routes/login/+page.svelte +++ b/web/src/routes/login/+page.svelte @@ -1,15 +1,12 @@ - -{#if !stop} -
-

Stop not found.

- Back to all stops -
-{:else} -
- - - - {String(stop.id).padStart(2, '0')} -
- -
- - {#if volumeWritable} -
- - -
- {/if} - - - -

{stop.title}

- {#if stop.caption} -

{stop.caption}

- {/if} - - {#if stop.description} -
- {#each stop.description.split(/\n\n+/) as para} -

{para}

- {/each} -
- {/if} -
- - -{/if} - - diff --git a/web/src/service-worker.ts b/web/src/service-worker.ts index 97aeda5..87ba42c 100644 --- a/web/src/service-worker.ts +++ b/web/src/service-worker.ts @@ -14,7 +14,7 @@ // /audio /img → stale-while-revalidate from ASSET_CACHE // everything → cache-first from SHELL_CACHE // - Messages: -// { type: 'precache-stops', urls: string[] } +// { type: 'precache-artists', urls: string[] } // Bulk-cache a list of URLs into ASSET_CACHE, posting progress back to // the sender via { type: 'precache-progress', done, total } and a final // { type: 'precache-done' } when finished. @@ -72,7 +72,6 @@ sw.addEventListener('fetch', (event) => { event.respondWith( (async () => { - // Navigation: prefer cached shell; fall back to network. if (request.mode === 'navigate') { const shell = await caches.open(SHELL_CACHE); const cached = (await shell.match('/')) || (await shell.match('/index.html')); @@ -89,7 +88,6 @@ sw.addEventListener('fetch', (event) => { const cached = await cache.match(request); if (cached) { if (isContent) { - // Stale-while-revalidate: return cached, refresh in background. event.waitUntil(refresh(cache, request)); } return cached; @@ -119,40 +117,79 @@ async function refresh(cache: Cache, request: Request) { sw.addEventListener('message', (event) => { const data = event.data; - if (data?.type !== 'precache-stops' || !Array.isArray(data.urls)) return; + if (data?.type !== 'precache-artists' || !Array.isArray(data.urls)) return; const urls: string[] = data.urls; const source = event.source; - event.waitUntil(precacheStops(urls, source)); + event.waitUntil(precacheArtists(urls, source)); }); -async function precacheStops(urls: string[], source: Client | ServiceWorker | MessagePort | null) { +async function precacheArtists(urls: string[], source: Client | ServiceWorker | MessagePort | null) { const cache = await caches.open(ASSET_CACHE); - let done = 0; const total = urls.length; - for (const url of urls) { - try { - const cached = await cache.match(url); - if (!cached) { - const res = await fetch(url, { credentials: 'same-origin' }); - if (res.ok && res.type === 'basic') await cache.put(url, res); - } - } catch { - // Network error — skip and continue. UI shows the count. - } - done++; + let done = 0; + let failed = 0; + + const queue = urls.slice(); + const CONCURRENCY = 3; + const MAX_ATTEMPTS = 3; + const BACKOFF_MS = 800; + + function postProgress() { try { (source as Client | null)?.postMessage?.({ type: 'precache-progress', done, - total + total, + failed }); - } catch { - // Sender went away. + } catch {} + } + + async function fetchWithRetry(url: string): Promise { + for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { + try { + const res = await fetch(url, { credentials: 'same-origin', cache: 'no-store' }); + if (res.ok && res.type === 'basic') { + await cache.put(url, res); + return true; + } + if (res.status >= 400 && res.status < 500) return false; // permanent + } catch {} + if (attempt < MAX_ATTEMPTS) { + await new Promise((r) => setTimeout(r, BACKOFF_MS * attempt)); + } + } + return false; + } + + async function worker() { + while (queue.length > 0) { + const url = queue.shift(); + if (!url) break; + try { + const cached = await cache.match(url); + if (!cached) { + const ok = await fetchWithRetry(url); + if (!ok) failed++; + } + } catch { + failed++; + } + done++; + postProgress(); } } + + await Promise.all(Array.from({ length: CONCURRENCY }, worker)); + try { - (source as Client | null)?.postMessage?.({ type: 'precache-done', done, total }); + (source as Client | null)?.postMessage?.({ + type: 'precache-done', + done, + total, + failed + }); } catch { // Sender went away. } diff --git a/web/static/manifest.webmanifest b/web/static/manifest.webmanifest index 58597e9..c9b5c4e 100644 --- a/web/static/manifest.webmanifest +++ b/web/static/manifest.webmanifest @@ -1,5 +1,5 @@ { - "name": "Mill Run", + "name": "Mill Run Theatre", "short_name": "Mill Run", "description": "Audio-guided exhibit", "start_url": "/", diff --git a/web/svelte.config.js b/web/svelte.config.js index 05357a3..939b0eb 100644 --- a/web/svelte.config.js +++ b/web/svelte.config.js @@ -14,6 +14,25 @@ export default { serviceWorker: { register: false }, + csp: { + mode: 'hash', + directives: { + 'default-src': ['self'], + 'script-src': ['self'], + 'style-src': ['self', 'unsafe-inline'], + 'img-src': ['self', 'data:', 'blob:'], + 'media-src': ['self', 'blob:'], + 'font-src': ['self', 'data:'], + 'connect-src': ['self'], + 'manifest-src': ['self'], + 'worker-src': ['self'], + 'object-src': ['none'], + 'base-uri': ['self'], + 'frame-ancestors': ['none'], + 'form-action': ['self'], + 'upgrade-insecure-requests': true + } + }, alias: { $content: './src/lib/content' }