Corrected 2 Sep 2026 against prototype/screens.jsx: the earned tile's CARD_ART[kind] lookup is at :2412, not :2434 (the other cites — CollectionCard :2393, LockedSilhouette :1743, .cc-sub index.html:712 — still resolve exactly). And the artwork half has an owner now: #480 rules it an extractor job — the thirty-seven CardArt* drawings are static SVG, so nobody draws them, the extractor ports them.
Surfaced while building #396, which changed which cards the grid draws and left how a tile looks untouched. The tile is CollectionCard (prototype/screens.jsx:2393); the app's is card_grid_item_widget.dart. Both of its branches diverge.
Locked
|
Design |
App |
| Top line |
an em-dash, at 0.55 opacity |
nothing |
| Body |
a mono ? — see the correction below |
a stock Icons.help_outline in a filled badge |
| Bottom line |
its place in the set — 03 / 30, at 0.55 opacity |
??? |
The index line is the interesting one: the design tells a learner which card they have not earned, so a locked tile is a gap in a known sequence rather than an anonymous blank. #396 makes that more visible, not less — there is now exactly one locked tile on the screen, and it is the next card the learner will earn.
Earned
|
Design |
App |
| Top line |
CARD 03 / 30, or VISUAL GUIDE |
nothing |
| Body |
the card's own artwork, CARD_ART[kind] (const Art = CARD_ART[card.kind], :2412) |
the module's icon mark in an accent badge |
| Surface |
tinted per kind, CARD_TINT[kind] |
flat surface |
| Bottom line |
the title, and nothing else — see the correction below |
title, then the tag |
| Corner |
a challenge stamp when tried, a dashed offer ring when open (:2419-2441) |
nothing |
The top line's type is already settled. .collect-card .cc-sub
(index.html:712) sets it in mono on the micro step at 0.16em — a step
smaller and a shade wider than the tag under the title, which is where the
app's moduleTag sits today. #410
gave AppText a tracking axis and 0.16em is already on it as
AppTracking.marker, so this line asks for it rather than naming a number.
Two corrections to the tables above
The silhouettes are unreachable. LockedSilhouette branches on 'silhouette' and 'dot', and CollectionCard calls it with card.kind (:2400) — which is botanical, burrs, roastcurve, and never either. Every real locked tile falls through to the mono ?. The two shapes above it are dead code, so only the ? is ported.
The design's tile prints no tag. The earned branch renders cc-title alone (:2447), and index.html:704 gives it no companion. This table ticked the tag as already matching; it does not, and the line comes off, as it came off the card sheet in #385.
Two halves, and they can land apart
The frame is portable now: the sub-lines, the index arithmetic, the per-kind tint and the silhouettes are all in the design source and need no new art.
The artwork needs no illustrator, and it is #480. The question this asked — who draws them — has the same wrong premise #87 turned out to have. All thirty-seven CardArt* components are static SVG taking no props, and tool/extract_icons.js already bridges prototype SVG into bundled assets "rather than redrawing them". It is an extractor job: new colour sentinels for the illustration palette, a second asset family, two call sites. The module's mark stands in meanwhile, as it did before.
The challenge stamp and offer ring ship. The state was already modelled, and the design's own rule decides the ring: earned, has a challenge, has not brewed it — every card that still owes a brew, not only the one the lifecycle has active.
Acceptance criteria
Surfaced while building #396, which changed which cards the grid draws and left how a tile looks untouched. The tile is
CollectionCard(prototype/screens.jsx:2393); the app's iscard_grid_item_widget.dart. Both of its branches diverge.Locked
?— see the correction belowIcons.help_outlinein a filled badge03 / 30, at 0.55 opacity???The index line is the interesting one: the design tells a learner which card they have not earned, so a locked tile is a gap in a known sequence rather than an anonymous blank. #396 makes that more visible, not less — there is now exactly one locked tile on the screen, and it is the next card the learner will earn.
Earned
CARD 03 / 30, orVISUAL GUIDECARD_ART[kind](const Art = CARD_ART[card.kind],:2412)CARD_TINT[kind]surface:2419-2441)The top line's type is already settled.
.collect-card .cc-sub(
index.html:712) sets it in mono on the micro step at 0.16em — a stepsmaller and a shade wider than the tag under the title, which is where the
app's
moduleTagsits today. #410gave
AppTexta tracking axis and 0.16em is already on it asAppTracking.marker, so this line asks for it rather than naming a number.Two corrections to the tables above
The silhouettes are unreachable.
LockedSilhouettebranches on'silhouette'and'dot', andCollectionCardcalls it withcard.kind(:2400) — which isbotanical,burrs,roastcurve, and never either. Every real locked tile falls through to the mono?. The two shapes above it are dead code, so only the?is ported.The design's tile prints no tag. The earned branch renders
cc-titlealone (:2447), andindex.html:704gives it no companion. This table ticked the tag as already matching; it does not, and the line comes off, as it came off the card sheet in #385.Two halves, and they can land apart
The frame is portable now: the sub-lines, the index arithmetic, the per-kind tint and the silhouettes are all in the design source and need no new art.
The artwork needs no illustrator, and it is #480. The question this asked — who draws them — has the same wrong premise #87 turned out to have. All thirty-seven
CardArt*components are static SVG taking no props, andtool/extract_icons.jsalready bridges prototype SVG into bundled assets "rather than redrawing them". It is an extractor job: new colour sentinels for the illustration palette, a second asset family, two call sites. The module's mark stands in meanwhile, as it did before.The challenge stamp and offer ring ship. The state was already modelled, and the design's own rule decides the ring: earned, has a challenge, has not brewed it — every card that still owes a brew, not only the one the lifecycle has active.
Acceptance criteria
NN / totalplace in the set, not???— the mono?, which is the one the design actually rendersCARD NN / totalline and its per-kind surface tint — the tint table generated fromCARD_TINTrather than transcribedflutter analyze, metrics gate and tests clean, with a changelog bullet — 2214 tests