diff --git a/CHANGELOG.md b/CHANGELOG.md index 94d7e7b..afe1975 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- **Gold tier (career passports)** — an earned badge turns **gold** when + Virtuoso verifies an improvised jam in the passport's style (the + `gold_improv` artifact relays with the drill snapshot; a genre inherits its + family's style, gained-only, and gold never substitutes for the badge bar + itself). Gold gets its own ceremony, stamp slam, foil chip, and gold ink on + the shelf cover, profile wall, and passport card; the bronze page's "Gold + rung coming" preview becomes a live invitation to jam it. - **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 diff --git a/docs/size-exemptions.md b/docs/size-exemptions.md index 49a6741..91d9344 100644 --- a/docs/size-exemptions.md +++ b/docs/size-exemptions.md @@ -62,7 +62,7 @@ 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) · `plugins/career/screen.js` -(1,516 — career v3 gigs pushed it over; split plan: carve the gig block into a +(1,530 — career v3 gigs + gold 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 c89cd5b..921eae3 100644 --- a/plugins/career/assets/career.css +++ b/plugins/career/assets/career.css @@ -525,7 +525,18 @@ pointer-events: none; } -/* Gold foil preview — honest "coming", never earnable-looking. */ +/* Gold ink — a REAL gold badge (comb-verified improv). */ +.pp-stamp-gold { + border-color: #b8860b; + color: #a97b1b; + box-shadow: inset 0 0 0 3px #f3e8c8, inset 0 0 0 4px #b8860b; +} +.pp-stamp-mini.pp-stamp-gold { + color: #f0c75e; + border-color: #f0c75e; + box-shadow: none; +} +/* Gold foil chip — rendered only alongside an earned gold stamp. */ .pp-gold-foil { position: relative; overflow: hidden; @@ -535,8 +546,8 @@ margin-top: 0.9rem; padding: 0.28rem 0.85rem; border-radius: 999px; - border: 2px dashed #c8b273; - color: #a8946d; + border: 2px solid #d9a253; + color: #c89040; font-size: 0.58rem; font-weight: 700; letter-spacing: 0.32em; diff --git a/plugins/career/screen.js b/plugins/career/screen.js index 03b1bcc..99d39c2 100644 --- a/plugins/career/screen.js +++ b/plugins/career/screen.js @@ -304,11 +304,15 @@ } catch (_) { return {}; } } - function badgeId(inst, gkey) { return inst + '/' + gkey; } + // Bronze keeps the legacy un-suffixed id, so badges seen before the Gold + // tier existed stay seen; gold is a distinct moment with its own id. + function badgeId(inst, gkey, tier) { return inst + '/' + gkey + (tier === 'gold' ? '@gold' : ''); } - function markBadgeSeen(inst, gkey) { + function markBadgeSeen(inst, gkey, tier) { const seen = seenBadges(); - seen[badgeId(inst, gkey)] = 1; + seen[badgeId(inst, gkey, tier)] = 1; + // A gold slam covers the bronze moment too — never queue both. + if (tier === 'gold') seen[badgeId(inst, gkey)] = 1; lsSet(PP_SEEN_KEY, JSON.stringify(seen)); } @@ -320,15 +324,19 @@ const seen = seenBadges(); for (const inst of Object.keys(view.instruments || {})) { for (const p of (view.instruments[inst].passports || [])) { - const id = badgeId(inst, p.genre_key); - if (p.badge !== 'earned' || seen[id] || _ppNotified[id]) continue; + if (p.badge !== 'earned' && p.badge !== 'gold') continue; + const gold = p.badge === 'gold'; + const id = badgeId(inst, p.genre_key, p.badge); + if (seen[id] || _ppNotified[id]) continue; _ppNotified[id] = true; sfx('chime'); if (window.fbNotify && typeof window.fbNotify.show === 'function') { window.fbNotify.show({ - big: true, icon: '🛂', accent: '#b45309', - title: 'Badge earned!', - message: `${p.genre} — Bronze, ready to stamp into your ${ppLabel(inst)} passport.`, + big: true, icon: gold ? '🏅' : '🛂', accent: gold ? '#d9a253' : '#b45309', + title: gold ? 'Gold — a verified improv!' : 'Badge earned!', + message: gold + ? `${p.genre} — your ${ppLabel(inst)} badge turns gold.` + : `${p.genre} — Bronze, ready to stamp into your ${ppLabel(inst)} passport.`, }); } badgeCeremony(inst, p); @@ -377,11 +385,11 @@ el.innerHTML = `