From 042035814e471dec152eac2c23ce1d341ad67a72 Mon Sep 17 00:00:00 2001 From: brookjeynes Date: Sat, 18 Apr 2026 21:48:24 +1000 Subject: [PATCH] feat(ui): first-pass design for login screen Signed-off-by: brookjeynes --- input.css | 124 ++++++--- internal/ui/components/header/header.templ | 10 +- internal/ui/layouts/base/base.templ | 4 +- internal/ui/views/login/login.css | 278 +++++++++++++++++++++ internal/ui/views/login/login.templ | 253 ++++++++++++++++--- internal/ui/views/shelf/shelf.css | 38 +-- 6 files changed, 615 insertions(+), 92 deletions(-) create mode 100644 internal/ui/views/login/login.css diff --git a/input.css b/input.css index 7041f3a..83c8592 100644 --- a/input.css +++ b/input.css @@ -1,10 +1,41 @@ @import "tailwindcss"; @import "./internal/ui/views/shelf/shelf.css"; +@import "./internal/ui/views/login/login.css"; + +@font-face { + font-family: 'Epilogue'; + font-style: normal; + font-weight: 100 900; + font-display: swap; + src: url('/static/fonts/epilogue.woff2') format('woff2'); +} + +@font-face { + font-family: 'Caveat'; + font-style: normal; + font-weight: 400 700; + font-display: swap; + src: url('/static/fonts/caveat.woff2') format('woff2'); +} @theme { - --color-primary: #1a1a1a; - --color-secondary: #757575; - --color-neutral: #fdfbf7; + --color-canvas: #fbf9f5; + --color-ink: #1a1a1a; + --color-tonal: #f5f4ef; + --color-dim: #5f5e5e; + --color-accent: #b1b3ab; + --color-hover: #e8e9e2; + + --font-primary: 'Epilogue', sans-serif; + --font-accent: 'Caveat', cursive; +} + +body { + display: flex; + flex-direction: column; + font-family: var(--font-primary); + background-color: var(--color-canvas); + color: var(--color-ink); } .container { @@ -13,33 +44,70 @@ padding: 2rem 1.125rem; } -@layer components { - .button { - display: flex; - align-items: center; - gap: theme(gap.2); - cursor: pointer; - border-width: 1px; - padding: theme(spacing.1) theme(spacing.2); - - &:hover { - background-color: var(--color-secondary); - } - - &:disabled { - opacity: 0.5; - cursor: not-allowed; - pointer-events: none; - } +.button { + display: flex; + align-items: center; + justify-content: center; + gap: theme(spacing.2); + cursor: pointer; + padding: theme(spacing.3) theme(spacing.2); + + &:hover { + background-color: var(--color-hover); + color: var(--color-ink); + } + + &:active { + transform: scale(0.98); } - .input { - padding: theme(spacing.1) theme(spacing.2); - border-width: 1px; - font-size: theme(text.sm); + &:disabled { + opacity: 0.5; + cursor: not-allowed; + pointer-events: none; + } +} + +.button-primary { + background-color: var(--color-ink); + color: var(--color-canvas); + font-weight: bold; + font-family: var(--font-primary); - &::placeholder { - opacity: 0.75; - } + &:hover { + background-color: var(--color-dim); + color: var(--color-canvas); } } + +.input { + padding: theme(spacing.2) theme(spacing.4); + background-color: var(--color-tonal); + font-size: var(--text-sm); + font-family: var(--font-primary); + + &::placeholder { + opacity: 0.75; + } + + &:focus { + outline: 1px solid var(--color-ink); + outline-offset: -2px; + } +} + +.spinner { + width: 1rem; + height: 1rem; + display: none; + animation: spin 1s linear infinite; +} + +@keyframes spin { + from { rotate: 0deg; } + to { rotate: 360deg; } +} + +.htmx-request .spinner { + display: inline; +} diff --git a/internal/ui/components/header/header.templ b/internal/ui/components/header/header.templ index 5ee05f6..8a8d4ea 100644 --- a/internal/ui/components/header/header.templ +++ b/internal/ui/components/header/header.templ @@ -10,20 +10,20 @@ templ Header(params HeaderParams) { if params.User != nil {
-
+
{ params.User.Account.Handle }
-
+
My shelf
- @@ -33,7 +33,7 @@ templ Header(params HeaderParams) { 🇰🇷 -
+
- -
- if params.ErrorCode != "" { -

- switch (params.ErrorCode) { - case "access_denied": - You have not authorized the app. - case "session": - Server failed to create user session. - case "handle": - Server failed to validate your handle. - default: - Internal Server error. - } - Please try again. -

- } -

+

Login

+
+ + + +
+ +
+ +
+ } } + +templ explainerContent() { + +
+
+

The Open
Social Web

+

shlf.space is part of the ATmosphere, a decentralized social network where you own your identity.

+
    +
  • + + No single company controls your account or data +
  • +
  • + + Like email, the ATmosphere is open, interoperable, and portable +
  • +
  • + + Bluesky, shlf.space, Tangled, and more all share this network +
  • +
+
+
+

Your Internet
Handle

+

Your handle is your unique identity across the whole ATmosphere.

+
+
+ Example + you.bsky.social +
+
+ Your own domain + yourname.com +
+
+ Switch between apps without creating new accounts +
+
+
+
+

Create Your
Account

+

Sign up with any ATmosphere provider. Your handle works on shlf.space straight away.

+ +
+
+ +} diff --git a/internal/ui/views/shelf/shelf.css b/internal/ui/views/shelf/shelf.css index 0abb955..5055988 100644 --- a/internal/ui/views/shelf/shelf.css +++ b/internal/ui/views/shelf/shelf.css @@ -4,15 +4,15 @@ > :first-child, > :last-child { - background: var(--color-primary); + background: var(--color-ink); } } .shelf-base { display: grid; grid-template-columns: repeat(auto-fill, minmax(2rem, 1fr)); - background: var(--color-primary); - border-block: 2px solid var(--color-primary); + background: var(--color-ink); + border-block: 2px solid var(--color-ink); row-gap: 8px; min-height: 25rem; } @@ -39,8 +39,8 @@ @layer components { .item-slot { - background: var(--color-neutral); - border: 1px dashed var(--color-secondary); + background: var(--color-tonal); + border: 1px dashed var(--color-dim); border-bottom: none; display: flex; align-items: flex-end; @@ -50,7 +50,7 @@ padding-top: 1rem; &:hover { - background: var(--color-secondary); + background: var(--color-accent); } &.slot-occupied { @@ -58,14 +58,14 @@ } &.slot-drag-over { - background: var(--color-secondary); - border: 2px solid var(--color-primary); + background: var(--color-accent); + border: 2px solid var(--color-ink); border-bottom: none; } } .book { - background: var(--color-neutral); + background: var(--color-tonal); width: 2rem; cursor: grab; flex-shrink: 0; @@ -88,8 +88,8 @@ gap: 0.625rem; position: fixed; width: 17.5rem; - background: var(--color-neutral); - border: 1px solid var(--color-primary); + background: var(--color-tonal); + border: 1px solid var(--color-ink); padding: theme(spacing.3); z-index: 100; pointer-events: none; @@ -100,18 +100,18 @@ font-weight: 300; h2 { - font-size: theme(text.base); + font-size: var(--text-base); font-weight: 600; margin-bottom: 0.125rem; } h4 { - font-size: theme(text.sm); + font-size: var(--text-sm); margin-bottom: theme(spacing.2); } p { - font-size: theme(text.xs); + font-size: var(--text-xs); } } @@ -125,12 +125,12 @@ inset: 0; background: repeating-linear-gradient( 45deg, - var(--color-secondary), - var(--color-secondary) 2px, - var(--color-neutral) 2px, - var(--color-neutral) 8px + var(--color-dim), + var(--color-dim) 2px, + var(--color-tonal) 2px, + var(--color-tonal) 8px ); - border: 1px dashed var(--color-secondary); + border: 1px dashed var(--color-dim); cursor: grab; } -- 2.51.2