diff --git a/src/apps/billboard/static/apps/billboard/note-page.js b/src/apps/billboard/static/apps/billboard/note-page.js index 6aa48d3..c648581 100644 --- a/src/apps/billboard/static/apps/billboard/note-page.js +++ b/src/apps/billboard/static/apps/billboard/note-page.js @@ -37,6 +37,16 @@ return m ? m[1] : ''; } + function _showConfirm(modal) { + var el = modal && modal.querySelector('.note-palette-confirm'); + if (el) el.style.display = 'flex'; + } + + function _hideConfirm(modal) { + var el = modal && modal.querySelector('.note-palette-confirm'); + if (el) el.style.display = 'none'; + } + // ── modal lifecycle ─────────────────────────────────────────────────────── function _openModal() { @@ -49,8 +59,7 @@ _wireModal(); } _activeItem.classList.add('note-item--active'); - var confirmEl = _activeModal().querySelector('.note-palette-confirm'); - if (confirmEl) confirmEl.hidden = true; + _hideConfirm(_activeModal()); } function _closeModal() { @@ -68,14 +77,12 @@ clearTimeout(_dismissTimer); _dismissTimer = null; _revertBodyPalette(); - // Remove .previewing from any swatch in the active modal var modal = _activeModal(); if (modal) { modal.querySelectorAll('.note-swatch-body.previewing').forEach(function (s) { s.classList.remove('previewing'); }); - var confirmEl = modal.querySelector('.note-palette-confirm'); - if (confirmEl) confirmEl.hidden = true; + _hideConfirm(modal); } _selectedPalette = null; _originalPalette = null; @@ -90,7 +97,6 @@ modal.querySelectorAll('.note-swatch-body').forEach(function (body) { body.addEventListener('click', function (e) { e.stopPropagation(); - // Clear any in-progress preview first if (_selectedPalette) _revertPreview(); _selectedPalette = _paletteClass(body.parentElement); @@ -98,11 +104,8 @@ body.classList.add('previewing'); _swapBodyPalette(_selectedPalette); + _showConfirm(modal); - var confirmEl = modal.querySelector('.note-palette-confirm'); - if (confirmEl) confirmEl.hidden = false; - - // Auto-revert after 10s _dismissTimer = setTimeout(function () { _revertPreview(); }, 10000); @@ -117,7 +120,7 @@ }); }); - // Confirm NVM → revert preview, hide confirm + // Confirm NVM → revert preview only; main swatch modal stays open modal.querySelectorAll('.note-palette-confirm .btn.btn-cancel').forEach(function (btn) { btn.addEventListener('click', function (e) { e.stopPropagation(); @@ -135,7 +138,11 @@ var url = _activeItem.dataset.setPaletteUrl; var palette = _selectedPalette; var item = _activeItem; - // Body already shows new palette from preview — keep it by not reverting. + // Read label from swatch row while modal is still in DOM + var swatchRow = _activeModal() && _activeModal().querySelector('.' + palette + '[data-palette-label]'); + var paletteLabel = swatchRow + ? swatchRow.dataset.paletteLabel + : palette.slice(8).replace(/^\w/, function (c) { return c.toUpperCase(); }); fetch(url, { method: 'POST', credentials: 'same-origin', @@ -154,13 +161,19 @@ swatch.className = 'note-item__palette ' + palette; imageBox.parentNode.replaceChild(swatch, imageBox); } + var list = item.querySelector('.note-recognitions__list'); + if (list && !item.querySelector('.note-recognitions__palette-line')) { + var li = document.createElement('li'); + li.className = 'note-recognitions__palette-line'; + li.innerHTML = 'Palette: ' + paletteLabel + ''; + list.appendChild(li); + } }); } // ── init ────────────────────────────────────────────────────────────────── function _init() { - // Image-box click → open modal document.querySelectorAll('.note-item__image-box').forEach(function (box) { box.addEventListener('click', function (e) { e.stopPropagation(); @@ -169,7 +182,7 @@ }); }); - // Body click → dismiss modal (and revert any preview) + // Body click → dismiss modal and revert any preview document.body.addEventListener('click', function () { if (_selectedPalette) _revertPreview(); _closeModal(); diff --git a/src/apps/billboard/tests/integrated/test_views.py b/src/apps/billboard/tests/integrated/test_views.py index ff7a1d5..fccc63c 100644 --- a/src/apps/billboard/tests/integrated/test_views.py +++ b/src/apps/billboard/tests/integrated/test_views.py @@ -214,6 +214,17 @@ class NotePageViewTest(TestCase): self.assertContains(response, 'class="note-item__description"') self.assertContains(response, 'class="note-item__image-box"') + def test_palette_modal_renders_swatch_labels(self): + """Each palette option in the swatch modal should display its human-readable + label next to the swatch body so the user knows what they are choosing.""" + Note.objects.create( + user=self.user, slug="stargazer", earned_at=timezone.now() + ) + response = self.client.get("/billboard/my-notes/") + self.assertContains(response, 'class="note-swatch-label"') + self.assertContains(response, "Bardo") + self.assertContains(response, "Sheol") + class NoteSetPaletteViewTest(TestCase): def setUp(self): diff --git a/src/apps/billboard/views.py b/src/apps/billboard/views.py index 44d2610..97dc18a 100644 --- a/src/apps/billboard/views.py +++ b/src/apps/billboard/views.py @@ -8,10 +8,13 @@ from django.shortcuts import redirect, render from apps.applets.utils import applet_context, apply_applet_toggle from apps.dashboard.forms import LineForm from apps.dashboard.models import Post +from apps.dashboard.views import _PALETTE_DEFS from apps.drama.models import GameEvent, Note, ScrollPosition from apps.epic.models import Room from apps.epic.utils import rooms_for_user +_PALETTE_LABELS = {p["name"]: p["label"] for p in _PALETTE_DEFS} + def _recent_posts(user, limit=3): return ( @@ -88,11 +91,15 @@ def room_scroll(request, room_id): }) +def _palette_opts(names): + return [{"name": n, "label": _PALETTE_LABELS.get(n, n)} for n in names] + + _NOTE_META = { "stargazer": { "title": "Stargazer", "description": "You saved your first personal sky chart.", - "palette_options": ["palette-bardo", "palette-sheol"], + "palette_options": _palette_opts(["palette-bardo", "palette-sheol"]), }, "schizo": { "title": "Schizo", @@ -136,6 +143,7 @@ def my_notes(request): "title": _NOTE_META.get(n.slug, {}).get("title", n.slug), "description": _NOTE_META.get(n.slug, {}).get("description", ""), "palette_options": _NOTE_META.get(n.slug, {}).get("palette_options", []), + "palette_label": _PALETTE_LABELS.get(n.palette, "") if n.palette else "", } for n in qs ] diff --git a/src/functional_tests/test_applet_my_notes.py b/src/functional_tests/test_applet_my_notes.py index e3efe86..2d429b8 100644 --- a/src/functional_tests/test_applet_my_notes.py +++ b/src/functional_tests/test_applet_my_notes.py @@ -272,6 +272,12 @@ class StargazerNoteFromDashboardTest(FunctionalTest): original_palette, self.browser.find_element(By.TAG_NAME, "body").get_attribute("class"), ) + # Swatch modal remains open after NVM; only the confirm bar is hidden + self.assertTrue( + self.browser.find_elements(By.CSS_SELECTOR, ".note-palette-modal") + ) + confirm_el = self.browser.find_element(By.CSS_SELECTOR, ".note-palette-confirm") + self.assertFalse(confirm_el.is_displayed()) # ── T2c ────────────────────────────────────────────────────────────────── diff --git a/src/static_src/scss/_note.scss b/src/static_src/scss/_note.scss index e1bf37e..96ffc5d 100644 --- a/src/static_src/scss/_note.scss +++ b/src/static_src/scss/_note.scss @@ -101,6 +101,36 @@ font-size: 0.8rem; opacity: 0.75; } + + .note-recognitions { + margin-top: 0.5rem; + } + + .note-recognitions__header { + font-size: 0.65rem; + font-weight: 600; + opacity: 0.55; + letter-spacing: 0.04em; + margin-bottom: 0.15rem; + } + + .note-recognitions__list { + list-style: none; + padding: 0; + margin: 0; + font-size: 0.75rem; + opacity: 0.8; + display: flex; + flex-direction: column; + gap: 0.1rem; + + strong { font-weight: 700; } + } + + .note-recognitions__dim { + opacity: 0.6; + font-size: 0.65rem; + } } // Image box — right-side; must have a defined size so Selenium can interact with it. @@ -112,22 +142,35 @@ align-items: center; justify-content: center; background: rgba(var(--priUser), 0.12); - border: 1px dashed rgba(var(--priUser), 0.7); + border: 1px dashed rgba(var(--terUser), 0.75); border-radius: 0.25rem; cursor: pointer; font-size: 1.2rem; + color: rgba(var(--terUser), 0.75); opacity: 0.6; &:hover { opacity: 1; } } -// Unlocked palette swatch (right side, same footprint as image-box) +// Confirmed palette swatch — right-side thumbnail, same gradient as .note-swatch-body. +// palette-* class is on the element so CSS vars cascade from that palette automatically. .note-item__palette { width: 3rem; height: 3rem; flex-shrink: 0; - border-radius: 2px; - border: 2px solid rgba(var(--priUser), 0.4); + border-radius: 0.25rem; + border: 0.15rem solid rgba(var(--secUser), 0.5); + background: linear-gradient( + to bottom, + rgba(var(--secUser), 1) 0%, + rgba(var(--secUser), 1) 30%, + rgba(var(--priUser), 1) 30%, + rgba(var(--priUser), 1) 70%, + rgba(var(--terUser), 1) 70%, + rgba(var(--terUser), 1) 85%, + rgba(var(--quaUser), 1) 85%, + rgba(var(--quaUser), 1) 100% + ); } // ── Palette modal ────────────────────────────────────────────────────────── @@ -143,7 +186,7 @@ border-radius: 0.5rem; padding: 0.75rem; gap: 0.5rem; - min-width: 10rem; + min-width: 14rem; &:not([hidden]) { display: flex; flex-direction: column; } @@ -151,11 +194,11 @@ > [class*="palette-"] { display: flex; align-items: center; - gap: 0.5rem; + gap: 0.75rem; .note-swatch-body { - width: 2.5rem; - height: 2.5rem; + width: 3.25rem; + height: 3.25rem; border-radius: 0.25rem; cursor: pointer; // Gradient uses vars scoped to the parent palette-* class, @@ -185,24 +228,26 @@ box-shadow: 0 0 0.75rem rgba(var(--ninUser), 0.6); } } + + .note-swatch-label { + flex: 1; + min-width: 0; + color: rgba(var(--terUser), 1); + font-size: 1rem; + font-weight: bold; + line-height: 1.2; + } } } -// ── Palette swatch color fills ───────────────────────────────────────────── - -.palette-bardo .note-swatch-body, -.note-item__palette.palette-bardo { - background: #2a1a2e; -} - -.palette-sheol .note-swatch-body, -.note-item__palette.palette-sheol { - background: #1a1a2e; -} - // ── Confirm submenu — floats below modal, out of its flow ───────────────── .note-palette-confirm { + // display controlled entirely by JS (style.display) — hidden until a swatch is previewed + display: none; + flex-direction: row; + align-items: center; + gap: 0.5rem; position: absolute; top: calc(100% + 4px); left: 0; @@ -213,9 +258,6 @@ border: 0.1rem solid rgba(var(--secUser), 0.4); border-radius: 0.5rem; padding: 0.5rem 0.75rem; - gap: 0.4rem; - - &:not([hidden]) { display: flex; flex-direction: row; align-items: center; gap: 0.5rem; } p { flex: 1; diff --git a/src/templates/apps/billboard/my_notes.html b/src/templates/apps/billboard/my_notes.html index b07088b..4e8b75f 100644 --- a/src/templates/apps/billboard/my_notes.html +++ b/src/templates/apps/billboard/my_notes.html @@ -15,6 +15,15 @@
{{ item.title }}
{{ item.description }}
+Lock in this palette?