Skip to content
Merged
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
18 changes: 18 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,3 +296,21 @@ Every call prints a usage line to stderr and appends to
- **The free tier can run out mid-task.** A 402 means the shared credit
pool is empty and will not recover today; a 429 with `free_tier_quota`
resets at 00:00 UTC. Neither is worth retrying — switch to a key.

## Working on this repo

Everything above is about using the tool. If you are changing it:

- `make check` before proposing anything. It builds, vets, runs the Go
tests, and checks the rate card, the docs corpus, the gateway and the
site.
- **Touching `site/`?** Read `DESIGN.md` first — it is the material
contract: the tokens, the two gestures the page is built on, and the
responsive rules. `TASTE.md` holds prior rulings, each with the reason
it was rejected; read it before re-proposing something it already
refused. Run `site/bans.sh` before you ship (`make site-check` does).
- The site's HTML is generated. Edit `site/build.py`, not the committed
`index.html` files, and rerun `python3 site/build.py`.
- Visual changes get looked at in a browser at more than one width before
they are called done. The stylesheet has three breakpoints and a
document that must never scroll sideways.
117 changes: 117 additions & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# DESIGN — the material

Read this before touching anything under `site/`. `TASTE.md` holds prior
rulings — the things we tried and rejected, with why. This file holds the
material: the tokens, the laws they follow, and the two gestures the site
is built around.

The test for any change here: **a change that makes the surface prettier
and the task harder must fail.** The task on this site is reading
documentation.

If the material changes, rewrite this file in the same commit. A contract
that lies about the code is worse than no contract.

## Surfaces

| Surface | Class | Primary task | Protected functions |
| --- | --- | --- | --- |
| `/` overview | brand | decide whether to install this | the pitch, the badges, the four buttons, the first terminal transcript |
| `install`, `commands`, `formats`, `cost`, `agents` | product | find one command or one flag and copy it | tables and their third column, `.term` transcripts, inline `code`, anchors, the pager |
| `news` | product | see what changed and when | dates, version numbers, links out |
| `playground` | product | send a real request without a key | the prompt field, Send/Stop, the transcript, error text, quota readout |
| `404` | product | get somewhere real | the links |

Brand moves applied to product surfaces are the failure mode to watch.
Everything below the fold on the overview page is a product surface
wearing the brand page's clothes.

## The material

No framework, no build step for the reader, no JavaScript required to
read anything. The generator (`site/build.py`) is stdlib Python; the page
is one stylesheet and two scripts that only ever upgrade something already
on the page.

| Dimension | Value | Law |
| --- | --- | --- |
| Colour | one token block on `:root` in `style.css` | Every colour is a custom property, declared twice: the dark value alone, then the `light-dark()` pair. A browser without `light-dark()` keeps the dark value and the site still works. No colour anywhere else in the stylesheet. |
| Themes | `color-scheme: light dark`, toggle pins `only dark` / `only light` | The default follows the OS. The toggle is the word `theme: auto` cycling auto → light → dark, because two icons cannot express three states. |
| Accents | `--cyan` `--pink` `--purple` `--yellow` | Not shared between themes: the dark set is tuned for black and every one of them fails 4.5:1 on paper, so the light values are separately measured equivalents. |
| Type | JetBrains Mono 300/400/700, monospace fallback stack | One face. The whole identity rests on it — a proportional face here would be a different product. |
| Measure | `--measure: 68ch` inside a `62rem` wrap | The margin this leaves on a wide screen is where the depth gauge lives; nothing else goes there. |
| Radius | `3px`, plus `50%` for actual circles | One corner language. |
| Motion | `--dur-fast: 140ms`, `--dur-slow: 320ms`, `--ease: cubic-bezier(0.25, 0, 0.2, 1)` | Two durations and one curve for every transition. Fast answers the pointer; slow follows the theme or moves something. |
| Terminal blocks | `.term` carries a fixed palette and `color-scheme: only dark` | A picture of a terminal does not invert when the page does. `@media print` is the one exception — on paper it is text, not a screen. |
| Sea | `--sea-0`..`--sea-3`, `--sea-deep`, `--sea-abyss`, `--sea-foam`, `--whale*`, `--sky-star*` | `waves.js` paints by resolving these off `:root`, so both themes stay in the stylesheet and the toggle just works. Colour literals in that file are a defect. |

## The two gestures

The tool is named for two verbs and the page performs both. They are the
only motion on the site that is not a hover state, and both answer
something the reader did — nothing here moves at a reader sitting still
except the water itself, which is scenery.

**Deep — scrolling descends.** One scalar, `scroll.depth`, is scroll
position over the scrollable range. `waves.js` owns it, publishes it to
CSS as `--depth` on `:root`, and every part of the descent reads that one
number: the surface climbs out of the frame with parallax, the stars go
out over the first eighth, marine snow streams upward, the water darkens
toward `--sea-abyss`, and the whale sinks more slowly than the surface so
it is still there — dim — when you are in the dark. The gauge in the right
margin is the same number as text.

