diff --git a/static/app.js b/static/app.js index 99789d2..5035c9f 100644 --- a/static/app.js +++ b/static/app.js @@ -206,10 +206,16 @@ function renderSlab(g, opts = {}) { ? `realistically ${g.grade_low}–${g.grade_high}` : ''; const title = opts.title || g.card_note || 'Card'; 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 `
- ${g.card_type ? `
${esc(TYPE_LABEL[g.card_type] || 'Card')}
` : ''} +
${esc(kicker)}
${opts.hideTitle ? '' : `
${esc(title)}
`}
${esc(bits)}
@@ -335,6 +341,17 @@ function gradePillClass(grade) { 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', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; @@ -370,7 +387,7 @@ function renderHistory() {
- ${ + ${ g.estimated_grade === null ? 'n/a' : `PSA ${g.estimated_grade}`} ${esc(g.confidence || '')} ${esc(fmtWhen(g.created_at))} diff --git a/static/style.css b/static/style.css index 934366c..06e61a6 100644 --- a/static/style.css +++ b/static/style.css @@ -32,6 +32,16 @@ --accent-2: #3ddcc0; --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-text: #4ade80; --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: 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 { + position: relative; display: flex; align-items: stretch; gap: 0; - border-radius: var(--radius); overflow: hidden; - border: 1px solid var(--rule); - background: var(--surface-2); + border-radius: 7px; overflow: hidden; + border: 1px solid var(--slab-rule); + background: linear-gradient(178deg, #fbfbfc 0%, var(--slab-face) 58%, #dde1e9 100%); + color: var(--slab-ink); box-shadow: var(--shadow-lg); width: 100%; } -.slab-main { - flex: 1; min-width: 0; padding: 20px 22px; - display: flex; flex-direction: column; justify-content: center; gap: 6px; +.slab::before { + content: ''; position: absolute; left: 0; right: 0; top: 0; height: 3px; + background: var(--slab-red); } -.slab-title { font-size: 17px; font-weight: 700; line-height: 1.3; letter-spacing: -.01em; } -.slab-sub { font-size: 12.5px; color: var(--muted); } +.slab-main { + 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 { position: relative; overflow: hidden; - flex: none; width: 148px; + flex: none; width: 136px; 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); } .slab-grade .n { @@ -478,12 +516,31 @@ textarea { resize: vertical; min-height: 64px; } .slab-10 .slab-grade::after { animation: none; opacity: .25; } } -.card-type-tag { - display: inline-block; font-size: 10px; font-weight: 800; - letter-spacing: .1em; text-transform: uppercase; - padding: 3px 9px; border-radius: 5px; - background: var(--accent-soft); color: #8fc0ff; - border: 1px solid color-mix(in srgb, var(--accent) 38%, transparent); +/* Card type now rides in the label's kicker line rather than as a separate + chip — one fewer element competing inside a label that should read as a + single printed artifact. */ + +/* History grade chips: the slab's grade block in miniature, on the same + 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 */ @@ -503,10 +560,10 @@ textarea { resize: vertical; min-height: 64px; } /* The slab stays horizontal — stacking it would bury the number, which is the one thing someone opens this app to see. Just tighter. */ - .slab-main { padding: 15px 16px; } - .slab-title { font-size: 15.5px; } - .slab-grade { width: 108px; padding: 16px 8px; } - .slab-grade .n { font-size: 44px; } + .slab-main { padding: 14px 15px 13px; } + .slab-title { font-size: 15px; } + .slab-grade { width: 98px; padding: 15px 8px; } + .slab-grade .n { font-size: 42px; } } /* On a narrow phone the five-column history table forces sideways