Paper and ink
Ponjo's design system. The page is quiet so the writing and the buddies stand out: a warm grey ramp, one ink, and colour only where it means something.
This page is rendered with the app's own web/app.css. Change the system there and here together.
Rules for changing the UI
For anyone, person or agent, who changes how the app looks.
- Reuse before adding. Use an existing class (
.pj-primary,.pj-secondary,.pj-icon-btn,.pj-input,.pj-segmented,.pj-switch,.pj-radio,.pj-panel…) before writing a new rule. - Colours are tokens. No hex,
rgb(),oklch(),blackorwhiteoutside the token blocks at the top ofapp.css. A colour with a new meaning gets a token there, and a line on this page, first. - Colour means something. Grey for the UI; ink for the one primary action and for what is on or selected; a buddy's own colour only for that buddy; red for danger and deletions (a buddy's deletions are grey); green for insertions and online; yellow for commented text; blue only for focus, text selection and links.
- Sizes come from the scales. Font sizes 11, 12, 13, 14, 16, 20, 24px; weights 400, 500, 600; radii 0, 4, 6, 10, 14px or full; spacing 2, 4, 6, 8, 12, 16, 24, 32, 48px; control heights 24, 28, 32px (36 and 40 for bars and big buttons).
- One of each control. Icon buttons are
.pj-icon-btn: 28px, 32 in the header, 24 inside a row. Menu and list rows share one size, set once inapp.css: 28px tall, 8px side padding, radius 6, 13px; grey on hover,--activewhen selected or current. - One primary button per view. Everything else is secondary, ghost or a link button.
- Sentence case. Labels and headings are never all caps.
- Both themes. Check light and dark; tokens flip on their own, so a change that needs a dark-only override is usually using the wrong token.
Colour
Surfaces and ink
Colours that mean something
Buddies
Each buddy has its own colour, the one in its face (see Avatars). It is set as --buddy on whatever belongs to that buddy: its @chip, edits, flag and the card it is working on. A run's edits show in the text as a revision: new text in its colour, removed text grey and struck through where it was.
Type
Inter for the UI, with each platform's own Chinese font behind it (PingFang SC, Microsoft YaHei UI, Noto Sans CJK SC; Songti SC, Noto Serif CJK SC for serif). The Markdown editor's page font (sans, serif or mono, set in that view) and its sizes in em are the writer's, not the UI's. A text file that is not Markdown shows as it is, in --mono at 14px by default (a size from 12 to 20 in its plugin).
Shape and depth
Components
Buttons
Toolbar: pressed is ink on grey
A doc that cannot be edited here (a teammate who can only read) shows no formatting tools and no Comment bubble, rather than tools that do nothing; what only reads stays: the outline, the word count, comments and history. For a teammate who can only read, the comments have no reply box, Resolve, Reopen or Undo, and history has no Restore.
Fields and choices
A stepper is a .pj-segmented with .pj-stepper: − value +, for a whole number in a range. A view's or rule's settings are rows of a switch, a segmented choice or a stepper; the first row of a group has a heading, .pj-setting-group: 12px, semibold, faint.
Settings with a page of their own
A group of settings with its own page, such as a view or a rule, is a row in a list: its name, what it is and whether it is on, the whole row one button with a chevron. Its page starts with .pj-back, the same Back as the history's.
One of several, with its settings
When exactly one of several is picked, such as the agent that runs every buddy, each is a row of .pj-choices: a .pj-radio (ink when picked), its name and what it is, the whole row its label. The picked one shows its settings under it, in .pj-choice-detail, lined up with its name; the others stay closed.
-
Model
Lists: current is ink on grey
A file or folder git ignores (build output, installed packages) is dimmed to --text-faint, not hidden; a filter leaves it out.
A sidebar is resized by dragging its edge: the cursor turns to col-resize and the edge shows a 2px --text-faint line; double-click the edge for the default width back.
Team panel
The Team button opens an .pj-panel, 360px: a head with its title and, for the owner of a project, Invite link and New link (both secondary); then .pj-people-rows in groups headed .pj-setting-group: you, Teammates, AI buddies, ending with New AI buddy. A request to join is the first teammate: its row says Asks to join, and under it Read or Edit (a .pj-segmented, Edit to start), Deny (secondary) and Allow, the panel's one primary button. A teammate's row says Can read or Can edit; its card has the same Read or Edit and Remove from this project (secondary, red). A teammate's page in a browser first asks their name in a card in the middle of the page.
Status bar
The plain view has a status bar under the page: .pj-statusbar, 28px, 12px text in --text-soft on --bg-soft, a hairline --border on top. The caret's place is on the left; on the right text buttons, 22px, grey on hover and ink on grey while pressed or open, and a menu above them in the .pj-block-menu style. Line numbers, whitespace marks (dots for spaces, arrows for tabs) are --text-faint; indent guides are --border-strong.
Text: comments, buddy edits, links, tasks
Invite the partners
Open the waitlist
A task list item has a box in place of its bullet: a .pj-radio's outline, square (radius 4); ink with a check when done. Clicking it ticks it.