PSA-slab theme: render the verdict as a slab label, matching grade chips in history
This commit is contained in:
parent
3c98373ad6
commit
f910a603c3
2 changed files with 96 additions and 22 deletions
|
|
@ -206,10 +206,16 @@ function renderSlab(g, opts = {}) {
|
||||||
? `realistically ${g.grade_low}–${g.grade_high}` : '';
|
? `realistically ${g.grade_low}–${g.grade_high}` : '';
|
||||||
const title = opts.title || g.card_note || 'Card';
|
const title = opts.title || g.card_note || 'Card';
|
||||||
const bits = [range, `${g.confidence || ''} confidence`].filter(Boolean).join(' · ');
|
const bits = [range, `${g.confidence || ''} confidence`].filter(Boolean).join(' · ');
|
||||||
|
// The kicker sits where a real slab label carries its grader's logo. It
|
||||||
|
// says ESTIMATE first and always — the label is styled to look like the
|
||||||
|
// real thing, so it has to say plainly that it isn't one.
|
||||||
|
const kicker = ['Grade estimate', g.card_type ? (TYPE_LABEL[g.card_type] || 'Card') : '']
|
||||||
|
.filter(Boolean).join(' · ');
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="slab ${slabClass(grade)}">
|
<div class="slab ${slabClass(grade)}">
|
||||||
<div class="slab-main">
|
<div class="slab-main">
|
||||||
${g.card_type ? `<div><span class="card-type-tag">${esc(TYPE_LABEL[g.card_type] || 'Card')}</span></div>` : ''}
|
<div class="slab-kicker">${esc(kicker)}</div>
|
||||||
${opts.hideTitle ? '' : `<div class="slab-title">${esc(title)}</div>`}
|
${opts.hideTitle ? '' : `<div class="slab-title">${esc(title)}</div>`}
|
||||||
<div class="slab-sub">${esc(bits)}</div>
|
<div class="slab-sub">${esc(bits)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -335,6 +341,17 @@ function gradePillClass(grade) {
|
||||||
return 'pill-critical';
|
return 'pill-critical';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mirrors slabClass so a history chip and that card's slab always land on
|
||||||
|
// the same colour band — they're the same verdict shown at two sizes.
|
||||||
|
function gradeChipClass(grade) {
|
||||||
|
if (grade === null || grade === undefined) return 'grade-chip-na';
|
||||||
|
if (grade >= 10) return 'grade-chip-10';
|
||||||
|
if (grade === 9) return 'grade-chip-9';
|
||||||
|
if (grade >= 7) return 'grade-chip-8';
|
||||||
|
if (grade >= 5) return 'grade-chip-6';
|
||||||
|
return 'grade-chip-low';
|
||||||
|
}
|
||||||
|
|
||||||
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
||||||
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||||
|
|
||||||
|
|
@ -370,7 +387,7 @@ function renderHistory() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td><span class="pill ${gradePillClass(g.estimated_grade)}">${
|
<td><span class="grade-chip ${gradeChipClass(g.estimated_grade)}">${
|
||||||
g.estimated_grade === null ? 'n/a' : `PSA ${g.estimated_grade}`}</span></td>
|
g.estimated_grade === null ? 'n/a' : `PSA ${g.estimated_grade}`}</span></td>
|
||||||
<td><span class="cell-conf conf-${esc(g.confidence || 'low')}">${esc(g.confidence || '')}</span></td>
|
<td><span class="cell-conf conf-${esc(g.confidence || 'low')}">${esc(g.confidence || '')}</span></td>
|
||||||
<td class="cardcell-meta cell-when">${esc(fmtWhen(g.created_at))}</td>
|
<td class="cardcell-meta cell-when">${esc(fmtWhen(g.created_at))}</td>
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,16 @@
|
||||||
--accent-2: #3ddcc0;
|
--accent-2: #3ddcc0;
|
||||||
--accent-soft: rgba(77, 155, 255, .14);
|
--accent-soft: rgba(77, 155, 255, .14);
|
||||||
|
|
||||||
|
/* Label chrome only — the red band on a slab label, never a severity.
|
||||||
|
Deliberately deeper than --critical (#f2555a) so the two don't read as
|
||||||
|
the same signal: this one is decoration on a physical-looking object,
|
||||||
|
that one means "distrust this number". */
|
||||||
|
--slab-red: #c8102e;
|
||||||
|
--slab-face: #eceef2;
|
||||||
|
--slab-ink: #0c111a;
|
||||||
|
--slab-ink-2: #5a6474;
|
||||||
|
--slab-rule: #b9c0cc;
|
||||||
|
|
||||||
--good: #23c55e;
|
--good: #23c55e;
|
||||||
--good-text: #4ade80;
|
--good-text: #4ade80;
|
||||||
--warning: #f5a33c;
|
--warning: #f5a33c;
|
||||||
|
|
@ -419,25 +429,53 @@ textarea { resize: vertical; min-height: 64px; }
|
||||||
than as one more statistic on the page. Promoted to a hero element here:
|
than as one more statistic on the page. Promoted to a hero element here:
|
||||||
full width, bigger numerals, real elevation. */
|
full width, bigger numerals, real elevation. */
|
||||||
|
|
||||||
|
/* Rendered as a graded slab's LABEL: light face, dark text, a red band
|
||||||
|
along the top where a real label carries its logo, and the grade boxed
|
||||||
|
off on the right. Against this dark UI a light label reads as a physical
|
||||||
|
object sitting on the page, which is the point — the verdict should look
|
||||||
|
like the thing it's estimating, not like another panel.
|
||||||
|
|
||||||
|
The band deliberately carries "GRADE ESTIMATE" rather than any grader's
|
||||||
|
mark: it keeps the label honest about what it is at a glance, which
|
||||||
|
matters more here than the resemblance does. */
|
||||||
.slab {
|
.slab {
|
||||||
|
position: relative;
|
||||||
display: flex; align-items: stretch; gap: 0;
|
display: flex; align-items: stretch; gap: 0;
|
||||||
border-radius: var(--radius); overflow: hidden;
|
border-radius: 7px; overflow: hidden;
|
||||||
border: 1px solid var(--rule);
|
border: 1px solid var(--slab-rule);
|
||||||
background: var(--surface-2);
|
background: linear-gradient(178deg, #fbfbfc 0%, var(--slab-face) 58%, #dde1e9 100%);
|
||||||
|
color: var(--slab-ink);
|
||||||
box-shadow: var(--shadow-lg);
|
box-shadow: var(--shadow-lg);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.slab-main {
|
.slab::before {
|
||||||
flex: 1; min-width: 0; padding: 20px 22px;
|
content: ''; position: absolute; left: 0; right: 0; top: 0; height: 3px;
|
||||||
display: flex; flex-direction: column; justify-content: center; gap: 6px;
|
background: var(--slab-red);
|
||||||
}
|
}
|
||||||
.slab-title { font-size: 17px; font-weight: 700; line-height: 1.3; letter-spacing: -.01em; }
|
.slab-main {
|
||||||
.slab-sub { font-size: 12.5px; color: var(--muted); }
|
flex: 1; min-width: 0; padding: 17px 20px 15px;
|
||||||
|
display: flex; flex-direction: column; justify-content: center; gap: 5px;
|
||||||
|
}
|
||||||
|
.slab-kicker {
|
||||||
|
display: flex; align-items: center; gap: 7px;
|
||||||
|
font-size: 9.5px; font-weight: 800; letter-spacing: .15em;
|
||||||
|
text-transform: uppercase; color: var(--slab-red);
|
||||||
|
}
|
||||||
|
.slab-kicker::before {
|
||||||
|
content: ''; width: 12px; height: 12px; border-radius: 2.5px; flex: none;
|
||||||
|
background: var(--slab-red);
|
||||||
|
}
|
||||||
|
.slab-title {
|
||||||
|
font-size: 16px; font-weight: 700; line-height: 1.3; letter-spacing: -.01em;
|
||||||
|
color: var(--slab-ink);
|
||||||
|
}
|
||||||
|
.slab-sub { font-size: 12px; color: var(--slab-ink-2); }
|
||||||
.slab-grade {
|
.slab-grade {
|
||||||
position: relative; overflow: hidden;
|
position: relative; overflow: hidden;
|
||||||
flex: none; width: 148px;
|
flex: none; width: 136px;
|
||||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||||
padding: 20px 10px; color: #fff; text-align: center;
|
padding: 18px 10px; color: #fff; text-align: center;
|
||||||
|
border-left: 1px solid var(--slab-rule);
|
||||||
background: linear-gradient(150deg, #1b4f8f, #2f6fd0);
|
background: linear-gradient(150deg, #1b4f8f, #2f6fd0);
|
||||||
}
|
}
|
||||||
.slab-grade .n {
|
.slab-grade .n {
|
||||||
|
|
@ -478,12 +516,31 @@ textarea { resize: vertical; min-height: 64px; }
|
||||||
.slab-10 .slab-grade::after { animation: none; opacity: .25; }
|
.slab-10 .slab-grade::after { animation: none; opacity: .25; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-type-tag {
|
/* Card type now rides in the label's kicker line rather than as a separate
|
||||||
display: inline-block; font-size: 10px; font-weight: 800;
|
chip — one fewer element competing inside a label that should read as a
|
||||||
letter-spacing: .1em; text-transform: uppercase;
|
single printed artifact. */
|
||||||
padding: 3px 9px; border-radius: 5px;
|
|
||||||
background: var(--accent-soft); color: #8fc0ff;
|
/* History grade chips: the slab's grade block in miniature, on the same
|
||||||
border: 1px solid color-mix(in srgb, var(--accent) 38%, transparent);
|
colour bands, so a scan down the list reads as a row of slabs. Kept
|
||||||
|
separate from .pill because those carry SEVERITY in the category table —
|
||||||
|
same shape, unrelated meaning, so they must not share styling. */
|
||||||
|
.grade-chip {
|
||||||
|
display: inline-flex; align-items: center; justify-content: center;
|
||||||
|
min-width: 54px; padding: 5px 9px; border-radius: 5px;
|
||||||
|
font-size: 12px; font-weight: 800; letter-spacing: .03em;
|
||||||
|
color: #fff; white-space: nowrap;
|
||||||
|
background: linear-gradient(150deg, #1b4f8f, #2f6fd0);
|
||||||
|
}
|
||||||
|
.grade-chip-9 { background: linear-gradient(150deg, #0b6d33, #16a34a); }
|
||||||
|
.grade-chip-8 { background: linear-gradient(150deg, #1b4f8f, #2f6fd0); }
|
||||||
|
.grade-chip-6 { background: linear-gradient(150deg, #9a5c0b, #e0891f); }
|
||||||
|
.grade-chip-low { background: linear-gradient(150deg, #8f1f24, #d13a40); }
|
||||||
|
.grade-chip-10 {
|
||||||
|
background: linear-gradient(140deg, #0b6d33, #3ddcc0 38%, #4d9bff 66%, #a97bff);
|
||||||
|
}
|
||||||
|
.grade-chip-na {
|
||||||
|
background: var(--surface-2); color: var(--muted);
|
||||||
|
border: 1px solid var(--rule); font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --------------------------------------------------------------- phone */
|
/* --------------------------------------------------------------- phone */
|
||||||
|
|
@ -503,10 +560,10 @@ textarea { resize: vertical; min-height: 64px; }
|
||||||
|
|
||||||
/* The slab stays horizontal — stacking it would bury the number, which is
|
/* The slab stays horizontal — stacking it would bury the number, which is
|
||||||
the one thing someone opens this app to see. Just tighter. */
|
the one thing someone opens this app to see. Just tighter. */
|
||||||
.slab-main { padding: 15px 16px; }
|
.slab-main { padding: 14px 15px 13px; }
|
||||||
.slab-title { font-size: 15.5px; }
|
.slab-title { font-size: 15px; }
|
||||||
.slab-grade { width: 108px; padding: 16px 8px; }
|
.slab-grade { width: 98px; padding: 15px 8px; }
|
||||||
.slab-grade .n { font-size: 44px; }
|
.slab-grade .n { font-size: 42px; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* On a narrow phone the five-column history table forces sideways
|
/* On a narrow phone the five-column history table forces sideways
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue