JavaScript-optional public web frontend for Bluesky anartia.kelinci.net
sveltekit atcute bluesky typescript svelte
at trunk 206 lines 3.9 kB view raw
1<script lang="ts" module> 2 const DEFAULT_RATIO = { width: 16, height: 9 }; 3</script> 4 5<script lang="ts"> 6 import type { AppBskyEmbedImages } from '@atcute/bluesky'; 7 8 import { trimRichText } from '$lib/utils/bluesky/richtext'; 9 10 import ImageAlt from './components/image-alt.svelte'; 11 12 interface Props { 13 embed: AppBskyEmbedImages.View; 14 borderless?: boolean; 15 standalone?: boolean; 16 blur?: boolean; 17 } 18 19 const { embed, borderless, standalone, blur }: Props = $props(); 20 21 const images = $derived(embed.images); 22 const length = $derived(images.length); 23</script> 24 25<div class={['image-embed', !borderless && 'is-bordered', standalone && length === 1 && 'is-aligned']}> 26 {#if length === 4} 27 <div class="grid"> 28 <div class="col"> 29 <div class="item wide tl"> 30 {@render Image(0)} 31 </div> 32 <div class="item wide bl"> 33 {@render Image(2)} 34 </div> 35 </div> 36 <div class="col"> 37 <div class="item wide tr"> 38 {@render Image(1)} 39 </div> 40 <div class="item wide br"> 41 {@render Image(3)} 42 </div> 43 </div> 44 </div> 45 {:else if length === 3} 46 <div class="grid"> 47 <div class="col square"> 48 <div class="item tl bl"> 49 {@render Image(0)} 50 </div> 51 </div> 52 <div class="col square"> 53 <div class="item tr"> 54 {@render Image(1)} 55 </div> 56 <div class="item br"> 57 {@render Image(2)} 58 </div> 59 </div> 60 </div> 61 {:else if length === 2} 62 <div class="grid"> 63 <div class="col"> 64 <div class="item square tl bl"> 65 {@render Image(0)} 66 </div> 67 </div> 68 <div class="col"> 69 <div class="item square tr br"> 70 {@render Image(1)} 71 </div> 72 </div> 73 </div> 74 {:else if length === 1} 75 {@const ratio = standalone && (images[0].aspectRatio || DEFAULT_RATIO)} 76 77 <div 78 class={['single-item tl tr bl br', ratio && 'is-standalone', ratio === DEFAULT_RATIO && 'is-defaulted']} 79 style={ratio ? `aspect-ratio: ${ratio.width}/${ratio.height}` : ``} 80 > 81 {@render Image(0)} 82 83 {#if ratio} 84 <div class="placeholder"></div> 85 {/if} 86 </div> 87 {/if} 88</div> 89 90{#snippet Image(index: number)} 91 {@const image = images[index]} 92 {@const alt = trimRichText(image.alt)} 93 94 {#if standalone} 95 <a href={image.fullsize.replace('@jpeg', '@png')} target="_blank" rel="noopener" class="image-wrapper"> 96 <img loading="lazy" src={image.thumb} {alt} class={`image` + (blur ? ` is-blurred` : ``)} /> 97 </a> 98 {:else} 99 <div class="image-wrapper"> 100 <img loading="lazy" src={image.thumb} {alt} class={`image` + (blur ? ` is-blurred` : ``)} /> 101 </div> 102 {/if} 103 104 {#if standalone && alt} 105 <ImageAlt {alt} /> 106 {/if} 107{/snippet} 108 109<style> 110 .is-aligned { 111 align-self: baseline; 112 max-width: 100%; 113 } 114 115 .grid { 116 display: flex; 117 gap: 2px; 118 } 119 .col { 120 display: flex; 121 flex: 1; 122 flex-direction: column; 123 gap: 2px; 124 } 125 126 .square { 127 aspect-ratio: 1; 128 } 129 .wide { 130 aspect-ratio: 1.5; 131 } 132 133 .item { 134 position: relative; 135 flex-grow: 1; 136 flex-shrink: 0; 137 overflow: hidden; 138 } 139 140 .is-bordered { 141 .tl, 142 .tr, 143 .bl, 144 .br { 145 border: 1px solid var(--divider-md); 146 } 147 148 .tl { 149 border-top-left-radius: 6px; 150 } 151 .tr { 152 border-top-right-radius: 6px; 153 } 154 .bl { 155 border-bottom-left-radius: 6px; 156 } 157 .br { 158 border-bottom-right-radius: 6px; 159 } 160 } 161 162 .single-item { 163 position: relative; 164 aspect-ratio: 16 / 9; 165 overflow: hidden; 166 } 167 .is-standalone { 168 min-width: 64px; 169 max-width: 100%; 170 min-height: 64px; 171 max-height: 320px; 172 } 173 174 .item, 175 .single-item { 176 &:has(.image-wrapper:focus-visible) { 177 outline: 2px solid var(--accent); 178 outline-offset: -1px; 179 } 180 } 181 182 .image-wrapper { 183 position: absolute; 184 inset: 0; 185 outline: none; 186 } 187 .image { 188 background: var(--bg-slate); 189 width: 100%; 190 height: 100%; 191 object-fit: cover; 192 font-size: 0px; 193 } 194 .single-item .image { 195 object-fit: contain; 196 } 197 .is-blurred { 198 scale: 125%; 199 filter: blur(24px); 200 } 201 202 .placeholder { 203 width: 100vw; 204 height: 100vh; 205 } 206</style>