|
| 1 | +<script> |
| 2 | + let { artist } = $props(); |
| 3 | +
|
| 4 | + const palette = ['var(--accent-primary)', 'var(--accent-secondary)', 'var(--seq-2)', 'var(--gender-nonbinary)']; |
| 5 | +
|
| 6 | + function hashString(value = '') { |
| 7 | + let hash = 2166136261; |
| 8 | + for (const char of value) { |
| 9 | + hash ^= char.charCodeAt(0); |
| 10 | + hash = Math.imul(hash, 16777619); |
| 11 | + } |
| 12 | + return hash >>> 0; |
| 13 | + } |
| 14 | +
|
| 15 | + function createRandom(seed) { |
| 16 | + let value = seed || 1; |
| 17 | + return () => { |
| 18 | + value = Math.imul(value, 1664525) + 1013904223; |
| 19 | + return (value >>> 0) / 4294967296; |
| 20 | + }; |
| 21 | + } |
| 22 | +
|
| 23 | + function buildArtwork(source) { |
| 24 | + const seed = hashString(`${source?.artist_id}-${source?.sample_work_title}-${source?.medium_primary}`); |
| 25 | + const random = createRandom(seed); |
| 26 | + const medium = source?.medium_primary ?? 'Other'; |
| 27 | + const lineCount = medium === 'Drawing' ? 8 : medium === 'Photography' ? 3 : medium === 'Film/Video' ? 5 : 6; |
| 28 | + const circles = Array.from({ length: 2 + (seed % 3) }, () => ({ |
| 29 | + cx: 64 + random() * 292, |
| 30 | + cy: 58 + random() * 126, |
| 31 | + r: 12 + random() * 24, |
| 32 | + color: palette[Math.floor(random() * palette.length)], |
| 33 | + })); |
| 34 | + const lines = Array.from({ length: lineCount }, (_, index) => { |
| 35 | + const y = 54 + random() * 142; |
| 36 | + const wobble = 18 + random() * 42; |
| 37 | + return { |
| 38 | + d: `M ${44 + random() * 34} ${y.toFixed(1)} C ${(118 + random() * 74).toFixed(1)} ${(y - wobble).toFixed( |
| 39 | + 1, |
| 40 | + )}, ${(214 + random() * 70).toFixed(1)} ${(y + wobble).toFixed(1)}, ${(338 + random() * 36).toFixed(1)} ${( |
| 41 | + 50 + |
| 42 | + random() * 152 |
| 43 | + ).toFixed(1)}`, |
| 44 | + width: index % 3 === 0 ? 3 : 2, |
| 45 | + }; |
| 46 | + }); |
| 47 | + const panels = Array.from({ length: medium === 'Film/Video' ? 5 : medium === 'Design' ? 6 : 0 }, (_, index) => ({ |
| 48 | + x: 48 + index * 58, |
| 49 | + y: 44 + random() * 20, |
| 50 | + height: 92 + random() * 52, |
| 51 | + })); |
| 52 | + return { circles, lines, panels, accent: palette[seed % palette.length], medium }; |
| 53 | + } |
| 54 | +
|
| 55 | + let artwork = $derived(buildArtwork(artist)); |
| 56 | +</script> |
| 57 | +
|
| 58 | +<figure class="work-card generated-work-card" aria-label={`Generated visual proxy for ${artist.sample_work_title}`}> |
| 59 | + <svg viewBox="0 0 420 250" aria-hidden="true"> |
| 60 | + <rect class="outer" x="18" y="18" width="384" height="214"></rect> |
| 61 | + {#if artwork.panels.length} |
| 62 | + {#each artwork.panels as panel} |
| 63 | + <rect class="panel" x={panel.x} y={panel.y} width="42" height={panel.height}></rect> |
| 64 | + {/each} |
| 65 | + {/if} |
| 66 | + {#if artwork.medium === 'Photography'} |
| 67 | + <rect class="photo-field" x="72" y="56" width="276" height="138"></rect> |
| 68 | + <line class="horizon" x1="72" x2="348" y1="152" y2="152"></line> |
| 69 | + {/if} |
| 70 | + {#each artwork.lines as line} |
| 71 | + <path d={line.d} style:stroke-width={line.width}></path> |
| 72 | + {/each} |
| 73 | + {#each artwork.circles as circle} |
| 74 | + <circle cx={circle.cx} cy={circle.cy} r={circle.r} style:fill={circle.color}></circle> |
| 75 | + {/each} |
| 76 | + <line class="baseline" x1="48" y1="206" x2="372" y2="206"></line> |
| 77 | + <path class="accent" d="M58 34 L362 34" style:stroke={artwork.accent}></path> |
| 78 | + </svg> |
| 79 | + <figcaption> |
| 80 | + <span>Representative artwork</span> |
| 81 | + <em>{artist.sample_work_title}</em> |
| 82 | + {#if artist.sample_work_year} |
| 83 | + <b>{artist.sample_work_year}</b> |
| 84 | + {/if} |
| 85 | + <b>{artist.medium_primary}</b> |
| 86 | + </figcaption> |
| 87 | +</figure> |
| 88 | +
|
| 89 | +<style> |
| 90 | + .generated-work-card svg { |
| 91 | + background: var(--bg-paper); |
| 92 | + } |
| 93 | +
|
| 94 | + .generated-work-card .outer, |
| 95 | + .generated-work-card path, |
| 96 | + .generated-work-card line, |
| 97 | + .generated-work-card .panel { |
| 98 | + fill: none; |
| 99 | + stroke: var(--fg-on-light-strong); |
| 100 | + stroke-linecap: round; |
| 101 | + stroke-linejoin: round; |
| 102 | + } |
| 103 | +
|
| 104 | + .generated-work-card .outer { |
| 105 | + stroke-width: 2; |
| 106 | + } |
| 107 | +
|
| 108 | + .generated-work-card path, |
| 109 | + .generated-work-card line { |
| 110 | + stroke-width: 2; |
| 111 | + } |
| 112 | +
|
| 113 | + .generated-work-card circle { |
| 114 | + opacity: 0.18; |
| 115 | + stroke: var(--fg-on-light-strong); |
| 116 | + stroke-width: 1.5; |
| 117 | + } |
| 118 | +
|
| 119 | + .generated-work-card .panel, |
| 120 | + .generated-work-card .photo-field { |
| 121 | + fill: color-mix(in srgb, var(--fg-on-light-strong) 6%, transparent); |
| 122 | + stroke-width: 1.5; |
| 123 | + } |
| 124 | +
|
| 125 | + .generated-work-card .horizon, |
| 126 | + .generated-work-card .baseline { |
| 127 | + stroke-width: 2.5; |
| 128 | + } |
| 129 | +
|
| 130 | + .generated-work-card .accent { |
| 131 | + stroke-width: 3; |
| 132 | + } |
| 133 | +</style> |
0 commit comments