my website at ewancroft.uk
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix(style): prevent horizontal overflow on mobile viewports

- Add overflow-x: hidden to html/body elements
- Set max-width: 100vw and min-width: 0 globally to constrain content
- Apply overflow-wrap-anywhere and break-words to all user-generated text
- Add max-w-full to all media elements (images, videos)
- Fix header layout with proper flex shrinking
- Update all card components to handle long content gracefully
- Add flex-wrap to metadata containers for better mobile wrapping

Affects:
- src/app.css: Global overflow prevention rules
- src/routes/+layout.svelte: Main wrapper overflow control
- src/lib/components/layout/Header.svelte: Header flex improvements
- src/lib/components/layout/main/card/*.svelte: Text and media constraints
- src/routes/+page.svelte: Page title/description word breaking

Closes horizontal scroll issues on mobile devices

+49 -34
+15
src/app.css
··· 74 74 /* Base styles for consistent typography and accessibility */ 75 75 html { 76 76 scroll-behavior: smooth; 77 + overflow-x: hidden; 78 + width: 100%; 77 79 } 78 80 79 81 body { ··· 81 83 text-rendering: optimizeLegibility; 82 84 -webkit-font-smoothing: antialiased; 83 85 -moz-osx-font-smoothing: grayscale; 86 + overflow-x: hidden; 87 + width: 100%; 88 + max-width: 100vw; 84 89 } 85 90 86 91 /* Focus visible styles for accessibility */ 87 92 *:focus-visible { 88 93 outline: 2px solid var(--color-sage-600); 89 94 outline-offset: 2px; 95 + } 96 + 97 + /* Ensure all elements stay within viewport */ 98 + * { 99 + min-width: 0; 100 + } 101 + 102 + img, video, iframe, embed, object { 103 + max-width: 100%; 104 + height: auto; 90 105 } 91 106 } 92 107
+3 -3
src/lib/components/layout/Header.svelte
··· 8 8 const siteMeta: SiteMetadata = createSiteMeta(defaultSiteMeta); 9 9 </script> 10 10 11 - <header class="sticky top-0 z-50 border-b border-canvas-200 bg-canvas-50/90 backdrop-blur-md dark:border-canvas-800 dark:bg-canvas-950/90"> 11 + <header class="sticky top-0 z-50 w-full border-b border-canvas-200 bg-canvas-50/90 backdrop-blur-md dark:border-canvas-800 dark:bg-canvas-950/90"> 12 12 <nav class="container mx-auto flex items-center justify-between px-4 py-4" aria-label="Main navigation"> 13 - <a href="/" class="group flex items-center gap-2"> 13 + <a href="/" class="group flex min-w-0 items-center gap-2"> 14 14 <span class="max-w-[200px] truncate text-xl font-bold text-ink-900 dark:text-ink-50" aria-label="{siteMeta.title} - Home"> 15 15 {siteMeta.title} 16 16 </span> 17 17 </a> 18 18 19 - <div class="flex items-center gap-4"> 19 + <div class="flex flex-shrink-0 items-center gap-4"> 20 20 <NavLinks {navItems} /> 21 21 <ThemeToggle /> 22 22 </div>
+11 -11
src/lib/components/layout/main/card/BlueskyPostCard.svelte
··· 159 159 160 160 <!-- Post Text with Rich Text Support --> 161 161 <div 162 - class="mb-{isQuoted ? '3' : '4'} whitespace-pre-wrap text-{isQuoted 162 + class="mb-{isQuoted ? '3' : '4'} overflow-wrap-anywhere break-words whitespace-pre-wrap text-{isQuoted 163 163 ? 'base' 164 164 : 'lg'} leading-relaxed text-ink-900 dark:text-ink-50" 165 165 > ··· 168 168 169 169 <!-- Video --> 170 170 {#if postData.hasVideo && postData.videoUrl} 171 - <div class="mb-{isQuoted ? '3' : '4'} overflow-hidden rounded-lg"> 171 + <div class="mb-{isQuoted ? '3' : '4'} max-w-full overflow-hidden rounded-lg"> 172 172 <video 173 173 src={postData.videoUrl} 174 174 controls 175 - class="w-full" 175 + class="w-full max-w-full" 176 176 preload="metadata" 177 177 poster={postData.videoThumbnail} 178 178 > ··· 184 184 <!-- Images --> 185 185 {#if postData.hasImages && postData.imageUrls && postData.imageUrls.length > 0} 186 186 <div 187 - class="mb-{isQuoted ? '3' : '4'} grid gap-2 {postData.imageUrls.length === 1 187 + class="mb-{isQuoted ? '3' : '4'} grid max-w-full gap-2 {postData.imageUrls.length === 1 188 188 ? 'grid-cols-1' 189 189 : postData.imageUrls.length === 2 190 190 ? 'grid-cols-2' ··· 197 197 type="button" 198 198 onclick={() => 199 199 openLightbox(imageUrl, postData.imageAlts?.[index] || `Post attachment ${index + 1}`)} 200 - class="h-auto w-full overflow-hidden rounded-lg transition-opacity hover:opacity-90 focus:ring-2 focus:ring-sage-500 focus:outline-none dark:focus:ring-sage-400" 200 + class="h-auto w-full max-w-full overflow-hidden rounded-lg transition-opacity hover:opacity-90 focus:ring-2 focus:ring-sage-500 focus:outline-none dark:focus:ring-sage-400" 201 201 > 202 202 <img 203 203 src={imageUrl} 204 204 alt={postData.imageAlts?.[index] || `Post attachment ${index + 1}`} 205 - class="h-auto w-full object-cover {postData.imageUrls.length === 4 205 + class="h-auto w-full max-w-full object-cover {postData.imageUrls.length === 4 206 206 ? 'aspect-square' 207 207 : postData.imageUrls.length > 1 208 208 ? 'aspect-video' ··· 224 224 rel="noopener noreferrer" 225 225 class="mb-{isQuoted 226 226 ? '3' 227 - : '4'} flex flex-col gap-2 overflow-hidden rounded-lg border border-canvas-300 bg-canvas-{isQuoted 227 + : '4'} flex max-w-full flex-col gap-2 overflow-hidden rounded-lg border border-canvas-300 bg-canvas-{isQuoted 228 228 ? '300' 229 229 : '200'} transition-colors hover:bg-canvas-{isQuoted 230 230 ? '400' ··· 236 236 <img 237 237 src={postData.externalLink.thumb} 238 238 alt={postData.externalLink.title} 239 - class="h-{isQuoted ? '32' : '48'} w-full object-cover" 239 + class="h-{isQuoted ? '32' : '48'} w-full max-w-full object-cover" 240 240 loading="lazy" 241 241 /> 242 242 {/if} 243 243 <div class="p-{isQuoted ? '3' : '4'}"> 244 244 <h3 245 - class="mb-1 text-{isQuoted 245 + class="mb-1 overflow-wrap-anywhere break-words text-{isQuoted 246 246 ? 'sm' 247 247 : 'base'} line-clamp-2 font-semibold text-ink-900 dark:text-ink-50" 248 248 > ··· 250 250 </h3> 251 251 {#if postData.externalLink.description} 252 252 <p 253 - class="mb-2 text-{isQuoted ? 'xs' : 'sm'} line-clamp-2 text-ink-700 dark:text-ink-200" 253 + class="mb-2 overflow-wrap-anywhere break-words text-{isQuoted ? 'xs' : 'sm'} line-clamp-2 text-ink-700 dark:text-ink-200" 254 254 > 255 255 {postData.externalLink.description} 256 256 </p> 257 257 {/if} 258 - <p class="text-xs text-ink-600 dark:text-ink-300"> 258 + <p class="overflow-wrap-anywhere break-words text-xs text-ink-600 dark:text-ink-300"> 259 259 {new URL(postData.externalLink.uri).hostname} 260 260 </p> 261 261 </div>
+2 -2
src/lib/components/layout/main/card/LinkCard.svelte
··· 71 71 {/if} 72 72 73 73 <!-- 👇 Title is always below the badges --> 74 - <h3 class="font-semibold text-ink-900 dark:text-ink-50">{title}</h3> 74 + <h3 class="overflow-wrap-anywhere break-words font-semibold text-ink-900 dark:text-ink-50">{title}</h3> 75 75 76 76 {#if displayDescription} 77 - <p class="text-sm text-ink-700 line-clamp-2 dark:text-ink-200"> 77 + <p class="overflow-wrap-anywhere break-words text-sm text-ink-700 line-clamp-2 dark:text-ink-200"> 78 78 {displayDescription} 79 79 </p> 80 80 {/if}
+4 -4
src/lib/components/layout/main/card/PostCard.svelte
··· 103 103 {/if} 104 104 105 105 <!-- Title --> 106 - <h3 class="font-semibold text-ink-900 dark:text-ink-50">{post.title}</h3> 106 + <h3 class="overflow-wrap-anywhere break-words font-semibold text-ink-900 dark:text-ink-50">{post.title}</h3> 107 107 108 108 <!-- Description --> 109 109 {#if post.description} 110 - <p class="line-clamp-2 text-sm text-ink-700 dark:text-ink-200"> 111 - {post.description} 112 - </p> 110 + <p class="overflow-wrap-anywhere break-words line-clamp-2 text-sm text-ink-700 dark:text-ink-200"> 111 + {post.description} 112 + </p> 113 113 {/if} 114 114 115 115 <!-- Timestamp -->
+3 -3
src/lib/components/layout/main/card/ProfileCard.svelte
··· 108 108 <p class="font-medium text-ink-700 dark:text-ink-200">@{safeProfile.handle}</p> 109 109 110 110 {#if safeProfile.description} 111 - <p class="mb-4 break-words whitespace-pre-wrap text-ink-700 dark:text-ink-200"> 112 - {safeProfile.description} 113 - </p> 111 + <p class="mb-4 overflow-wrap-anywhere break-words whitespace-pre-wrap text-ink-700 dark:text-ink-200"> 112 + {safeProfile.description} 113 + </p> 114 114 {/if} 115 115 116 116 <div class="flex gap-6 text-sm font-medium">
+1 -1
src/lib/components/layout/main/card/StatusCard.svelte
··· 47 47 </span> 48 48 </div> 49 49 50 - <p class="mb-2 text-lg font-medium text-ink-900 dark:text-ink-50"> 50 + <p class="mb-2 overflow-wrap-anywhere break-words text-lg font-medium text-ink-900 dark:text-ink-50"> 51 51 {safeStatus.text} 52 52 </p> 53 53
+7 -7
src/lib/components/layout/main/card/TangledRepoCard.svelte
··· 40 40 class="h-5 w-5 flex-shrink-0 text-sage-600 dark:text-sage-400" 41 41 aria-hidden="true" 42 42 /> 43 - <div class="flex flex-col gap-1 min-w-0"> 44 - <h3 class="font-semibold text-ink-900 truncate dark:text-ink-50"> 43 + <div class="flex flex-col gap-1 min-w-0 flex-1"> 44 + <h3 class="overflow-wrap-anywhere break-words font-semibold text-ink-900 dark:text-ink-50"> 45 45 {repo.name} 46 46 </h3> 47 - <div class="flex items-center gap-3 text-xs text-ink-700 dark:text-ink-200"> 48 - <div class="flex items-center gap-1"> 49 - <Server class="h-3 w-3" aria-hidden="true" /> 47 + <div class="flex flex-wrap items-center gap-3 text-xs text-ink-700 dark:text-ink-200"> 48 + <div class="flex items-center gap-1 min-w-0"> 49 + <Server class="h-3 w-3 flex-shrink-0" aria-hidden="true" /> 50 50 <span class="truncate">{getKnotServerName(repo.knot)}</span> 51 51 </div> 52 - <div class="flex items-center gap-1"> 53 - <User class="h-3 w-3" aria-hidden="true" /> 52 + <div class="flex items-center gap-1 min-w-0"> 53 + <User class="h-3 w-3 flex-shrink-0" aria-hidden="true" /> 54 54 <span class="truncate">{handle || PUBLIC_ATPROTO_DID}</span> 55 55 </div> 56 56 </div>
+1 -1
src/routes/+layout.svelte
··· 51 51 <!-- Bespoke MetaTags component --> 52 52 <MetaTags meta={headMeta} siteMeta={data.siteMeta} /> 53 53 54 - <div class="flex min-h-screen flex-col bg-canvas-50 text-ink-900 dark:bg-canvas-950 dark:text-ink-50"> 54 + <div class="flex min-h-screen flex-col overflow-x-hidden bg-canvas-50 text-ink-900 dark:bg-canvas-950 dark:text-ink-50"> 55 55 <Header /> 56 56 57 57 <main class="container mx-auto flex-grow px-4 py-8">
+2 -2
src/routes/+page.svelte
··· 18 18 19 19 <div class="mx-auto max-w-6xl"> 20 20 <div class="mb-8 text-center"> 21 - <h1 class="mb-4 text-4xl font-bold text-ink-900 md:text-5xl dark:text-ink-50"> 21 + <h1 class="mb-4 overflow-wrap-anywhere break-words text-4xl font-bold text-ink-900 md:text-5xl dark:text-ink-50"> 22 22 Welcome to {meta.title} 23 23 </h1> 24 - <p class="mx-auto max-w-2xl text-lg text-ink-700 dark:text-ink-200"> 24 + <p class="mx-auto max-w-2xl overflow-wrap-anywhere break-words text-lg text-ink-700 dark:text-ink-200"> 25 25 {meta.description} 26 26 </p> 27 27 </div>