fuzzy find my records ken.waow.tech
embeddings pds search
5
fork

Configure Feed

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

kill the pill: inline pack state, move share back out

the bordered pill wrapped awkwardly on mobile and buried share behind
2 extra clicks. share is about the query, not the pack — it should be
one click from the results page.

- pack state ("saved" / "not saved") is inline muted text in the
stats line, clickable for the disclosure (save/delete/view actions)
- share is back as its own inline button, pushed right with
margin-left: auto, visible whenever there's a query
- no more bordered pill, no "search index" label, no wrapping issues
- trimmed popover description copy

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+176 -407
+6 -8
backend/src/assets/index.html
··· 62 62 63 63 <div id="pack-meta" class="pack-meta hidden"> 64 64 <span id="pack-stats" class="muted"></span> 65 + <span class="sep muted">·</span> 65 66 <details id="pack-menu" class="pack-menu"> 66 - <summary class="pack-menu-trigger"> 67 - <span class="pack-menu-label">search index</span> 68 - <span id="pack-state" class="pack-menu-state">not saved</span> 69 - </summary> 67 + <summary id="pack-state" class="pack-menu-trigger muted">not saved</summary> 70 68 <div class="pack-menu-panel"> 71 69 <div id="pack-menu-desc" class="pack-menu-desc muted"></div> 72 - <a id="pack-view-link" class="pack-menu-item text-btn hidden" href="#" target="_blank" rel="noopener">view search index on PDS ↗</a> 73 - <button id="pack-save-btn" type="button" class="pack-menu-item text-btn text-btn-accent hidden">save search index to my PDS</button> 74 - <button id="pack-delete-btn" type="button" class="pack-menu-item text-btn text-btn-danger hidden">delete saved search index</button> 75 - <button id="pack-share-btn" type="button" class="pack-menu-item text-btn text-btn-accent hidden">share this search</button> 70 + <a id="pack-view-link" class="pack-menu-item text-btn hidden" href="#" target="_blank" rel="noopener">view on PDS ↗</a> 71 + <button id="pack-save-btn" type="button" class="pack-menu-item text-btn text-btn-accent hidden">save to PDS</button> 72 + <button id="pack-delete-btn" type="button" class="pack-menu-item text-btn text-btn-danger hidden">delete from PDS</button> 76 73 </div> 77 74 </details> 75 + <button id="pack-share-btn" type="button" class="text-btn text-btn-accent hidden">share</button> 78 76 <span id="pack-shared-label" class="muted hidden">shared view</span> 79 77 </div> 80 78
+2 -7
backend/src/assets/main.js
··· 338 338 if (j.persisted) { 339 339 packStateEl.textContent = "saved"; 340 340 packMenuDescEl.textContent = 341 - "a vector index of your records lives as a record on your PDS. ken reloads it on sign-in so you can search across everything you've written without re-embedding."; 341 + "a vector index of your records lives as a record on your PDS. ken reloads it on sign-in so you don't have to re-embed."; 342 342 packViewLink.classList.remove("hidden"); 343 343 packViewLink.href = `https://pdsls.dev/${j.persisted_uri}`; 344 344 packDeleteBtn.classList.remove("hidden"); ··· 346 346 } else { 347 347 packStateEl.textContent = "not saved"; 348 348 packMenuDescEl.textContent = 349 - "a vector index of your records lives in ken's memory right now. save it to your PDS to keep it across sessions and make it portable."; 349 + "the index lives in ken's memory only. save it to your PDS to keep it across sessions."; 350 350 packViewLink.classList.add("hidden"); 351 351 packDeleteBtn.classList.add("hidden"); 352 352 packSaveBtn.classList.remove("hidden"); 353 353 } 354 354 355 - // share is about the current search, not the pack — but it's a pack- 356 - // scoped action (shares a query + the pack that answered it), so it 357 - // lives in the same menu. only useful when there's actually a query 358 - // to share; updateShareButtonVisibility handles the gating. 359 355 updateShareButtonVisibility(); 360 356 361 357 // point the about-modal's "example pack" link at whatever pack is ··· 886 882 } 887 883 }); 888 884 packShareBtn.addEventListener("click", () => { 889 - closePackMenu(); 890 885 // mobile native share if available, otherwise modal 891 886 if (navigator.share && /Mobi|Android|iPhone/i.test(navigator.userAgent)) { 892 887 navigator
+168 -392
backend/src/assets/style.css
··· 1 - /* ken — minimal, dark, monospace, mobile-first. 2 - * palette kept to 5 custom properties. single accent (warm amber) so the 3 - * page has one clear interaction color. sizing via clamp() throughout so 4 - * the layout breathes from 360px phones up to wide desktops with no 5 - * breakpoints. soft radii, subtle transitions. 6 - */ 7 - 1 + /* ------- ken ------- */ 8 2 :root { 9 3 --bg: #0d0d0f; 10 4 --fg: #e6e2d7; ··· 12 6 --accent: #d4a76a; 13 7 --accent-dim: #8a6b40; 14 8 --border: #242329; 15 - --surface: #16161a; 16 - --surface-hover: #1c1c22; 9 + --bg-2: #141318; 10 + --bg-3: #1a191f; 17 11 --danger: #e08872; 18 12 --danger-dim: #4a2a22; 19 - 13 + --hero-size: clamp(16px, 1.5vmin, 18px); 20 14 --radius: clamp(6px, 0.6vmin, 10px); 21 - --gap: clamp(10px, 2vmin, 16px); 22 - --pad: clamp(14px, 3vmin, 22px); 23 - --text-body: clamp(13px, 1.6vmin, 15px); 15 + /* base body size — clamped so it never goes below 12px on phones. 16 + * --text-small derives from it for secondary UI elements. */ 17 + --text-body: clamp(13px, 1.5vmin, 15px); 24 18 --text-small: clamp(11px, 1.3vmin, 12px); 25 - --text-h: clamp(15px, 2vmin, 18px); 26 19 } 27 20 28 - * { box-sizing: border-box; margin: 0; padding: 0; } 29 - 30 - html, body { 21 + /* ------- reset / base ------- */ 22 + *, 23 + *::before, 24 + *::after { box-sizing: border-box; margin: 0; padding: 0; } 25 + html { height: 100%; } 26 + body { 31 27 background: var(--bg); 32 28 color: var(--fg); 33 - font: var(--text-body) / 1.5 ui-monospace, "SF Mono", Menlo, "Cascadia Code", monospace; 29 + font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', ui-monospace, monospace; 30 + font-size: var(--text-body); 31 + line-height: 1.6; 34 32 min-height: 100vh; 35 - -webkit-font-smoothing: antialiased; 36 - -moz-osx-font-smoothing: grayscale; 33 + display: flex; 34 + flex-direction: column; 37 35 } 38 - 39 36 a { 40 37 color: var(--accent); 41 38 text-decoration: none; 42 39 border-bottom: 1px solid transparent; 43 - transition: border-color 0.15s; 44 40 } 45 41 a:hover { border-bottom-color: var(--accent); } 46 42 47 - button { 48 - font: inherit; 49 - cursor: pointer; 50 - background: transparent; 43 + /* ------- shared chrome ------- */ 44 + button, .text-btn { 45 + background: none; 46 + border: none; 51 47 color: var(--fg); 52 - border: 1px solid var(--border); 53 - padding: clamp(8px, 1.4vmin, 10px) clamp(12px, 2vmin, 16px); 54 - border-radius: var(--radius); 55 - transition: all 0.15s; 56 - min-height: 44px; 48 + cursor: pointer; 49 + font: inherit; 50 + padding: 0; 57 51 } 58 - button:hover:not(:disabled) { 59 - border-color: var(--fg-mute); 52 + .text-btn { 53 + border-bottom: 1px solid transparent; 54 + transition: border-color 0.15s; 60 55 } 61 - button:disabled { opacity: 0.5; cursor: wait; } 62 - 63 - .hidden { display: none !important; } 56 + .text-btn:hover { color: var(--fg); } 64 57 .muted { color: var(--fg-mute); } 65 - 66 - /* ------- header ------- */ 58 + .hidden { display: none !important; } 67 59 60 + /* ------- nav / brand ------- */ 68 61 header { 69 62 display: flex; 70 63 align-items: baseline; 71 - justify-content: space-between; 72 - gap: var(--gap); 73 - padding: clamp(14px, 2.5vmin, 22px) var(--pad); 64 + gap: 12px; 65 + padding: clamp(12px, 2vmin, 20px) clamp(12px, 3vmin, 24px); 74 66 } 75 - 76 67 .brand { 77 - font-size: var(--text-h); 68 + font-size: var(--hero-size); 69 + font-weight: 700; 78 70 color: var(--fg); 71 + text-decoration: none; 79 72 border-bottom: none; 80 - letter-spacing: -0.01em; 81 73 } 82 74 .brand:hover { border-bottom: none; color: var(--accent); } 83 - 84 - .signed-nav { 75 + nav { 85 76 display: flex; 77 + gap: 8px; 86 78 align-items: baseline; 87 - gap: 8px; 79 + margin-left: auto; 88 80 font-size: var(--text-small); 89 81 color: var(--fg-mute); 90 - font-variant-numeric: tabular-nums; 91 82 } 92 83 .session-handle { color: var(--accent); } 93 84 .sep { color: var(--fg-mute); opacity: 0.4; } 94 85 95 - .text-btn { 96 - background: transparent; 97 - border: none; 98 - padding: 0; 99 - min-height: 0; 86 + /* ------- handle suggestions ------- */ 87 + .handle-suggestions { 88 + position: absolute; 89 + width: 100%; 90 + max-height: 240px; 91 + overflow-y: auto; 92 + background: var(--bg-2); 100 93 color: var(--fg-mute); 101 - font: inherit; 102 94 font-size: var(--text-small); 95 + border: 1px solid var(--border); 96 + border-top: none; 97 + border-radius: 0 0 var(--radius) var(--radius); 98 + z-index: 10; 99 + top: 100%; 100 + left: 0; 101 + list-style: none; 102 + } 103 + .handle-suggestion { 104 + padding: 8px 14px; 103 105 cursor: pointer; 104 - transition: color 0.15s; 105 106 } 106 - .text-btn:hover { color: var(--fg); } 107 - 108 - /* ------- footer ------- */ 109 - 110 - footer { 111 - display: flex; 112 - justify-content: center; 113 - align-items: baseline; 114 - gap: 8px; 115 - padding: clamp(20px, 4vmin, 40px) var(--pad); 116 - font-size: var(--text-small); 117 - color: var(--fg-mute); 107 + .handle-suggestion:hover, 108 + .handle-suggestion.active { 109 + background: var(--bg-3); 110 + color: var(--fg); 118 111 } 119 - footer .text-btn { color: var(--fg-mute); } 120 - footer .text-btn:hover { color: var(--accent); } 121 - footer a { color: var(--fg-mute); } 122 - footer a:hover { color: var(--accent); } 123 112 124 - /* ------- main ------- */ 125 - 126 - main { 127 - max-width: 820px; 128 - margin: 0 auto; 129 - padding: var(--pad); 130 - padding-bottom: clamp(40px, 8vmin, 80px); 131 - } 113 + /* ------- main / sections ------- */ 114 + main { flex: 1; display: flex; flex-direction: column; align-items: center; } 115 + .section { width: 100%; max-width: 800px; padding: clamp(16px, 3vmin, 40px); } 132 116 133 - .row { 134 - position: relative; 135 - margin-bottom: var(--gap); 117 + /* ------- sign-in form ------- */ 118 + .search-form { 136 119 display: flex; 137 - gap: var(--gap); 120 + gap: clamp(6px, 1vmin, 10px); 121 + position: relative; 138 122 } 139 - 140 - .row input[type="text"], 141 - #search-form input { 123 + .search-form input { 142 124 flex: 1; 143 - background: var(--surface); 125 + background: var(--bg); 144 126 border: 1px solid var(--border); 145 127 color: var(--fg); 146 - padding: clamp(12px, 2vmin, 16px); 128 + padding: clamp(10px, 1.5vmin, 14px) clamp(12px, 2vmin, 16px); 147 129 font: inherit; 148 - /* 16px floor — iOS Safari auto-zooms into any text input rendered 149 - smaller than that, which is obnoxious on phones. max() keeps the 150 - desktop clamp behavior intact when --text-body ever exceeds 16. */ 130 + /* 16px floor stops iOS from zooming into <input>. inherit above sets 131 + * the font-family and weight; the font-size override here only raises 132 + * the floor on phones, it doesn't override the clamp on desktop. */ 151 133 font-size: max(16px, var(--text-body)); 152 134 border-radius: var(--radius); 153 135 outline: none; 154 136 transition: border-color 0.15s; 155 - min-height: 44px; 156 137 } 157 - .row input[type="text"]:focus, 158 - #search-form input:focus { border-color: var(--accent-dim); } 159 - .row input[type="text"]::placeholder, 160 - #search-form input::placeholder { color: var(--fg-mute); } 161 - 162 - /* share view: search input is locked to the shared query. visually 163 - signal that the input is fixed — no focus outline, muted fill, text 164 - is still selectable/copyable but not editable (readonly, not disabled). */ 165 - #search-form input[readonly] { 166 - background: transparent; 167 - border-style: dashed; 168 - border-color: var(--border); 169 - color: var(--fg-mute); 170 - cursor: default; 171 - } 172 - #search-form input[readonly]:focus { 173 - border-color: var(--border); 174 - outline: none; 175 - } 176 - 177 - #signed-out .row button { 138 + .search-form input:focus { border-color: var(--fg-mute); } 139 + .search-form input::placeholder { color: var(--fg-mute); } 140 + .search-form button[type="submit"] { 141 + padding: clamp(8px, 1.5vmin, 14px) clamp(14px, 2vmin, 20px); 178 142 background: var(--accent-dim); 179 143 color: var(--fg); 180 - border-color: var(--accent-dim); 181 - } 182 - #signed-out .row button:hover { 183 - background: var(--accent); 184 - color: var(--bg); 185 - border-color: var(--accent); 186 - } 187 - 188 - /* ------- typeahead ------- */ 189 - 190 - .typeahead { 191 - position: relative; 192 - flex: 1; 193 - display: flex; 194 - } 195 - .typeahead input { flex: 1; } 196 - 197 - .suggestions { 198 - position: absolute; 199 - top: calc(100% + 4px); 200 - left: 0; 201 - right: 0; 202 - list-style: none; 203 - background: var(--surface); 204 - border: 1px solid var(--border); 144 + border: 1px solid var(--accent-dim); 205 145 border-radius: var(--radius); 206 - overflow: hidden; 207 - z-index: 10; 208 - max-height: 320px; 209 - overflow-y: auto; 210 - } 211 - .suggestions li { 212 - display: flex; 213 - align-items: center; 214 - gap: 10px; 215 - padding: clamp(8px, 1.5vmin, 12px) clamp(10px, 2vmin, 14px); 146 + font: inherit; 147 + font-size: var(--text-body); 216 148 cursor: pointer; 217 - font-size: var(--text-small); 218 - border-bottom: 1px solid var(--border); 219 - min-height: 44px; 220 - } 221 - .suggestions li:last-child { border-bottom: none; } 222 - .suggestions li.active, 223 - .suggestions li:hover { background: var(--surface-hover); } 224 - .suggestions img, 225 - .suggestions .avatar-placeholder { 226 - width: 24px; 227 - height: 24px; 228 - border-radius: 50%; 229 - background: var(--border); 230 - flex-shrink: 0; 231 - object-fit: cover; 232 - } 233 - .suggestions .handle { color: var(--fg); } 234 - .suggestions .display { color: var(--fg-mute); } 235 - 236 - /* ------- status / progress ------- */ 237 - 238 - .status { 239 - font-size: var(--text-small); 240 - color: var(--fg-mute); 241 - margin-bottom: var(--gap); 242 - padding: clamp(10px, 1.8vmin, 14px); 243 - background: var(--surface); 244 - border: 1px solid var(--border); 245 - border-radius: var(--radius); 149 + transition: background 0.15s; 246 150 } 247 - .status.error { color: var(--danger); border-color: var(--danger-dim); } 151 + .search-form button:hover { background: var(--accent); color: var(--bg); } 248 152 249 - .progress { 250 - margin-bottom: var(--gap); 251 - padding: clamp(12px, 2vmin, 16px); 252 - background: var(--surface); 253 - border: 1px solid var(--border); 254 - border-radius: var(--radius); 153 + /* ------- progress ------- */ 154 + .progress-wrap { 155 + margin-top: clamp(12px, 2vmin, 20px); 156 + width: 100%; 255 157 } 256 158 .progress-bar { 257 - height: 4px; 159 + height: 3px; 258 160 background: var(--border); 259 161 border-radius: 2px; 260 162 overflow: hidden; 261 - margin-bottom: 10px; 163 + margin-bottom: clamp(6px, 1vmin, 10px); 262 164 } 263 165 .progress-fill { 264 166 height: 100%; 265 - background: var(--accent); 266 167 width: 0%; 267 - transition: width 0.4s ease-out; 268 - position: relative; 269 - } 270 - .progress-fill::after { 271 - content: ""; 272 - position: absolute; 273 - inset: 0; 274 - background: linear-gradient(90deg, transparent, rgba(255,255,255,0.12), transparent); 275 - animation: shimmer 1.4s linear infinite; 276 - } 277 - @keyframes shimmer { 278 - 0% { transform: translateX(-100%); } 279 - 100% { transform: translateX(100%); } 168 + background: var(--accent); 169 + transition: width 0.3s ease; 280 170 } 281 171 .progress-meta { 282 172 display: flex; 283 173 justify-content: space-between; 284 - gap: var(--gap); 285 174 font-size: var(--text-small); 286 - margin-bottom: 6px; 287 - font-variant-numeric: tabular-nums; 288 - flex-wrap: wrap; 175 + color: var(--fg-mute); 289 176 } 290 177 .progress-flavor { 291 178 font-size: var(--text-small); ··· 317 204 } 318 205 .text-btn-danger:hover { color: var(--fg); } 319 206 320 - /* ------- pack menu (single-disclosure popover) ------- 321 - * 322 - * the trigger is a real pill button that always says "search index" 323 - * plus its current state ("saved" / "not saved" / "local only"). the 324 - * subject is explicit so users don't have to wonder "saved what?". 325 - * clicking the pill opens a popover with every action you can take 326 - * on the index (view on PDS, save, delete, share-this-search). 327 - * 328 - * visual weight: the pill has a visible border, padding, and a chevron 329 - * that flips when the popover is open, so it's obviously clickable — 330 - * an earlier iteration used muted text with a 0.75em chevron and was 331 - * borderline invisible. 332 - */ 207 + /* ------- pack menu (inline disclosure) ------- */ 333 208 .pack-menu { 334 209 position: relative; 335 - display: inline-block; 210 + display: inline; 336 211 } 337 212 .pack-menu-trigger { 338 213 list-style: none; 339 214 cursor: pointer; 340 - display: inline-flex; 341 - align-items: center; 342 - gap: 6px; 343 - padding: 5px 10px 5px 12px; 344 - border: 1px solid var(--border); 345 - border-radius: 999px; 346 - background: transparent; 347 - color: var(--fg); 348 - font-size: var(--text-small); 349 - line-height: 1; 350 - transition: border-color 0.15s, background 0.15s; 351 - white-space: nowrap; 215 + display: inline; 216 + font-size: inherit; 217 + color: inherit; 352 218 } 353 219 .pack-menu-trigger::-webkit-details-marker { display: none; } 354 220 .pack-menu-trigger::after { 355 - content: "▾"; 356 - font-size: 0.9em; 357 - opacity: 0.7; 358 - margin-left: 2px; 221 + content: " \25BE"; 222 + font-size: 0.85em; 223 + opacity: 0.5; 359 224 } 360 - .pack-menu-trigger:hover { 361 - border-color: var(--accent-dim); 362 - background: rgba(255, 255, 255, 0.025); 363 - } 364 - .pack-menu[open] .pack-menu-trigger { 365 - border-color: var(--accent); 366 - background: rgba(255, 255, 255, 0.03); 367 - } 368 - .pack-menu[open] .pack-menu-trigger::after { content: "▴"; } 225 + .pack-menu-trigger:hover { color: var(--fg); } 226 + .pack-menu[open] .pack-menu-trigger { color: var(--fg); } 227 + .pack-menu[open] .pack-menu-trigger::after { content: " \25B4"; } 369 228 370 - /* "search index" label is muted, the state word pops */ 371 - .pack-menu-label { 372 - color: var(--fg-mute); 373 - } 374 - .pack-menu-state { 375 - color: var(--accent); 376 - } 229 + #pack-share-btn { margin-left: auto; } 377 230 378 231 .pack-menu-panel { 379 232 position: absolute; 380 233 right: 0; 381 234 top: calc(100% + 8px); 382 - /* wide enough that the description prose wraps to ~3-4 lines in the 383 - * monospace face, not a tall narrow column. the max-width caps it on 384 - * huge viewports so it doesn't spread unreasonably. */ 385 - min-width: 24rem; 386 - max-width: min(32rem, calc(100vw - 32px)); 235 + min-width: 20rem; 236 + max-width: min(28rem, calc(100vw - 32px)); 387 237 padding: 14px 16px; 388 238 background: var(--bg); 389 239 border: 1px solid var(--border); ··· 405 255 padding: 4px 0; 406 256 font-size: var(--text-small); 407 257 } 408 - /* on very narrow screens pin the popover to the viewport so it doesn't 409 - * clip off the right edge of the meta line */ 410 258 @media (max-width: 480px) { 411 259 .pack-menu-panel { 412 260 right: auto; ··· 443 291 #share-modal #share-copy:hover { 444 292 background: var(--accent); 445 293 color: var(--bg); 446 - border-color: var(--accent); 447 294 } 448 295 449 - /* ------- results ------- */ 450 - 451 - .results { 452 - display: flex; 453 - flex-direction: column; 454 - gap: 6px; 296 + /* ------- status banner ------- */ 297 + .status { 298 + margin-top: clamp(8px, 1vmin, 12px); 299 + padding: 8px 12px; 300 + font-size: var(--text-small); 301 + border-radius: var(--radius); 302 + text-align: center; 303 + background: var(--bg-2); 304 + border: 1px solid var(--border); 455 305 } 306 + .status.error { border-color: var(--danger-dim); color: var(--danger); } 456 307 457 - .result { 458 - background: var(--surface); 308 + /* ------- result cards ------- */ 309 + .results { margin-top: clamp(12px, 2vmin, 20px); } 310 + .result-card { 311 + background: var(--bg-2); 459 312 border: 1px solid var(--border); 460 313 border-radius: var(--radius); 461 - padding: clamp(10px, 1.8vmin, 14px); 462 - display: grid; 463 - grid-template-columns: 1fr auto; 464 - gap: 4px 12px; 465 - transition: background 0.1s, border-color 0.1s; 466 - } 467 - .result:hover { 468 - background: var(--surface-hover); 469 - border-color: var(--fg-mute); 314 + padding: clamp(12px, 2vmin, 16px); 315 + margin-bottom: clamp(8px, 1vmin, 12px); 316 + transition: border-color 0.15s; 470 317 } 471 - 472 - .result .head { 473 - grid-column: 1; 318 + .result-card:hover { border-color: var(--fg-mute); } 319 + .result-header { 474 320 display: flex; 321 + justify-content: space-between; 475 322 align-items: baseline; 476 - gap: 10px; 477 - min-width: 0; 323 + margin-bottom: 4px; 324 + gap: 8px; 478 325 } 479 - 480 - .result .collection-chip { 481 - display: inline-flex; 482 - align-items: center; 483 - gap: 6px; 484 - min-width: 0; 326 + .result-icon { 327 + width: 20px; 328 + height: 20px; 329 + border-radius: 3px; 330 + vertical-align: middle; 331 + margin-right: 6px; 485 332 } 486 - .result .collection-name { 333 + .result-collection { 487 334 font-size: var(--text-small); 488 335 color: var(--fg-mute); 489 - white-space: nowrap; 490 - overflow: hidden; 491 - text-overflow: ellipsis; 492 336 } 493 - .result .collection-logo-slot { 494 - width: 14px; 495 - height: 14px; 496 - border-radius: 3px; 497 - background: var(--border); 498 - flex-shrink: 0; 499 - display: inline-flex; 500 - overflow: hidden; 501 - } 502 - .result .collection-logo { 503 - width: 100%; 504 - height: 100%; 505 - object-fit: cover; 506 - } 507 - 508 - .result .date { 337 + .result-date { 509 338 font-size: var(--text-small); 510 339 color: var(--fg-mute); 511 - margin-left: auto; 512 340 white-space: nowrap; 513 341 } 514 - 515 - .result .body { 516 - grid-column: 1; 342 + .result-body { 517 343 font-size: var(--text-body); 518 - line-height: 1.45; 519 - display: -webkit-box; 520 - -webkit-line-clamp: 2; 521 - -webkit-box-orient: vertical; 522 - overflow: hidden; 523 - word-break: break-word; 344 + color: var(--fg); 345 + margin-top: 2px; 524 346 } 525 - 526 - .result .body-second { 527 - grid-column: 1; 347 + .result-link { 528 348 font-size: var(--text-small); 529 349 color: var(--fg-mute); 530 - line-height: 1.45; 531 - display: -webkit-box; 532 - -webkit-line-clamp: 2; 533 - -webkit-box-orient: vertical; 534 - overflow: hidden; 535 - word-break: break-word; 350 + display: inline-block; 351 + margin-top: 4px; 536 352 } 537 353 538 - .result .actions { 539 - grid-column: 2; 540 - grid-row: 1 / span 3; 541 - display: flex; 542 - align-items: start; 543 - } 544 - .result .pdsls-link { 545 - color: var(--fg-mute); 546 - font-size: var(--text-small); 547 - padding: 4px 8px; 548 - border-radius: 4px; 549 - border: 1px solid transparent; 550 - } 551 - .result .pdsls-link:hover { 552 - color: var(--accent); 553 - border-color: var(--border); 554 - } 555 - 556 - /* ------- about modal ------- */ 557 - 354 + /* ------- modal ------- */ 558 355 .overlay { 559 356 position: fixed; 560 357 inset: 0; 561 358 background: rgba(0, 0, 0, 0.6); 562 - z-index: 100; 359 + z-index: 50; 563 360 } 564 - 565 361 .modal { 566 362 position: fixed; 567 363 top: 50%; 568 364 left: 50%; 569 365 transform: translate(-50%, -50%); 570 - background: var(--surface); 366 + background: var(--bg-2); 571 367 border: 1px solid var(--border); 572 368 border-radius: var(--radius); 573 - padding: clamp(20px, 4vmin, 32px); 574 - max-width: min(92vw, 460px); 575 - z-index: 101; 576 - } 577 - .modal h2 { 578 - font-size: var(--text-h); 579 - margin-bottom: clamp(10px, 2vmin, 14px); 580 - color: var(--fg); 581 - font-weight: 500; 369 + padding: clamp(20px, 3vmin, 30px); 370 + z-index: 51; 371 + max-width: 500px; 372 + width: calc(100% - 32px); 373 + max-height: calc(100vh - 40px); 374 + overflow-y: auto; 582 375 } 583 - .modal p { 584 - font-size: var(--text-small); 585 - line-height: 1.6; 586 - color: var(--fg); 587 - margin-bottom: clamp(8px, 1.5vmin, 12px); 588 - } 589 - .modal p:last-of-type { color: var(--fg-mute); } 590 - .modal ul.about-links { 591 - list-style: none; 592 - margin: clamp(14px, 2.5vmin, 20px) 0; 593 - display: flex; 594 - flex-direction: column; 595 - gap: 6px; 596 - } 597 - .modal ul.about-links a { font-size: var(--text-small); } 598 - .modal button { 599 - margin-top: clamp(10px, 2vmin, 14px); 600 - font-size: var(--text-small); 376 + .modal h2 { font-size: var(--hero-size); margin-bottom: 12px; } 377 + .modal p { color: var(--fg-mute); font-size: var(--text-small); margin-bottom: 8px; } 378 + .about-links { list-style: none; margin-top: 12px; } 379 + .about-links li { margin-bottom: 6px; } 380 + .about-links a { font-size: var(--text-small); } 381 + #about-close { 382 + display: block; 383 + margin-top: 16px; 384 + width: 100%; 385 + padding: 8px; 386 + border: 1px solid var(--border); 387 + border-radius: var(--radius); 601 388 } 602 389 603 - /* ------- mobile layout tweaks ------- */ 604 - 605 - @media (max-width: 520px) { 606 - header { gap: 8px; } 607 - .stats { flex-basis: 100%; order: 5; margin-left: 0; } 608 - #signed-out .row { flex-direction: column; } 609 - #signed-out .row button { width: 100%; } 610 - .pack-actions-row { flex-direction: column; align-items: stretch; } 611 - .pack-btn { width: 100%; } 612 - .result { 613 - grid-template-columns: 1fr; 614 - } 615 - .result .actions { 616 - grid-column: 1; 617 - grid-row: auto; 618 - justify-content: flex-end; 619 - } 390 + /* ------- footer ------- */ 391 + footer { 392 + text-align: center; 393 + padding: clamp(16px, 3vmin, 24px); 394 + font-size: var(--text-small); 395 + color: var(--fg-mute); 620 396 }