**Seek — clicking pings.** A click sends two things: a wave packet into
the water (what the water does) and a sonar ring from the pointer (what
you did). When the ring reaches the whale, the whale's outline answers and
fades. Down deep that echo is the only time you see it clearly, which is
the argument for the whole mechanism. Clicks on controls get the same
pulse from the control's own edge, in CSS, so the page has one click
language rather than one for scenery and one for buttons.

Both stop completely under `prefers-reduced-motion`: the sea paints one
still frame, no ping is emitted at all (a ring that cannot travel is a
circle left on the page), and no control pulses.

## Responsive

Four breakpoints, each answering a different question — which is why they
are not one number:

- **≥1100px** — the depth gauge appears in the right margin. It is the
only thing that ever goes there.
- **≤52rem** — the masthead stacks: brand row, then one horizontally
scrolling nav strip that bleeds to both edges with a mask fade as its
affordance. This is arithmetic, not comfort — brand, nav and toggle need
about 760px of row between them. The theme toggle lifts out of the strip
onto the brand row, because a preference control you have to scroll to
find is the one control on the site you cannot see. The masthead is
sticky, so a row saved here is a row saved on every screenful.
- **≤36rem** — tables get the same edge fade, because a table narrower
than its `34rem` minimum hides the column carrying the actual
explanation and puts the scrollbar thirteen rows below the fold.
- **≤640px** — type drops to 14px. Reading comfort, nothing structural.
- No page ever scrolls horizontally. `.term` blocks and tables scroll
inside themselves; the document does not. This is checkable and worth
checking after any layout change.

## Hard bans

`site/bans.sh`, wired into `make site-check`. Each one encodes a scar, not
a preference; the why is in `TASTE.md` or beside the code:

1. A hardcoded colour outside the token block.
2. A colour literal in `waves.js` outside a `var()` fallback.
3. A corner radius outside `3px` / `50%` / `inherit`.
4. A second font family.
5. An urgency animation — pulse, blink, bounce, flash, shake.
6. A missing `prefers-reduced-motion` block.

## Behavioral floor

Never traded away for a surface: text readable with JavaScript off, every
colour pair above 4.5:1 against its background in both themes, controls
that name their state rather than implying it with an icon, and a document
that does not move sideways. When a change is proposed, judge behaviour
before surfaces and count what it changes — a redesign that improves the
screenshot and costs a reader the third column of a table has lost.
6 changes: 4 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,9 @@ price-check:
# generator has its own --check; these cover the parts it cannot see —
# that the playground's script and markup still agree, that all three
# implementations of the enrolment puzzle still produce the same answers,
# and that the sea still runs, stops when it should, and draws the same
# whale on the canvas as whale.svg draws for readers without JavaScript.
# that the sea still runs, stops when it should, and draws the same whale
# on the canvas as whale.svg draws for readers without JavaScript, and
# that the design rules in DESIGN.md still describe the stylesheet.
.PHONY: site-check
site-check:
python3 site/build.py --check
Expand All @@ -110,6 +111,7 @@ site-check:
node site/playground.dom.test.js
node site/waves.test.js
node site/waves.dom.test.js
./site/bans.sh

.PHONY: site
site:
Expand Down
54 changes: 54 additions & 0 deletions TASTE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,66 @@ with its why — a rule without a why fossilizes into style police. Read
this before any design verdict; delete a scar when its expiry condition
arrives.

The material these rulings are about is in `DESIGN.md`.

The test behind all of them: a surface that gets prettier while the task
gets harder is a costume, and it fails no matter how clean it looks in a
screenshot.

---

## 2026-08-07 rejected: rebuilding the sea in three.js

**Why.** The obvious way to make a page about depth feel deep is WebGL: a
real volume, real caustics, real particles, and three.js is right there.
The cost is not the frame budget, it is everything the site currently is.
`waves.js` has no dependencies and no build step, it reads every colour it
paints from a CSS custom property so both themes live in the stylesheet,
its star and snow fields are seeded so a rendered frame is reproducible
and testable, and the whole thing degrades to a CSS gradient and an SVG
whale with JavaScript off. three.js is ~600KB before a line of ours, needs
a bundler on a site whose pitch includes not having one, cannot read the
theme without a colour bridge written by hand anyway, and turns a
deterministic frame into something no test can assert about. It would buy
a better-looking sea and sell the reasons the sea is good.

**Reuse.** The 2D canvas already had the right substrate — four sine
layers with a nearness gradient, a whale reading its height out of the
same field. Depth came from making those layers respond to scroll
position, which is arithmetic on numbers that were already there: about
sixty lines, no new dependency, and the descent is testable because the
fields are still seeded. Parallax, an abyss veil and marine snow do the
job WebGL was wanted for, at a few hundred sine calls a frame.

**Expires.** If the site ever grows something that genuinely needs a
scene graph — a 3D model of something, real lighting — this is worth
revisiting for that thing, on that page, not for the background.

---

## 2026-08-07 rejected: a scroll-position effect that is only an effect

