Design system

Everything in static/ds.css, in the current theme. The painter's Dark button switches it.

Direction

Web 2.0, built on the 90s web. Things have clear edges and vivid colour, like the web around 2006, and sit on bones from the oekaki boards a decade earlier: the painter's window chrome, graph paper under everything, hard pixels. The site should feel like a place where people draw, not like a product dashboard that happens to show pictures.

  • Edges are drawn. Every box, control and bar has a line around it or between it and the next. Nothing is marked out by a shadow or a change of shade alone. Rows share one line between them, as the toolbar and its notices do.
  • Colour is vivid. The ground is NEO's lavender and night blue. The cucumber lime means "do this" and appears once per view. Orange is for counts, red for what cannot be undone. Use saturated colours from the palette rather than neutral greys.
  • The ground is graph paper. NEO's 14px grid runs under every page, so the site and the painter share one surface. Cards, fields and rows sit on it with solid fills and never let it show through.
  • Depth is shallow and hard. Shadows have no blur: a raised control sits on a 1px ledge, and a floating menu or dialog casts a 3px offset block. There are no soft, wide glows, and frosted glass appears only where the platform uses it, as in the macOS app's toolbar.
  • The 90s stay in the details. What belongs to drawing wears the painter's window: bevel, title bar, grab dots. Drawings keep square corners and a frame, and are never smoothed when scaled. Type is small and dense (13–14px for the chrome), and corners are rounded only a little.
  • No gradients. Every fill is one flat colour: no gloss on buttons, no fades on bars, no sheen on cards. Depth comes from lines and the one small shadow, never from shading. CSS may still use linear-gradient() as a tool when both stops are the same colour or meet at a hard edge: the grid's 1px lines, a flat tint laid over the ground, a slider's filled part. What you see must never fade from one colour into another. The one exception is the painter itself, whose title bars are NEO's and stay exactly as NEO draws them.
  • Native before custom. Use the browser's own element for a job whenever one exists (select, details, checkbox, radio, range, fieldset, table) and style it here, rather than building a widget from divs and script. Native elements work with the keyboard, screen readers and the phone's own pickers for free, and they survive old engines. Build a custom widget only when no native element does the job, or when the Firefox 56 floor rules the native one out.
  • Avoid: gradients, borderless flat cards, grey-on-white minimalism, pill-shaped or heavily rounded boxes, large empty margins, and thin light type.

It all has to hold in Firefox 56, the engine oekaki users keep for the old applets. That is one more reason to use lines and solid fills rather than filters and blends.

Colour

Ground and surfaces

--ds-ground
--ds-grid
--ds-surface
--ds-field
--ds-track
--ds-thumb

Ink

--ds-ink
--ds-muted
--ds-faint

Lines

--ds-line
--ds-line-strong
--ds-frame

Action and signals

--ds-accent
--ds-accent-hover
--ds-accent-ink
--ds-accent-line
--ds-focus
--ds-badge
--ds-danger
--ds-danger-hover

States, laid over what is beneath

--ds-hover
--ds-selected
--ds-press

Loading

--ds-skeleton
--ds-skeleton-drawing

The painter's window

--ds-neo-panel
--ds-neo-ink
--ds-neo-text
--ds-neo-light
--ds-neo-shadow
--ds-neo-title
--ds-neo-well

Notices: tint and the ink that reads on it

Success
--ds-success-*
Warning
--ds-warning-*
Danger
--ds-danger-*

Type

--ds-text-2xl · 오이카페 OEEEカフェ Oeee Cafe
--ds-text-xl · 오이카페 OEEEカフェ Oeee Cafe
--ds-text-l · 오이카페 OEEEカフェ Oeee Cafe
--ds-text-base · 오이카페 OEEEカフェ Oeee Cafe
--ds-text-m · 오이카페 OEEEカフェ Oeee Cafe
--ds-text-s · 오이카페 OEEEカフェ Oeee Cafe
--ds-text-xs · 오이카페 OEEEカフェ Oeee Cafe

Headings and text

Heading one · 그림 게시판

Heading two · お絵かき

Heading three

Body text is the page's own weight. A link in a sentence is underlined; strong and emphasis read as they do anywhere. Marked text for a search's match.

Help text, under a field or beside a count.

Inline code, and keys: G then R.

Space, corners and depth

Space: a 4px scale

--ds-space-1
--ds-space-2
--ds-space-3
--ds-space-4
--ds-space-5
--ds-space-6

Corners and shadows

radius-s
radius-m
shadow-raise
shadow-menu

Buttons

A link as a button

Native, with no class

Fields

Native, with no class

Drop-downs

Choices

3 128

Forms

A plain fieldset is a card and its legend the title; the submit is the lime one.

Profile

Native elements

Elements the browser already knows how to do, styled rather than rebuilt.

What is oekaki?

Drawing in the browser, on a small canvas, with a replay of every stroke.

An open disclosure

Opened by default with the open attribute.

62% 70%
A replay that renders differently from the canvas it was recorded on is the worst failure this codebase can produce.

ssh mini 'zsh -l -c deploy.sh'

Table

UserDrawingsJoined
날다람지1282024-03-02
Tandemaus412025-11-19
きゅうり72026-09-01

Notices

Flash messages and the htmx error banner, as rows of the header. The level is the colour; Info and Debug are plain, with the accent's dot.

  • Your drawing is saved as a draft.
  • Welcome, 날다람지!
  • This community is about to be archived.
  • The passwords did not match. 비밀번호가 일치하지 않습니다.

Surfaces

A card

Settings groups, forms, anything that is a box on the ground.

A card that is a stack

.ds-stack drops its children's own margins and puts one gap between each.

Whatever comes next is the same distance away.

Replay
What belongs to drawing wears the painter's chrome.

An alert, as hx-confirm asks through it

Delete this drawing? Its replay goes with it.

People

person() in person_macro.jinja: the name, then the handle as a pill. Everyone's pill is a ring; a supporter's is the accent, with their mark inside. Someone from here is @login_name; someone from another server keeps @[email protected]. handle() is the pill alone, for a line that says more after it. Short of room, a remote pill takes the next line and its name gives way to an ellipsis; its host never does.

Tandemaus@tandemaus
@tandemaus · a supporter's pill, as a page draws it once it has asked
Someone far@someone@example.social
@tandemaus · 3 days ago
Someone far@a_name_long_enough_to_run_out_of_room@example.social

Waiting

Loading