Redesign dashboard: refined-minimal layout with product thumbnails

Move the dashboard from boxed glass cards to an airy, hairline-separated
layout where type and whitespace carry the page, then surface listing
images as the visual anchor.

- Solid surfaces (drop backdrop-filter glass, per spec); calm flat field
  with a single cool top-glow replacing the drifting aurora and dot-grid.
- Recent results / recent alerts are now product feeds with 52px
  thumbnails, listing titles, and price; image_url threaded through
  ResultRow/AlertRow (data already stored, just unsurfaced).
- ListResults gains an OnlyAlerted filter so the alerts panel reuses its
  decryption path instead of a raw query.
- Dashboard recent results capped at 8 for a balanced snapshot.
This commit is contained in:
prosolis
2026-06-20 12:42:00 -07:00
parent ed03494edf
commit 9a7f8b47a3
8 changed files with 647 additions and 392 deletions

View File

@@ -1,9 +1,9 @@
/* Veola — Sega-blue palette and component overrides for Tailwind play CDN. */
:root {
--bg: #1a2b6d;
--surface: #1f3380;
--surface-2: #243a93;
--bg: #16224f;
--surface: #243a93;
--surface-2: #2c47ad;
--accent: #00a4e4;
--yellow: #f5c400;
--text: #ffffff;
@@ -25,59 +25,175 @@ html, body {
a { color: var(--accent); }
a:hover { text-decoration: underline; }
/* Solid surfaces, per the spec's "blue mode": no glassmorphism, no blur. A
clear value gap between --bg and --surface is what lifts cards off the
page; a 1px top highlight + soft blue shadow gives the cartridge edge. */
.v-card {
position: relative;
isolation: isolate;
background:
linear-gradient(180deg, rgba(36, 58, 147, 0.82), rgba(31, 51, 128, 0.82));
backdrop-filter: blur(10px) saturate(140%);
-webkit-backdrop-filter: blur(10px) saturate(140%);
background: var(--surface);
border: 1px solid var(--border);
border-radius: 10px;
box-shadow: var(--shadow);
border-radius: 12px;
box-shadow: 0 1px 2px rgba(0, 0, 40, 0.25);
transition:
transform 180ms ease,
box-shadow 180ms ease,
border-color 180ms ease;
}
.v-card::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
padding: 1px;
background: linear-gradient(135deg,
rgba(0, 164, 228, 0.65) 0%,
rgba(245, 196, 0, 0.30) 45%,
rgba(255, 255, 255, 0.04) 100%);
-webkit-mask:
linear-gradient(#000 0 0) content-box,
linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
pointer-events: none;
z-index: -1;
}
.v-card:hover {
transform: translateY(-2px);
box-shadow:
0 10px 28px rgba(0, 0, 80, 0.55),
0 0 0 1px rgba(0, 164, 228, 0.30);
border-color: rgba(255, 255, 255, 0.2);
box-shadow: 0 2px 8px rgba(0, 0, 40, 0.3);
}
.v-card-flat {
position: relative;
isolation: isolate;
background:
linear-gradient(180deg, rgba(36, 58, 147, 0.70), rgba(31, 51, 128, 0.70));
backdrop-filter: blur(8px) saturate(130%);
-webkit-backdrop-filter: blur(8px) saturate(130%);
background: var(--surface);
border: 1px solid var(--border);
border-radius: 10px;
}
.v-divider { border-top: 1px solid var(--border); }
/* --- Console readout / page header system -------------------------------
The dashboard's signature: a mono "terminal" kicker over a heavy display
title, and the Spend/Saved pair styled as an arcade scoreboard readout.
JetBrains Mono is the machine voice here (labels, counts); Outfit carries
the title. */
.v-kicker {
font-family: 'JetBrains Mono', ui-monospace, monospace;
font-size: 0.72rem;
font-weight: 500;
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--accent);
}
.v-kicker .v-kicker-sep { color: var(--text-2); opacity: 0.6; }
.v-kicker .v-kicker-dim { color: var(--text-2); }
/* --- Refined header + metric system -------------------------------------
Calm and spacious: type and whitespace carry the page. No glow, no
uppercase-mono labels, no accent rules. Mono is reserved for the numbers
themselves; Outfit carries everything else. */
.v-page-title {
font-family: 'Outfit', system-ui, sans-serif;
font-size: 1.9rem;
font-weight: 600;
letter-spacing: -0.015em;
line-height: 1.1;
text-shadow: none;
}
.v-page-sub {
color: var(--text-2);
font-size: 0.95rem;
margin-top: 0.3rem;
}
/* Hero metric: a small quiet label over a large clean number. Presented in
open space with a hairline, not a boxed card. */
.v-metric-label {
color: var(--text-2);
font-size: 0.82rem;
letter-spacing: 0.01em;
}
.v-metric-value {
font-family: 'JetBrains Mono', ui-monospace, monospace;
font-size: clamp(2rem, 3.8vw, 2.75rem);
font-weight: 600;
line-height: 1.05;
margin-top: 0.45rem;
}
.v-metric-value.v-price-deal { color: var(--success); }
.v-metric-sub {
color: var(--text-2);
font-size: 0.82rem;
margin-top: 0.5rem;
}
/* Secondary stat: smaller label/number pair for the supporting counts. */
.v-stat-label {
color: var(--text-2);
font-size: 0.78rem;
letter-spacing: 0.01em;
}
.v-stat-value {
font-family: 'JetBrains Mono', ui-monospace, monospace;
font-size: 1.5rem;
font-weight: 600;
line-height: 1;
margin-top: 0.3rem;
}
/* Understated section heading for list panels — sized to sit below the page
title, sentence case, no glow. */
.v-section-title {
font-family: 'Outfit', system-ui, sans-serif;
font-size: 0.95rem;
font-weight: 600;
letter-spacing: 0;
color: var(--text);
text-shadow: none;
}
/* Hairline used to separate open sections instead of boxing them. */
.v-rule {
border: 0;
border-top: 1px solid var(--border);
}
/* Quiet empty-state line. */
.v-empty {
color: var(--text-2);
font-size: 0.9rem;
padding: 0.4rem 0;
}
/* --- Product feed (thumbnails) ------------------------------------------
Listing thumbnails are what give the data life. A square cropped image,
the listing title as the line that earns its space, price on the right. */
.v-thumb {
width: 52px;
height: 52px;
flex: none;
border-radius: 8px;
object-fit: cover;
background: var(--surface-2);
border: 1px solid var(--border);
display: block;
}
.v-thumb-empty {
display: flex;
align-items: center;
justify-content: center;
color: var(--text-2);
font-size: 1.1rem;
font-weight: 600;
text-transform: uppercase;
}
.v-feed { display: block; }
.v-feed-row {
display: flex;
align-items: center;
gap: 0.85rem;
padding: 0.7rem 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.v-feed-row:last-child { border-bottom: 0; }
.v-feed-name {
display: block;
font-weight: 500;
color: var(--text);
}
a.v-feed-name:hover { color: var(--accent); text-decoration: none; }
.v-feed-meta {
color: var(--text-2);
font-size: 0.8rem;
margin-top: 0.15rem;
}
.v-feed-price { flex: none; white-space: nowrap; }
.v-btn {
background: var(--accent);
color: white;
@@ -212,21 +328,22 @@ a:hover { text-decoration: underline; }
table.v-table { width: 100%; border-collapse: collapse; }
table.v-table th {
text-align: left;
font-size: 0.75rem;
letter-spacing: 0.05em;
text-transform: uppercase;
font-size: 0.74rem;
font-weight: 500;
letter-spacing: 0.01em;
color: var(--text-2);
padding: 0.6rem 0.75rem;
padding: 0.5rem 0.75rem 0.7rem;
border-bottom: 1px solid var(--border);
}
table.v-table td {
padding: 0.7rem 0.75rem;
border-bottom: 1px solid var(--border);
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
vertical-align: middle;
}
/* No zebra — hairline rows and a faint hover keep it calm but scannable. */
table.v-table td { transition: background 140ms ease; }
table.v-table tr { transition: transform 140ms ease; }
table.v-table tbody tr:hover td { background: rgba(0, 164, 228, 0.08); }
table.v-table tbody tr:last-child td { border-bottom: 0; }
table.v-table tbody tr:hover td { background: rgba(255, 255, 255, 0.04); }
.v-error-text { color: var(--danger); font-size: 0.85rem; }
.v-muted { color: var(--text-2); }

View File

@@ -23,17 +23,20 @@ body {
html {
background-color: var(--bg);
/* Static dot-grid baked into the root background so the two pseudo-element
slots stay free for the animated blobs. */
/* Refined direction: dot-grid and drifting blobs removed. A single calm,
cool top-glow gradient gives the field depth without reading as a smudge
or animating in the user's peripheral vision. */
background-image: radial-gradient(
circle,
rgba(255, 255, 255, 0.045) 1px,
transparent 1px
120% 75% at 50% -15%,
#1f3074 0%,
var(--bg) 58%
);
background-size: 26px 26px;
background-attachment: fixed;
}
/* Retire the animated aurora blobs for the refined look. */
html::before, html::after { display: none; }
/* Two aurora blobs animated with seven waypoints each (no `alternate`, so
the second half doesn't mirror the first) on incommensurate periods —
the two cycles drift in and out of phase, so the eye never locks onto a
@@ -48,13 +51,13 @@ html::before {
pointer-events: none;
background: radial-gradient(
closest-side,
rgba(0, 164, 228, 0.60),
rgba(0, 164, 228, 0.13),
rgba(0, 164, 228, 0.0) 70%
);
width: 70vmax;
height: 70vmax;
width: 60vmax;
height: 60vmax;
border-radius: 50%;
filter: blur(60px);
filter: blur(70px);
/* Centered base; keyframes drive all motion from here. */
top: 50%; left: 50%;
margin-top: -35vmax; margin-left: -35vmax;
@@ -68,15 +71,17 @@ html::after {
inset: 0;
z-index: -2;
pointer-events: none;
/* Cooled to a faint mint instead of arcade yellow: the warm blob read as a
smudge against the cold palette. Kept subtle so it's ambient, not a wash. */
background: radial-gradient(
closest-side,
rgba(245, 196, 0, 0.50),
rgba(245, 196, 0, 0.0) 70%
rgba(0, 228, 164, 0.07),
rgba(0, 228, 164, 0.0) 70%
);
width: 55vmax;
height: 55vmax;
width: 46vmax;
height: 46vmax;
border-radius: 50%;
filter: blur(70px);
filter: blur(80px);
top: 50%; left: 50%;
margin-top: -27.5vmax; margin-left: -27.5vmax;
animation: v-retro-drift-b 29s cubic-bezier(.5,.2,.3,.8) infinite;
@@ -104,13 +109,11 @@ html::after {
100% { transform: translate3d( 28vw, -25vh, 0) scale(1.00); opacity: 0.70; }
}
/* Display headings: heavier, slightly tighter, with an accent glow that ties
into the card border gradient. Class-free selectors so existing Tailwind
utilities (text-3xl etc.) stack on top untouched. */
/* Refined direction: headings stay clean — no glow, modest weight. The
page-title/section-title classes in app.css own the real hierarchy. */
h1, h2 {
font-weight: 800;
letter-spacing: -0.02em;
text-shadow: 0 2px 14px rgba(0, 164, 228, 0.30);
font-weight: 600;
letter-spacing: -0.01em;
}
@media (prefers-reduced-motion: reduce) {

File diff suppressed because one or more lines are too long