**Why.** "Scrolling should feel like going deeper" is a mood, and the
first version of it was exactly that: the water darkened as you scrolled
and nothing else was true. It photographs well and it tells the reader
nothing they did not already know — which is the definition of the costume
this file exists to catch. On a docs page long enough to want the effect,
the reader's actual question is *how much of this is left*, and the native
scrollbar autohides on every platform that matters.

**Reuse.** The descent publishes one number, `--depth`, and the same
number is legible as text in the margin the 68ch measure was already
wasting. It is the scroll position — the thing the scrollbar shows — said
in the page's own vocabulary and left on screen. That is the difference
between an effect and an instrument: the effect makes you feel deep, the
instrument tells you how deep. We shipped the second one and got the first
for free.

**Expires.** If the gauge ever has to move over the text column to fit, it
has stopped being free and should be deleted rather than shrunk.

---

## 2026-08-05 rejected: one `ask` verb with `--api` to pick the format

**Why.** DeepSeek's four wire formats all answer "talk to the model", so
Expand Down
15 changes: 12 additions & 3 deletions site/404.html
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,15 @@
<a class="brand" href="https://thevibeworks.github.io/deepseek-cli/">
<span class="caret">&gt;</span><span class="org">thevibeworks/</span><span class="name">deepseek-cli</span>
</a>
<nav class="sitenav" aria-label="Sections">
<!-- Nav and toggle travel together — they are the controls end of the
masthead — but the toggle is beside the nav rather than inside it.
Two reasons, and the second is the load-bearing one: a theme
preference is not a section of the site, and on a phone the nav
becomes a scrolling strip with a mask on it, which would take the
toggle with it. A control nobody can see is worse than a control
in the wrong list. -->
<div class="bar-end">
<nav class="sitenav" aria-label="Sections">
<a href="https://thevibeworks.github.io/deepseek-cli/" aria-current="page">overview</a>
<a href="install/">install</a>
<a href="commands/">commands</a>
Expand All @@ -69,10 +77,11 @@
<a href="news/">news</a>
<a href="agents/">agents</a>
<a href="playground/">playground</a>
<a href="https://github.com/thevibeworks/deepseek-cli">github&nbsp;&#8599;</a>
<a href="https://github.com/thevibeworks/deepseek-cli">github&nbsp;&#8599;</a>
</nav>
<button class="themetoggle" id="theme-toggle" type="button" hidden
aria-label="Theme: auto. Click to change.">theme:&nbsp;<span class="val">auto</span></button>
</nav>
</div>
</div>
</header>
<div class="sea sea-band" data-ocean="band"></div><main id="main">
Expand Down
16 changes: 13 additions & 3 deletions site/agents/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,23 @@
</head>
<body>
<div class="sea" data-ocean></div>
<div class="gauge" data-depth-gauge aria-hidden="true"><span class="gauge-rail"><span class="gauge-fill"></span></span><span class="gauge-read">0 m</span></div>
<a class="skip" href="#main">skip to content</a>
<header class="masthead">
<svg class="mark" viewBox="0 6 32 20" width="22" height="14" aria-hidden="true"><path d="M5.6 8.2C4 7 2.4 7.6 3.3 9.5C5.2 13.7 8.7 17.8 12.1 20.7C13.3 21.8 14.1 23.3 14.6 24.9C15.2 23.5 16 22.8 17 22.4C18.1 22.7 19.1 23.5 19.9 24.6C20.7 22.9 21.6 21.2 23 19.5C25.4 16.5 27.9 13.6 29.2 10.5C30 8.5 28.4 7.5 26.7 8.7C23 11.4 19.3 14.9 16.9 18.4C14.3 14.7 9.9 10.5 5.6 8.2Z"/></svg>
<div class="wrap">
<a class="brand" href="../">
<span class="caret">&gt;</span><span class="org">thevibeworks/</span><span class="name">deepseek-cli</span>
</a>
<nav class="sitenav" aria-label="Sections">
<!-- Nav and toggle travel together — they are the controls end of the
masthead — but the toggle is beside the nav rather than inside it.
Two reasons, and the second is the load-bearing one: a theme
preference is not a section of the site, and on a phone the nav
becomes a scrolling strip with a mask on it, which would take the
toggle with it. A control nobody can see is worse than a control
in the wrong list. -->
<div class="bar-end">
<nav class="sitenav" aria-label="Sections">
<a href="../">overview</a>
<a href="../install/">install</a>
<a href="../commands/">commands</a>
Expand All @@ -63,10 +72,11 @@
<a href="../news/">news</a>
<a href="../agents/" aria-current="page">agents</a>
<a href="../playground/">playground</a>
<a href="https://github.com/thevibeworks/deepseek-cli">github&nbsp;&#8599;</a>
<a href="https://github.com/thevibeworks/deepseek-cli">github&nbsp;&#8599;</a>
</nav>
<button class="themetoggle" id="theme-toggle" type="button" hidden
aria-label="Theme: auto. Click to change.">theme:&nbsp;<span class="val">auto</span></button>
</nav>
</div>
</div>
</header>
<main id="main">
Expand Down
Loading