@@ -259,48 +259,39 @@ export function TreasuryInflowsList({
259259 </ ul >
260260 ) : null }
261261
262+ { /* Column headers, like the reference design — the grid below shares
263+ this exact template, which is what actually aligns the columns. */ }
264+ < div className = "hidden gap-x-3 border-b border-border pb-2 text-[11px] font-medium text-muted-foreground sm:grid sm:grid-cols-[minmax(0,1.3fr)_116px_minmax(0,1fr)_44px_20px]" >
265+ < span > { t ( "colFrom" ) } </ span >
266+ < span > { t ( "colSource" ) } </ span >
267+ < span className = "text-right" > { t ( "colAmount" ) } </ span >
268+ < span className = "text-right" > { t ( "colAge" ) } </ span >
269+ < span aria-hidden />
270+ </ div >
271+
262272 < ul className = "divide-y divide-border" >
263- { rows . map ( ( flow ) => (
264- // `flex-wrap` is the mobile layout: at 390px a hex sender + badge +
265- // amount + age don't fit one line, and without the wrap the left
266- // group's overflow painted OVER the amount. Wrapped, the amount
267- // group drops to its own right-aligned line; on desktop nothing
268- // wraps and the row is identical to before.
269- < li key = { flow . hash } className = "flex flex-wrap items-center gap-x-3 gap-y-1 py-2.5" >
270- < div className = "flex min-w-0 flex-1 basis-40 items-center gap-2" >
271- { ( ( ) => {
272- const entity = knownEntity ( flow . from , flow . source , t ) ;
273- if ( ! entity )
274- return (
275- < AddressDisplay
276- address = { flow . from }
277- variant = "compact"
278- showCopy = { false }
279- showExplorer = { false }
280- truncateLength = { 4 }
281- />
282- ) ;
283- // `min-h-8` matches the avatar AddressDisplay renders on hex
284- // rows: without it a named row is text-height, a hex row is
285- // avatar-height, and the card's total height becomes a function
286- // of which senders happen to be on the page — which is the
287- // instability the fillers below exist to prevent.
288- return (
289- < span className = "flex min-h-8 min-w-0 items-center gap-2" >
290- { entity . icon ? (
291- < span className = "relative size-8 shrink-0 overflow-hidden rounded-full bg-muted" >
292- { /* Rider cut-outs reuse the sponsorship card's face
293- crop; square logos just cover; the noggles sit
294- padded on a dark ground like the design shows. */ }
295- { entity . icon . size ? (
296- /* eslint-disable-next-line @next/next/no-img-element -- 32px local asset; next/image adds nothing at this size */
273+ { rows . map ( ( flow ) => {
274+ const entity = knownEntity ( flow . from , flow . source , t ) ;
275+ return (
276+ // One grid, five fixed columns from `sm` up — a row is not a bag of
277+ // floats, and every cell starts where the header says it does.
278+ // Below `sm` the same children wrap: sender+badge line, amount
279+ // right, age+link trailing.
280+ < li
281+ key = { flow . hash }
282+ className = "flex flex-wrap items-center gap-x-3 gap-y-1 py-2 sm:grid sm:grid-cols-[minmax(0,1.3fr)_116px_minmax(0,1fr)_44px_20px]"
283+ >
284+ < div className = "flex min-w-0 flex-1 basis-40 items-center gap-2 sm:flex-none" >
285+ { entity ? (
286+ < >
287+ < span className = "relative size-8 shrink-0 overflow-hidden rounded-full bg-muted" >
288+ { entity . icon ? (
289+ entity . icon . size ? (
290+ /* eslint-disable-next-line @next/next/no-img-element -- 32px local asset; the sponsorship card's verified face crop */
297291 < img
298292 src = { entity . icon . src }
299293 alt = ""
300294 className = "size-full object-cover"
301- // Same crop the sponsorship card renders — the one
302- // face-crop treatment that has been verified on
303- // screen, not a reimplementation of it.
304295 style = { {
305296 objectPosition : entity . icon . pos ,
306297 scale : String ( parseFloat ( entity . icon . size ) / 100 ) ,
@@ -317,45 +308,65 @@ export function TreasuryInflowsList({
317308 : "size-full object-cover"
318309 }
319310 />
320- ) }
321- </ span >
322- ) : null }
311+ )
312+ ) : (
313+ /* A named entity with no known mark still gets ITS
314+ identicon (seeded by address, same generator the
315+ hex rows use) — never a bare text row: one ragged
316+ left edge and the whole column stops reading as a
317+ column. */
318+ /* eslint-disable-next-line @next/next/no-img-element -- tiny remote identicon, same source AddressDisplay uses */
319+ < img
320+ src = { `https://api.dicebear.com/7.x/identicon/svg?seed=${ flow . from . toLowerCase ( ) } &backgroundColor=b6e3f4,c0aede,d1d4f9` }
321+ alt = ""
322+ className = "size-full"
323+ />
324+ ) }
325+ </ span >
323326 < span className = "truncate text-sm" > { entity . name } </ span >
324- </ span >
325- ) ;
326- } ) ( ) }
327- { /* Every row carries its origin, not just auctions. The binary
328- "internal = auction" badge could not say where the other three
329- quarters of the income came from. */ }
330- < span
331- className = { `inline-flex shrink-0 items-center gap-1 rounded-full border px-2 py-0.5 text-[10px] font-medium ${ SOURCE_TONE [ flow . source ] } ` }
332- title = { flow . source === "auction" ? t ( "auctionHint" ) : undefined }
333- >
334- { flow . source === "auction" ? < Gavel className = "size-3" /> : null }
335- { t ( flow . source ) }
327+ </ >
328+ ) : (
329+ < AddressDisplay
330+ address = { flow . from }
331+ variant = "compact"
332+ showCopy = { false }
333+ showExplorer = { false }
334+ truncateLength = { 4 }
335+ />
336+ ) }
337+ </ div >
338+
339+ < span className = "shrink-0" >
340+ < span
341+ className = { `inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[10px] font-medium ${ SOURCE_TONE [ flow . source ] } ` }
342+ title = { flow . source === "auction" ? t ( "auctionHint" ) : undefined }
343+ >
344+ { flow . source === "auction" ? < Gavel className = "size-3" /> : null }
345+ { t ( flow . source ) }
346+ </ span >
336347 </ span >
337- </ div >
338348
339- < span className = "ml-auto shrink-0 font-mono text-sm font-semibold whitespace-nowrap tabular-nums" >
340- +{ formatAmount ( flow . amount , flow . asset , locale ) } { " " }
341- < span className = { ASSET_TONE [ flow . asset ] } > { flow . asset } </ span >
342- </ span >
349+ < span className = "ml-auto shrink-0 font-mono text-sm font-semibold whitespace-nowrap tabular-nums sm:ml-0 sm:justify-self-end " >
350+ +{ formatAmount ( flow . amount , flow . asset , locale ) } { " " }
351+ < span className = { ASSET_TONE [ flow . asset ] } > { flow . asset } </ span >
352+ </ span >
343353
344- < span className = "w-9 shrink-0 text-right font-mono text-[11px] text-muted-foreground" >
345- { ageLabel ( flow . at , now ) }
346- </ span >
354+ < span className = "shrink-0 text-right font-mono text-[11px] text-muted-foreground sm:justify-self-end " >
355+ { ageLabel ( flow . at , now ) }
356+ </ span >
347357
348- < a
349- href = { `https://basescan.org/tx/${ flow . hash } ` }
350- target = "_blank"
351- rel = "noopener noreferrer"
352- aria-label = { t ( "viewTx" ) }
353- className = "shrink-0 text-muted-foreground hover:text-foreground"
354- >
355- < ExternalLink className = "size-3.5" />
356- </ a >
357- </ li >
358- ) ) }
358+ < a
359+ href = { `https://basescan.org/tx/${ flow . hash } ` }
360+ target = "_blank"
361+ rel = "noopener noreferrer"
362+ aria-label = { t ( "viewTx" ) }
363+ className = "shrink-0 text-muted-foreground hover:text-foreground sm:justify-self-end"
364+ >
365+ < ExternalLink className = "size-3.5" />
366+ </ a >
367+ </ li >
368+ ) ;
369+ } ) }
359370
360371 { /* Height reservation. `border-t-transparent` cancels the divider so the
361372 padding shows up as space, not as empty ruled lines. The `h-8` block
0 commit comments