diff --git a/CHANGELOG.md b/CHANGELOG.md index b2dcfcb6..94d7e7b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- **Gigs (the career verb, frontend)** — book a gig from any opened passport: + a gig poster proposes the setlist (re-roll for a different bill; save or + copy the poster as a PNG), "Play the gig" hands the set to the play queue + with the venue on stage, a floating strip tracks the set, and finishing it + logs dated entries with per-song accuracies in the passport book — with an + encore celebration (crowd eruption + confetti) when the whole set clears + the bar, and a summary poster to share. Quitting mid-set simply abandons + it: no log, no fail state. - **Career on the Profile and Home pages** — the Profile gains a passport wall (earned-badge covers per instrument, hours, gig count; absent until a passport exists), injected through the same mount-point + rendered-event diff --git a/docs/size-exemptions.md b/docs/size-exemptions.md index 0861ae55..49a6741a 100644 --- a/docs/size-exemptions.md +++ b/docs/size-exemptions.md @@ -61,6 +61,8 @@ extractions and twenty-two `routers/` modules, plus lib/library_registry.py for and is a monolith in its own right, to be split per-table once the router train lands) · `static/v3/songs.js` (4,134) · `static/capabilities/audio-session.js` (2,974) · `plugins/highway_3d/screen.js` (15,656) · `plugins/keys_highway_3d/screen.js` -(3,780) · `plugins/drum_highway_3d/screen.js` (3,597) — and every monolith with a PR +(3,780) · `plugins/drum_highway_3d/screen.js` (3,597) · `plugins/career/screen.js` +(1,516 — career v3 gigs pushed it over; split plan: carve the gig block into a +`scriptType: module` file when career work next touches it) — and every monolith with a PR train in the refactor plan. Test files (e.g. `tests/test_plugins.py`) are out of scope by policy — the norm governs source files. diff --git a/plugins/career/assets/career.css b/plugins/career/assets/career.css index 6fcc12a1..c89cd5b3 100644 --- a/plugins/career/assets/career.css +++ b/plugins/career/assets/career.css @@ -714,3 +714,67 @@ .pp-dash-card:hover .pp-dash-shine { animation: none; } .pp-wall-cover, .pp-dash-card { transition: none; } } + +/* ── Gigs: poster, runner strip, summary, log ───────────────────────────── */ + +.pp-poster { + position: relative; + width: min(92vw, 420px); + padding: 2rem 1.6rem 1.4rem; + border-radius: 0.5rem; + background: linear-gradient(180deg, #141019, #241318); + border: 2px solid rgba(217, 162, 83, 0.45); + box-shadow: 0 10px 32px rgba(0, 0, 0, 0.6); + display: flex; + flex-direction: column; + align-items: center; + gap: 0.35rem; + text-align: center; +} +.pp-poster-venue { color: rgba(240, 226, 195, 0.7); font-size: 0.95rem; letter-spacing: 0.08em; } +.pp-poster-presents { color: rgba(240, 226, 195, 0.4); font-size: 0.58rem; letter-spacing: 0.4em; text-transform: uppercase; } +.pp-poster-title { + color: #d9a253; + font-size: 1.7rem; + font-weight: 800; + letter-spacing: 0.1em; + line-height: 1.15; + overflow-wrap: anywhere; +} +.pp-poster-inst { color: rgba(240, 226, 195, 0.5); font-size: 0.68rem; letter-spacing: 0.2em; text-transform: uppercase; } +.pp-poster-bill { margin: 0.9rem 0 0.5rem; display: flex; flex-direction: column; gap: 0.35rem; width: 100%; } +.pp-poster-line { color: rgba(240, 226, 195, 0.85); font-size: 0.85rem; } +.pp-poster-line span { color: rgba(217, 162, 83, 0.7); margin-right: 0.35rem; } +.pp-poster-line em { color: rgba(240, 226, 195, 0.5); font-style: italic; font-size: 0.72rem; } +.pp-poster-line b { color: #f3d179; margin-left: 0.3rem; } +.pp-poster-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; justify-content: center; margin-top: 0.6rem; } +.pp-poster-summary { cursor: default; } + +.pp-gig-strip { + position: fixed; + top: 0.5rem; + left: 50%; + transform: translateX(-50%); + z-index: 35; /* above the rail (30), under popovers (40) — the chrome invariant */ + background: rgba(10, 8, 14, 0.85); + border: 1px solid rgba(217, 162, 83, 0.4); + border-radius: 999px; + color: rgba(240, 226, 195, 0.85); + font-size: 0.72rem; + padding: 0.3rem 0.9rem; + pointer-events: none; + backdrop-filter: blur(2px); +} +.pp-gig-strip b { color: #d9a253; letter-spacing: 0.2em; } +.pp-gig-strip em { color: #f3ead2; font-style: italic; } + +.pp-giglog { margin-top: 0.6rem; border-top: 1px dashed rgba(138, 122, 94, 0.4); padding-top: 0.5rem; } +.pp-giglog-head { + font-size: 0.58rem; + letter-spacing: 0.22em; + text-transform: uppercase; + color: #8a7a5e; + margin-bottom: 0.25rem; +} +.pp-giglog-row { font-size: 0.7rem; color: #6d5d40; padding: 0.1rem 0; } +.pp-giglog-row b { color: #9a5b16; letter-spacing: 0.06em; } diff --git a/plugins/career/screen.js b/plugins/career/screen.js index 1188d25e..03b1bcc9 100644 --- a/plugins/career/screen.js +++ b/plugins/career/screen.js @@ -38,6 +38,8 @@ let _ppCeremonyActive = false; let _ppBootstrapped = false; let _ppNotified = {}; // badges chimed this session (slam still pending) + let _ppGigProposal = null; // the booking poster's proposal, while open + let _ppGigRun = null; // {songs, venue_id, genre, genre_key, instrument, idx} mid-set function $(id) { return document.getElementById(id); } @@ -686,15 +688,24 @@ `
${esc(s.title)} — best ${pct(s.best_accuracy)}%, ${starGl} at ${pct(s.bar_at)}%
`).join('')} `; } + let gigLog = ''; + if ((p.gigs || []).length) { + gigLog = `
+
Gigs played
+ ${p.gigs.slice(0, 6).map((g) => + `
${esc((g.at || '').slice(0, 10))} · ${esc(_venueName(g.venue_id))}${g.encore ? ' · encore' : ''}
`).join('')} +
`; + } return `