wip bsky client for the web & android
bbell.vt3e.cat
1<script setup lang="ts">
2import { onMounted, onUnmounted, ref } from 'vue'
3
4import ScreenLayout from '@/components/Layout/ScreenLayout.vue'
5import SVG from '@/components/UI/SVG.vue'
6import BluebellLogo from '@/assets/icons/bluebell.svg?raw'
7import { tap } from '@/utils/haptics'
8import { useAuthStore } from '@/stores/auth'
9
10const auth = useAuthStore()
11const messages = [
12 "they're calling it the coilest client",
13 'thank you for using bell >.<',
14 'awawawwaawawa',
15 'bwaaaaaaa',
16 "remember that you're cute (or whatever your preferred compliment is)!",
17 '@xan.lol im coming for ur crown',
18 'the logo is NOT a wilted rose oh my GOD',
19 'isabluebel roses',
20 'trans rights',
21 'do you have your coil habdel?',
22 'CHOCCY MILK IS SHOOO GOOD',
23 'dhis is habdelslop , you only liek it bcuz its coil',
24
25 // ty pupcloud @upcloud.com
26 'use code "CLICKER" and test upcloud.com with €50 in free credit!',
27
28 // ty hoi.tgirl.gay!
29 'shooo eepy',
30 'blahaj approved',
31 "you're so valid bloomf...",
32 'sho gleepy',
33
34 // ty @coolhands.nekoweb.org!
35 ',,i like ,,..ice cweam .,',
36 'get that handle ready',
37
38 // ty @bot-tan.suibari.com!
39 'And thanks a bunch for liking my post! It makes me so happy!',
40]
41const setMessage = () => {
42 let newIndex
43 do {
44 newIndex = Math.floor(Math.random() * messages.length)
45 } while (messages[newIndex] === message.value && messages.length > 1)
46
47 message.value = messages[newIndex]
48 tap()
49}
50const message = ref()
51setMessage()
52
53onMounted(() => {
54 document.addEventListener('pointerdown', setMessage)
55})
56onUnmounted(() => {
57 document.removeEventListener('pointerdown', setMessage)
58})
59</script>
60
61<template>
62 <ScreenLayout bgImage="/images/bluebell.webp" :contentPosition="'center'" maxWidth="640px">
63 <div class="splash-content">
64 <div class="app-hero">
65 <div class="logo-wrapper" aria-hidden="true">
66 <SVG :icon="BluebellLogo" class="logo" />
67 </div>
68 <h1 class="title">Bluebell</h1>
69 <p class="subtitle" role="status" aria-live="polite">{{ message }}</p>
70 </div>
71
72 <Teleport to="body">
73 <!-- me fr -->
74 <div class="bottom">
75 <Transition name="pop-up">
76 <div v-if="auth.profile" class="hi-user-pill">
77 <img
78 :src="auth.profile.avatar"
79 :alt="`Avatar for ${auth.profile.handle}`"
80 class="avatar"
81 />
82 <p class="hello">
83 hi, <span class="name">{{ auth.profile.displayName || auth.profile.handle }}</span
84 >!
85 </p>
86 </div>
87 </Transition>
88
89 <div class="loader-track" aria-hidden="true">
90 <div class="loader-bar"></div>
91 </div>
92 </div>
93 </Teleport>
94 </div>
95 </ScreenLayout>
96</template>
97
98<style scoped lang="scss">
99.pop-up-enter-active {
100 animation-delay: 2s;
101 animation: contentIn 0.5s cubic-bezier(0.2, 0.9, 0.2, 1) both;
102}
103
104@keyframes popUp {
105}
106
107.splash-content {
108 position: relative;
109 z-index: 10;
110 display: flex;
111 flex-direction: column;
112 align-items: center;
113
114 gap: 1.5rem;
115 padding: 2rem;
116 width: 100%;
117 max-width: 640px;
118 margin: 0 auto;
119 text-align: center;
120 transform-origin: center;
121 animation: contentIn 0.7s cubic-bezier(0.2, 0.9, 0.2, 1) both;
122}
123
124.bottom {
125 position: fixed;
126 bottom: calc(2rem + var(--inset-bottom, 0));
127 left: 50%;
128 transform: translateX(-50%);
129 width: 100%;
130
131 z-index: 2000;
132 display: flex;
133 flex-direction: column;
134 justify-content: center;
135 align-items: center;
136}
137
138.hi-user-pill {
139 display: flex;
140 align-items: center;
141 gap: 0.75em;
142 padding: 0.5rem;
143 padding-right: 1.25rem;
144 background-color: hsla(var(--base) / 1);
145 border: 1px solid hsla(var(--surface2) / 0.3);
146 border-radius: 10rem;
147 margin-bottom: 1.5rem;
148 max-width: 40vw;
149
150 .avatar {
151 width: 2rem;
152 height: 2rem;
153 border-radius: 50%;
154 object-fit: cover;
155 border: 1.5px solid hsl(var(--surface2));
156
157 @media (min-width: 512px) {
158 width: 2.5rem;
159 height: 2.5rem;
160 }
161 }
162
163 .hello {
164 font-size: 0.875rem;
165 color: hsl(var(--text));
166
167 .name {
168 font-weight: 600;
169 color: hsl(var(--accent));
170 }
171
172 @media (min-width: 512px) {
173 font-size: 1rem;
174 }
175 }
176}
177
178.app-hero {
179 display: flex;
180 flex-direction: column;
181 align-items: center;
182 gap: 0.75rem;
183
184 .logo-wrapper {
185 width: 6rem;
186 height: 6rem;
187 color: hsl(var(--accent));
188
189 :deep(svg) {
190 width: 100%;
191 height: 100%;
192 }
193 }
194
195 .title {
196 font-size: 2.25rem;
197 font-weight: 800;
198 letter-spacing: -0.02em;
199 margin: 0;
200 background: linear-gradient(135deg, hsl(var(--text)) 0%, hsl(var(--subtext0)) 100%);
201 -webkit-background-clip: text;
202 background-clip: text;
203 -webkit-text-fill-color: transparent;
204 }
205 .subtitle {
206 font-size: 1.25rem;
207 font-weight: 400;
208 margin: 0;
209 width: 100%;
210 color: hsl(var(--subtext1));
211 user-select: none;
212 }
213}
214
215.loader-track {
216 width: 10rem;
217 height: 0.5rem;
218 background-color: hsla(var(--surface2) / 0.3);
219 border-radius: 5rem;
220 overflow: hidden;
221
222 .loader-bar {
223 position: relative;
224 top: 0;
225 left: 0;
226 height: 100%;
227 width: 100%;
228 background-color: hsl(var(--accent));
229 border-radius: 99px;
230 transform-origin: left;
231 animation: loading 1.5s cubic-bezier(0.65, 0, 0.35, 1) infinite;
232 }
233}
234
235@keyframes loading {
236 0% {
237 transform: translateX(-100%);
238 }
239 50% {
240 transform: translateX(0);
241 }
242 100% {
243 transform: translateX(100%);
244 }
245}
246
247@keyframes contentIn {
248 from {
249 opacity: 0;
250 transform: translateY(3rem) scale(0.75);
251 filter: blur(8px);
252 }
253 to {
254 opacity: 1;
255 transform: translateY(0) scale(1);
256 filter: blur(0);
257 }
258}
259
260@media (min-width: 512px) {
261 .title {
262 font-size: 3rem;
263 }
264}
265</style>