DESIGN.md
Rama main · réplica de lectura del repositorio original.
---
name: Beehive Datasheet
description: "The Owner's datasheet, set dark. Records the design system in src/beehive/web/static/admin.css, the one stylesheet for every page, shared by three shells: the admin, the Owner's workspace (/research, /watchlist) and the reading pages (the home page, channel pages, archive, search and deep-read briefs). The sheet fills the screen at any width."
colors:
desk: "#0A0C0E"
sheet: "#111417"
band: "#191D21"
hover: "#161A1E"
field: "#0C0E10"
rule: "#262B30"
rule-2: "#3A4046"
rule-ink: "#C9CFD6"
ctl: "#666E76"
ctl-danger: "#B04A42"
ink: "#E7EAED"
ink-2: "#A7AFB7"
ink-3: "#808891"
on-fill: "#0A0C0E"
link: "#7DB6FF"
link-2: "#A9CEFF"
caution: "#F0B44C"
danger: "#FF6E61"
typography:
headline:
fontFamily: "Archivo, PingFang SC, Hiragino Sans GB, Noto Sans CJK SC, Noto Sans SC, Microsoft YaHei, Hiragino Kaku Gothic ProN, Noto Sans CJK JP, Apple SD Gothic Neo, Noto Sans CJK KR, system-ui, sans-serif"
fontSize: "1.5rem"
fontWeight: 700
lineHeight: 1.25
letterSpacing: "0"
title:
fontFamily: "Archivo, PingFang SC, Hiragino Sans GB, Noto Sans CJK SC, Noto Sans SC, Microsoft YaHei, Hiragino Kaku Gothic ProN, Noto Sans CJK JP, Apple SD Gothic Neo, Noto Sans CJK KR, system-ui, sans-serif"
fontSize: "1rem"
fontWeight: 700
lineHeight: 1.35
subhead:
fontFamily: "Archivo, PingFang SC, Hiragino Sans GB, Noto Sans CJK SC, Noto Sans SC, Microsoft YaHei, Hiragino Kaku Gothic ProN, Noto Sans CJK JP, Apple SD Gothic Neo, Noto Sans CJK KR, system-ui, sans-serif"
fontSize: "0.9375rem"
fontWeight: 700
lineHeight: 1.35
lead:
fontFamily: "Archivo, PingFang SC, Hiragino Sans GB, Noto Sans CJK SC, Noto Sans SC, Microsoft YaHei, Hiragino Kaku Gothic ProN, Noto Sans CJK JP, Apple SD Gothic Neo, Noto Sans CJK KR, system-ui, sans-serif"
fontSize: "1.0625rem"
fontWeight: 500
lineHeight: 1.75
body:
fontFamily: "Archivo, PingFang SC, Hiragino Sans GB, Noto Sans CJK SC, Noto Sans SC, Microsoft YaHei, Hiragino Kaku Gothic ProN, Noto Sans CJK JP, Apple SD Gothic Neo, Noto Sans CJK KR, system-ui, sans-serif"
fontSize: "0.9375rem"
fontWeight: 400
lineHeight: 1.55
reading:
fontFamily: "Archivo, PingFang SC, Hiragino Sans GB, Noto Sans CJK SC, Noto Sans SC, Microsoft YaHei, Hiragino Kaku Gothic ProN, Noto Sans CJK JP, Apple SD Gothic Neo, Noto Sans CJK KR, system-ui, sans-serif"
fontSize: "0.9375rem"
fontWeight: 400
lineHeight: 1.75
data:
fontFamily: "Archivo, PingFang SC, Hiragino Sans GB, Noto Sans CJK SC, Noto Sans SC, Microsoft YaHei, Hiragino Kaku Gothic ProN, Noto Sans CJK JP, Apple SD Gothic Neo, Noto Sans CJK KR, system-ui, sans-serif"
fontSize: "0.875rem"
fontWeight: 400
lineHeight: 1.45
note:
fontFamily: "Archivo, PingFang SC, Hiragino Sans GB, Noto Sans CJK SC, Noto Sans SC, Microsoft YaHei, Hiragino Kaku Gothic ProN, Noto Sans CJK JP, Apple SD Gothic Neo, Noto Sans CJK KR, system-ui, sans-serif"
fontSize: "0.8125rem"
fontWeight: 400
lineHeight: 1.6
button:
fontFamily: "Archivo, PingFang SC, Hiragino Sans GB, Noto Sans CJK SC, Noto Sans SC, Microsoft YaHei, Hiragino Kaku Gothic ProN, Noto Sans CJK JP, Apple SD Gothic Neo, Noto Sans CJK KR, system-ui, sans-serif"
fontSize: "0.8125rem"
fontWeight: 600
lineHeight: 1.2
label:
fontFamily: "Archivo, PingFang SC, Hiragino Sans GB, Noto Sans CJK SC, Noto Sans SC, Microsoft YaHei, Hiragino Kaku Gothic ProN, Noto Sans CJK JP, Apple SD Gothic Neo, Noto Sans CJK KR, system-ui, sans-serif"
fontSize: "0.78rem"
fontWeight: 600
lineHeight: 1.3
fontVariation: "'wdth' 85"
caption:
fontFamily: "Archivo, PingFang SC, Hiragino Sans GB, Noto Sans CJK SC, Noto Sans SC, Microsoft YaHei, Hiragino Kaku Gothic ProN, Noto Sans CJK JP, Apple SD Gothic Neo, Noto Sans CJK KR, system-ui, sans-serif"
fontSize: "0.78rem"
fontWeight: 400
tag:
fontFamily: "Archivo, PingFang SC, Hiragino Sans GB, Noto Sans CJK SC, Noto Sans SC, Microsoft YaHei, Hiragino Kaku Gothic ProN, Noto Sans CJK JP, Apple SD Gothic Neo, Noto Sans CJK KR, system-ui, sans-serif"
fontSize: "0.7rem"
fontWeight: 700
lineHeight: "15px"
numeric:
fontFamily: "Archivo, PingFang SC, Hiragino Sans GB, Noto Sans CJK SC, Noto Sans SC, Microsoft YaHei, Hiragino Kaku Gothic ProN, Noto Sans CJK JP, Apple SD Gothic Neo, Noto Sans CJK KR, system-ui, sans-serif"
fontSize: "0.875rem"
fontWeight: 400
lineHeight: 1.45
fontFeature: "'tnum'"
fontVariation: "'wdth' 88"
mono:
fontFamily: "ui-monospace, SFMono-Regular, Cascadia Mono, DejaVu Sans Mono, Menlo, Consolas, monospace"
fontSize: "0.8125rem"
fontWeight: 400
lineHeight: 1.75
rounded:
square: "0px"
control: "2px"
spacing:
gap: "8px"
cell: "10px"
row: "14px"
desk-margin: "16px"
gutter: "24px"
sheet-inset: "28px"
section: "32px"
section-gutter: "48px"
ledger-gap: "3.5rem"
components:
sheet:
backgroundColor: "{colors.sheet}"
textColor: "{colors.ink}"
rounded: "{rounded.square}"
padding: "16px 28px 36px"
button-primary:
backgroundColor: "{colors.ink}"
textColor: "{colors.on-fill}"
typography: "{typography.button}"
rounded: "{rounded.control}"
padding: "0 12px"
height: "32px"
button-primary-hover:
backgroundColor: "#FFFFFF"
textColor: "{colors.on-fill}"
button-primary-active:
backgroundColor: "{colors.rule-ink}"
textColor: "{colors.on-fill}"
button-secondary:
backgroundColor: "transparent"
textColor: "{colors.ink}"
typography: "{typography.button}"
rounded: "{rounded.control}"
padding: "0 12px"
height: "32px"
button-secondary-hover:
backgroundColor: "{colors.hover}"
textColor: "{colors.ink}"
button-secondary-active:
backgroundColor: "{colors.band}"
button-danger:
backgroundColor: "transparent"
textColor: "{colors.danger}"
typography: "{typography.button}"
rounded: "{rounded.control}"
padding: "0 12px"
height: "32px"
button-danger-hover:
backgroundColor: "{colors.danger}"
textColor: "{colors.on-fill}"
button-small:
padding: "0 9px"
height: "26px"
button-disabled:
backgroundColor: "transparent"
textColor: "{colors.ink-3}"
link:
textColor: "{colors.link}"
link-hover:
textColor: "{colors.link-2}"
link-danger:
textColor: "{colors.danger}"
input:
backgroundColor: "{colors.field}"
textColor: "{colors.ink}"
rounded: "{rounded.control}"
padding: "6px 10px"
height: "34px"
input-line:
backgroundColor: "{colors.field}"
textColor: "{colors.ink}"
rounded: "{rounded.control}"
padding: "6px 10px"
height: "34px"
rail:
backgroundColor: "{colors.sheet}"
width: "216px"
padding: "18px 12px 16px"
rail-link:
textColor: "{colors.ink-2}"
rounded: "{rounded.control}"
padding: "4px 8px"
height: "32px"
rail-link-hover:
backgroundColor: "{colors.hover}"
textColor: "{colors.ink}"
rail-link-current:
backgroundColor: "{colors.band}"
textColor: "{colors.ink}"
rail-count:
textColor: "{colors.caution}"
rounded: "{rounded.control}"
padding: "0 4px"
height: "18px"
rail-count-info:
textColor: "{colors.link}"
rounded: "{rounded.control}"
padding: "0 4px"
height: "18px"
rail-sublink-current:
backgroundColor: "{colors.band}"
textColor: "{colors.ink}"
rounded: "{rounded.control}"
height: "28px"
running-head:
textColor: "{colors.ink-2}"
typography: "{typography.caption}"
params-head:
backgroundColor: "{colors.band}"
textColor: "{colors.ink-2}"
typography: "{typography.label}"
padding: "6px 10px"
param-row:
typography: "{typography.data}"
padding: "14px 10px"
param-note:
textColor: "{colors.ink-2}"
typography: "{typography.note}"
tag-modified:
textColor: "{colors.link}"
typography: "{typography.tag}"
rounded: "{rounded.control}"
padding: "0 5px"
tag-unread:
textColor: "{colors.link}"
typography: "{typography.tag}"
rounded: "{rounded.control}"
padding: "0 5px"
savebar:
backgroundColor: "{colors.sheet}"
padding: "12px 10px"
table-head:
backgroundColor: "{colors.band}"
textColor: "{colors.ink-2}"
typography: "{typography.label}"
padding: "7px 10px"
table-cell:
typography: "{typography.data}"
padding: "{spacing.cell}"
table-row-hover:
backgroundColor: "{colors.hover}"
note-tag-info:
backgroundColor: "{colors.ink-2}"
textColor: "{colors.on-fill}"
padding: "6px 10px"
note-tag-caution:
backgroundColor: "{colors.caution}"
textColor: "{colors.on-fill}"
padding: "6px 10px"
note-tag-danger:
backgroundColor: "{colors.danger}"
textColor: "{colors.on-fill}"
padding: "6px 10px"
note-tag-done:
backgroundColor: "{colors.rule-ink}"
textColor: "{colors.on-fill}"
padding: "6px 10px"
note-body:
typography: "{typography.data}"
padding: "10px 14px"
confirm-panel:
backgroundColor: "{colors.field}"
padding: "12px 14px"
popover:
backgroundColor: "{colors.sheet}"
textColor: "{colors.ink}"
rounded: "{rounded.square}"
width: "460px"
popover-head:
backgroundColor: "{colors.danger}"
textColor: "{colors.on-fill}"
padding: "10px 14px"
empty-state:
textColor: "{colors.ink-2}"
padding: "18px 14px"
segmented-filter:
textColor: "{colors.ink-2}"
rounded: "{rounded.control}"
padding: "0 11px"
height: "32px"
segmented-filter-hover:
backgroundColor: "{colors.hover}"
textColor: "{colors.ink}"
segmented-filter-current:
backgroundColor: "{colors.band}"
textColor: "{colors.ink}"
session-tabs:
textColor: "{colors.ink-2}"
rounded: "{rounded.control}"
padding: "4px 8px"
height: "38px"
session-tabs-current:
backgroundColor: "{colors.band}"
textColor: "{colors.ink}"
lot-photo:
backgroundColor: "{colors.field}"
rounded: "{rounded.square}"
width: "80px"
height: "60px"
lot-where:
textColor: "{colors.ink-3}"
typography: "{typography.caption}"
price-facts:
textColor: "{colors.ink-2}"
typography: "{typography.caption}"
reading-column:
textColor: "{colors.ink}"
typography: "{typography.reading}"
width: "72ch"
answer-lead:
textColor: "{colors.ink}"
typography: "{typography.lead}"
citation:
textColor: "{colors.link}"
citation-hover:
textColor: "{colors.link-2}"
turn:
padding: "16px 0"
turn-label:
textColor: "{colors.ink-2}"
typography: "{typography.label}"
width: "104px"
notebook-side:
width: "300px"
side-facts-row:
padding: "7px 0"
evidence-snippet:
textColor: "{colors.ink-2}"
typography: "{typography.note}"
evidence-group-row:
textColor: "{colors.ink-2}"
typography: "{typography.label}"
padding: "14px 10px 4px"
evidence-row-excluded:
textColor: "{colors.ink-3}"
run-reason:
textColor: "{colors.danger}"
typography: "{typography.data}"
plan-latest:
padding: "12px 10px 4px"
skeleton-line:
backgroundColor: "{colors.band}"
height: "10px"
hint:
textColor: "{colors.ink-3}"
pager-field:
width: "80px"
height: "28px"
rail-tally:
textColor: "{colors.ink-3}"
rail-tally-current:
textColor: "{colors.ink-2}"
meta-link:
textColor: "{colors.ink-2}"
meta-link-hover:
textColor: "{colors.ink}"
head-search:
backgroundColor: "{colors.field}"
textColor: "{colors.ink}"
rounded: "{rounded.control}"
padding: "6px 10px"
width: "260px"
height: "32px"
desk-state:
textColor: "{colors.ink-2}"
read-toggle:
rounded: "{rounded.control}"
size: "22px"
reading-touch-target:
size: "44px"
reading-action-notice:
backgroundColor: "{colors.sheet}"
borderColor: "{colors.link}"
textColor: "{colors.ink}"
padding: "10px 14px"
width: "42rem"
read-toggle-hover:
backgroundColor: "{colors.band}"
unread-mark:
backgroundColor: "{colors.link}"
size: "8px"
unread-mark-hover:
backgroundColor: "{colors.link-2}"
score:
textColor: "{colors.ink-2}"
score-high:
textColor: "{colors.ink}"
score-low:
textColor: "{colors.ink-3}"
story-summary:
textColor: "{colors.ink}"
story-summary-hover:
textColor: "{colors.link-2}"
story-summary-read:
textColor: "{colors.ink-2}"
deep-read-pending:
textColor: "{colors.ink-2}"
comment-disclosure:
textColor: "{colors.link}"
typography: "{typography.caption}"
price-now:
textColor: "{colors.ink}"
price-was:
textColor: "{colors.ink-3}"
price-change:
textColor: "{colors.link}"
table-row-selected:
backgroundColor: "{colors.band}"
keycap:
backgroundColor: "{colors.field}"
textColor: "{colors.ink-2}"
rounded: "{rounded.control}"
padding: "0 4px"
toggle:
backgroundColor: "transparent"
textColor: "{colors.ink-2}"
rounded: "{rounded.control}"
padding: "0 9px"
height: "26px"
toggle-hover:
textColor: "{colors.ink}"
toggle-pressed:
backgroundColor: "{colors.band}"
textColor: "{colors.ink}"
toggle-negative-pressed:
backgroundColor: "{colors.band}"
textColor: "{colors.danger}"
toggle-mark:
size: "7px"
reason-field:
backgroundColor: "{colors.field}"
textColor: "{colors.ink}"
rounded: "{rounded.control}"
height: "30px"
width: "44rem"
filter-label:
textColor: "{colors.ink-2}"
typography: "{typography.label}"
filter-text-field:
width: "40rem"
filter-select:
width: "20rem"
multiselect:
backgroundColor: "{colors.field}"
textColor: "{colors.ink}"
rounded: "{rounded.control}"
width: "22rem"
multiselect-panel:
backgroundColor: "{colors.sheet}"
rounded: "{rounded.control}"
padding: "8px"
width: "28rem"
height: "340px"
multiselect-option-hover:
backgroundColor: "{colors.hover}"
history-disclosure:
textColor: "{colors.link}"
history-disclosure-hover:
textColor: "{colors.link-2}"
why-line:
textColor: "{colors.ink-2}"
section-columns:
width: "60rem"
ledger-column:
width: "56rem"
ledger-facts:
textColor: "{colors.ink-2}"
ledger-label:
textColor: "{colors.ink-3}"
typography: "{typography.caption}"
brief-head:
typography: "{typography.subhead}"
figure-value:
textColor: "{colors.ink}"
figure-label:
textColor: "{colors.ink-2}"
brief-status:
width: "72ch"
error-sheet:
backgroundColor: "{colors.sheet}"
textColor: "{colors.ink}"
rounded: "{rounded.square}"
padding: "16px 28px 36px"
width: "560px"
error-code:
textColor: "{colors.ink-3}"
admin-button:
height: "36px"
admin-button-small:
height: "32px"
admin-button-touch:
height: "44px"
admin-task-tab:
textColor: "{colors.ink-2}"
rounded: "{rounded.control}"
padding: "6px 14px"
height: "40px"
admin-task-tab-current:
backgroundColor: "{colors.band}"
textColor: "{colors.ink}"
admin-task-tab-mobile:
padding: "6px 10px"
height: "44px"
admin-task-form:
width: "60rem"
admin-param-row:
padding: "20px 0"
admin-savebar:
backgroundColor: "{colors.sheet}"
padding: "14px 0"
---
# Design System: Beehive Datasheet
> **Scope.** This file records one datasheet system with three shells. The admin is every page under `/admin/` plus the admin login. The Owner's workspace is `/research` (the session list, a session's four pages, the new-research form, the run-budget preview and the source form) and `/watchlist`. The reading shell holds every public reading page: the home page (`/`, 精选, with its channel desk and its ranked list), the channel pages (`/channels/{id}` for news, store monitors and auction trackers), the archive (`/archive`), search (`/search`) and the deep-read briefs (`/items/{id}/brief`). `/channels/` redirects to the home page. Error and 404 pages are one narrow sheet without the rail. All three shells are styled only by `src/beehive/web/static/admin.css`, all three render through the shared document `templates/datasheet_base.html` (extended by `admin_base.html`, `workspace_base.html` and `reading_base.html`, whose body class is `page-reading`), and all three share behaviour in `src/beehive/web/static/beehive.js`. There is no other stylesheet. `base.html`, `beehive.css` and their partials are gone.
## Overview
**Creative North Star: "The Owner's Datasheet"**
Beehive reads like a technical datasheet for one person's pipeline, set dark. A graphite sheet lies on a darker desk. A contents rail numbers the chapters, and a running head gives the location and the Auckland clock that every schedule is read against. The admin Channel workspace separates five persistent tasks, using stacked fields and purposeful tables rather than a long configuration sheet. Legacy parameter forms retain their label, value and note structure. Shared body type remains 15px; admin task controls and spacing are roomier than the reading and research shells.
The workspace is the same sheet with its own rail of two chapters, 1 研究 and 2 关注列表. A research session reads like a notebook. The conclusion is the first turn of a transcript, follow-up questions continue below it, and on a desktop window wider than 1180px a sticky side column shows the source behind a citation. Long AI text sits at a 72ch reading measure, and citations are small blue numbers. The watch list is a banded table of lots by deadline. Each lot's reminder state sits under its deadline, and failed reminders are boxed above the list.
The reading shell is the same sheet again, for the Owner and for anonymous readers, and it holds every public reading page. Its rail opens with 1 精选, gives every Channel a chapter of its own and ends with the archive. Every reading page is numbered sections whose headings carry their state or count. The home page is a channel desk. Each Channel gets a numbered section (1.1 on) whose heading says the Channel's state, with its best few rows beneath as stories, live listings or open lots. A Channel's own page numbers its parts, the archive its Auckland days, search its Channels and a deep-read brief its findings, and the rail lists them under the current chapter. A story leads with its AI summary, and a score is a bare tabular numeral. For the Owner, a small blue square marks a story unread and is also the switch that marks it read, in place, and relevance and watch are words with a square that fills when on. Readers never see the Owner's controls. Nothing on a reading page is cut short.
The sheet fills the window at any width, and what sits on it keeps a readable shape. Prose holds its 72ch measure and fields their width. Sections that stand alone flow side by side. On a wide screen a long list splits into two lanes that each fit the window and scroll on their own, and sections side by side become lanes the same way, so finishing one never moves another. A store's photos open as a gallery of ruled plates, each photo whole.
Colour is a signal. Ink on graphite carries everything that is fine. Amber appears only where something waits on the Owner, red only for failure, a destructive action or the Owner's pressed 不相关, and blue for links, focus, unsaved edits, unread results and stories, and a listing's latest change. State is always a shape plus a word. Controls have 2px corners and everything else is square. Depth comes from tone. Only the two things that open over the page cast a shadow, the removal popover and a multiselect's checklist.
The system refuses the card-grid SaaS console and the chat window, so there are no tiles, no dashboards of boxes, no chat bubbles and no decorative colour. The Owner visits admin rarely, so every page says what it controls and what state it is in. Unsaved rows are marked and counted, and destructive rows open in place into a confirmation that says what goes and whether it can be undone.
**Key Characteristics:**
- Graphite sheet on a darker desk, flat and tonal.
- One document, three shells: the admin (管理), the Owner's workspace (工作区) and the reading pages (阅读), each with its own numbered rail and footer.
- Admin Channel tasks use stacked forms with nearby hints and errors; legacy parameter layouts remain documented below. Lists retain banded tables under a heavy ink rule.
- Admin ranking saves affect future ranking by default; sample preview and explicitly confirmed reprocessing are separate actions.
- A research conversation is a transcript with a label column, conclusion first, beside a sticky side column for the cited source.
- The home page is a channel desk: a numbered section per Channel, its state in the heading and its best rows beneath. Every other reading page is numbered sections too, listed in the rail under its chapter.
- A story leads with its AI summary. A score is a bare tabular numeral, and unread is a small blue square that is also the read toggle.
- Two-state controls (relevance, watch) are a word with a 7px square that fills when on.
- The sheet has no maximum width. Sections that stand alone flow side by side, and on a wide sheet a long list and sections side by side become lanes that each scroll on their own.
- A store's or auction's listings can be a gallery: ruled plates with the whole photo on the field fill, the one grid of boxes the system allows.
- Long AI text at a 72ch measure, with small blue citation numbers.
- Boxed notes tagged 说明 / 注意 / 警告 / 完成.
- Colour only where the Owner is needed. Amber for attention, red for failure and a pressed 不相关, blue for links, focus, unread results and stories, and a listing's latest change.
- State is a square mark plus a word.
- Archivo throughout, condensed only in column heads, labels and numbers, with tabular figures wherever numbers change.
- 2px corners on controls, square containers.
- Nothing overflows at 390, 1024, 1440, 2560 or 3840px, units never split, and nothing on a reading page is cut short.
**Admin record (2026-10-28).** This is a CODE-LED extension of the existing world, preserving seed `9e52a516`, not a new identity or an approved comp. The update records the finished local Channel, Source, rules and Email implementation. The supplied ship verdict covers eight resolved material corrections, not every admin route. Public reading and research/workspace authority and existing global tokens are unchanged. Capture and validation provenance lives in `.impeccable/surfaces/src-beehive-web-templates-admin-base-html.md`; this is not a deployed-release record.
## Colors
A restrained graphite palette with three functional signals, and each signal has one meaning.
### Primary
- **Datasheet Blue** (#7DB6FF): the only interactive colour. It marks links, the focus outline and field ring, the 已修改 tag on changed rows, the unsaved count in the save bar, the research unread count (in the admin and reading footers, on the workspace rail and as the unread tag on a session), citation numbers and the outline of the one in view, and in-progress state marks. On the reading pages it also marks an unread story (the 8px square that is also its read toggle), the change line under a listing's price (降价, 重新有货, 新增), the best-comment disclosure, the history disclosure (展开 N 条), the border of an open multiselect, and a lot the Owner watches on the desk (已关注, as the in-progress mark). It is never a fill, except for that one 8px unread square.
- **Pale Datasheet Blue** (#A9CEFF): link hover, and hover on names in tables, lot titles, citation numbers and cited titles. On the reading pages it is also the hover on a story's summary, the channel column, the unread square and the best-comment and history disclosures.
### Secondary
- **Attention Amber** (#F0B44C): something waits on the Owner. It marks paused sources, warnings, caution notes (注意) and the attention counts on the contents rail, such as failed reminders on 2 关注列表. On the watch list it also marks the deadline of a lot that closes within the hour. Admin state legends may show it on a healthy page to explain the paused mark.
### Tertiary
- **Fault Red** (#FF6E61): failure and destruction. It marks failed fetches, failed runs and reminders, a run's failure reason, error lines, danger notes (警告), danger buttons and links, the removal popover, invalid fields and field errors. On the reading pages it also marks a failed deep read's 重试 and a pressed 不相关 toggle, the Owner's rejection of an item.
- **Danger Stroke** (#B04A42): the resting boundary of danger buttons, and the border of a pressed 不相关 toggle. It is darker than Fault Red so a danger button stays quiet until hovered, and it holds at least 3.15:1 on every admin surface.
### Neutral
- **Desk Black** (#0A0C0E): the desk under the sheet, and the page background behind the shell.
- **Graphite Sheet** (#111417): the sheet, the contents rail, the save bar and the popover body. It is also the tile behind the mark in the favicon and the touch icon.
- **Header Band** (#191D21): table and parameter head rows, the current rail entry and rail sub-page, the current segment of a segmented filter and the current session tab, checked selection rows, prefix cells, highlighted rows and skeleton lines. On the reading pages it also sits behind a hovered read toggle, fills a pressed two-state toggle and sits under a story row selected from the keyboard.
- **Hover Wash** (#161A1E): hover on table rows, rail entries, filter segments, buttons and a multiselect's options.
- **Recessed Field** (#0C0E10): input wells, confirmation panels, raw errors, the plain-text preview, the well behind a lot photo or a gallery plate's photo, and keycaps. It sits darker than the sheet, so fields read as sunk.
- **Hairline** (#262B30): row and turn dividers, side-facts rows, the line between a deadline and its reminder or watch state, the sheet and rail edges, facts dividers, raw-error boxes, lot photo edges, and the faded border of disabled buttons. On the reading pages it also draws the rules above and below a filter bar, the rows of a brief's figures, the rule between two lanes and the lines between their entries, and the ruled grid of gallery plates with each plate photo's edge.
- **Rule Stroke** (#3A4046): structural lines only. It draws the line under table heads and the parameter head row, the save bar's top edge, the confirmation panel's top edge, the plain-text preview border, the dividers inside a segmented filter and the session tabs, the group bar on grouped evidence rows, the line beside an older plan's source list, the underline of a cited title, of the home page's count links and of a running deep read, the outline of a keycap, the border of a multiselect's open checklist, dashed empty-state boxes and the scrollbar thumb. It is never a control boundary.
- **Control Stroke** (#666E76): the resting boundary of every enabled control, meaning buttons, two-state toggles, fields (inputs, selects, multiselects, textareas and one-line fields), prefix cells, weekday chips and the empty square of a read story's toggle. It holds at least 3.27:1 on every admin surface.
- **Ink Rule** (#C9CFD6): the heavy rules over tables, parameter tables, the transcript, side-column boxes and the newest plan, the running-head rule, the filled "ok" mark and the 完成 note.
- **Sheet Ink** (#E7EAED): primary text, the fill of the primary button, and the mark's stroke in the favicon and the touch icon. On the reading pages it sets a story's summary, a listing's current price, scores of 90 and above, a pressed toggle's word and square, and a brief's figure values.
- **Secondary Ink** (#A7AFB7): notes, meta lines, column heads, transcript labels, side-box heads, snippets, price-fact lines, closed and archived rows, and the 说明 note. On the reading pages it also sets the state items in a desk section's heading, the home page's count links, scores from 60 to 89, a read story's summary, the original title under a summary, a story's byline and age on phones, an opened best comment, the rail tally on the current chapter and keycap text. It also sets a resting toggle's word, the labels over filter fields and the reason field, the AI's reason line under a row, a brief's figure labels, a story entry's source, channel and time in a lane, a lot plate's closing line, and a hidden section's heading.
- **Faint Ink** (#808891): chapter and section numbers, placeholders, stacked-row labels, empty values, disabled text, hints, times under a transcript label, the auction line under a lot, list markers in AI text, excluded evidence, and the border of a hovered field. On the reading pages it also sets the rail's quiet tally, scores under 60 and unranked dashes, a listing's vendor line and former price, the keyboard hint, the fact labels of a listing or lot entry in a lane, a plate's score label and the time on a lot plate's closing line, the label before the page sizes, and an error page's status code. It holds at least 4.7:1 on every admin surface.
- **Fill Ink** (#0A0C0E): text on solid fills (primary button, note tags, danger hover, popover head). It has the same value as the desk but is its own role.
A few component states use fixed values that are deliberately not tokens: primary hover #FFFFFF, danger link hover #FF9A90, text selection #28507F under white text, the popover backdrop `rgba(5,6,7,.6)`, link underlines at 40% of their link colour, a closed lot's photo in grayscale at 65% opacity, and a read toggle or two-state toggle at 60% opacity while its request runs. Reuse them only in those states.
### Named Rules
**The Owner-Needed Rule.** Colour appears only where something needs the Owner. Amber means attention, red means failure, a destructive action or the Owner's own 不相关 once pressed, and blue means a link, focus, an unsaved edit, an unread result or story, a listing's latest change or work in progress. Everything else is ink on graphite. Admin state legends may show amber and red on a healthy page to explain the marks; they are not decoration.
**The Solid Ink Rule.** The primary action is a solid block of ink with dark text. Blue never fills a control, and amber never fills a button.
**The Shape-Plus-Word Rule.** Every state is a 9px square plus its word. Settled states are filled (ok in ink rule, error in red). Waiting states are hollow (paused and warning in amber, never fetched, scheduled or excluded in faint ink, in progress or watched in blue). Colour never carries a state alone. The 8px unread square on a story row is the one mark without a word beside it, because it is also the read toggle. Its word is its tooltip and accessible name (标为已读 or 标为未读), and the story's own weight repeats the state. A two-state toggle carries its own 7px square before its word, hollow when off and filled when on.
**The Visible Control Rule.** Every outlined control the stylesheet draws (buttons, two-state toggles, fields, prefix cells, weekday chips, segmented filters, session tabs, and the empty square of a read story's toggle) rests on the control stroke, or the danger stroke on danger buttons, and both hold at least 3:1 on every admin surface. The rule stroke draws structure only and never outlines a control. States only strengthen the boundary (secondary ink on a hovered button or toggle, a checked chip, a pressed toggle or a hovered read square, faint ink on a hovered field, blue on focus or an open multiselect, red when invalid, and the danger stroke on a pressed 不相关). Only disabled buttons fade to the hairline.
## Typography
**Display Font:** Archivo, self-hosted variable (weight 100–900, width 62–125%), falling back to the platform CJK sans (PingFang SC, Hiragino Sans GB, Noto Sans CJK SC, Microsoft YaHei, then the Japanese and Korean equivalents) and system-ui
**Body Font:** the same stack
**Label/Mono Font:** the ui-monospace stack (SF Mono, Cascadia Mono, DejaVu Sans Mono, Menlo, Consolas), for raw errors, plain-text email and the admin's immutable run-rule snapshot
**Character:** One grotesque in several widths and weights, set like a spec sheet. Bold full-width heads name things, and condensed semibold column heads and numbers pack the data.
Archivo ships as two woff2 subsets (latin and latin-ext) in `static/fonts/`, with `font-display: swap`, and the latin file is preloaded by the shared shell document. The CSP (`default-src 'self'`) rules out third-party font hosts. `font-synthesis: none` stops the browser from faking bold or italic in fallback faces.
Archivo covers Latin only. In the zh-CN interface, Chinese text in headings, labels and column heads renders in the platform CJK sans, which has no width axis. The condensed heads therefore show only on Latin runs such as channel names, numbers and times. The direction's "Archivo with condensed headers" lands fully only in Latin locales.
### Hierarchy
- **Headline** (700, 1.5rem/24px, 1.25): the page h1, with a 14px meta line of counts under it in secondary ink. It wraps balanced and breaks anywhere rather than overflow. A research question set as the h1 also keeps CJK words whole. On the home page's desk the counts are quiet links into the ranked list.
- **Title** (700, 1rem/16px, 1.35): numbered section heads. The number sits in faint ink at 600 in a 2.4em column, and section tools (counts, small buttons) align right on the same line. On every reading page the tools carry the section's state or count in 13px secondary ink: a Channel's state and fetch time on the desk, a part's count on a channel page, a day's total in the archive, a Channel's hits in search and a brief's state. 打开频道 closes the line on the desk and in search.
- **Subhead** (700, 0.9375rem/15px, 1.35): an unnumbered head inside a numbered section, such as AI 计划 under 1.3, with 30px above and 6px below. The heads inside AI text (主要发现, 来源一致之处) use the same size and weight with 22px above and 8px below. A brief's numbered heads (结论, 关键发现, 为何重要) use it too, 26px above and 8px below, with the number in the title's faint column.
- **Lead** (500, 1.0625rem/17px, 1.75): the bottom line of a research conclusion, the first answer in the notebook, and a brief's 结论. Facts-strip values share the size at 600 with tabular figures.
- **Body** (400, 0.9375rem/15px, 1.55): base text. Field values use the same size at 1.4. Ledes stop at 72ch.
- **Reading** (400, 0.9375rem/15px, 1.75): long AI text (conclusions, replies, plan summaries) at the 72ch measure. Paragraphs sit 10px apart and list items 9px apart, indented 1.35em. Ordered lists take decimal markers and unordered lists square ones, in faint ink with tabular figures. The Owner's own question in a turn is ink at 600 and 1.7.
- **Data** (400, 0.875rem/14px, 1.45): table cells, note bodies and meta lines. Parameter labels use it at 600, and so does a story's summary, which drops to 500 once read.
- **Note** (400, 0.8125rem/13px, 1.6): the note column of parameter rows, secondary lines in forms and popovers, and confirmation labels. Key values inside a note go bold in ink. On the reading pages it also sets the AI's reason line under a row (at 1.55), a figure's label (at 1.5), and a story entry's source, channel and time in a lane.
- **Button** (600, 0.8125rem/13px, 1.2): button labels. Small buttons and two-state toggles drop to 0.78rem.
- **Label** (600, 0.78rem/12.5px, 1.3, 85% width): column heads, the parameter head row, fact labels, side-column box heads, evidence group rows and the labels over filter fields. The transcript's label column uses it at 1.4.
- **Caption** (400, 0.78rem/12.5px): secondary lines under table values, the running head, footnote strips, stacked-row labels, error and caution lines, the auction line under a lot and price-fact lines. On the reading pages it also sets the original title under a story's summary, the change line under a price (at 600), the best-comment disclosure, and a story's byline and age on phones.
- **Tag** (700, 0.7rem/11.2px, 15px line): the outlined tags beside a label, 已修改 on a changed row and the unread tag on a session.
- **Numeric** (400, 0.875rem/14px, tabular, 88% width): numeric table columns, right-aligned. Scores use it at 600, at 700 for 90 and above and at 400 under 60. Prices and discounts keep full width with tabular figures, and a brief's figure values are 17px at 700 with tabular figures.
- **Mono** (400, 0.8125rem/13px, 1.75): the plain-text email preview and the admin's Rules used for this run disclosure. Raw error text uses 0.75rem at 1.55.
- Rail counts sit at 0.72rem and 700. The rail's quiet tally sits at 0.78rem and 400, and keycaps at 0.72rem and 600. Citation numbers are 0.75rem at 600 with tabular figures, and 0.8125rem where one leads the citation card's title. Footnote references use 0.68rem.
The admin extension reuses this ramp: unnumbered task headings use Title, task tabs use 14px semibold, and field hints and comparison labels use Note-sized text. Preview summaries remain Body-sized. Integer display scores are bare tabular numerals, not a new badge or type scale.
### Named Rules
**The Condensed Head Rule.** Width marks data. Column heads, fact labels, transcript labels, side-column box heads, group rows and filter-field labels set at 85% width, and numeric columns and scores at 88%. Headings and body text stay at full width.
**The Tabular Figures Rule.** Every number that changes (counts, the clock, chapter and section numbers, numeric columns, facts, citation numbers, filter counts, list markers, scores, prices, figure values, error codes and the rail tally) uses tabular figures, so columns and counters never jitter.
**The Reading Measure Rule.** Long AI text sits at 72ch with 1.75 line height, and a conclusion's bottom line leads at 17px. Ledes stop at the same 72ch, and so do an opened best comment, a deep-read brief and a brief's status section. Tables, forms and notes keep working density.
## Layout
All three shells use one document (`datasheet_base.html`) with two columns, a 216px contents rail and a fluid column that holds one sheet. The rail is sticky, runs the full viewport height and scrolls on its own. Its footer sits at the bottom above a hairline. The admin's footer holds back to the site, watch list, research and sign out, and the workspace's holds 返回阅读, 管理后台 and 退出登录. The reading shell's holds 关注列表, 研究, 管理 and 退出登录 for the Owner, and only 登录 for a reader. The sheet sits on the desk with a 16px margin and fills the window at any width, with a 16px top, 28px side and 36px bottom inset inside a 1px hairline edge.
Because the sheet has no maximum width, what sits on it takes its width from its own content. Prose stops at 72ch. A free-text filter field flexes from 16rem to 40rem, a select stops at 20rem, a multiselect's summary runs from 10rem to 22rem, and the reason field stops at 44rem. Sections that stand alone flow into `.cols`, a grid of as many columns as fit at 60rem or more each (`repeat(auto-fit,minmax(min(100%,60rem),1fr))`) with a 48px gutter, so a lone section takes the full width. Two columns need a sheet about 1970px wide inside its inset, so the home desk shows one Channel per row at 1440, two at 2560 and three at 3840. A news channel sets its top stories beside the rest, an auction channel its watched, ending and upcoming lots, and search its Channels. A long list that has the width to itself splits into two lanes, and sections side by side become lanes of their own (see Lanes).
Every sheet opens with the running head. The location sits on the left (chapter number and name, then crumbs separated by ›) and the Auckland clock on the right, with a 2px ink rule under both and 20px of space below. The page head comes next, with the h1 and its meta line on the left and actions on the right, wrapping under the title when space runs out. Legacy sheets use numbered heads (N.M, matching the rail's sub-entries), with 32px above and 10px below. The admin Channel workspace instead uses persistent task navigation and unnumbered task headings. A chapter's attention notes come before the list they concern.
Parameter tables use three columns: label at 140–190px, control fluid, and note at 200–290px. The gutter is 24px, the side inset 10px, and each row has 14px of padding above and below. Controls in a row sit 8px apart and wrap when they run out of room.
**Admin task layout.** The `.page-admin` extension gives the sheet a 22px top inset. Its parameter containers and task forms stop at 60rem; parameter rows stack label, control and note with a 7px gap and 20px vertical padding, without a parameter header band. Notes stop at 74ch. These are selector-scoped overrides, not a claim that every legacy admin form has been rebuilt: existing parameter markup, inline single-setting saves, selection tables and nested schedule rows remain. The shared three-column layout above still applies outside this scope.
Channel task navigation sticks at the viewport top on the sheet tone, 22px below the head. All five links remain visible at 390px, wrapping rather than hiding behind a menu. The main commitment sits in the head or the current form's sticky bottom save bar. Overview facts use three ruled columns with a 24px gap; rule comparisons use two columns with the same gap. Neither is a decorative card grid.
A research session's conclusion page is a notebook. The reading column is fluid, and a 300px side column sits 36px to its right, sticky 16px from the top, with its boxes 18px apart. Inside the reading column the transcript runs as turns, each with a 104px label column and the words 20px beside it, and 16px above and below (20px above the conclusion). A session's four parts (1.1 conclusion and follow-ups, 1.2 evidence, 1.3 sources and plan, 1.4 runs) are separate pages under the same head, listed in the rail as sub-pages, or as tabs under the head at the narrow rule. The watch list puts its attention notes (2.1) above the list (2.2). Irreversible clean-ups (delete the session, clear closed watches) sit at the foot of the page, 36px below the content.
The home page's head holds the h1 精选 and its counts on the left, and search on the right with 全部标记为已读 beside it for the Owner. The channel desk follows as sections 1.1 on, one per Channel in the rail's order, each a heading over a short banded table of 4 stories, 5 listings or 5 lots. The sections are numbered under chapter 1 but not listed in the rail, since the rail already gives every Channel its own chapter. The ranked list replaces the desk under the same head, with its segmented filter 18px below the head, then the table and the pager. A page-level empty state sits 24px below the head, and the keyboard hint closes the page 28px below the last table.
A channel opens with its name, count and freshness. Channel details holds source/fetch facts and scoring explanations. A news channel follows with its read-state filter and two `.cols` sections. A catalog keeps its view switch at the head and primary search/sort or status under it; Filters and details holds secondary choices and page size. A store monitor puts its view and page-size switches at the head and search, filters and sort in one filter row. Monitors retain current listings and history. Trackers retain watched lots, deadlines within 24 hours, later/undated active lots and history. Every page numbers the sections it actually shows.
The archive puts its filter bar under the head, then one section per Auckland day on the page (9.1 2026-09-29 星期二), then the pager. Search puts one field and a primary 搜索 under the head, then the page's hits as one section per Channel in `.cols.lanes`, then the pager. A deep-read brief is the notebook, 24px under its head, with the brief's text in the reading column and its figures and source in the side column. A brief that isn't finished is one numbered section, at most 72ch wide, 24px under the head.
Error and 404 pages skip the shell. One sheet, at most 560px wide, sits centred on the desk as the admin login does.
Shared shell, reading and research breakpoints are container queries. Most read the `adm` container (the body), and lanes read each reading table's own box (`ledger`) or a section group's (`cols`). The admin extension also has viewport and pointer media queries.
- **Mid (1180px and below):** the desk margin drops to 8px and the sheet inset to 14px 18px 32px. Table cells take 8px at the sides, no-wrap cells relax, and operation columns wrap. Parameter columns become 120–160px, fluid and 180–240px, with an 18px gutter. The notebook drops to one column. The side column follows the transcript as a static block 28px below it, and its citation box hides, because a citation opens its source in a new tab at this width. On the reading pages a story's summary column drops its minimum width from 20em to 14em, and the channel column from 7em to 5em.
- **Narrow (880px and below):** the rail becomes a top bar and the sheet runs edge to edge with 14px side insets. The reading strip reveals its active channel; Watch List and Research remain direct links, with Admin and Sign out in More. The mobile popup requires a shrinkable rail (`min-width:0`) despite visible overflow. Parameter and table rows stack, research keeps its 2x2 page tabs, and watch-list select-all moves into the section tools. Catalog secondary controls start closed, and gallery titles/prices precede their square photos. Coarse-pointer targets are 44px without enlarging their small state marks; the read-row lead and watch-checkbox space grow accordingly. Multiselect panels remain bounded by their fields. Existing wide-screen lanes are unaffected.
- **Wide (1800px and above):** parameter columns become 160–220px, up to 960px and at least 290px, so a field stops at a comfortable width and the note takes the rest. The notebook's reading column stops at 960px with a 300–440px side column beside it, both held to the left.
- **Lanes (a table box of 115.5rem, 1848px, and more, in a window at least 40rem tall):** on a reading page every table wrapper is its own size container (`ledger`), so a long story, listing or lot list splits into two lanes when its own box reaches 115.5rem, exactly the width of two 56rem lanes and their 3.5rem gutter, whatever the window. Narrower boxes keep the table, so a table in a section beside another stays a table. A section group (`.cols.lanes`) whose box reaches 123rem, room for two 60rem sections and their gap, makes each of its sections a lane. A shorter window, and print, keep the plain page.
- **Admin records (viewport 1100px and below):** admin stacking tables become label/value records before the shared narrow breakpoint. Labels are 86px wide inside a 94px indent; rows have 14px vertical padding and operations follow the values.
- **Admin phone layout (viewport 700px and below):** overview facts and comparisons become one column with an 18px gap; parameter rows take 18px vertical padding. Task links and header buttons have 44px minimum height, and the head's actions take a full line. The shared rail still becomes a top bar at 880px. The admin footer wraps within the rail so longer labels and Sign out remain visible without horizontal scrolling.
- **Admin touch (`pointer: coarse`):** brand, chapter and footer navigation have 44px minimum width and height. Buttons, small buttons, source-operation links and option labels have 44px minimum height; operation links gain 8px side padding and native checkboxes/radios become 20px. Fine-pointer desktop controls retain their compact sizing. This is a pointer rule, not a promise that every legacy control has a 44px target.
### Named Rules
**The No-Overflow Rule.** No text leaves its box at 390, 1024, 1440, 2560 or 3840px, in Chinese or English. Free-length values wrap (one-line fields, break-anywhere cells), and bounded values ellipsize. No layout depends on sideways scrolling. The table wrapper's scroll is a guard, not a layout. The one sideways scroll drawn on purpose is the reading rail's chapter line at the narrow rule, which scrolls inside its bar and never widens the page.
**The Uncut Reading Rule.** Nothing on a reading page is cut short. Every cell wraps in full, with no ellipsis and no line clamp. Where the workspace clamps a line, the reading pages undo it under `.page-reading`, as they do for a lot's auction line (`.page-reading .lot-where`). Units still hold whole.
**The Whole Unit Rule.** In a table a unit never splits, whether it is a count with its word, a day with its time or an email address. Lines break between units, and URLs break only before a slash. At the narrow rule these holds relax so stacked rows can wrap.
**The Stacked Row Rule.** Shared stacking tables become labelled rows at 880px and below; admin stacking tables do so at 1100px. Each value is led by its column name in a faint-ink label column (76px shared, 86px admin), the row checkbox moves to the top right, and operations drop to the bottom. Story tables are the exception. A story reads as a headline row, summary first, with its byline, age and deep read beneath, because column labels would outweigh the story. Its toggle and score hang in a lead on the left, and everything else wraps inside the indent, so no wrapped line starts left of the summary.
**The Side Column Rule.** On a desktop window wider than 1180px, a citation shows its source in the side column beside the text, so checking it never leaves the page. At 1180px and narrower, the side column follows the transcript, its citation box hides, and a citation opens the source in a new tab. A brief's figures and source follow the same widths.
**The Readable Shape Rule.** The sheet has no maximum width, so nothing on it takes its width from the sheet alone. Prose holds 72ch, a free-text field 40rem and a select 20rem, and sections that stand alone flow into columns of at least 60rem rather than stretch.
**The Lane Rule.** On a wide sheet no list makes the reader scroll back up. A long list whose own box is at least 115.5rem (1848px) wide, room for two 56rem lanes and their gutter, splits at its middle row into two lanes, and sections side by side become lanes too. Each lane fits the window below where it starts and scrolls on its own without moving the page or another lane, so the top of the next lane is always in view. The order runs down the first lane, then the second, so reading and keyboard order stay as listed. Narrower boxes keep the table.
**The Five-Task Rule.** In the admin Channel workspace, Overview, Sources, Organization rules, Email and Settings remain visible on desktop and phone. Keep the current task's commitment in its header or save bar; Sources are never buried under the rule profile.
## Elevation & Depth
The system is flat. Depth is three steps of graphite. The desk sits lowest, the sheet lies on it behind a hairline edge, and header bands sit one step lighter than the sheet. Fields sit one step darker than the sheet, so they read as wells. Hover is a tone change, never a lift, and so is a row or gallery plate selected from the keyboard, which takes the band. The sticky save bar and the notebook's sticky side column hold their place without a shadow. Only two things open over the page and cast a shadow. The source-removal popover opens in the top layer over a 60% near-black backdrop, and a multiselect's checklist hangs over the rows below it with a softer shadow and no backdrop.
### Shadow Vocabulary
- **Popover lift** (`box-shadow: 0 18px 40px -8px rgba(0,0,0,.65)`): the removal popover only.
- **Checklist lift** (`box-shadow: 0 14px 32px -10px rgba(0,0,0,.7)`): a multiselect's open checklist only.
- **Field focus ring** (`box-shadow: 0 0 0 1px var(--link)`): doubles the blue border of a focused field. It is a ring, not elevation.
- **Hollow mark** (`box-shadow: inset 0 0 0 1.5px <tone>`): draws the hollow state squares, and the empty square a read story keeps in its read toggle, in the control stroke. A two-state toggle's 7px square uses it in the toggle's own text colour (`currentColor`) and fills with that colour when pressed.
- **Group bar** (`box-shadow: inset 2px 0 0 var(--rule-2)`): the 2px rule-stroke bar on the first cell of evidence rows that belong to a duplicate group. It is a rule, not elevation, and it never takes a tone.
### Named Rules
**The Flat Sheet Rule.** Depth is tone. The desk sits below the sheet, bands sit above it and fields sink into it. Nothing floats except the removal popover and a multiselect's open checklist.
## Shapes
The sheet is rectangular and ruled like a datasheet. Rectangular controls and counters take a 2px corner. That covers buttons, two-state toggles, fields, a multiselect and its open checklist with its option rows, rail entries, weekday chips, prefix cells, segmented filters, session tabs, the read toggle, rail counts, keycaps, and the 已修改 and unread tags. The rail's quiet tally is a bare numeral with no box. The sheet, the error sheet, tables, notes, the popover, facts strips, side-column boxes, lot photos, empty states, skeleton lines and code blocks are square. Lines do the structural work. A 2px ink rule sits under the running head. A 1.5px ink rule opens tables, legacy parameter tables, selection tables, facts strips, transcripts and side-column boxes, and the newest plan; admin task forms omit the parameter header and top rule. Notes and the popover take 1.5px borders in their tone, and 1px lines in the hairline and rule-stroke tones do everything else. Two 2px rule-stroke bars group things, one on the first cell of grouped evidence rows and one beside an older plan's source list. Controls are outlined apart from all of this, in 1px of the control stroke. Empty states use a dashed rule-stroke border. Two lanes are split by a 1px hairline rule in the middle of their 3.5rem gutter, each opens with the ink rule and each entry closes with a hairline. Gallery plates are square cells of a hairline grid under the ink rule, with no shadow and no corner. State marks are 9px squares, the unread square on a story row is 8px, a two-state toggle's square is 7px, and the citation number in view takes a 1px blue outline at a 1px offset. The product's hexagon mark from `favicon.svg` is drawn as an 18px stroked outline beside the product name. Admin setup also uses circled step numbers to convey sequence, alongside native radio choices; neither changes the rectangular container language. The favicon itself sets the mark on a tile with 12-unit corners (of 64), an icon convention that sits outside the page's corner rules.
### Named Rules
**The Two Corner Rule.** Rectangular controls, counters, tags and keycaps get 2px, and containers get none. A multiselect's open checklist is part of its control and takes 2px. Admin setup step numbers use circles to convey their sequence, not a new container shape.
**The Heavy Ink Rule.** A 1.5px ink rule opens tables, legacy parameter tables, transcripts and side-column boxes. Admin task forms omit the parameter header and top rule. Hairlines separate their fields.
## Components
### Buttons
Quiet rectangles, labelled in words.
- **Shape:** 2px corners, 32px minimum height and 12px sides in the shared base. Admin buttons use 36px, small admin buttons 32px, and touch targets 44px as scoped in Layout. Labels do not wrap.
- **Primary:** a solid block of sheet ink with fill-ink text, brightening to pure white on hover and dimming to the ink rule tone while pressed, by mouse or keyboard. It is reserved for the page's main commitment, such as create, save, sign in, send a test, preview or start a research run, refresh a session, send a follow-up, start a deep read, run a search, or go home from an error page.
- **Hover / Focus:** colour-only transitions at 150ms ease-out. Focus is the global 2px blue outline at a 2px offset.
- **Secondary (default):** transparent, with a 1px control-stroke border and ink text. On hover the border moves to secondary ink and the hover wash fills it. Active uses the band.
- **Danger:** transparent, with red text and a danger-stroke border. On hover it fills solid red with fill-ink text. It appears only in danger rows and the removal popover.
- **Small and block:** small buttons are 26px tall with 9px sides, used for note actions and section tools, such as 打开频道 on the channel desk and in search, and 重试 on a failed brief. The block button is full width at 38px, used for sign-in.
- **Disabled and loading:** disabled buttons take a hairline border, faint ink and a not-allowed cursor. Loading shows a wait cursor at 70% opacity.
### Links
- Links are datasheet blue with a 40% blue underline at a 3px offset. On hover they turn pale blue with a full underline. Danger links are red.
- Row operations in tables are links, even when they post a form. Admin source operations (test, edit, pause, copy, remove) can wrap within their 17rem operations column and form a touch-sized row under mobile records. The workspace uses links for exclude and restore, stop watching and retry a reminder, and sets its older-plan disclosure as a link.
- **Title links:** a story's summary is an ink link, like a name in a table. It turns pale blue with an underline on hover and opens the story in a new tab. The ranked list's channel column links to the Channel the same way.
- **Quiet links:** the home page's counts are links in secondary ink with a rule-stroke underline at a 3px offset, the device a cited title uses. On hover they turn ink with a full underline.
- **Deep read:** a plain datasheet link in a story row's last cell. 深度阅读 starts one (a form button set as a link) and 打开简报 opens the brief. A running one reads 生成中… in secondary ink with a rule-stroke underline, and a failed one offers a red 重试.
### State Marks
- **Style:** a 9px square and a word, 7px apart, never wrapping.
- **In a sentence:** `.st-line` puts the same square before a whole sentence in a note column, for example a refresh in progress or a failure reason. The square stays on the first line and the words wrap, so a long status never overflows.
- **States:** ok is a filled ink-rule square with normal text. Paused and warning are a hollow amber square with amber semibold text. Error is a filled red square with red semibold text. Never fetched is a hollow faint square with faint text. In progress is a hollow blue square with blue text.
- **In the workspace:** runs mark completed as ok, failed as error, cancelled as never and queued or running as in progress. Reminders mark 已发送 as ok, 发送失败 as error and 正在发送 as in progress, and 已安排 and 没有提醒 take the hollow faint square. Excluded evidence carries a hollow faint 已排除. A lot that closes within the hour marks its deadline as a warning.
- **On the reading pages:** a lot the Owner watches carries 已关注 under its deadline on the desk, below a hairline, as the hollow blue in-progress mark, because the watch is running. A listing that is out of stock or gone carries 缺货 or 已下架 as the hollow faint mark under its vendor line. Where the Owner has voted on a story, a reader sees the verdict as a mark in the row's last cell instead of toggles, 相关 as ok and 不相关 as never, and never sees the reason.
- The channel and source lists carry a legend of the marks in the footnote strip below them.
### Inputs / Fields
- **Style:** 34px minimum height, 6px 10px padding, a 1px control-stroke border, 2px corners, the recessed field fill, and 15px text at 1.4. Placeholders use faint ink. On hover the border moves to faint ink. Selects, textareas and one-line fields share the same border.
- **Focus:** the border turns blue and a 1px blue ring doubles it. There is no outline.
- **Error:** an invalid field takes a red border, and the message uses red 13px semibold. Legacy parameter forms use the note column; stacked admin fields keep hints and errors beside the control in reading order, below it.
- **Bounded and free-length values:** names, numbers, times and selects are inputs, and input text ellipsizes. Values of any length (URLs, queries, email addresses, subject templates, source names) use the one-line field (`ui.line_field`). It is a one-row textarea that wraps and grows with its content (`field-sizing: content`, with a script fallback). Enter submits the form and pasted line breaks are dropped. Literal values turn off spellcheck, autocapitalize and autocorrect.
- **Multi-line:** the shared textarea starts at 132px with 1.65 line height and grows with its content. The admin rule editor instead has a 12rem minimum and 28rem maximum height with internal scrolling and fixed field sizing.
- **Sizes and variants:** number fields are 96px, time fields 132px, and mid selects up to 260px. Growing fields flex from 220px. Selects draw a 12px chevron in secondary ink. A prefix cell (such as `r/`) joins its field in band fill under the same control-stroke border. Checkboxes and radios are 16px native controls tinted ink. Weekday chips are 30px labels with a control-stroke border, and a checked chip takes a secondary-ink border and band fill.
- **Workspace sizes:** filter fields drop to 32px to sit level with the segments and buttons. The composer's textarea starts at 92px. The pager's page field is 80px wide and 28px tall.
- **Reading sizes:** the search field in the head of the home page and of a news channel is 260px wide at 32px, level with its 搜索 button, and flexes to fill its line at the narrow rule. Filter bars, the multiselect and the reason field have their own entries below.
### Navigation
- **Contents rail:** a brand line linking to the shell's home: the hexagon mark, the product name at 700, the numeric package version (such as `v1.0.0`) in 13px secondary-ink monospace, and the admin or workspace name in secondary ink at 500. Reading pages omit the redundant mode name. The tooltip and accessible name use the same numeric version; commit IDs are diagnostic metadata, never the displayed version. Brand units wrap whole when needed. Numbered chapters follow as 32px rows with a 22px column of faint tabular numbers. Entries sit in secondary ink, take the hover wash and ink on hover, and the current chapter takes the band, ink and 600. The current chapter lists its N.M sections as 28px sub-rows at 13px. When those sections are separate pages, as a research session's four parts are, the open one takes the band and ink at regular weight and the chapter stays marked.
- **Attention counts:** a chapter that needs the Owner shows an 18px amber-outlined count with a full sentence for screen readers. Recovery stays in the chapter where the problem is fixed. Time-sensitive reminder failures also appear at the Owner's reading entry points and beside affected lots, so a watched marker never implies successful delivery.
- **Unread count:** in the workspace, 1 研究 counts finished results the Owner hasn't opened, in the same count outlined in blue, while 2 关注列表 counts failed reminders in amber. A chapter shows one count at most, and attention wins.
- **Reading rail:** 1 精选 comes first, then one chapter per Channel under its own name, numbered from 2, and the archive (归档) last. Long Channel names wrap inside the rail. The desk's sections get no sub-rows, since each Channel already has its chapter. Every other reading page lists its numbered sections under the current chapter: a Channel's parts under its chapter, the archive's days under 归档, search's Channels under 1 精选, and a brief's parts under the chapter it was opened from (its Channel, the archive or 1 精选).
- **Quiet tally:** 1 精选 carries the Owner's unread featured stories as a bare number in 12.5px regular faint ink with tabular figures, secondary ink on the current chapter, and a full sentence in its tooltip and for screen readers (精选中未读 228 条). It shows 0 once everything is read, and nothing for an anonymous reader. A chapter's count slot holds one thing at most, in this order: attention, then an unread count, then the tally.
- **Rail footer:** the admin's holds back to the site, watch list, research (with a blue-outlined unread count) and sign out. The workspace's holds 返回阅读, 管理后台 and 退出登录. The reading shell's holds 关注列表 (with an amber failed-reminder count), 研究 (with the same blue unread count), 管理 and 退出登录 for the Owner, and only 登录 for a reader. All are 13px. Reading footer links wrap on narrow screens when their labels and counts need more room.
- **Running head:** 12.5px secondary ink. Crumbs are links that underline on hover, and the clock uses tabular figures. A research question is cut to 40 characters in the crumbs. On the ranked list the crumb names the view (全部, 未读, 已读 or N 分以上). Search adds the crumb 搜索, and a brief 深度阅读.
- **Mobile:** at the narrow rule the rail becomes a wrapping top bar, with chapters in one row and footer links at the right. The reading rail's chapters stay on one line that scrolls sideways inside the bar, with 4px below for the scrollbar, because a chapter per Channel would otherwise stack several rows above the page. The shell's rows are `auto 1fr`, so a short page starts right under the bar.
- **Session tabs:** at the narrow rule, where the rail hides its sub-rows, a research session lists its four pages 14px under the page head as a 2×2 grid of 38px cells, with the rail's sub-row labels. The grid takes a control-stroke outline with 2px corners and rule-stroke dividers. Labels are 13px semibold in secondary ink, the current page takes the band and ink, and focus draws inside the cell. Wider screens hide the grid.
### Segmented Filter
One outlined control that switches a list's view, such as the watch list's 进行中 / 已结束 / 全部.
- **Style:** a control-stroke outline with 2px corners holding 32px link segments with 11px sides at 13px semibold in secondary ink. Rule-stroke lines divide the segments, and each segment ends in its count in faint regular tabular figures.
- **States:** hover takes the hover wash and ink. The current view takes the band and ink. Focus draws the blue outline 2px inside the segment.
- **Filter row:** the segments come first, then a search field and a select at 32px, a secondary apply button, and a clear link once a filter is set. The row wraps and sits 12px above its table.
- **Ranked list:** 频道总览 comes first, without a count, and leads back to the desk. Then come 全部, and for the Owner 未读 and 已读, each with its count, and last 90 分以上 with its count, or the score filter in use (N 分以上) as the current segment. The segments stand alone, 18px under the head, with no search form beside them.
- **News channel:** for the Owner, 未读 and 全部 with their counts, 18px under the criteria note. While the Channel is searched, a faint hint (正在搜索“…”) with a 清除 link sits beside the segments, and that hint is all a reader sees.
### Parameter Table
The retained legacy form, 参数 | 设置 | 说明 (Parameter | Setting | Notes). These dimensions describe the shared base; the `.page-admin` stack is described in Admin Task Layout and below.
- A 1.5px ink rule sits on top, then an optional band head row in label type over a rule-stroke line. The head row is hidden from assistive tech because every row labels itself.
- Each row has a 14px semibold label (with an optional 可选 line in faint caption), a control column with 10px between stacked controls, and a 13px secondary-ink note. Fixed values, such as a channel's type, sit in the control column as plain text. A hairline closes each row.
- A changed row shows the 已修改 tag after its label, a blue outlined 11px bold tag with 2px corners. Radio and checkbox groups carry the tag in their section head instead.
- The sticky save bar closes the form on the sheet tone above a rule-stroke line. It holds the primary save, then 放弃修改 once something has changed, an optional cancel, and the blue count "N 项修改还没保存". After a rejected save the bar stays unsaved and says so, and discard reloads the saved page.
- Single-setting forms (language, model, news time window, default recipient) put their own secondary button in the row instead of a save bar.
- Workspace forms (new research, source form) use the table without change tracking. There are no 已修改 tags, and the bar holds the primary action, a cancel and a faint hint. The new-research form puts its sources in a selection table between two parameter tables.
- At 1800px and wider the control column stops at 960px and the note column takes the rest.
### Admin Channel Tasks
One Channel, five persistent task links; no configuration card grid.
- **Navigation:** 14px semibold links on the sticky sheet-tone strip, with a hover wash, a banded current task and control-stroke boundary. Shared blue keyboard focus remains visible.
- **Creation:** choose the immutable type and a name, then continue through Source → Rules → Email → Review/Start. The draft stays paused; collection starts only after explicit Start. Circled step numbers indicate this real sequence.
- **Review:** show source count and pending state, collection schedule, Email choice, the saved rule summary, score cutoff and highlight cap. Start or Continue stays in the header, including on phone.
### Admin Task Forms and Drafts
Stacked, task-local fields preserve the parameter form's label/value/explanation relationship.
- Rules, Email and Settings save independently without overwriting unrelated settings. The sheet-tone save bar has no shadow; changed rows carry blue modified tags and a counted unsaved message.
- Forms marked for draft preservation keep values in session storage by page path and form action. Normal navigation restores a matching draft; a changed saved baseline offers restore or discard rather than silently replacing server values. Hidden fields, passwords and CSRF tokens are not draft values.
- Tracked unsaved forms protect navigation with before-unload handling. Failed saves remain unsaved; discard clears the draft and returns to saved values. Focused inputs scroll clear of the sticky footer.
### Source Preflight
Test the connection before committing it.
- Source type selects the applicable connection fields. Test connection uses the current unsaved values without saving a Source or collecting items.
- Results follow Test connection immediately, before the optional display name. The result receives focus and scroll alignment; an invalid field takes priority and keeps its nearby error and recovery message.
- Source lists retain shape-plus-word state, last fetch, counts and word-labelled operations. Mobile records and touch targets follow the admin rules in Layout.
### Rules Preview and Reprocessing
Saving, trying and applying are visibly different commitments.
- Saving the profile affects future ranking, not existing summaries. The cutoff filters current results and pending Email selection; it does not restore already suppressed events.
- Preview uses up to five eligible existing items without mutating the saved profile, items or delivery state. Compare before/after summaries and integer display scores, with rationale below. Python supplies separate display-score fields; templates do not round, and raw numeric scores remain the cutoff inputs.
- Show the cutoff sample count with its sample-only qualification, **Edit rules**, **Rules used for this run**, and **Save current rules**. The saved action submits the current editor, not the historical run's profile. An edited profile makes the preview stale; save availability tracks the current draft.
- Reprocessing sits behind its own disclosure and explicit confirmation. It uses saved rules on unread Editorial items or current listings, preserves history and delivery exclusions, and does not replay sent content. Unsaved-rule feedback and the disabled action stay synchronized, including after polling.
- Progress, recent operations, refresh and cancellation remain visible. Cancellation stops remaining work but keeps results already applied.
### Channel Email
Delivery choices stay in the Channel task, with shared scope made explicit.
- Choose website-only, an existing shared group, or a contextual new daily email. The current group's member count and link to shared-group settings explain that group's scope; a Channel save does not silently reconfigure other Channels.
- A live first/next-due forecast sits by schedule fields and repeats in the save bar. It describes the schedule, not a guarantee of transport or eligible content.
- Preview the real HTML email in a sandboxed iframe with a plain-text alternative. Preview and explicit test-send do not consume pending delivery. Diagnostics distinguish current items, waiting ranking, eligible content and source/transport problems.
- The email document's dark canvas, entry blocks and inline styling are embedded output (see Digest Email), not additions to the admin palette or a card pattern for admin pages.
### Banded Table
The signature list.
- A 1.5px ink rule sits over a band head in label type that never wraps, with a rule-stroke line under the head. The body is 14px at 1.45 with 10px cells, a hairline under every row and the hover wash on hover. A highlighted row takes the band.
- **Columns:** the name column is semibold, at least 12em wide, wraps balanced, and its links turn pale blue on hover. Numeric columns right-align in tabular figures at 88% width. Unit columns never wrap, and word columns keep CJK words whole. Wide columns (details, sources) start at 16em and break anywhere. The operations column is as narrow as its links, right-aligned, with 12px between links.
- **Cell holds:** cells set `text-wrap: pretty`, which resets wrapping and outranks a single-class `white-space: nowrap`. Scope any cell-level hold under the table, as the build's `.tbl .c-nw` and `.tbl .c-num` rules do, or the unit breaks anyway.
- **Lines under a value:** a caption line in secondary ink, a failure line in red, or a caution line in amber. A raw error folds into a disclosure, set in 12px mono on the field fill.
- **Footnote strip:** below the table, in caption type. It holds the mark legend, notes, and numbered footnotes referenced by a faint superscript "(1)".
- **Selection tables:** source type, channel kind, group members and a new research's sources use the same ink rule. Each row holds a radio or checkbox, a semibold name, a description and a side note. The checked row takes the band, keyboard focus draws a blue outline inside the row, and unavailable rows go faint.
- **Unread and archived rows:** a session with a finished result the Owner hasn't opened carries the blue unread tag after its question. Archived sessions and closed lots set their rows in secondary ink.
- **Times:** every short time uses one label in Auckland time (今天 14:21, 明天 09:00, 昨天 22:10, or 2026-10-02 10:02 further out), from the `short_time` filter all three shells share.
### Watched Lot Row
The watch list's banded table holds a checkbox, the lot, 截止与提醒, 出价, and the 取消关注 link.
- **Lot cell:** an 80px square photo, shown whole inside a hairline edge on the field fill (an empty well when there is no photo, or when its CDN will not serve it), beside the title in semibold ink and the auction line in faint caption type, clamped to two lines. The title opens the lot in a new tab and turns pale blue with an underline on hover.
- **Deadline and reminder cell:** at least 9.5em wide. The relative deadline (2 天后截止) comes first with its exact time as a caption line. Below a hairline with 6px either side, the reminder state follows as a state mark with its time or hint on a caption line, and a failed reminder adds a 立即重试提醒 link. A closed lot shows 已结束 with its closing time, and a lot without a deadline says so in faint ink.
- **Price facts:** at least 11.5em wide. The first fact (the current bid, or that there is none) leads at 14px semibold in ink, and the rest (the estimate with buyer's premium, the RRP, the sold price) follow as caption lines in secondary ink. A lot without prices shows a faint dash.
- **Closed rows:** text drops to secondary ink and the photo to grayscale at 65% opacity.
- **Failed reminders first:** 2.1 需要注意 boxes every failed reminder as a 警告 note above the list, whatever the filter, with its deadline, the folded raw error, a small retry button and a link to delivery health. When nothing failed it shows a 完成 note, and a missing reminder address is a 注意 note.
- **Removing:** row checkboxes belong to a form outside the table. The head checkbox selects all and shows a partial pick as indeterminate, and the section tools show 已选 N 个 beside a small danger button that stays disabled until something is picked. At the narrow rule, where the table head is hidden, a semibold 全选 checkbox joins the section tools before the count, and both select-alls stay in step. Stopping one watch re-renders the list, the head counts and the rail count from the server, moves focus to the next lot's title (else the previous one, else the settings button) and announces the change in a polite live region.
- **Recovery:** individual removal also offers the shared visible action notice. Undo restores the previous watch, including its sent/error/deadline state, only while the saved action is valid. An in-flight delivery gets an explicit warning without an undo promise. Selection labels retain their small checkbox inside a 44px target; bulk removal and clearing closed watches remain non-undoable.
### Notebook Transcript
The conclusion page of a research session (1.1). It is a transcript, not a chat.
- **Turns:** rows under one 1.5px ink rule, each closed by a hairline. The label column says who and when in label type, with 综合结论 and its version, time and sufficiency, 你 for the Owner, and 回答 for a reply. Versions and times sit on caption lines in faint ink with tabular figures.
- **Conclusion:** always the first turn. The bottom line is the lead, in ink at 500. Key findings, agreements, conflicts, unknowns and evidence coverage follow under in-text heads in reading type. General model knowledge comes last in a 说明 note inside the reading column, as a 14px list in secondary ink with no citations. When the conclusion can be rewritten, a small 重新生成结论 button and a faint hint sit 18px below.
- **Citation numbers:** a bracketed number right after the claim, in 12px semibold blue with tabular figures, 3px from the text, raised 0.1em and never wrapping. Hover turns it pale blue with an underline. The number shown in the side column, and every copy of it in the text, takes a 1px blue outline.
- **Follow-ups:** the Owner's question is ink at 600 and 1.7. A reply is reading type that keeps its line breaks. A pending reply is a 回答 turn holding skeleton lines, and a reply that failed is a faint hint. A failure to send is a 警告 note under the transcript.
- **Composer:** last. A semibold label, a textarea from 92px, then a row with a faint hint on the left and the primary 发送 on the right. Discard and not-sent feedback stay grouped beside Send. The restore notice names an unsent message, not an unsaved setting; the transcript and citation layout do not change.
- **Research drafts:** questions, source forms and follow-ups opt into the shared same-tab draft mechanism. Navigation restores matching baselines, changed server values offer explicit restore/discard, and accepted submission clears only the submitted form. Previewing a new session does not consume its draft. Explicit discard does not ask a second leave-confirmation.
- **No conclusion yet:** the first turn is muted and holds an empty state.
### Side Column
On a desktop window wider than 1180px the side column sits beside the transcript, sticky 16px from the top.
- **Boxes:** each opens with the 1.5px ink rule and 10px of space, headed in label type in secondary ink.
- **Citation box (引用):** a faint hint until a number is clicked, then that source's card, which leads with its title. The title line starts with the citation number in 13px blue tabular figures, then the linked title in semibold ink with a rule-stroke underline that turns pale blue on hover. Under it sit the publisher and quality as a faint caption line (with a hollow faint 已排除 mark once excluded), then the saved excerpt at 13px in secondary ink at 1.65, or a note in the same style that no excerpt was saved. Last come a row with a small 排除这条证据 button (restore once excluded) and an 打开原文 link, and a faint hint that the conclusion has to be rewritten after excluding.
- **Session facts (这次研究):** rows split by hairlines, 7px above and below, with the label in secondary ink on the left and the value right-aligned in tabular figures. Evidence, sources and runs link to their pages, and the model is plain text.
### Evidence Table
1.2 lists every evidence item in the latest snapshot, a page at a time.
- **Columns:** the citation number, right-aligned in tabular figures; the title with its snippet; the publisher, at least 6.5em; the quality; and an exclude or restore link.
- **Snippet:** under the semibold title at 13px in secondary ink at 1.6, clamped to three lines on purpose. The title opens the full text in a new tab.
- **Duplicate groups:** a group row in label type and secondary ink, with 14px above, 4px below and no rule, says how many reports of one story follow. Each grouped row carries the group bar on its first cell.
- **Excluded rows:** text drops to faint ink, the title to secondary ink struck through in faint ink, and the quality cell adds a hollow faint 已排除 mark. When everything is excluded, a 注意 note says so above the table.
### Sources and Plan
1.3 holds what the next run searches and what the AI planned on each run.
- **Sources:** a banded table of source, who added it, and edit and remove links (remove in red, while more than one source is left), with a small 添加信源 button in the section tools. While a run is going, a 说明 note says the sources are locked.
- **Newest plan:** under an unnumbered subhead (AI 计划) and a lede, the newest plan shows in full as a block under a 1.5px ink rule, with 12px 10px 4px padding. Its run and version sit in bold with the time in faint ink, then the summary in reading type, then a banded table of the planned sources and their rationale.
- **Older plans:** one disclosure, set as a blue link (看其余 N 个计划版本), opens a table of run and version, time, and summary. Each summary carries its own disclosure, 信源与理由(N), in 13px blue text with the native marker. Open, it lists every source in semibold ink with its reason below in secondary ink, indented 12px behind a 2px rule-stroke line.
### Run History
1.4 has one row per run.
- **Columns:** the run number in bold with its kind as a caption line, a state mark, the start time, the duration, the work (deep reads, with plan versions and attempts as a caption line), and the outcome.
- **Outcome:** up to three lines, each on its own and each shown only when it applies. The failure reason comes first as a red sentence at the data size. The snapshot the run sealed (快照 N:X 条证据) and the conclusion it wrote (结论第 N 版 · sufficiency) follow in ink at the data size, 4px below a reason. A failed run that sealed evidence shows both its reason and its snapshot, and a conclusion is credited to the run that wrote it, including a retry that only rewrote the conclusion. The stored technical detail folds under 原始错误 after the lines, in mono on the field fill. A run with none of these shows a faint in-progress or no-outcome note.
### Running Work
- **Session vocabulary:** Open/Unarchived describes a session that can accept work; Running describes a run actually executing. A completed session is not labelled as running.
- **Status note:** while a run is queued or running, a 说明 note heads every session page with the run and its state in bold, the phase, a hint and three skeleton lines. It refreshes every three seconds. A failed run leaves a 警告 note with the reason and the folded raw error. The control beside the title is a cancel button while a run is going and the primary 刷新研究 otherwise.
- **Skeleton lines:** three 10px bars in the band tone at full, 75% and 50% width, 8px apart, pulsing to 45% opacity over 1.6s. They hold still under reduced motion. There is never a spinner.
### Reading Page Head
Every reading page opens the same way: the h1 and a meta line on the left, search and page actions on the right.
- **Counts:** on the home page, under the h1 精选, the meta line gives the rolling window (最近 24 小时 by default) in plain text, then the story count, the Owner's unread count and the count at 90 or above, split by middots. On the desk each count is a quiet link into the ranked list. In the ranked list they are plain text, because the filter does that job. Editorial channel lists use the same window before score ordering; explicit searches are labelled 全部历史内容.
- **Channel heads:** the h1 is the Channel's name. The meta line gives its kind, the relevant count and freshness with an exact-time tooltip. During search, the count describes matches. Detailed fetch statistics and source names move into Channel details, or the catalog's Filters and details, where they still wrap in full. A store monitor instead appends them to its meta line (上次抓取 and 信源, split by middots), where they wrap in full.
- **Other heads:** the archive and search give their result count and a lede. A brief's h1 is the story's AI summary, and its meta line gives the source, the age and 阅读原文 as a link.
- **Search (`.head-search`):** on the home page and a news channel, a 260px search field at 32px and a secondary 搜索 button, 8px apart, on the right. The home page searches through `/search`, and a news channel searches itself. Monitor and tracker pages, the archive and search put their fields in a filter bar under the head instead.
- **Page actions:** for the Owner, and only while something is unread, a secondary 全部标记为已读 follows the search. It is a plain form post that reloads the page. On a news channel it sits in a swap slot (`.swap-slot`, `display:contents`), so a read toggle can update it without touching the search field. A tracker's head offers the Owner 关注列表, and a brief's head the primary 深度阅读 while one can be started, or a secondary 重新生成 once it is ready.
- **Channel details:** a native disclosure carries source/fetch facts and a plain-language explanation of relevance scores. The Owner also sees match counts, the score floor, the below-threshold toggle and 编辑条件. Editorial pages place it below the head. Catalogs include it inside Filters and details, avoiding two stacked phone disclosures.
- At the narrow rule the search takes a full line and 全部标记为已读 wraps below it.
### Channel Desk
The home page's default view: one numbered section per Channel (`.desk-sec`), its state in the heading and its best rows beneath.
- **Section head:** the number (1.1 on) and the Channel's name, then the tools on the right. The state items (`.desk-state`) are 13px secondary ink and never wrap inside themselves. The fetch time (14 小时前抓取) carries the exact time, the next fetch and the last fetch's numbers in its tooltip. A small 打开频道 button closes the line and names the Channel to screen readers.
- **State by kind:** an Editorial Channel says how many stories its window holds (24 小时内 6 条 by default) and, for the Owner, how many are unread (未读 5). A Monitor Channel says how many listings are live (在售 3,767 件). A Tracker Channel says how many lots are open at its score floor (80 分以上在拍 16 件), where the floor is 80 or the Channel's own minimum when that is higher, and, for the Owner, how many it watches (关注 0 个).
- **Editorial rows:** its top 4 featured stories in the window, unread first for the Owner, as story rows.
- **Monitor rows:** its 5 best live listings in a banded table of 商品 | 价格 | 折扣 | 分数. The listing cell is the watched lot's cell (`.lot`): the photo well, the title opening the listing in a new tab, and the vendor as the faint line under it. Prices follow (see Prices), and the score closes the row.
- **Tracker rows:** 5 open lots at the floor, soonest deadline first, in 拍品 | 截止 | 出价 | 分数, reusing the watched lot's cells (`.lot`, `.kv`). The auction line wraps in full, the relative deadline sits over its exact time, 已关注 follows under a hairline when the Owner watches the lot, and the price facts lead with the current bid.
- **Reminder failures:** for the Owner, a failed reminder replaces the desk row's reassuring watched mark with a red recovery link. The Tracker heading counts all its failed watches, not just the short list. Channel/list/gallery watch controls carry the same failure link; unwatch updates that control and the reading footer count together. Diagnostics and retry remain in the Watch List. Long labels and existing gallery controls wrap inside their cells; recovery links have at least 44px height for coarse pointers.
- **Empty sections:** keep the numbered heading and use the dashed empty-state treatment. Actual inventory/window/ranking counts distinguish missing collection, older content, incomplete ranking and score/relevance exclusion. Links open collection settings for the Owner, channel history for Editorial content, or broader channel content. With no Channels at all, retain the primary 新建频道 empty state.
- Listing and lot tables stack as labelled rows at the narrow rule. Story tables become headline rows (see Story Row).
- **Wide sheets:** the sections flow into `.cols`, one per row at 1440, two at 2560 and three at 3840. Sections side by side stay narrower than 115.5rem, so they keep their tables.
### Story Row
One story, wherever stories are listed: a desk section, the ranked list, a news channel, the archive and search (`.tbl-feed`, built by `rd.story_row`).
- **Columns:** the read toggle (Owner only), the score, the summary, the channel (where rows mix Channels, in the ranked list and the archive), the source, the age and the last cell for the deep read and relevance. The toggle and last columns have no visible head.
- **Read toggle (`.rd`):** a 22px pointer control, expanded to a 44px square for coarse pointers, with 2px corners and no border. Its visual unread mark stays an 8px blue square. A read story keeps the outlined square in the same place. Hover, focus, accessible action names and pending opacity remain unchanged.
- **Score (`.score`):** a bare numeral, right-aligned in a narrow column, in tabular figures at 88% width. 90 and above is ink at 700 (`.is-high`), under 60 is faint ink at 400 (`.is-low`), and the rest are secondary ink at 600. An unranked story shows a faint dash.
- **Summary (`.c-sum`):** at least 20em wide, breaking anywhere. The AI summary is the link, in ink at 600, and the original title follows under it as a caption line in secondary ink (`<small>`). A read story's summary drops to secondary ink at 500. A story without a summary shows its title as the link.
- **Byline and age:** the source cell (`.c-src`, at least 6.5em) joins the byline's parts with middots (HN · 热门 · 299分 94评论). The age (`.c-age`, 1 天前) never wraps and carries the exact time in its tooltip, or shows a faint dash. In the archive, where the day is in the section heading, it gives the time of day (06:15) instead.
- **Reason line (`small.why`):** among a news channel's top stories and in search, the AI's reason for the score follows the original title as a 13px line in secondary ink at 1.55, 4px below.
- **Relevance (Owner):** on channel pages, the ranked list, the archive and search, the last cell holds 相关 and 不相关 as two-state toggles, 6px under the deep read. Judging a story 不相关 also reads it. The row stays in place for that render, read, with the reason field open under its summary, and leaves an unread list at the next load. Taking the judgement back, by clearing it or switching to 相关, makes the story unread again. A 相关 vote leaves the read state alone.
- **Best comment (`.cmt`):** when a story has one, a 热评 disclosure sits 6px under the summary in 12.5px blue with the native marker. Open, the comment reads in secondary ink at 1.6, up to 72ch.
- **Phones:** the headline row has no table head or repeated labels. The toggle and score hang in the lead, with the lead widened for the 44px coarse-pointer target so their hit areas do not overlap. The summary, byline and actions wrap inside that indent.
- An anonymous reader gets the same row without the toggle, without read state and without the relevance toggles. A vote the Owner cast shows as a state mark (see State Marks).
### Prices
A listing's price, as the Monitor rows show it on the desk, on a channel page and in search.
- **Current price (`.price-now`):** ink at 600 in tabular figures, in a cell at least 6.5em wide that never wraps.
- **Reference price (`.price-was`):** struck through in faint ink with a visible Merchant reference label in rows and on the desk. A gallery plate drops the visible label and keeps it for screen readers. It is not presented as a verified previous selling price.
- **Discount (`.price-off`):** the calculated percentage uses the locale's `web.monitor.discount` string, with its merchant-reference basis explicit. No discount is a faint dash. Calculation and sorting stay unchanged; advertised percentages in collected offer text are not rewritten.
- **Change (`small.chg`):** the separately observed change in blue. An observed price drop names its own recorded old/new values wherever available, including the home desk. It never borrows the merchant reference price as historical evidence.
- A discount or a drop takes no green or red. The change line is the only colour in a price, because it is news. A gallery plate's cut is emphasized by a solid ink mark, not by colour.
- Lot prices on the desk are the watched lot's price facts. The first fact leads in ink at 600 and the rest follow as caption lines.
### Ranked List
Every featured story in the window, ranked by score, a page at a time (`/?view=`, `&minimum_score=`, `&page=`). The head's counts lead here.
- It replaces the desk under the same head. The running head's crumb names the view, the head's counts turn to plain text, and the segmented filter sits 18px below (see Segmented Filter).
- The table is the story row's, with a channel column (`.c-chan`, at least 7em, breaking between words) after the summary and the Owner's relevance toggles. The channel links to its page in ink, with a pale-blue underline on hover. On a wide sheet the page splits into two lanes.
- The workspace pager closes the list, labelled 精选分页, with small previous and next buttons and the page count. An empty view is one empty state, which also takes focus when the view's last row is marked read. A page past the end shows the last page with rows instead.
### Channel Pages
A Channel's own page (`/channels/{id}`), under its rail chapter. The head, the criteria note and the order of the parts are under Reading Page Head and Layout.
- **News (`channel_editorial.html`):** N.1 重点内容 holds the top stories with their reason lines, and N.2 其余内容 the rest, a page at a time with its own pager. Both are story rows with relevance, and each heading counts its stories. A read toggle re-renders both parts together, with the head's meta, the mark-all slot, the segmented filter and the rail tally.
- **Store monitor (`channel_monitor.html`):** N.1 当前可购买 lists the live listings a page at a time, as gallery plates or listing rows, and its heading counts them (在售 3,767 件). N.2 暂不可购买与历史 keeps listings that are out of stock or gone under a history disclosure, in the same view.
- **Auction tracker (`channel_tracker.html`):** Watched, Ending within 24 hours, and Closes later or has no deadline describe the actual groups. Their rows/plates and pagers still form wide-screen lanes. History opens when requested or matched by a search; a matching history section is not preceded by a false no-results claim.
### Archive
Every story on file, 30 to a page, filed by Auckland day.
- **Filter bar:** a keyword, the Channel (Owner), a from and a to date and the read state (Owner), then 应用筛选 and 清除. The dates are Auckland days.
- **Date errors:** invalid or reversed bounds retain the date inputs, mark them invalid and place recovery text beside the form. Do not render a normal empty-result message for an invalid range.
- **Days:** one numbered section per Auckland day on the page, headed by the date and its weekday (9.1 2026-09-29 星期二), with that day's total across all pages on the right (184 条). Its stories are story rows with the channel column and relevance.
- A read toggle re-renders the whole list with the head's count and the rail tally.
### Search
- **Field:** one free-text field and a primary 搜索 in a filter bar under the head.
- **Sections:** the page's hits, 30 to a page, sort into one numbered section per Channel in rail order, under chapter 1 (1.1, 1.2 …). Each heading gives the Channel's total hits across all pages and 打开频道, which opens the Channel filtered by the query. Each Channel's hits use its own rows: story rows with their reason lines and relevance, listing rows or lot rows.
- The sections flow into `.cols.lanes`, so on a wide sheet each Channel's hits scroll in a lane of their own, and a lone section's list splits into two lanes.
- Opening a Channel preserves the query, read-item scope and below-threshold matches. Channel counts describe matching results while searching, and stored body text remains searchable.
### Listing and Lot Rows
The shared rows for a Monitor listing and a Tracker lot (`rd.listing_row`, `rd.lot_row`) on channel pages and in search. Both are `.tbl-stack` tables. The desk keeps its own shorter rows (see Channel Desk).
- **Listing:** the item cell (`.lot`) holds the photo well, the title opening the listing in a new tab, and a faint line of vendor, type and store. A store collection is named by its store's host (bivouac.co.nz), and the Owner's own name for a Source wins. A listing that is out of stock or gone reads as closed, like a closed lot, with its hollow faint mark under that line. The AI's reason follows as a 13px secondary-ink line. Then come 价格 (see Prices), 折扣 and 分数.
- **Lot:** the photo, the title, the auction line and the AI's reason (or its summary). 截止 gives the relative deadline over its exact time, 已结束 with its closing time once closed, or a faint note that there is no deadline. 出价 holds the price facts (see Watched Lot Row), then comes 分数.
- **Operations (`.ops-stack`):** for the Owner, a last cell stacks the watch toggle (lots only) above the relevance toggles, right-aligned and 6px apart. At the narrow rule and in a lane they run as one row.
### Two-State Toggle
The Owner's relevance and watch controls (`.tg`): a word with a square that fills when on.
- **Style:** 26px tall with 9px sides, a 1px control-stroke border, 2px corners and no fill. The word is 12.5px semibold secondary ink after a 7px square outlined 1.5px in the word's colour, 6px apart, and it never wraps.
- **States:** hover moves the border to secondary ink and the word to ink. Pressed (`aria-pressed="true"`) takes a secondary-ink border, the band and ink, and fills the square. The negative choice (`.is-neg`, 不相关) takes the danger stroke and red once pressed. While its request runs it shows a wait cursor at 60% opacity. Focus is the global blue outline.
- **Uses:** 相关 and 不相关 on stories, listings and lots, as a group named for screen readers, and 关注 or 已关注 on an open lot, whose accessible name says which lot.
- Each toggle is a submit button in a real form, so it works without script.
- On a touch screen (`pointer: coarse`) a toggle grows to 44px tall with 12px sides, and toggles stand 10px apart, so a tap on 相关 never lands on 不相关.
### Reason Field
Under a story the Owner voted 不相关, 8px below the summary and at most 44rem wide. A 12.5px secondary-ink label (AI 排序哪里判断错了?(可选)) sits 4px over a 30px one-line input at 14px with a small 保存备注 button beside it. Saving posts the vote again with the reason and re-renders the row.
### Filter Bar
The filters of a monitor or tracker channel, the archive and search (`.fields`).
- **Style:** a row of labelled fields between two hairlines, 18px under the head (or the criteria note), with 12px above and 14px below inside. Fields sit 10px 12px apart, wrap as needed and line up at the bottom.
- **Fields (`.fld`):** a label in label type and secondary ink, 4px over its control, and each control 32px at least. The free-text field (`.fld-grow`) flexes from 16rem to 40rem. A select stops at 20rem, since an auction category is a whole sale title. Number fields keep 96px, date fields start at 10.5rem (`.inp-date`), and a checkbox option sits at control height (`.fld-check`).
- **Actions (`.fld-act`):** a secondary 应用筛选 and a 清除 link, 8px 14px apart. Search uses a primary 搜索 instead.
- **Store filter row (`.filter-row`):** a store monitor sets search, 信源, 品牌, 性别, 仅看折扣, 排序 and a primary 搜索 on one row. The search field flexes from 12rem to 20rem and takes the full line only at the narrow rule. A filter or the sort applies as soon as it is picked (`data-auto-apply` in `beehive.js`): a select or checkbox on change, a multiselect when its checklist closes with a different choice. The button submits the typed search, and without JavaScript it submits everything. Where the row cannot fit, search, sort and 搜索 stay together and the filters take the next line. At the narrow rule the filters fold into a 筛选 disclosure as below.
- Catalogs keep search and sort/status in the primary row, with the applied-filter count on Filters and details. Secondary filters, page-size links and channel facts are open on desktop and closed by default on narrow screens. The phone search action sits beside its field; sort/status follows on its own row. An Apply action also closes the expanded control area. Clear appears when there is a selection to reset.
- At 360px and below, the search field takes its own row and Apply follows the fields, keeping the localized search hint readable.
- The Watch List keeps status/search visible and folds its reminder filter on phones. Archive date controls remain explicit. Coarse inputs, selects, segmented links, clear actions and native summaries have 44px targets; text inputs use 16px type. Unsupported gender choices are not displayed as a useless default filter.
### Multiselect
A filter that takes several values (`.ms`), such as a monitor's 信源, 品牌 and 性别.
- **Closed:** a disclosure whose summary reads like a select, with the field well, the control stroke and the chevron, 10rem to 22rem wide. It names the choice (全部品牌, or the picked values, wrapping in full).
- **Open:** the summary's border turns blue, and a checklist opens 4px below it over the rows beneath. It is at least as wide as the summary and at most 28rem (or 90% of the window), at most 340px tall and scrolls inside, with 8px padding, a rule-stroke border, 2px corners, the sheet tone and the checklist lift. Options are 14px checkbox labels with 4px 6px padding and 2px corners that take the hover wash. Opening one multiselect closes the others in the bar. A click outside an open checklist or Escape closes it, as a select does.
- **Leave out:** on a store monitor, each option row ends in a small 不看 toggle (`.ms-not`, sent as `not_source`, `not_vendor` or `not_gender`). It is set like the 不相关 toggle: hairline at rest, danger stroke and text when on, with the option's label struck through in faint ink. A value is kept or left out, never both, and left out wins on the server. The summary adds `· 不看 童款` after the choice.
- At the narrow rule the summary drops its maximum width and the open checklist is bounded by its field, not allowed to overflow the viewport.
### History Disclosure
A part kept for reference (`.more`), such as a monitor's 暂不可购买与历史 or a tracker's 永久历史. Its heading keeps its number and count, and the list sits closed under a summary set as a blue link (展开 564 条) at 14px, with a 40% underline and the native marker. Open, a faint hint explains the list, then come the table and its pager. It opens by itself on any page after the first.
Search matches and an explicit History status also open it.
### Individual Action Recovery
A square, sheet-tone notice near the viewport's bottom-right, bounded by the viewport and a 42rem measure. It uses a 1.5px blue rule, 14px text, a small Undo button and a dismiss link. The unavailable state uses the danger rule. It is Owner-only and dismissible; without JavaScript, ordinary forms and links remain usable. A valid latest individual read/unread or unwatch operation can be undone for ten minutes. Undo refreshes the relevant main pane and counts while retaining applied query context. In-flight reminders never promise undo.
### Digest Email
A user-approved, code-led dark email (2026-10-30, evolved from seed `0f190027`). Each event is one complete, bounded block, not part of a continuous newsletter. Approval and capture provenance live in `.impeccable/surfaces/src-beehive-digest-templates-digest-email-html.md`.
- **Frame:** a fluid, single-column presentation table spans 100% up to a 640px maximum, with 8px outer gutters. Inline styles are the baseline; Outlook gets a conditional 640px wrapper. Metadata and the body declare a dark-only color scheme. Content begins immediately, with no visible masthead, contents list or section headings. A hidden, localized inbox preheader carries the digest name, date and channel/item counts.
- **Entry block:** entry slate (`#19232f`) on the email canvas (`#10151c`), with a 1px slate-blue stroke (`#62768c`), 5px corners and 10px by 12px padding. Blocks are 8px apart. The full summary comes first at 16px/24px in soft white (`#e7eaed`). The decision fact follows 5px below at the same size. A price change keeps the old price regular and the new price bold. A Tracker deadline is bold, absolute and local, with its date and its time plus zone each kept whole, and the Tracker label moves to the reference line. Label-only facts are bold. References close the block at 14px/24px in `#a7b8cc`: the source link named by hostname, then a saved-brief link 10px after it, only when a saved brief exists.
- **Links:** blue (`#a9ceff`), always underlined with a 2px offset, at 14px/24px. They wrap within the block, so long URLs and names never overflow. The generic source label appears only when there is no hostname. Focus uses a 2px blue outline.
- **Group order:** an amber source warning (`#f0b44c`, 14px/21px, with a bold channel name and localized separator) precedes the group it qualifies. Events follow. The channel line closes the group at 14px/24px in `#a7afb7`, 12px before the next group: an optional omission note (punctuated in English and Chinese), then the localized link to that channel.
- **Footer:** per-channel health statuses, the snapshot note and the site link, at 13px/20px in `#a7afb7`.
- **Type:** the system stack (-apple-system, BlinkMacSystemFont, Segoe UI, Arial, PingFang SC, Microsoft YaHei, sans-serif) is for email compatibility only; it is not web typography. Text is regular weight except for bold decision facts and warning prefixes. Caps are not reduced, and there are no kind-colored accents or badges.
- **Behavior:** configured caps, group-local deadlines, explicit website return links and saved-brief-only links remain. Preview permits user-activated no-opener windows, but not scripts or same-origin privileges.
- **Email-local values:** `#10151c`, `#19232f`, `#62768c` and `#a7b8cc` exist only in the email. The soft white, `#a9ceff`, `#a7afb7` and `#f0b44c` match the datasheet's ink, link-2, ink-2 and caution. None is a new web token, and the bounded entry block is not a card pattern for web pages. Browser previews show no overflow, but native Gmail, Apple Mail and Outlook rendering has not been validated.
### Lanes
On a wide sheet a list never makes the reader scroll back up (see The Lane Rule).
- **Split lists:** a long story, listing or lot list renders as one table with two row groups (`rd.lane_rows` over a `.tbl-lanes` table), the first holding the extra row when the count is odd. Below the lane width they read as one table. From 115.5rem each group is a lane: the head hides, the lanes sit 3.5rem apart with a 1px hairline rule in the middle, and each opens with the ink rule. Entries close with a hairline.
- **Section lanes:** sections side by side in `.cols.lanes` (a news channel's two parts, a tracker's lots, search's Channels) become lanes once the group is 123rem wide and holds two or more. Each section's heading stays pinned at the top of its lane on the sheet tone, with its Hide switch (see Hidden Sections), and its pager sits at the lane's foot. A lane is one scroller: a table inside a section lane stays a table, however wide the section.
- **Height:** each lane fits the window below where it starts, leaving room for the pager under a split list, so the whole of both lanes shows without moving the page. A lane that starts further down fits the window once scrolled to. `beehive.js` sets each lane's `--lane-h`; without it a lane stops 6rem (a section 2rem) short of the window.
- **Scrolling:** a lane scrolls on its own and never hands its scroll on to the page (`overscroll-behavior: contain`). Its scrollbar is thin, a rule-stroke thumb on no track. A swap that re-renders a block of lanes, such as a read toggle, keeps each lane where it was scrolled to. A lane is positioned (`position: relative`), so screen-reader text inside it clips with it and never stretches the table or the page below.
- **Paging:** a pager link carries no fragment, because a fragment scrolls the page even when its section is already in view, and on a wide sheet that pulls the lanes to the bottom of the window. `beehive.js` remembers the pager's section instead and, on the next page, brings it into view only when it starts in the lower half of the window, as a stacked section far down a laptop page does.
- **Story entries:** the read square and the score hang in the lead, as on phones, and the summary runs across beside them. Beneath it the source, channel and time follow in 13px secondary ink, with the deep read and relevance toggles at the right of that line.
- **Listing and lot entries:** the photo and title block spans the entry. Beneath the title, indented to it, the facts follow on one line that wraps, each led by its column name in faint caption type (价格, 折扣, 分数, 截止, 出价), with the Owner's toggles at the right.
### Gallery
The desktop photo-first specimen remains the base. On narrow screens the title, price and deadline/change facts precede the photo, so decision-making content appears before the square image. Shared subgrid tracks align the fact blocks and photos across a row where supported, with the content-first flex layout as the fallback. Photos remain whole and uncropped.
A store's or auction's listings as catalogue plates (`rd.listing_plates`, `rd.lot_plates`), for when the photo decides. A store opens as a gallery and an auction as a list.
- **Grid:** a ruled grid (`.plates`) of as many plates to a row as fit at 15rem or more (10rem at the narrow rule, two to a row on a phone), under the ink rule, split by hairlines. No shadow, no corner and no gap: the rules make the grid.
- **Plate:** 10px 12px padding, then the photo, square and whole (`object-fit: contain`) on the field fill inside a hairline edge. The name leads, at 15px in semibold ink. A store plate follows with its price line (current price at 15px, the struck former price and the discount on one line), the blue change line, and the vendor and store in faint caption type. A lot plate follows with its price facts, then its closing line as a quiet caption (the relative deadline in secondary ink and its exact time in faint ink), then its auction line. The foot, level across every plate in the row, holds the score after a faint 分数 label on the left and the Owner's watch and relevance toggles on the right.
- **Store plate (`.plate-listing`):** the brand leads the title link at 15px bold ink, and the product name follows at 14px in secondary ink, dropping a leading brand it repeats. The price line reads struck reference price (faint, 13px), current price (17px bold ink), then the cut (`.price-cut`, −70%) as a solid ink mark with on-fill text, 13px bold. The reference label and the localized discount wording stay as screen-reader text. The blue change line follows, and the store closes the plate in faint caption type, level along the row. A store plate carries no score and no relevance toggles; the list view keeps both.
- **Photo:** the photo and the title open the same page in a new tab, and only the title is a tab stop. A plate hovers in the wash. A closed plate's photo goes grayscale at 65% and its title, price and closing line drop to secondary ink. A plate the Owner marked 不相关 steps back to 55% opacity, its toggles still in full.
- **Photo sizes:** a photo is asked of its CDN near the size it shows (`sized` and `srcset` filters, `web/images.py`): Shopify takes a width, END. a square crop and YOOX a fixed size code. Any other CDN serves its one size. A photo that fails to load becomes the empty frame, never the browser's broken-image mark.
### View and Page Size
A store's or auction's head carries two segmented groups of plain links at its right, 8px apart (`rd.view_controls()`): 列表 | 画廊, and a faint 每页 label before 24 | 48 | 96. The current choice takes the band, as in any segmented filter. A choice is a query parameter (`?view=`, `?per_page=`) that the page remembers in a cookie, the view for each Channel and the page size for every store and auction, so the Channel opens the same way next time. Changing the page size starts every list on the page over. A page's own links carry a choice that isn't the default, so it holds without the cookie.
### Hidden Sections
Where sections sit side by side, including news sections, open-lot deadline groups and search channels, the reader can hide one so the rest take the width.
- **Switch (`rd.lane_toggle`):** a small secondary 隐藏 button at the end of each section heading's tools, named for its section to screen readers. The last section shown has none, so a page never hides everything. It is a plain link (`?hide=`), so it works without script.
- **Hidden section (`rd.lane_stub`):** its numbered heading alone, above the sections still shown: the number in faint ink, the name in secondary ink, its state on the right and a small 显示 button, over a hairline, 18px below the filters and 6px apart when there are several. It keeps its id, so the rail still leads to it.
- **The rest:** a lone section left takes the full width, and on a wide sheet its list splits into two lanes. Two or more stay side by side as lanes.
- **Remembered:** the choice is kept per page in a cookie (`reading_hidden`: a news or auction Channel by its id, and search as one page), so the page opens the same way next time. A section the page lacks on this render, such as the top stories past a news channel's first page, keeps its choice for when it comes back, and a remembered choice that would hide every section on a render shows them all.
### Deep-Read Brief
A story's brief (`/items/{id}/brief`), under the rail chapter it was opened from, with the crumb 深度阅读.
- **Finished:** the notebook, 24px under the head. The reading column (72ch) opens with a 注意 note when the brief carries a warning, then N.1 结论 as the answer lead, N.2 关键发现 with its count as a decimal list, and N.3 为何重要 in reading type. A 说明 note (局限性) closes it 26px below when the brief has limits. Its heads are subheads with their numbers, 26px above and 8px below.
- **Side column:** N.4 重要数据, with its count, lists each figure as a value over its label. The value is 17px bold ink in tabular figures, the label 13px secondary ink at 1.5, and hairlines split the rows with 8px above and below. N.5 来源 gives the original title as a cited title, then faint caption lines with the source and its time, and when and in which language the brief was written. Side-box heads keep label type with their number and count.
- **Unfinished without a saved result:** one numbered section, 深度阅读, at most 72ch wide, whose heading distinguishes queued, being written, failed, not started and unavailable. Queued or being written, it holds a lede and five 12px skeleton lines (full, 75%, 50%, 90% and 40% wide) and polls every three seconds. A finished brief then loads in full. A failed one holds a 警告 note with what went wrong, the next step and, for the Owner, a small 重试.
- **Updating a saved result:** keep the five content sections, generation time, language and source limitations visible. A compact status above them explains that the last successful brief is still shown. Do not replace saved content with a skeleton. A failed update retains the content and offers retry; only a successful replacement changes it. Story lists continue to offer Open brief, with a quiet update-state caption.
### Past the End
A page past the end of its list, for example after its last unread story was marked read, says so rather than going blank.
- **Note (`rd.past_end()`):** one dashed empty state with the bold line 这一页已经没有内容了。 above the pager. It can take focus, so a toggle that empties the page leaves focus on it.
- **Pager:** 第 x 页,共 y 页 always gives the real page count (第 999 页,共 509 页 on a page past the end), and 上一页 leads to the last page that has rows. Every reading pager works this way, a channel page's included. The home page's ranked list never goes past the end, since it shows its last page instead.
### Read Toggle in Place
Marking a story read or unread changes the page in place, with htmx, never by reload.
- The toggle posts, and the page swaps back only what changed: its own region (a desk section with its heading, the ranked list, a news channel's two parts, the archive's list or search's list), plus the head's meta line, the rail's count slot (`#toc-count-featured`) and, where they exist, the filter's counts and a news channel's mark-all slot.
- Focus returns to the toggle in the same row slot, which may now hold the next story. With no row left it moves to the region's first link or its empty state, so it never drops to the page.
- A polite live region says 已标为已读 or 已标为未读.
- **Relevance and watch:** a vote posts, and htmx swaps the story's row from the page the post redirects to. A listing's or lot's relevance toggles and a lot's watch toggle swap only their own form, which the route re-renders. Focus goes back to the button that submitted (`data-focus-key`), and the live region reads that button's message (已标记为有用, 已加入关注列表).
- Without script every toggle is still a form that posts and comes back to the same view. Only the Owner's pages load htmx, and a brief, which polls while it is written.
- Nothing moves. The only feedback in flight is the toggle's wait cursor and 60% opacity.
- **Opening reads:** opening a story (a click, a middle click or `o`) reads it for the Owner on the way to the article (`/items/{id}/open`). Its row takes the read state in place and stays put, and its toggle now offers 标为未读, with the words the row carries for it (`data-read-*`).
- **Mark all as read:** the post returns to the page with a 完成 note under the head that says how many stories it read, with a small 撤销 button. The batch is kept for 10 minutes (`db/read_batches.py`), and undoing it marks exactly those stories unread again.
### Keyboard Reading
- `j` and `k` walk the rows down the whole page, across sections and lanes: story rows, listing and lot rows, and gallery plates, on every reading page that lists them. `o`, or Enter on the selected row, opens it in a new tab, and `/` or `f` focuses the page's search field (`data-kb-search`). Keys do nothing while a field has focus.
- Only rows on screen take part: the rows under a closed history disclosure are skipped until it opens.
- The Owner's keys press the selected row's own control, so each works exactly as its button does: `m` marks a story read or unread, `+` (or `=`) and `-` rate it relevant or not, and `w` watches a lot or stops.
- The selected row (`.kb-row.is-selected`) takes the band, and keyboard focus adds a 1.5px blue outline inside the row. Rows keep a roving tabindex, so only the selected row joins the tab order.
- A polite live region announces the selection with its Channel, AI score and summary.
- The hint closes each of those pages in 13px faint type (`rd.kbd_hint`): `j` `k` 移动 · `o` 打开, the Owner's keys the page's rows answer to (`m` 已读/未读, `+` `-` 相关/不相关, `w` 关注), then `/` 搜索. Each key is a keycap (`kbd`), at least 1.6em wide with 4px sides, outlined in 1px rule stroke with 2px corners on the field fill, in secondary ink at 0.72rem and 600. A keycap is type, not a control. The hint hides at the narrow rule.
### Boxed Notes
The datasheet's callouts.
- **Style:** a square box with a 1.5px border in its tone. A solid tag cell at least 64px wide carries the tone word in 13px bold fill ink, tracked 0.02em (0.12em in CJK). The body is 14px with 10px 14px padding, and actions sit in a cell on the right.
- **Tones:** 说明 Note (secondary ink), 注意 Caution (amber), 警告 Warning (red) and 完成 Done (ink rule). Confirmations after an action use Done. Failures use Warning and are announced as alerts.
- Notes stack 8px apart. At the narrow rule the tag becomes a strip above the body.
- Notes also sit inside the reading column (the model-knowledge 说明 note, 22px below the text) and in a session's status region (a running or failed run).
- On the reading pages, source and scoring information uses Channel details or the catalog's expanded controls. Brief warnings, limits and failures retain their boxed notes, and bulk mark-read retains its counted completion note. Individual action recovery uses its own viewport-visible notice.
### Danger Row and Removal Popover
- **Danger row:** a Warning note built on a disclosure. Closed, it shows the bold title, what will go with its counts, an undo hint and a small danger button. Open, the button becomes 取消 and a confirmation panel unfolds below on the field fill, under a rule-stroke line. A deletion is typed. In the admin the hint is the 7-day undo, and the panel reads "输入“name”以确认。" with a growing input and a danger submit. Deleting a research session can't be undone, so its hint says 不能撤销。 and its panel asks for the word 删除 (delete in each locale) the same way. A list cleanup that keeps the records behind it, such as clearing closed watches (the lots and their history stay), also says 不能撤销。 but asks once. Its panel states what goes in one sentence above the danger submit, with no typed field.
- **Removal popover:** removing a source uses the native popover, so the table row stays in place. It is up to 460px wide, with a 1.5px red border and a red head strip with the title in 14px bold fill ink. Below come the source name, what goes with it, the undo hint and the same typed confirmation, with a cancel.
### Containers
- **Sheet:** see Layout.
- **Facts strip:** auto-fit cells of at least 150px between a 1.5px ink rule and a hairline, split by hairlines. A condensed label sits on top and a 17px semibold tabular value below. The run-budget preview sets a run's limits in one.
- **Empty state:** a dashed rule-stroke box with 18px 14px padding, a bold ink first line over secondary-ink text, and the primary action below.
- **Plain-text preview:** 13px mono at 1.75 on the field fill with a rule-stroke border, at most 560px tall and scrolling inside.
- **Pager:** 10px below a table, small previous and next buttons (a disabled one takes the hairline border and faint ink), the page count in 13px secondary ink, and on long lists a page field with a small jump button. Screen readers hear each pager named for the list it pages, such as 精选分页 on the ranked list and 其余内容分页 on a news channel. Every reading list but the ranked list uses one pager macro (`rd.pager`), shown when there is more than one page or the page is past the end (see Past the End). Paging a section returns to it only when it starts below the middle of the window (see Lanes).
- **Empty states** also sit inside a turn and under each session section. An empty research list offers a new session and an empty watch list a route to lots. An empty desk offers the Owner 新建频道; empty channel sections explain their actual exclusion and link to collection settings or broader content. A filtered catalog offers Clear, and invalid archive dates use field feedback rather than a no-results state.
### Error Sheet
Error and 404 pages (`error.html`, `not_found.html`) run without the route's data, so they skip the rail.
- One sheet, at most 560px wide, centred on the desk like the admin login. Its running head holds the brand (the hexagon, product name and numeric application version) on the left and the status code on the right in faint tabular figures.
- The h1 follows the running head, then the message 8px below in 15px secondary ink at 1.65, then the actions 22px below: the primary 返回首页, and on a 404 an 归档 link beside it.
### Brand Assets
The mark and the share images are drawn from the datasheet's own tokens, with no colour of their own.
- **Favicon (`favicon.svg`):** the hexagon mark (`M32 12 49.8 22v20L32 52 14.2 42V22Z`) as a 5.5-unit Sheet Ink stroke with round joins, on a 64×64 Graphite Sheet tile with 12-unit corners. The dark tile and the light stroke hold it at 16px on light and dark tab strips.
- **Touch icon (`apple-touch-icon.png`, 180×180):** the same mark full bleed on Graphite Sheet, with square corners, since iOS rounds its own. `datasheet_base.html` links it on every page.
- **Social card:** one composition at two sizes, `static/social-card.png` (1200×630, the site's `og:image`) and `docs/assets/github-social-preview.png` (1280×640, the README hero and GitHub's social preview). It is a reading page set larger, with synthetic stories. Its source, `docs/assets/social-card.html`, links the repository's own `admin.css` (the fonts resolve beside it), so it renders straight from a checkout with no server, and its header comment holds the two render commands, one per size. The rail holds the brand line and the chapters from 1 Featured, with its tally, to 8 Archive, and the running head reads 1 Featured on the left and Self-hosted · MIT licensed on the right. The page head sets the product line, "Turn noisy feeds into a focused daily brief.", as the h1 (Archivo 700, 52px, 1.08 line height, -0.022em tracking), then a 19px secondary-ink lede and a 14px meta line (Interest-aware · AI-ranked · Concise). The desk's 1.1 Global Markets section follows, with its state in the heading and three story rows: unread squares, scores, summaries over titles, fictional outlets and deep-read links. Only the h1, the lede and the meta line are set larger than the site's own. There is no kicker, no gradient and no extra colour, and the only blue is the unread squares and the links.
- **Share metadata:** every reading page carries a description, `og:site_name`, `og:title` (the page title), `og:description`, `og:type`, the card as `og:image` with its width, height and alt text, and `twitter:card` as `summary_large_image`. A brief is an `article`, and its description is the brief's bottom line, or the story's summary until the brief is ready, cut to 200 characters. Other pages are a `website`. The image URL uses the origin the visitor used, with the scheme from `X-Forwarded-Proto` (http or https only). The pages still send `noindex, nofollow`, so a shared link previews but the pages stay out of search results.
### Named Rules
**The Parameter Row Rule.** Legacy parameter forms keep label, control and note columns. Admin task forms stack the same relationship: the label names the parameter, the control holds the value, and the nearby note says what it does, what is in effect now and what went wrong.
**The Counted Save Rule.** A changed row is tagged 已修改 in blue and counted in the sticky save bar. After a rejected save the bar stays unsaved and says so.
**The Preview-before-Apply Rule.** In admin, testing a Source does not save it, and rule preview leaves saved rules, items and delivery unchanged. Saving rules changes future ranking; updating eligible existing results requires a separate, explicitly confirmed reprocessing action.
**The Saved-Rules Boundary Rule.** Reprocessing uses saved rules, never an unsaved draft. Its disabled state and explanatory notice follow draft changes and polling; cancellation retains results already applied.
**The Draft Boundary Rule.** Research drafts stay local to the browser tab until accepted. Navigation and preview do not consume them; successful creation, saving or sending clears only the submitted draft. Changed server values require an explicit restore or discard decision.
**The Saved-Result Rule.** A replacement attempt never removes the last successful brief. Keep its content and provenance visible through queueing and failure, and replace them only when the new result succeeds.
**The Reminder Visibility Rule.** For the Owner, show reminder failure where a watched lot is read, and link to the Watch List for recovery. A watch selection is not evidence of delivery; anonymous readers see neither private watch state nor its failure counts.
**The Mobile Disclosure Rule.** Keep search, sort/status and applied-filter counts in view. Put secondary catalog filters, page size and channel facts in one disclosure on narrow screens; keep the current channel visible and preserve 44px coarse-pointer targets without enlarging state marks.
**The Individual Recovery Rule.** Show individual read/unread and unwatch feedback in a visible, dismissible notice. Offer only a valid ten-minute undo for the latest individual action, preserve reminder delivery state, and never promise to recall an in-flight reminder.
**The Search Continuity Rule.** A search result remains reachable when opening its channel, including read and below-threshold matches. Counts describe the current scope; invalid date ranges retain values and explain the error instead of pretending there are no results.
**The Price Evidence Rule.** Label merchant reference prices separately from observed price changes. A relevance score is neither discount size nor verification of price, availability or facts; never invent a historical-low baseline.
**The Email Return Path Rule.** Email links use the explicitly configured website address and preserve channel context. Keep all selected entries within the configured cap, format deadlines in the group's time zone, and link only to saved briefs rather than implying generation.
**The One Event, One Block Rule.** In the digest email, each event is one bounded entry block holding its full summary, decision fact and references. Source warnings, channel lines and the health footer stay outside the blocks. The block is email-local; web pages do not adopt it as a card.
**The In-Place Confirmation Rule.** A destructive action opens in place (or, for a source, in a popover) into a confirmation that names what goes, gives the counts and says whether it can be undone. Deleting data is typed. Admin removals, which can be undone for 7 days, ask for the name, and deleting a research session, which can't, asks for the word 删除. A list cleanup that keeps the records behind it, such as clearing closed watches, asks once, in words.
**The Boxed Note Rule.** Callouts are square boxes in their tone, with a solid tag cell that says the tone in words (说明, 注意, 警告, 完成).
**The Notebook Rule.** A research conversation is a transcript. Who and when sit in a label column with the words beside them. The conclusion is the first turn, follow-ups continue below it and the composer comes last. Never chat bubbles, never cards.
**The Citation Number Rule.** A citation is a small blue number in brackets right after the claim it supports. Its visible text is only the number, and the source's title and quality go to the tooltip and to screen readers.
**The Labelled Skeleton Rule.** Work in progress shows skeleton lines inside a note or turn that says what is running. Never a spinner.
**The Quiet Tally Rule.** A number that informs rather than asks is a bare faint numeral. The rail's unread tally on 1 精选 shows 0 when everything is read and nothing for a reader. Outlined counts stay for attention (amber) and unread research results (blue).
**The Score Numeral Rule.** A score is a bare tabular numeral: 90 and above in ink at 700, under 60 faint, the rest in secondary ink, and an unranked item a faint dash. Never a badge, a bar or a colour.
**The Toggle-in-Place Rule.** Every Owner toggle re-renders from the server only what changed. Marking a story read swaps its section or list, the head's counts and the rail tally, and focus stays on the same row slot. A vote swaps its row, and a feedback or watch toggle its own form, with focus back on the button that submitted. A live region says what happened, and nothing reloads or animates.
**The Numbered Section Rule.** Every reading page is numbered sections under its rail chapter, and each heading carries its state or count on the right. The rail lists them under the current chapter, except the home desk's, since every Channel has a chapter of its own.
**The Two-State Toggle Rule.** A choice that is on or off is a word with a 7px square that fills when on, read from `aria-pressed` and never from colour alone. Only a pressed negative choice turns red.
**The Past-the-End Rule.** A page past the end of its list says so above its pager. The pager counts the real pages and leads back to the last page with rows, so no one is left on a blank page.
## Do's and Don'ts
### Do:
- **Do** preserve label, value and explanation together: use stacked admin task fields with nearby hints/errors, and retain the documented parameter layout for legacy forms and other shells.
- **Do** keep admin saves section-local, distinguish preview from reprocessing, and keep draft recovery and unsaved-rule feedback synchronized with the available actions.
- **Do** use the one-line field (`ui.line_field`) for any value without a length limit, such as URLs, queries, email addresses, subject templates and source names. Keep bounded values as inputs.
- **Do** keep units whole in table cells with `ui.units`, `ui.keep` and `.nw`, and break URL paths with `ui.url_path`. Mark multi-column list tables to stack at the narrow rule (`.tbl-stack`, with `data-label` on each cell), except story tables, which take `.tbl-feed` and read as headline rows. On a reading page a long list's table also takes `.tbl-lanes` and renders its rows through `rd.lane_rows`, which makes them two lanes on a wide sheet, so keep each row's cells in reading order.
- **Do** pair every state with a square mark and a word, and add amber or red only when the Owner has to act.
- **Do** give each destructive action its own Warning row (`ui.danger`) that opens in place into a confirmation naming what goes. Pass a `confirmation_value` for any deletion of data, keep the one-step worded confirmation for list cleanups, and pass a `hint` whenever the admin's 7-day undo doesn't apply, so the row never promises an undo that doesn't exist.
- **Do** use tabular figures for every changing number, and condense only label type (column heads, fact labels, transcript labels, box heads, group rows), numeric columns and scores.
- **Do** set long AI text in the reading column (`.read`, 72ch), lead a conclusion with its bottom line (`.answer`), and mark citations with `rx.cite`.
- **Do** format every short time with the shared `short_time` filter, so all three shells write today, tomorrow, yesterday and full dates the same way, in Auckland time.
- **Do** show running work as labelled skeleton lines, and mark closed lots and archived sessions with `.is-closed`.
- **Do** keep all admin, workspace and reading styling in `admin.css`, and render every page, error pages included, through `datasheet_base.html`. Templates carry no inline `style=` (a test enforces this), scripts load only from the site (`script-src 'self'`), and fonts are self-hosted.
- **Do** render every reading page through `render_reading` and `reading_base.html` (body class `page-reading`), so the rail, its tally, the footer and the clock are built in one place. Error and 404 pages extend `datasheet_base.html` as one narrow sheet, because they run without the route's data.
- **Do** build reading rows from the macros in `_reading_macros.html` (`story_row`, `listing_row`, `lot_row`, `listing_plate`, `lot_plate`, `lane_rows`, `score`, `pager`, `past_end`, `criteria`, `kbd_hint`), so a row reads the same on every page.
- **Do** ask a store's or auction's CDN for a photo near the size it shows, with the `sized` and `srcset` filters, and let a photo that fails fall back to its empty frame.
- **Do** give each section in a `.cols.lanes` group its Hide switch (`rd.lane_toggle`) and render a hidden one with `rd.lane_stub` above the group, and position anything that scrolls on its own, so absolutely placed text inside it clips with it.
- **Do** number a reading page's sections with `number_sections`, pass them to the rail as `toc_sections`, and put each section's state or count in its heading's tools (`.desk-state`).
- **Do** let the sheet run full width and give each thing its own measure: prose in `.read` (72ch), a free-text filter field in `.fld-grow` (40rem), selects under 20rem, and sections that stand alone in `.cols`.
- **Do** build every two-state control as a `.tg` button with `aria-pressed`, and mark the negative choice `.is-neg`.
- **Do** show a score with the score numeral (`.score`, `.is-high` at 90 and above, `.is-low` under 60), and a listing's price with `.price-now`, `.price-was`, `.price-off` and `small.chg`.
- **Do** give every in-place toggle its own region with `data-refocus-slot`, an `hx-select` of that region, the head's meta and the rail's count slot (`#toc-count-…`) out of band, and a `data-feedback-message` for the live region. Put `data-focus-key` and `data-feedback-message` on each button of a two-state toggle, since focus and the announcement follow the button that submitted, and wrap a swapped slot that may be empty in `.swap-slot`.
- **Do** undo any workspace clamp under `.page-reading`, as `.lot-where` does, so a reading page cuts nothing short.
- **Do** write 频道 and 信源 in Chinese copy. English capitalises Channel and Source as product terms.
- **Do** re-render the social card from `docs/assets/social-card.html` at both sizes (1200×630 and 1280×640), with the commands in its header comment, whenever the reading design changes. It styles itself with the live `admin.css`, so a shared link never previews a design the site has left behind.
### Don't:
- **Don't** build card grids, tiles or dashboards of boxes. Lists are banded tables, admin task settings are stacked fields, legacy settings retain parameter rows, and figures use ruled facts. The one catalogue grid is a store's or auction's gallery, which the Owner can switch to or from, and it stays a flat ruled plate page.
- **Don't** set a conversation as chat bubbles or cards. Turns are rows with a label column.
- **Don't** fill a control, tag or mark with blue. The one blue fill is the 8px unread square on a story row. Blue is for links, focus, unsaved edits, unread results and stories, a listing's latest change and work in progress, and the primary action is solid ink.
- **Don't** use amber or red for emphasis or decoration.
- **Don't** round containers, or give any control more than 2px.
- **Don't** lift things off the sheet with drop shadows. Change the tone instead. The removal popover and a multiselect's open checklist are the only shadows.
- **Don't** put a kicker or eyebrow above a heading or a title. Caption lines go under the value they describe. The running head and facts labels are structure, not kickers.
- **Don't** give a row, note or card a coloured side stripe. The only left bars are 2px rule-stroke lines that group things, on grouped evidence rows and beside an older plan's source list.
- **Don't** set free-length text on one unbreakable line, or let a layout depend on sideways scrolling at 390, 1024, 1440, 2560 or 3840px. The reading rail's chapter line at the narrow rule is the one deliberate sideways scroll.
- **Don't** cut text short on a reading page with an ellipsis or a line clamp.
- **Don't** give a score a badge, a bar or a colour, or colour a discount green or red.
- **Don't** write "Channel" in Chinese copy.
- **Don't** outline a control with the rule stroke. Outlined controls take the control stroke (`--ctl`), or the danger stroke (`--ctl-danger`) on danger buttons, and both hold at least 3:1 on every admin surface.
- **Don't** bring back a second stylesheet or page document. `beehive.css`, `base.html` and their partials are gone, and every page is set in `admin.css` through `datasheet_base.html`.
- **Don't** cap the sheet's width or stretch a row to fill it. Hold each thing to its own measure, and flow sections into `.cols` and a long list into lanes.
- **Don't** render an Owner control for a reader. Read state, toggles, the reason field, the criteria note and the page actions belong to the Owner, and a reader sees at most a cast vote as a state mark.
- **Don't** put product photos or real stories on the social card, or give it a gradient or a colour the reading page doesn't use. It shows synthetic stories from fictional outlets, and its only blue is the unread squares and the links.