mobile bluesky app made with flutter lazurite.stormlightlabs.org/
mobile bluesky flutter
3
fork

Configure Feed

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

at main 565 lines 15 kB view raw
1<!doctype html> 2<html lang="en"> 3 <head> 4 <meta charset="UTF-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> 6 <title>Compose - Lazurite</title> 7 <link rel="preconnect" href="https://fonts.googleapis.com" /> 8 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> 9 <link href="https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&display=swap" rel="stylesheet" /> 10 <link 11 href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap" 12 rel="stylesheet" /> 13 <link rel="stylesheet" href="styles.css" /> 14 <style> 15 .compose-header { 16 display: flex; 17 align-items: center; 18 justify-content: space-between; 19 padding: 12px 16px; 20 border-bottom: 1px solid var(--border); 21 background-color: var(--bg); 22 position: sticky; 23 top: 0; 24 z-index: 50; 25 } 26 27 .compose-header-cancel { 28 background: none; 29 border: none; 30 color: var(--text-secondary); 31 font-size: 15px; 32 font-weight: 500; 33 cursor: pointer; 34 } 35 36 .compose-header-title { 37 font-size: 18px; 38 font-weight: 600; 39 color: var(--text-primary); 40 font-family: var(--font-heading); 41 } 42 43 .compose-header-post { 44 padding: 8px 20px; 45 border-radius: 9999px; 46 border: none; 47 background-color: var(--accent-primary); 48 color: white; 49 font-weight: 600; 50 font-size: 14px; 51 cursor: pointer; 52 transition: background-color 0.2s ease; 53 } 54 55 .compose-header-post:hover { 56 background-color: var(--accent-primary-hover); 57 } 58 59 .compose-header-post:disabled { 60 opacity: 0.5; 61 cursor: not-allowed; 62 } 63 64 .compose-body { 65 padding: 16px; 66 display: flex; 67 gap: 12px; 68 min-height: 200px; 69 } 70 71 .compose-textarea { 72 flex: 1; 73 border: none; 74 background: transparent; 75 color: var(--text-primary); 76 font-size: 16px; 77 font-family: var(--font-body); 78 resize: none; 79 outline: none; 80 min-height: 160px; 81 line-height: 1.5; 82 } 83 84 .compose-textarea::placeholder { 85 color: var(--text-muted); 86 } 87 88 /* Reply Context */ 89 .reply-context { 90 padding: 12px 16px; 91 border-bottom: 1px solid var(--border); 92 display: flex; 93 align-items: center; 94 gap: 8px; 95 background-color: var(--surface); 96 } 97 98 .reply-context-icon { 99 width: 16px; 100 height: 16px; 101 color: var(--text-muted); 102 } 103 104 .reply-context-text { 105 font-size: 13px; 106 color: var(--text-secondary); 107 } 108 109 .reply-context-handle { 110 color: var(--accent-primary); 111 font-weight: 500; 112 } 113 114 /* Media Attachments */ 115 .compose-media { 116 padding: 0 16px 16px; 117 padding-left: 68px; 118 } 119 120 .media-grid { 121 display: grid; 122 grid-template-columns: repeat(2, 1fr); 123 gap: 8px; 124 } 125 126 .media-item { 127 position: relative; 128 border-radius: 12px; 129 overflow: hidden; 130 background: linear-gradient(135deg, var(--surface) 0%, var(--surface-variant) 100%); 131 aspect-ratio: 4 / 3; 132 display: flex; 133 align-items: center; 134 justify-content: center; 135 color: var(--text-muted); 136 font-size: 13px; 137 } 138 139 .media-item-remove { 140 position: absolute; 141 top: 6px; 142 right: 6px; 143 width: 24px; 144 height: 24px; 145 border-radius: 50%; 146 background-color: rgba(0, 0, 0, 0.6); 147 border: none; 148 color: white; 149 cursor: pointer; 150 display: flex; 151 align-items: center; 152 justify-content: center; 153 } 154 155 .media-item-remove svg { 156 width: 14px; 157 height: 14px; 158 } 159 160 .media-item-alt { 161 position: absolute; 162 bottom: 6px; 163 left: 6px; 164 padding: 2px 8px; 165 border-radius: 4px; 166 background-color: rgba(0, 0, 0, 0.6); 167 color: white; 168 font-size: 11px; 169 font-weight: 600; 170 cursor: pointer; 171 } 172 173 .media-item-alt.has-alt { 174 background-color: var(--accent-primary); 175 } 176 177 /* Toolbar */ 178 .compose-toolbar { 179 display: flex; 180 align-items: center; 181 justify-content: space-between; 182 padding: 12px 16px; 183 border-top: 1px solid var(--border); 184 position: sticky; 185 bottom: 0; 186 background-color: var(--bg); 187 } 188 189 .toolbar-actions { 190 display: flex; 191 gap: 4px; 192 } 193 194 .toolbar-btn { 195 width: 40px; 196 height: 40px; 197 border-radius: 50%; 198 border: none; 199 background: transparent; 200 color: var(--accent-primary); 201 cursor: pointer; 202 display: flex; 203 align-items: center; 204 justify-content: center; 205 transition: background-color 0.2s ease; 206 } 207 208 .toolbar-btn:hover { 209 background-color: var(--surface); 210 } 211 212 .toolbar-btn svg { 213 width: 22px; 214 height: 22px; 215 } 216 217 .toolbar-btn.disabled { 218 color: var(--text-muted); 219 cursor: not-allowed; 220 } 221 222 /* Character Counter */ 223 .char-counter { 224 display: flex; 225 align-items: center; 226 gap: 8px; 227 } 228 229 .char-counter-ring { 230 width: 28px; 231 height: 28px; 232 position: relative; 233 } 234 235 .char-counter-ring svg { 236 width: 28px; 237 height: 28px; 238 transform: rotate(-90deg); 239 } 240 241 .char-counter-ring circle { 242 fill: none; 243 stroke-width: 2.5; 244 } 245 246 .char-counter-bg { 247 stroke: var(--surface-variant); 248 } 249 250 .char-counter-fill { 251 stroke: var(--accent-primary); 252 stroke-dasharray: 75.4; 253 stroke-dashoffset: 30; 254 stroke-linecap: round; 255 transition: 256 stroke-dashoffset 0.2s ease, 257 stroke 0.2s ease; 258 } 259 260 .char-counter-fill.warning { 261 stroke: var(--accent-warning); 262 } 263 264 .char-counter-fill.danger { 265 stroke: var(--accent-error); 266 } 267 268 .char-counter-text { 269 font-size: 13px; 270 color: var(--text-muted); 271 font-variant-numeric: tabular-nums; 272 } 273 274 /* Drafts */ 275 .drafts-divider { 276 height: 8px; 277 background-color: var(--surface); 278 border-top: 1px solid var(--border); 279 border-bottom: 1px solid var(--border); 280 } 281 282 .drafts-header { 283 display: flex; 284 align-items: center; 285 justify-content: space-between; 286 padding: 16px; 287 } 288 289 .drafts-title { 290 font-size: 16px; 291 font-weight: 600; 292 color: var(--text-primary); 293 } 294 295 .drafts-count { 296 font-size: 13px; 297 color: var(--text-muted); 298 } 299 300 .draft-item { 301 padding: 12px 16px; 302 border-bottom: 1px solid var(--border); 303 cursor: pointer; 304 transition: background-color 0.2s ease; 305 } 306 307 .draft-item:hover { 308 background-color: var(--surface); 309 } 310 311 .draft-item-text { 312 font-size: 15px; 313 color: var(--text-primary); 314 line-height: 1.4; 315 display: -webkit-box; 316 line-clamp: 2; 317 -webkit-line-clamp: 2; 318 -webkit-box-orient: vertical; 319 overflow: hidden; 320 } 321 322 .draft-item-meta { 323 display: flex; 324 align-items: center; 325 gap: 8px; 326 margin-top: 6px; 327 font-size: 12px; 328 color: var(--text-muted); 329 } 330 331 .draft-item-badge { 332 padding: 2px 6px; 333 border-radius: 4px; 334 background-color: var(--surface-variant); 335 font-size: 11px; 336 font-weight: 600; 337 color: var(--text-secondary); 338 } 339 340 .draft-item-badge.scheduled { 341 background-color: var(--accent-primary); 342 color: white; 343 } 344 345 /* Schedule Pill */ 346 .schedule-pill { 347 display: inline-flex; 348 align-items: center; 349 gap: 6px; 350 padding: 6px 12px; 351 border-radius: 9999px; 352 background-color: var(--surface); 353 border: 1px solid var(--border); 354 font-size: 13px; 355 color: var(--text-secondary); 356 margin-left: 68px; 357 margin-bottom: 12px; 358 cursor: pointer; 359 transition: all 0.2s ease; 360 } 361 362 .schedule-pill:hover { 363 border-color: var(--accent-primary); 364 color: var(--accent-primary); 365 } 366 367 .schedule-pill svg { 368 width: 14px; 369 height: 14px; 370 } 371 372 .schedule-pill.active { 373 background-color: var(--accent-primary); 374 border-color: var(--accent-primary); 375 color: white; 376 } 377 </style> 378 </head> 379 <body> 380 <div class="mobile-container"> 381 <!-- Compose Header --> 382 <div class="compose-header"> 383 <button class="compose-header-cancel">Cancel</button> 384 <span class="compose-header-title">New Post</span> 385 <button class="compose-header-post">Post</button> 386 </div> 387 388 <!-- Reply Context (shown when replying) --> 389 <div class="reply-context"> 390 <svg 391 class="reply-context-icon" 392 viewBox="0 0 24 24" 393 fill="none" 394 stroke="currentColor" 395 stroke-width="2" 396 stroke-linecap="round" 397 stroke-linejoin="round"> 398 <polyline points="9 14 4 9 9 4" /> 399 <path d="M20 20v-7a4 4 0 0 0-4-4H4" /> 400 </svg> 401 <span class="reply-context-text">Replying to <span class="reply-context-handle">@alice.bsky.social</span></span> 402 </div> 403 404 <!-- Compose Body --> 405 <div class="compose-body"> 406 <div class="avatar avatar-sm">JD</div> 407 <textarea class="compose-textarea" placeholder="What's on your mind?" rows="6"> 408Excited to share my latest project built with the AT Protocol! Check it out and let me know what you think.</textarea 409 > 410 </div> 411 412 <!-- Schedule Pill --> 413 <div class="schedule-pill"> 414 <svg 415 viewBox="0 0 24 24" 416 fill="none" 417 stroke="currentColor" 418 stroke-width="2" 419 stroke-linecap="round" 420 stroke-linejoin="round"> 421 <circle cx="12" cy="12" r="10" /> 422 <polyline points="12 6 12 12 16 14" /> 423 </svg> 424 Schedule for later 425 </div> 426 427 <!-- Media Attachments --> 428 <div class="compose-media"> 429 <div class="media-grid"> 430 <div class="media-item"> 431 [Photo 1] 432 <button class="media-item-remove"> 433 <svg 434 viewBox="0 0 24 24" 435 fill="none" 436 stroke="currentColor" 437 stroke-width="2" 438 stroke-linecap="round" 439 stroke-linejoin="round"> 440 <line x1="18" y1="6" x2="6" y2="18" /> 441 <line x1="6" y1="6" x2="18" y2="18" /> 442 </svg> 443 </button> 444 <span class="media-item-alt has-alt">ALT</span> 445 </div> 446 <div class="media-item"> 447 [Photo 2] 448 <button class="media-item-remove"> 449 <svg 450 viewBox="0 0 24 24" 451 fill="none" 452 stroke="currentColor" 453 stroke-width="2" 454 stroke-linecap="round" 455 stroke-linejoin="round"> 456 <line x1="18" y1="6" x2="6" y2="18" /> 457 <line x1="6" y1="6" x2="18" y2="18" /> 458 </svg> 459 </button> 460 <span class="media-item-alt">ALT</span> 461 </div> 462 </div> 463 </div> 464 465 <!-- Toolbar --> 466 <div class="compose-toolbar"> 467 <div class="toolbar-actions"> 468 <!-- Image --> 469 <button class="toolbar-btn" title="Add image"> 470 <svg 471 viewBox="0 0 24 24" 472 fill="none" 473 stroke="currentColor" 474 stroke-width="2" 475 stroke-linecap="round" 476 stroke-linejoin="round"> 477 <rect x="3" y="3" width="18" height="18" rx="2" ry="2" /> 478 <circle cx="8.5" cy="8.5" r="1.5" /> 479 <polyline points="21 15 16 10 5 21" /> 480 </svg> 481 </button> 482 <!-- Drafts --> 483 <button class="toolbar-btn" title="Drafts"> 484 <svg 485 viewBox="0 0 24 24" 486 fill="none" 487 stroke="currentColor" 488 stroke-width="2" 489 stroke-linecap="round" 490 stroke-linejoin="round"> 491 <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /> 492 <polyline points="14 2 14 8 20 8" /> 493 <line x1="16" y1="13" x2="8" y2="13" /> 494 <line x1="16" y1="17" x2="8" y2="17" /> 495 </svg> 496 </button> 497 <!-- Schedule --> 498 <button class="toolbar-btn" title="Schedule"> 499 <svg 500 viewBox="0 0 24 24" 501 fill="none" 502 stroke="currentColor" 503 stroke-width="2" 504 stroke-linecap="round" 505 stroke-linejoin="round"> 506 <circle cx="12" cy="12" r="10" /> 507 <polyline points="12 6 12 12 16 14" /> 508 </svg> 509 </button> 510 </div> 511 512 <div class="char-counter"> 513 <span class="char-counter-text">192</span> 514 <div class="char-counter-ring"> 515 <svg viewBox="0 0 28 28"> 516 <circle class="char-counter-bg" cx="14" cy="14" r="12" /> 517 <circle class="char-counter-fill" cx="14" cy="14" r="12" /> 518 </svg> 519 </div> 520 </div> 521 </div> 522 523 <!-- Drafts Section (shown when drafts toolbar button is tapped) --> 524 <div class="drafts-divider"></div> 525 526 <div class="drafts-header"> 527 <span class="drafts-title">Drafts</span> 528 <span class="drafts-count">3 drafts</span> 529 </div> 530 531 <div class="draft-item"> 532 <div class="draft-item-text"> 533 Working on a thread about decentralised identity and why it matters for the open web... 534 </div> 535 <div class="draft-item-meta"> 536 <span>2 hours ago</span> 537 <span class="draft-item-badge">Draft</span> 538 </div> 539 </div> 540 541 <div class="draft-item"> 542 <div class="draft-item-text">Hot take: the best developer experience is the one you don't notice</div> 543 <div class="draft-item-meta"> 544 <span>Yesterday</span> 545 <span class="draft-item-badge scheduled">Mar 18, 9:00 AM</span> 546 </div> 547 </div> 548 549 <div class="draft-item"> 550 <div class="draft-item-text">Quick review of the new AT Protocol SDK features that shipped this week</div> 551 <div class="draft-item-meta"> 552 <span>3 days ago</span> 553 <span class="draft-item-badge">Draft</span> 554 </div> 555 </div> 556 </div> 557 558 <script> 559 if (localStorage.getItem("theme")) { 560 const t = localStorage.getItem("theme"); 561 if (t !== "light") document.documentElement.setAttribute("data-theme", t); 562 } 563 </script> 564 </body> 565</html>