Skip to content

Commit 358a4de

Browse files
feat: floating top-right "Buy me a coffin" pill + footer polish
Adds a sticky CoffinPill button positioned top-right just below the ScannerBanner so the support link is visible without scrolling. On mobile it collapses to icon-only to save space. Footer redesigned for visual hierarchy: - Info links (About, Legal, Open source) on the left as a group - Social icons (X, Instagram) in the middle, text hidden on mobile - Buy me a coffin as a pill-shaped CTA on the right - Vertical hairline dividers between groups (hidden on mobile) - Tighter typography (12.5px) and consistent gaps New analytics event from_pill so we can compare top vs footer placement. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 91d9d56 commit 358a4de

4 files changed

Lines changed: 213 additions & 79 deletions

File tree

src/app/globals.css

Lines changed: 142 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@
2222
--c-border-light: #cec6bb; /* dividers, ghost buttons */
2323

2424
/* Accent */
25-
--c-red: #8B1A1A; /* dead / error */
25+
--c-red: #8B0000; /* dead / error */
2626
--c-green: #2d7a3c; /* alive */
2727
--c-amber: #b45309; /* struggling */
28-
--c-stamp-red: #8B1A1A; /* semantic alias for certificate stamp */
28+
--c-stamp-red: #8B0000; /* semantic alias for certificate stamp */
2929
}
3030

3131
[data-theme="dark"] {
@@ -41,14 +41,14 @@
4141
--c-faint: #4a4038; /* very quiet text */
4242

4343
/* Borders */
44-
--c-border: #3a3028; /* all solid borders — cards, inputs, buttons */
45-
--c-border-light: #2a241e; /* dividers, ghost buttons */
44+
--c-border: #6a5a4c; /* all solid borders — cards, inputs, buttons */
45+
--c-border-light: #3a3028; /* dividers, ghost buttons */
4646

4747
/* Accent */
48-
--c-red: #C0392B; /* dead / error */
48+
--c-red: #8B1A1A; /* dead / error */
4949
--c-green: #27ae60; /* alive */
5050
--c-amber: #d35400; /* struggling */
51-
--c-stamp-red: #C0392B;
51+
--c-stamp-red: #8B1A1A;
5252
}
5353

5454
/* ── Unified record / institutional card system ───────────────────── */
@@ -516,6 +516,142 @@ a.subpage-inline-mail {
516516
padding-top: 20px;
517517
}
518518

