diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c30282..494a943 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 attributed via the existing progression arrangement→instrument mapping; non-graded instruments (bass, drums) render shown-not-judged — repertoire without a pass bar, never a false badge denial. +- **Career passports (UI)** — the Career screen gains a Passports tab beside + Venues: a physical per-instrument passport book (embossed leather cover, 3D + page-turn) with a wax-seal commitment ceremony (Stage 0), rubber-stamp badge + slam with ink bleed and deterministic per-genre jitter, qualifying songs as + collected ticket stubs, and unopened genres as an "Explore next" + travel-brochure rack (invitations, never greyed-out slots or completion + meters). Badge earns chime + notify immediately; the stamp slam plays when + the passport is next opened. Four small synthesized sound effects ship as + plugin assets. The career screen also relays the Virtuoso `virtuoso.progress` + localStorage snapshot to the drill-state intake on `virtuoso:progress` bus + events (debounced, plus a one-time bootstrap), closing the + fires-into-a-void seam without touching the virtuoso plugin. ### Removed - **The classic v2 UI shell is gone — v3 is the only UI (R3a).** `static/index.html`, the diff --git a/plugins/career/assets/career.css b/plugins/career/assets/career.css index 5fac8c2..b21d9f2 100644 --- a/plugins/career/assets/career.css +++ b/plugins/career/assets/career.css @@ -64,3 +64,347 @@ .career-star-row .song .artist { color: #9ca3af; } .career-star-row .hint { color: #6b7280; white-space: nowrap; } .career-star-row .hint.close { color: #22d3ee; } + +/* ── Passports (badge journey) ─────────────────────────────────────────── */ + +.career-tabs { + display: flex; + gap: 0.25rem; + margin-bottom: 1rem; + border-bottom: 1px solid rgba(55, 65, 81, 0.6); +} +.career-tab { + padding: 0.375rem 0.875rem; + font-size: 0.85rem; + color: #9ca3af; + border-bottom: 2px solid transparent; + margin-bottom: -1px; +} +.career-tab:hover { color: #e5e7eb; } +.career-tab.active { color: #fff; border-bottom-color: #06b6d4; } + +.pp-instruments { display: flex; flex-wrap: wrap; gap: 0.5rem; } +.pp-inst { + padding: 0.3rem 0.8rem; + border-radius: 999px; + font-size: 0.8rem; + color: #d1d5db; + background-color: rgba(31, 41, 55, 0.7); + border: 1px solid transparent; +} +.pp-inst:hover { background-color: rgba(55, 65, 81, 0.9); } +.pp-inst.active { border-color: #06b6d4; color: #fff; } +.pp-inst.uncommitted { color: #6b7280; border-style: dashed; border-color: rgba(107, 114, 128, 0.5); } +.pp-inst-badges { color: #fbbf24; font-size: 0.7rem; } +.pp-inst-plus { color: #6b7280; } + +/* Leather covers — per-instrument hue, embossed with layered shadows and a + subtle grain gradient (no image assets). */ +.pp-leather-guitar { background: linear-gradient(160deg, #5c2321, #401412); } +.pp-leather-bass { background: linear-gradient(160deg, #1f3252, #131f36); } +.pp-leather-keys { background: linear-gradient(160deg, #1e4034, #122a21); } +.pp-leather-drums { background: linear-gradient(160deg, #3f3f46, #26262b); } + +.pp-shelf { display: flex; flex-wrap: wrap; gap: 1rem; align-items: flex-end; } +.pp-cover, .pp-commit-cover { + position: relative; + width: 9.5rem; + height: 13rem; + border-radius: 0.5rem 0.75rem 0.75rem 0.5rem; + box-shadow: + inset 0 0 0 1px rgba(255, 255, 255, 0.06), + inset 0.5rem 0 0.75rem -0.5rem rgba(0, 0, 0, 0.8), + 0 6px 16px rgba(0, 0, 0, 0.45); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.35rem; + padding: 0.75rem; + text-align: center; +} +.pp-cover { transition: transform 0.15s ease, box-shadow 0.15s ease; } +.pp-cover:hover { transform: translateY(-4px) !important; box-shadow: 0 10px 22px rgba(0, 0, 0, 0.55); } +.pp-cover-title { + font-weight: 700; + font-size: 0.85rem; + letter-spacing: 0.14em; + color: rgba(240, 226, 195, 0.92); + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.7), 0 -1px 0 rgba(255, 255, 255, 0.12); + overflow-wrap: anywhere; +} +.pp-cover-inst { + font-size: 0.6rem; + letter-spacing: 0.2em; + text-transform: uppercase; + color: rgba(240, 226, 195, 0.55); +} +.pp-cover-sub { + position: absolute; + bottom: 0.6rem; + font-size: 0.6rem; + color: rgba(240, 226, 195, 0.5); +} + +.pp-commit-card { + display: flex; + gap: 1.25rem; + align-items: center; + padding: 1rem; + border-radius: 0.75rem; + border: 1px solid rgba(55, 65, 81, 0.6); + background-color: rgba(31, 41, 55, 0.35); +} +.pp-commit-card .pp-commit-cover { width: 7rem; height: 9.5rem; flex: none; } + +.pp-rack { display: grid; gap: 0.75rem; grid-template-columns: repeat(auto-fill, minmax(10.5rem, 1fr)); } +.pp-brochure { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.15rem; + padding: 0.75rem 0.875rem; + border-radius: 0.5rem; + text-align: left; + background: linear-gradient(165deg, rgba(45, 55, 72, 0.55), rgba(31, 41, 55, 0.55)); + border: 1px solid rgba(75, 85, 99, 0.5); + transition: transform 0.15s ease, border-color 0.15s ease; +} +.pp-brochure:hover { transform: translateY(-2px); border-color: #06b6d4; } +.pp-brochure-art { font-size: 1.4rem; } +.pp-brochure-name { color: #e5e7eb; font-size: 0.85rem; font-weight: 600; } +.pp-brochure-sub { color: #6b7280; font-size: 0.65rem; } + +/* The open book */ +.pp-overlay { position: fixed; inset: 0; z-index: 60; } +.pp-book-wrap { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + background: rgba(3, 7, 18, 0.72); + backdrop-filter: blur(2px); +} +.pp-book { + position: relative; + width: min(92vw, 720px); + height: min(72vh, 470px); + perspective: 1800px; +} +.pp-page { + position: absolute; + top: 0; + bottom: 0; + width: 50%; + background: + linear-gradient(105deg, rgba(0, 0, 0, 0.08), transparent 12%), + #efe6d0; + color: #3f3428; + padding: 1.1rem 1.2rem; + overflow: hidden; + display: flex; + flex-direction: column; + gap: 0.75rem; +} +.pp-page-left { left: 0; border-radius: 0.6rem 0 0 0.6rem; opacity: 0; transition: opacity 0.35s ease 0.3s; align-items: center; } +.pp-page-right { right: 0; border-radius: 0 0.6rem 0.6rem 0; box-shadow: inset 0.4rem 0 0.6rem -0.4rem rgba(0, 0, 0, 0.35); } +.pp-book.open .pp-page-left { opacity: 1; } +.pp-book-cover { + position: absolute; + top: 0; + bottom: 0; + left: 50%; + width: 50%; + border-radius: 0 0.6rem 0.6rem 0; + transform-origin: left center; + transform: rotateY(0deg); + backface-visibility: hidden; + transition: transform 0.8s cubic-bezier(0.4, 0.1, 0.2, 1); + z-index: 5; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.35rem; + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.06), 0 6px 20px rgba(0, 0, 0, 0.5); +} +.pp-book.open .pp-book-cover { transform: rotateY(-180deg); } +.pp-book-close { + position: absolute; + top: -0.75rem; + right: -0.75rem; + z-index: 8; + width: 2rem; + height: 2rem; + border-radius: 999px; + background: rgba(17, 24, 39, 0.95); + color: #d1d5db; + border: 1px solid rgba(107, 114, 128, 0.5); +} +.pp-book-close:hover { color: #fff; border-color: #06b6d4; } +.pp-page-head { + font-size: 0.7rem; + letter-spacing: 0.18em; + text-transform: uppercase; + color: #8a7a5e; + border-bottom: 1px solid rgba(138, 122, 94, 0.35); + padding-bottom: 0.4rem; + width: 100%; + text-align: center; +} + +/* The rubber stamp */ +.pp-stamp { + --pp-rot: 0deg; + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.1rem; + width: 9rem; + height: 9rem; + border-radius: 999px; + border: 3px solid #9a5b16; + box-shadow: inset 0 0 0 3px #efe6d0, inset 0 0 0 4px #9a5b16; + color: #9a5b16; + transform: rotate(var(--pp-rot)); + margin-top: 1.25rem; + text-align: center; + padding: 0.75rem; + opacity: 0.92; +} +.pp-stamp-genre { font-size: 0.72rem; font-weight: 800; letter-spacing: 0.16em; overflow-wrap: anywhere; } +.pp-stamp-tier { font-size: 0.58rem; letter-spacing: 0.3em; } +.pp-stamp-ghost { + border-style: dashed; + box-shadow: none; + border-color: #b3a68b; + color: #b3a68b; + opacity: 0.8; +} +.pp-stamp-hidden { opacity: 0; } +.pp-stamp-mini { + position: absolute; + top: 0.5rem; + right: 0.5rem; + width: auto; + height: auto; + border-width: 2px; + box-shadow: none; + border-radius: 999px; + font-size: 0.5rem; + font-weight: 800; + letter-spacing: 0.2em; + color: #d9a253; + border-color: #d9a253; + padding: 0.2rem 0.4rem; + margin: 0; + display: inline-block; + transform: rotate(var(--pp-rot)); + opacity: 0.95; +} +.pp-stamp-page::after { + content: ''; + position: absolute; + inset: -10%; + border-radius: 999px; + background: radial-gradient(closest-side, rgba(154, 91, 22, 0.25), transparent 72%); + filter: blur(5px); + opacity: 0; + pointer-events: none; +} +.pp-slam { animation: pp-slam 0.5s cubic-bezier(0.2, 0.8, 0.3, 1) forwards; } +.pp-slam::after { animation: pp-ink 0.45s ease-out 0.12s forwards; } +@keyframes pp-slam { + 0% { transform: rotate(calc(var(--pp-rot) - 15deg)) scale(2.5); opacity: 0; } + 55% { transform: rotate(var(--pp-rot)) scale(0.92); opacity: 1; } + 75% { transform: rotate(var(--pp-rot)) scale(1.05); } + 100% { transform: rotate(var(--pp-rot)) scale(1); opacity: 0.92; } +} +@keyframes pp-ink { + from { opacity: 0; transform: scale(0.6); } + to { opacity: 1; transform: scale(1); } +} +.pp-shake { animation: pp-shake 0.4s ease-out 0.28s; } +@keyframes pp-shake { + 0%, 100% { transform: translate(0, 0) rotate(0); } + 25% { transform: translate(2px, 1px) rotate(0.3deg); } + 50% { transform: translate(-2px, 2px) rotate(-0.25deg); } + 75% { transform: translate(1px, -1px) rotate(0.15deg); } +} + +.pp-invite, .pp-snj, .pp-gold-note { font-size: 0.75rem; text-align: center; } +.pp-invite { color: #6d5d40; } +.pp-snj { color: #6d5d40; margin-top: 2rem; font-style: italic; max-width: 15rem; } +.pp-gold-note { color: #a8946d; font-size: 0.62rem; margin-top: 0.5rem; } +.pp-drills { display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.7rem; color: #6d5d40; } +.pp-drill.cleared { color: #4d7c0f; } + +/* Ticket stubs */ +.pp-stubs { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 0.5rem; padding-right: 0.25rem; } +.pp-stub { + background: #f7f1e3; + border: 1px solid #d8cbaa; + border-left: 2px dashed #b6a98c; + border-radius: 0.25rem 0.4rem 0.4rem 0.25rem; + padding: 0.4rem 0.6rem 0.4rem 0.75rem; + display: grid; + grid-template-columns: auto 1fr; + column-gap: 0.6rem; + align-items: baseline; + box-shadow: 0 1px 2px rgba(63, 52, 40, 0.15); +} +.pp-stub-stars { color: #b8860b; font-size: 0.7rem; letter-spacing: 0.08em; grid-row: span 2; } +.pp-stub-title { font-size: 0.78rem; font-weight: 600; color: #3f3428; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.pp-stub-artist { grid-column: 2; font-size: 0.65rem; color: #6d5d40; } +.pp-stub-meta { grid-column: 2; font-size: 0.6rem; color: #8a7a5e; } +.pp-stub-empty { font-size: 0.72rem; color: #8a7a5e; font-style: italic; padding: 0.75rem 0.25rem; } + +/* Wax-seal commitment ceremony */ +.pp-ceremony { width: 11rem; height: 15rem; } +.pp-wax { + position: absolute; + bottom: 1.4rem; + display: flex; + align-items: center; + justify-content: center; + width: 3.4rem; + height: 3.4rem; + border-radius: 999px; + background: + radial-gradient(circle at 32% 30%, #d24545 0%, #a41f1f 42%, #7c1414 100%); + box-shadow: + inset 0 0 0 4px rgba(124, 20, 20, 0.9), + inset 0 2px 4px rgba(255, 255, 255, 0.25), + 0 3px 8px rgba(0, 0, 0, 0.55); + color: rgba(255, 235, 235, 0.9); + font-weight: 700; + font-size: 1.15rem; + animation: pp-seal-drop 0.9s cubic-bezier(0.25, 0.9, 0.3, 1.15) 0.35s backwards; +} +@keyframes pp-seal-drop { + 0% { transform: translateY(-120px) scale(2.1); opacity: 0; } + 60% { transform: translateY(0) scale(0.9); opacity: 1; } + 80% { transform: translateY(0) scale(1.05); } + 100% { transform: translateY(0) scale(1); } +} + +/* Small screens: the spread stacks; the flip cover would straddle both + pages, so the book simply opens. */ +@media (max-width: 640px) { + .pp-book { height: min(80vh, 620px); } + .pp-page { position: static; width: 100%; height: 50%; border-radius: 0; } + .pp-page-left { border-radius: 0.6rem 0.6rem 0 0; opacity: 1; } + .pp-page-right { border-radius: 0 0 0.6rem 0.6rem; } + .pp-book-cover { display: none; } + .pp-book { display: flex; flex-direction: column; } +} + +@media (prefers-reduced-motion: reduce) { + .pp-book-cover, .pp-page-left, .pp-cover { transition: none; } + .pp-slam, .pp-slam::after, .pp-shake, .pp-wax { animation: none; } + .pp-slam, .pp-stamp-page::after { opacity: 1; } + .pp-stamp-hidden { opacity: 0.92; } +} diff --git a/plugins/career/assets/sfx/chime.mp3 b/plugins/career/assets/sfx/chime.mp3 new file mode 100644 index 0000000..24fd9f2 Binary files /dev/null and b/plugins/career/assets/sfx/chime.mp3 differ diff --git a/plugins/career/assets/sfx/page.mp3 b/plugins/career/assets/sfx/page.mp3 new file mode 100644 index 0000000..de05e8a Binary files /dev/null and b/plugins/career/assets/sfx/page.mp3 differ diff --git a/plugins/career/assets/sfx/seal.mp3 b/plugins/career/assets/sfx/seal.mp3 new file mode 100644 index 0000000..1c0ca97 Binary files /dev/null and b/plugins/career/assets/sfx/seal.mp3 differ diff --git a/plugins/career/assets/sfx/stamp.mp3 b/plugins/career/assets/sfx/stamp.mp3 new file mode 100644 index 0000000..757ba71 Binary files /dev/null and b/plugins/career/assets/sfx/stamp.mp3 differ diff --git a/plugins/career/screen.html b/plugins/career/screen.html index 38c58ca..756f369 100644 --- a/plugins/career/screen.html +++ b/plugins/career/screen.html @@ -3,19 +3,40 @@
Earn stars by playing songs well — 60% accuracy is a star, 75% two, 85% three. Stars unlock bigger stages, and the crowd plays along with you.
-Earn stars by playing songs well — 60% accuracy is a star, 75% two, 85% three. Stars unlock bigger stages, and the crowd plays along with you.
+Commit to an instrument, pick a genre, and stamp your way to its badge — five ★★ songs mint a Bronze. Your passport wall is who you are as a musician.
+ +More genres, whenever you want them — your wall is complete as it is.
+