wip bsky client for the web & android
0
fork

Configure Feed

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

feat: prettier splash screen

vi 3e055252 9ea4d2de

+164 -77
+90
src/components/Layout/ScreenLayout.vue
··· 1 + <script setup lang="ts"> 2 + const props = defineProps<{ 3 + bgImage?: string | null 4 + hasImage?: boolean 5 + contentPosition?: 'center' | 'bottom' 6 + maxWidth?: string 7 + }>() 8 + 9 + const bgStyle = props.bgImage ? { backgroundImage: `url(${props.bgImage})` } : undefined 10 + </script> 11 + 12 + <template> 13 + <div class="screen-layout" :data-position="contentPosition"> 14 + <div 15 + class="bg-layer" 16 + :class="{ 'has-image': !!bgImage || hasImage }" 17 + :style="bgStyle" 18 + aria-hidden="true" 19 + /> 20 + <div class="bg-overlay" aria-hidden="true" /> 21 + <div class="content-layer" :style="{ maxWidth: maxWidth || '640px' }"> 22 + <slot /> 23 + </div> 24 + </div> 25 + </template> 26 + 27 + <style scoped lang="scss"> 28 + .screen-layout { 29 + position: fixed; 30 + inset: 0; 31 + z-index: 9999; 32 + display: flex; 33 + flex-direction: column; 34 + align-items: center; 35 + justify-content: center; 36 + background-color: hsl(var(--base)); 37 + color: hsl(var(--text)); 38 + overflow: hidden; 39 + perspective: 1000px; 40 + } 41 + 42 + .bg-layer { 43 + position: absolute; 44 + inset: -10%; 45 + z-index: -2; 46 + will-change: transform, filter, opacity; 47 + opacity: 1; 48 + transition: 49 + transform 0.9s cubic-bezier(0.2, 0.8, 0.2, 1), 50 + filter 0.9s ease, 51 + opacity 0.9s ease; 52 + filter: blur(16px) saturate(1.05); 53 + transform: scale(1.05); 54 + background-size: cover; 55 + background-position: center; 56 + background-repeat: no-repeat; 57 + } 58 + 59 + .bg-overlay { 60 + position: absolute; 61 + inset: 0; 62 + z-index: -1; 63 + background: linear-gradient( 64 + to bottom, 65 + hsla(var(--base) / 0.12) 0%, 66 + hsla(var(--base) / 0.7) 60%, 67 + hsla(var(--base) / 0.95) 100% 68 + ); 69 + } 70 + 71 + .content-layer { 72 + position: relative; 73 + z-index: 10; 74 + width: 100%; 75 + padding: 2rem; 76 + margin: 0 auto; 77 + display: flex; 78 + flex-direction: column; 79 + align-items: center; 80 + text-align: center; 81 + will-change: transform, opacity, filter; 82 + transition: all 0.6s cubic-bezier(0.6, 0, 0.4, 1); 83 + justify-content: center; 84 + } 85 + 86 + .screen-layout[data-position='bottom'] .content-layer { 87 + justify-content: flex-end; 88 + padding-bottom: calc(2rem + env(safe-area-inset-bottom)); 89 + } 90 + </style>
+74 -77
src/components/Layout/SplashScreen.vue
··· 1 1 <script setup lang="ts"> 2 + import ScreenLayout from '@/components/Layout/ScreenLayout.vue' 2 3 import SVG from '@/components/UI/SVG.vue' 3 4 import BluebellLogo from '@/assets/icons/bluebell.svg?raw' 4 5 </script> 5 6 6 7 <template> 7 - <div class="splash-screen"> 8 + <ScreenLayout bgImage="/images/bluebell.webp" :contentPosition="'center'" maxWidth="640px"> 8 9 <div class="splash-content"> 9 - <div class="logo-wrapper"> 10 - <SVG :icon="BluebellLogo" class="logo" /> 10 + <div class="app-hero"> 11 + <div class="logo-wrapper" aria-hidden="true"> 12 + <SVG :icon="BluebellLogo" class="logo" /> 13 + </div> 14 + <h1 class="title">Bluebell</h1> 11 15 </div> 12 16 13 - <div class="brand-text"> 14 - <h1 class="title">Bluebell</h1> 15 - <div class="loader-track"> 16 - <div class="loader-bar"></div> 17 - </div> 17 + <div class="loader-track" aria-hidden="true"> 18 + <div class="loader-bar"></div> 18 19 </div> 19 20 </div> 20 - </div> 21 + </ScreenLayout> 21 22 </template> 22 23 23 24 <style scoped lang="scss"> 24 - .splash-screen { 25 + .splash-content { 26 + position: relative; 27 + z-index: 10; 25 28 display: flex; 26 29 flex-direction: column; 27 30 align-items: center; 28 - justify-content: center; 29 - height: 100vh; 30 - width: 100vw; 31 - background-color: hsl(var(--base)); 32 - color: hsl(var(--text)); 33 - position: fixed; 34 - inset: 0; 35 - z-index: 9999; 31 + gap: 1.5rem; 32 + padding: 2rem; 33 + width: 100%; 34 + max-width: 640px; 35 + margin: 0 auto; 36 + text-align: center; 37 + transform-origin: center; 38 + animation: contentIn 0.7s cubic-bezier(0.2, 0.9, 0.2, 1) both; 36 39 } 37 40 38 - .splash-content { 41 + .app-hero { 39 42 display: flex; 40 43 flex-direction: column; 41 44 align-items: center; 42 - gap: 2rem; 43 - margin-bottom: 10vh; 44 - } 45 + gap: 0.75rem; 45 46 46 - .logo-wrapper { 47 - width: 6rem; 48 - height: 6rem; 49 - color: hsl(var(--accent)); 47 + .logo-wrapper { 48 + width: 6rem; 49 + height: 6rem; 50 + color: hsl(var(--accent)); 50 51 51 - :deep(svg) { 52 - width: 100%; 53 - height: 100%; 52 + :deep(svg) { 53 + width: 100%; 54 + height: 100%; 55 + } 54 56 } 55 - } 56 57 57 - .brand-text { 58 - display: flex; 59 - flex-direction: column; 60 - align-items: center; 61 - gap: 1rem; 62 - } 63 - 64 - .title { 65 - font-size: 2.5rem; 66 - font-weight: 800; 67 - letter-spacing: -0.03em; 68 - background: linear-gradient(135deg, hsl(var(--text)) 0%, hsl(var(--subtext0)) 100%); 69 - background-clip: text; 70 - -webkit-text-fill-color: transparent; 71 - margin: 0; 58 + .title { 59 + font-size: 2.25rem; 60 + font-weight: 800; 61 + letter-spacing: -0.02em; 62 + margin: 0; 63 + background: linear-gradient(135deg, hsl(var(--text)) 0%, hsl(var(--subtext0)) 100%); 64 + -webkit-background-clip: text; 65 + background-clip: text; 66 + -webkit-text-fill-color: transparent; 67 + } 72 68 } 73 69 74 70 .loader-track { ··· 78 74 border-radius: 99px; 79 75 overflow: hidden; 80 76 position: relative; 81 - } 82 77 83 - .loader-bar { 84 - position: absolute; 85 - top: 0; 86 - left: 0; 87 - height: 100%; 88 - width: 100%; 89 - background-color: hsl(var(--accent)); 90 - border-radius: 99px; 91 - transform-origin: left; 92 - animation: loading 1.5s cubic-bezier(0.65, 0, 0.35, 1) infinite; 93 - } 94 - 95 - .footer { 96 - position: absolute; 97 - bottom: 2rem; 98 - p { 99 - font-size: 0.875rem; 100 - color: hsl(var(--subtext0)); 101 - opacity: 0.6; 102 - font-weight: 500; 103 - letter-spacing: 0.02em; 104 - } 105 - } 106 - 107 - @keyframes float { 108 - 0%, 109 - 100% { 110 - transform: translateY(0); 111 - } 112 - 50% { 113 - transform: translateY(-10px); 78 + .loader-bar { 79 + position: absolute; 80 + top: 0; 81 + left: 0; 82 + height: 100%; 83 + width: 100%; 84 + background-color: hsl(var(--accent)); 85 + border-radius: 99px; 86 + transform-origin: left; 87 + animation: loading 1.5s cubic-bezier(0.65, 0, 0.35, 1) infinite; 114 88 } 115 89 } 116 90 ··· 123 97 } 124 98 100% { 125 99 transform: translateX(100%); 100 + } 101 + } 102 + 103 + @keyframes contentIn { 104 + from { 105 + opacity: 0; 106 + transform: translateY(12px) scale(0.995); 107 + filter: blur(6px); 108 + } 109 + to { 110 + opacity: 1; 111 + transform: translateY(0) scale(1); 112 + filter: blur(0); 113 + } 114 + } 115 + 116 + @media (min-width: 512px) { 117 + .title { 118 + font-size: 3rem; 119 + } 120 + .loader-track { 121 + width: 220px; 122 + height: 8px; 126 123 } 127 124 } 128 125 </style>