Gitfed
bastien-mrq/gitfed/ Commits/ 9df2745

Full-width add-repo panel; centered branded login page

Add-repo panel: split the trigger button out of the <details> element so it no longer has to size itself as a flex sibling of the button — it's now a separate full-width card below the header row, matching the width of the repo list and stat row (was capped at 480px, floating awkwardly next to empty space). Needed a small new data-toggle click handler in the shared script since native <details>/<summary> can't express "button in one place, panel spanning something else" — same pattern already used for the nav burger/profile menu toggles. Login page: centered branded card (logo, tagline, form, note) instead of a bare card dropped at the top of the page — option D from the mockup.

bastien-mrq 2026-07-29 10:14 commit 9df27459fb946489318671eee94f996f2e70d418 parent c6ad7893816e532ac790e661983cf7d26e17fa27
3 files changed +66 −46
M cmd/gitfed-web/handlers_auth.go +19 −14
M cmd/gitfed-web/handlers_dashboard.go +28 −29
M cmd/gitfed-web/render.go +19 −3
cmd/gitfed-web/handlers_auth.go
diff --git a/cmd/gitfed-web/handlers_auth.go b/cmd/gitfed-web/handlers_auth.go index 53a56c3..1c5c77f 100644 --- a/cmd/gitfed-web/handlers_auth.go +++ b/cmd/gitfed-web/handlers_auth.go @@ -11,17 +11,22 @@ import ( ) var loginTpl = newTpl("login", ` -{{.Flash}} -<form class="card narrow" method="post" action="/login"> - <strong>{{t .Lang "auth.login"}}</strong> - <input type="hidden" name="next" value="{{.Next}}"> - <label>{{t .Lang "auth.username"}}</label> - <input name="username" required autofocus> - <label>{{t .Lang "auth.password"}}</label> - <input name="password" type="password" required> - <button type="submit">{{t .Lang "auth.login"}}</button> -</form> -<p class="muted">{{t .Lang "auth.note"}}</p> +<div class="gf-auth-center"> + <div class="gf-auth-box"> + <div class="gf-auth-brand">{{.BrandMark}} Gitfed</div> + <p class="gf-auth-tagline">{{t .Lang "landing.kicker"}}</p> + {{.Flash}} + <form class="card" method="post" action="/login"> + <input type="hidden" name="next" value="{{.Next}}"> + <label>{{t .Lang "auth.username"}}</label> + <input name="username" required autofocus> + <label>{{t .Lang "auth.password"}}</label> + <input name="password" type="password" required> + <button type="submit">{{t .Lang "auth.login"}}</button> + </form> + <p class="muted gf-auth-note">{{t .Lang "auth.note"}}</p> + </div> +</div> `) func (s *server) handleLoginForm(w http.ResponseWriter, r *http.Request) { @@ -33,9 +38,9 @@ func (s *server) handleLoginForm(w http.ResponseWriter, r *http.Request) { next := sanitizeNext(r.URL.Query().Get("next")) var buf bytes.Buffer _ = loginTpl.Execute(&buf, struct { - Next, Lang string - Flash template.HTML - }{next, string(lang), flash(r)}) + Next, Lang string + Flash, BrandMark template.HTML + }{next, string(lang), flash(r), template.HTML(brandMark)}) s.render(w, r, i18n.T(lang, "auth.login"), "login", template.HTML(buf.String())) }
cmd/gitfed-web/handlers_dashboard.go
diff --git a/cmd/gitfed-web/handlers_dashboard.go b/cmd/gitfed-web/handlers_dashboard.go index d8c3aca..38fb543 100644 --- a/cmd/gitfed-web/handlers_dashboard.go +++ b/cmd/gitfed-web/handlers_dashboard.go @@ -21,37 +21,36 @@ var dashboardTpl = newTpl("dashboard", ` <div class="gf-page-head"> <h1>{{t .Lang "dashboard.title"}}</h1> - <details class="gf-new-repo"> - <summary class="gf-btn primary" style="margin:0;">+ {{t .Lang "dashboard.add_repo"}}</summary> - <div class="gf-card gf-add-repo-panel" style="margin-top:0.75rem;"> - <div class="gf-tabs"> - <button type="button" data-tab="tab-new-repo" class="active">{{t .Lang "dashboard.new_repo"}}</button> - <button type="button" data-tab="tab-import-repo">{{t .Lang "dashboard.import_repo"}}</button> - </div> - <div id="tab-new-repo" class="gf-tabpane active"> - <form method="post" action="/repos"> - <label>{{t .Lang "dashboard.repo_name"}}</label> - <div class="gf-input-group"> - <span class="prefix">{{.Username}}/</span> - <input name="name" required placeholder="my-project" autocomplete="off"> - </div> - <button type="submit">{{t .Lang "dashboard.create"}}</button> - </form> + <button type="button" class="gf-btn primary" data-toggle="addRepoPanel" aria-expanded="false" aria-controls="addRepoPanel">+ {{t .Lang "dashboard.add_repo"}}</button> +</div> + +<div class="gf-card gf-add-repo-panel" id="addRepoPanel" hidden> + <div class="gf-tabs"> + <button type="button" data-tab="tab-new-repo" class="active">{{t .Lang "dashboard.new_repo"}}</button> + <button type="button" data-tab="tab-import-repo">{{t .Lang "dashboard.import_repo"}}</button> + </div> + <div id="tab-new-repo" class="gf-tabpane active"> + <form method="post" action="/repos"> + <label>{{t .Lang "dashboard.repo_name"}}</label> + <div class="gf-input-group"> + <span class="prefix">{{.Username}}/</span> + <input name="name" required placeholder="my-project" autocomplete="off"> </div> - <div id="tab-import-repo" class="gf-tabpane gf-import-form"> - <form method="post" action="/repos/import"> - <label>{{t .Lang "dashboard.import_url_label"}}</label> - <input name="source_url" type="url" required placeholder="https://github.com/owner/repo.git" id="importUrl" autocomplete="off"> - <label>{{t .Lang "dashboard.repo_name"}}</label> - <div class="gf-input-group"> - <span class="prefix">{{.Username}}/</span> - <input name="name" required placeholder="repo" id="importName" autocomplete="off"> - </div> - <button type="submit">{{t .Lang "dashboard.import"}}</button> - </form> + <button type="submit">{{t .Lang "dashboard.create"}}</button> + </form> + </div> + <div id="tab-import-repo" class="gf-tabpane gf-import-form"> + <form method="post" action="/repos/import"> + <label>{{t .Lang "dashboard.import_url_label"}}</label> + <input name="source_url" type="url" required placeholder="https://github.com/owner/repo.git" id="importUrl" autocomplete="off"> + <label>{{t .Lang "dashboard.repo_name"}}</label> + <div class="gf-input-group"> + <span class="prefix">{{.Username}}/</span> + <input name="name" required placeholder="repo" id="importName" autocomplete="off"> </div> - </div> - </details> + <button type="submit">{{t .Lang "dashboard.import"}}</button> + </form> + </div> </div> <div class="gf-card gf-repo-list">
cmd/gitfed-web/render.go
diff --git a/cmd/gitfed-web/render.go b/cmd/gitfed-web/render.go index cb12b44..5bd923c 100644 --- a/cmd/gitfed-web/render.go +++ b/cmd/gitfed-web/render.go @@ -488,9 +488,10 @@ const shellHeadSrc = `<!doctype html> .gf-new-repo summary::-webkit-details-marker { display: none; } .gf-new-repo[open] summary { margin-bottom: 0.5rem; } .gf-new-repo form.card { min-width: 320px; } - .gf-add-repo-panel { min-width: 360px; max-width: 480px; } + .gf-add-repo-panel { margin-bottom: 1.25rem; } .gf-add-repo-panel .gf-tabs { margin: 0; padding: 0.3rem 1.1rem 0; } .gf-add-repo-panel .gf-tabpane { padding: 1.1rem; } + .gf-add-repo-panel .gf-tabpane form { max-width: 480px; } .gf-input-group { display: flex; align-items: stretch; margin-top: 0.25rem; } .gf-input-group input { margin-top: 0; border-radius: 0 6px 6px 0; } @@ -575,7 +576,6 @@ const shellHeadSrc = `<!doctype html> /* ---------- forms & buttons ---------- */ form.inline { display: inline; } form.card { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 1.1rem; margin: 1rem 0; } - form.card.narrow { max-width: 480px; } form.card label { display: block; font-size: 0.85rem; color: var(--text-dim); margin-top: 0.6rem; } input, select, textarea { width: 100%; box-sizing: border-box; padding: 0.45rem 0.6rem; margin-top: 0.25rem; background: var(--canvas); border: 1px solid var(--border-strong); color: var(--text); border-radius: 6px; font-size: 0.9rem; font-family: inherit; } textarea { resize: vertical; line-height: 1.5; } @@ -599,6 +599,14 @@ const shellHeadSrc = `<!doctype html> .badge.plain { background: var(--surface-2); border: 1px solid var(--border); color: var(--text-dim); margin-right: 0.3rem; } .badge.danger { background: var(--danger-bg); color: var(--danger-fg); } .muted { color: var(--text-dim); font-size: 0.85rem; } + + /* ---------- auth (login) ---------- */ + .gf-auth-center { display: flex; align-items: center; justify-content: center; min-height: 60vh; padding: 2rem 0; } + .gf-auth-box { width: 100%; max-width: 340px; text-align: center; } + .gf-auth-brand { display: flex; align-items: center; justify-content: center; gap: 0.5rem; color: var(--accent); font-weight: 700; font-size: 1.15rem; margin-bottom: 0.4rem; } + .gf-auth-tagline { color: var(--text-faint); font-size: 0.82rem; margin: 0 0 1.6rem; } + .gf-auth-box form.card { text-align: left; margin: 0; } + .gf-auth-note { margin: 1.2rem auto 0; font-size: 0.78rem; } code, pre { background: var(--surface-2); border-radius: 6px; font-family: var(--mono); font-size: 0.86em; } code { padding: 0.15rem 0.4rem; } pre { padding: 1rem; overflow-x: auto; border: 1px solid var(--border); line-height: 1.5; } @@ -629,7 +637,6 @@ const shellHeadSrc = `<!doctype html> @media (max-width: 480px) { main { padding: 1rem 0.85rem; } - form.card.narrow { max-width: none; } } </style> </head> @@ -708,6 +715,15 @@ const shellScriptJS = ` burger.setAttribute('aria-expanded', open ? 'true' : 'false'); }); } + document.querySelectorAll('[data-toggle]').forEach(function (btn) { + var panel = document.getElementById(btn.getAttribute('data-toggle')); + if (!panel) return; + btn.addEventListener('click', function () { + var open = panel.hidden; + panel.hidden = !open; + btn.setAttribute('aria-expanded', open ? 'true' : 'false'); + }); + }); var profileTrigger = document.getElementById('profileTrigger'); var profileMenu = document.getElementById('profileMenu'); if (profileTrigger && profileMenu) {