wip bsky client for the web & android
0
fork

Configure Feed

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

feat: add progressive blur; make nav bar slightly transparent

vi 6d990b58 31be7e74

+109 -17
+4 -4
src/assets/main.css
··· 49 49 } 50 50 51 51 :root { 52 - --inset-top: var(--safe-area-inset-top, env(safe-area-inset-top, 0px)); 53 - --inset-bottom: var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px)); 54 - --inset-left: var(--safe-area-inset-left, env(safe-area-inset-left, 0px)); 55 - --inset-right: var(--safe-area-inset-right, env(safe-area-inset-right, 0px)); 52 + --inset-top: var(--safe-area-inset-top, env(safe-area-inset-top)); 53 + --inset-bottom: var(--safe-area-inset-bottom, env(safe-area-inset-bottom)); 54 + --inset-left: var(--safe-area-inset-left, env(safe-area-inset-left)); 55 + --inset-right: var(--safe-area-inset-right, env(safe-area-inset-right)); 56 56 57 57 --space-1: 0.25rem; 58 58 --space-2: 0.5rem;
+102 -11
src/components/Navigation/AppBar.vue
··· 1 1 <script setup lang="ts"> 2 - import { computed, ref, onMounted } from 'vue' 2 + import { computed, ref, onMounted, type CSSProperties } from 'vue' 3 3 import { useNavigationStore, type StackEntry, type TabKey } from '@/stores/navigation' 4 4 import BackButton from './BackButton.vue' 5 5 ··· 50 50 const topEntry = stack.value?.[stack.value.length - 1] 51 51 return topEntry?.title ?? 'Page' 52 52 }) 53 + 54 + const LAYER_MASKS: readonly { 55 + start: number 56 + end: number 57 + }[] = [ 58 + { start: 95, end: 100 }, 59 + { start: 90, end: 95 }, 60 + { start: 80, end: 100 }, 61 + { start: 60, end: 80 }, 62 + { start: 40, end: 60 }, 63 + { start: 20, end: 40 }, 64 + { start: 10, end: 20 }, 65 + ] as const 66 + 67 + const createLayers = (direction: 'to-bottom' | 'to-top') => { 68 + return Array.from({ length: 7 }, (_, i) => { 69 + const divisor = Math.pow(2, 6 - i) 70 + const zIndex = 7 - i 71 + 72 + const mask = LAYER_MASKS[i] 73 + if (!mask?.start || !mask?.end) return null 74 + const { start, end } = mask 75 + 76 + const gradientDir = direction === 'to-bottom' ? 'to bottom' : 'to top' 77 + const maskString = `linear-gradient(${gradientDir}, rgba(0, 0, 0, 1) ${start}%, rgba(0, 0, 0, 0) ${end}%)` 78 + 79 + return { 80 + style: { 81 + zIndex, 82 + backdropFilter: `blur(calc(var(--blur-strength) / ${divisor}))`, 83 + mask: maskString, 84 + WebkitMask: maskString, 85 + } as CSSProperties, 86 + } 87 + }) 88 + } 89 + 90 + const blurLayers = computed(() => createLayers('to-bottom')) 91 + const blurLayersBottom = computed(() => createLayers('to-top')) 53 92 </script> 54 93 55 94 <template> 56 - <div 57 - class="overlay" 58 - ref="overlayElement" 59 - aria-hidden="true" 60 - :style="{ 61 - height: `calc(var(--inset-top) + ${appBarElement?.offsetHeight || 0}px)`, 62 - }" 63 - ></div> 95 + <div class="progressive-blur progressive-blur-top"> 96 + <div v-for="(layer, index) in blurLayers" :key="`top-${index}`" :style="layer?.style"></div> 97 + </div> 98 + <div class="progressive-blur progressive-blur-bottom"> 99 + <div 100 + v-for="(layer, index) in blurLayersBottom" 101 + :key="`bottom-${index}`" 102 + :style="layer?.style" 103 + /> 104 + </div> 105 + 106 + <div class="overlay overlay-top" ref="overlayElement" aria-hidden="true"></div> 107 + <div class="overlay overlay-bottom" aria-hidden="true"></div> 108 + 64 109 <header 65 110 ref="appBarElement" 66 111 class="topbar" ··· 86 131 </template> 87 132 88 133 <style scoped lang="scss"> 89 - .overlay { 134 + .progressive-blur { 135 + --blur-strength: 16px; 136 + height: calc(var(--inset-top) + 3.5rem); 137 + min-height: 3.5rem; 138 + 90 139 position: absolute; 140 + pointer-events: none; 141 + left: 0; 142 + right: 0; 143 + 144 + > div { 145 + position: absolute; 146 + left: 0; 147 + right: 0; 148 + bottom: 0; 149 + top: 0; 150 + } 151 + } 152 + 153 + .progressive-blur-top { 91 154 top: 0; 155 + bottom: auto; 156 + z-index: 5; 157 + } 158 + 159 + .progressive-blur-bottom { 160 + bottom: 0; 161 + top: auto; 162 + height: calc(var(--inset-bottom, var(--inset-top)) + 3.5rem); 163 + min-height: 4rem; 164 + z-index: 5; 165 + } 166 + 167 + .overlay { 168 + position: absolute; 92 169 left: 0; 93 170 width: 100%; 94 - background: linear-gradient(to bottom, hsla(var(--base) / 1) 0%, hsla(var(--base) / 0) 100%); 95 171 pointer-events: none; 96 172 z-index: 99; 173 + } 174 + 175 + .overlay-top { 176 + top: 0; 177 + height: calc(var(--inset-top) + 3.5rem); 178 + min-height: 4.5rem; 179 + background: linear-gradient(to bottom, hsla(var(--base) / 0.8) 0%, hsla(var(--base) / 0) 100%); 180 + } 181 + 182 + .overlay-bottom { 183 + bottom: 0; 184 + top: auto; 185 + height: var(--inset-bottom); 186 + min-height: 4.5rem; 187 + background: linear-gradient(to top, hsla(var(--base) / 0.8) 0%, hsla(var(--base) / 0) 90%); 97 188 } 98 189 99 190 .topbar {
+3 -2
src/components/Navigation/NavigationBar.vue
··· 51 51 <style scoped lang="scss"> 52 52 .navigation-bar { 53 53 --gap: 0.5rem; 54 - background: hsla(var(--mantle) / 1); 55 - border-top: 1px solid hsla(var(--surface2) / 0.25); 54 + background: hsla(var(--base) / 0.8); 55 + backdrop-filter: blur(0.75rem); 56 + border-top: 1px solid hsla(var(--surface2) / 0.3); 56 57 57 58 width: 100%; 58 59 max-width: 100vw;