your personal website on atproto - mirror blento.app

like this

jycouet 510a5ac3 04eb63a7

+61 -31
+15 -12
src/lib/cards/GitHubContributorsCard/GitHubContributorsCard.svelte
··· 13 let owner: string = $derived(item.cardData.owner ?? ''); 14 let repo: string = $derived(item.cardData.repo ?? ''); 15 let repoKey: string = $derived(owner && repo ? `${owner}/${repo}` : ''); 16 - let layout: 'hex' | 'grid' = $derived(item.cardData.layout ?? 'hex'); 17 18 let serverContributors: GitHubContributor[] = $derived.by(() => { 19 if (!repoKey) return []; ··· 61 const MIN_SIZE = 16; 62 const MAX_SIZE = 120; 63 64 - function hexCapacity(size: number, availW: number, availH: number): number { 65 const colsWide = Math.floor((availW + GAP) / (size + GAP)); 66 if (colsWide < 1) return 0; 67 const colsNarrow = Math.max(1, colsWide - 1); ··· 85 86 let lo = MIN_SIZE; 87 let hi = MAX_SIZE; 88 - const capacityFn = layout === 'hex' ? hexCapacity : gridCapacity; 89 90 while (lo <= hi) { 91 const mid = Math.floor((lo + hi) / 2); 92 - const availW = containerWidth - (layout === 'hex' ? mid / 2 : 0); 93 - const availH = containerHeight - (layout === 'hex' ? mid / 2 : 0); 94 if (availW <= 0 || availH <= 0) { 95 hi = mid - 1; 96 continue; ··· 105 return Math.max(MIN_SIZE, hi); 106 }); 107 108 - let padding = $derived(layout === 'hex' ? computedSize / 4 : 0); 109 110 let rows = $derived.by(() => { 111 - const availW = containerWidth - (layout === 'hex' ? computedSize / 4 : 0); 112 if (availW <= 0) return [] as GitHubContributor[][]; 113 114 const colsWide = Math.floor((availW + GAP) / (computedSize + GAP)); 115 - const colsNarrow = layout === 'hex' ? Math.max(1, colsWide - 1) : colsWide; 116 117 // Calculate row sizes from bottom up, then reverse for incomplete row at top 118 const rowSizes: number[] = []; 119 let remaining = namedContributors.length; 120 let rowNum = 0; 121 while (remaining > 0) { 122 - const cols = layout === 'hex' && rowNum % 2 === 0 ? colsNarrow : colsWide; 123 rowSizes.push(Math.min(cols, remaining)); 124 remaining -= cols; 125 rowNum++; ··· 139 let textSize = $derived( 140 computedSize < 24 ? 'text-[10px]' : computedSize < 40 ? 'text-xs' : 'text-sm' 141 ); 142 </script> 143 144 <div ··· 162 href="https://github.com/{contributor.username}" 163 target="_blank" 164 rel="noopener noreferrer" 165 - class="accent:ring-accent-500 block rounded-full ring-2 ring-white transition-transform hover:scale-110 dark:ring-neutral-900" 166 > 167 {#if contributor.avatarUrl} 168 <img 169 src={contributor.avatarUrl} 170 alt={contributor.username} 171 - class="rounded-full object-cover" 172 style="width: {computedSize}px; height: {computedSize}px;" 173 /> 174 {:else} 175 <div 176 - class="bg-base-200 dark:bg-base-700 accent:bg-accent-400 flex items-center justify-center rounded-full" 177 style="width: {computedSize}px; height: {computedSize}px;" 178 > 179 <span
··· 13 let owner: string = $derived(item.cardData.owner ?? ''); 14 let repo: string = $derived(item.cardData.repo ?? ''); 15 let repoKey: string = $derived(owner && repo ? `${owner}/${repo}` : ''); 16 + let layout: 'grid' | 'cinema' = $derived(item.cardData.layout ?? 'grid'); 17 + let shape: 'square' | 'circle' = $derived(item.cardData.shape ?? 'square'); 18 19 let serverContributors: GitHubContributor[] = $derived.by(() => { 20 if (!repoKey) return []; ··· 62 const MIN_SIZE = 16; 63 const MAX_SIZE = 120; 64 65 + function cinemaCapacity(size: number, availW: number, availH: number): number { 66 const colsWide = Math.floor((availW + GAP) / (size + GAP)); 67 if (colsWide < 1) return 0; 68 const colsNarrow = Math.max(1, colsWide - 1); ··· 86 87 let lo = MIN_SIZE; 88 let hi = MAX_SIZE; 89 + const capacityFn = layout === 'cinema' ? cinemaCapacity : gridCapacity; 90 91 while (lo <= hi) { 92 const mid = Math.floor((lo + hi) / 2); 93 + const availW = containerWidth - (layout === 'cinema' ? mid / 2 : 0); 94 + const availH = containerHeight - (layout === 'cinema' ? mid / 2 : 0); 95 if (availW <= 0 || availH <= 0) { 96 hi = mid - 1; 97 continue; ··· 106 return Math.max(MIN_SIZE, hi); 107 }); 108 109 + let padding = $derived(layout === 'cinema' ? computedSize / 4 : 0); 110 111 let rows = $derived.by(() => { 112 + const availW = containerWidth - (layout === 'cinema' ? computedSize / 4 : 0); 113 if (availW <= 0) return [] as GitHubContributor[][]; 114 115 const colsWide = Math.floor((availW + GAP) / (computedSize + GAP)); 116 + const colsNarrow = layout === 'cinema' ? Math.max(1, colsWide - 1) : colsWide; 117 118 // Calculate row sizes from bottom up, then reverse for incomplete row at top 119 const rowSizes: number[] = []; 120 let remaining = namedContributors.length; 121 let rowNum = 0; 122 while (remaining > 0) { 123 + const cols = layout === 'cinema' && rowNum % 2 === 0 ? colsNarrow : colsWide; 124 rowSizes.push(Math.min(cols, remaining)); 125 remaining -= cols; 126 rowNum++; ··· 140 let textSize = $derived( 141 computedSize < 24 ? 'text-[10px]' : computedSize < 40 ? 'text-xs' : 'text-sm' 142 ); 143 + 144 + let shapeClass = $derived(shape === 'circle' ? 'rounded-full' : 'rounded-lg'); 145 </script> 146 147 <div ··· 165 href="https://github.com/{contributor.username}" 166 target="_blank" 167 rel="noopener noreferrer" 168 + class="accent:ring-accent-500 block {shapeClass} ring-2 ring-white transition-transform hover:scale-110 dark:ring-neutral-900" 169 > 170 {#if contributor.avatarUrl} 171 <img 172 src={contributor.avatarUrl} 173 alt={contributor.username} 174 + class="{shapeClass} object-cover" 175 style="width: {computedSize}px; height: {computedSize}px;" 176 /> 177 {:else} 178 <div 179 + class="bg-base-200 dark:bg-base-700 accent:bg-accent-400 flex items-center justify-center {shapeClass}" 180 style="width: {computedSize}px; height: {computedSize}px;" 181 > 182 <span
+46 -19
src/lib/cards/GitHubContributorsCard/GitHubContributorsCardSettings.svelte
··· 5 let { item = $bindable() }: SettingsComponentProps = $props(); 6 7 const layoutOptions = [ 8 - { value: 'hex', label: 'Hexagon' }, 9 - { value: 'grid', label: 'Grid' } 10 ]; 11 12 - let layout = $derived(item.cardData.layout ?? 'hex'); 13 </script> 14 15 - <div class="flex flex-col gap-2"> 16 - <Label>Layout</Label> 17 - <div class="flex gap-2"> 18 - {#each layoutOptions as opt (opt.value)} 19 - <button 20 - class={[ 21 - 'flex-1 rounded-xl border px-3 py-2 text-sm transition-colors', 22 - layout === opt.value 23 - ? 'bg-accent-500 border-accent-500 text-white' 24 - : 'bg-base-100 dark:bg-base-800 border-base-300 dark:border-base-700 text-base-900 dark:text-base-100 hover:border-accent-400' 25 - ]} 26 - onclick={() => (item.cardData.layout = opt.value)} 27 - > 28 - {opt.label} 29 - </button> 30 - {/each} 31 </div> 32 </div>
··· 5 let { item = $bindable() }: SettingsComponentProps = $props(); 6 7 const layoutOptions = [ 8 + { value: 'grid', label: 'Grid' }, 9 + { value: 'cinema', label: 'Cinema' } 10 + ]; 11 + 12 + const shapeOptions = [ 13 + { value: 'square', label: 'Square' }, 14 + { value: 'circle', label: 'Circle' } 15 ]; 16 17 + let layout = $derived(item.cardData.layout ?? 'grid'); 18 + let shape = $derived(item.cardData.shape ?? 'square'); 19 </script> 20 21 + <div class="flex flex-col gap-4"> 22 + <div class="flex flex-col gap-2"> 23 + <Label>Layout</Label> 24 + <div class="flex gap-2"> 25 + {#each layoutOptions as opt (opt.value)} 26 + <button 27 + class={[ 28 + 'flex-1 rounded-xl border px-3 py-2 text-sm transition-colors', 29 + layout === opt.value 30 + ? 'bg-accent-500 border-accent-500 text-white' 31 + : 'bg-base-100 dark:bg-base-800 border-base-300 dark:border-base-700 text-base-900 dark:text-base-100 hover:border-accent-400' 32 + ]} 33 + onclick={() => (item.cardData.layout = opt.value)} 34 + > 35 + {opt.label} 36 + </button> 37 + {/each} 38 + </div> 39 + </div> 40 + 41 + <div class="flex flex-col gap-2"> 42 + <Label>Shape</Label> 43 + <div class="flex gap-2"> 44 + {#each shapeOptions as opt (opt.value)} 45 + <button 46 + class={[ 47 + 'flex-1 rounded-xl border px-3 py-2 text-sm transition-colors', 48 + shape === opt.value 49 + ? 'bg-accent-500 border-accent-500 text-white' 50 + : 'bg-base-100 dark:bg-base-800 border-base-300 dark:border-base-700 text-base-900 dark:text-base-100 hover:border-accent-400' 51 + ]} 52 + onclick={() => (item.cardData.shape = opt.value)} 53 + > 54 + {opt.label} 55 + </button> 56 + {/each} 57 + </div> 58 </div> 59 </div>