init elas atelier #1

Merged
nvrl merged 82 commits from ela into main 2026-05-18 13:55:42 +02:00
13 changed files with 99 additions and 82 deletions
Showing only changes of commit 288bf890dc - Show all commits
@@ -8,7 +8,7 @@ interface Props {
} }
export default function AssetsButton({ 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', label = 'Assets',
iconSize = 14, iconSize = 14,
}: Props) { }: Props) {
@@ -89,8 +89,7 @@ export default function AssetsButton({
type="button" type="button"
onClick={() => setOpen(false)} onClick={() => setOpen(false)}
aria-label="Close" aria-label="Close"
className="p-1.5 text-[var(--subtext0)] hover:text-[var(--text)] hover:bg-[var(--surface0)] transition-colors" className="btn btn--ghost btn--icon btn--sm"
style={{ borderRadius: 2 }}
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -169,7 +169,7 @@ export default function ContactForm() {
<button <button
type="submit" type="submit"
disabled={status === 'sending'} disabled={status === 'sending'}
className="btn-stamp disabled:opacity-60" className="btn btn--primary"
> >
{status === 'sending' ? 'Sending…' : 'Send message'} {status === 'sending' ? 'Sending…' : 'Send message'}
</button> </button>
@@ -31,7 +31,7 @@ export default function DeletePostButton({ slug, title, variant = 'full' }: Prop
disabled={busy} disabled={busy}
title="Delete post" title="Delete post"
aria-label="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"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -60,7 +60,7 @@ export default function DeletePostButton({ slug, title, variant = 'full' }: Prop
type="button" type="button"
onClick={handleClick} onClick={handleClick}
disabled={busy} disabled={busy}
className="inline-flex items-center gap-2 bg-surface0 hover:bg-red/15 text-subtext1 hover:text-red px-3 py-1.5 md:px-4 md:py-2 rounded-md border border-surface1 hover:border-red/30 transition-colors text-sm disabled:opacity-50" className="btn btn--danger btn--sm"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
+2 -4
View File
@@ -197,8 +197,7 @@ export default function PostList({ posts: initialPosts, isAdmin = false }: Props
onClick={e => e.stopPropagation()} onClick={e => e.stopPropagation()}
title="Edit" title="Edit"
aria-label={`Edit ${displayTitle}`} aria-label={`Edit ${displayTitle}`}
className="p-1.5 bg-[var(--mantle)] text-[var(--rosewater)] hover:bg-[var(--blue)] border border-[var(--surface2)] transition-colors" className="btn btn--ghost btn--icon btn--sm"
style={{ borderRadius: 1 }}
> >
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" /><path d="m15 5 4 4" /></svg> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" /><path d="m15 5 4 4" /></svg>
</a> </a>
@@ -208,8 +207,7 @@ export default function PostList({ posts: initialPosts, isAdmin = false }: Props
disabled={isDeleting} disabled={isDeleting}
title="Remove" title="Remove"
aria-label={`Remove ${displayTitle}`} 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" className="btn btn--danger btn--icon btn--sm"
style={{ borderRadius: 1 }}
> >
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 6h18" /><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /><line x1="10" x2="10" y1="11" y2="17" /><line x1="14" x2="14" y1="11" y2="17" /></svg> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 6h18" /><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /><line x1="10" x2="10" y1="11" y2="17" /><line x1="14" x2="14" y1="11" y2="17" /></svg>
</button> </button>
@@ -109,8 +109,7 @@ export default function AssetManager({ mode = 'manage', onSelect }: Props) {
{mode === 'select' && onSelect && ( {mode === 'select' && onSelect && (
<button <button
onClick={() => onSelect(asset)} onClick={() => onSelect(asset)}
className="bg-[var(--mauve)] hover:bg-[var(--red)] text-[var(--rosewater)] px-4 py-2 text-sm font-display italic font-medium transition-colors shadow-lg inline-flex items-center gap-1.5" className="btn btn--primary btn--sm"
style={{ borderRadius: 1 }}
> >
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
Insert Insert
@@ -118,8 +117,7 @@ export default function AssetManager({ mode = 'manage', onSelect }: Props) {
)} )}
<button <button
onClick={() => handleDelete(asset.name)} onClick={() => handleDelete(asset.name)}
className="bg-[var(--red)]/80 hover:bg-[var(--red)] text-[var(--rosewater)] p-2.5 transition-colors shadow-lg" className="btn btn--danger btn--icon btn--sm"
style={{ borderRadius: 1 }}
> >
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/></svg>
</button> </button>
+9 -27
View File
@@ -415,11 +415,11 @@ export default function Editor({ editSlug }: Props) {
{/* Actions bar */} {/* Actions bar */}
<div className="flex flex-wrap gap-4 mb-6"> <div className="flex flex-wrap gap-4 mb-6">
{originalSlug && ( {originalSlug && (
<button onClick={handleDelete} className="btn-ghost py-3 px-6 text-[var(--red)] border-[var(--red)]/40 hover:bg-[var(--red)]/10 hover:border-[var(--red)] hover:text-[var(--red)]"> <button onClick={handleDelete} className="btn btn--danger">
Remove Remove
</button> </button>
)} )}
<button onClick={handleSave} className="btn-stamp py-3 px-8 whitespace-nowrap"> <button onClick={handleSave} className="btn btn--primary">
Save work Save work
</button> </button>
{originalSlug && ( {originalSlug && (
@@ -427,7 +427,7 @@ export default function Editor({ editSlug }: Props) {
href={`/posts/${encodeURIComponent(originalSlug)}`} href={`/posts/${encodeURIComponent(originalSlug)}`}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
className="btn-ghost py-3 px-6 inline-flex items-center justify-center gap-2 whitespace-nowrap" className="btn btn--ghost"
> >
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
View work View work
@@ -521,12 +521,7 @@ export default function Editor({ editSlug }: Props) {
<button <button
type="button" type="button"
onClick={() => setVimEnabled(v => !v)} onClick={() => setVimEnabled(v => !v)}
className={`text-xs px-3 py-1.5 border transition-colors font-mono ${ className={`btn btn--sm${vimEnabled ? ' is-active' : ''}`}
vimEnabled
? 'bg-[var(--mauve)]/20 text-[var(--mauve)] border-[var(--mauve)]/30'
: 'bg-[var(--surface0)] text-[var(--subtext0)] border-[var(--surface2)] hover:text-[var(--text)]'
}`}
style={{ borderRadius: 2 }}
title={vimEnabled ? 'Vim mode ON' : 'Vim mode OFF'} title={vimEnabled ? 'Vim mode ON' : 'Vim mode OFF'}
> >
{vimEnabled ? 'VIM' : 'vim'} {vimEnabled ? 'VIM' : 'vim'}
@@ -534,19 +529,14 @@ export default function Editor({ editSlug }: Props) {
<button <button
type="button" type="button"
onClick={() => setShowPreview(p => !p)} onClick={() => setShowPreview(p => !p)}
className={`text-xs px-3 py-1.5 border transition-colors ${ className={`btn btn--sm${showPreview ? ' is-active' : ''}`}
showPreview
? 'bg-[var(--blue)]/20 text-[var(--blue)] border-[var(--blue)]/30'
: 'bg-[var(--surface0)] text-[var(--subtext0)] border-[var(--surface2)] hover:text-[var(--text)]'
}`}
style={{ borderRadius: 2 }}
> >
{showPreview ? 'Hide Preview' : 'Show Preview'} {showPreview ? 'Hide Preview' : 'Show Preview'}
</button> </button>
<button <button
type="button" type="button"
onClick={() => setShowModal(true)} onClick={() => setShowModal(true)}
className="btn-stamp text-sm py-1.5 px-4" className="btn btn--primary btn--sm"
title="Insert an image — also: drag an image into the editor, or paste from clipboard" title="Insert an image — also: drag an image into the editor, or paste from clipboard"
> >
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>
@@ -563,11 +553,7 @@ export default function Editor({ editSlug }: Props) {
role="tab" role="tab"
aria-selected={mobileView === 'edit'} aria-selected={mobileView === 'edit'}
onClick={() => setMobileView('edit')} onClick={() => setMobileView('edit')}
className={`flex-1 text-xs px-3 py-2 rounded border transition-colors ${ className={`btn btn--sm flex-1${mobileView === 'edit' ? ' is-active' : ''}`}
mobileView === 'edit'
? 'bg-blue/20 text-blue border-blue/30'
: 'bg-surface0 text-subtext0 border-surface1 hover:text-text'
}`}
> >
Edit Edit
</button> </button>
@@ -576,11 +562,7 @@ export default function Editor({ editSlug }: Props) {
role="tab" role="tab"
aria-selected={mobileView === 'preview'} aria-selected={mobileView === 'preview'}
onClick={() => setMobileView('preview')} onClick={() => setMobileView('preview')}
className={`flex-1 text-xs px-3 py-2 rounded border transition-colors ${ className={`btn btn--sm flex-1${mobileView === 'preview' ? ' is-active' : ''}`}
mobileView === 'preview'
? 'bg-blue/20 text-blue border-blue/30'
: 'bg-surface0 text-subtext0 border-surface1 hover:text-text'
}`}
> >
Preview Preview
</button> </button>
@@ -679,7 +661,7 @@ export default function Editor({ editSlug }: Props) {
<h2 className="font-display italic text-2xl md:text-3xl text-[var(--text)] leading-tight">Add image</h2> <h2 className="font-display italic text-2xl md:text-3xl text-[var(--text)] leading-tight">Add image</h2>
<p className="text-xs text-[var(--subtext0)] font-display italic mt-1">Click an image to insert it. Drag new files in to upload.</p> <p className="text-xs text-[var(--subtext0)] font-display italic mt-1">Click an image to insert it. Drag new files in to upload.</p>
</div> </div>
<button onClick={closeAssetModal} className="p-2 text-[var(--subtext0)] hover:text-[var(--red)] transition-colors"> <button onClick={closeAssetModal} className="btn btn--ghost btn--icon btn--sm">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
</button> </button>
</header> </header>
@@ -120,7 +120,7 @@ export default function Inbox() {
{m.email && ( {m.email && (
<a <a
href={`mailto:${m.email}${m.subject ? `?subject=${encodeURIComponent('Re: ' + m.subject)}` : ''}`} href={`mailto:${m.email}${m.subject ? `?subject=${encodeURIComponent('Re: ' + m.subject)}` : ''}`}
className="chip chip-accent uppercase" className="btn btn--ghost btn--sm"
> >
Reply Reply
</a> </a>
@@ -128,7 +128,7 @@ export default function Inbox() {
<button <button
type="button" type="button"
onClick={() => remove(m.id)} onClick={() => remove(m.id)}
className="chip text-[var(--red)]" className="btn btn--danger btn--sm"
> >
Delete Delete
</button> </button>
@@ -65,7 +65,7 @@ export default function Login() {
<button <button
type="submit" type="submit"
disabled={busy} disabled={busy}
className="btn-stamp w-full justify-center disabled:opacity-60 disabled:cursor-not-allowed" className="btn btn--primary btn--block"
> >
{busy ? 'Unlocking' : 'Enter'} {busy ? 'Unlocking' : 'Enter'}
</button> </button>
@@ -215,7 +215,7 @@ export default function Settings() {
<Field label="Footer text" value={config.footer || ''} onChange={v => update('footer', v)} /> <Field label="Footer text" value={config.footer || ''} onChange={v => update('footer', v)} />
</section> </section>
<button type="submit" className="btn-stamp"> <button type="submit" className="btn btn--primary">
Save site settings Save site settings
</button> </button>
</form> </form>
+1 -1
View File
@@ -19,6 +19,6 @@ import Layout from '../layouts/Layout.astro';
The room you reached for has either been re-hung, withdrawn,<br class="hidden md:block" /> The room you reached for has either been re-hung, withdrawn,<br class="hidden md:block" />
or never made it to the wall in the first place. or never made it to the wall in the first place.
</p> </p>
<a href="/" class="btn-stamp">↶ Return to the catalogue</a> <a href="/" class="btn btn--primary">↶ Return to the catalogue</a>
</div> </div>
</Layout> </Layout>
+5 -5
View File
@@ -96,16 +96,16 @@ const isAdmin = Astro.cookies.get('admin_session')?.value === '1';
{isAdmin && ( {isAdmin && (
<div class="mt-8 flex flex-wrap items-center gap-3"> <div class="mt-8 flex flex-wrap items-center gap-3">
<a href="/admin/editor" class="btn-stamp"> <a href="/admin/editor" class="btn btn--primary">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14"/><path d="M12 5v14"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14"/><path d="M12 5v14"/></svg>
New work New work
</a> </a>
<AssetsButton client:idle className="btn-ghost" iconSize={12} /> <AssetsButton client:idle className="btn btn--ghost btn--sm" iconSize={12} />
<a href="/admin/messages" class="btn-ghost"> <a href="/admin/messages" class="btn btn--ghost">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
Messages Messages
</a> </a>
<a href="/admin/settings" class="btn-ghost"> <a href="/admin/settings" class="btn btn--ghost">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/></svg>
Settings Settings
</a> </a>
@@ -128,7 +128,7 @@ const isAdmin = Astro.cookies.get('admin_session')?.value === '1';
</p> </p>
<p class="font-sans text-[var(--subtext1)] mt-4">Check back soon.</p> <p class="font-sans text-[var(--subtext1)] mt-4">Check back soon.</p>
{isAdmin && ( {isAdmin && (
<a href="/admin/editor" class="btn-stamp mt-8">Create the first post</a> <a href="/admin/editor" class="btn btn--primary mt-8">Create the first post</a>
)} )}
</div> </div>
)} )}
+2 -2
View File
@@ -84,7 +84,7 @@ const displayTitle = post ? (post.title || formatSlug(post.slug)) : 'Work';
<div class="max-w-2xl mx-auto py-20 md:py-32 text-center"> <div class="max-w-2xl mx-auto py-20 md:py-32 text-center">
<div class="font-display italic text-[var(--subtext0)] text-sm tracking-[0.3em] uppercase mb-4">Pardon —</div> <div class="font-display italic text-[var(--subtext0)] text-sm tracking-[0.3em] uppercase mb-4">Pardon —</div>
<h2 class="font-display italic text-3xl md:text-5xl text-[var(--mauve)] mb-6 leading-tight">{error}</h2> <h2 class="font-display italic text-3xl md:text-5xl text-[var(--mauve)] mb-6 leading-tight">{error}</h2>
<a href="/" class="btn-ghost">← Return to the catalogue</a> <a href="/" class="btn btn--ghost">← Return to the catalogue</a>
</div> </div>
)} )}
@@ -99,7 +99,7 @@ const displayTitle = post ? (post.title || formatSlug(post.slug)) : 'Work';
{isAdmin && ( {isAdmin && (
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<a href={`/admin/editor?edit=${encodeURIComponent(post.slug)}`} class="btn-ghost"> <a href={`/admin/editor?edit=${encodeURIComponent(post.slug)}`} class="btn btn--ghost">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>
Edit Edit
</a> </a>
+69 -29
View File
@@ -914,58 +914,98 @@ code, pre, kbd, samp {
0 14px 40px -24px rgba(0, 0, 0, 0.9); 0 14px 40px -24px rgba(0, 0, 0, 0.9);
} }
/* ───── Buttons ───── */ /* ───── Buttons — one system ─────
.btn-stamp { * 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; display: inline-flex;
align-items: center; align-items: center;
justify-content: center;
gap: 0.5rem; gap: 0.5rem;
height: 2.5rem;
padding: 0 1.2rem;
font-family: var(--font-display); font-family: var(--font-display);
font-style: italic; font-style: italic;
font-weight: 500; font-weight: 500;
font-size: 0.95rem; 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); background: var(--mauve);
color: var(--rosewater); color: var(--rosewater);
border: 1px solid var(--mauve); border-color: 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;
box-shadow: 0 4px 0 -2px color-mix(in srgb, var(--mauve) 60%, black); box-shadow: 0 4px 0 -2px color-mix(in srgb, var(--mauve) 60%, black);
} }
.btn-stamp:hover { .btn--primary:hover {
transform: translateY(-1px); transform: translateY(-1px);
background: var(--red); background: var(--red);
border-color: var(--red); border-color: var(--red);
box-shadow: 0 6px 0 -2px color-mix(in srgb, var(--red) 60%, black); box-shadow: 0 6px 0 -2px color-mix(in srgb, var(--red) 60%, black);
} }
.btn-stamp:active { .btn--primary:active {
transform: translateY(1px); transform: translateY(1px);
box-shadow: 0 1px 0 -1px color-mix(in srgb, var(--mauve) 60%, black); box-shadow: 0 1px 0 -1px color-mix(in srgb, var(--mauve) 60%, black);
} }
.btn-ghost { .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;
color: var(--subtext1); color: var(--subtext1);
border: 1px solid var(--surface2); border-color: var(--surface2);
border-radius: 1px; background: color-mix(in srgb, var(--surface0) 45%, transparent);
letter-spacing: 0.06em;
text-transform: uppercase;
text-decoration: none;
cursor: pointer;
transition: color 0.15s, border-color 0.15s, background 0.15s;
} }
.btn-ghost:hover { .btn--ghost:hover {
color: var(--mauve); color: var(--mauve);
border-color: var(--mauve); border-color: color-mix(in srgb, var(--mauve) 50%, var(--surface2));
background: color-mix(in srgb, var(--mauve) 8%, transparent); 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 ───── */ /* ───── Top-bar controls — one height, one language ───── */
.topbar-control { .topbar-control {