your personal website on atproto - mirror blento.app
at fix-500-on-first-login 177 lines 5.1 kB view raw
1<script lang="ts"> 2 import { browser } from '$app/environment'; 3 import { page } from '$app/state'; 4 import { innerWidth } from 'svelte/reactivity/window'; 5 import BaseCard from '$lib/cards/_base/BaseCard/BaseCard.svelte'; 6 import Card from '$lib/cards/_base/Card/Card.svelte'; 7 import { CardDefinitionsByType, getColor } from '$lib/cards'; 8 import { getDescription, getImage, getName } from '$lib/helper'; 9 import QRModalProvider from '$lib/components/qr/QRModalProvider.svelte'; 10 import ImageViewerProvider from '$lib/components/image-viewer/ImageViewerProvider.svelte'; 11 import type { WebsiteData } from '$lib/types'; 12 import Context from './Context.svelte'; 13 import Head from './Head.svelte'; 14 import { setIsMobile } from './context'; 15 16 let { data }: { data: WebsiteData } = $props(); 17 18 let item = $derived(data.cards[0]); 19 let embeddedItem = $derived({ 20 ...item, 21 x: 0, 22 y: 0, 23 mobileX: 0, 24 mobileY: 0 25 }); 26 27 let isMobile = $derived((innerWidth.current ?? 1000) < 1024); 28 setIsMobile(() => isMobile); 29 30 const colors = { 31 base: 'bg-base-200/50 dark:bg-base-950/50', 32 accent: 'bg-accent-400 dark:bg-accent-500 accent', 33 transparent: 'bg-transparent' 34 } as Record<string, string>; 35 36 let color = $derived(getColor(item)); 37 let backgroundClass = $derived(color ? (colors[color] ?? colors.accent) : colors.base); 38 let pageColorClass = $derived( 39 color !== 'accent' && item?.color !== 'base' && item?.color !== 'transparent' ? color : '' 40 ); 41 let cardWidth = $derived(Math.max(isMobile ? item.mobileW : item.w, 1)); 42 let cardHeight = $derived(Math.max(isMobile ? item.mobileH : item.h, 1)); 43 44 let title = $derived.by(() => { 45 const label = item?.cardData?.label as string | undefined; 46 const cardName = CardDefinitionsByType[item?.cardType ?? '']?.name; 47 48 return label 49 ? `${label}${getName(data)}` 50 : cardName 51 ? `${cardName}${getName(data)}` 52 : getName(data); 53 }); 54 55 let description = $derived( 56 (item?.cardData?.title as string | undefined) || 57 (item?.cardData?.text as string | undefined) || 58 getDescription(data) 59 ); 60 61 const safeJson = (value: string) => JSON.stringify(value).replace(/</g, '\\u003c'); 62 63 let themeMode = $derived.by(() => { 64 const theme = page.url.searchParams.get('theme'); 65 return theme === 'dark' || theme === 'light' || theme === 'auto' ? theme : undefined; 66 }); 67 68 let themeScript = $derived.by(() => { 69 if (!themeMode) return ''; 70 71 return ( 72 `<script>(function(){var theme=${safeJson(themeMode)};var el=document.documentElement;` + 73 `var apply=function(mode){el.classList.remove('dark','light');` + 74 `el.classList.add(mode==='auto'&&window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':mode==='auto'?'light':mode);};` + 75 `apply(theme);})();<` + 76 '/script>' 77 ); 78 }); 79 80 $effect(() => { 81 if (!browser || !themeMode) return; 82 83 const root = document.documentElement; 84 const previousHadDark = root.classList.contains('dark'); 85 const previousHadLight = root.classList.contains('light'); 86 87 const applyTheme = () => { 88 root.classList.remove('dark', 'light'); 89 90 if (themeMode === 'auto') { 91 root.classList.add( 92 window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' 93 ); 94 return; 95 } 96 97 root.classList.add(themeMode); 98 }; 99 100 applyTheme(); 101 102 if (themeMode !== 'auto') { 103 return () => { 104 root.classList.remove('dark', 'light'); 105 if (previousHadDark) root.classList.add('dark'); 106 if (previousHadLight) root.classList.add('light'); 107 }; 108 } 109 110 const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); 111 mediaQuery.addEventListener('change', applyTheme); 112 113 return () => { 114 mediaQuery.removeEventListener('change', applyTheme); 115 root.classList.remove('dark', 'light'); 116 if (previousHadDark) root.classList.add('dark'); 117 if (previousHadLight) root.classList.add('light'); 118 }; 119 }); 120</script> 121 122<Head 123 favicon={getImage(data.publication, data.did, 'icon') || data.profile.avatar} 124 {title} 125 {description} 126 accentColor={data.publication?.preferences?.accentColor} 127 baseColor={data.publication?.preferences?.baseColor} 128/> 129 130<svelte:head> 131 <meta name="robots" content="noindex" /> 132 {@html themeScript} 133</svelte:head> 134 135<Context {data}> 136 <QRModalProvider /> 137 <ImageViewerProvider /> 138 139 <div class={[backgroundClass, pageColorClass, 'embed-page w-full']}> 140 <div class="embed-stage @container/grid"> 141 <div 142 class="embed-content" 143 style={`--embed-ratio: ${cardWidth / cardHeight}; aspect-ratio: ${cardWidth} / ${cardHeight};`} 144 > 145 <BaseCard item={embeddedItem} fillPage> 146 <Card item={embeddedItem} /> 147 </BaseCard> 148 </div> 149 </div> 150 </div> 151</Context> 152 153<style> 154 :global(html), 155 :global(body) { 156 min-height: 100%; 157 } 158 159 .embed-page { 160 min-height: 100vh; 161 } 162 163 .embed-stage { 164 min-height: 100vh; 165 display: flex; 166 align-items: center; 167 justify-content: center; 168 padding: clamp(12px, 3vw, 32px); 169 container-type: inline-size; 170 } 171 172 .embed-content { 173 width: min(100%, calc((100vh - clamp(24px, 6vw, 64px)) * var(--embed-ratio))); 174 max-width: calc(100vw - clamp(24px, 6vw, 64px)); 175 max-height: calc(100vh - clamp(24px, 6vw, 64px)); 176 } 177</style>