diff --git a/frontend/src/components/react/AssetsButton.tsx b/frontend/src/components/react/AssetsButton.tsx index 2b507ec..22b0165 100644 --- a/frontend/src/components/react/AssetsButton.tsx +++ b/frontend/src/components/react/AssetsButton.tsx @@ -8,7 +8,7 @@ interface Props { } export default function AssetsButton({ - className = 'inline-flex items-center gap-2 bg-surface0 hover:bg-surface1 text-subtext1 hover:text-text px-3 py-2 rounded-lg border border-surface1 transition-colors text-sm', + className = 'btn btn--ghost', label = 'Assets', iconSize = 14, }: Props) { @@ -89,8 +89,7 @@ export default function AssetsButton({ type="button" onClick={() => setOpen(false)} aria-label="Close" - className="p-1.5 text-[var(--subtext0)] hover:text-[var(--text)] hover:bg-[var(--surface0)] transition-colors" - style={{ borderRadius: 2 }} + className="btn btn--ghost btn--icon btn--sm" > {status === 'sending' ? 'Sending…' : 'Send message'} diff --git a/frontend/src/components/react/DeletePostButton.tsx b/frontend/src/components/react/DeletePostButton.tsx index 45ac22e..0434bd4 100644 --- a/frontend/src/components/react/DeletePostButton.tsx +++ b/frontend/src/components/react/DeletePostButton.tsx @@ -31,7 +31,7 @@ export default function DeletePostButton({ slug, title, variant = 'full' }: Prop disabled={busy} title="Delete post" aria-label="Delete post" - className="p-2 rounded-md bg-surface0/80 hover:bg-red/20 text-subtext0 hover:text-red border border-surface1 transition-colors disabled:opacity-50" + className="btn btn--danger btn--icon btn--sm" > e.stopPropagation()} title="Edit" aria-label={`Edit ${displayTitle}`} - className="p-1.5 bg-[var(--mantle)] text-[var(--rosewater)] hover:bg-[var(--blue)] border border-[var(--surface2)] transition-colors" - style={{ borderRadius: 1 }} + className="btn btn--ghost btn--icon btn--sm" > @@ -208,8 +207,7 @@ export default function PostList({ posts: initialPosts, isAdmin = false }: Props disabled={isDeleting} title="Remove" aria-label={`Remove ${displayTitle}`} - className="p-1.5 bg-[var(--mantle)] text-[var(--rosewater)] hover:bg-[var(--red)] border border-[var(--surface2)] transition-colors disabled:opacity-50" - style={{ borderRadius: 1 }} + className="btn btn--danger btn--icon btn--sm" > diff --git a/frontend/src/components/react/admin/AssetManager.tsx b/frontend/src/components/react/admin/AssetManager.tsx index 0ffec2f..60c0d58 100644 --- a/frontend/src/components/react/admin/AssetManager.tsx +++ b/frontend/src/components/react/admin/AssetManager.tsx @@ -109,8 +109,7 @@ export default function AssetManager({ mode = 'manage', onSelect }: Props) { {mode === 'select' && onSelect && (
{originalSlug && ( - )} - {originalSlug && ( @@ -427,7 +427,7 @@ export default function Editor({ editSlug }: Props) { href={`/posts/${encodeURIComponent(originalSlug)}`} target="_blank" rel="noreferrer" - className="btn-ghost py-3 px-6 inline-flex items-center justify-center gap-2 whitespace-nowrap" + className="btn btn--ghost" > View work @@ -521,12 +521,7 @@ export default function Editor({ editSlug }: Props) { @@ -576,11 +562,7 @@ export default function Editor({ editSlug }: Props) { role="tab" aria-selected={mobileView === 'preview'} onClick={() => setMobileView('preview')} - className={`flex-1 text-xs px-3 py-2 rounded border transition-colors ${ - mobileView === 'preview' - ? 'bg-blue/20 text-blue border-blue/30' - : 'bg-surface0 text-subtext0 border-surface1 hover:text-text' - }`} + className={`btn btn--sm flex-1${mobileView === 'preview' ? ' is-active' : ''}`} > Preview @@ -679,7 +661,7 @@ export default function Editor({ editSlug }: Props) {

Add image

Click an image to insert it. Drag new files in to upload.

- diff --git a/frontend/src/components/react/admin/Inbox.tsx b/frontend/src/components/react/admin/Inbox.tsx index 5d24d88..7280608 100644 --- a/frontend/src/components/react/admin/Inbox.tsx +++ b/frontend/src/components/react/admin/Inbox.tsx @@ -120,7 +120,7 @@ export default function Inbox() { {m.email && ( Reply @@ -128,7 +128,7 @@ export default function Inbox() { diff --git a/frontend/src/components/react/admin/Login.tsx b/frontend/src/components/react/admin/Login.tsx index db3e1e5..fbf28a4 100644 --- a/frontend/src/components/react/admin/Login.tsx +++ b/frontend/src/components/react/admin/Login.tsx @@ -65,7 +65,7 @@ export default function Login() { diff --git a/frontend/src/components/react/admin/Settings.tsx b/frontend/src/components/react/admin/Settings.tsx index 77d7e0b..cb63232 100644 --- a/frontend/src/components/react/admin/Settings.tsx +++ b/frontend/src/components/react/admin/Settings.tsx @@ -215,7 +215,7 @@ export default function Settings() { update('footer', v)} /> - diff --git a/frontend/src/pages/404.astro b/frontend/src/pages/404.astro index e39601d..56c4a4f 100644 --- a/frontend/src/pages/404.astro +++ b/frontend/src/pages/404.astro @@ -19,6 +19,6 @@ import Layout from '../layouts/Layout.astro'; The room you reached for has either been re-hung, withdrawn, or never made it to the wall in the first place.

- ↶ Return to the catalogue + ↶ Return to the catalogue diff --git a/frontend/src/pages/index.astro b/frontend/src/pages/index.astro index b8d95a0..9866d90 100644 --- a/frontend/src/pages/index.astro +++ b/frontend/src/pages/index.astro @@ -96,16 +96,16 @@ const isAdmin = Astro.cookies.get('admin_session')?.value === '1'; {isAdmin && (
- + New work - - + + Messages - + Settings @@ -128,7 +128,7 @@ const isAdmin = Astro.cookies.get('admin_session')?.value === '1';

Check back soon.

{isAdmin && ( - Create the first post + Create the first post )}
)} diff --git a/frontend/src/pages/posts/[slug].astro b/frontend/src/pages/posts/[slug].astro index bd4c80e..0ce26b7 100644 --- a/frontend/src/pages/posts/[slug].astro +++ b/frontend/src/pages/posts/[slug].astro @@ -84,7 +84,7 @@ const displayTitle = post ? (post.title || formatSlug(post.slug)) : 'Work';
Pardon —

{error}

- ← Return to the catalogue + ← Return to the catalogue
)} @@ -99,7 +99,7 @@ const displayTitle = post ? (post.title || formatSlug(post.slug)) : 'Work'; {isAdmin && (
- + Edit diff --git a/frontend/src/styles/global.css b/frontend/src/styles/global.css index 71d1860..bc01371 100644 --- a/frontend/src/styles/global.css +++ b/frontend/src/styles/global.css @@ -914,58 +914,98 @@ code, pre, kbd, samp { 0 14px 40px -24px rgba(0, 0, 0, 0.9); } -/* ───── Buttons ───── */ -.btn-stamp { +/* ───── Buttons — one system ───── + * Base .btn = layout + size + focus/disabled. One variant for color + * (--primary / --ghost / --danger), one size modifier (--sm / --lg), + * shape modifiers (--icon / --block). Never restyle buttons ad-hoc. */ +.btn { display: inline-flex; align-items: center; + justify-content: center; gap: 0.5rem; + height: 2.5rem; + padding: 0 1.2rem; font-family: var(--font-display); font-style: italic; font-weight: 500; font-size: 0.95rem; - padding: 0.55rem 1.2rem; + line-height: 1; + letter-spacing: 0.02em; + background: transparent; + color: var(--text); + border: 1px solid transparent; + border-radius: 1px; + text-decoration: none; + white-space: nowrap; + cursor: pointer; + transition: transform 0.15s ease, background 0.15s ease, color 0.15s ease, + border-color 0.15s ease, box-shadow 0.15s ease; +} +.btn:focus-visible { + outline: none; + border-color: var(--mauve); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--mauve) 35%, transparent); +} +.btn:disabled, +.btn[aria-disabled="true"] { + opacity: 0.55; + cursor: default; + pointer-events: none; +} +.btn svg { width: 1.05em; height: 1.05em; flex-shrink: 0; } + +/* Variants */ +.btn--primary { background: var(--mauve); color: var(--rosewater); - border: 1px solid var(--mauve); - border-radius: 1px; - letter-spacing: 0.02em; - text-decoration: none; - cursor: pointer; - transition: transform 0.15s ease, background 0.15s ease, box-shadow 0.15s ease; + border-color: var(--mauve); box-shadow: 0 4px 0 -2px color-mix(in srgb, var(--mauve) 60%, black); } -.btn-stamp:hover { +.btn--primary:hover { transform: translateY(-1px); background: var(--red); border-color: var(--red); box-shadow: 0 6px 0 -2px color-mix(in srgb, var(--red) 60%, black); } -.btn-stamp:active { +.btn--primary:active { transform: translateY(1px); box-shadow: 0 1px 0 -1px color-mix(in srgb, var(--mauve) 60%, black); } -.btn-ghost { - display: inline-flex; - align-items: center; - gap: 0.5rem; - font-family: var(--font-sans); - font-size: 0.82rem; - padding: 0.4rem 0.85rem; - background: transparent; +.btn--ghost { color: var(--subtext1); - border: 1px solid var(--surface2); - border-radius: 1px; - letter-spacing: 0.06em; - text-transform: uppercase; - text-decoration: none; - cursor: pointer; - transition: color 0.15s, border-color 0.15s, background 0.15s; + border-color: var(--surface2); + background: color-mix(in srgb, var(--surface0) 45%, transparent); } -.btn-ghost:hover { +.btn--ghost:hover { color: var(--mauve); - border-color: var(--mauve); - background: color-mix(in srgb, var(--mauve) 8%, transparent); + border-color: color-mix(in srgb, var(--mauve) 50%, var(--surface2)); + background: color-mix(in srgb, var(--surface0) 80%, transparent); } +.btn--danger { + color: var(--red); + border-color: color-mix(in srgb, var(--red) 40%, var(--surface2)); + background: color-mix(in srgb, var(--surface0) 45%, transparent); +} +.btn--danger:hover { + color: var(--rosewater); + background: var(--red); + border-color: var(--red); +} +/* Pressed/selected state for toggle & tab buttons */ +.btn.is-active { + color: var(--mauve); + border-color: color-mix(in srgb, var(--mauve) 55%, var(--surface2)); + background: color-mix(in srgb, var(--mauve) 14%, transparent); +} + +/* Sizes */ +.btn--sm { height: 2rem; padding: 0 0.85rem; font-size: 0.85rem; gap: 0.35rem; } +.btn--lg { height: 3rem; padding: 0 1.6rem; font-size: 1.05rem; } + +/* Shapes */ +.btn--icon { padding: 0; width: 2.5rem; } +.btn--icon.btn--sm { width: 2rem; } +.btn--block { width: 100%; } /* ───── Top-bar controls — one height, one language ───── */ .topbar-control {