A social RSS reader built on the AT Protocol. glean.at
glean atproto atmosphere rss feed social app
14
fork

Configure Feed

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

Add squared design support and improve shadows

+83 -27
+7 -4
docs/design.md
··· 10 10 11 11 Surfaces breathe through rounded geometry: pill buttons (`9999px`), `12px` card corners, and `50%` circular avatars. Shadows are whisper-soft dual-layers, never heavy. The system feels like a well-lit reading room. 12 12 13 + All radii are driven by CSS custom properties (`--radius-sm`, `--radius-md`, `--radius-lg`, `--radius-pill`, `--radius-full`) wired through `tailwind.config.js`. A **squared** shape mode is available via `data-shape="squared"` on the root element, which sets all radius variables to `0`. The toggle lives in the footer alongside the theme switcher and persists via `localStorage('shape')`. 14 + 13 15 **Color-block rhythm (landing page):** Cream/forest hero → white card sections → House Green (`#1E3932`) feature band with white text → cream utility zone → House Green footer. 14 16 15 17 Logo is a stylized bee: body with horizontal stripes (like text lines on a page), semi-transparent wings that evoke open book pages, round eyes, curved antennae, and a small smile. The bee represents gleaning (collecting nectar/knowledge), social behavior (hives/communities), and reading (the striped body reads like lines of text, wings like turning pages). ··· 105 107 106 108 ### Shadows 107 109 108 - | Token | Value | Use | 109 - | ------------------- | -------------------------------------------------------- | ------------ | 110 - | `shadow-spot` | `0 0 0.5px rgba(0,0,0,0.14), 0 1px 1px rgba(0,0,0,0.24)` | Cards | 111 - | `shadow-spot-heavy` | `0 0 6px rgba(0,0,0,0.24), 0 8px 12px rgba(0,0,0,0.14)` | Modals, hero | 110 + | Token | Use | 111 + | ---------------------- | ------------ | 112 + | `shadow-spot` | Cards | 113 + | `shadow-spot-heavy` | Modals, hero | 114 + | `shadow-spot-elevated` | Dialogs, floating elements | 112 115 113 116 ### Navigation 114 117
+3 -3
internal/tmpl/articles.html
··· 56 56 </form> 57 57 <div class="flex items-center gap-2 shrink-0"> 58 58 <a href="/articles?status=all{{if .FeedURL}}&feed={{.FeedURL}}{{end}}" 59 - class="px-4 py-1.5 rounded-full text-xs font-bold uppercase tracking-button transition 59 + class="px-4 py-1.5 rounded-pill text-xs font-bold uppercase tracking-button transition 60 60 {{if eq .Status "all"}}bg-spot-active-pill-bg text-spot-active-pill-text{{else}}bg-spot-hover text-spot-secondary hover:text-spot-text{{end}}"> 61 61 All 62 62 </a> 63 63 <a href="/articles?status=unread{{if .FeedURL}}&feed={{.FeedURL}}{{end}}" 64 - class="px-4 py-1.5 rounded-full text-xs font-bold uppercase tracking-button transition 64 + class="px-4 py-1.5 rounded-pill text-xs font-bold uppercase tracking-button transition 65 65 {{if or (eq .Status "unread") (eq .Status "")}}bg-spot-active-pill-bg text-spot-active-pill-text{{else}}bg-spot-hover text-spot-secondary hover:text-spot-text{{end}}"> 66 66 Unread 67 67 </a> 68 68 <a href="/articles?status=read{{if .FeedURL}}&feed={{.FeedURL}}{{end}}" 69 - class="px-4 py-1.5 rounded-full text-xs font-bold uppercase tracking-button transition 69 + class="px-4 py-1.5 rounded-pill text-xs font-bold uppercase tracking-button transition 70 70 {{if eq .Status "read"}}bg-spot-active-pill-bg text-spot-active-pill-text{{else}}bg-spot-hover text-spot-secondary hover:text-spot-text{{end}}"> 71 71 Read 72 72 </a>
+35 -3
internal/tmpl/base.html
··· 6 6 <meta name="viewport" content="width=device-width, initial-scale=1"> 7 7 <title>Glean</title> 8 8 <script> 9 - (function(){var p=localStorage.getItem('theme')||'system';var r=p==='system'?(window.matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light'):p;document.documentElement.setAttribute('data-theme',r)})(); 9 + (function(){var p=localStorage.getItem('theme')||'system';var r=p==='system'?(window.matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light'):p;document.documentElement.setAttribute('data-theme',r);var s=localStorage.getItem('shape');if(s)document.documentElement.setAttribute('data-shape',s)})(); 10 10 </script> 11 11 <link rel="stylesheet" href="/static/output.css"> 12 12 <script src="https://unpkg.com/htmx.org@2"></script> ··· 37 37 <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"> 38 38 </head> 39 39 <body class="bg-spot-bg text-spot-text min-h-screen flex"> 40 - <dialog id="confirm-dialog" class="bg-spot-surface rounded-xl p-6 max-w-sm shadow-lg border border-spot-divider backdrop:bg-black/50" onclick="if(event.target===this)this.close()"> 40 + <dialog id="confirm-dialog" class="bg-spot-surface rounded-xl p-6 max-w-sm shadow-spot-elevated border border-spot-divider backdrop:bg-black/50" onclick="if(event.target===this)this.close()"> 41 41 <p id="confirm-dialog-msg" class="text-spot-text text-sm mb-6"></p> 42 42 <div class="flex justify-end gap-3"> 43 43 <button onclick="document.getElementById('confirm-dialog').close(false)" class="text-sm text-spot-secondary hover:text-spot-text px-4 py-2 rounded-pill border border-spot-outline transition">Cancel</button> 44 44 <button id="confirm-dialog-ok" class="text-sm text-white bg-spot-red hover:brightness-110 px-4 py-2 rounded-pill font-bold uppercase tracking-button transition">Confirm</button> 45 45 </div> 46 46 </dialog> 47 - <dialog id="shortcuts-dialog" class="bg-spot-surface rounded-xl p-6 max-w-xs shadow-lg border border-spot-divider backdrop:bg-black/50" onclick="if(event.target===this)this.close()"> 47 + <dialog id="shortcuts-dialog" class="bg-spot-surface rounded-xl p-6 max-w-xs shadow-spot-elevated border border-spot-divider backdrop:bg-black/50" onclick="if(event.target===this)this.close()"> 48 48 <h3 class="text-sm font-bold text-spot-text uppercase tracking-wide mb-4">Keyboard shortcuts</h3> 49 49 <div class="space-y-2 text-sm"> 50 50 <div class="text-spot-secondary font-bold text-xs uppercase tracking-wide">Navigation</div> ··· 198 198 <span class="theme-text-light hidden">Light</span> 199 199 <span class="theme-text-system hidden">System</span> 200 200 </button> 201 + <button onclick="toggleShape()" class="text-xs text-spot-secondary hover:text-spot-text hover:underline inline-flex gap-1.5 items-center transition text-left"> 202 + <svg class="w-3.5 h-3.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4h16v16H4z"/></svg> 203 + <span class="shape-text">Squared</span> 204 + </button> 201 205 <button onclick="document.getElementById('shortcuts-dialog').showModal()" class="text-xs text-spot-secondary hover:text-spot-text hover:underline inline-flex gap-1.5 items-center transition text-left"> 202 206 <svg class="w-3.5 h-3.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m6.75 7.5 3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0 0 21 18V6a2.25 2.25 0 0 0-2.25-2.25H5.25A2.25 2.25 0 0 0 3 6v12a2.25 2.25 0 0 0 2.25 2.25Z"/></svg> 203 207 Keyboard shortcuts ··· 234 238 <span class="theme-text-dark">Dark</span> 235 239 <span class="theme-text-light hidden">Light</span> 236 240 <span class="theme-text-system hidden">System</span> 241 + </button> 242 + <button onclick="toggleShape()" class="text-xs text-spot-secondary hover:text-spot-text inline-flex gap-1.5 items-center transition"> 243 + <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4h16v16H4z"/></svg> 244 + <span class="shape-text">Squared</span> 237 245 </button> 238 246 </div> 239 247 <span class="text-xs text-spot-secondary">&copy; {{now.Format "2006"}} <a href="https://bsky.app/profile/julien.rbrt.fr">julien.rbrt.fr</a> &middot; Made in Europe &#127466;&#127482;</span> ··· 269 277 applyTheme(); 270 278 } 271 279 280 + function applyShape() { 281 + var shapePref = localStorage.getItem('shape') || 'rounded'; 282 + if (shapePref === 'squared') { 283 + document.documentElement.setAttribute('data-shape', 'squared'); 284 + } else { 285 + document.documentElement.removeAttribute('data-shape'); 286 + } 287 + document.querySelectorAll('.shape-text').forEach(function(el) { 288 + el.textContent = shapePref === 'squared' ? 'Rounded' : 'Squared'; 289 + }); 290 + } 291 + 292 + function toggleShape() { 293 + var current = localStorage.getItem('shape') || 'rounded'; 294 + var next = current === 'squared' ? 'rounded' : 'squared'; 295 + if (next === 'rounded') { 296 + localStorage.removeItem('shape'); 297 + } else { 298 + localStorage.setItem('shape', next); 299 + } 300 + applyShape(); 301 + } 302 + 272 303 window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function() { 273 304 if (themePref === 'system') applyTheme(); 274 305 }); 275 306 276 307 applyTheme(); 308 + applyShape(); 277 309 278 310 function closeAnnotate(el) { 279 311 var form = el.closest('.annotate-form');
+2 -2
internal/tmpl/feeds.html
··· 35 35 <div class="lg:col-span-2"> 36 36 {{if .Categories}} 37 37 <div class="flex gap-2 mb-6 flex-wrap"> 38 - <a href="/feeds" class="text-sm px-4 py-1.5 rounded-full font-bold {{if not .Category}}bg-spot-active-pill-bg text-spot-active-pill-text{{else}}bg-spot-hover text-spot-secondary hover:text-spot-text{{end}} transition">All</a> 38 + <a href="/feeds" class="text-sm px-4 py-1.5 rounded-pill font-bold {{if not .Category}}bg-spot-active-pill-bg text-spot-active-pill-text{{else}}bg-spot-hover text-spot-secondary hover:text-spot-text{{end}} transition">All</a> 39 39 {{range .Categories}} 40 - <a href="/feeds?category={{.}}" class="text-sm px-4 py-1.5 rounded-full font-bold {{if eq $.Category .}}bg-spot-active-pill-bg text-spot-active-pill-text{{else}}bg-spot-hover text-spot-secondary hover:text-spot-text{{end}} transition">{{.}}</a> 40 + <a href="/feeds?category={{.}}" class="text-sm px-4 py-1.5 rounded-pill font-bold {{if eq $.Category .}}bg-spot-active-pill-bg text-spot-active-pill-text{{else}}bg-spot-hover text-spot-secondary hover:text-spot-text{{end}} transition">{{.}}</a> 41 41 {{end}} 42 42 </div> 43 43 {{end}}
+1 -1
internal/tmpl/partials/annotation-card.html
··· 16 16 {{end}} 17 17 {{if .annotation.Tags.Valid}} 18 18 <div class="flex gap-1.5 mt-3 flex-wrap"> 19 - {{range $tag := split .annotation.Tags.String ","}}<span class="text-xs bg-spot-hover text-spot-secondary px-2.5 py-0.5 rounded-full">{{$tag}}</span>{{end}} 19 + {{range $tag := split .annotation.Tags.String ","}}<span class="text-xs bg-spot-hover text-spot-secondary px-2.5 py-0.5 rounded-pill">{{$tag}}</span>{{end}} 20 20 </div> 21 21 {{end}} 22 22 {{if .annotation.Rating.Valid}}
+2 -2
internal/tmpl/partials/feed-item.html
··· 5 5 <div class="min-w-0 flex-1"> 6 6 <div class="flex items-center gap-2 flex-wrap"> 7 7 <span class="font-bold text-spot-text truncate">{{if .FeedTitle}}{{.FeedTitle}}{{else}}{{.FeedURL}}{{end}}</span> 8 - {{if and .Category.Valid .Category.String}}<span class="text-xs bg-spot-hover text-spot-secondary px-2 py-0.5 rounded-full shrink-0">{{.Category.String}}</span>{{end}} 8 + {{if and .Category.Valid .Category.String}}<span class="text-xs bg-spot-hover text-spot-secondary px-2 py-0.5 rounded-pill shrink-0">{{.Category.String}}</span>{{end}} 9 9 </div> 10 10 <div class="text-xs text-spot-muted truncate mt-0.5">{{.FeedURL}}</div> 11 11 </div> 12 12 </a> 13 13 <div class="flex items-center gap-3 shrink-0"> 14 - {{if .UnreadCount}}<span class="text-xs bg-spot-green/20 text-spot-green px-2.5 py-0.5 rounded-full font-bold">{{.UnreadCount}}</span>{{end}} 14 + {{if .UnreadCount}}<span class="text-xs bg-spot-green/20 text-spot-green px-2.5 py-0.5 rounded-pill font-bold">{{.UnreadCount}}</span>{{end}} 15 15 <form hx-delete="/feeds/remove" hx-target="closest .feed-item" hx-swap="outerHTML swap:0.3s" 16 16 hx-confirm="Unsubscribe from this feed?" class="inline"> 17 17 {{csrfInput .CSRFToken}}
+1 -1
internal/tmpl/partials/profile-card.html
··· 4 4 <div class="min-w-0 flex-1"> 5 5 <span class="font-bold text-spot-text hover:text-spot-green transition">@{{.Handle}}</span> 6 6 {{if .IsFollowed}} 7 - <span class="inline-flex items-center gap-0.5 text-[10px] font-medium text-spot-green bg-spot-green/10 px-1.5 py-0.5 rounded-full ml-1.5">Following</span> 7 + <span class="inline-flex items-center gap-0.5 text-[10px] font-medium text-spot-green bg-spot-green/10 px-1.5 py-0.5 rounded-pill ml-1.5">Following</span> 8 8 {{end}} 9 9 {{if .DisplayName}}<div class="text-sm text-spot-secondary">{{.DisplayName}}</div>{{end}} 10 10 </div>
+1 -1
internal/tmpl/profile.html
··· 30 30 <div class="min-w-0 flex-1"> 31 31 <div class="flex items-center gap-2 flex-wrap"> 32 32 <span class="font-bold text-spot-text truncate">{{if .FeedTitle}}{{.FeedTitle}}{{else}}{{.FeedURL}}{{end}}</span> 33 - {{if and .Category.Valid .Category.String}}<span class="text-xs bg-spot-hover text-spot-secondary px-2 py-0.5 rounded-full shrink-0">{{.Category.String}}</span>{{end}} 33 + {{if and .Category.Valid .Category.String}}<span class="text-xs bg-spot-hover text-spot-secondary px-2 py-0.5 rounded-pill shrink-0">{{.Category.String}}</span>{{end}} 34 34 </div> 35 35 <div class="text-xs text-spot-muted truncate mt-0.5">{{.FeedURL}}</div> 36 36 </div>
+1 -1
internal/tmpl/stats.html
··· 27 27 {{if .Labels}} 28 28 <div class="flex gap-2 mt-1"> 29 29 {{range $key, $value := .Labels}} 30 - <span class="text-xs bg-spot-hover text-spot-secondary px-2 py-0.5 rounded-full">{{$key}}={{$value}}</span> 30 + <span class="text-xs bg-spot-hover text-spot-secondary px-2 py-0.5 rounded-pill">{{$key}}={{$value}}</span> 31 31 {{end}} 32 32 </div> 33 33 {{end}}
+2 -2
internal/tmpl/trending.html
··· 12 12 <h1 class="text-2xl font-bold text-spot-text">Trending</h1> 13 13 <div class="flex gap-2"> 14 14 <a href="/trending?scope=all" 15 - class="text-sm px-4 py-1.5 rounded-full font-bold {{if ne .Scope "for-me"}}bg-spot-active-pill-bg text-spot-active-pill-text{{else}}bg-spot-hover text-spot-secondary hover:text-spot-text{{end}} transition">All</a> 15 + class="text-sm px-4 py-1.5 rounded-pill font-bold {{if ne .Scope "for-me"}}bg-spot-active-pill-bg text-spot-active-pill-text{{else}}bg-spot-hover text-spot-secondary hover:text-spot-text{{end}} transition">All</a> 16 16 <a href="/trending?scope=for-me" 17 - class="text-sm px-4 py-1.5 rounded-full font-bold {{if eq .Scope "for-me"}}bg-spot-active-pill-bg text-spot-active-pill-text{{else}}bg-spot-hover text-spot-secondary hover:text-spot-text{{end}} transition">For me</a> 17 + class="text-sm px-4 py-1.5 rounded-pill font-bold {{if eq .Scope "for-me"}}bg-spot-active-pill-bg text-spot-active-pill-text{{else}}bg-spot-hover text-spot-secondary hover:text-spot-text{{end}} transition">For me</a> 18 18 </div> 19 19 </div> 20 20 <p class="text-sm text-spot-secondary mb-6">
+19 -5
static/input.css
··· 19 19 --spot-placeholder: rgba(255,255,255,0.45); 20 20 --spot-active-bg: #ffffff; 21 21 --spot-active-text: #1E3932; 22 - --spot-shadow: 0 0 0.5px rgba(0,0,0,0.20), 0 1px 1px rgba(0,0,0,0.30); 23 - --spot-shadow-heavy: 0 0 6px rgba(0,0,0,0.30), 0 8px 12px rgba(0,0,0,0.20); 22 + --radius-sm: 0.25rem; 23 + --radius-md: 0.5rem; 24 + --radius-lg: 0.75rem; 25 + --radius-pill: 9999px; 26 + --radius-full: 50%; 27 + --spot-shadow: 0 1px 2px rgba(0,0,0,0.40), 0 2px 4px rgba(0,0,0,0.20); 28 + --spot-shadow-heavy: 0 4px 8px rgba(0,0,0,0.35), 0 12px 24px rgba(0,0,0,0.25); 29 + --spot-shadow-elevated: 0 8px 16px rgba(0,0,0,0.40), 0 24px 48px rgba(0,0,0,0.30); 24 30 } 25 31 26 32 [data-theme="light"] { ··· 39 45 --spot-placeholder: rgba(0,0,0,0.30); 40 46 --spot-active-bg: #1E3932; 41 47 --spot-active-text: #ffffff; 42 - --spot-shadow: 0 0 0.5px rgba(0,0,0,0.14), 0 1px 1px rgba(0,0,0,0.24); 43 - --spot-shadow-heavy: 0 0 6px rgba(0,0,0,0.14), 0 8px 12px rgba(0,0,0,0.08); 48 + --spot-shadow: 0 1px 2px rgba(0,0,0,0.06), 0 2px 4px rgba(0,0,0,0.04); 49 + --spot-shadow-heavy: 0 4px 12px rgba(0,0,0,0.08), 0 12px 28px rgba(0,0,0,0.06); 50 + --spot-shadow-elevated: 0 8px 24px rgba(0,0,0,0.10), 0 24px 48px rgba(0,0,0,0.08); 51 + } 52 + 53 + [data-shape="squared"] { 54 + --radius-sm: 0; 55 + --radius-md: 0; 56 + --radius-lg: 0; 57 + --radius-pill: 0; 44 58 } 45 59 } 46 60 ··· 96 110 97 111 ::-webkit-scrollbar { width: 8px; } 98 112 ::-webkit-scrollbar-track { background: var(--spot-bg); } 99 - ::-webkit-scrollbar-thumb { background: var(--spot-muted); border-radius: 4px; } 113 + ::-webkit-scrollbar-thumb { background: var(--spot-muted); border-radius: var(--radius-md); } 100 114 ::-webkit-scrollbar-thumb:hover { background: var(--spot-outline); } 101 115 102 116 .htmx-swapping {
+9 -2
tailwind.config.js
··· 30 30 } 31 31 }, 32 32 borderRadius: { 33 - pill: '9999px', 34 - 'pill-lg': '500px', 33 + DEFAULT: 'var(--radius-sm)', 34 + pill: 'var(--radius-pill)', 35 + 'pill-lg': 'var(--radius-pill)', 36 + sm: 'var(--radius-sm)', 37 + md: 'var(--radius-md)', 38 + lg: 'var(--radius-md)', 39 + xl: 'var(--radius-lg)', 40 + full: 'var(--radius-full)', 35 41 }, 36 42 boxShadow: { 37 43 'spot': 'var(--spot-shadow)', 38 44 'spot-heavy': 'var(--spot-shadow-heavy)', 45 + 'spot-elevated': 'var(--spot-shadow-elevated)', 39 46 }, 40 47 letterSpacing: { 41 48 button: '1.4px',