Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions docs/adr/0001-tipjar-support-button.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
public: false
---

# 0001: A floating tipjar button complements the footer's Ko-fi link

- **Status:** Accepted
- **Date:** 2026-11-07
- **PR:** (this PR)

## Context

The landing page's only support link was the footer's "☕ Support future projects" (Ko-fi), which most
visitors never scroll to. Matt's own `tipjar` package (npm, MIT, zero dependencies) draws a floating support
button. Options: replace the footer line with the button, add the button and keep the line, or leave it.
The site's CSP is `script-src 'self'` and the site self-hosts everything it loads.

## Decision

We add the tipjar button to the landing page and keep the footer line. Both go to https://ko-fi.com/mtblck,
and the button tucks itself away (`hideWhenVisible: 'footer'`) while the footer is on screen, so only one
shows at a time. `tools/site.mjs` copies `tipjar/dist/*.js` and `style.css` from node_modules into
`dist/site/tipjar/`, and `site/tip.js` (a module file) mounts it, so no CDN and no CSP change. It takes the
page's cobalt via `--tipjar-*` in `site.css`, so it follows `data-theme`. The docs pages don't get it: they
are for reading, and their footer has no support link to hand over to.

## Consequences

Upgrading the button is a `tipjar` version bump in package.json. Clicks are not counted: a new event name
would need adding to Boards' events list first. Revisit if the button crowds the playground on phones.
16 changes: 15 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@fontsource-variable/bricolage-grotesque": "^5.3.0",
"@fontsource-variable/jetbrains-mono": "^5.3.0",
"marked": "^16.4.2",
"playwright": "1.64.1"
"playwright": "1.64.1",
"tipjar": "^1.2.0"
}
}
2 changes: 2 additions & 0 deletions site/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<link rel="preload" as="font" type="font/woff2" crossorigin href="/fonts/bricolage.woff2" />
<script src="/theme.js"></script>
<script src="/analytics.js" defer></script>
<link rel="stylesheet" href="/tipjar/style.css" />
<link rel="stylesheet" href="/site.css" />
</head>
<body>
Expand Down Expand Up @@ -258,5 +259,6 @@ <h2 id="install-h">Install</h2>
<script src="/isogram.js"></script>
<script src="/examples.js"></script>
<script src="/site.js"></script>
<script type="module" src="/tip.js"></script>
</body>
</html>
9 changes: 9 additions & 0 deletions site/site.css
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,12 @@ footer p { margin: 0 0 8px; max-width: 60em; }

@media (prefers-reduced-motion: reduce) { * { scroll-behavior: auto !important; } }
.prose :not(pre) > code { overflow-wrap: anywhere; }

/* the floating support button (tipjar): the page's cobalt, ringed in paper; it tucks away at the footer */
:root {
--tipjar-bg: var(--accent);
--tipjar-label: var(--accent-ink);
--tipjar-ring: var(--paper);
--tipjar-font: var(--sans);
--tipjar-icon-size: 24px; /* pixel icons sit on a 12px grid */
}
12 changes: 12 additions & 0 deletions site/tip.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// getisogram.com: the floating "Support" button (the tipjar package, copied in by tools/site.mjs).
// It goes to the same Ko-fi page as the footer's support line and tucks itself away while the
// footer is on screen, so the two never show at once. A module from a file: the CSP is script-src 'self'.
import { mount } from '/tipjar/index.js'

mount({
href: 'https://ko-fi.com/mtblck',
description: 'future projects on Ko-fi',
icon: 'coffee',
iconStyle: 'pixel',
hideWhenVisible: 'footer',
})
2 changes: 1 addition & 1 deletion test/site.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const out = path.join(ROOT, 'dist', 'site')
test('the site builds', () => {
const r = spawnSync(process.execPath, [path.join(ROOT, 'tools/site.mjs')], { encoding: 'utf8' })
assert.equal(r.status, 0, r.stderr)
for (const f of ['index.html', 'docs/index.html', 'docs/skill/index.html', 'docs/api/index.html', 'docs/craft/index.html', 'isogram.js', 'examples.js', 'isogram.skill', 'fonts/bricolage.woff2', 'og.png'])
for (const f of ['index.html', 'docs/index.html', 'docs/skill/index.html', 'docs/api/index.html', 'docs/craft/index.html', 'isogram.js', 'examples.js', 'isogram.skill', 'fonts/bricolage.woff2', 'og.png', 'tip.js', 'tipjar/index.js', 'tipjar/style.css'])
assert.ok(fs.existsSync(path.join(out, f)), `${f} missing`)
})

Expand Down
8 changes: 7 additions & 1 deletion tools/site.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const LOGO = '<svg viewBox="0 0 32 32" aria-hidden="true"><path d="M16 3 28 10 1

// ---- landing page and static files ----
write('index.html', fs.readFileSync(src('index.html'), 'utf8').replaceAll('{{logo}}', LOGO))
for (const f of ['site.css', 'site.js', 'theme.js', 'analytics.js', 'docs.js', 'icon.svg', 'og.png']) if (fs.existsSync(src(f))) fs.copyFileSync(src(f), path.join(out, f))
for (const f of ['site.css', 'site.js', 'theme.js', 'analytics.js', 'docs.js', 'tip.js', 'icon.svg', 'og.png']) if (fs.existsSync(src(f))) fs.copyFileSync(src(f), path.join(out, f))
write('isogram.js', read('assets/isogram.js'))
write('examples.js', examples().map(f => read(`examples/${f}`)).join('\n'))
const files = skillFiles()
Expand All @@ -32,6 +32,12 @@ fs.mkdirSync(path.join(out, 'fonts'), { recursive: true })
font('@fontsource-variable/bricolage-grotesque', 'bricolage-grotesque-latin-standard-normal.woff2', 'bricolage.woff2')
font('@fontsource-variable/jetbrains-mono', 'jetbrains-mono-latin-wght-normal.woff2', 'jetbrains-mono.woff2')

// the floating support button (tipjar), self-hosted like the fonts: its ES modules and stylesheet
const tipjar = path.dirname(req.resolve('tipjar/package.json'))
fs.mkdirSync(path.join(out, 'tipjar'), { recursive: true })
for (const f of fs.readdirSync(path.join(tipjar, 'dist'))) if (f.endsWith('.js')) fs.copyFileSync(path.join(tipjar, 'dist', f), path.join(out, 'tipjar', f))
fs.copyFileSync(path.join(tipjar, 'style.css'), path.join(out, 'tipjar', 'style.css'))

// ---- docs: one page written for people (site/docs/), then the skill's own markdown, as Claude reads it ----
const PAGES = [
{ path: '/docs/', source: 'site/docs/start.md', nav: 'Using isogram', description: 'Install the isogram skill, ask Claude for a good diagram, embed it with one tag, and every attribute it takes.' },
Expand Down
Loading