The site for Sign in with Ethereum. One site, five things: the landing page, the documentation, the libraries, the blog, and the tools.
Built with VitePress 2. The docs use the default theme, because for reference material it is already the best thing available; everything else is hand-built on top of it.
pnpm install
pnpm dev # http://localhost:4321
pnpm build # → .vitepress/dist
pnpm preview
pnpm typecheckNode 22.12 or newer. VitePress 2 dropped Node 20 but ships no engines field, so
nothing will warn you.
.vitepress/
config.ts site config, nav, sidebar
rss.ts feed.rss, generated in buildEnd
og.ts per-page Open Graph images, generated in buildEnd
theme/
index.ts registers every global component
Layout.vue default theme + our slots
style/ tokens.css, base.css, vp.css, fonts.css
components/ the landing sections, the tools, the mark
data/ ecosystem, libraries, stories, categories, posts loader
lib/siwe/ the ERC-4361 parser, linter and fixer
lib/example.ts the one example message the whole site shares
mark/ the brand engine, vendored — see below
src/
index.md the landing page
contact.md the contact form, posting to the API below
docs/ the documentation
blog/ posts, one directory per category
ecosystem/ tools/ brand.md
public/ fonts, favicon, the three case-study marks
api/ the contact/newsletter service — see api/README.md
pnpm build generates a 1200 × 630 PNG for every Markdown page under the
build output's og/ directory and writes matching Open Graph and Twitter
metadata into every rendered page. pnpm og is a convenient alias for that build.
The mark engine. .vitepress/theme/mark/scanlines.js and icon.js are
byte-for-byte copies from
signinwithethereum/brand-experiments.
Do not edit them here — change them there and copy again, so the site and the
brand repository cannot drift. mark/index.ts is the only local file: it types
the engine and loads it on the client.
The validator. .vitepress/theme/lib/siwe/ is the linter that shipped on
docs.siwe.xyz, moved across unchanged. It was written as framework-free
TypeScript, so all of it ported; only the interface is new. Its Jest suites
(~1,500 lines) were left behind in the old repository and are worth bringing over.
An ecosystem entry — one object in .vitepress/theme/data/ecosystem.ts. No
logo: third-party marks arrive in mixed polarity and a wall of 90 of them says
less than 90 legible names. Add a note if there is something specific to say.
A blog post — a markdown file in src/blog/<category>/. The category comes
from the directory, so the URL and the category cannot disagree. Frontmatter
needs title, date and description; the byline is rendered by the layout.
A blog category — a directory, an index.md holding
<BlogIndex category="the-slug" />, and an entry in data/categories.ts. A
category with no posts is hidden rather than shown with a zero.
A docs page — a markdown file under src/docs/, plus a sidebar entry in
.vitepress/config.ts. Internal links are absolute and extensionless
(/docs/quickstart/), and ignoreDeadLinks is off, so a wrong one fails the
build rather than shipping.
Everything comes off the mark. It is a 31 × 9 grid with ink on rows 0, 2, 4, 6 and 8 — so the cell is the unit, section dividers are two hairlines a cell apart (an ink row and its gap, at page scale), and there are exactly four colour roles.
The tokens are named --canvas, --field, --ink and --accent on purpose:
those are the four names the mark engine reads, so a mark inherits whatever
palette it sits in with no mapping layer. A .screen panel rebinds them to the
screen palette and a mark inside one follows automatically.
--accent is #7f00f6 in both themes and is for fills. --accent-ui is the
readable sibling per theme — 7.4:1 on white, 7.6:1 on black — and is what carries
text and links.
Geist Sans for prose, Geist Mono for labels, values, code and anything a machine
wrote. Both variable, both self-hosted. See /brand.
Static build → nginx → Kamal, the same shape as the two sites this replaces.
cp .env.production.example .env.production # fill it in; it is gitignored
cp .kamal/secrets.example .kamal/secrets
pnpm kamal:setup # first time
pnpm kamal:deploy
pnpm kamal:logsconfig/deploy.yml claims APP_HOST — siwe.xyz — plus three retired names
the same container answers on only to redirect them: www.siwe.xyz,
next.siwe.xyz (the pre-release hostname for this rewrite) and docs.siwe.xyz
(the Docusaurus site this replaces). docs.siwe.xyz forwards the path
unchanged to siwe.xyz, where the path rules in nginx.conf move the old
documentation URLs under /docs/; the mapping therefore lives in one place.
The contact form and the newsletter signup post to a small companion service —
plain Node plus PostgreSQL, forwarding contact mail via Resend — that lives in
api/ and deploys as its own Kamal app (config/deploy.api.yml, the
kamal:api:* scripts) on the same server under API_HOST. The site build
bakes https://API_HOST in as the endpoint, so point DNS for it at
DEPLOY_HOST before the cutover. See api/README.md.
Do not commit .env.production. The repository this replaces committed its deploy
host and registry username.
MIT. The mark and the wordmark are covered by the brand guidance at /brand.