Discover books, shows, and movies at your level. Track your progress by filling your Shelf with what you find, and share with other language learners. *No dusting required. shlf.space
4
fork

Configure Feed

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

feat(ui): first-pass design for login screen

Signed-off-by: brookjeynes <me@brookjeynes.dev>

authored by

brookjeynes and committed by
Tangled
04203581 0a782306

+613 -90
+94 -26
input.css
··· 1 1 @import "tailwindcss"; 2 2 @import "./internal/ui/views/shelf/shelf.css"; 3 + @import "./internal/ui/views/login/login.css"; 4 + 5 + @font-face { 6 + font-family: 'Epilogue'; 7 + font-style: normal; 8 + font-weight: 100 900; 9 + font-display: swap; 10 + src: url('/static/fonts/epilogue.woff2') format('woff2'); 11 + } 12 + 13 + @font-face { 14 + font-family: 'Caveat'; 15 + font-style: normal; 16 + font-weight: 400 700; 17 + font-display: swap; 18 + src: url('/static/fonts/caveat.woff2') format('woff2'); 19 + } 3 20 4 21 @theme { 5 - --color-primary: #1a1a1a; 6 - --color-secondary: #757575; 7 - --color-neutral: #fdfbf7; 22 + --color-canvas: #fbf9f5; 23 + --color-ink: #1a1a1a; 24 + --color-tonal: #f5f4ef; 25 + --color-dim: #5f5e5e; 26 + --color-accent: #b1b3ab; 27 + --color-hover: #e8e9e2; 28 + 29 + --font-primary: 'Epilogue', sans-serif; 30 + --font-accent: 'Caveat', cursive; 31 + } 32 + 33 + body { 34 + display: flex; 35 + flex-direction: column; 36 + font-family: var(--font-primary); 37 + background-color: var(--color-canvas); 38 + color: var(--color-ink); 8 39 } 9 40 10 41 .container { ··· 13 44 padding: 2rem 1.125rem; 14 45 } 15 46 16 - @layer components { 17 - .button { 18 - display: flex; 19 - align-items: center; 20 - gap: theme(gap.2); 21 - cursor: pointer; 22 - border-width: 1px; 23 - padding: theme(spacing.1) theme(spacing.2); 47 + .button { 48 + display: flex; 49 + align-items: center; 50 + justify-content: center; 51 + gap: theme(spacing.2); 52 + cursor: pointer; 53 + padding: theme(spacing.3) theme(spacing.2); 24 54 25 - &:hover { 26 - background-color: var(--color-secondary); 27 - } 55 + &:hover { 56 + background-color: var(--color-hover); 57 + color: var(--color-ink); 58 + } 28 59 29 - &:disabled { 30 - opacity: 0.5; 31 - cursor: not-allowed; 32 - pointer-events: none; 33 - } 60 + &:active { 61 + transform: scale(0.98); 34 62 } 35 63 36 - .input { 37 - padding: theme(spacing.1) theme(spacing.2); 38 - border-width: 1px; 39 - font-size: theme(text.sm); 64 + &:disabled { 65 + opacity: 0.5; 66 + cursor: not-allowed; 67 + pointer-events: none; 68 + } 69 + } 40 70 41 - &::placeholder { 42 - opacity: 0.75; 43 - } 71 + .button-primary { 72 + background-color: var(--color-ink); 73 + color: var(--color-canvas); 74 + font-weight: bold; 75 + font-family: var(--font-primary); 76 + 77 + &:hover { 78 + background-color: var(--color-dim); 79 + color: var(--color-canvas); 44 80 } 45 81 } 82 + 83 + .input { 84 + padding: theme(spacing.2) theme(spacing.4); 85 + background-color: var(--color-tonal); 86 + font-size: var(--text-sm); 87 + font-family: var(--font-primary); 88 + 89 + &::placeholder { 90 + opacity: 0.75; 91 + } 92 + 93 + &:focus { 94 + outline: 1px solid var(--color-ink); 95 + outline-offset: -2px; 96 + } 97 + } 98 + 99 + .spinner { 100 + width: 1rem; 101 + height: 1rem; 102 + display: none; 103 + animation: spin 1s linear infinite; 104 + } 105 + 106 + @keyframes spin { 107 + from { rotate: 0deg; } 108 + to { rotate: 360deg; } 109 + } 110 + 111 + .htmx-request .spinner { 112 + display: inline; 113 + }
+5 -5
internal/ui/components/header/header.templ
··· 10 10 if params.User != nil { 11 11 <details class="relative inline-block text-left"> 12 12 <summary class="cursor-pointer list-none flex gap-2 items-center"> 13 - <div class="flex items-center justify-center w-7 h-7 rounded-full bg-secondary"> 13 + <div class="flex items-center justify-center w-7 h-7 rounded-full bg-dim"> 14 14 <i class="w-4 h-4" data-lucide="user"></i> 15 15 </div> 16 16 <span class="text-sm">{ params.User.Account.Handle }</span> 17 17 </summary> 18 - <div class="absolute flex flex-col right-0 mt-2 p-1 gap-1 w-48 bg-neutral drop-shadow-sm"> 18 + <div class="absolute flex flex-col right-0 mt-2 p-1 gap-1 w-48 bg-tonal drop-shadow-sm"> 19 19 <a 20 20 href={ templ.SafeURL(fmt.Sprintf("/%s/shelf", params.User.Account.Did)) } 21 - class="button border-none text-sm w-full" 21 + class="button p-2 justify-start text-sm w-full" 22 22 > 23 23 My shelf 24 24 </a> 25 25 <hr/> 26 - <button type="button" hx-post="/logout" hx-swap="none" class="button border-none text-sm w-full"> 26 + <button type="button" hx-post="/logout" hx-swap="none" class="button p-2 justify-start text-sm w-full"> 27 27 <i class="w-4 h-4" data-lucide="log-out"></i> 28 28 Log out 29 29 </button> ··· 33 33 <summary class="cursor-pointer list-none flex gap-2 items-center"> 34 34 <span>🇰🇷</span> 35 35 </summary> 36 - <div class="absolute flex flex-col right-0 mt-2 p-1 gap-1 w-48 bg-neutral drop-shadow-sm"> 36 + <div class="absolute flex flex-col right-0 mt-2 p-1 gap-1 w-48 bg-tonal drop-shadow-sm"> 37 37 <button 38 38 type="button" 39 39 @click="localStorage.setItem('shlf-language', 'ko');"
+3 -1
internal/ui/layouts/base/base.templ
··· 7 7 <meta charset="UTF-8"/> 8 8 <meta name="viewport" content="width=device-width, initial-scale=1.0"/> 9 9 <title>shlf.space - { params.Title }</title> 10 + <link rel="preload" href="/static/fonts/epilogue.woff2" as="font" type="font/woff2" crossorigin/> 11 + <link rel="preload" href="/static/fonts/caveat.woff2" as="font" type="font/woff2" crossorigin/> 10 12 <script src="/static/js/htmx.min.js" defer></script> 11 13 <script src="/static/js/lucide.min.js"></script> 12 14 <script src="/static/js/alpinejs.min.js" defer></script> 13 15 <link rel="stylesheet" href="/static/style.css" type="text/css"/> 14 16 </head> 15 17 <body class="min-h-screen"> 16 - <main> 18 + <main class="flex-1 flex flex-col text-ink"> 17 19 { children... } 18 20 </main> 19 21 </body>
+278
internal/ui/views/login/login.css
··· 1 + .login-col-panel { 2 + position: relative; 3 + padding: 2.5rem 1.25rem; 4 + width: 100%; 5 + max-width: 28rem; 6 + height: 25rem; 7 + 8 + @media (min-width: 640px) { 9 + padding-inline: 3rem; 10 + height: 40rem; 11 + } 12 + } 13 + 14 + .login-page { 15 + display: flex; 16 + flex: 1; 17 + flex-direction: column; 18 + align-items: center; 19 + justify-content: center; 20 + padding: 4rem 1rem; 21 + 22 + @media (min-width: 640px) { 23 + flex-direction: row; 24 + } 25 + } 26 + 27 + .login-panel { 28 + background: #ffffff; 29 + display: flex; 30 + flex-direction: column; 31 + justify-content: center; 32 + 33 + h1 { 34 + font-size: 2.25rem; 35 + font-weight: bold; 36 + margin-bottom: 2rem; 37 + 38 + @media (min-width: 640px) { 39 + margin-bottom: 3rem; 40 + } 41 + } 42 + } 43 + 44 + .login-how-btn { 45 + position: absolute; 46 + top: -2rem; 47 + right: 0; 48 + font-family: var(--font-accent); 49 + font-size: 1rem; 50 + text-decoration: underline; 51 + text-decoration-style: dotted; 52 + cursor: pointer; 53 + rotate: 5deg; 54 + 55 + @media (min-width: 640px) { 56 + display: none; 57 + } 58 + } 59 + 60 + .spinner { 61 + width: 1rem; 62 + height: 1rem; 63 + display: none; 64 + animation: spin 1s linear infinite; 65 + } 66 + 67 + @keyframes spin { 68 + from { rotate: 0deg; } 69 + to { rotate: 360deg; } 70 + } 71 + 72 + .htmx-request .spinner { 73 + display: inline; 74 + } 75 + 76 + .explainer-panel { 77 + background: var(--color-tonal); 78 + display: none; 79 + flex-direction: column; 80 + justify-content: space-between; 81 + 82 + @media (min-width: 640px) { 83 + display: flex; 84 + } 85 + } 86 + 87 + .explainer-label { 88 + position: absolute; 89 + top: 1.5rem; 90 + right: 1.5rem; 91 + font-size: 0.875rem; 92 + color: var(--color-dim); 93 + font-family: var(--font-accent); 94 + rotate: 2deg; 95 + user-select: none; 96 + display: none; 97 + 98 + @media (min-width: 640px) { 99 + display: block; 100 + } 101 + } 102 + 103 + .explainer-body { 104 + flex: 1; 105 + display: flex; 106 + flex-direction: column; 107 + justify-content: center; 108 + min-height: 0; 109 + font-size: 0.875rem; 110 + 111 + h2 { 112 + font-size: 1.875rem; 113 + font-weight: bold; 114 + margin-bottom: 1rem; 115 + line-height: 1.25; 116 + } 117 + 118 + & > div > p { 119 + color: var(--color-dim); 120 + margin-bottom: 2rem; 121 + line-height: 1.625; 122 + } 123 + } 124 + 125 + .explainer-list { 126 + display: flex; 127 + flex-direction: column; 128 + gap: 1rem; 129 + list-style: none; 130 + padding: 0; 131 + margin: 0; 132 + 133 + li { 134 + display: flex; 135 + gap: 0.75rem; 136 + 137 + i { 138 + margin-top: 0.125rem; 139 + flex-shrink: 0; 140 + width: 1.25rem; 141 + height: 1.25rem; 142 + color: var(--color-dim); 143 + } 144 + } 145 + } 146 + 147 + .explainer-handle-card-label { 148 + font-size: 0.75rem; 149 + color: var(--color-dim); 150 + text-transform: uppercase; 151 + letter-spacing: 0.05em; 152 + } 153 + 154 + .explainer-provider { 155 + background: #ffffff; 156 + padding: 0.75rem 1rem; 157 + display: flex; 158 + align-items: center; 159 + gap: 0.75rem; 160 + text-decoration: none; 161 + color: inherit; 162 + 163 + &:hover { 164 + background: var(--color-hover); 165 + 166 + i { 167 + color: var(--color-ink); 168 + translate: 0.125rem 0; 169 + } 170 + } 171 + 172 + img { 173 + width: 1.25rem; 174 + height: 1.25rem; 175 + flex-shrink: 0; 176 + } 177 + 178 + i { 179 + width: 1rem; 180 + height: 1rem; 181 + color: var(--color-dim); 182 + } 183 + } 184 + 185 + .explainer-nav { 186 + display: flex; 187 + align-items: center; 188 + justify-content: space-between; 189 + margin-top: 2.5rem; 190 + padding-top: 1.5rem; 191 + border-top: 1px solid color-mix(in srgb, var(--color-ink) 10%, transparent); 192 + } 193 + 194 + .dot-active, 195 + .dot-inactive { 196 + border-radius: 9999px; 197 + cursor: pointer; 198 + } 199 + 200 + .dot-active { 201 + width: 1rem; 202 + height: 0.375rem; 203 + background: var(--color-ink); 204 + } 205 + 206 + .dot-inactive { 207 + width: 0.375rem; 208 + height: 0.375rem; 209 + background: color-mix(in srgb, var(--color-dim) 30%, transparent); 210 + } 211 + 212 + .explainer-nav-btn { 213 + width: 2rem; 214 + height: 2rem; 215 + display: flex; 216 + align-items: center; 217 + justify-content: center; 218 + border: 1px solid color-mix(in srgb, var(--color-ink) 20%, transparent); 219 + background: none; 220 + cursor: pointer; 221 + 222 + &:hover { 223 + border-color: color-mix(in srgb, var(--color-ink) 60%, transparent); 224 + } 225 + 226 + &:disabled { 227 + opacity: 0.3; 228 + cursor: not-allowed; 229 + } 230 + 231 + i { 232 + width: 1rem; 233 + height: 1rem; 234 + } 235 + } 236 + 237 + .mobile-drawer-overlay { 238 + position: fixed; 239 + inset: 0; 240 + z-index: 50; 241 + 242 + @media (min-width: 640px) { 243 + display: none !important; 244 + } 245 + } 246 + 247 + .mobile-drawer-backdrop { 248 + position: absolute; 249 + inset: 0; 250 + background: color-mix(in srgb, var(--color-ink) 40%, transparent); 251 + } 252 + 253 + .mobile-drawer-sheet { 254 + position: absolute; 255 + bottom: 0; 256 + inset-inline: 0; 257 + background: var(--color-tonal); 258 + display: flex; 259 + flex-direction: column; 260 + max-height: 85dvh; 261 + overflow-y: auto; 262 + } 263 + 264 + .mobile-drawer-close-btn { 265 + color: var(--color-dim); 266 + background: none; 267 + border: none; 268 + cursor: pointer; 269 + 270 + &:hover { 271 + color: var(--color-ink); 272 + } 273 + 274 + i { 275 + width: 1.25rem; 276 + height: 1.25rem; 277 + } 278 + }
+214 -39
internal/ui/views/login/login.templ
··· 4 4 5 5 templ LoginPage(params LoginPageParams) { 6 6 @layouts.Base(layouts.BaseParams{Title: "login"}) { 7 - <div class="container"> 8 - <form class="group flex flex-col gap-2" hx-post="/login" hx-swap="none" hx-disabled-elt="#login-button"> 9 - <label> 10 - <span>Handle</span> 11 - <input 12 - class="input" 13 - id="handle" 14 - name="handle" 15 - type="text" 16 - placeholder="username.bsky.social" 17 - autocapitalize="none" 18 - autocorrect="off" 19 - autocomplete="username" 20 - required 21 - tabindex="1" 22 - /> 23 - </label> 24 - <input type="hidden" name="return_url" value={ params.ReturnUrl }/> 25 - <button type="submit" id="login-button" tabindex="2" class="button self-end"> 26 - <i class="w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" data-lucide="loader-circle"></i> 27 - <span>Login</span> 7 + <div 8 + class="login-page" 9 + x-data="{ open: false, slide: 0, slides: 3 }" 10 + > 11 + <div class="login-col-panel login-panel"> 12 + <button 13 + @click="open = true; slide = 0" 14 + class="login-how-btn" 15 + aria-expanded="false" 16 + :aria-expanded="open.toString()" 17 + aria-controls="explainer-drawer" 18 + > 19 + How it works? 28 20 </button> 29 - </form> 30 - <div data-field="error" class="text-red-500"> 31 - if params.ErrorCode != "" { 32 - <p class="error"> 33 - switch (params.ErrorCode) { 34 - case "access_denied": 35 - You have not authorized the app. 36 - case "session": 37 - Server failed to create user session. 38 - case "handle": 39 - Server failed to validate your handle. 40 - default: 41 - Internal Server error. 42 - } 43 - Please try again. 44 - </p> 45 - } 46 - <p id="login-msg"></p> 21 + <h1>Login</h1> 22 + <form 23 + style="display:flex; flex-direction:column; gap:2rem" 24 + hx-post="/login" 25 + hx-swap="none" 26 + hx-disabled-elt="#login-button" 27 + aria-label="Login form" 28 + > 29 + <label for="handle" style="display:flex; flex-direction:column; gap:0.5rem"> 30 + <span style="font-size:0.875rem">YOUR HANDLE</span> 31 + <actor-typeahead> 32 + <input 33 + class="input" 34 + style="width:100%" 35 + id="handle" 36 + name="handle" 37 + type="text" 38 + placeholder="you.service.xyz" 39 + autocapitalize="none" 40 + autocorrect="off" 41 + autocomplete="username" 42 + required 43 + tabindex="1" 44 + aria-required="true" 45 + aria-describedby="login-msg" 46 + /> 47 + </actor-typeahead> 48 + </label> 49 + <input type="hidden" name="return_url" value={ params.ReturnUrl }/> 50 + <button 51 + type="submit" 52 + id="login-button" 53 + tabindex="2" 54 + class="button button-primary" 55 + aria-label="Login" 56 + > 57 + <i class="spinner" data-lucide="loader-circle" aria-hidden="true"></i> 58 + <span aria-hidden="true">Login</span> 59 + <i data-lucide="arrow-right" aria-hidden="true"></i> 60 + </button> 61 + </form> 62 + <div 63 + data-field="error" 64 + class="login-error" 65 + role="alert" 66 + aria-live="assertive" 67 + aria-atomic="true" 68 + > 69 + if params.ErrorCode != "" { 70 + <p class="error"> 71 + switch (params.ErrorCode) { 72 + case "access_denied": 73 + You have not authorized the app. 74 + case "session": 75 + Server failed to create user session. 76 + case "handle": 77 + Server failed to validate your handle. 78 + default: 79 + Internal Server error. 80 + } 81 + Please try again. 82 + </p> 83 + } 84 + <p id="login-msg" aria-live="polite" aria-atomic="true"></p> 85 + </div> 86 + </div> 87 + <div class="login-col-panel explainer-panel" aria-hidden="true"> 88 + @explainerContent() 89 + </div> 90 + <div 91 + x-show="open" 92 + class="mobile-drawer-overlay" 93 + role="dialog" 94 + aria-modal="true" 95 + aria-label="How it works" 96 + id="explainer-drawer" 97 + style="display:none" 98 + > 99 + <div class="mobile-drawer-backdrop" @click="open = false"></div> 100 + <div class="mobile-drawer-sheet"> 101 + <div style="display:flex; justify-content:flex-end; padding:1.25rem 0.5rem"> 102 + <button 103 + @click="open = false" 104 + class="mobile-drawer-close-btn" 105 + aria-label="Close" 106 + > 107 + <i data-lucide="x" aria-hidden="true"></i> 108 + </button> 109 + </div> 110 + <div style="padding:0 1.25rem 2rem"> 111 + @explainerContent() 112 + </div> 113 + </div> 47 114 </div> 48 115 </div> 116 + <script type="module" src="/static/js/actor-typeahead.js"></script> 49 117 } 50 118 } 119 + 120 + templ explainerContent() { 121 + <span class="explainer-label" aria-hidden="true">ATmosphere</span> 122 + <div 123 + class="explainer-body" 124 + aria-live="polite" 125 + aria-atomic="true" 126 + aria-label="Explainer" 127 + > 128 + <div x-show="slide === 0" role="group" aria-label="Slide 1 of 3: The Open Social Web"> 129 + <h2>The Open<br/>Social Web</h2> 130 + <p>shlf.space is part of the ATmosphere, a decentralized social network where you own your identity.</p> 131 + <ul class="explainer-list"> 132 + <li> 133 + <i data-lucide="shield" aria-hidden="true"></i> 134 + <span>No single company controls your account or data</span> 135 + </li> 136 + <li> 137 + <i data-lucide="mail" aria-hidden="true"></i> 138 + <span>Like email, the ATmosphere is open, interoperable, and portable</span> 139 + </li> 140 + <li> 141 + <i data-lucide="share-2" aria-hidden="true"></i> 142 + <span>Bluesky, shlf.space, Tangled, and more all share this network</span> 143 + </li> 144 + </ul> 145 + </div> 146 + <div x-show="slide === 1" style="display:none" role="group" aria-label="Slide 2 of 3: Your Internet Handle"> 147 + <h2>Your Internet<br/>Handle</h2> 148 + <p>Your handle is your unique identity across the whole ATmosphere.</p> 149 + <div style="display:flex; flex-direction:column; gap:0.75rem"> 150 + <div style="background:#fff; padding:0.75rem 1rem; display:flex; flex-direction:column; gap:0.25rem"> 151 + <span class="explainer-handle-card-label">Example</span> 152 + <span style="font-family:monospace; font-weight:500">you.bsky.social</span> 153 + </div> 154 + <div style="background:#fff; padding:0.75rem 1rem; display:flex; flex-direction:column; gap:0.25rem"> 155 + <span class="explainer-handle-card-label">Your own domain</span> 156 + <span style="font-family:monospace; font-weight:500">yourname.com</span> 157 + </div> 158 + <div style="background:#fff; padding:0.75rem 1rem"> 159 + <span>Switch between apps without creating new accounts</span> 160 + </div> 161 + </div> 162 + </div> 163 + <div x-show="slide === 2" style="display:none" role="group" aria-label="Slide 3 of 3: Create Your Account"> 164 + <h2>Create Your<br/>Account</h2> 165 + <p>Sign up with any ATmosphere provider. Your handle works on shlf.space straight away.</p> 166 + <div style="display:flex; flex-direction:column; gap:0.5rem"> 167 + <a href="https://bsky.app" target="_blank" rel="noopener noreferrer" class="explainer-provider" aria-label="Bluesky — bsky.app (opens in new tab)"> 168 + <img src="https://www.google.com/s2/favicons?domain=bsky.app&sz=32" alt="" aria-hidden="true"/> 169 + <div style="display:flex; flex-direction:column; flex:1"> 170 + <span style="font-weight:500; font-size:0.875rem">Bluesky</span> 171 + <span style="font-size:0.75rem; color:var(--color-dim)">bsky.app</span> 172 + </div> 173 + <i data-lucide="arrow-right" aria-hidden="true"></i> 174 + </a> 175 + <a href="https://tangled.sh" target="_blank" rel="noopener noreferrer" class="explainer-provider" aria-label="Tangled — tangled.sh (opens in new tab)"> 176 + <img src="https://www.google.com/s2/favicons?domain=tangled.sh&sz=32" alt="" aria-hidden="true"/> 177 + <div style="display:flex; flex-direction:column; flex:1"> 178 + <span style="font-weight:500; font-size:0.875rem">Tangled</span> 179 + <span style="font-size:0.75rem; color:var(--color-dim)">tangled.sh</span> 180 + </div> 181 + <i data-lucide="arrow-right" aria-hidden="true"></i> 182 + </a> 183 + <a href="https://pckt.blog" target="_blank" rel="noopener noreferrer" class="explainer-provider" aria-label="pckt — pckt.blog (opens in new tab)"> 184 + <img src="https://www.google.com/s2/favicons?domain=pckt.blog&sz=32" alt="" aria-hidden="true"/> 185 + <div style="display:flex; flex-direction:column; flex:1"> 186 + <span style="font-weight:500; font-size:0.875rem">pckt</span> 187 + <span style="font-size:0.75rem; color:var(--color-dim)">pckt.blog</span> 188 + </div> 189 + <i data-lucide="arrow-right" aria-hidden="true"></i> 190 + </a> 191 + </div> 192 + </div> 193 + </div> 194 + <nav class="explainer-nav" aria-label="Slide navigation"> 195 + <div style="display:flex; gap:0.5rem; align-items:center" role="tablist" aria-label="Slides"> 196 + <template x-for="i in slides" :key="i"> 197 + <button 198 + @click="slide = i - 1" 199 + :class="slide === i - 1 ? 'dot-active' : 'dot-inactive'" 200 + :aria-label="`Go to slide ${i}`" 201 + :aria-selected="(slide === i - 1).toString()" 202 + role="tab" 203 + ></button> 204 + </template> 205 + </div> 206 + <div style="display:flex; gap:0.5rem"> 207 + <button 208 + @click="slide = Math.max(0, slide - 1)" 209 + :disabled="slide === 0" 210 + class="explainer-nav-btn" 211 + aria-label="Previous slide" 212 + > 213 + <i data-lucide="arrow-left" aria-hidden="true"></i> 214 + </button> 215 + <button 216 + @click="slide = Math.min(slides - 1, slide + 1)" 217 + :disabled="slide === slides - 1" 218 + class="explainer-nav-btn" 219 + aria-label="Next slide" 220 + > 221 + <i data-lucide="arrow-right" aria-hidden="true"></i> 222 + </button> 223 + </div> 224 + </nav> 225 + }
+19 -19
internal/ui/views/shelf/shelf.css
··· 4 4 5 5 > :first-child, 6 6 > :last-child { 7 - background: var(--color-primary); 7 + background: var(--color-ink); 8 8 } 9 9 } 10 10 11 11 .shelf-base { 12 12 display: grid; 13 13 grid-template-columns: repeat(auto-fill, minmax(2rem, 1fr)); 14 - background: var(--color-primary); 15 - border-block: 2px solid var(--color-primary); 14 + background: var(--color-ink); 15 + border-block: 2px solid var(--color-ink); 16 16 row-gap: 8px; 17 17 min-height: 25rem; 18 18 } ··· 39 39 40 40 @layer components { 41 41 .item-slot { 42 - background: var(--color-neutral); 43 - border: 1px dashed var(--color-secondary); 42 + background: var(--color-tonal); 43 + border: 1px dashed var(--color-dim); 44 44 border-bottom: none; 45 45 display: flex; 46 46 align-items: flex-end; ··· 50 50 padding-top: 1rem; 51 51 52 52 &:hover { 53 - background: var(--color-secondary); 53 + background: var(--color-accent); 54 54 } 55 55 56 56 &.slot-occupied { ··· 58 58 } 59 59 60 60 &.slot-drag-over { 61 - background: var(--color-secondary); 62 - border: 2px solid var(--color-primary); 61 + background: var(--color-accent); 62 + border: 2px solid var(--color-ink); 63 63 border-bottom: none; 64 64 } 65 65 } 66 66 67 67 .book { 68 - background: var(--color-neutral); 68 + background: var(--color-tonal); 69 69 width: 2rem; 70 70 cursor: grab; 71 71 flex-shrink: 0; ··· 88 88 gap: 0.625rem; 89 89 position: fixed; 90 90 width: 17.5rem; 91 - background: var(--color-neutral); 92 - border: 1px solid var(--color-primary); 91 + background: var(--color-tonal); 92 + border: 1px solid var(--color-ink); 93 93 padding: theme(spacing.3); 94 94 z-index: 100; 95 95 pointer-events: none; ··· 100 100 font-weight: 300; 101 101 102 102 h2 { 103 - font-size: theme(text.base); 103 + font-size: var(--text-base); 104 104 font-weight: 600; 105 105 margin-bottom: 0.125rem; 106 106 } 107 107 108 108 h4 { 109 - font-size: theme(text.sm); 109 + font-size: var(--text-sm); 110 110 margin-bottom: theme(spacing.2); 111 111 } 112 112 113 113 p { 114 - font-size: theme(text.xs); 114 + font-size: var(--text-xs); 115 115 } 116 116 } 117 117 ··· 125 125 inset: 0; 126 126 background: repeating-linear-gradient( 127 127 45deg, 128 - var(--color-secondary), 129 - var(--color-secondary) 2px, 130 - var(--color-neutral) 2px, 131 - var(--color-neutral) 8px 128 + var(--color-dim), 129 + var(--color-dim) 2px, 130 + var(--color-tonal) 2px, 131 + var(--color-tonal) 8px 132 132 ); 133 - border: 1px dashed var(--color-secondary); 133 + border: 1px dashed var(--color-dim); 134 134 cursor: grab; 135 135 } 136 136