docs(14): create phase plan
Plan 14-01: Auth foundation (logo assets, auth.css, auth_components.templ, auth_layout.templ) Plan 14-02: Page migration (auth_login.templ, auth_signup.templ → AuthLayout + ui.FormField + nav links) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5ed9291d76
commit
522c071550
2 changed files with 540 additions and 7 deletions
|
|
@ -2,15 +2,15 @@
|
|||
gsd_state_version: 1.0
|
||||
milestone: v3.0
|
||||
milestone_name: Design System & Visual Polish
|
||||
status: planning
|
||||
last_updated: "2026-05-16T16:35:15.959Z"
|
||||
last_activity: 2026-05-16
|
||||
status: executing
|
||||
last_updated: "2026-05-16T17:00:49.619Z"
|
||||
last_activity: 2026-05-16 -- Phase 14 planning complete
|
||||
progress:
|
||||
total_phases: 5
|
||||
completed_phases: 1
|
||||
total_plans: 5
|
||||
total_plans: 7
|
||||
completed_plans: 5
|
||||
percent: 100
|
||||
percent: 71
|
||||
---
|
||||
|
||||
# STATE
|
||||
|
|
@ -30,8 +30,8 @@ See: `.planning/PROJECT.md` (updated 2026-05-16)
|
|||
|
||||
Phase: 14
|
||||
Plan: Not started
|
||||
Status: Ready to plan
|
||||
Last activity: 2026-05-16
|
||||
Status: Ready to execute
|
||||
Last activity: 2026-05-16 -- Phase 14 planning complete
|
||||
|
||||
## Previous Milestone Status
|
||||
|
||||
|
|
|
|||
533
.planning/phases/14-auth-pages/14-PATTERNS.md
Normal file
533
.planning/phases/14-auth-pages/14-PATTERNS.md
Normal file
|
|
@ -0,0 +1,533 @@
|
|||
# Phase 14: Auth Pages - Pattern Map
|
||||
|
||||
**Mapped:** 2026-05-16
|
||||
**Files analyzed:** 7 (5 templates/CSS + 2 static assets)
|
||||
**Analogs found:** 7 / 7
|
||||
|
||||
---
|
||||
|
||||
## File Classification
|
||||
|
||||
| New/Modified File | Role | Data Flow | Closest Analog | Match Quality |
|
||||
|-------------------|------|-----------|----------------|---------------|
|
||||
| `backend/templates/auth_layout.templ` | template/layout | request-response | `backend/templates/layout.templ` | exact-role |
|
||||
| `backend/templates/auth_components.templ` | template/component | static-render | `go-backend/internal/web/views/auth_components.templ` | exact |
|
||||
| `backend/templates/auth_login.templ` | template/page | request-response | `backend/templates/auth_signup.templ` (peer) | exact |
|
||||
| `backend/templates/auth_signup.templ` | template/page | request-response | `backend/templates/auth_login.templ` (peer) | exact |
|
||||
| `backend/internal/web/ui/auth.css` | stylesheet | static-render | `go-backend/static/styles.css` lines 1063–1510, 2533–2947 | exact (extract) |
|
||||
| `backend/static/logo_dark.png` | static asset | file-copy | `go-backend/static/logo_dark.png` | exact |
|
||||
| `backend/static/logo_white.png` | static asset | file-copy | `go-backend/static/logo_white.png` | exact |
|
||||
|
||||
---
|
||||
|
||||
## Pattern Assignments
|
||||
|
||||
### `backend/templates/auth_layout.templ` (layout, request-response)
|
||||
|
||||
**Analog:** `backend/templates/layout.templ`
|
||||
|
||||
**Package and imports pattern** (`backend/templates/layout.templ` lines 1–9):
|
||||
```go
|
||||
package templates
|
||||
|
||||
import (
|
||||
"backend/internal/web/ui"
|
||||
)
|
||||
```
|
||||
|
||||
`AuthLayout` does NOT import `backend/internal/auth` — auth pages are always unauthenticated. No `user *auth.User` parameter.
|
||||
|
||||
**HTML shell pattern** (`backend/templates/layout.templ` lines 25–59 adapted):
|
||||
```go
|
||||
templ AuthLayout(title string, csrfToken string) {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>{ title }</title>
|
||||
<link rel="stylesheet" href="/static/tailwind.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-screen">
|
||||
@AnimatedBackground()
|
||||
<div class="card-wrap">
|
||||
<div class="card-glow"></div>
|
||||
<div class="auth-card-shell">
|
||||
{ children... }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/htmx.min.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
```
|
||||
|
||||
Key deltas from `Layout`:
|
||||
- No `<header>`, no `<footer>`, no `<main>` wrapper
|
||||
- Body wraps in `.login-screen` (not `min-h-screen bg-white`)
|
||||
- Scripts: keep `htmx.min.js`; drop `sortable.min.js` and `discussion-sse.js` (not needed on auth pages)
|
||||
- `csrfToken` is threaded through because the page children (form fragments) use `ui.CSRFField(csrfToken)`
|
||||
- `@AnimatedBackground()` is called from `auth_components.templ` in the same package
|
||||
|
||||
**CSRF threading note:** `csrfToken` on `AuthLayout` is passed to child page templates (`LoginPage`, `SignupPage`) which forward it into their form fragments. The layout itself does not embed a CSRF field — only the forms do.
|
||||
|
||||
---
|
||||
|
||||
### `backend/templates/auth_components.templ` (component, static-render)
|
||||
|
||||
**Analog:** `go-backend/internal/web/views/auth_components.templ` (port verbatim, with adaptations noted)
|
||||
|
||||
**Package declaration:**
|
||||
```go
|
||||
package templates
|
||||
```
|
||||
(not `package views` — must match the backend package)
|
||||
|
||||
**AnimatedBackground component** (`go-backend/internal/web/views/auth_components.templ` lines 108–158):
|
||||
|
||||
Port verbatim from go-backend. Two adaptations:
|
||||
1. Image src path: change `/logo_dark.png` → `/static/logo_dark.png` (backend serves static files under `/static/`)
|
||||
2. Dark-mode images (`light-only` / `dark-only` pairs): Phase 14 uses `logo_dark.png` only — replace the two-image pattern with a single `<img>` per element (dark mode deferred per DEFERRED section). The `light-only` / `dark-only` CSS classes are defined in `base.css` lines 206–213 but should not be emitted on images in Phase 14.
|
||||
|
||||
```go
|
||||
templ AnimatedBackground() {
|
||||
<div class="background-layer" aria-hidden="true">
|
||||
<div class="background-logo bg-01 animate-move-right-slow opacity-04">
|
||||
<img alt="Xtablo" class="logo-asset size-16 animate-spin-slow" src="/static/logo_dark.png"/>
|
||||
</div>
|
||||
<div class="background-logo bg-02 animate-move-right-medium opacity-03">
|
||||
<img alt="Xtablo" class="logo-asset size-12 animate-bounce-gentle" src="/static/logo_dark.png"/>
|
||||
</div>
|
||||
// ... all 35 elements following the same single-img pattern
|
||||
// See go-backend source lines 110–156 for the full class list per element
|
||||
</div>
|
||||
}
|
||||
```
|
||||
|
||||
**Full element list** (go-backend lines 110–156 — class assignments per slot):
|
||||
| Slot | Outer classes | Inner img classes |
|
||||
|------|--------------|-------------------|
|
||||
| bg-01 | `animate-move-right-slow opacity-04` | `size-16 animate-spin-slow` |
|
||||
| bg-02 | `animate-move-right-medium opacity-03` | `size-12 animate-bounce-gentle` |
|
||||
| bg-03 | `animate-move-right-fast opacity-05` | `size-20 animate-pulse-gentle` |
|
||||
| bg-04 | `animate-move-right-slow opacity-02` | `size-14 animate-wiggle` |
|
||||
| bg-05 | `animate-move-right-medium opacity-03` | `size-18 animate-float-gentle` |
|
||||
| bg-06 | `animate-move-diagonal-1 opacity-03` | `size-10 animate-spin-reverse` |
|
||||
| bg-07 | `animate-move-diagonal-2 opacity-04` | `size-16 animate-scale-gentle` |
|
||||
| bg-08 | `animate-move-diagonal-3 opacity-02` | `size-12 animate-rotate-gentle` |
|
||||
| bg-09 | `animate-move-down-slow opacity-03` | `size-14 animate-bounce-soft` |
|
||||
| bg-10 | `animate-move-down-medium opacity-04` | `size-16 animate-sway` |
|
||||
| bg-11 | `animate-orbit-1 opacity-02` | `size-08` (no img animation) |
|
||||
| bg-12 | `animate-orbit-2 opacity-03` | `size-10` (no img animation) |
|
||||
| bg-13 | `animate-orbit-3 opacity-02` | `size-06` (no img animation) |
|
||||
| bg-14 | `animate-orbit-4 opacity-03` | `size-12 animate-spin-fast` |
|
||||
| bg-15 | `animate-orbit-5 opacity-02` | `size-07 animate-pulse-fast` |
|
||||
| bg-16 | `animate-zigzag-1 opacity-04` | `size-14 animate-wobble` |
|
||||
| bg-17 | `animate-zigzag-2 opacity-03` | `size-11 animate-shake` |
|
||||
| bg-18 | `animate-zigzag-3 opacity-05` | `size-16 animate-bounce-crazy` |
|
||||
| bg-19 | `animate-spiral-1 opacity-03` | `size-09 animate-spin-wobble` |
|
||||
| bg-20 | `animate-spiral-2 opacity-04` | `size-13 animate-flip` |
|
||||
| bg-21 | `animate-float-random-1 opacity-02` | `size-08 animate-twirl` |
|
||||
| bg-22 | `animate-float-random-2 opacity-03` | `size-10 animate-dance` |
|
||||
| bg-23 | `animate-float-random-3 opacity-04` | `size-12 animate-jiggle` |
|
||||
| bg-24 | `animate-float-random-4 opacity-02` | `size-09 animate-vibrate` |
|
||||
| bg-25 | `animate-wave-1 opacity-03` | `size-11 animate-swing` |
|
||||
| bg-26 | `animate-wave-2 opacity-04` | `size-13 animate-pendulum` |
|
||||
| bg-27 | `animate-wave-3 opacity-02` | `size-10 animate-elastic` |
|
||||
| bg-28 | `animate-wave-4 opacity-05` | `size-15 animate-rubber` |
|
||||
| bg-29 | `animate-corner-shoot-1 opacity-03` | `size-12 animate-rocket` |
|
||||
| bg-30 | `animate-corner-shoot-2 opacity-04` | `size-14 animate-comet` |
|
||||
| bg-31 | `animate-corner-shoot-3 opacity-02` | `size-10 animate-meteor` |
|
||||
| bg-32 | `animate-corner-shoot-4 opacity-05` | `size-16 animate-blast` |
|
||||
| bg-33 | `animate-bounce-ball-1 opacity-04` | `size-08 animate-spin-bounce` |
|
||||
| bg-34 | `animate-bounce-ball-2 opacity-03` | `size-11 animate-flip-bounce` |
|
||||
| bg-35 | `animate-bounce-ball-3 opacity-05` | `size-13 animate-scale-bounce` |
|
||||
|
||||
**GoogleButton component** (`go-backend/internal/web/views/auth_components.templ` lines 89–106):
|
||||
|
||||
Port structure verbatim; change label text to English per D-G03:
|
||||
```go
|
||||
templ GoogleButton(href string, configured bool) {
|
||||
if configured {
|
||||
<a class="gsi-material-button" href={ templ.SafeURL(href) }>
|
||||
<div class="gsi-material-button-state"></div>
|
||||
<div class="gsi-material-button-content-wrapper">
|
||||
<div class="gsi-material-button-icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" aria-hidden="true">
|
||||
<path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"></path>
|
||||
<path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"></path>
|
||||
<path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"></path>
|
||||
<path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"></path>
|
||||
<path fill="none" d="M0 0h48v48H0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="gsi-material-button-contents">Sign in with Google</span>
|
||||
<span class="visually-hidden">Sign in with Google</span>
|
||||
</div>
|
||||
</a>
|
||||
} else {
|
||||
<button type="button" class="gsi-material-button" disabled aria-disabled="true">
|
||||
// same inner structure, same label
|
||||
</button>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note: go-backend `GoogleButton` is a `<button>` unconditionally. The backend already has the configured/unconfigured split via `AuthProviderButtonControl` — wire `GoogleButton` to accept `(href string, configured bool)` to preserve this logic.
|
||||
|
||||
**AuthDivider component** (`go-backend/internal/web/views/auth_components.templ` lines 81–87):
|
||||
```go
|
||||
templ AuthDivider() {
|
||||
<div class="divider-row">
|
||||
<div class="divider-line"></div>
|
||||
<span class="divider-pill">or</span>
|
||||
<div class="divider-line"></div>
|
||||
</div>
|
||||
}
|
||||
```
|
||||
Change "Ou continuer avec" → "or" per D-G03 English convention.
|
||||
|
||||
---
|
||||
|
||||
### `backend/templates/auth_login.templ` (page, request-response)
|
||||
|
||||
**Analog:** `backend/templates/auth_login.templ` (current — this file is being refactored)
|
||||
|
||||
**Function signatures stay compatible with handlers** (do not change):
|
||||
```go
|
||||
templ LoginPage(form LoginForm, errs LoginErrors, csrfToken string, providers AuthProviderButtons) {
|
||||
@AuthLayout("Sign in to Xtablo", csrfToken) {
|
||||
<div class="auth-card-topbar"></div>
|
||||
<div class="brand-header">
|
||||
<img class="brand-logo" src="/static/logo_dark.png" alt="Xtablo"/>
|
||||
</div>
|
||||
<div class="title-group">
|
||||
<h1>Sign in to Xtablo</h1>
|
||||
</div>
|
||||
<div class="auth-body">
|
||||
@GoogleButton(providers.Google.StartURL, providers.Google.Configured)
|
||||
@AuthDivider()
|
||||
@LoginFormFragment(form, errs, csrfToken)
|
||||
</div>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**LoginFormFragment pattern** (migrate raw inputs to `@ui.FormField` + `@ui.Input`):
|
||||
|
||||
Current pattern (lines 28–75 of `backend/templates/auth_login.templ`) uses raw `<input>` with Tailwind classes. Replace with:
|
||||
|
||||
```go
|
||||
templ LoginFormFragment(form LoginForm, errs LoginErrors, csrfToken string) {
|
||||
<form
|
||||
id="login-form"
|
||||
method="POST"
|
||||
action="/login"
|
||||
hx-post="/login"
|
||||
hx-target="#login-form"
|
||||
hx-swap="outerHTML"
|
||||
class="login-form"
|
||||
>
|
||||
@ui.CSRFField(csrfToken)
|
||||
@GeneralError(errs.General)
|
||||
@ui.FormField(ui.FormFieldProps{
|
||||
Label: "Email address",
|
||||
For: "email",
|
||||
Field: ui.Input(ui.InputProps{
|
||||
ID: "email",
|
||||
Name: "email",
|
||||
Type: "email",
|
||||
Placeholder: "you@example.com",
|
||||
Value: form.Email,
|
||||
Required: true,
|
||||
Attrs: templ.Attributes{"autocomplete": "email"},
|
||||
}),
|
||||
Error: errs.Email,
|
||||
})
|
||||
@ui.FormField(ui.FormFieldProps{
|
||||
Label: "Password",
|
||||
For: "password",
|
||||
Field: ui.Input(ui.InputProps{
|
||||
ID: "password",
|
||||
Name: "password",
|
||||
Type: "password",
|
||||
Placeholder: "Your password",
|
||||
Required: true,
|
||||
Attrs: templ.Attributes{"autocomplete": "current-password"},
|
||||
}),
|
||||
Error: errs.Password,
|
||||
})
|
||||
@ui.Button(ui.ButtonProps{
|
||||
Label: "Sign in to Xtablo",
|
||||
Variant: ui.ButtonVariantDefault,
|
||||
Tone: ui.ButtonToneSolid,
|
||||
Size: ui.SizeMD,
|
||||
Type: "submit",
|
||||
})
|
||||
<p class="signup-copy">
|
||||
Don't have an account?
|
||||
<a class="signup-link" href="/signup">Sign up</a>
|
||||
</p>
|
||||
</form>
|
||||
}
|
||||
```
|
||||
|
||||
Key changes from current:
|
||||
- `class="space-y-5"` on form → `class="login-form"` (auth.css drives layout)
|
||||
- Raw `<input>` elements → `@ui.FormField` + `@ui.Input`
|
||||
- `@FieldError(errs.X)` calls removed — errors wired through `FormFieldProps.Error`
|
||||
- Submit label: "Sign in" → "Sign in to Xtablo" (copywriting contract)
|
||||
- Nav link added below submit button inside the form element
|
||||
- HTMX attributes preserved exactly: `hx-post="/login" hx-target="#login-form" hx-swap="outerHTML"`
|
||||
- `AuthProviderButtonsBlock` component replaced by `GoogleButton` + `AuthDivider` called from the page template (not the fragment)
|
||||
|
||||
---
|
||||
|
||||
### `backend/templates/auth_signup.templ` (page, request-response)
|
||||
|
||||
**Analog:** `backend/templates/auth_login.templ` (mirror of login pattern)
|
||||
|
||||
**Function signature** (unchanged for handler compatibility):
|
||||
```go
|
||||
templ SignupPage(form SignupForm, errs SignupErrors, csrfToken string, providers AuthProviderButtons) {
|
||||
@AuthLayout("Create your account", csrfToken) {
|
||||
<div class="auth-card-topbar"></div>
|
||||
<div class="brand-header">
|
||||
<img class="brand-logo" src="/static/logo_dark.png" alt="Xtablo"/>
|
||||
</div>
|
||||
<div class="title-group">
|
||||
<h1>Create your account</h1>
|
||||
</div>
|
||||
<div class="auth-body">
|
||||
@GoogleButton(providers.Google.StartURL, providers.Google.Configured)
|
||||
@AuthDivider()
|
||||
@SignupFormFragment(form, errs, csrfToken)
|
||||
</div>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**SignupFormFragment pattern** (mirror of login, differences noted):
|
||||
```go
|
||||
templ SignupFormFragment(form SignupForm, errs SignupErrors, csrfToken string) {
|
||||
<form
|
||||
id="signup-form"
|
||||
method="POST"
|
||||
action="/signup"
|
||||
hx-post="/signup"
|
||||
hx-target="#signup-form"
|
||||
hx-swap="outerHTML"
|
||||
class="login-form"
|
||||
>
|
||||
@ui.CSRFField(csrfToken)
|
||||
@GeneralError(errs.General)
|
||||
@ui.FormField(ui.FormFieldProps{
|
||||
Label: "Email address",
|
||||
For: "email",
|
||||
Field: ui.Input(ui.InputProps{
|
||||
ID: "email",
|
||||
Name: "email",
|
||||
Type: "email",
|
||||
Placeholder: "you@example.com",
|
||||
Value: form.Email,
|
||||
Required: true,
|
||||
Attrs: templ.Attributes{"autocomplete": "email"},
|
||||
}),
|
||||
Error: errs.Email,
|
||||
})
|
||||
@ui.FormField(ui.FormFieldProps{
|
||||
Label: "Password",
|
||||
For: "password",
|
||||
Field: ui.Input(ui.InputProps{
|
||||
ID: "password",
|
||||
Name: "password",
|
||||
Type: "password",
|
||||
Placeholder: "12 characters minimum", // differs from login
|
||||
Required: true,
|
||||
Attrs: templ.Attributes{"autocomplete": "new-password"}, // differs from login
|
||||
}),
|
||||
Error: errs.Password,
|
||||
})
|
||||
@ui.Button(ui.ButtonProps{
|
||||
Label: "Create account", // differs from login
|
||||
Variant: ui.ButtonVariantDefault,
|
||||
Tone: ui.ButtonToneSolid,
|
||||
Size: ui.SizeMD,
|
||||
Type: "submit",
|
||||
})
|
||||
<p class="signup-copy">
|
||||
Already have an account?
|
||||
<a class="signup-link" href="/login">Sign in</a>
|
||||
</p>
|
||||
</form>
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `backend/internal/web/ui/auth.css` (stylesheet, replace entirely)
|
||||
|
||||
**Analog:** `go-backend/static/styles.css` (extract auth-relevant sections only — do NOT import the file)
|
||||
|
||||
This file replaces the existing minimal `auth.css` (62 lines). The new file is a full extraction from go-backend's `styles.css`. The existing `.auth-provider-stack`, `.auth-provider-button`, `.auth-provider-separator` rules should be kept at the bottom for any non-Google controls.
|
||||
|
||||
**Section map — what to extract and from where:**
|
||||
|
||||
| CSS section | go-backend source lines | Notes |
|
||||
|-------------|------------------------|-------|
|
||||
| `.login-screen` | 1063–1079 | Two declarations; merge into one |
|
||||
| `.background-layer` | 1081–1086 | |
|
||||
| `.background-logo` | 1088–1090 | |
|
||||
| `.logo-asset` | 1092–1097 | |
|
||||
| `.size-06` through `.size-20` | 1099–1111 | 13 size utilities |
|
||||
| `.opacity-02` through `.opacity-05` | 1113–1116 | 4 opacity utilities |
|
||||
| `.bg-01` through `.bg-35` | 1118–1155 | 35 position rules |
|
||||
| `.card-wrap` | 1157–1163 | |
|
||||
| `.card-glow` | 1165–1172 | |
|
||||
| `.auth-card-shell` | 1174–1182 | `padding: 1.25rem` ported verbatim |
|
||||
| `.auth-card-topbar` | 1184–1189 | |
|
||||
| `.brand-header` | 1237–1241 | |
|
||||
| `.brand-logo` | 1243–1248 | |
|
||||
| `.title-group` and `.title-group h1` | 1250–1259 | |
|
||||
| `.auth-body` | 1279–1284 | |
|
||||
| `.login-form` | 1286–1293 | Keep for HTMX swap compat |
|
||||
| `.divider-row` | 1356–1363 | |
|
||||
| `.divider-line` | 1365–1368 | |
|
||||
| `.divider-pill` | 1370–1379 | |
|
||||
| `.signup-copy` | 1381–1386 | |
|
||||
| `.signup-link` | 1388–1402 | Includes hover state |
|
||||
| `.status-slot`, `.status-banner`, `.status-success`, `.status-error` | 1404–1425 | |
|
||||
| `.gsi-material-button` and all sub-classes | 1427–1509 | Full block verbatim |
|
||||
| All `@keyframes` definitions | 2533–2882 | ~350 lines |
|
||||
| All `.animate-*` utility classes | 2883–2947 | ~65 lines |
|
||||
|
||||
**Critical CSS values to copy verbatim** (go-backend lines 1174–1182):
|
||||
```css
|
||||
.auth-card-shell {
|
||||
backdrop-filter: blur(12px);
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 1rem;
|
||||
box-shadow: var(--shadow-auth-card);
|
||||
padding: 1.25rem; /* NOT a design token — ported as-is */
|
||||
position: relative;
|
||||
}
|
||||
```
|
||||
|
||||
**Tokens already defined in `base.css`** (no need to redefine):
|
||||
- `--gradient-shell` (line 139)
|
||||
- `--gradient-card-glow` (line 143)
|
||||
- `--shadow-auth-card` (line 127)
|
||||
- `--color-surface-default`, `--color-border-google`, `--color-text-google` (lines 20, 45, 14)
|
||||
- `--overlay-google-state` (line 126)
|
||||
- `--shadow-google-button` (lines 135–137)
|
||||
- `--card`, `--border`, `--muted-foreground`, `--foreground`, `--accent`, `--background` (legacy alias block, lines 161–171)
|
||||
|
||||
---
|
||||
|
||||
### `backend/static/logo_dark.png` and `backend/static/logo_white.png` (static assets, file-copy)
|
||||
|
||||
**Source:** `go-backend/static/logo_dark.png` and `go-backend/static/logo_white.png`
|
||||
|
||||
**Action:** Binary file copy. No transformation.
|
||||
|
||||
```bash
|
||||
cp go-backend/static/logo_dark.png backend/static/logo_dark.png
|
||||
cp go-backend/static/logo_white.png backend/static/logo_white.png
|
||||
```
|
||||
|
||||
`AnimatedBackground` references `/static/logo_dark.png` — this path must resolve before `templ generate` is run and before the server starts. Copy the asset before any template work.
|
||||
|
||||
---
|
||||
|
||||
## Shared Patterns
|
||||
|
||||
### templ package declaration
|
||||
**Apply to:** `auth_layout.templ`, `auth_components.templ`
|
||||
```go
|
||||
package templates
|
||||
```
|
||||
All files in `backend/templates/` use this package. Components in `auth_components.templ` are called with `@AnimatedBackground()`, `@AuthDivider()`, `@GoogleButton(...)` from sibling files in the same package — no import needed.
|
||||
|
||||
### ui package import
|
||||
**Source:** `backend/templates/auth_login.templ` line 3; `backend/internal/web/ui/input.templ`, `form_field.templ`
|
||||
**Apply to:** `auth_login.templ`, `auth_signup.templ`
|
||||
```go
|
||||
import "backend/internal/web/ui"
|
||||
```
|
||||
`@ui.FormField`, `@ui.Input`, `@ui.Button`, `ui.CSRFField` are all called with this import.
|
||||
|
||||
### CSRF field threading
|
||||
**Source:** `backend/templates/layout.templ` line 41; `backend/templates/auth_login.templ` line 38
|
||||
**Apply to:** `auth_layout.templ`, `auth_login.templ`, `auth_signup.templ`
|
||||
```go
|
||||
// AuthLayout receives csrfToken and passes it to children via { children... }
|
||||
// The page template (LoginPage/SignupPage) forwards it to FormFragment
|
||||
// The fragment embeds it: @ui.CSRFField(csrfToken)
|
||||
```
|
||||
|
||||
### HTMX swap pattern
|
||||
**Source:** `backend/templates/auth_login.templ` lines 29–36
|
||||
**Apply to:** `LoginFormFragment`, `SignupFormFragment` (preserve exactly — no changes)
|
||||
```go
|
||||
hx-post="/login"
|
||||
hx-target="#login-form"
|
||||
hx-swap="outerHTML"
|
||||
// form id must match hx-target: id="login-form" / id="signup-form"
|
||||
```
|
||||
|
||||
### templ.SafeURL for external hrefs
|
||||
**Source:** `go-backend/internal/web/views/auth_components.templ` line 69
|
||||
**Apply to:** `GoogleButton` (when rendering the `<a>` variant), nav links
|
||||
```go
|
||||
href={ templ.SafeURL(href) }
|
||||
// Use for any runtime-constructed URL; static strings like "/signup" can be plain href="/signup"
|
||||
```
|
||||
|
||||
### CSS design token usage
|
||||
**Source:** `backend/internal/web/ui/base.css`
|
||||
**Apply to:** `auth.css` (all CSS rules)
|
||||
- Use `var(--card)` for glassmorphism card background (= `rgba(255,255,255,0.8)`)
|
||||
- Use `var(--border)` for card border
|
||||
- Use `var(--gradient-shell)` for `.login-screen` background
|
||||
- Use `var(--gradient-card-glow)` for `.card-glow`
|
||||
- Use `var(--shadow-auth-card)` for card box-shadow
|
||||
- Use `var(--muted-foreground)` for `.signup-copy`, `.divider-pill`
|
||||
- Use `var(--foreground)` for `.signup-link` base color
|
||||
- Use `var(--accent)` for `.signup-link:hover` background
|
||||
- Use `var(--background)` for `.divider-pill` background and input backgrounds
|
||||
- Do NOT hardcode colors that have token equivalents
|
||||
|
||||
---
|
||||
|
||||
## No Analog Found
|
||||
|
||||
No files in this phase are without an analog. All patterns have direct source counterparts.
|
||||
|
||||
---
|
||||
|
||||
## Metadata
|
||||
|
||||
**Analog search scope:**
|
||||
- `backend/templates/` — current backend templates
|
||||
- `backend/internal/web/ui/` — design system components and CSS
|
||||
- `go-backend/internal/web/views/` — reference implementation templates
|
||||
- `go-backend/static/styles.css` — reference CSS source
|
||||
|
||||
**Files scanned:** 8 source files read in full or in targeted sections
|
||||
**Pattern extraction date:** 2026-05-16
|
||||
|
||||
---
|
||||
|
||||
## Implementation Order
|
||||
|
||||
Execute in this order to avoid broken references:
|
||||
|
||||
1. Copy static assets (`logo_dark.png`, `logo_white.png`) — required before template renders
|
||||
2. Write `auth_components.templ` — `AnimatedBackground`, `GoogleButton`, `AuthDivider` components
|
||||
3. Write `auth_layout.templ` — calls `@AnimatedBackground()` from step 2
|
||||
4. Replace `auth.css` — all CSS classes must exist before browser renders
|
||||
5. Update `auth_login.templ` — switches to `@AuthLayout`, migrates inputs
|
||||
6. Update `auth_signup.templ` — same changes as login
|
||||
Loading…
Reference in a new issue