From 955c8a12d0e2fc9a792473df45fc6da1825c7584 Mon Sep 17 00:00:00 2001 From: byrongamatos Date: Mon, 13 Jul 2026 14:55:41 +0200 Subject: [PATCH] =?UTF-8?q?feat(career):=20passport=20visuals=20pack=20?= =?UTF-8?q?=E2=80=94=20tilt,=20emerging=20ink,=20gold=20foil?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Career v2, WS4. All CSS + a rAF-throttled pointer handler, no deps: - Trading-card tilt on EARNED artifacts only (shelf covers + the badge page stamp): pointer-tracked perspective rotateX/Y with a glint sweep following the pointer. The cover's jitter rotation moves into a CSS var so the tilt transform composes with it; the blanket cover-hover translate is scoped :not(.pp-tilt) so it can't fight the tilt. Hover-capable pointers only; off under prefers-reduced-motion. - Emerging-stamp ink: the ghost stamp fills by qualifying/required via a conic-gradient — the stamp visibly carves in, no numbers added. - Gold foil preview: the "coming" note gains a dashed foil chip with a periodic shimmer — honest, never earnable-looking. Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 6 ++ plugins/career/assets/career.css | 86 +++++++++++++++++++++++++- plugins/career/screen.js | 75 ++++++++++++++++++++-- plugins/career/tests/passports.test.js | 12 ++++ 4 files changed, 172 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23acc1d..d087ae2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 top-tier clean pass in one key (`keysCleared`), any depth rung, or mastery — rather than only the maxed-speed depth flips. Genres without a curated drill stay songs-only. +- **Career passport visuals pack** — earned covers and badge stamps become + trading cards (pointer-tracked tilt + light glint, hover-capable devices + only); the ghost stamp visibly "carves in" as qualifying songs land (a + conic ink fill, no numbers added); the Gold rung preview is a small foil + chip with a shimmer sweep, still honestly labeled coming. All theatrics + disabled under `prefers-reduced-motion`. - **Career passports (backend)** — the badge-journey layer on top of career stars. New career-plugin endpoints: `GET /api/plugins/career/passports` (per-instrument passport walls: genre badges computed on read from `song_stats` × the library's diff --git a/plugins/career/assets/career.css b/plugins/career/assets/career.css index 631269f..76c2af0 100644 --- a/plugins/career/assets/career.css +++ b/plugins/career/assets/career.css @@ -124,7 +124,7 @@ 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:not(.pp-tilt):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; @@ -475,3 +475,87 @@ margin-top: 0.75rem; font-variant-numeric: tabular-nums; } + +/* ── Visuals pack: trading-card tilt, emerging ink, gold foil ──────────── */ + +/* Trading-card tilt (earned artifacts; JS feeds --pp-tilt-* on hover-capable + pointers only). */ +.pp-tilt { + position: relative; + overflow: hidden; + will-change: transform; +} +.pp-cover.pp-tilt { + transform: perspective(700px) + rotateX(var(--pp-tilt-x, 0deg)) rotateY(var(--pp-tilt-y, 0deg)) + rotate(var(--pp-cover-rot, 0deg)); + transition: transform 0.12s ease, box-shadow 0.15s ease; +} +.pp-cover.pp-tilt:hover { box-shadow: 0 12px 26px rgba(0, 0, 0, 0.6); } +.pp-stamp-page.pp-tilt { + overflow: visible; + transform: perspective(600px) + rotateX(var(--pp-tilt-x, 0deg)) rotateY(var(--pp-tilt-y, 0deg)) + rotate(var(--pp-rot)); + transition: transform 0.12s ease; +} +.pp-tilt::after { + content: ''; + position: absolute; + inset: 0; + border-radius: inherit; + background: linear-gradient(105deg, + transparent calc(var(--pp-glint-x, 50%) - 14%), + rgba(255, 255, 255, 0.16) var(--pp-glint-x, 50%), + transparent calc(var(--pp-glint-x, 50%) + 14%)); + opacity: 0; + transition: opacity 0.2s ease; + pointer-events: none; +} +.pp-tilt:hover::after { opacity: 1; } + +/* Emerging-stamp ink: the ghost fills as qualifying songs land. */ +.pp-stamp-ghost::before { + content: ''; + position: absolute; + inset: 7%; + border-radius: 999px; + background: conic-gradient(rgba(154, 91, 22, 0.16) var(--pp-fill, 0%), transparent 0); + pointer-events: none; +} + +/* Gold foil preview — honest "coming", never earnable-looking. */ +.pp-gold-foil { + position: relative; + overflow: hidden; + display: inline-flex; + align-items: center; + justify-content: center; + margin-top: 0.9rem; + padding: 0.28rem 0.85rem; + border-radius: 999px; + border: 2px dashed #c8b273; + color: #a8946d; + font-size: 0.58rem; + font-weight: 700; + letter-spacing: 0.32em; +} +.pp-gold-foil::after { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(100deg, transparent 40%, rgba(255, 223, 128, 0.35) 50%, transparent 60%); + transform: translateX(-120%); + animation: pp-foil 3.4s ease-in-out infinite; +} +@keyframes pp-foil { + 0%, 55% { transform: translateX(-120%); } + 100% { transform: translateX(120%); } +} + +@media (prefers-reduced-motion: reduce) { + .pp-gold-foil::after { animation: none; } + .pp-cover.pp-tilt, .pp-stamp-page.pp-tilt { transition: none; } + /* The hover glint is motion theatrics too — not just the JS tilt. */ + .pp-tilt::after { display: none; } +} diff --git a/plugins/career/screen.js b/plugins/career/screen.js index 1beb96d..238fe37 100644 --- a/plugins/career/screen.js +++ b/plugins/career/screen.js @@ -479,12 +479,17 @@ function ppCoverHTML(inst, p) { const rot = ppJitter(inst + p.genre_key, 1.6).toFixed(2); - const stamp = p.badge === 'earned' + const earned = p.badge === 'earned'; + const stamp = earned ? `BRONZE` : ''; const stubs = p.qualifying_count === 1 ? '1 stub' : `${p.qualifying_count} stubs`; const hours = fmtHours(p.seconds_total); - return `