note palette: swatch previews body palette, NVM reverts, OK saves sitewide; note_set_palette also saves user.palette — TDD

- note-page.js: body class swap on swatch click; 10s auto-revert timer; NVM reverts;
  .note-item--active persists border/glow while modal open; .previewing on swatch
- billboard/views.py: note_set_palette also saves user.palette via _unlocked_palettes_for_user
- _note.scss: .note-swatch-body gradient (palette vars cascade from parent palette-* class);
  .previewing state; .note-item--active; note-palette-modal tooltip glass;
  note-palette-confirm floats below modal (position:absolute, out of flow)
- my_notes.html: note-item__body wrapper; image-box right; swatch row OK buttons removed
- FTs: T2a URL fix (/recognition → /my-notes); T2b split into preview+persist & NVM tests;
  NoteSetPaletteViewTest.test_also_saves_user_palette IT

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-04-22 23:54:05 -04:00
parent e8687dc050
commit cd5252c185
7 changed files with 258 additions and 98 deletions

View File

@@ -52,55 +52,80 @@
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-wrap: wrap;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
@media (min-width: 900px) { grid-template-columns: repeat(3, 1fr); }
@media (min-width: 1200px) { grid-template-columns: repeat(4, 1fr); }
}
.note-item {
position: relative;
display: flex;
flex-direction: column;
gap: 0.4rem;
flex-direction: row;
align-items: flex-start;
gap: 0.75rem;
padding: 0.75rem;
background: rgba(var(--priUser), 0.06);
border: 1px solid rgba(var(--priUser), 0.2);
border-radius: 4px;
width: 14rem;
background-color: rgba(var(--tooltip-bg), 0.75);
backdrop-filter: blur(6px);
border: 0.1rem solid rgba(var(--secUser), 0.4);
border-radius: 0.5rem;
cursor: help;
transition: border-color 0.15s, box-shadow 0.15s;
&:hover,
&:active,
&.note-item--active {
border-color: rgba(var(--terUser), 1);
opacity: 1;
box-shadow: 0 0 10px rgba(var(--ninUser), 0.35);
.note-item__title { color: rgba(var(--terUser), 1); }
}
.note-item__body {
flex: 1;
min-width: 0;
}
.note-item__title {
margin: 0;
font-weight: bold;
font-size: 1rem;
transition: color 0.15s;
}
.note-item__description {
margin: 0;
font-size: 0.85rem;
margin: 0.25rem 0 0;
font-size: 0.8rem;
opacity: 0.75;
}
}
// Image box — must have a defined size so Selenium can interact with it.
// Image box — right-side; must have a defined size so Selenium can interact with it.
.note-item__image-box {
width: 5rem;
height: 5rem;
width: 3rem;
height: 3rem;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(var(--priUser), 0.12);
border: 1px dashed rgba(var(--priUser), 0.4);
border-radius: 2px;
border: 1px dashed rgba(var(--priUser), 0.7);
border-radius: 0.25rem;
cursor: pointer;
font-size: 1.5rem;
font-size: 1.2rem;
opacity: 0.6;
&:hover { opacity: 1; }
}
// Unlocked palette swatch inside a note item
// Unlocked palette swatch (right side, same footprint as image-box)
.note-item__palette {
width: 5rem;
height: 5rem;
width: 3rem;
height: 3rem;
flex-shrink: 0;
border-radius: 2px;
border: 2px solid rgba(var(--priUser), 0.4);
}
@@ -112,12 +137,13 @@
top: 0;
left: 0;
z-index: 200;
background: var(--bg, #1a1a1a);
border: 1px solid rgba(var(--priUser), 0.4);
border-radius: 4px;
background-color: rgba(var(--tooltip-bg), 0.92);
backdrop-filter: blur(8px);
border: 0.1rem solid rgba(var(--secUser), 0.4);
border-radius: 0.5rem;
padding: 0.75rem;
gap: 0.5rem;
min-width: 12rem;
min-width: 10rem;
&:not([hidden]) { display: flex; flex-direction: column; }
@@ -130,12 +156,34 @@
.note-swatch-body {
width: 2.5rem;
height: 2.5rem;
border-radius: 2px;
border-radius: 0.25rem;
cursor: pointer;
border: 2px solid rgba(var(--priUser), 0.3);
// Gradient uses vars scoped to the parent palette-* class,
// so each swatch shows its own palette's colours (same as .swatch).
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%
);
border: 0.15rem solid rgba(var(--secUser), 0.5);
flex-shrink: 0;
transition: border-color 0.12s, box-shadow 0.12s;
&:hover { border-color: rgba(var(--priUser), 0.8); }
&:hover {
border-color: rgba(var(--terUser), 1);
box-shadow: 0 0 6px rgba(var(--ninUser), 0.3);
}
&.previewing {
border: 0.2rem solid rgba(var(--ninUser), 1);
box-shadow: 0 0 0.75rem rgba(var(--ninUser), 0.6);
}
}
}
}
@@ -152,16 +200,25 @@
background: #1a1a2e;
}
// ── Confirm submenu ────────────────────────────────────────────────────────
// ── Confirm submenu — floats below modal, out of its flow ─────────────────
.note-palette-confirm {
border-top: 1px solid rgba(var(--priUser), 0.2);
padding-top: 0.5rem;
position: absolute;
top: calc(100% + 4px);
left: 0;
right: 0;
z-index: 201;
background-color: rgba(var(--tooltip-bg), 0.92);
backdrop-filter: blur(8px);
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: column; }
&:not([hidden]) { display: flex; flex-direction: row; align-items: center; gap: 0.5rem; }
p {
flex: 1;
margin: 0;
font-size: 0.85rem;
}