519+
/* Footer row: grouped sections separated by subtle vertical dividers */
520+
.footer-row {
521+
display: flex;
522+
align-items: center;
523+
justify-content: center;
524+
gap: 12px;
525+
flex-wrap: wrap;
526+
row-gap: 6px;
527+
}
528+
529+
.footer-group {
530+
display: inline-flex;
531+
align-items: center;
532+
gap: 10px;
533+
}
534+
535+
.footer-group--socials {
536+
gap: 8px;
537+
}
538+
539+
.footer-link {
540+
display: inline-flex;
541+
align-items: center;
542+
gap: 5px;
543+
font-family: var(--font-courier), system-ui, sans-serif;
544+
font-size: 12.5px;
545+
color: var(--c-muted);
546+
text-decoration: none;
547+
transition: color 0.15s;
548+
-webkit-tap-highlight-color: transparent;
549+
}
550+
551+
.footer-link:hover {
552+
color: var(--c-ink);
553+
}
554+
555+
.footer-sep {
556+
color: var(--c-border-light);
557+
font-size: 11px;
558+
user-select: none;
559+
}
560+
561+
.footer-divider {
562+
width: 1px;
563+
height: 12px;
564+
background: var(--c-border-light);
565+
display: inline-block;
566+
}
567+
568+
.footer-coffin {
569+
display: inline-flex;
570+
align-items: center;
571+
gap: 6px;
572+
padding: 5px 11px;
573+
font-family: var(--font-courier), system-ui, sans-serif;
574+
font-size: 12px;
575+
font-weight: 600;
576+
letter-spacing: 0.04em;
577+
color: var(--c-ink);
578+
background: transparent;
579+
border: 1.5px solid var(--c-ink);
580+
border-radius: 999px;
581+
text-decoration: none;
582+
transition: background 0.15s, color 0.15s;
583+
-webkit-tap-highlight-color: transparent;
584+
}
585+
586+
.footer-coffin:hover {
587+
background: var(--c-ink);
588+
color: var(--c-bg);
589+
}
590+
591+
/* Mobile: hide social link text, keep icons; drop dividers */
592+
@media (max-width: 640px) {
593+
.footer-row { gap: 8px; }
594+
.footer-group { gap: 8px; }
595+
.footer-link { font-size: 12px; }
596+
.footer-group--socials .footer-link-text { display: none; }
597+
.footer-divider { display: none; }
598+
.footer-coffin { padding: 5px 10px; font-size: 11.5px; }
599+
}
600+
601+
/* Floating top-right "Buy me a coffin" pill — sits just below the ScannerBanner */
602+
.coffin-pill {
603+
position: fixed;
604+
top: 44px;
605+
right: 14px;
606+
z-index: 50;
607+
display: inline-flex;
608+
align-items: center;
609+
gap: 6px;
610+
padding: 7px 12px;
611+
font-family: var(--font-courier), system-ui, sans-serif;
612+
font-size: 11.5px;
613+
font-weight: 600;
614+
letter-spacing: 0.06em;
615+
color: var(--c-ink);
616+
background: var(--c-bg);
617+
border: 1.5px solid var(--c-ink);
618+
border-radius: 999px;
619+
text-decoration: none;
620+
box-shadow: 0 1px 0 var(--c-border-light), 0 4px 12px rgba(0,0,0,0.06);
621+
transition: transform 0.15s, box-shadow 0.15s, background 0.15s, color 0.15s;
622+
-webkit-tap-highlight-color: transparent;
623+
animation: section-fade-up 600ms ease-out both;
624+
}
625+
626+
.coffin-pill:hover {
627+
background: var(--c-ink);
628+
color: var(--c-bg);
629+
transform: translateY(-1px);
630+
box-shadow: 0 1px 0 var(--c-border-light), 0 6px 16px rgba(0,0,0,0.12);
631+
}
632+
633+
.coffin-pill:active {
634+
transform: translateY(0);
635+
}
636+
637+
@media (max-width: 640px) {
638+
.coffin-pill {
639+
top: 40px;
640+
right: 10px;
641+
padding: 6px 10px;
642+
font-size: 11px;
643+
}
644+
.coffin-pill-text {
645+
display: none;
646+
}
647+
}
648+
649+
@media print {
650+
.coffin-pill {
651+
display: none !important;
652+
}
653+
}
654+
519655
/* Certificate view actions */
520656
.certificate-card-shell {
521657
width: min(100%, 480px);

src/app/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Script from 'next/script'
44
import { Analytics } from '@vercel/analytics/next'
55
import { SpeedInsights } from '@vercel/speed-insights/next'
66
import ScannerBanner from '@/components/ScannerBanner'
7+
import CoffinPill from '@/components/CoffinPill'
78
import './globals.css'
89

910
const spaceGrotesk = Space_Grotesk({
@@ -106,6 +107,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
106107
</head>
107108
<body className={`${spaceGrotesk.variable} ${unifraktur.variable} ${lora.variable} antialiased`}>
108109
<ScannerBanner />
110+
<CoffinPill />
109111
{children}
110112
<Analytics />
111113
<SpeedInsights />

src/components/CoffinPill.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use client'
2+
3+
import { track } from '@vercel/analytics'
4+
5+
export default function CoffinPill() {
6+
return (
7+
<a
8+
href="https://buymeacoffee.com/dotdevs"
9+
target="_blank"
10+
rel="noopener noreferrer"
11+
aria-label="Buy me a coffin — support the morgue"
12+
onClick={() => track('buy_me_a_coffin_clicked', { from: 'top_right_pill' })}
13+
className="coffin-pill"
14+
>
15+
<span aria-hidden style={{ fontSize: '13px', lineHeight: 1 }}></span>
16+
<span className="coffin-pill-text">Buy me a coffin</span>
17+
</a>
18+
)
19+
}

src/components/SiteFooter.tsx

Lines changed: 50 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,10 @@ import Link from 'next/link'
44
import { track } from '@vercel/analytics'
55
import GitHubIcon from '@/components/GitHubIcon'
66

7-
const FONT = `var(--font-courier), system-ui, sans-serif`
8-
97
interface SiteFooterProps {
108
compact?: boolean
119
}
1210

13-
const linkStyle = {
14-
fontFamily: FONT, fontSize: '13px', color: 'var(--c-muted)',
15-
textDecoration: 'none', display: 'inline-flex', alignItems: 'center',
16-
gap: '4px', transition: 'color 0.15s',
17-
WebkitTapHighlightColor: 'transparent',
18-
} as const
19-
2011
function XIcon({ size = 13 }: { size?: number }) {
2112
return (
2213
<svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor" aria-hidden>
@@ -38,77 +29,63 @@ function InstagramIcon({ size = 13 }: { size?: number }) {
3829
export default function SiteFooter({ compact = false }: SiteFooterProps) {
3930
return (
4031
<footer className={`site-footer${compact ? ' site-footer--compact' : ''}`}>
41-
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '14px', flexWrap: 'wrap' }}>
42-
<Link
43-
href="/about"
44-
style={linkStyle}
45-
onMouseEnter={e => (e.currentTarget.style.color = 'var(--c-ink)')}
46-
onMouseLeave={e => (e.currentTarget.style.color = 'var(--c-muted)')}
47-
>
48-
About
49-
</Link>
50-
<span style={{ color: 'var(--c-border-light)', fontSize: '12px' }}>·</span>
51-
<Link
52-
href="/legal"
53-
style={linkStyle}
54-
onMouseEnter={e => (e.currentTarget.style.color = 'var(--c-ink)')}
55-
onMouseLeave={e => (e.currentTarget.style.color = 'var(--c-muted)')}
56-
>
57-
Legal
58-
</Link>
59-
<span style={{ color: 'var(--c-border-light)', fontSize: '12px' }}>·</span>
60-
<a
61-
href="https://github.com/dotsystemsdevs/commitmentissues"
62-
target="_blank"
63-
rel="noopener noreferrer"
64-
style={linkStyle}
65-
onMouseEnter={e => (e.currentTarget.style.color = 'var(--c-ink)')}
66-
onMouseLeave={e => (e.currentTarget.style.color = 'var(--c-muted)')}
67-
>
68-
<GitHubIcon size={13} />
69-
Open source
70-
</a>
71-
<span style={{ color: 'var(--c-border-light)', fontSize: '12px' }}>·</span>
72-
<a
73-
href="https://x.com/Dotsystemsdevs"
74-
target="_blank"
75-
rel="noopener noreferrer"
76-
aria-label="Follow on X"
77-
onClick={() => track('social_clicked', { platform: 'x', from: 'footer' })}
78-
style={linkStyle}
79-
onMouseEnter={e => (e.currentTarget.style.color = 'var(--c-ink)')}
80-
onMouseLeave={e => (e.currentTarget.style.color = 'var(--c-muted)')}
81-
>
82-
<XIcon size={12} />
83-
X
84-
</a>
85-
<span style={{ color: 'var(--c-border-light)', fontSize: '12px' }}>·</span>
86-
<a
87-
href="https://www.instagram.com/dotsystemsdevs/"
88-
target="_blank"
89-
rel="noopener noreferrer"
90-
aria-label="Follow on Instagram"
91-
onClick={() => track('social_clicked', { platform: 'instagram', from: 'footer' })}
92-
style={linkStyle}
93-
onMouseEnter={e => (e.currentTarget.style.color = 'var(--c-ink)')}
94-
onMouseLeave={e => (e.currentTarget.style.color = 'var(--c-muted)')}
95-
>
96-
<InstagramIcon size={13} />
97-
Instagram
98-
</a>
99-
<span style={{ color: 'var(--c-border-light)', fontSize: '12px' }}>·</span>
32+
<nav className="footer-row" aria-label="Site footer">
33+
<div className="footer-group">
34+
<Link href="/about" className="footer-link">About</Link>
35+
<span className="footer-sep" aria-hidden>·</span>
36+
<Link href="/legal" className="footer-link">Legal</Link>
37+
<span className="footer-sep" aria-hidden>·</span>
38+
<a
39+
href="https://github.com/dotsystemsdevs/commitmentissues"
40+
target="_blank"
41+
rel="noopener noreferrer"
42+
className="footer-link"
43+
>
44+
<GitHubIcon size={13} />
45+
<span className="footer-link-text">Open source</span>
46+
</a>
47+
</div>
48+
49+
<span className="footer-divider" aria-hidden />
50+
51+
<div className="footer-group footer-group--socials">
52+
<a
53+
href="https://x.com/Dotsystemsdevs"
54+
target="_blank"
55+
rel="noopener noreferrer"
56+
aria-label="Follow on X"
57+
onClick={() => track('social_clicked', { platform: 'x', from: 'footer' })}
58+
className="footer-link footer-link--icon"
59+
>
60+
<XIcon size={12} />
61+
<span className="footer-link-text">X</span>
62+
</a>
63+
<a
64+
href="https://www.instagram.com/dotsystemsdevs/"
65+
target="_blank"
66+
rel="noopener noreferrer"
67+
aria-label="Follow on Instagram"
68+
onClick={() => track('social_clicked', { platform: 'instagram', from: 'footer' })}
69+
className="footer-link footer-link--icon"
70+
>
71+
<InstagramIcon size={13} />
72+
<span className="footer-link-text">Instagram</span>
73+
</a>
74+
</div>
75+
76+
<span className="footer-divider" aria-hidden />
77+
10078
<a
10179
href="https://buymeacoffee.com/dotdevs"
10280
target="_blank"
10381
rel="noopener noreferrer"
10482
onClick={() => track('buy_me_a_coffin_clicked', { from: 'footer' })}
105-
style={linkStyle}
106-
onMouseEnter={e => (e.currentTarget.style.color = 'var(--c-ink)')}
107-
onMouseLeave={e => (e.currentTarget.style.color = 'var(--c-muted)')}
83+
className="footer-coffin"
10884
>
109-
⚰ Buy me a coffin
85+
<span aria-hidden></span>
86+
<span>Buy me a coffin</span>
11087
</a>
111-
</div>
88+
</nav>
11289
</footer>
11390
)
11491
}

0 commit comments

Comments
 (0)