@gjermundgaraba-designs/pixel-paper 0.0.0-stage → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DESIGN.md +1258 -0
- package/README.md +64 -2
- package/dist/src/adapters/mermaid.d.ts +59 -0
- package/dist/src/adapters/mermaid.d.ts.map +1 -0
- package/dist/src/adapters/mermaid.js +272 -0
- package/dist/src/announcer/announcer.d.ts +42 -0
- package/dist/src/announcer/announcer.d.ts.map +1 -0
- package/dist/src/announcer/announcer.js +109 -0
- package/dist/src/announcer/index.d.ts +2 -0
- package/dist/src/announcer/index.d.ts.map +1 -0
- package/dist/src/announcer/index.js +1 -0
- package/dist/src/attributes/attributes.d.ts +5 -0
- package/dist/src/attributes/attributes.d.ts.map +1 -0
- package/dist/src/attributes/attributes.js +1 -0
- package/dist/src/audioFrame/audioFrame.d.ts +80 -0
- package/dist/src/audioFrame/audioFrame.d.ts.map +1 -0
- package/dist/src/audioFrame/audioFrame.js +45 -0
- package/dist/src/audioFrame/index.d.ts +3 -0
- package/dist/src/audioFrame/index.d.ts.map +1 -0
- package/dist/src/audioFrame/index.js +1 -0
- package/dist/src/banner/banner.d.ts +21 -0
- package/dist/src/banner/banner.d.ts.map +1 -0
- package/dist/src/banner/banner.js +12 -0
- package/dist/src/banner/index.d.ts +3 -0
- package/dist/src/banner/index.d.ts.map +1 -0
- package/dist/src/banner/index.js +1 -0
- package/dist/src/barChart/barChart.d.ts +41 -0
- package/dist/src/barChart/barChart.d.ts.map +1 -0
- package/dist/src/barChart/barChart.js +113 -0
- package/dist/src/barChart/index.d.ts +3 -0
- package/dist/src/barChart/index.d.ts.map +1 -0
- package/dist/src/barChart/index.js +1 -0
- package/dist/src/board/board.d.ts +1199 -0
- package/dist/src/board/board.d.ts.map +1 -0
- package/dist/src/board/board.js +607 -0
- package/dist/src/board/index.d.ts +3 -0
- package/dist/src/board/index.d.ts.map +1 -0
- package/dist/src/board/index.js +1 -0
- package/dist/src/button/button.d.ts +70 -0
- package/dist/src/button/button.d.ts.map +1 -0
- package/dist/src/button/button.js +65 -0
- package/dist/src/button/index.d.ts +3 -0
- package/dist/src/button/index.d.ts.map +1 -0
- package/dist/src/button/index.js +1 -0
- package/dist/src/card/card.d.ts +35 -0
- package/dist/src/card/card.d.ts.map +1 -0
- package/dist/src/card/card.js +31 -0
- package/dist/src/card/index.d.ts +3 -0
- package/dist/src/card/index.d.ts.map +1 -0
- package/dist/src/card/index.js +1 -0
- package/dist/src/chartTable/chartTable.d.ts +32 -0
- package/dist/src/chartTable/chartTable.d.ts.map +1 -0
- package/dist/src/chartTable/chartTable.js +31 -0
- package/dist/src/chip/chip.d.ts +18 -0
- package/dist/src/chip/chip.d.ts.map +1 -0
- package/dist/src/chip/chip.js +18 -0
- package/dist/src/chip/index.d.ts +3 -0
- package/dist/src/chip/index.d.ts.map +1 -0
- package/dist/src/chip/index.js +1 -0
- package/dist/src/choice/choice.d.ts +17 -0
- package/dist/src/choice/choice.d.ts.map +1 -0
- package/dist/src/choice/choice.js +23 -0
- package/dist/src/choice/index.d.ts +3 -0
- package/dist/src/choice/index.d.ts.map +1 -0
- package/dist/src/choice/index.js +1 -0
- package/dist/src/choices/choices.d.ts +12 -0
- package/dist/src/choices/choices.d.ts.map +1 -0
- package/dist/src/choices/choices.js +7 -0
- package/dist/src/choices/index.d.ts +3 -0
- package/dist/src/choices/index.d.ts.map +1 -0
- package/dist/src/choices/index.js +1 -0
- package/dist/src/codeEditor/codeEditor.d.ts +16 -0
- package/dist/src/codeEditor/codeEditor.d.ts.map +1 -0
- package/dist/src/codeEditor/codeEditor.js +20 -0
- package/dist/src/codeEditor/index.d.ts +3 -0
- package/dist/src/codeEditor/index.d.ts.map +1 -0
- package/dist/src/codeEditor/index.js +1 -0
- package/dist/src/codeSurface/codeSurface.d.ts +13 -0
- package/dist/src/codeSurface/codeSurface.d.ts.map +1 -0
- package/dist/src/codeSurface/codeSurface.js +5 -0
- package/dist/src/codeSurface/index.d.ts +3 -0
- package/dist/src/codeSurface/index.d.ts.map +1 -0
- package/dist/src/codeSurface/index.js +1 -0
- package/dist/src/commandPalette/commandPalette.d.ts +587 -0
- package/dist/src/commandPalette/commandPalette.d.ts.map +1 -0
- package/dist/src/commandPalette/commandPalette.js +593 -0
- package/dist/src/commandPalette/index.d.ts +8 -0
- package/dist/src/commandPalette/index.d.ts.map +1 -0
- package/dist/src/commandPalette/index.js +5 -0
- package/dist/src/commandPalette/keys.d.ts +46 -0
- package/dist/src/commandPalette/keys.d.ts.map +1 -0
- package/dist/src/commandPalette/keys.js +104 -0
- package/dist/src/commandPalette/match.d.ts +45 -0
- package/dist/src/commandPalette/match.d.ts.map +1 -0
- package/dist/src/commandPalette/match.js +27 -0
- package/dist/src/commandPalette/message.d.ts +105 -0
- package/dist/src/commandPalette/message.d.ts.map +1 -0
- package/dist/src/commandPalette/message.js +47 -0
- package/dist/src/confirmDialog/confirmDialog.d.ts +302 -0
- package/dist/src/confirmDialog/confirmDialog.d.ts.map +1 -0
- package/dist/src/confirmDialog/confirmDialog.js +129 -0
- package/dist/src/confirmDialog/index.d.ts +3 -0
- package/dist/src/confirmDialog/index.d.ts.map +1 -0
- package/dist/src/confirmDialog/index.js +1 -0
- package/dist/src/contextMenu/contextMenu.d.ts +234 -0
- package/dist/src/contextMenu/contextMenu.d.ts.map +1 -0
- package/dist/src/contextMenu/contextMenu.js +179 -0
- package/dist/src/contextMenu/index.d.ts +4 -0
- package/dist/src/contextMenu/index.d.ts.map +1 -0
- package/dist/src/contextMenu/index.js +2 -0
- package/dist/src/copyButton/copyButton.d.ts +145 -0
- package/dist/src/copyButton/copyButton.d.ts.map +1 -0
- package/dist/src/copyButton/copyButton.js +126 -0
- package/dist/src/copyButton/index.d.ts +3 -0
- package/dist/src/copyButton/index.d.ts.map +1 -0
- package/dist/src/copyButton/index.js +1 -0
- package/dist/src/crumbs/crumbs.d.ts +18 -0
- package/dist/src/crumbs/crumbs.d.ts.map +1 -0
- package/dist/src/crumbs/crumbs.js +13 -0
- package/dist/src/crumbs/index.d.ts +3 -0
- package/dist/src/crumbs/index.d.ts.map +1 -0
- package/dist/src/crumbs/index.js +1 -0
- package/dist/src/deck/deck.d.ts +10 -0
- package/dist/src/deck/deck.d.ts.map +1 -0
- package/dist/src/deck/deck.js +7 -0
- package/dist/src/deck/index.d.ts +3 -0
- package/dist/src/deck/index.d.ts.map +1 -0
- package/dist/src/deck/index.js +1 -0
- package/dist/src/dialog/dialog.d.ts +265 -0
- package/dist/src/dialog/dialog.d.ts.map +1 -0
- package/dist/src/dialog/dialog.js +147 -0
- package/dist/src/dialog/fold.d.ts +51 -0
- package/dist/src/dialog/fold.d.ts.map +1 -0
- package/dist/src/dialog/fold.js +46 -0
- package/dist/src/dialog/index.d.ts +4 -0
- package/dist/src/dialog/index.d.ts.map +1 -0
- package/dist/src/dialog/index.js +2 -0
- package/dist/src/disclosure/disclosure.d.ts +27 -0
- package/dist/src/disclosure/disclosure.d.ts.map +1 -0
- package/dist/src/disclosure/disclosure.js +26 -0
- package/dist/src/disclosure/index.d.ts +3 -0
- package/dist/src/disclosure/index.d.ts.map +1 -0
- package/dist/src/disclosure/index.js +1 -0
- package/dist/src/dropZone/dropZone.d.ts +120 -0
- package/dist/src/dropZone/dropZone.d.ts.map +1 -0
- package/dist/src/dropZone/dropZone.js +73 -0
- package/dist/src/dropZone/index.d.ts +3 -0
- package/dist/src/dropZone/index.d.ts.map +1 -0
- package/dist/src/dropZone/index.js +1 -0
- package/dist/src/editableTitle/editableTitle.d.ts +161 -0
- package/dist/src/editableTitle/editableTitle.d.ts.map +1 -0
- package/dist/src/editableTitle/editableTitle.js +104 -0
- package/dist/src/editableTitle/index.d.ts +4 -0
- package/dist/src/editableTitle/index.d.ts.map +1 -0
- package/dist/src/editableTitle/index.js +2 -0
- package/dist/src/emptyState/emptyState.d.ts +17 -0
- package/dist/src/emptyState/emptyState.d.ts.map +1 -0
- package/dist/src/emptyState/emptyState.js +13 -0
- package/dist/src/emptyState/index.d.ts +3 -0
- package/dist/src/emptyState/index.d.ts.map +1 -0
- package/dist/src/emptyState/index.js +1 -0
- package/dist/src/errorSummary/errorSummary.d.ts +35 -0
- package/dist/src/errorSummary/errorSummary.d.ts.map +1 -0
- package/dist/src/errorSummary/errorSummary.js +33 -0
- package/dist/src/errorSummary/index.d.ts +3 -0
- package/dist/src/errorSummary/index.d.ts.map +1 -0
- package/dist/src/errorSummary/index.js +1 -0
- package/dist/src/field/field.d.ts +39 -0
- package/dist/src/field/field.d.ts.map +1 -0
- package/dist/src/field/field.js +41 -0
- package/dist/src/field/index.d.ts +3 -0
- package/dist/src/field/index.d.ts.map +1 -0
- package/dist/src/field/index.js +1 -0
- package/dist/src/fieldGrid/fieldGrid.d.ts +9 -0
- package/dist/src/fieldGrid/fieldGrid.d.ts.map +1 -0
- package/dist/src/fieldGrid/fieldGrid.js +6 -0
- package/dist/src/fieldGrid/index.d.ts +3 -0
- package/dist/src/fieldGrid/index.d.ts.map +1 -0
- package/dist/src/fieldGrid/index.js +1 -0
- package/dist/src/fields/fields.d.ts +13 -0
- package/dist/src/fields/fields.d.ts.map +1 -0
- package/dist/src/fields/fields.js +7 -0
- package/dist/src/fields/index.d.ts +3 -0
- package/dist/src/fields/index.d.ts.map +1 -0
- package/dist/src/fields/index.js +1 -0
- package/dist/src/fieldset/fieldset.d.ts +22 -0
- package/dist/src/fieldset/fieldset.d.ts.map +1 -0
- package/dist/src/fieldset/fieldset.js +25 -0
- package/dist/src/fieldset/index.d.ts +3 -0
- package/dist/src/fieldset/index.d.ts.map +1 -0
- package/dist/src/fieldset/index.js +1 -0
- package/dist/src/fileTree/fileTree.d.ts +144 -0
- package/dist/src/fileTree/fileTree.d.ts.map +1 -0
- package/dist/src/fileTree/fileTree.js +230 -0
- package/dist/src/fileTree/index.d.ts +3 -0
- package/dist/src/fileTree/index.d.ts.map +1 -0
- package/dist/src/fileTree/index.js +1 -0
- package/dist/src/filterChip/filterChip.d.ts +24 -0
- package/dist/src/filterChip/filterChip.d.ts.map +1 -0
- package/dist/src/filterChip/filterChip.js +33 -0
- package/dist/src/filterChip/index.d.ts +3 -0
- package/dist/src/filterChip/index.d.ts.map +1 -0
- package/dist/src/filterChip/index.js +1 -0
- package/dist/src/filterStrip/filterStrip.d.ts +13 -0
- package/dist/src/filterStrip/filterStrip.d.ts.map +1 -0
- package/dist/src/filterStrip/filterStrip.js +7 -0
- package/dist/src/filterStrip/index.d.ts +3 -0
- package/dist/src/filterStrip/index.d.ts.map +1 -0
- package/dist/src/filterStrip/index.js +1 -0
- package/dist/src/flashWord/flashWord.d.ts +32 -0
- package/dist/src/flashWord/flashWord.d.ts.map +1 -0
- package/dist/src/flashWord/flashWord.js +30 -0
- package/dist/src/flashWord/index.d.ts +3 -0
- package/dist/src/flashWord/index.d.ts.map +1 -0
- package/dist/src/flashWord/index.js +1 -0
- package/dist/src/focus/focus.d.ts +16 -0
- package/dist/src/focus/focus.d.ts.map +1 -0
- package/dist/src/focus/focus.js +19 -0
- package/dist/src/focus/index.d.ts +2 -0
- package/dist/src/focus/index.d.ts.map +1 -0
- package/dist/src/focus/index.js +1 -0
- package/dist/src/format/format.d.ts +17 -0
- package/dist/src/format/format.d.ts.map +1 -0
- package/dist/src/format/format.js +70 -0
- package/dist/src/format/index.d.ts +3 -0
- package/dist/src/format/index.d.ts.map +1 -0
- package/dist/src/format/index.js +2 -0
- package/dist/src/glyph/glyph.d.ts +12 -0
- package/dist/src/glyph/glyph.d.ts.map +1 -0
- package/dist/src/glyph/glyph.js +24 -0
- package/dist/src/glyph/glyphs.d.ts +380 -0
- package/dist/src/glyph/glyphs.d.ts.map +1 -0
- package/dist/src/glyph/glyphs.js +2612 -0
- package/dist/src/glyph/index.d.ts +5 -0
- package/dist/src/glyph/index.d.ts.map +1 -0
- package/dist/src/glyph/index.js +2 -0
- package/dist/src/group/group.d.ts +9 -0
- package/dist/src/group/group.d.ts.map +1 -0
- package/dist/src/group/group.js +7 -0
- package/dist/src/group/index.d.ts +3 -0
- package/dist/src/group/index.d.ts.map +1 -0
- package/dist/src/group/index.js +1 -0
- package/dist/src/history/history.d.ts +37 -0
- package/dist/src/history/history.d.ts.map +1 -0
- package/dist/src/history/history.js +22 -0
- package/dist/src/history/index.d.ts +3 -0
- package/dist/src/history/index.d.ts.map +1 -0
- package/dist/src/history/index.js +1 -0
- package/dist/src/iconButton/iconButton.d.ts +19 -0
- package/dist/src/iconButton/iconButton.d.ts.map +1 -0
- package/dist/src/iconButton/iconButton.js +18 -0
- package/dist/src/iconButton/index.d.ts +3 -0
- package/dist/src/iconButton/index.d.ts.map +1 -0
- package/dist/src/iconButton/index.js +1 -0
- package/dist/src/identity/identity.d.ts +16 -0
- package/dist/src/identity/identity.d.ts.map +1 -0
- package/dist/src/identity/identity.js +12 -0
- package/dist/src/identity/index.d.ts +3 -0
- package/dist/src/identity/index.d.ts.map +1 -0
- package/dist/src/identity/index.js +1 -0
- package/dist/src/imageFrame/imageFrame.d.ts +48 -0
- package/dist/src/imageFrame/imageFrame.d.ts.map +1 -0
- package/dist/src/imageFrame/imageFrame.js +75 -0
- package/dist/src/imageFrame/index.d.ts +3 -0
- package/dist/src/imageFrame/index.d.ts.map +1 -0
- package/dist/src/imageFrame/index.js +1 -0
- package/dist/src/index.d.ts +95 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +94 -0
- package/dist/src/inlineInput/index.d.ts +3 -0
- package/dist/src/inlineInput/index.d.ts.map +1 -0
- package/dist/src/inlineInput/index.js +1 -0
- package/dist/src/inlineInput/inline.d.ts +6 -0
- package/dist/src/inlineInput/inline.d.ts.map +1 -0
- package/dist/src/inlineInput/inline.js +7 -0
- package/dist/src/inlineInput/inlineInput.d.ts +214 -0
- package/dist/src/inlineInput/inlineInput.d.ts.map +1 -0
- package/dist/src/inlineInput/inlineInput.js +229 -0
- package/dist/src/keyHelp/index.d.ts +3 -0
- package/dist/src/keyHelp/index.d.ts.map +1 -0
- package/dist/src/keyHelp/index.js +1 -0
- package/dist/src/keyHelp/keyHelp.d.ts +330 -0
- package/dist/src/keyHelp/keyHelp.d.ts.map +1 -0
- package/dist/src/keyHelp/keyHelp.js +153 -0
- package/dist/src/keyHint/index.d.ts +4 -0
- package/dist/src/keyHint/index.d.ts.map +1 -0
- package/dist/src/keyHint/index.js +2 -0
- package/dist/src/keyHint/keyHint.d.ts +14 -0
- package/dist/src/keyHint/keyHint.d.ts.map +1 -0
- package/dist/src/keyHint/keyHint.js +5 -0
- package/dist/src/keyHint/keyLabel.d.ts +22 -0
- package/dist/src/keyHint/keyLabel.d.ts.map +1 -0
- package/dist/src/keyHint/keyLabel.js +78 -0
- package/dist/src/keymap/index.d.ts +3 -0
- package/dist/src/keymap/index.d.ts.map +1 -0
- package/dist/src/keymap/index.js +1 -0
- package/dist/src/keymap/keymap.d.ts +21 -0
- package/dist/src/keymap/keymap.d.ts.map +1 -0
- package/dist/src/keymap/keymap.js +10 -0
- package/dist/src/keys/keys.d.ts +5 -0
- package/dist/src/keys/keys.d.ts.map +1 -0
- package/dist/src/keys/keys.js +4 -0
- package/dist/src/lineChart/index.d.ts +3 -0
- package/dist/src/lineChart/index.d.ts.map +1 -0
- package/dist/src/lineChart/index.js +1 -0
- package/dist/src/lineChart/lineChart.d.ts +33 -0
- package/dist/src/lineChart/lineChart.d.ts.map +1 -0
- package/dist/src/lineChart/lineChart.js +96 -0
- package/dist/src/lineDiff/diff.d.ts +38 -0
- package/dist/src/lineDiff/diff.d.ts.map +1 -0
- package/dist/src/lineDiff/diff.js +80 -0
- package/dist/src/lineDiff/index.d.ts +3 -0
- package/dist/src/lineDiff/index.d.ts.map +1 -0
- package/dist/src/lineDiff/index.js +1 -0
- package/dist/src/lineDiff/lineDiff.d.ts +30 -0
- package/dist/src/lineDiff/lineDiff.d.ts.map +1 -0
- package/dist/src/lineDiff/lineDiff.js +65 -0
- package/dist/src/linkButton/index.d.ts +3 -0
- package/dist/src/linkButton/index.d.ts.map +1 -0
- package/dist/src/linkButton/index.js +1 -0
- package/dist/src/linkButton/linkButton.d.ts +16 -0
- package/dist/src/linkButton/linkButton.d.ts.map +1 -0
- package/dist/src/linkButton/linkButton.js +8 -0
- package/dist/src/linkState/index.d.ts +3 -0
- package/dist/src/linkState/index.d.ts.map +1 -0
- package/dist/src/linkState/index.js +1 -0
- package/dist/src/linkState/linkState.d.ts +16 -0
- package/dist/src/linkState/linkState.d.ts.map +1 -0
- package/dist/src/linkState/linkState.js +20 -0
- package/dist/src/loading/index.d.ts +3 -0
- package/dist/src/loading/index.d.ts.map +1 -0
- package/dist/src/loading/index.js +1 -0
- package/dist/src/loading/loading.d.ts +15 -0
- package/dist/src/loading/loading.d.ts.map +1 -0
- package/dist/src/loading/loading.js +9 -0
- package/dist/src/locale/locale.d.ts +4 -0
- package/dist/src/locale/locale.d.ts.map +1 -0
- package/dist/src/locale/locale.js +3 -0
- package/dist/src/mark/index.d.ts +3 -0
- package/dist/src/mark/index.d.ts.map +1 -0
- package/dist/src/mark/index.js +1 -0
- package/dist/src/mark/mark.d.ts +24 -0
- package/dist/src/mark/mark.d.ts.map +1 -0
- package/dist/src/mark/mark.js +27 -0
- package/dist/src/media/index.d.ts +3 -0
- package/dist/src/media/index.d.ts.map +1 -0
- package/dist/src/media/index.js +1 -0
- package/dist/src/media/media.d.ts +251 -0
- package/dist/src/media/media.d.ts.map +1 -0
- package/dist/src/media/media.js +386 -0
- package/dist/src/menu/index.d.ts +6 -0
- package/dist/src/menu/index.d.ts.map +1 -0
- package/dist/src/menu/index.js +3 -0
- package/dist/src/menu/item.d.ts +298 -0
- package/dist/src/menu/item.d.ts.map +1 -0
- package/dist/src/menu/item.js +16 -0
- package/dist/src/menu/menu.d.ts +218 -0
- package/dist/src/menu/menu.d.ts.map +1 -0
- package/dist/src/menu/menu.js +208 -0
- package/dist/src/menu/slip.d.ts +56 -0
- package/dist/src/menu/slip.d.ts.map +1 -0
- package/dist/src/menu/slip.js +218 -0
- package/dist/src/meter/index.d.ts +3 -0
- package/dist/src/meter/index.d.ts.map +1 -0
- package/dist/src/meter/index.js +1 -0
- package/dist/src/meter/meter.d.ts +56 -0
- package/dist/src/meter/meter.d.ts.map +1 -0
- package/dist/src/meter/meter.js +82 -0
- package/dist/src/monogram/index.d.ts +3 -0
- package/dist/src/monogram/index.d.ts.map +1 -0
- package/dist/src/monogram/index.js +1 -0
- package/dist/src/monogram/monogram.d.ts +19 -0
- package/dist/src/monogram/monogram.d.ts.map +1 -0
- package/dist/src/monogram/monogram.js +22 -0
- package/dist/src/navStrip/index.d.ts +3 -0
- package/dist/src/navStrip/index.d.ts.map +1 -0
- package/dist/src/navStrip/index.js +1 -0
- package/dist/src/navStrip/navStrip.d.ts +24 -0
- package/dist/src/navStrip/navStrip.d.ts.map +1 -0
- package/dist/src/navStrip/navStrip.js +27 -0
- package/dist/src/notice/index.d.ts +3 -0
- package/dist/src/notice/index.d.ts.map +1 -0
- package/dist/src/notice/index.js +1 -0
- package/dist/src/notice/notice.d.ts +32 -0
- package/dist/src/notice/notice.d.ts.map +1 -0
- package/dist/src/notice/notice.js +30 -0
- package/dist/src/outcome/index.d.ts +3 -0
- package/dist/src/outcome/index.d.ts.map +1 -0
- package/dist/src/outcome/index.js +1 -0
- package/dist/src/outcome/outcome.d.ts +22 -0
- package/dist/src/outcome/outcome.d.ts.map +1 -0
- package/dist/src/outcome/outcome.js +11 -0
- package/dist/src/pageHead/index.d.ts +3 -0
- package/dist/src/pageHead/index.d.ts.map +1 -0
- package/dist/src/pageHead/index.js +1 -0
- package/dist/src/pageHead/pageHead.d.ts +14 -0
- package/dist/src/pageHead/pageHead.d.ts.map +1 -0
- package/dist/src/pageHead/pageHead.js +14 -0
- package/dist/src/panel/index.d.ts +3 -0
- package/dist/src/panel/index.d.ts.map +1 -0
- package/dist/src/panel/index.js +1 -0
- package/dist/src/panel/panel.d.ts +28 -0
- package/dist/src/panel/panel.d.ts.map +1 -0
- package/dist/src/panel/panel.js +18 -0
- package/dist/src/passwordField/index.d.ts +3 -0
- package/dist/src/passwordField/index.d.ts.map +1 -0
- package/dist/src/passwordField/index.js +1 -0
- package/dist/src/passwordField/passwordField.d.ts +41 -0
- package/dist/src/passwordField/passwordField.d.ts.map +1 -0
- package/dist/src/passwordField/passwordField.js +66 -0
- package/dist/src/places/index.d.ts +3 -0
- package/dist/src/places/index.d.ts.map +1 -0
- package/dist/src/places/index.js +1 -0
- package/dist/src/places/places.d.ts +28 -0
- package/dist/src/places/places.d.ts.map +1 -0
- package/dist/src/places/places.js +27 -0
- package/dist/src/propsList/index.d.ts +3 -0
- package/dist/src/propsList/index.d.ts.map +1 -0
- package/dist/src/propsList/index.js +1 -0
- package/dist/src/propsList/propsList.d.ts +19 -0
- package/dist/src/propsList/propsList.d.ts.map +1 -0
- package/dist/src/propsList/propsList.js +10 -0
- package/dist/src/prose/index.d.ts +3 -0
- package/dist/src/prose/index.d.ts.map +1 -0
- package/dist/src/prose/index.js +1 -0
- package/dist/src/prose/prose.d.ts +15 -0
- package/dist/src/prose/prose.d.ts.map +1 -0
- package/dist/src/prose/prose.js +4 -0
- package/dist/src/queryChip/index.d.ts +3 -0
- package/dist/src/queryChip/index.d.ts.map +1 -0
- package/dist/src/queryChip/index.js +1 -0
- package/dist/src/queryChip/queryChip.d.ts +19 -0
- package/dist/src/queryChip/queryChip.d.ts.map +1 -0
- package/dist/src/queryChip/queryChip.js +18 -0
- package/dist/src/radioChoices/index.d.ts +3 -0
- package/dist/src/radioChoices/index.d.ts.map +1 -0
- package/dist/src/radioChoices/index.js +1 -0
- package/dist/src/radioChoices/radioChoices.d.ts +124 -0
- package/dist/src/radioChoices/radioChoices.d.ts.map +1 -0
- package/dist/src/radioChoices/radioChoices.js +79 -0
- package/dist/src/rail/index.d.ts +3 -0
- package/dist/src/rail/index.d.ts.map +1 -0
- package/dist/src/rail/index.js +1 -0
- package/dist/src/rail/rail.d.ts +157 -0
- package/dist/src/rail/rail.d.ts.map +1 -0
- package/dist/src/rail/rail.js +181 -0
- package/dist/src/rows/index.d.ts +3 -0
- package/dist/src/rows/index.d.ts.map +1 -0
- package/dist/src/rows/index.js +1 -0
- package/dist/src/rows/rows.d.ts +92 -0
- package/dist/src/rows/rows.d.ts.map +1 -0
- package/dist/src/rows/rows.js +60 -0
- package/dist/src/scheme/index.d.ts +4 -0
- package/dist/src/scheme/index.d.ts.map +1 -0
- package/dist/src/scheme/index.js +3 -0
- package/dist/src/scheme/scheme.d.ts +14 -0
- package/dist/src/scheme/scheme.d.ts.map +1 -0
- package/dist/src/scheme/scheme.js +12 -0
- package/dist/src/scheme/schemeSwitch.d.ts +105 -0
- package/dist/src/scheme/schemeSwitch.d.ts.map +1 -0
- package/dist/src/scheme/schemeSwitch.js +110 -0
- package/dist/src/scheme/script.d.ts +9 -0
- package/dist/src/scheme/script.d.ts.map +1 -0
- package/dist/src/scheme/script.js +8 -0
- package/dist/src/secret/index.d.ts +3 -0
- package/dist/src/secret/index.d.ts.map +1 -0
- package/dist/src/secret/index.js +1 -0
- package/dist/src/secret/secret.d.ts +14 -0
- package/dist/src/secret/secret.d.ts.map +1 -0
- package/dist/src/secret/secret.js +7 -0
- package/dist/src/segmented/index.d.ts +3 -0
- package/dist/src/segmented/index.d.ts.map +1 -0
- package/dist/src/segmented/index.js +1 -0
- package/dist/src/segmented/segmented.d.ts +25 -0
- package/dist/src/segmented/segmented.d.ts.map +1 -0
- package/dist/src/segmented/segmented.js +27 -0
- package/dist/src/selectAll/index.d.ts +3 -0
- package/dist/src/selectAll/index.d.ts.map +1 -0
- package/dist/src/selectAll/index.js +1 -0
- package/dist/src/selectAll/selectAll.d.ts +26 -0
- package/dist/src/selectAll/selectAll.d.ts.map +1 -0
- package/dist/src/selectAll/selectAll.js +35 -0
- package/dist/src/selectField/index.d.ts +3 -0
- package/dist/src/selectField/index.d.ts.map +1 -0
- package/dist/src/selectField/index.js +1 -0
- package/dist/src/selectField/selectField.d.ts +39 -0
- package/dist/src/selectField/selectField.d.ts.map +1 -0
- package/dist/src/selectField/selectField.js +36 -0
- package/dist/src/sheet/index.d.ts +3 -0
- package/dist/src/sheet/index.d.ts.map +1 -0
- package/dist/src/sheet/index.js +1 -0
- package/dist/src/sheet/sheet.d.ts +17 -0
- package/dist/src/sheet/sheet.d.ts.map +1 -0
- package/dist/src/sheet/sheet.js +13 -0
- package/dist/src/skipLink/index.d.ts +2 -0
- package/dist/src/skipLink/index.d.ts.map +1 -0
- package/dist/src/skipLink/index.js +1 -0
- package/dist/src/skipLink/skipLink.d.ts +17 -0
- package/dist/src/skipLink/skipLink.d.ts.map +1 -0
- package/dist/src/skipLink/skipLink.js +19 -0
- package/dist/src/slot/slot.d.ts +9 -0
- package/dist/src/slot/slot.d.ts.map +1 -0
- package/dist/src/slot/slot.js +8 -0
- package/dist/src/stack/index.d.ts +3 -0
- package/dist/src/stack/index.d.ts.map +1 -0
- package/dist/src/stack/index.js +1 -0
- package/dist/src/stack/stack.d.ts +17 -0
- package/dist/src/stack/stack.d.ts.map +1 -0
- package/dist/src/stack/stack.js +15 -0
- package/dist/src/stage/index.d.ts +3 -0
- package/dist/src/stage/index.d.ts.map +1 -0
- package/dist/src/stage/index.js +1 -0
- package/dist/src/stage/stage.d.ts +19 -0
- package/dist/src/stage/stage.d.ts.map +1 -0
- package/dist/src/stage/stage.js +17 -0
- package/dist/src/stat/index.d.ts +3 -0
- package/dist/src/stat/index.d.ts.map +1 -0
- package/dist/src/stat/index.js +1 -0
- package/dist/src/stat/stat.d.ts +35 -0
- package/dist/src/stat/stat.d.ts.map +1 -0
- package/dist/src/stat/stat.js +44 -0
- package/dist/src/statusBar/index.d.ts +4 -0
- package/dist/src/statusBar/index.d.ts.map +1 -0
- package/dist/src/statusBar/index.js +2 -0
- package/dist/src/statusBar/statusBar.d.ts +29 -0
- package/dist/src/statusBar/statusBar.d.ts.map +1 -0
- package/dist/src/statusBar/statusBar.js +21 -0
- package/dist/src/subscription/prefixKeys.d.ts +14 -0
- package/dist/src/subscription/prefixKeys.d.ts.map +1 -0
- package/dist/src/subscription/prefixKeys.js +16 -0
- package/dist/src/table/index.d.ts +3 -0
- package/dist/src/table/index.d.ts.map +1 -0
- package/dist/src/table/index.js +1 -0
- package/dist/src/table/table.d.ts +31 -0
- package/dist/src/table/table.d.ts.map +1 -0
- package/dist/src/table/table.js +24 -0
- package/dist/src/tabs/index.d.ts +3 -0
- package/dist/src/tabs/index.d.ts.map +1 -0
- package/dist/src/tabs/index.js +1 -0
- package/dist/src/tabs/tabs.d.ts +127 -0
- package/dist/src/tabs/tabs.d.ts.map +1 -0
- package/dist/src/tabs/tabs.js +76 -0
- package/dist/src/tag/index.d.ts +3 -0
- package/dist/src/tag/index.d.ts.map +1 -0
- package/dist/src/tag/index.js +1 -0
- package/dist/src/tag/tag.d.ts +17 -0
- package/dist/src/tag/tag.d.ts.map +1 -0
- package/dist/src/tag/tag.js +23 -0
- package/dist/src/textButton/index.d.ts +3 -0
- package/dist/src/textButton/index.d.ts.map +1 -0
- package/dist/src/textButton/index.js +1 -0
- package/dist/src/textButton/textButton.d.ts +9 -0
- package/dist/src/textButton/textButton.d.ts.map +1 -0
- package/dist/src/textButton/textButton.js +10 -0
- package/dist/src/textareaField/index.d.ts +3 -0
- package/dist/src/textareaField/index.d.ts.map +1 -0
- package/dist/src/textareaField/index.js +1 -0
- package/dist/src/textareaField/textareaField.d.ts +29 -0
- package/dist/src/textareaField/textareaField.d.ts.map +1 -0
- package/dist/src/textareaField/textareaField.js +32 -0
- package/dist/src/time/index.d.ts +3 -0
- package/dist/src/time/index.d.ts.map +1 -0
- package/dist/src/time/index.js +1 -0
- package/dist/src/time/time.d.ts +41 -0
- package/dist/src/time/time.d.ts.map +1 -0
- package/dist/src/time/time.js +61 -0
- package/dist/src/tips/index.d.ts +3 -0
- package/dist/src/tips/index.d.ts.map +1 -0
- package/dist/src/tips/index.js +1 -0
- package/dist/src/tips/tips.d.ts +390 -0
- package/dist/src/tips/tips.d.ts.map +1 -0
- package/dist/src/tips/tips.js +314 -0
- package/dist/src/toast/index.d.ts +5 -0
- package/dist/src/toast/index.d.ts.map +1 -0
- package/dist/src/toast/index.js +2 -0
- package/dist/src/toast/stack.d.ts +3223 -0
- package/dist/src/toast/stack.d.ts.map +1 -0
- package/dist/src/toast/stack.js +38 -0
- package/dist/src/toast/toast.d.ts +639 -0
- package/dist/src/toast/toast.d.ts.map +1 -0
- package/dist/src/toast/toast.js +296 -0
- package/dist/src/toc/index.d.ts +3 -0
- package/dist/src/toc/index.d.ts.map +1 -0
- package/dist/src/toc/index.js +1 -0
- package/dist/src/toc/toc.d.ts +47 -0
- package/dist/src/toc/toc.d.ts.map +1 -0
- package/dist/src/toc/toc.js +48 -0
- package/dist/src/toolbar/index.d.ts +3 -0
- package/dist/src/toolbar/index.d.ts.map +1 -0
- package/dist/src/toolbar/index.js +1 -0
- package/dist/src/toolbar/toolbar.d.ts +48 -0
- package/dist/src/toolbar/toolbar.d.ts.map +1 -0
- package/dist/src/toolbar/toolbar.js +102 -0
- package/dist/src/topBar/barHeight.d.ts +26 -0
- package/dist/src/topBar/barHeight.d.ts.map +1 -0
- package/dist/src/topBar/barHeight.js +37 -0
- package/dist/src/topBar/index.d.ts +4 -0
- package/dist/src/topBar/index.d.ts.map +1 -0
- package/dist/src/topBar/index.js +2 -0
- package/dist/src/topBar/topBar.d.ts +21 -0
- package/dist/src/topBar/topBar.d.ts.map +1 -0
- package/dist/src/topBar/topBar.js +12 -0
- package/dist/src/videoFrame/index.d.ts +3 -0
- package/dist/src/videoFrame/index.d.ts.map +1 -0
- package/dist/src/videoFrame/index.js +1 -0
- package/dist/src/videoFrame/videoFrame.d.ts +106 -0
- package/dist/src/videoFrame/videoFrame.d.ts.map +1 -0
- package/dist/src/videoFrame/videoFrame.js +86 -0
- package/dist/src/wall/index.d.ts +3 -0
- package/dist/src/wall/index.d.ts.map +1 -0
- package/dist/src/wall/index.js +1 -0
- package/dist/src/wall/wall.d.ts +31 -0
- package/dist/src/wall/wall.d.ts.map +1 -0
- package/dist/src/wall/wall.js +20 -0
- package/dist/src/window/index.d.ts +3 -0
- package/dist/src/window/index.d.ts.map +1 -0
- package/dist/src/window/index.js +1 -0
- package/dist/src/window/window.d.ts +27 -0
- package/dist/src/window/window.d.ts.map +1 -0
- package/dist/src/window/window.js +28 -0
- package/dist/src/wordmark/index.d.ts +3 -0
- package/dist/src/wordmark/index.d.ts.map +1 -0
- package/dist/src/wordmark/index.js +1 -0
- package/dist/src/wordmark/wordmark.d.ts +11 -0
- package/dist/src/wordmark/wordmark.d.ts.map +1 -0
- package/dist/src/wordmark/wordmark.js +10 -0
- package/dist/src/wrap/index.d.ts +3 -0
- package/dist/src/wrap/index.d.ts.map +1 -0
- package/dist/src/wrap/index.js +1 -0
- package/dist/src/wrap/wrap.d.ts +13 -0
- package/dist/src/wrap/wrap.d.ts.map +1 -0
- package/dist/src/wrap/wrap.js +7 -0
- package/fonts/DepartureMono-Regular.woff2 +0 -0
- package/fonts/LICENSE.txt +2 -0
- package/package.json +73 -3
- package/src/adapters/mermaid.ts +335 -0
- package/src/announcer/announcer.ts +156 -0
- package/src/announcer/index.ts +1 -0
- package/src/attributes/attributes.ts +5 -0
- package/src/audioFrame/audioFrame.ts +75 -0
- package/src/audioFrame/index.ts +2 -0
- package/src/banner/banner.ts +32 -0
- package/src/banner/index.ts +2 -0
- package/src/barChart/barChart.ts +220 -0
- package/src/barChart/index.ts +2 -0
- package/src/board/board.ts +1044 -0
- package/src/board/index.ts +19 -0
- package/src/button/button.ts +156 -0
- package/src/button/index.ts +2 -0
- package/src/card/card.ts +92 -0
- package/src/card/index.ts +2 -0
- package/src/chartTable/chartTable.ts +88 -0
- package/src/chip/chip.ts +39 -0
- package/src/chip/index.ts +2 -0
- package/src/choice/choice.ts +49 -0
- package/src/choice/index.ts +2 -0
- package/src/choices/choices.ts +19 -0
- package/src/choices/index.ts +2 -0
- package/src/codeEditor/codeEditor.ts +40 -0
- package/src/codeEditor/index.ts +2 -0
- package/src/codeSurface/codeSurface.ts +20 -0
- package/src/codeSurface/index.ts +2 -0
- package/src/commandPalette/commandPalette.ts +968 -0
- package/src/commandPalette/index.ts +26 -0
- package/src/commandPalette/keys.ts +145 -0
- package/src/commandPalette/match.ts +82 -0
- package/src/commandPalette/message.ts +56 -0
- package/src/confirmDialog/confirmDialog.ts +225 -0
- package/src/confirmDialog/index.ts +13 -0
- package/src/contextMenu/contextMenu.ts +289 -0
- package/src/contextMenu/index.ts +15 -0
- package/src/copyButton/copyButton.ts +181 -0
- package/src/copyButton/index.ts +13 -0
- package/src/crumbs/crumbs.ts +43 -0
- package/src/crumbs/index.ts +2 -0
- package/src/deck/deck.ts +17 -0
- package/src/deck/index.ts +2 -0
- package/src/dialog/dialog.ts +266 -0
- package/src/dialog/fold.ts +112 -0
- package/src/dialog/index.ts +16 -0
- package/src/disclosure/disclosure.ts +59 -0
- package/src/disclosure/index.ts +2 -0
- package/src/dropZone/dropZone.ts +120 -0
- package/src/dropZone/index.ts +2 -0
- package/src/editableTitle/editableTitle.ts +172 -0
- package/src/editableTitle/index.ts +13 -0
- package/src/emptyState/emptyState.ts +34 -0
- package/src/emptyState/index.ts +2 -0
- package/src/errorSummary/errorSummary.ts +69 -0
- package/src/errorSummary/index.ts +2 -0
- package/src/field/field.ts +107 -0
- package/src/field/index.ts +2 -0
- package/src/fieldGrid/fieldGrid.ts +16 -0
- package/src/fieldGrid/index.ts +2 -0
- package/src/fields/fields.ts +23 -0
- package/src/fields/index.ts +2 -0
- package/src/fieldset/fieldset.ts +54 -0
- package/src/fieldset/index.ts +2 -0
- package/src/fileTree/fileTree.ts +470 -0
- package/src/fileTree/index.ts +2 -0
- package/src/filterChip/filterChip.ts +69 -0
- package/src/filterChip/index.ts +2 -0
- package/src/filterStrip/filterStrip.ts +20 -0
- package/src/filterStrip/index.ts +2 -0
- package/src/flashWord/flashWord.ts +42 -0
- package/src/flashWord/index.ts +2 -0
- package/src/focus/focus.ts +24 -0
- package/src/focus/index.ts +1 -0
- package/src/format/format.ts +111 -0
- package/src/format/index.ts +2 -0
- package/src/glyph/glyph.ts +43 -0
- package/src/glyph/glyphs.ts +2644 -0
- package/src/glyph/index.ts +4 -0
- package/src/group/group.ts +16 -0
- package/src/group/index.ts +2 -0
- package/src/history/history.ts +71 -0
- package/src/history/index.ts +2 -0
- package/src/iconButton/iconButton.ts +46 -0
- package/src/iconButton/index.ts +2 -0
- package/src/identity/identity.ts +38 -0
- package/src/identity/index.ts +2 -0
- package/src/imageFrame/imageFrame.ts +143 -0
- package/src/imageFrame/index.ts +2 -0
- package/src/index.ts +94 -0
- package/src/inlineInput/index.ts +16 -0
- package/src/inlineInput/inline.ts +13 -0
- package/src/inlineInput/inlineInput.ts +325 -0
- package/src/keyHelp/index.ts +17 -0
- package/src/keyHelp/keyHelp.ts +265 -0
- package/src/keyHint/index.ts +3 -0
- package/src/keyHint/keyHint.ts +19 -0
- package/src/keyHint/keyLabel.ts +126 -0
- package/src/keymap/index.ts +2 -0
- package/src/keymap/keymap.ts +38 -0
- package/src/keys/keys.ts +6 -0
- package/src/lineChart/index.ts +2 -0
- package/src/lineChart/lineChart.ts +226 -0
- package/src/lineDiff/diff.ts +166 -0
- package/src/lineDiff/index.ts +2 -0
- package/src/lineDiff/lineDiff.ts +140 -0
- package/src/linkButton/index.ts +2 -0
- package/src/linkButton/linkButton.ts +29 -0
- package/src/linkState/index.ts +2 -0
- package/src/linkState/linkState.ts +47 -0
- package/src/loading/index.ts +2 -0
- package/src/loading/loading.ts +25 -0
- package/src/locale/locale.ts +3 -0
- package/src/mark/index.ts +2 -0
- package/src/mark/mark.ts +63 -0
- package/src/media/index.ts +17 -0
- package/src/media/media.ts +565 -0
- package/src/menu/index.ts +17 -0
- package/src/menu/item.ts +86 -0
- package/src/menu/menu.ts +318 -0
- package/src/menu/slip.ts +424 -0
- package/src/meter/index.ts +2 -0
- package/src/meter/meter.ts +182 -0
- package/src/monogram/index.ts +2 -0
- package/src/monogram/monogram.ts +57 -0
- package/src/navStrip/index.ts +2 -0
- package/src/navStrip/navStrip.ts +59 -0
- package/src/notice/index.ts +2 -0
- package/src/notice/notice.ts +78 -0
- package/src/outcome/index.ts +2 -0
- package/src/outcome/outcome.ts +39 -0
- package/src/pageHead/index.ts +2 -0
- package/src/pageHead/pageHead.ts +35 -0
- package/src/panel/index.ts +2 -0
- package/src/panel/panel.ts +53 -0
- package/src/passwordField/index.ts +2 -0
- package/src/passwordField/passwordField.ts +147 -0
- package/src/pixel-paper.css +5220 -0
- package/src/places/index.ts +2 -0
- package/src/places/places.ts +74 -0
- package/src/propsList/index.ts +2 -0
- package/src/propsList/propsList.ts +38 -0
- package/src/prose/index.ts +2 -0
- package/src/prose/prose.ts +19 -0
- package/src/queryChip/index.ts +2 -0
- package/src/queryChip/queryChip.ts +45 -0
- package/src/radioChoices/index.ts +11 -0
- package/src/radioChoices/radioChoices.ts +138 -0
- package/src/rail/index.ts +14 -0
- package/src/rail/rail.ts +301 -0
- package/src/rows/index.ts +9 -0
- package/src/rows/rows.ts +218 -0
- package/src/scheme/index.ts +3 -0
- package/src/scheme/scheme.ts +30 -0
- package/src/scheme/schemeSwitch.ts +157 -0
- package/src/scheme/script.ts +10 -0
- package/src/secret/index.ts +2 -0
- package/src/secret/secret.ts +24 -0
- package/src/segmented/index.ts +2 -0
- package/src/segmented/segmented.ts +73 -0
- package/src/selectAll/index.ts +2 -0
- package/src/selectAll/selectAll.ts +72 -0
- package/src/selectField/index.ts +2 -0
- package/src/selectField/selectField.ts +99 -0
- package/src/sheet/index.ts +2 -0
- package/src/sheet/sheet.ts +36 -0
- package/src/skipLink/index.ts +1 -0
- package/src/skipLink/skipLink.ts +27 -0
- package/src/slot/slot.ts +21 -0
- package/src/stack/index.ts +2 -0
- package/src/stack/stack.ts +37 -0
- package/src/stage/index.ts +2 -0
- package/src/stage/stage.ts +44 -0
- package/src/stat/index.ts +2 -0
- package/src/stat/stat.ts +117 -0
- package/src/statusBar/index.ts +3 -0
- package/src/statusBar/statusBar.ts +63 -0
- package/src/subscription/prefixKeys.ts +29 -0
- package/src/table/index.ts +2 -0
- package/src/table/table.ts +81 -0
- package/src/tabs/index.ts +2 -0
- package/src/tabs/tabs.ts +139 -0
- package/src/tag/index.ts +2 -0
- package/src/tag/tag.ts +50 -0
- package/src/textButton/index.ts +2 -0
- package/src/textButton/textButton.ts +23 -0
- package/src/textareaField/index.ts +2 -0
- package/src/textareaField/textareaField.ts +78 -0
- package/src/time/index.ts +2 -0
- package/src/time/time.ts +136 -0
- package/src/tips/index.ts +15 -0
- package/src/tips/tips.ts +518 -0
- package/src/toast/index.ts +17 -0
- package/src/toast/stack.ts +60 -0
- package/src/toast/toast.ts +482 -0
- package/src/toc/index.ts +2 -0
- package/src/toc/toc.ts +129 -0
- package/src/tokens.css +159 -0
- package/src/toolbar/index.ts +2 -0
- package/src/toolbar/toolbar.ts +206 -0
- package/src/topBar/barHeight.ts +56 -0
- package/src/topBar/index.ts +3 -0
- package/src/topBar/topBar.ts +36 -0
- package/src/videoFrame/index.ts +2 -0
- package/src/videoFrame/videoFrame.ts +167 -0
- package/src/wall/index.ts +2 -0
- package/src/wall/wall.ts +59 -0
- package/src/window/index.ts +2 -0
- package/src/window/window.ts +71 -0
- package/src/wordmark/index.ts +2 -0
- package/src/wordmark/wordmark.ts +22 -0
- package/src/wrap/index.ts +2 -0
- package/src/wrap/wrap.ts +21 -0
package/DESIGN.md
ADDED
|
@@ -0,0 +1,1258 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pixel paper
|
|
3
|
+
description: Printed paper panels with ink borders, solid offset shadows, a pixel monospace voice and 12×12 cell glyphs, in a paper and a plum palette; tokens, a stylesheet and Foldkit components for dense working interfaces.
|
|
4
|
+
colors:
|
|
5
|
+
ink: "#16150f"
|
|
6
|
+
paper: "#fdfdfa"
|
|
7
|
+
ground: "#dcdcd4"
|
|
8
|
+
rule: "#d8d8d0"
|
|
9
|
+
head: "#efefe8"
|
|
10
|
+
muted: "#5d5c52"
|
|
11
|
+
amber: "#f5a623"
|
|
12
|
+
green: "#2fbf71"
|
|
13
|
+
red: "#b3251e"
|
|
14
|
+
on-status: "#16150f"
|
|
15
|
+
on-red: "#fdfdfa"
|
|
16
|
+
on-head: "#5d5c52"
|
|
17
|
+
shadow: "rgba(22, 21, 15, 0.85)"
|
|
18
|
+
shadow-danger: "rgba(179, 37, 30, 0.6)"
|
|
19
|
+
veil: "rgba(22, 21, 15, 0.4)"
|
|
20
|
+
dither-base: "#e0e0d8"
|
|
21
|
+
dither-dot: "#bcbcb2"
|
|
22
|
+
ink-plum: "#efecf3"
|
|
23
|
+
paper-plum: "#3b3640"
|
|
24
|
+
ground-plum: "#2d2931"
|
|
25
|
+
rule-plum: "#534c5a"
|
|
26
|
+
head-plum: "#453f4b"
|
|
27
|
+
muted-plum: "#aea7b8"
|
|
28
|
+
red-plum: "#ff8074"
|
|
29
|
+
on-red-plum: "#16150f"
|
|
30
|
+
on-head-plum: "#b6afc1"
|
|
31
|
+
shadow-plum: "rgba(0, 0, 0, 0.8)"
|
|
32
|
+
dither-base-plum: "#2f2b33"
|
|
33
|
+
dither-dot-plum: "#423d4a"
|
|
34
|
+
term-black: "#16150f"
|
|
35
|
+
term-red: "#a3271f"
|
|
36
|
+
term-green: "#2b6a2e"
|
|
37
|
+
term-yellow: "#735600"
|
|
38
|
+
term-blue: "#274b8c"
|
|
39
|
+
term-magenta: "#7a2a5c"
|
|
40
|
+
term-cyan: "#23605c"
|
|
41
|
+
term-white: "#5d5c52"
|
|
42
|
+
term-bright-black: "#45443c"
|
|
43
|
+
term-bright-red: "#b8321f"
|
|
44
|
+
term-bright-green: "#347a36"
|
|
45
|
+
term-bright-yellow: "#7d6200"
|
|
46
|
+
term-bright-blue: "#3059a3"
|
|
47
|
+
term-bright-magenta: "#8f3570"
|
|
48
|
+
term-bright-cyan: "#2a6f6a"
|
|
49
|
+
term-bright-white: "#16150f"
|
|
50
|
+
term-black-plum: "#16150f"
|
|
51
|
+
term-red-plum: "#ff9589"
|
|
52
|
+
term-green-plum: "#8fd49a"
|
|
53
|
+
term-yellow-plum: "#e6c86e"
|
|
54
|
+
term-blue-plum: "#a9c1fb"
|
|
55
|
+
term-magenta-plum: "#f2a7d4"
|
|
56
|
+
term-cyan-plum: "#8fd3cb"
|
|
57
|
+
term-white-plum: "#d9d4e0"
|
|
58
|
+
term-bright-black-plum: "#b6afc1"
|
|
59
|
+
term-bright-red-plum: "#ffa89d"
|
|
60
|
+
term-bright-green-plum: "#a8e3b1"
|
|
61
|
+
term-bright-yellow-plum: "#f0d98f"
|
|
62
|
+
term-bright-blue-plum: "#c0d2fd"
|
|
63
|
+
term-bright-magenta-plum: "#f7c1e2"
|
|
64
|
+
term-bright-cyan-plum: "#aee3dc"
|
|
65
|
+
term-bright-white-plum: "#efecf3"
|
|
66
|
+
typography:
|
|
67
|
+
wordmark:
|
|
68
|
+
fontFamily: "Departure Mono, ui-monospace, Menlo, Monaco, monospace"
|
|
69
|
+
fontSize: "18px"
|
|
70
|
+
fontWeight: 400
|
|
71
|
+
letterSpacing: "-0.02em"
|
|
72
|
+
wordmark-wall:
|
|
73
|
+
fontFamily: "Departure Mono, ui-monospace, Menlo, Monaco, monospace"
|
|
74
|
+
fontSize: "22px"
|
|
75
|
+
fontWeight: 400
|
|
76
|
+
letterSpacing: "-0.02em"
|
|
77
|
+
title-bar:
|
|
78
|
+
fontFamily: "Departure Mono, ui-monospace, Menlo, Monaco, monospace"
|
|
79
|
+
fontSize: "13px"
|
|
80
|
+
fontWeight: 400
|
|
81
|
+
letterSpacing: "0.08em"
|
|
82
|
+
name:
|
|
83
|
+
fontFamily: "Departure Mono, ui-monospace, Menlo, Monaco, monospace"
|
|
84
|
+
fontSize: "14px"
|
|
85
|
+
fontWeight: 400
|
|
86
|
+
letterSpacing: "0.04em"
|
|
87
|
+
control:
|
|
88
|
+
fontFamily: "Departure Mono, ui-monospace, Menlo, Monaco, monospace"
|
|
89
|
+
fontSize: "12px"
|
|
90
|
+
fontWeight: 400
|
|
91
|
+
letterSpacing: "0.04em"
|
|
92
|
+
control-small:
|
|
93
|
+
fontFamily: "Departure Mono, ui-monospace, Menlo, Monaco, monospace"
|
|
94
|
+
fontSize: "11px"
|
|
95
|
+
fontWeight: 400
|
|
96
|
+
letterSpacing: "0.04em"
|
|
97
|
+
chip:
|
|
98
|
+
fontFamily: "Departure Mono, ui-monospace, Menlo, Monaco, monospace"
|
|
99
|
+
fontSize: "11px"
|
|
100
|
+
fontWeight: 400
|
|
101
|
+
letterSpacing: "0.05em"
|
|
102
|
+
label:
|
|
103
|
+
fontFamily: "Departure Mono, ui-monospace, Menlo, Monaco, monospace"
|
|
104
|
+
fontSize: "10px"
|
|
105
|
+
fontWeight: 400
|
|
106
|
+
letterSpacing: "0.1em"
|
|
107
|
+
meta:
|
|
108
|
+
fontFamily: "Departure Mono, ui-monospace, Menlo, Monaco, monospace"
|
|
109
|
+
fontSize: "10px"
|
|
110
|
+
fontWeight: 400
|
|
111
|
+
letterSpacing: "normal"
|
|
112
|
+
age:
|
|
113
|
+
fontFamily: "Departure Mono, ui-monospace, Menlo, Monaco, monospace"
|
|
114
|
+
fontSize: "11px"
|
|
115
|
+
fontWeight: 400
|
|
116
|
+
fontFeature: "tabular-nums"
|
|
117
|
+
figure:
|
|
118
|
+
fontFamily: "Departure Mono, ui-monospace, Menlo, Monaco, monospace"
|
|
119
|
+
fontSize: "30px"
|
|
120
|
+
fontWeight: 400
|
|
121
|
+
lineHeight: 1.1
|
|
122
|
+
fontFeature: "tabular-nums"
|
|
123
|
+
figure-narrow:
|
|
124
|
+
fontFamily: "Departure Mono, ui-monospace, Menlo, Monaco, monospace"
|
|
125
|
+
fontSize: "22px"
|
|
126
|
+
fontWeight: 400
|
|
127
|
+
lineHeight: 1.1
|
|
128
|
+
fontFeature: "tabular-nums"
|
|
129
|
+
heading:
|
|
130
|
+
fontFamily: "system-ui, Helvetica Neue, Helvetica, Arial, sans-serif"
|
|
131
|
+
fontSize: "16px"
|
|
132
|
+
fontWeight: 600
|
|
133
|
+
lineHeight: 1.45
|
|
134
|
+
page-title:
|
|
135
|
+
fontFamily: "system-ui, Helvetica Neue, Helvetica, Arial, sans-serif"
|
|
136
|
+
fontSize: "22px"
|
|
137
|
+
fontWeight: 600
|
|
138
|
+
lineHeight: 1.2
|
|
139
|
+
letterSpacing: "-0.01em"
|
|
140
|
+
page-title-narrow:
|
|
141
|
+
fontFamily: "system-ui, Helvetica Neue, Helvetica, Arial, sans-serif"
|
|
142
|
+
fontSize: "18px"
|
|
143
|
+
fontWeight: 600
|
|
144
|
+
lineHeight: 1.2
|
|
145
|
+
letterSpacing: "-0.01em"
|
|
146
|
+
body:
|
|
147
|
+
fontFamily: "system-ui, Helvetica Neue, Helvetica, Arial, sans-serif"
|
|
148
|
+
fontSize: "13px"
|
|
149
|
+
fontWeight: 400
|
|
150
|
+
lineHeight: 1.45
|
|
151
|
+
note:
|
|
152
|
+
fontFamily: "system-ui, Helvetica Neue, Helvetica, Arial, sans-serif"
|
|
153
|
+
fontSize: "12px"
|
|
154
|
+
fontWeight: 400
|
|
155
|
+
lineHeight: 1.45
|
|
156
|
+
code:
|
|
157
|
+
fontFamily: "ui-monospace, Menlo, Monaco, monospace"
|
|
158
|
+
fontSize: "12px"
|
|
159
|
+
fontWeight: 400
|
|
160
|
+
lineHeight: 1.6
|
|
161
|
+
reading-title:
|
|
162
|
+
fontFamily: "system-ui, Helvetica Neue, Helvetica, Arial, sans-serif"
|
|
163
|
+
fontSize: "30px"
|
|
164
|
+
fontWeight: 650
|
|
165
|
+
lineHeight: 1.15
|
|
166
|
+
letterSpacing: "-0.01em"
|
|
167
|
+
reading-title-phone:
|
|
168
|
+
fontFamily: "system-ui, Helvetica Neue, Helvetica, Arial, sans-serif"
|
|
169
|
+
fontSize: "25px"
|
|
170
|
+
fontWeight: 650
|
|
171
|
+
lineHeight: 1.15
|
|
172
|
+
letterSpacing: "-0.01em"
|
|
173
|
+
reading-h2:
|
|
174
|
+
fontFamily: "system-ui, Helvetica Neue, Helvetica, Arial, sans-serif"
|
|
175
|
+
fontSize: "20px"
|
|
176
|
+
fontWeight: 650
|
|
177
|
+
lineHeight: 1.3
|
|
178
|
+
letterSpacing: "-0.005em"
|
|
179
|
+
reading-h3:
|
|
180
|
+
fontFamily: "system-ui, Helvetica Neue, Helvetica, Arial, sans-serif"
|
|
181
|
+
fontSize: "15px"
|
|
182
|
+
fontWeight: 650
|
|
183
|
+
lineHeight: 1.4
|
|
184
|
+
reading-body:
|
|
185
|
+
fontFamily: "system-ui, Helvetica Neue, Helvetica, Arial, sans-serif"
|
|
186
|
+
fontSize: "15px"
|
|
187
|
+
fontWeight: 400
|
|
188
|
+
lineHeight: 1.65
|
|
189
|
+
reading-aside:
|
|
190
|
+
fontFamily: "system-ui, Helvetica Neue, Helvetica, Arial, sans-serif"
|
|
191
|
+
fontSize: "14px"
|
|
192
|
+
fontWeight: 400
|
|
193
|
+
lineHeight: 1.55
|
|
194
|
+
rounded:
|
|
195
|
+
none: "0"
|
|
196
|
+
spacing:
|
|
197
|
+
hair: "2px"
|
|
198
|
+
xs: "4px"
|
|
199
|
+
sm: "6px"
|
|
200
|
+
md: "8px"
|
|
201
|
+
lg: "12px"
|
|
202
|
+
xl: "16px"
|
|
203
|
+
2xl: "24px"
|
|
204
|
+
space-s: "8px"
|
|
205
|
+
space-m: "14px"
|
|
206
|
+
space-l: "24px"
|
|
207
|
+
deck-bottom: "80px"
|
|
208
|
+
deck-max: "1160px"
|
|
209
|
+
rail: "272px"
|
|
210
|
+
card-max: "680px"
|
|
211
|
+
card-narrow: "440px"
|
|
212
|
+
row-min: "34px"
|
|
213
|
+
reading-measure: "68ch"
|
|
214
|
+
chart-plot: "160px"
|
|
215
|
+
components:
|
|
216
|
+
button:
|
|
217
|
+
backgroundColor: "{colors.paper}"
|
|
218
|
+
textColor: "{colors.ink}"
|
|
219
|
+
typography: "{typography.control}"
|
|
220
|
+
rounded: "{rounded.none}"
|
|
221
|
+
padding: "5px 14px"
|
|
222
|
+
button-primary:
|
|
223
|
+
backgroundColor: "{colors.ink}"
|
|
224
|
+
textColor: "{colors.paper}"
|
|
225
|
+
typography: "{typography.control}"
|
|
226
|
+
rounded: "{rounded.none}"
|
|
227
|
+
padding: "5px 14px"
|
|
228
|
+
button-small:
|
|
229
|
+
backgroundColor: "{colors.paper}"
|
|
230
|
+
textColor: "{colors.ink}"
|
|
231
|
+
typography: "{typography.control-small}"
|
|
232
|
+
rounded: "{rounded.none}"
|
|
233
|
+
padding: "3px 10px"
|
|
234
|
+
button-danger:
|
|
235
|
+
backgroundColor: "{colors.paper}"
|
|
236
|
+
textColor: "{colors.red}"
|
|
237
|
+
typography: "{typography.control}"
|
|
238
|
+
rounded: "{rounded.none}"
|
|
239
|
+
padding: "5px 14px"
|
|
240
|
+
button-disabled:
|
|
241
|
+
backgroundColor: "{colors.paper}"
|
|
242
|
+
textColor: "{colors.muted}"
|
|
243
|
+
typography: "{typography.control}"
|
|
244
|
+
rounded: "{rounded.none}"
|
|
245
|
+
padding: "5px 14px"
|
|
246
|
+
button-done:
|
|
247
|
+
backgroundColor: "{colors.ink}"
|
|
248
|
+
textColor: "{colors.paper}"
|
|
249
|
+
typography: "{typography.control}"
|
|
250
|
+
rounded: "{rounded.none}"
|
|
251
|
+
padding: "5px 14px"
|
|
252
|
+
text-button:
|
|
253
|
+
textColor: "{colors.ink}"
|
|
254
|
+
typography: "{typography.control}"
|
|
255
|
+
padding: "2px 0"
|
|
256
|
+
icon-button:
|
|
257
|
+
textColor: "{colors.muted}"
|
|
258
|
+
size: "24px"
|
|
259
|
+
padding: "0"
|
|
260
|
+
nav-segment:
|
|
261
|
+
backgroundColor: "{colors.paper}"
|
|
262
|
+
textColor: "{colors.ink}"
|
|
263
|
+
typography: "{typography.control}"
|
|
264
|
+
rounded: "{rounded.none}"
|
|
265
|
+
padding: "5px 14px"
|
|
266
|
+
nav-segment-current:
|
|
267
|
+
backgroundColor: "{colors.ink}"
|
|
268
|
+
textColor: "{colors.paper}"
|
|
269
|
+
typography: "{typography.control}"
|
|
270
|
+
rounded: "{rounded.none}"
|
|
271
|
+
padding: "5px 14px"
|
|
272
|
+
nav-count:
|
|
273
|
+
backgroundColor: "{colors.head}"
|
|
274
|
+
textColor: "{colors.on-head}"
|
|
275
|
+
typography: "{typography.meta}"
|
|
276
|
+
padding: "0 5px"
|
|
277
|
+
place:
|
|
278
|
+
textColor: "{colors.ink}"
|
|
279
|
+
typography: "{typography.body}"
|
|
280
|
+
padding: "8px 12px"
|
|
281
|
+
height: "{spacing.row-min}"
|
|
282
|
+
place-hover:
|
|
283
|
+
backgroundColor: "{colors.head}"
|
|
284
|
+
place-current:
|
|
285
|
+
backgroundColor: "{colors.ink}"
|
|
286
|
+
textColor: "{colors.paper}"
|
|
287
|
+
typography: "{typography.body}"
|
|
288
|
+
padding: "8px 12px"
|
|
289
|
+
height: "{spacing.row-min}"
|
|
290
|
+
page-title:
|
|
291
|
+
textColor: "{colors.ink}"
|
|
292
|
+
typography: "{typography.page-title}"
|
|
293
|
+
key-hint:
|
|
294
|
+
typography: "{typography.meta}"
|
|
295
|
+
tab:
|
|
296
|
+
backgroundColor: "{colors.paper}"
|
|
297
|
+
textColor: "{colors.ink}"
|
|
298
|
+
typography: "{typography.control-small}"
|
|
299
|
+
rounded: "{rounded.none}"
|
|
300
|
+
padding: "4px 12px"
|
|
301
|
+
chip:
|
|
302
|
+
backgroundColor: "{colors.paper}"
|
|
303
|
+
textColor: "{colors.ink}"
|
|
304
|
+
typography: "{typography.chip}"
|
|
305
|
+
rounded: "{rounded.none}"
|
|
306
|
+
padding: "1px 6px"
|
|
307
|
+
chip-go:
|
|
308
|
+
backgroundColor: "{colors.green}"
|
|
309
|
+
textColor: "{colors.on-status}"
|
|
310
|
+
typography: "{typography.chip}"
|
|
311
|
+
rounded: "{rounded.none}"
|
|
312
|
+
padding: "1px 6px"
|
|
313
|
+
chip-warn:
|
|
314
|
+
backgroundColor: "{colors.amber}"
|
|
315
|
+
textColor: "{colors.on-status}"
|
|
316
|
+
typography: "{typography.chip}"
|
|
317
|
+
rounded: "{rounded.none}"
|
|
318
|
+
padding: "1px 6px"
|
|
319
|
+
chip-blocked:
|
|
320
|
+
backgroundColor: "{colors.paper}"
|
|
321
|
+
textColor: "{colors.red}"
|
|
322
|
+
typography: "{typography.chip}"
|
|
323
|
+
rounded: "{rounded.none}"
|
|
324
|
+
padding: "1px 6px"
|
|
325
|
+
chip-done:
|
|
326
|
+
backgroundColor: "{colors.head}"
|
|
327
|
+
textColor: "{colors.on-head}"
|
|
328
|
+
typography: "{typography.chip}"
|
|
329
|
+
rounded: "{rounded.none}"
|
|
330
|
+
padding: "1px 6px"
|
|
331
|
+
filter-chip:
|
|
332
|
+
backgroundColor: "{colors.paper}"
|
|
333
|
+
textColor: "{colors.ink}"
|
|
334
|
+
typography: "{typography.chip}"
|
|
335
|
+
rounded: "{rounded.none}"
|
|
336
|
+
padding: "1px 6px"
|
|
337
|
+
height: "22px"
|
|
338
|
+
filter-chip-pinned:
|
|
339
|
+
backgroundColor: "{colors.ink}"
|
|
340
|
+
textColor: "{colors.paper}"
|
|
341
|
+
typography: "{typography.chip}"
|
|
342
|
+
rounded: "{rounded.none}"
|
|
343
|
+
padding: "1px 6px"
|
|
344
|
+
height: "22px"
|
|
345
|
+
mark:
|
|
346
|
+
backgroundColor: "{colors.amber}"
|
|
347
|
+
textColor: "{colors.on-status}"
|
|
348
|
+
rounded: "{rounded.none}"
|
|
349
|
+
padding: "2px"
|
|
350
|
+
mark-plain:
|
|
351
|
+
textColor: "{colors.muted}"
|
|
352
|
+
padding: "2px"
|
|
353
|
+
mark-reading:
|
|
354
|
+
backgroundColor: "{colors.amber}"
|
|
355
|
+
textColor: "{colors.on-status}"
|
|
356
|
+
typography: "{typography.control-small}"
|
|
357
|
+
padding: "1px 6px"
|
|
358
|
+
tag:
|
|
359
|
+
textColor: "{colors.muted}"
|
|
360
|
+
typography: "{typography.meta}"
|
|
361
|
+
padding: "0 5px"
|
|
362
|
+
due-label:
|
|
363
|
+
textColor: "{colors.muted}"
|
|
364
|
+
typography: "{typography.control-small}"
|
|
365
|
+
due-label-late:
|
|
366
|
+
textColor: "{colors.red}"
|
|
367
|
+
typography: "{typography.control-small}"
|
|
368
|
+
panel:
|
|
369
|
+
backgroundColor: "{colors.paper}"
|
|
370
|
+
rounded: "{rounded.none}"
|
|
371
|
+
panel-title-bar:
|
|
372
|
+
backgroundColor: "{colors.ink}"
|
|
373
|
+
textColor: "{colors.paper}"
|
|
374
|
+
typography: "{typography.title-bar}"
|
|
375
|
+
padding: "6px 12px"
|
|
376
|
+
panel-body:
|
|
377
|
+
backgroundColor: "{colors.paper}"
|
|
378
|
+
typography: "{typography.body}"
|
|
379
|
+
padding: "16px"
|
|
380
|
+
panel-foot:
|
|
381
|
+
backgroundColor: "{colors.paper}"
|
|
382
|
+
padding: "10px 12px"
|
|
383
|
+
panel-action:
|
|
384
|
+
backgroundColor: "{colors.ink}"
|
|
385
|
+
textColor: "{colors.paper}"
|
|
386
|
+
typography: "{typography.control-small}"
|
|
387
|
+
rounded: "{rounded.none}"
|
|
388
|
+
padding: "3px 10px"
|
|
389
|
+
panel-action-primary:
|
|
390
|
+
backgroundColor: "{colors.paper}"
|
|
391
|
+
textColor: "{colors.ink}"
|
|
392
|
+
typography: "{typography.control-small}"
|
|
393
|
+
rounded: "{rounded.none}"
|
|
394
|
+
padding: "3px 10px"
|
|
395
|
+
stat:
|
|
396
|
+
padding: "14px 16px"
|
|
397
|
+
stat-value:
|
|
398
|
+
textColor: "{colors.ink}"
|
|
399
|
+
typography: "{typography.figure}"
|
|
400
|
+
stat-delta:
|
|
401
|
+
textColor: "{colors.ink}"
|
|
402
|
+
typography: "{typography.chip}"
|
|
403
|
+
stat-delta-warn:
|
|
404
|
+
backgroundColor: "{colors.amber}"
|
|
405
|
+
textColor: "{colors.on-status}"
|
|
406
|
+
typography: "{typography.chip}"
|
|
407
|
+
padding: "1px 6px"
|
|
408
|
+
stat-delta-error:
|
|
409
|
+
backgroundColor: "{colors.paper}"
|
|
410
|
+
textColor: "{colors.red}"
|
|
411
|
+
typography: "{typography.chip}"
|
|
412
|
+
padding: "1px 6px"
|
|
413
|
+
filter-strip:
|
|
414
|
+
backgroundColor: "{colors.paper}"
|
|
415
|
+
typography: "{typography.control-small}"
|
|
416
|
+
rounded: "{rounded.none}"
|
|
417
|
+
padding: "8px 12px"
|
|
418
|
+
card:
|
|
419
|
+
backgroundColor: "{colors.paper}"
|
|
420
|
+
rounded: "{rounded.none}"
|
|
421
|
+
width: "{spacing.card-max}"
|
|
422
|
+
card-title-bar:
|
|
423
|
+
backgroundColor: "{colors.ink}"
|
|
424
|
+
textColor: "{colors.paper}"
|
|
425
|
+
typography: "{typography.title-bar}"
|
|
426
|
+
padding: "6px 8px 6px 12px"
|
|
427
|
+
card-actions:
|
|
428
|
+
backgroundColor: "{colors.paper}"
|
|
429
|
+
padding: "12px 16px"
|
|
430
|
+
board-card:
|
|
431
|
+
backgroundColor: "{colors.paper}"
|
|
432
|
+
textColor: "{colors.ink}"
|
|
433
|
+
typography: "{typography.body}"
|
|
434
|
+
rounded: "{rounded.none}"
|
|
435
|
+
padding: "9px 11px"
|
|
436
|
+
board-card-hover:
|
|
437
|
+
backgroundColor: "{colors.head}"
|
|
438
|
+
board-card-current:
|
|
439
|
+
backgroundColor: "{colors.ink}"
|
|
440
|
+
textColor: "{colors.paper}"
|
|
441
|
+
typography: "{typography.body}"
|
|
442
|
+
rounded: "{rounded.none}"
|
|
443
|
+
padding: "9px 11px"
|
|
444
|
+
menu:
|
|
445
|
+
backgroundColor: "{colors.paper}"
|
|
446
|
+
rounded: "{rounded.none}"
|
|
447
|
+
width: "148px"
|
|
448
|
+
menu-item:
|
|
449
|
+
backgroundColor: "{colors.paper}"
|
|
450
|
+
textColor: "{colors.ink}"
|
|
451
|
+
typography: "{typography.control}"
|
|
452
|
+
padding: "7px 12px"
|
|
453
|
+
menu-item-hover:
|
|
454
|
+
backgroundColor: "{colors.head}"
|
|
455
|
+
menu-item-current:
|
|
456
|
+
backgroundColor: "{colors.ink}"
|
|
457
|
+
textColor: "{colors.paper}"
|
|
458
|
+
typography: "{typography.control}"
|
|
459
|
+
padding: "7px 12px"
|
|
460
|
+
menu-item-unavailable:
|
|
461
|
+
backgroundColor: "{colors.paper}"
|
|
462
|
+
textColor: "{colors.muted}"
|
|
463
|
+
typography: "{typography.control}"
|
|
464
|
+
padding: "7px 12px"
|
|
465
|
+
row:
|
|
466
|
+
backgroundColor: "{colors.paper}"
|
|
467
|
+
typography: "{typography.body}"
|
|
468
|
+
padding: "5px 12px"
|
|
469
|
+
height: "{spacing.row-min}"
|
|
470
|
+
row-hover:
|
|
471
|
+
backgroundColor: "{colors.head}"
|
|
472
|
+
row-current:
|
|
473
|
+
typography: "{typography.body}"
|
|
474
|
+
padding: "5px 12px"
|
|
475
|
+
height: "{spacing.row-min}"
|
|
476
|
+
row-check-selected:
|
|
477
|
+
backgroundColor: "{colors.ink}"
|
|
478
|
+
padding: "0 10px"
|
|
479
|
+
bulk-count:
|
|
480
|
+
textColor: "{colors.muted}"
|
|
481
|
+
typography: "{typography.chip}"
|
|
482
|
+
field-label:
|
|
483
|
+
textColor: "{colors.muted}"
|
|
484
|
+
typography: "{typography.label}"
|
|
485
|
+
field-input:
|
|
486
|
+
backgroundColor: "{colors.paper}"
|
|
487
|
+
textColor: "{colors.ink}"
|
|
488
|
+
typography: "{typography.body}"
|
|
489
|
+
rounded: "{rounded.none}"
|
|
490
|
+
padding: "2px 0"
|
|
491
|
+
field-input-boxed:
|
|
492
|
+
backgroundColor: "{colors.paper}"
|
|
493
|
+
textColor: "{colors.ink}"
|
|
494
|
+
typography: "{typography.body}"
|
|
495
|
+
rounded: "{rounded.none}"
|
|
496
|
+
padding: "5px 8px"
|
|
497
|
+
select:
|
|
498
|
+
backgroundColor: "{colors.paper}"
|
|
499
|
+
textColor: "{colors.ink}"
|
|
500
|
+
typography: "{typography.control-small}"
|
|
501
|
+
rounded: "{rounded.none}"
|
|
502
|
+
padding: "5px 28px 3px 8px"
|
|
503
|
+
height: "30px"
|
|
504
|
+
checkbox:
|
|
505
|
+
backgroundColor: "{colors.paper}"
|
|
506
|
+
rounded: "{rounded.none}"
|
|
507
|
+
size: "14px"
|
|
508
|
+
notice:
|
|
509
|
+
backgroundColor: "{colors.paper}"
|
|
510
|
+
textColor: "{colors.ink}"
|
|
511
|
+
typography: "{typography.note}"
|
|
512
|
+
rounded: "{rounded.none}"
|
|
513
|
+
padding: "8px 10px"
|
|
514
|
+
notice-glyph:
|
|
515
|
+
backgroundColor: "{colors.amber}"
|
|
516
|
+
textColor: "{colors.on-status}"
|
|
517
|
+
size: "20px"
|
|
518
|
+
notice-glyph-error:
|
|
519
|
+
backgroundColor: "{colors.red}"
|
|
520
|
+
textColor: "{colors.on-red}"
|
|
521
|
+
size: "20px"
|
|
522
|
+
notice-glyph-quiet:
|
|
523
|
+
backgroundColor: "{colors.head}"
|
|
524
|
+
textColor: "{colors.on-head}"
|
|
525
|
+
size: "20px"
|
|
526
|
+
error-summary-link:
|
|
527
|
+
textColor: "{colors.ink}"
|
|
528
|
+
typography: "{typography.note}"
|
|
529
|
+
toast:
|
|
530
|
+
backgroundColor: "{colors.paper}"
|
|
531
|
+
textColor: "{colors.ink}"
|
|
532
|
+
typography: "{typography.note}"
|
|
533
|
+
rounded: "{rounded.none}"
|
|
534
|
+
padding: "8px 10px"
|
|
535
|
+
width: "360px"
|
|
536
|
+
table-head:
|
|
537
|
+
backgroundColor: "{colors.head}"
|
|
538
|
+
textColor: "{colors.on-head}"
|
|
539
|
+
typography: "{typography.label}"
|
|
540
|
+
padding: "6px 12px"
|
|
541
|
+
table-cell:
|
|
542
|
+
typography: "{typography.body}"
|
|
543
|
+
padding: "8px 12px"
|
|
544
|
+
table-row-hover:
|
|
545
|
+
backgroundColor: "{colors.head}"
|
|
546
|
+
table-stacked-row:
|
|
547
|
+
typography: "{typography.body}"
|
|
548
|
+
padding: "10px 12px"
|
|
549
|
+
props-term:
|
|
550
|
+
textColor: "{colors.muted}"
|
|
551
|
+
typography: "{typography.label}"
|
|
552
|
+
padding: "7px 16px 7px 0"
|
|
553
|
+
code-inline:
|
|
554
|
+
backgroundColor: "{colors.head}"
|
|
555
|
+
textColor: "{colors.ink}"
|
|
556
|
+
typography: "{typography.code}"
|
|
557
|
+
padding: "1px 6px"
|
|
558
|
+
code-surface:
|
|
559
|
+
backgroundColor: "{colors.head}"
|
|
560
|
+
textColor: "{colors.ink}"
|
|
561
|
+
typography: "{typography.code}"
|
|
562
|
+
rounded: "{rounded.none}"
|
|
563
|
+
padding: "10px 12px"
|
|
564
|
+
secret:
|
|
565
|
+
backgroundColor: "{colors.head}"
|
|
566
|
+
textColor: "{colors.ink}"
|
|
567
|
+
typography: "{typography.code}"
|
|
568
|
+
padding: "6px 6px 6px 12px"
|
|
569
|
+
diff-line:
|
|
570
|
+
backgroundColor: "{colors.paper}"
|
|
571
|
+
textColor: "{colors.ink}"
|
|
572
|
+
typography: "{typography.code}"
|
|
573
|
+
padding: "0 8px"
|
|
574
|
+
diff-line-add:
|
|
575
|
+
backgroundColor: "{colors.head}"
|
|
576
|
+
textColor: "{colors.ink}"
|
|
577
|
+
typography: "{typography.code}"
|
|
578
|
+
padding: "0 8px"
|
|
579
|
+
diff-line-del:
|
|
580
|
+
backgroundColor: "{colors.paper}"
|
|
581
|
+
textColor: "{colors.muted}"
|
|
582
|
+
typography: "{typography.code}"
|
|
583
|
+
padding: "0 8px"
|
|
584
|
+
diff-fold:
|
|
585
|
+
backgroundColor: "{colors.head}"
|
|
586
|
+
textColor: "{colors.on-head}"
|
|
587
|
+
typography: "{typography.meta}"
|
|
588
|
+
padding: "2px 12px"
|
|
589
|
+
meter-cells:
|
|
590
|
+
backgroundColor: "{colors.paper}"
|
|
591
|
+
rounded: "{rounded.none}"
|
|
592
|
+
padding: "2px"
|
|
593
|
+
meter-cell:
|
|
594
|
+
backgroundColor: "{colors.head}"
|
|
595
|
+
width: "10px"
|
|
596
|
+
height: "14px"
|
|
597
|
+
meter-cell-on:
|
|
598
|
+
backgroundColor: "{colors.ink}"
|
|
599
|
+
width: "10px"
|
|
600
|
+
height: "14px"
|
|
601
|
+
meter-cell-stale:
|
|
602
|
+
backgroundColor: "{colors.muted}"
|
|
603
|
+
width: "10px"
|
|
604
|
+
height: "14px"
|
|
605
|
+
strength-cell:
|
|
606
|
+
backgroundColor: "{colors.head}"
|
|
607
|
+
width: "24px"
|
|
608
|
+
height: "8px"
|
|
609
|
+
palette-row:
|
|
610
|
+
textColor: "{colors.ink}"
|
|
611
|
+
typography: "{typography.control}"
|
|
612
|
+
padding: "8px 16px"
|
|
613
|
+
palette-row-selected:
|
|
614
|
+
backgroundColor: "{colors.ink}"
|
|
615
|
+
textColor: "{colors.paper}"
|
|
616
|
+
typography: "{typography.control}"
|
|
617
|
+
padding: "8px 16px"
|
|
618
|
+
file-item:
|
|
619
|
+
textColor: "{colors.ink}"
|
|
620
|
+
typography: "{typography.code}"
|
|
621
|
+
file-item-current:
|
|
622
|
+
backgroundColor: "{colors.ink}"
|
|
623
|
+
textColor: "{colors.paper}"
|
|
624
|
+
typography: "{typography.code}"
|
|
625
|
+
drop-zone:
|
|
626
|
+
textColor: "{colors.muted}"
|
|
627
|
+
typography: "{typography.body}"
|
|
628
|
+
padding: "28px 16px"
|
|
629
|
+
drop-zone-over:
|
|
630
|
+
backgroundColor: "{colors.head}"
|
|
631
|
+
padding: "28px 16px"
|
|
632
|
+
reading-sheet:
|
|
633
|
+
backgroundColor: "{colors.paper}"
|
|
634
|
+
textColor: "{colors.ink}"
|
|
635
|
+
typography: "{typography.reading-body}"
|
|
636
|
+
padding: "28px 32px 36px"
|
|
637
|
+
width: "{spacing.reading-measure}"
|
|
638
|
+
missing-link:
|
|
639
|
+
textColor: "{colors.red}"
|
|
640
|
+
typography: "{typography.reading-body}"
|
|
641
|
+
link-state-offline:
|
|
642
|
+
backgroundColor: "{colors.red}"
|
|
643
|
+
textColor: "{colors.on-red}"
|
|
644
|
+
typography: "{typography.meta}"
|
|
645
|
+
padding: "2px 6px"
|
|
646
|
+
link-state-reconnecting:
|
|
647
|
+
backgroundColor: "{colors.amber}"
|
|
648
|
+
textColor: "{colors.on-status}"
|
|
649
|
+
typography: "{typography.meta}"
|
|
650
|
+
padding: "2px 6px"
|
|
651
|
+
tip:
|
|
652
|
+
backgroundColor: "{colors.ink}"
|
|
653
|
+
textColor: "{colors.paper}"
|
|
654
|
+
typography: "{typography.chip}"
|
|
655
|
+
rounded: "{rounded.none}"
|
|
656
|
+
padding: "3px 7px"
|
|
657
|
+
toolbar-tool:
|
|
658
|
+
backgroundColor: "{colors.paper}"
|
|
659
|
+
textColor: "{colors.ink}"
|
|
660
|
+
typography: "{typography.chip}"
|
|
661
|
+
rounded: "{rounded.none}"
|
|
662
|
+
height: "28px"
|
|
663
|
+
padding: "0 10px"
|
|
664
|
+
toolbar-tool-pressed:
|
|
665
|
+
backgroundColor: "{colors.ink}"
|
|
666
|
+
textColor: "{colors.paper}"
|
|
667
|
+
monogram:
|
|
668
|
+
backgroundColor: "{colors.paper}"
|
|
669
|
+
textColor: "{colors.ink}"
|
|
670
|
+
typography: "{typography.chip}"
|
|
671
|
+
rounded: "{rounded.none}"
|
|
672
|
+
size: "22px"
|
|
673
|
+
monogram-current:
|
|
674
|
+
backgroundColor: "{colors.ink}"
|
|
675
|
+
textColor: "{colors.paper}"
|
|
676
|
+
identity-name:
|
|
677
|
+
textColor: "{colors.ink}"
|
|
678
|
+
typography: "{typography.body}"
|
|
679
|
+
loading:
|
|
680
|
+
textColor: "{colors.muted}"
|
|
681
|
+
typography: "{typography.control}"
|
|
682
|
+
padding: "26px 16px"
|
|
683
|
+
disclosure-summary:
|
|
684
|
+
textColor: "{colors.ink}"
|
|
685
|
+
typography: "{typography.control}"
|
|
686
|
+
padding: "3px 0"
|
|
687
|
+
rail-drawer:
|
|
688
|
+
backgroundColor: "{colors.ground}"
|
|
689
|
+
width: "320px"
|
|
690
|
+
padding: "12px 16px 24px 12px"
|
|
691
|
+
window:
|
|
692
|
+
backgroundColor: "{colors.paper}"
|
|
693
|
+
rounded: "{rounded.none}"
|
|
694
|
+
window-title-bar:
|
|
695
|
+
backgroundColor: "{colors.ink}"
|
|
696
|
+
textColor: "{colors.paper}"
|
|
697
|
+
typography: "{typography.title-bar}"
|
|
698
|
+
height: "30px"
|
|
699
|
+
padding: "3px 3px 3px 10px"
|
|
700
|
+
outcome-glyph:
|
|
701
|
+
backgroundColor: "{colors.amber}"
|
|
702
|
+
textColor: "{colors.on-status}"
|
|
703
|
+
size: "34px"
|
|
704
|
+
outcome-glyph-error:
|
|
705
|
+
backgroundColor: "{colors.red}"
|
|
706
|
+
textColor: "{colors.on-red}"
|
|
707
|
+
size: "34px"
|
|
708
|
+
outcome-glyph-ok:
|
|
709
|
+
backgroundColor: "{colors.ink}"
|
|
710
|
+
textColor: "{colors.paper}"
|
|
711
|
+
size: "34px"
|
|
712
|
+
outcome-glyph-quiet:
|
|
713
|
+
backgroundColor: "{colors.head}"
|
|
714
|
+
textColor: "{colors.on-head}"
|
|
715
|
+
size: "34px"
|
|
716
|
+
banner:
|
|
717
|
+
backgroundColor: "{colors.paper}"
|
|
718
|
+
textColor: "{colors.ink}"
|
|
719
|
+
typography: "{typography.body}"
|
|
720
|
+
padding: "8px 14px"
|
|
721
|
+
banner-glyph:
|
|
722
|
+
backgroundColor: "{colors.red}"
|
|
723
|
+
textColor: "{colors.on-red}"
|
|
724
|
+
size: "26px"
|
|
725
|
+
banner-glyph-warn:
|
|
726
|
+
backgroundColor: "{colors.amber}"
|
|
727
|
+
textColor: "{colors.on-status}"
|
|
728
|
+
size: "26px"
|
|
729
|
+
banner-glyph-ok:
|
|
730
|
+
backgroundColor: "{colors.ink}"
|
|
731
|
+
textColor: "{colors.paper}"
|
|
732
|
+
size: "26px"
|
|
733
|
+
banner-glyph-quiet:
|
|
734
|
+
backgroundColor: "{colors.head}"
|
|
735
|
+
textColor: "{colors.on-head}"
|
|
736
|
+
size: "26px"
|
|
737
|
+
wall-corner:
|
|
738
|
+
backgroundColor: "{colors.paper}"
|
|
739
|
+
rounded: "{rounded.none}"
|
|
740
|
+
padding: "6px 8px"
|
|
741
|
+
empty-state:
|
|
742
|
+
typography: "{typography.body}"
|
|
743
|
+
padding: "26px 16px"
|
|
744
|
+
skip-link:
|
|
745
|
+
backgroundColor: "{colors.ink}"
|
|
746
|
+
textColor: "{colors.paper}"
|
|
747
|
+
typography: "{typography.control}"
|
|
748
|
+
padding: "6px 14px"
|
|
749
|
+
image-frame:
|
|
750
|
+
backgroundColor: "{colors.head}"
|
|
751
|
+
rounded: "{rounded.none}"
|
|
752
|
+
image-wait:
|
|
753
|
+
backgroundColor: "{colors.head}"
|
|
754
|
+
textColor: "{colors.on-head}"
|
|
755
|
+
media-bar:
|
|
756
|
+
backgroundColor: "{colors.paper}"
|
|
757
|
+
padding: "3px 6px"
|
|
758
|
+
media-time:
|
|
759
|
+
textColor: "{colors.ink}"
|
|
760
|
+
typography: "{typography.age}"
|
|
761
|
+
chart-cell:
|
|
762
|
+
backgroundColor: "{colors.head}"
|
|
763
|
+
height: "10px"
|
|
764
|
+
chart-cell-on:
|
|
765
|
+
backgroundColor: "{colors.ink}"
|
|
766
|
+
height: "10px"
|
|
767
|
+
chart-label:
|
|
768
|
+
textColor: "{colors.muted}"
|
|
769
|
+
typography: "{typography.label}"
|
|
770
|
+
line-plot:
|
|
771
|
+
backgroundColor: "{colors.paper}"
|
|
772
|
+
height: "{spacing.chart-plot}"
|
|
773
|
+
---
|
|
774
|
+
|
|
775
|
+
# Design System: pixel paper
|
|
776
|
+
|
|
777
|
+
## Overview
|
|
778
|
+
|
|
779
|
+
**Creative North Star: "The Printed Console"**
|
|
780
|
+
|
|
781
|
+
pixel paper is a working console that looks like it came off a monochrome printer: paper panels laid on a dithered grey ground, each panel closed by a 2px ink border, titled by an inverted ink bar, and lifted by a solid offset shadow with no blur. The chrome speaks in one voice, Departure Mono at weight 400, uppercase and tracked; the things people write (an item title, a page, a client name) are set in the system sans in sentence case; code sits in the system monospace on a head tint. Three hues exist, and each is a meaning, never a decoration: amber marks or warns, green is live, red is urgent, offline, failed, missing or destructive. Everything else is a neutral, except what a program prints: code and terminals carry sixteen printer's inks of their own, and only there.
|
|
782
|
+
|
|
783
|
+
Density is that of a tool, not a page: 13px body, 34px rows, 16px panel padding, 24px between panels. The one place the system slows to reading pace is an authored page (a Sheet holding Prose), which keeps the same chrome and sets text at 15px on 1.65 inside a 68ch measure with a larger sans heading scale. Every surface is one of a few shapes (panel, card, menu, chip, button) and every shape is square. Depth is a printed shadow, not light; motion is stepped or instant, the way a terminal redraws; a committed change flashes the element inverted for a beat.
|
|
784
|
+
|
|
785
|
+
The system ships as a package: `src/tokens.css` (every value, both palettes), `src/pixel-paper.css` (base elements and every component class, built on the tokens only), Foldkit components (stateless Views and stateful Submodels) that render exactly that markup, and the font; an optional adapter (`./adapters/mermaid`) prints mermaid diagrams in the same inks, lines and faces. It serves dense working interfaces (queues, boards, admin consoles, file and config managers, editors) and the documents that sit beside them. With all content removed, the inverted title bars and the stepped shadows alone should identify it.
|
|
786
|
+
|
|
787
|
+
**Key Characteristics:**
|
|
788
|
+
|
|
789
|
+
- Paper panels with 2px ink borders and inverted mono title bars on a 4px checker-dithered ground.
|
|
790
|
+
- Zero-blur solid offset shadows: 8px card and menu; 5px panel and picker; 3px button, strip and board card; 2px small button; 1px pressed.
|
|
791
|
+
- Departure Mono 400, uppercase and tracked, for every control; system sans for authored text; system mono for code.
|
|
792
|
+
- Amber, green and red carry meaning only; every other colour on chrome is a neutral token. Sixteen content inks exist for code and terminals alone.
|
|
793
|
+
- 12×12 filled-cell pixel glyphs, one weight, one grammar; no other icon style.
|
|
794
|
+
- Square corners everywhere; 2px borders for anything that contains, 1px rules for divisions inside.
|
|
795
|
+
- Quantity is cells: a meter, a bar chart, a media seek bar. A reading over time is a stepped line of whole pixels, never a diagonal or a curve.
|
|
796
|
+
- Paper (light) and plum (dark) are two values of one token set: the page follows the OS, and `data-scheme` on `<html>` pins one.
|
|
797
|
+
- Motion is stepped or instant: a two-step invert flash on committed changes, a stepped cursor blink, three-step dialog and toast entrances; reduced motion swaps the flash for a still 2px inset ring held for the beat.
|
|
798
|
+
|
|
799
|
+
## Colors
|
|
800
|
+
|
|
801
|
+
Two neutral palettes over one set of tokens, with three meaning hues shared between them; the frontmatter hex values are the normative source, written once in `src/tokens.css` as `light-dark()` pairs.
|
|
802
|
+
|
|
803
|
+
### Primary
|
|
804
|
+
|
|
805
|
+
- **Marker Amber** (`{colors.amber}`, both palettes): the Mark (a viewfinder glyph on an amber square beside a marked item), the warn chip (an update waiting, a source behind), a meter reading at or above 80% of its limit, the glyph box of a warning notice, of a confirm dialog and of a reconnecting banner; a stat's change or a bar's reading that the product calls a warning. Always carries Printer Ink text (`{colors.on-status}`); an amber glyph on paper is never allowed because it fails contrast. Where every row is marked the Mark prints plain (muted, no fill): amber is for the one among many.
|
|
806
|
+
- **Active Green** (`{colors.green}`, both palettes): the go chip of something that is live or in sync. Also carries Printer Ink text. Nothing else is green.
|
|
807
|
+
- **Urgent Red** (`{colors.red}` paper / `{colors.red-plum}` plum): the danger button's text, border and shadow tint; the blocked and limit-reached chips; a field's error line; the error notice's and error toast's border and glyph box; the offline word and an offline banner's glyph box; a stat's change or a bar's reading at fault; a broken image, video or audio frame; a stale age, a late due day (on a cursor row or card, a red slip with `on-red` text), a failed check, a stale meter's foot; a failed field's rule and label, a failed radio or select box, a fieldset's legend over a failed control; a link to a page that does not exist; the minus sign of a deleted diff line. Red fills carry Paper White text on paper (`{colors.on-red}`) and Printer Ink on plum (`{colors.on-red-plum}`), because plum lightens red for text contrast.
|
|
808
|
+
|
|
809
|
+
### Neutral
|
|
810
|
+
|
|
811
|
+
- **Printer Ink** (`{colors.ink}` paper / `{colors.ink-plum}` plum): all text, every 2px border, every inverted fill (title bars, primary button, current nav segment, pressed filter chip, current file, current menu item, the palette cursor row, the cursor card of a board, a lit cell, the version in force, a done button, a selected row's check cell, the ok glyph box), chart cells and the line chart's line, the keyboard ring, the scrollbar thumb, the text-selection wash.
|
|
812
|
+
- **Paper White** (`{colors.paper}` paper / `{colors.paper-plum}` plum): the panel, card, sheet, menu, button and chip surface; the text on inverted fills; inside a board's cursor card, what prints muted on paper prints in paper at 0.75.
|
|
813
|
+
- **Ground Grey** (`{colors.ground}` paper / `{colors.ground-plum}` plum): the page behind the panels, always under the dither (`{colors.dither-base}` cells with `{colors.dither-dot}` dots; `{colors.dither-base-plum}` / `{colors.dither-dot-plum}` on plum). Also the scrollbar track.
|
|
814
|
+
- **Head Tint** (`{colors.head}` paper / `{colors.head-plum}` plum): the one tonal step off the paper: table heads, every hover (row, board card, menu item, file, table row), inline code, the code surface and secret, an added diff line and a folded run, an unlit cell, the done chip, the quiet notice's glyph box, the nav count, a drop target, a picture's placeholder under the picture, an unlit chart cell. Text on a head fill uses **On Head** (`{colors.on-head}` / `{colors.on-head-plum}`), a separate token because Muted Ink misses AA on plum's head.
|
|
815
|
+
- **Rule Grey** (`{colors.rule}` paper / `{colors.rule-plum}` plum): every 1px division: table, property, menu, history and keymap rows, meter rows, the panel foot, outline and file-tree guides, the diff's number gutter, the tag border, the quiet notice's border, the divisions between stats, the line chart's axes, the select-all head's foot.
|
|
816
|
+
- **Muted Ink** (`{colors.muted}` paper / `{colors.muted-plum}` plum): a list's cursor line, labels, meta, ages, due days, notes, placeholders, disabled controls (text and border), quiet icon buttons, the filter chip's name half, a select-all count with nothing selected, an unavailable menu item, chart scales, labels and a second series, an unknown stat's dash, context rows, struck-through done titles, a deleted diff line, a stale meter's lit cells, list markers in prose, the boxless field's dotted baseline.
|
|
817
|
+
- **Shadow** (`{colors.shadow}` paper / `{colors.shadow-plum}` plum): the fill of every offset shadow. **Danger Shadow** (`{colors.shadow-danger}`) tints the danger button's shadow on paper; plum drops back to the plain shadow. **Veil** (`{colors.veil}`) darkens the page under a dialog, dotted with a 50% ink checker.
|
|
818
|
+
|
|
819
|
+
### Content inks
|
|
820
|
+
|
|
821
|
+
What a program prints (terminal output, highlighted code, a log) is content, not chrome, and it keeps its colours. Sixteen printer's inks, the ANSI set, are `light-dark()` tokens (`--term-black` to `--term-bright-white`; frontmatter `term-*` and `term-*-plum`): dark, muted inks on paper and light, soft ones on plum, each at AA or better on the head tint that code sits on, except black on plum, which is the ground's own tone. On paper the two whites print as a grey and as ink, because white on paper cannot be read. Six syntax roles alias them: `--code-keyword` (magenta), `--code-string` (cyan), `--code-number` (yellow), `--code-function` (blue), `--code-type` (green) and `--code-comment` (On Head). The stylesheet maps the `tok-*` classes that Lezer's `classHighlighter` prints onto the roles, so any highlighter that writes those names colours a code surface with no theme of its own.
|
|
822
|
+
|
|
823
|
+
### Named Rules
|
|
824
|
+
|
|
825
|
+
**The Content Ink Rule.** The `--term-*` and `--code-*` tokens colour only what a program printed, inside a code surface, a terminal, a log or a library adapter. They never colour chrome, state, a border, a fill or a person: a busy build is a chip, not a cyan word, and presence is a Monogram, not a hue.
|
|
826
|
+
|
|
827
|
+
**The Meaning Colour Rule.** Amber means marked or warning, green means active or in sync, red means urgent, offline, error, missing or destructive. Every other colour on a surface is a neutral token. A new element that wants colour must be one of those three meanings, or it is ink (content inks aside; see the Content Ink Rule). A meter's cells stay ink at every level; the reading beside them carries the colour. A search hit is inverted ink and a list's cursor row a muted outline, never amber, because a hit is a selection, not a warning. Charts and stats keep the rule: bar cells and the line stay ink (a second series is muted ink with smaller cells, never a hue), and a stat's change prints in ink with a direction glyph, because up is not good and down is not bad until the product says so; only a reading the product calls a warning or a fault takes amber or red.
|
|
828
|
+
|
|
829
|
+
**The Red On Commit Rule.** Red never rests on a button. Row and form actions that destroy something (disable, delete, restore over, reset) are neutral buttons; the ConfirmDialog they open carries the one red button, the commit. Red at rest belongs to states only: a blocked chip, a failed check, a missing page, the offline word. A `ConfirmDialog` with `isDanger` is the only destructive confirm: there is no inline arm-then-confirm button ("delete" that turns into "sure?"), because the dialog gives the commit protected focus (cancel is the default) and an alert dialog's semantics, while an armed button is easy to double-click through and shifts the layout as its word changes.
|
|
830
|
+
|
|
831
|
+
**The On-Fill Rule.** Status, red and head fills never take an inherited text colour. Amber and green fills pair with `on-status`; red fills pair with `on-red`; head fills pair with `on-head`. Each pair is a token so plum can flip the foreground without touching the component.
|
|
832
|
+
|
|
833
|
+
**The One Token Set Rule.** Plum is not a theme; it is the second value of the same custom properties, written as `light-dark(paper, plum)` in `tokens.css`. The page's `color-scheme` picks the value: `light dark` by default (the OS decides), `light` under `data-scheme="light"`, `dark` under `data-scheme="dark"`. No component carries a plum-only style; the only selector-switched value is the dither image, because an image cannot be a `light-dark()` pair.
|
|
834
|
+
|
|
835
|
+
**The Ground Only Dither Rule.** The 4px checker dither is the ground's texture and the grain of a dialog's veil. It never appears inside a panel, on a button, or on any surface that carries text.
|
|
836
|
+
|
|
837
|
+
## Typography
|
|
838
|
+
|
|
839
|
+
**Display Font:** none; the wordmark is the chrome face at 18px (22px on a wall).
|
|
840
|
+
**Chrome Font:** Departure Mono, weight 400, shipped at `fonts/DepartureMono-Regular.woff2` (MIT, Helena Zhang & Tobias Fried; license beside it) and declared in `tokens.css` with `font-display: swap` (ui-monospace, Menlo, Monaco fallback)
|
|
841
|
+
**Body Font:** system-ui (with Helvetica Neue, Helvetica, Arial fallback)
|
|
842
|
+
**Label/Mono Font:** the system monospace stack (ui-monospace, Menlo, Monaco) for code
|
|
843
|
+
|
|
844
|
+
**Character:** One pixel monospace does every piece of chrome at one weight; it is the machine's voice, so it is always uppercase and tracked except where it prints a number, a date or a path. The user's own words are set in whatever sans the system has, sentence case, so authored content reads as content and not as interface. Code is the plain terminal monospace, never the pixel face, so a document body cannot be confused with a control.
|
|
845
|
+
|
|
846
|
+
The working body is 13px on 1.45; the reading body is 15px on 1.65. There is no rem scale; sizes are fixed pixels on the `--type-*` and `--read-*` tokens because the face is a pixel font and the grid is pixels.
|
|
847
|
+
|
|
848
|
+
### Hierarchy
|
|
849
|
+
|
|
850
|
+
Working scale:
|
|
851
|
+
|
|
852
|
+
- **Wordmark** (400, 18px `--type-wordmark`, -0.02em): the product name in a top bar, followed by an 8×15px block cursor that blinks four times. **Wordmark wall** (22px `--type-wordmark-wall`) beside the glyph box on a wall.
|
|
853
|
+
- **Title bar** (400, 13px `--type-body`, uppercase, 0.08em `--tracking-title`; 0.06em `--tracking-head` in cards): the inverted heading of every panel and card. Its meta sits at 11px at 75% opacity, right-aligned.
|
|
854
|
+
- **Name** (400, 14px `--type-name`, uppercase, 0.04em): a group's name in an outline, a meter's name.
|
|
855
|
+
- **Control** (400, 12px `--type-control`, uppercase, 0.04em): buttons, nav segments, menu items, palette rows, text buttons, a meter's percentage; the empty-state and drop-zone headline at 0.08em (`--tracking-title`).
|
|
856
|
+
- **Figure** (400, 30px `--type-figure` / 1.1, tabular figures): a stat's value, its unit beside it at 12px uppercase muted; 22px (`--type-figure-narrow`) at 900px and under.
|
|
857
|
+
- **State** (400, 11px `--type-state`): tabs, small buttons, the filter strip, selects, choice names, chips and tips (0.05em `--tracking-state`), crumbs, ages, due days, history versions, keymap rows, the flash word (0.08em `--tracking-title`), menu key hints (no case change), a stat's change and the select-all count (0.05em `--tracking-state`), a chart's readings, a player's time.
|
|
858
|
+
- **Label** (400, 10px `--type-label`, uppercase, 0.1em, Muted Ink): field labels, table heads, a stacked table's cell labels, property terms, palette group headings, keymap section heads, a stat's label, a chart's scale, labels and key.
|
|
859
|
+
- **Meta** (400, 10px, Muted Ink, no case change): tags, authors, bylines, the nav count, the palette scope, a meter's detail line, diff line numbers.
|
|
860
|
+
- **Page title** (600, 22px `--type-page` / 1.2, -0.01em, sans, balanced, breaking anywhere): a working screen's name in a PageHead, its count beside it in the 11px pixel face, in ink (muted ink is too faint on the dithered ground); 18px (`--type-page-narrow`) under 600px. Nothing sits above it.
|
|
861
|
+
- **Heading** (600, 16px `--type-title`, sans): an editable title in a card.
|
|
862
|
+
- **Body** (400, 13px / 1.45, sans): item titles in rows and cards, table cells, dialog sentences, field values; the textarea at 13px / 1.5.
|
|
863
|
+
- **Note** (400, 12px / 1.45, sans, Muted Ink, max 46ch): field help, choice notes, menu notes, notice bodies, empty-state copy; in ink, a wrapped history's what.
|
|
864
|
+
- **Code** (400, 12px / 1.6, system mono): inline code on head tint, the code surface, the secret, the diff, file names in the tree, `kbd`, mono fields. A key printed as a hint is chrome, in the pixel face: 10px with no case change or tracking after a button's words.
|
|
865
|
+
|
|
866
|
+
Reading scale (authored pages only):
|
|
867
|
+
|
|
868
|
+
- **Reading title** (650, 30px `--read-title` / 1.15, -0.01em, balanced): the Sheet's h1; 25px under 600px.
|
|
869
|
+
- **Reading h2** (650, 20px `--read-h2` / 1.3, -0.005em, balanced): section headings in Prose.
|
|
870
|
+
- **Reading h3** (650, 15px `--read-h3` / 1.4): sub-sections; the body's size, carried by weight.
|
|
871
|
+
- **Reading h4** (Departure Mono 12px `--type-control` / 1.4, uppercase, 0.1em `--tracking-label`), **Reading h5** (650, 13px `--type-body` / 1.45) and **Reading h6** (Departure Mono 10px `--type-label` / 1.4, uppercase, 0.1em, Muted Ink): the levels under h3. The reading scale has no smaller step that still reads as a heading, so each level changes voice instead of size alone, and no two neighbours look alike; each stands as far below the text above it as an h3 (1.6 × the reading body). An `h1` inside Prose takes the Reading h2.
|
|
872
|
+
- **Reading body** (400, 15px `--read-body` / 1.65): prose paragraphs and lists, at most 68ch (`--read-measure`); `strong` at 650.
|
|
873
|
+
- **Reading aside** (400, 14px / 1.55): notices and tables inside Prose.
|
|
874
|
+
|
|
875
|
+
### Named Rules
|
|
876
|
+
|
|
877
|
+
**The Register Rule.** Chrome is the pixel mono, uppercase and tracked. Authored content is the system sans in sentence case. Code is the plain system mono. A string belongs to exactly one register; a control never borrows the sans and a title never borrows the pixel face.
|
|
878
|
+
|
|
879
|
+
**The One Weight Rule.** Departure Mono ships only at 400 and the system never asks it for bold. Emphasis in chrome is inversion (ink fill, paper text), never weight. The sans goes above 400 only for headings: 600 on the working scale, 650 on the reading scale.
|
|
880
|
+
|
|
881
|
+
**The State Word Rule.** A word that reports state is never below 11px: chips, the connection word, stale and reset lines, the flash word. 10px is for column heads, field labels, property names and other labels that name rather than report.
|
|
882
|
+
|
|
883
|
+
**The Pixel Size Rule.** Type sizes are whole pixels on a fixed ramp (30, 25, 22, 20, 18, 16, 15, 14, 13, 12, 11, 10). Nothing scales fluidly; a phone steps to another size on the ramp where it must (nav 12 to 11, page title 22 to 18, reading title 30 to 25, a stat's figure 30 to 22 at 900px) and otherwise tightens padding, not type.
|
|
884
|
+
|
|
885
|
+
**The Reading Measure Rule.** Only an authored page uses the reading scale. Its text holds to 68ch; code blocks and tables may use the full sheet. Chrome on the same screen stays on the working scale.
|
|
886
|
+
|
|
887
|
+
## Layout
|
|
888
|
+
|
|
889
|
+
The Deck is one centred column, `max-width: 1160px` (`--deck`), padded 24px top, 20px sides and 80px bottom, a vertical grid with 24px (`--gap`) between panels; on phones (≤600px) it pads 20px 12px 64px. Every grid child is `min-width: 0` so a wide table or code line scrolls inside its panel instead of widening the deck.
|
|
890
|
+
|
|
891
|
+
**Space between components:** three steps and nothing else, given by Stack (a column whose children take its width; `m` by default), Group (one line centred on its cross axis that never wraps; `s`) and Wrap (a line of buttons or chips that wraps, the same gap across and down; `s`). `--space-s` (8px) is for things in one line, `--space-m` (14px) for the parts of one body (a panel's, a card's and a window's body grid use it too), `--space-l` (`--gap`, 24px) for whole surfaces. They draw nothing: no paper, border or shadow, and their children keep `min-width: 0` so a wide table scrolls inside its own box.
|
|
892
|
+
|
|
893
|
+
**Top bar:** sticky (z-index 40), Paper White, closed below by a 2px ink rule, padded 8px 20px, wrapping with a 10px × 16px gap; the right-hand tools wrap as one group. Anchors land 16px clear of it at whatever height it wraps to: the page's first TopBar keeps `--topbar-h` on the page at its measured height (a bar shown inside the page leaves it alone), and `html`'s `scroll-padding-top` adds 16px to it (to one line's 48px until the bar is measured). Under 600px the wordmark and tools share the first line, padded 8px 12px, and the nav drops to a full-width strip under them with equal segments; where wordmark and tools do not fit side by side the tools wrap under the wordmark, which never clips. Text buttons and the online word hide, and the offline word stays.
|
|
894
|
+
|
|
895
|
+
**Rows under 600px:** hover tools hide; titles wrap to two lines (three in an outline, with chips on the line below) instead of clipping beside the chips. Inline choices wrap onto the next line with a 2px × 24px gap rather than running off the panel.
|
|
896
|
+
|
|
897
|
+
**Board:** a grid of one column per board column (`--cols`) with a 24px gutter; at 901px and above the columns stretch to equal height, cap at `calc(100vh - 150px)` and scroll their own cards; under 900px they stack. Cards sit on a 12px grid with 12px padding.
|
|
898
|
+
|
|
899
|
+
**Meters:** a meter row is a grid of `minmax(180px, 240px) minmax(0, 1fr)` (label and detail beside cells, reading and foot), padded 14px 16px on 1px rules, stacking under 700px.
|
|
900
|
+
|
|
901
|
+
**Walls:** a full-height centred grid holding one narrow card on the dithered ground. A page with no top bar keeps what a bar would hold (a palette switch, a way back) on a wall's corner slip (`Wall.cornerView`): a paper slip 12px in from the top-left corner, in the page rather than fixed (it scrolls away and never covers a tall card), with the wall padded 72px clear of it; on a phone it sits in the flow above the card. A list of Rows in a card's body meets the card's edges between two rules.
|
|
902
|
+
|
|
903
|
+
**Stats:** a grid of `--cols` equal cells (four by default), two across on a phone.
|
|
904
|
+
|
|
905
|
+
**Charts:** a line chart's plot is `--chart-plot` (160px) tall and stretches to its column; bar columns run 6–40px wide (up to 48px while every reading prints) and scroll sideways in their own box; on a phone a line chart prints every other label and a bar row's label clips at 12ch.
|
|
906
|
+
|
|
907
|
+
**Toasts:** the stack sits 16px in from the bottom right, `min(360px, 100vw - 32px)` wide, newest lowest, 10px apart, standing 12px clear of a showing Banner (anchored to it); on a phone it spans the bottom. While the system's Dialog is open the stack moves inside it, so a toast raised from a card prints above the card and can be used; a raw `<dialog>` an adopter opens with `showModal()` is not followed.
|
|
908
|
+
|
|
909
|
+
**Rail:** a column of panels beside the deck (`--rail`, 272px, then the deck centred in what is left), fixed from the top bar's lower edge (hung from it by anchor positioning, like the stage) to the window's bottom, scrolling itself, padded `24px 10px 24px 20px` so its panels' shadows print. At 900px and under it leaves the page: the deck starts with a small button (the `sidebar` glyph and the rail's name) that opens the same element as a drawer.
|
|
910
|
+
|
|
911
|
+
Frames with no surface of their own (Deck, Wall, Stage) are described here rather than among the components.
|
|
912
|
+
|
|
913
|
+
**Stage:** the ground itself for a canvas: fixed from the top bar's lower edge (it hangs from the bar by anchor positioning, so a phone's two-line bar fits) to the window's edges. It does not scroll; what is on it pans. Its floating tools sit 16px in from the left edge, centred (along the bottom, centred, on a phone); its bar sits 16px down from the top, centred, wrapping when narrow.
|
|
914
|
+
|
|
915
|
+
Dialogs are native `<dialog>` elements that fill the viewport as a veil; the card centres in a 24px pad (or sits at 18vh for the command palette) at `min(680px, 100%)`, or `min(440px, 100%)` when narrow, capped at `calc(100vh - 48px)` with its body scrolling between a fixed title bar and a fixed action row. The Banner is fixed 16px above the bottom edge, centred, at most `min(680px, 100vw - 32px)`.
|
|
916
|
+
|
|
917
|
+
Breakpoints: 600px (phone; the page title steps to 18px, stats two across, the wall's corner joins the flow, toasts span the bottom), 700px (meter rows stack), 900px (board columns stack, above it they stretch; a stat's figure steps to 22px).
|
|
918
|
+
|
|
919
|
+
### Named Rules
|
|
920
|
+
|
|
921
|
+
**The Panel Scroll Rule.** The page scrolls; a panel does not, except a board column above 900px, a card body inside a dialog, the rail, and a window's body (a window is sized by the canvas, not by its content). A stage never scrolls. A wide table, a long code line or a run of bar columns scrolls horizontally inside its own panel and never widens the deck.
|
|
922
|
+
|
|
923
|
+
**The 24px Target Rule.** Every target is at least 24px square. A control drawn smaller (a tab, a glyph-switch option, a filter chip, a row toggle, a clickable mark or tag, a text button, a crumb, an error-summary link) keeps its look and takes the pointer over an invisible 24px square centred on it; an icon button never gives up its 24px to a full line (a tag beside it clips first), and links in a list stand 24px apart so no two squares overlap.
|
|
924
|
+
|
|
925
|
+
## Elevation & Depth
|
|
926
|
+
|
|
927
|
+
Depth is printed, not lit. Every raised surface casts a solid rectangle of Shadow, offset down and right, with zero blur and zero spread; the offset is the elevation. Surfaces at rest inside a panel (rows, table cells, fields, prose tables, the diff) have no shadow and are separated by 1px rules or a head tint on hover. Pressing a button moves it 2px down-right into a 1px shadow, so the print appears to touch the paper. Disabled, busy and done buttons lose their shadow entirely. A button in an ink title bar (a panel's actions) prints in paper and flat, because a shadow cannot print on ink, and nudges 1px when pressed, as a toolbar tool does. Figures sit at rest wherever they are placed: an image, video or audio frame is a 2px ink frame with no shadow. Dialogs sit on a 40% ink veil with a 50% ink checker grain.
|
|
928
|
+
|
|
929
|
+
### Shadow Vocabulary
|
|
930
|
+
|
|
931
|
+
- **Card** (`box-shadow: var(--shadow-card)`, 8px): dialog cards and menus; the highest print.
|
|
932
|
+
- **Panel** (`box-shadow: var(--shadow-panel)`, 5px): panels, sheets and the open select picker.
|
|
933
|
+
- **Button** (`box-shadow: var(--shadow-button)`, 3px): buttons, the filter strip, board cards, the Banner, toasts, the wall's corner slip.
|
|
934
|
+
- **Small button** (`box-shadow: var(--shadow-button-small)`, 2px): small buttons.
|
|
935
|
+
- **Pressed** (`box-shadow: var(--shadow-pressed)`, 1px, with `translate(2px, 2px)`): the active state of any button.
|
|
936
|
+
- **Danger** (`box-shadow: var(--shadow-danger)`, 3px, red-tinted on paper, plain on plum): the danger button.
|
|
937
|
+
- **Default ring** (`box-shadow: 0 0 0 2px var(--paper), 0 0 0 4px var(--ink)`, 4px margin): the default action in a card's action row; replaces the offset shadow and falls back to the plain shadow when another control in the row holds the keyboard.
|
|
938
|
+
|
|
939
|
+
### Named Rules
|
|
940
|
+
|
|
941
|
+
**The Print Shadow Rule.** A shadow is a solid rectangle in Shadow with zero blur, offset equally down and right. Its size is one of 8, 5, 3, 2 or 1px, chosen by the surface's class, never by state or hover. No soft, coloured or ambient shadow exists in this world.
|
|
942
|
+
|
|
943
|
+
**The Press Rule.** A button that can be pressed travels 2px into a 1px shadow while active. A held button (busy, done, or given `aria-disabled` by its caller; it renders `aria-disabled="true"`) stays reachable from the keyboard but ignores clicks, does not travel and has no shadow.
|
|
944
|
+
|
|
945
|
+
## Shapes
|
|
946
|
+
|
|
947
|
+
Rectilinear. Corners are square on every element (`border-radius: 0` is set explicitly on inputs, selects and checkboxes to defeat native rounding). Anything that contains something (panel, card, sheet, menu, button, chip, filter strip, notice, boxed field, code surface, secret, diff, cells, file tree, drop zone, select, checkbox, radio, glyph box) is closed by a 2px (`--line`) border in Printer Ink; anything that divides content inside a container (table rows, menu items, property rows, meter rows, the panel foot, outline and tree guides, the diff gutter) is a 1px (`--hair`) rule in Rule Grey. A boxless field is a 1px dotted Muted Ink baseline that becomes a 2px solid ink baseline when it holds the keyboard, and a 2px red one when invalid; the boxed field is a 2px ink box that gains the keyboard ring outside it.
|
|
948
|
+
|
|
949
|
+
A dashed 2px ink line means "a place things can land": the receiving board column (outline, offset 4px) and the drop zone (border, solid while a file hovers).
|
|
950
|
+
|
|
951
|
+
Toggles are 14px ink-bordered squares holding a 6px cell that is painted ink when checked; a radio holds the same cell as an 8px diamond so a single choice reads differently at a glance. The cell exists unpainted in the unchecked state so a toggle never reflows; a mixed box (some of a list) paints a 2px bar across it. A failed radio or select turns its box red; the mark inside stays ink. The select's caret is two 4px CSS gradient triangles; the textarea's resize grip is a 45° ink gradient. The scrollbar is a 10px Ground Grey track with an ink thumb inset by 2px. The wordmark's cursor is an 8×15px ink block; a busy button's cursor and the loading line's are 6×11px blocks in `currentColor`; a busy chip's and the connection word's are 5×9px.
|
|
952
|
+
|
|
953
|
+
Cells are the system's unit of quantity: a meter is twenty 10×14px cells (8px wide on phones) in a 2px ink box with 2px gaps, lit in ink up to the reading; a short reading (a strength, a step count) is four 24×8px cells; the contents list marks the reading position with one 6px ink cell. A bar chart is the same cells inside one 2px ink frame: a column of 10px-tall cells per point, or a row of the meter's twenty per category; a player's seek bar is a row of cells lit up to the place. A reading over time is a stepped line: a 2px ink line level to halfway between points and then straight up or down, each point a 6px cell (a second series muted, 4px), on 1px Rule Grey axes. No bar, ring or gauge replaces a row of cells, and no line is a diagonal, a curve or a filled area.
|
|
954
|
+
|
|
955
|
+
Icons are pixel glyphs: filled `<rect>` cells on a 12×12 `viewBox`, `fill="currentColor"`, `shape-rendering: crispedges`, so the whole set shares one weight and one grammar and inherits its colour from the text around it.
|
|
956
|
+
|
|
957
|
+
### Named Rules
|
|
958
|
+
|
|
959
|
+
**The Two Lines Rule.** 2px ink closes a container; 1px rule divides inside it. There is no 1px ink border and no 2px rule.
|
|
960
|
+
|
|
961
|
+
**The Pixel Glyph Rule.** Every icon is one of the glyphs in `src/glyph/glyphs.ts`, drawn by the Glyph component. A new glyph is drawn in that file as it prints, twelve rows of twelve cells (`#` inked, `.` blank), in the family it belongs to, with a one-line meaning above it. It keeps the set's grammar: a one-cell margin (ink in rows and columns 1 to 10), 2px strokes for marks and arrows, 1px lines for outlines and line art, diagonals stepped in whole cells (2×2 blocks for bold ones), and no anti-aliasing; an off state is the object's outline with a 1px stroke from the top-left to the bottom-right laid over it. Before adding one, look for an existing glyph with the meaning. No stroke icons, no icon fonts, no emoji, no glyph from another set, at any size.
|
|
962
|
+
|
|
963
|
+
## Components
|
|
964
|
+
|
|
965
|
+
Every component below is a namespace exported from the package root (`import { Button, ConfirmDialog } from "@gjermundgaraba-designs/pixel-paper"`) and a set of classes in `pixel-paper.css`. Each is one of two kinds. A **View** is a stateless render helper, `X.view(config, h)`: the caller owns any state and passes its own Messages in the config (`onClick`, `onToggle`, `onInput`). A **Submodel** keeps state of its own: `X.Model`, `X.Message`, `X.init(config)`, `X.update`, and a `view` built with `defineView` and embedded with `h.submodel`, with its per-render `viewInputs`; it reports what the parent must act on as an `X.OutMessage`, folded in with `Update.foldChild`, and exports the accessors something reads (`X.id`, `Dialog.isOpen`) and update functions (`X.open`, `X.close`) for the parent to call. Each entry says what the component is for, its kind where it is a Submodel, the markup it renders, which is the contract for use without Foldkit, and how it behaves. Its config or view inputs, its OutMessages, its accessors and its Commands are in its types and their doc comments (`src/<name>/<name>.ts`), not restated here. Every view also takes `attributes` for its root; "Building components" below holds the rules they all follow.
|
|
966
|
+
|
|
967
|
+
```ts
|
|
968
|
+
import { Button, ConfirmDialog } from "@gjermundgaraba-designs/pixel-paper";
|
|
969
|
+
|
|
970
|
+
// A View: the caller's Model decides, the caller's Message comes back.
|
|
971
|
+
Button.view(
|
|
972
|
+
{ label: "save", variant: "Primary", isBusy: model.isSaving, onClick: Message.ClickedSave() },
|
|
973
|
+
h,
|
|
974
|
+
);
|
|
975
|
+
|
|
976
|
+
// A Submodel: its Model lives in the caller's, its OutMessages come back through update.
|
|
977
|
+
h.submodel({
|
|
978
|
+
slotId: ConfirmDialog.id(model.dropFile),
|
|
979
|
+
model: model.dropFile,
|
|
980
|
+
view: ConfirmDialog.view,
|
|
981
|
+
viewInputs: {
|
|
982
|
+
title: "Delete file",
|
|
983
|
+
question: "Delete notes.md?",
|
|
984
|
+
body: ["It goes from every device, and it cannot be undone."],
|
|
985
|
+
label: "delete",
|
|
986
|
+
isDanger: true,
|
|
987
|
+
},
|
|
988
|
+
toParentMessage: (message) => Message.GotDropFileMessage({ message }),
|
|
989
|
+
});
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
### Buttons
|
|
993
|
+
|
|
994
|
+
The printed button: a paper rectangle in a 2px ink frame that casts a 3px shadow and physically travels when pressed. **Button** → `button.btn` (`.primary`, `.danger`, `.small`, `.default-ring`); **LinkButton** (the same look, never busy) goes somewhere instead of doing something and renders `a.btn`, the same button.
|
|
995
|
+
|
|
996
|
+
- **Shape:** square, 2px ink border, `padding: 5px 14px`, control register on one line (a button's words never wrap), inline-flex with a 6px gap for a leading glyph. Small: `3px 10px`, 11px, 2px shadow.
|
|
997
|
+
- **Default:** Paper White fill, Printer Ink text, button shadow. **Primary:** Printer Ink fill, Paper White text. **Danger:** paper fill, Urgent Red text and border, danger shadow; its keyboard ring turns red. Used only as the commit inside a ConfirmDialog; a destructive action at rest is a neutral button that opens a ConfirmDialog with `isDanger`.
|
|
998
|
+
- **Active:** `translate(2px, 2px)` into a 1px shadow. **Keyboard:** the global ring, 2px solid ink offset 2px.
|
|
999
|
+
- **Disabled / held:** Muted Ink text and border (a done button inverts instead, below), no shadow, no travel. One held rule covers `isBusy`, a done `done` and `isDisabled` (which means what it means in `@foldkit/ui`): the button renders `aria-disabled="true"`, stays focusable, has no click handler, and renders `type="button"`, so a held submit button submits nothing by itself. Enter in a field can still submit its form, so the form's update ignores a submit while its work is saving. A busy button says so, `aria-busy="true"`, and its blinking `span.cursor` prints in the right padding one cell after the label (5×9px on a small button), so going busy moves nothing and the button keeps its label and its width. A LinkButton is never busy. Give the button an id (`attributes`) for `Announcer.flash`. A view takes `attributes`, or a named part's `<part>Attributes`, only where a caller passes them (Building components, rule 4); they never carry a class.
|
|
1000
|
+
- **Done** (`done`, with a `glyph`): the work went (a form sent). `button.btn.done[aria-disabled="true"]` prints in Printer Ink with paper text, flat and unavailable, the check in its glyph's place: success is not a status colour and never green. The label and the done label share one cell (`span.btn-words`), the hidden word keeping its room, so the button is as wide as the longer word and never changes width. Flash and announce it where it is set.
|
|
1001
|
+
- **Default ring (`isDefault`):** the action Enter takes in a card; one ring at a time across the action row. A held default that does not hold focus (a confirm's cancel while its delete runs) prints as any held button, with no ring and no shadow; its 4px margin stays, so nothing moves.
|
|
1002
|
+
- **Toggle:** a button that stays pressed (a tool in force, a filter) takes `aria-pressed` from `Button.pressedAttribute(isPressed, h)` in its `attributes`, the one source of it for every pressable in the system.
|
|
1003
|
+
- **Key hint (`keyHint`):** the key that does the same, as its Foldkit key binding names it (`"N"`, `"U"`, `"Mod+K"`), printed after the words as `kbd.key-hint[aria-hidden="true"]`: 8px clear of them, the 10px pixel face with no case change or tracking, in the button's own colour at 0.65. It is for the eye; the button's name stays its words. A done button drops it.
|
|
1004
|
+
- **TextButton** → `button.textbtn`: the control register with no box and a dotted underline on hover ("sign out"). **IconButton** → `button.iconbtn[aria-label][data-tip]` (`.quiet`): a 24px square glyph button whose `label`, required, is both its accessible name and its tip; quiet is Muted Ink until hovered. A Secret or a PropsList row that copies carries a CopyButton (see Code surface).
|
|
1005
|
+
|
|
1006
|
+
### Navigation
|
|
1007
|
+
|
|
1008
|
+
- **NavStrip** → `nav.nav > a[aria-current="page"]`: one ink-bordered strip of segments, 2px ink rules between them, control register, `padding: 5px 14px`; the current segment inverts. A count is `span.navnum`, a head-tinted badge 8px after the label that stays readable on both plain and inverted segments; a `span.sr-only` space before it keeps the label and the count two words in the link's name. The strip is as wide as its segments; in a phone top bar it spans the width with equal segments.
|
|
1009
|
+
- **Segmented** → `div.nav[role="group"] > button[aria-pressed]`: a choice of one printed as the same strip. When every option carries a `glyph` it prints as `div.switch > button[aria-pressed][aria-label]`, the strip holding 26×22px glyph cells (a layout, a density), each option's label its name and its tip. **Scheme.SchemeSwitch** is a Submodel printing a Segmented of system, paper and plum that remembers the choice (see Palette below).
|
|
1010
|
+
- **Tabs** (Submodel over `@foldkit/ui`'s Tabs; the caller keeps the tab on show) → `div.tabbed > div.nav.tabs[role="tablist"] > button[role="tab"][aria-selected]` plus `div.tab-panel[role="tabpanel"]`: the strip at 11px with `4px 12px` segments, a count spaced as NavStrip's; arrows, Home and End move and select. At the top of a flush panel the strip sits inside the panel's padding.
|
|
1011
|
+
- **Crumbs** → `nav.crumbs > ol > li`: the path in the 11px pixel face, muted slashes between, the last crumb ink with `aria-current="page"`, the others muted with a dotted underline on hover.
|
|
1012
|
+
- **Places** (a View over `@foldkit/ui`'s Button) → `nav.places[aria-label] > ul > li`, each place (keyed by an `id` that is stable and unique among the places, so two places that share a name stay apart) → `li > button` holding `span.place-name` and `span.place-count`, then `span.place-tools`: a column of places to go (areas, collections, saved filters), flush in a rail panel. Each line is at least 34px (`--row`), `8px 12px`, over a 1px rule; the name in the sans, sentence case, wrapping anywhere rather than clipping; the count in the 11px pixel face, muted, at the right; a quiet IconButton after it acts on the place itself (remove a saved filter). Hover tints head. The place you are in carries `aria-current="page"` and its whole `li` prints inverted, the count and a quiet tool in paper at 0.75, never tinted by hover; the ring sits 4px inside the line, in paper on the current one.
|
|
1013
|
+
- **PageHead** → `header.page-head > div.page-name > h1.page-title + span.meta`, then `div.page-acts`: the heading of a working screen, one per screen, above its panels (which title in h2). The name in the page-title register with a count or state beside it in the 11px pixel face; the screen's own Buttons at the right, 8px apart, wrapping under the name when the line runs out (`14px × 24px` gaps), aligned to the name's foot. No surface of its own: it stands on the ground. There is no label above the heading; the name says where you are.
|
|
1014
|
+
- **TopBar** (a Mount measures it, reporting through `toParentMessage`) → `header.topbar` (brand, nav, `div.topbar-tools`), **Wordmark** → `.wordmark` with `span.cursor`, **LinkState** → `span.link-state[role="status"]`: online prints plain; `.connecting` (the first reach) prints muted with the working cursor; `.reconnecting` (the link dropped and is coming back) fills amber with `on-status` and the cursor; `.offline` (given up) fills red with `on-red`. Reconnecting and offline stay in a phone's top bar; the other two hide there.
|
|
1015
|
+
- **StatusBar** (a Mount measures it, reporting through `toParentMessage`) → `footer.statusbar > div.statusbar-state + p.statusbar-keys`: the quiet line along the bottom of an interface, the page's footer (one per page, after the rail and the deck, outside `main`). Fixed to the bottom edge on paper over a 1px rule, in the pixel face at the state size in Muted Ink: the status at the left (a LinkState, a sync word), the keys at the right, each a `kbd.key-hint` in ink (printed as the help prints it, ⌘K or Ctrl+K by platform) and its word, plain text and not pressable. Narrow, the keys wrap under the status; nothing hides. It keeps `--statusbar-h` on the page at its height (25px, one line, until it is measured), like the top bar's `--topbar-h`: the page pads its end by it, a scrolled-to anchor stays clear of it, and the rail, the stage, a Banner and the toast stack all stop above it.
|
|
1016
|
+
|
|
1017
|
+
### Chips
|
|
1018
|
+
|
|
1019
|
+
- **Chip** → `span.chip` (`.go`, `.warn`, `.blocked`, `.done`; `isBusy` sets `aria-busy="true"` and appends `span.cursor`, the 5×9px working cursor, on a chip that is polling, building or starting): 11px uppercase 0.05em (`--tracking-state`), `padding: 1px 6px`, 2px ink border on paper. `go` fills Active Green and `warn` fills Marker Amber, both with `on-status` text; `blocked` keeps paper with red text and border (also "limit reached"); `done` fills Head Tint with On Head text and a muted border.
|
|
1020
|
+
- **FilterChip** → `button.fchip[aria-pressed]` (without `onToggle`, a pinned label, `span.fchip.pinned`; the clear chip is `.reset`): 22px minimum, the name half (`span.fchip-name`) muted; pressed inverts, both halves in paper (a faded name misses AA on plum); hover tints head. `full` writes out a chip whose words alone do not say it ("Reset the filters" for "reset"), as a Tag's does: the button's `aria-label` (on a pinned label, a `span.sr-only` beside the hidden words) and its tip. Chips mapped from a list that changes take a `key`. **QueryChip** → `label.fchip.query` holding a bare sans search input (8–32ch); tinted `.live` while it holds a query. The input lies above the chip's hit square, so a press in it places the caret or selects text; the rest of the chip focuses it. **FilterStrip** → `div.strip`: the paper bar holding them, 3px shadow, `padding: 8px 12px`; a `span.spacer` pushes what follows right.
|
|
1021
|
+
- **Tag** → `span.tag` (or `button.tag` with `onClick`): a 10px muted label in a 1px Rule Grey box, `0 5px`, clipped at 18ch; a count, a group, a scope. `full` writes out a tag that is abbreviated or clipped ("3 in Operations" for "3"): it is the tag's name for screen readers (the short text is hidden from them) and its tip. Tags mapped from a list that changes take a `key`.
|
|
1022
|
+
- **Registers without a component:** `.label`, `.meta`, `.age` (`.stale` red), `.due` (`.late` red), `.tnum`, `.note-line`, `.done-title`, `.group-name`.
|
|
1023
|
+
|
|
1024
|
+
### Marks
|
|
1025
|
+
|
|
1026
|
+
**Mark** → `span.mark` or, with `onToggle`, `button.mark[aria-pressed]` holding the `mark` glyph: an amber square with 2px padding and `on-status` ink; `isPlain` drops the fill and prints Muted Ink. Its label is its accessible name and its tip. The reading mark (`span.mark.reading`, which Meter and BarChart print) holds a percentage in the 11px pixel face beside a meter at 80% or more.
|
|
1027
|
+
|
|
1028
|
+
### Monograms
|
|
1029
|
+
|
|
1030
|
+
**Monogram** → `span.monogram[role="img"][aria-label]` (`.large`, `.current`): one or two letters (the label's initials, or `letters`) in the 11px pixel face, uppercase, centred in a 22px square closed by a 2px ink border on paper; large is 34px with 14px letters, for a card of readings or a wall. `isCurrent` inverts it: you, among others. `isDecorative` → `span.monogram[aria-hidden="true"]`, for a monogram beside the same name in words, which screen readers then hear once. The system draws no logos: a provider, a source, a harness or a person is a monogram beside its name, never a brand mark and never a colour.
|
|
1031
|
+
|
|
1032
|
+
**Identity** → `div.identity (.large) > span.monogram[aria-hidden="true"] + span.identity-text > strong + span.meta`: that pairing as one piece, its name read once (the monogram is decorative beside it). The name in the sans at 600 (16px when large, beside the large monogram, as a card's subject), wrapping rather than clipping; under it a meta line of whatever says which one (an id in code, a kind in a Tag), 8px apart and wrapping. A consent card names the client asking with it; a list of sources, harnesses or people names each.
|
|
1033
|
+
|
|
1034
|
+
### Time
|
|
1035
|
+
|
|
1036
|
+
**Time** → `time[datetime]` by `format`: `"Age"` ("19d") in the age register (`.age`, `.stale` red past `staleAfter`); `"Ago"` ("4m ago", "just now") in the same register, for a sentence; `"Until"` ("in 1h 41m", "in 3d 4h") in the due register (`.due`), which prints `passed` once the moment has gone ("reset due", "expired"), red when `isLate`; `"Date"` ("12 Mar 2026") and `"DateTime"` ("12 Mar 2026, 14:05") in the text around them, in the reader's zone. Spans are the fewest words that say them: seconds under a minute, minutes under an hour, hours under two days, then days; a countdown adds the next unit down. Everything is English (`en-GB` dates, 24-hour times). The clock is the application's: `Time.clockEntry` is a Subscription entry that ticks once a second while `isActive` says a relative Time is on show, and the application keeps the time in its Model and passes it to every Time as `maybeNow`. While it is `None` (a server render, before the first tick), where the reader's clock and zone are unknown, every Time prints its moment in UTC ("12 Mar 2026, 14:05 UTC"), so server and browser agree. `Format.formatDate` holds the same words for a date anywhere else.
|
|
1037
|
+
|
|
1038
|
+
### Panels
|
|
1039
|
+
|
|
1040
|
+
- **Panel** → `section.panel[aria-labelledby] > header.panel-tb > h2 + span.meta`, then `div.panel-body` (or the body directly when `isFlush`), then `div.panel-foot`. The section is keyed by its `id`, so a panel among siblings that come and go keeps its own element, and a panel under another id is another element: one that replaces another in the same place is built anew (a live region inside it is inserted, not kept), so keep one panel under one id across states that should share it. Square, 2px ink border, Paper White, panel shadow. The title bar is inverted Departure Mono 13px uppercase 0.08em (`--tracking-title`), `padding: 6px 12px`; its meta is 11px at 75% opacity. A control in a title bar (a panel's, a window's or a card's) rings in paper, 2px inside its own edge, because the ink ring would vanish on the ink bar. The body pads 16px with a 14px grid gap; the foot pads `10px 12px` over a 1px rule, with a `.push` slot that goes right. `actions` puts small Buttons and IconButtons in `span.panel-acts` (6px apart) at the title bar's right end, after the meta; the bar trims to `3px 3px 3px 12px` at 31px or more. On the ink bar a button is a paper outline (a primary one fills paper), flat, nudged 1px when pressed, its ring 4px inside in its own colour; disabled, it prints at 60%.
|
|
1041
|
+
- **Deck** → `main.deck#main`: the column of panels.
|
|
1042
|
+
- **EmptyState** → `div.empty > strong.empty-title + p` (an `h1` to `h6.empty-title` with `headingLevel`, where the empty state is a page's or a section's main content, looking the same): centred, `26px 16px`, a 12px uppercase 0.08em (`--tracking-title`) headline, a muted 46ch sentence, an optional action 6px below.
|
|
1043
|
+
- **Wall** → `main.wall`, `Wall.markView` → `div.wall-mark` (a 30px ink-bordered glyph box beside the 22px wordmark). `Wall.cornerView` → `aside.wall-corner[aria-label]`, rendered just before the Wall: a paper slip in a 2px ink box on the 3px shadow, `6px 8px`, holding what a top bar would (see Layout).
|
|
1044
|
+
- **Banner** → `div.banner > span.glyph (.error, .ok, .quiet) + span`: a fixed paper slip on the 3px shadow with a 26px glyph box in the Notice's tones and a sentence that says what happens to the reader's work. It holds a state the whole interface is in, for as long as it lasts, one at a time. It is not a live region (it arrives already holding its sentence, which would go unheard): the application says the state once, when it begins, with `Announcer.announce` from its own Command. `"Error"` (the default; red, bang): a state that stops work (offline). `"Warn"` (amber): a state that is passing (reconnecting). `"Ok"` (ink, check): the state just ended (back online), shown briefly and then taken away. `"Quiet"` (head, info): news. With the connection word it is one pair: LinkState `"Reconnecting"` beside a warn Banner, `"Offline"` beside an error Banner with a retry, then `"Online"` beside an ok Banner for a moment; `"Connecting"` takes none.
|
|
1045
|
+
- **Rail** (Submodel) → `div.railed > main.deck#main + aside.rail[popover="auto"][aria-label]`: the rail's panels (a tree, places, filters) beside the deck's, after it in the markup so the page's main content and its h1 come first in reading order; the stylesheet fixes the rail at the left under the top bar. One element serves both layouts, so nothing in it renders twice or loses its state: at rest it stands there (its `popover` attribute is inert until opened), and at 900px and under it is hidden and `span.rail-open > button.btn.small[aria-expanded]` at the top of the deck opens it as a drawer: a slice of the dithered ground, `min(320px, 100vw - 40px)` wide from the left edge, closed by a 2px ink rule and the 8px card shadow, entering from the left in three steps over a veil. Its head (`div.rail-head`: the name in the label register and a close IconButton) shows only in the drawer. Opening focuses its first control; Escape, a click on the veil, the close glyph or focus leaving closes it, and all but the last return focus to the button. Widening the window past 900px closes it. The parent reads whether the drawer is open with `Rail.isOpen`, to hold its own page keys while the drawer covers the page. A file tree flush in a rail panel (or any panel) drops its own border.
|
|
1046
|
+
- **Stage** → `main.stage#main > div.stage-work > div.stage-canvas + div.stage-tools + div.stage-bar`: see Layout. `paused` (a Notice saying why: the link dropped, the page is read-only) veils the work when editing stops: `div.stage-paused[role="status"]` lays the dialog veil and its ink grain over everything and centres that Notice on the 3px button shadow, while the work beneath is `inert`. It lifts when `paused` is not given; the connection word says reconnecting meanwhile.
|
|
1047
|
+
- **Window** → `section.panel.window (.flush) > header.panel-tb + div.window-body + div.panel-foot`: a Panel for a stage, as tall and wide as whatever places it (and moved by it, if anything moves it). Its title bar is the panel's, trimmed to hold tools: 30px tall (`3px 3px 3px 10px`), centred, with an optional kind glyph, the title (clipped with an ellipsis), a status chip (its border turns paper so it reads on ink), tools and a close IconButton, both in the bar's paper. The body scrolls inside (`16px`, a 14px grid); flush, what fills it (a code surface, a file tree, a diff) drops its border and fills it. A foot is a panel foot; a notice there takes the whole line.
|
|
1048
|
+
- **Loading** → `div.loading[role="status"]`: where content is on its way, centred like an EmptyState at `26px 16px`, a 12px uppercase 0.08em (`--tracking-title`) muted line ("loading the items") ending in the 6×11px working cursor. Its status element stays in the page and only its words change: with `isLoading` false it is `div.loading-idle[role="status"]`, empty and out of the flow (`position: absolute`, never `display: none`, which would drop the live region), so it takes no room and no gap at the top of a panel's or a card's body, and a screen reader hears the line when it arrives. The content or an EmptyState takes its place on screen; the line never prints beside either.
|
|
1049
|
+
- **Disclosure** (a View over `@foldkit/ui`'s Disclosure; the caller keeps whether it is open) → `div.disclosure > button.disclosure-summary[aria-expanded] + div.disclosure-body`. The summary is a control-register line (`3px 0`) of a 12px `caret-right` glyph, `span.disclosure-label` (a dotted underline on hover) and an optional `span.meta` pushed right; open, the caret turns a quarter in one step (a right angle keeps the grid). The body hangs from the caret on a 1px Rule Grey guide, its content level with the summary's words (`margin-left: 5px`, `padding-left: 14px`), in a 14px grid. For what a reader opens on purpose (grants to edit, details, a danger zone), never to hide the one thing the screen is for.
|
|
1050
|
+
|
|
1051
|
+
### Outcomes
|
|
1052
|
+
|
|
1053
|
+
**Outcome** → `div.outcome > span.glyph (.error, .ok, .quiet) + div.outcome-body > strong + p`: a state said in full. The Notice's glyph box at 34px, in the same tones (`"Warn"`, amber with `on-status` and a bang, by default; `"Error"`, red with `on-red`, for refused or failed; `"Ok"`, ink with paper and a check, for done, because success is not a status; `"Quiet"`, head with `on-head` and info, when nothing is wrong) beside a 12px uppercase 0.06em (`--tracking-head`) title and a 52ch sans sentence, then `detail` 8px below (an error code in a PropsList, when it was last tried, the error Notice of a confirm whose work failed). The title wraps anywhere, so a long word never widens the card. It is the body of a ConfirmDialog and of every wall that stops the reader: first run done, session ended, service away, access allowed or denied, authorization refused.
|
|
1054
|
+
|
|
1055
|
+
### Cards (dialogs)
|
|
1056
|
+
|
|
1057
|
+
**Dialog** is a Submodel over `@foldkit/ui`'s Dialog: a `<dialog class="veil">` that fills the viewport, opened with `show()` in place rather than `showModal()` in the top layer. That is a deliberate exception to the baseline's "prefer the platform" for overlays: Foldkit's Dialog (through `Dom.showDialog`) makes everything outside the dialog `inert` and `aria-hidden`, traps focus, handles Escape on the topmost dialog and returns focus on close, so the platform's modal behaviour is reproduced, not dropped; what is lost is the top layer and its `::backdrop`. The veil is therefore an element of its own, `div.veil-backdrop` (fixed, Veil with the ink checker grain, entering in 3 steps over 0.12s), under `div.veil-pad` (`.top` for the palette), which lets a press through everywhere but on the card it holds; a press on the veil, Escape, or a control given the `closeButton` group closes it and reports `Closed`. Opening and closing are the parent's, through `Dialog.open` and `Dialog.close` in its update, and a step the parent asked for reports nothing; `Dialog.foldDialog` lifts a dialog the parent always holds (its update, `open` and `close`) into the parent at once, `Closed` reported only where the parent maps it. The content is the parent's `toView`, which receives the attribute groups to spread (`title`, `description`, `initialFocus`, `closeButton`, `actionBar`). Focus starts on the element given the `initialFocus` group, else the first control. The Card inside names the dialog through its title (the `title` group, `aria-labelledby`); a dialog without a heading takes `label`, printed for screen readers only. `isAlert` makes it an `alertdialog`, and `hasDescription` points `aria-describedby` at the element given the `description` group. Because the page outside is inert and hidden, anything that must be heard or reached while it is open renders inside it: the topmost open dialog hosts the page's live region and the toast stack, both outside the card, through its `toLiveRegionView` (see Building components).
|
|
1058
|
+
|
|
1059
|
+
**Card** → `div.card` (`.narrow`; a `form` when it has `onSubmit`, named by its title (`aria-label`), so a form landmark; no role of its own, the Dialog around it is the dialog): a paper panel with the card shadow, `min(680px, 100%)` (440px narrow), entering in 3 steps over 0.14s from `scale(0.97)`. An inverted title bar `header.card-tb` (`6px 8px 6px 12px`, 13px uppercase 0.06em (`--tracking-head`), a close IconButton when it has `closeAttributes`, held with `isCloseHeld` while work the reader may not walk away from runs: `aria-disabled`, in the keyboard, its `closeAttributes` and so its handler dropped, faded to 0.6 on the bar, as a held button in a panel's title bar), a scrolling `div.card-body` (16px, 14px gap; a word longer than the card, a path or an id, breaks rather than widening it), and `div.card-actions` (`12px 16px`) over a 2px ink rule: `lead` first, then `span.card-acts` holding the actions at the right, which drop below the lead together when the bar wraps. Inside a Dialog, where the page's keys wait, the bar moves focus between its actions: `h` or ← to the one before, `l` or → to the one after, in the order they print, stopping at the ends, never pressing one (Enter, Space and Tab stay the browser's). The keys are bound by a Mount on the bar (`BindActionKeys`), which reads the real key event: only a key whose target is one of that bar's own buttons or links, unmodified, outside an IME and not already handled, and only when there is an action that way, moves focus and has its default prevented. A field, the title bar's close, a menu's items and a nested dialog's bar keep their keys. A disabled action is passed over; an `aria-disabled` one (held: busy, done or disabled) is still a stop and still ignores presses. A Card with `onSubmit` is a form: Enter in a field submits it even while its submit button is held, so the parent's update ignores the submit while its work is saving. A ConfirmDialog with `isDanger` still opens on cancel.
|
|
1060
|
+
|
|
1061
|
+
- **ConfirmDialog** → a Submodel over Dialog (an `alertdialog` when `isDanger`, a `dialog` otherwise) holding a narrow Card whose body is an Outcome (warn, or error when `isDanger`), the Outcome's body describing the dialog. Its view inputs follow the system's words: `title` (the title bar), `question` (the Outcome's title), `body`, and `label` (the confirming button's word). Without `isDanger` the confirm is primary and default; with it the confirm is the red button and cancel is the default, where focus starts. `Confirmed` does not close it: work that takes a while stays in the dialog. `isBusy` holds the confirming button (its word, the working cursor, focus kept, a second press ignored) while cancel stays live; the parent calls `ConfirmDialog.close` when the work lands, or keeps it open while it is not done. `isDisabled` holds it the same way without the working cursor, for an action that is blocked rather than running (writes not accepted yet). `toDetailView` prints the application's own content under the sentence, inside the Outcome's body and so in the dialog's description: what the work said back (a refused delete's reason as an error Notice) or a notice with its own retry. Cancel, Escape and the veil report `Cancelled`; the parent decides whether that also stops work already running, and ignores the work's result once cancelled. Where the reader may not walk away from the work (its failure must land in the dialog), `isCloseHeld` holds the ways out while it runs: cancel prints held (`aria-disabled`, in the keyboard, no ring and no shadow while the busy commit keeps focus), and Escape and a press on the veil reach the view as `RequestedCloseWhileHeld`, which changes nothing, so the dialog stays open and reports no `Cancelled`; `close` still closes it. A form dialog holds its own ways out the same way: Card's `isCloseHeld` on the close glyph, `isDisabled` on its cancel or close Button, and its update leaving out a Dialog Message that asks to close (`Dialog.isCloseRequest`: those controls, Escape and the veil all send one) while it saves.
|
|
1062
|
+
- **CommandPalette** (Submodel over Dialog and `@foldkit/ui`'s Input with a listbox of its own, since a combobox would not let the cursor land on held rows or Escape reach the Dialog) → a card at 18vh holding `div.palette-q` (a `span.palette-scope` and a combobox input), `div.palette-detail` when the app gives `toDetailView`, `div.palette-list[role="listbox"]` of `div.palette-group` headings and `div.palette-row[role="option"][aria-selected]` rows (control register; `isAuthored` titles, `.authored`, set in sans with a glyph and wait for a query), a `div.sr-only[role="status"]`, and `div.palette-more`. Once a query is typed the status says how many rows it finds ("3 results.", "Nothing matches."); it is empty as the card opens and on a fresh page, so it is never mounted holding its words (rule 12). Labels and hints wrap within their row, including long names and destination paths. One cursor: the inverted row, moved by the keys only. ↑ ↓ move it and stop at the ends, each key one row from where the last left it however fast they come (the field's own handler, in the view, reports `PressedArrowKey` with the rows its render showed, and update steps from the Model's cursor); Home and End stay the field's, moving the caret; Enter does what a click on its row does, decided in update from what the render showed each row to be (held, opening a page, a choice on a page): a command closes the palette first so what it opens takes focus, and reports `SelectedCommand`. An arrow or an Enter pressed before the list has caught up with the keys before it (straight after a letter, or before the arrows already waiting have moved the cursor) cannot name a row yet, so it waits for the next render: the row that render shows the cursor on becomes the cursor, and a waiting Enter acts on it as on any row. So arrows typed straight after a letter step through the new query's rows, and an Enter acts on the keys before it, never on letters typed after it. While the palette is open, ↑ ↓ and Enter pressed before its field has focus (straight after the key that opened it) are its own too: `CommandPalette.subscriptions(keyPrefix)` takes them on the window before the page's bindings see them, and they wait as above. A key an IME is composing stays the IME's. The pointer shades the row under it (Header Grey, as a list's row) and a click runs that row; it never moves the cursor and never scrolls. The cursor comes into view on every key that moves it, when the view or the query changes, and when its row leaves the list (it falls back to the first): the app, which changes the list, returns `CommandPalette.ScrollToCursor` with the palette's id; a row that only reflows is not followed. The cursor holds to its row by id while the app updates the list; `CommandPalette.maybeCursorId` reads the row it shows for a list of commands, so the app can compare it before and after its change. A row with `isDisabled` prints muted (`[aria-disabled="true"]`), its `reason` in the hint, and the cursor still lands on it but Enter and a click do nothing. **Pages:** a command with a `page` (a status, a date, a destination) ends in a caret and opens that page in place instead of closing: `div.palette-q` holds a Back IconButton, the scope chip (what the commands act on, kept on every page), the page's title (`span.palette-title`, pixel caps), then the page's own field (placeholder `find`, else "type to narrow"), focused. A long scope wraps inside its chip and the field takes its own line when too little is left beside it, so nothing leaves the card at 390px. The choices are found in like the commands. The choice in force now (`current`) carries a `current` tag and cannot be picked again. The cursor starts on the page's `start` (the next status; the first choice a typed query finds), else on `current`, else on the first choice, read while the reader has not moved it, so an application that keeps `start` steady across a change made elsewhere moves the tag, not the cursor. Back, or Backspace in an empty field, returns to the commands with their query and cursor as they were, also when the palette opened straight on a page (`CommandPalette.openAt`). A pick (`SelectedChoice`) holds the page until the parent answers: the row prints the working cursor, the list is `aria-busy`, and moving, picking, typing and Back wait. With `CommandPalette.pickSucceeded` the palette closes; with `pickFailed` the page stays with its query, focus and the cursor on that choice, and the app says why in `toDetailView`, which shows on the commands and on every page alike. Escape closes the whole palette, even while a pick is out, and reports `Closed`; an answer arriving after that, or after the page was left, changes nothing. A page whose command leaves the list gives way to the commands. One level: choices do not open pages.
|
|
1063
|
+
- **Keymap** → `div.keymap > div > h3 + div.keymap-row > kbd + span`: keys in the 11px pixel face under 10px muted group heads, for a narrow Card.
|
|
1064
|
+
|
|
1065
|
+
### Keys
|
|
1066
|
+
|
|
1067
|
+
One keyboard for the page, Vim first, with the arrows beside the letters. The keys are the application's: it binds them with Foldkit's `Dom.streamFromKeyBindings` in a Subscription entry (`Subscription.persistentEntry` for a fixed table), each binding naming its keys as Foldkit does (`"J"`, `"?"`, `"Enter"`, `"ArrowDown"`, `"Mod+K"` with the platform's command key, a sequence as an array, `["G", "G"]`), saying whether it acts while the reader types (`whileTyping`: only `Mod+` keys do) and mapping the press to a Message. A key whose meaning depends on the Model sends a plain fact (`PressedNewItemKey`) and the update decides. Escape is not bound: it closes menus and dialogs and lets go of a waiting key. The system makes keys visible, from the same list: the binding, its line in the help and its hint beside a button are one thing, so a key never says one thing and does another.
|
|
1068
|
+
|
|
1069
|
+
- **KeyHelp** (Submodel; `KeyHelp.subscriptions` bind `?`) → a Dialog holding a narrow Card titled "Keys": the application's own words when it gives `toIntroView` (what the covered page would say that the reader still needs, a sync notice and its retry), a text Field ("Find a key", focused on open) that finds a key by its key or its words, then a Keymap of every binding given, under its group, each key listed once under the command it gives, and a note saying that keys wait while you type in a field and, when sequences exist, that a sequence's keys are typed one straight after the other and any other key lets go of the first. That is what Foldkit's key bindings do; any other pause (while a card is held, while a menu is open) is the application's to make, so the note does not claim it. `?` opens it and so does `KeyHelp.open` from the parent's update (a button, a palette entry). Render it once per page, with the bindings in force on that page. `Closed` reports the reader closing it (Escape, the veil, a close button); opening it reports nothing, by `?` or by `open`, so read `KeyHelp.isOpen` to keep another dialog from opening over the help.
|
|
1070
|
+
- **KeyHint** → `kbd.key-hint[aria-hidden="true"]` (Button's and a menu item's `keyHint`), a key sequence printed as the help prints it: arrows as arrows, a sequence of characters as one word (`["G", "G"]` prints gg; named keys keep their space), `Mod+K` as ⌘K or Ctrl+K, Shift as ⇧ or Shift+. The platform lives in the application's Model as `KeyHint.KeyPlatform`, `"Other"` until `KeyHint.detectKeyPlatform` runs once from `init`, so a server render prints Ctrl. The same notation is exported as `KeyHint.keyLabel(keys, platform)`, for anything else that names a key (a command palette's hint).
|
|
1071
|
+
- **A list's cursor** is the application's: it keeps the current item in its Model, sets `aria-current="true"` and a roving `tabindex` (0 on the current item, -1 elsewhere) on that row's `div.row` or on that board card (`Board.cardView`'s `attributes`), and binds j and k (and ↓ ↑) to move it, `g g` and G (Home and End) to go to the ends, in an outline h and l to fold and unfold or step to the parent and the first child, on a board h and l to cross columns, and Enter to open, with the commands on the current item (`d d` removes it) beside them. A move focuses the item and scrolls it into view by the least distance; when the current item leaves (filtered, removed, folded away) the cursor takes its place. While a Board holds a card, the arrows and Space are the move's: turn the page's own bindings for them off. Give a Row `role="group"` and a label.
|
|
1072
|
+
|
|
1073
|
+
### Tips and toolbars
|
|
1074
|
+
|
|
1075
|
+
**Tips** (Submodel; `Tips.subscriptions(keyPrefix)` follow the pointer, focus and keys across the page, keyed behind the parent's prefix: `Tips.subscriptions("tip")` is keyed `tipPointerAndFocus` and `tipKeys`) → one `div.tip[popover="manual"][aria-hidden="true"]` for the page, rendered once beside the Announcer, which shows the words of any element carrying `data-tip`: a label, not a surface, so flat Printer Ink with Paper White text and no print shadow, ringed by a 2px paper outline so it parts from an ink bar under it; the 11px pixel face, uppercase, 0.05em (`--tracking-state`), `3px 7px`, at most 36ch. It is placed by anchor positioning 6px above its control (the control is the tip's source, so its implicit anchor), hangs from the control's side near a window edge and flips below near the top; inside a vertical strip it opens to the right. It opens on a resting pointer after 400ms (at once while another tip has just closed, so sweeping a toolbar reads each name), never on touch, and on keyboard focus (`:focus-visible`) a beat later, only if the control is still on the page and still the keyboard's, so focus a closing dialog or menu hands back does not open a tip mid top-layer change; it can be hovered, it closes on leave and blur (which also cancels a tip still waiting), and using the control (a press, any key) closes it, so a control that renames itself is read afresh next time. Escape closes a tip that focus opened, and nothing else, never the menu or card around it. **The Tip Rule:** a tip shows its element's accessible name and nothing else, which is why it stays out of the accessibility tree; a control that needs a description says it in words beside it. IconButton, the glyph switch, Mark, the board grip, a menu's glyph toggle and an abbreviated Tag carry one. A pressed toggle keeps its name and lets `aria-pressed` carry the change. The system uses no `title` attribute tooltips.
|
|
1076
|
+
|
|
1077
|
+
**Toolbar** (a Mount moves focus, reporting through `toParentMessage`) → `div.toolbar[role="toolbar"][aria-orientation]` (`.floating`): one strip of tools that is one stop for Tab: its own buttons and links, not the items of a menu one of them opens. Arrows on either axis move between tools and stop at the ends, as a cursor's, the palette's and a dialog's action bar's do (a menu and tabs wrap, as their platform patterns do; a vertical strip lies flat along the bottom of a stage on a phone), Home and End go to the ends, and the tool last used is where Tab returns (a roving `tabindex`). The strip is a 2px ink box whose ink shows through a 2px gap between tools, so the rules between them never change with a tool's state. Tools are 28px tall, glyph-only IconButtons 30px wide, labelled Buttons `0 10px` in the 11px pixel face, all paper, head on hover, inverted when `aria-pressed` (the tool in force), muted when disabled, nudged 1px while pressed. A tool's focus ring sits 4px inside it in the tool's own text colour, so it reads on a pressed tool and never lands on the strip's ink. Floating (over a canvas) it takes the 3px button shadow; vertical, its tips open to the right.
|
|
1078
|
+
|
|
1079
|
+
### Menus
|
|
1080
|
+
|
|
1081
|
+
**Menu** (Submodel, pixel-paper's own ARIA menu rather than `@foldkit/ui`'s, whose rows cannot take a role or attributes; it reports `Selected` for an action, a radio item or a link, and `Toggled` for a check item) → `div.menu-wrap > button[aria-haspopup="menu"][aria-expanded]` (the toggle, built on `@foldkit/ui`'s Button) and, while open, `div.menu[role="menu"]`, named by its toggle and hung under it by `@foldkit/ui`'s Anchor, above the page (inside the open dialog when there is one), so no scrolling container clips it. The slip is `position: absolute`, so the `left` and `top` Anchor writes place it (Anchor makes it `fixed` itself under a fixed toggle, in a dialog too). It sits at `z-index: 200`, over the sticky top bar and status bar (40) and a banner (150), under the skip link (300). It hangs 6px under its toggle from its left edge (`"End"`: its right edge) and flips to the other side, or above, when the window has no room; paper, 2px ink border, card shadow, 148px minimum, capped at `100vw - 24px`. Its items are built with `Menu.action`, `Menu.radio`, `Menu.check`, `Menu.link`, `Menu.note` and `Menu.group`. An **action** → `button[role="menuitem"]`; a **link** → `a[role="menuitem"]`, a place to go, which a new tab can open (`aria-current="page"` inverts the page you are on); a **radio** item is one choice among its siblings, `button[role="menuitemradio"][aria-checked]`, inverted when checked. Each holds `span.menu-label`, and an action an optional `keyHint`, the same `kbd.key-hint[aria-hidden="true"]` as Button's (see Keys), at the right in the menu's own face at 11px, no case change, Muted Ink (its item's colour at 0.75 in an inverted item), so the name is the words alone. A **check** item → `button[role="menuitemcheckbox"][aria-checked]` with a 14px `span.menu-check`; a **note** → `span.menu-note`, a muted sans sentence; a **group** heads its items with a note. Items are `7px 12px` between 1px rules, head on hover. An action's or a radio item's `isDisabled` (`[aria-disabled="true"]`) prints it muted with no hover tint and the default cursor; choosing it does nothing, and the arrows still stop on it, as the ARIA menu pattern asks, its ring showing where the keyboard is. Say why beside it in a note. The rows take real focus: the first as the slip opens (the last on ↑ from the toggle); ↑ and ↓ move and wrap, Home and End go to the ends, and a letter goes to the row it starts. Enter, Space or a click chooses and closes it (a check item keeps it open, and the parent flips the value it keeps); Escape or Tab closes it and puts focus back on the toggle; a click elsewhere closes it, and so does the slip leaving the page while open (the row the menu sits on is gone), so the parent's keys never wait on a menu nobody sees. One Menu Model serves one toggle: a menu on every row is a Model per row. `LeftSlip`, `UnmountedSlip` and `CompletedFocus` can arrive after a close and must reach the Model of the menu that sent them, where they change nothing; given to another menu's Model, a late `LeftSlip` or `UnmountedSlip` closes it. The menu does not trap focus, so it hosts no live region.
|
|
1082
|
+
|
|
1083
|
+
**ContextMenu** (Submodel) → the same slip as `div.menu.at-point[role="menu"]`, opened where a right click lands (at the thing's box for the context menu key or Shift+F10), kept 12px inside the window, with no gap under the pointer; its first item takes focus, and closing it returns focus to where it was unless the reader moved it. One context menu (one Model) serves every thing that spreads `ContextMenu.target` (a row, a card, a table row; give it an id so focus can return), and its OutMessages carry the `targetId`. It is a shortcut, never the only way in: the same items are always in a visible Menu on the thing (a quiet `more` glyph toggle on a row), which is the keyboard's and the phone's way. Never an action found only there.
|
|
1084
|
+
|
|
1085
|
+
### Rows and the outline
|
|
1086
|
+
|
|
1087
|
+
**Rows** → `ul.rows` (`.tree` for an outline); each row, `Rows.rowView` → `li > div.row` (`.context`) with an optional nested `ul.rows`. A context row (outside a filter, printed only to keep a match on its chain) is muted and carries no chips, and since muting is colour alone it ends its text with `span.sr-only` " (context)", which a selectable row's box takes into its name. A 34px flex row (`5px 12px`, 8px gap): an 18px glyph cell (`Rows.glyphView` → `span.glyph`, or `Rows.toggleView` → `button.glyph[aria-expanded]` with the ring glyph open or closed, named "Rows under" its label whether open or not), `div.grow` holding the text (a `label.grow` when it is the label of the row's box; `Rows.titleView` → `button.title`, underlined on hover, `.done-title` when done), `span.slot` for chips, marks and due days, the row's actions, and `span.tools` (`tools`, icon buttons) resting at 55% opacity until hover or focus-within (hidden on phones). Hover and focus-within tint the row head with `background-color` only, so the indent guides survive; the cursor row takes neither. A keyboard-driven list puts its cursor on `div.row` (`aria-current="true"`, a roving `tabindex`), which the stylesheet outlines (see "The cursor" below), through `Rows.rowView`'s `attributes`, which go on `div.row` rather than its `li` and also carry the row's `role="group"`, its name and its key handlers (never a class). Nested lists indent 26px on a 1px rule guide. **InlineInput** (Submodel; its `init` focuses and selects the field, so fold it in with `Update.foldChildInit`) → `input.inline-input` swaps in for text at exactly `1lh + 1px` so the row never moves. Enter or leaving the field reports new, non-empty text, trimmed, as `Committed`; an untouched field reports nothing, so a remote edit is never written back. The field then holds `readonly` and `aria-busy` (muted, still focused) until the parent answers with `InlineInput.succeeded` or `InlineInput.failed`: a failure keeps the field and its text for another try and, when it is a refusal with a reason, says why through `InlineInput.errorView`, which the field points at (`aria-invalid`, a red rule, and `aria-describedby`); the parent reads the held reason with `InlineInput.maybeError`. The parent's own `error` (the item's parent was deleted while editing) marks the field the same way, given to the view and to `errorView` alike, and shows before a refused save's reason. `Ended` says the edit is over (`"Enter"`, `"Blur"` or `"Escape"`, and `"Blur"` when focus left while the save was out) with the id the edit's last save returned (`maybeLastSavedId`, the item a repeat added last); the parent moves focus on `"Enter"` and `"Escape"` only, so a clicked destination keeps it. `isRepeating` (adding several) empties the field after each saved Enter instead of ending; Enter on an empty one ends. The text lives in the Submodel's Model, so it outlives a render. Escape is stopped from closing a dialog around it; an IME's Enter and Escape stay with the IME; removing the field writes nothing. **EditableTitle** (Submodel; it reports `Committed`, which the parent answers with `EditableTitle.succeeded` or `failed`) → `button.title-display` ⇄ `input.inline-input.title-edit` at 16px/600; Enter and Escape put focus back on the title.
|
|
1088
|
+
|
|
1089
|
+
**The cursor:** `div.row[aria-current="true"]`, set on the row by the application, the one row the keyboard's commands act on. It is drawn, not filled: a 1px Muted Ink outline 4px inside the row (`outline-offset: -4px`), so a nested row's follows its indent and none leaves the panel. It takes no fill at all, not even the hover or focus tint other rows take, and its text, chips and tools keep their own colours (the tools at full opacity). Focus on the row itself thickens the same line to 2px (`--line`): one line inside the row, never a second ring outside it, for any focused row. It is neither focus nor selection (the check cell), it stays put when focus leaves, and moving it is the application's (see Keys). Controls in it keep their own ring: the text cell (`.grow`) clips, but with 4px of room inside the clip, so a title's ring prints whole; an InlineInput in a row shows its focus as its caret and its rule doubled to 2px (drawn inside its box, so the row keeps its height; red while refused), not a ring. A board's cursor card is inverted instead (see Board).
|
|
1090
|
+
|
|
1091
|
+
**Selectable rows:** a row's `select` puts `span.row-check > button[role="checkbox"][aria-checked]`, built on `@foldkit/ui`'s Checkbox, at the row's left edge: the cell is the row's full height and all of it the box's target, and the text is the box's label (`label.grow`, which names it through `aria-labelledby`), so a click there toggles too. A row whose text holds a button or a link names the box with `label` instead (a `span.sr-only` in the cell, the text a plain `div.grow`), since a label's click is on all of its text. `select.id` is the stem of the label's id (`<id>-label`); the box has no id. Checked, the box's cell inverts: an ink square at the edge with the box in paper inside. Only the cell (the whole row inverts only as the cursor): inversion marks the one current thing, and half a list inverted reads as a barcode that buries its titles and chips. **SelectAll** (a View over `@foldkit/ui`'s Checkbox) → `div.row.bulk > span.row-check + div.grow > span.bulk-count`, then `span.bulk-acts`: the head above a selectable list in a flush panel, over a 1px rule: a box that is checked, clear or mixed (`aria-checked="mixed"`, a 2px bar; a mixed box selects all), named "Select all" by a `span.sr-only` in its cell and described by the count ("3 of 12 selected") in the 11px pixel face, muted until something is selected, and the bulk actions (small Buttons), which always render so the head never shifts and focus never drops when the selection clears; the caller marks them `aria-disabled` while nothing is selected. Only the box toggles: a click on the count does nothing. With no rows the box is `aria-disabled`, muted and still in the keyboard. `id` is the stem of the name's and the count's ids (`<id>-label`, `<id>-description`); the box has no id.
|
|
1092
|
+
|
|
1093
|
+
### Board
|
|
1094
|
+
|
|
1095
|
+
**Board** (Submodel over `@foldkit/ui`'s DragAndDrop. `Board.setMovable` says whether cards may move, and turning it off cancels a move. It reports `Moved`, and `SelectedCard` for a press on a card's body, never on a control inside it or anything marked `[data-no-lift]`. While a card is held the application pauses its own keys. `Board.subscriptions(keyPrefix)` are lifted into the application's once per board, keyed behind the parent's prefix: `Board.subscriptions("queueBoard")` is keyed `queueBoardPointer`, `queueBoardKeyboard`, `queueBoardAutoScroll` and `queueBoardEscape`, so two boards on one page take two prefixes) → `div.board` (`--cols`) of `div.board-col (.drop-target)`, each a panel holding `ul.board-cards`; each card is the parent's `toCardView`, which receives what the board hands it (`render`) and returns **Board.cardView** → `article.board-card (.movable, .drag-dim, .lifted)`: a paper slip in a 2px ink border with a 3px shadow, `padding: 9px 11px`, a 7px gap between a 2-line-clamped sans `button.title` (`isDisabled` holds it while work runs: `[aria-disabled="true"]`, it ignores presses but keeps its words and its place in the keyboard), or, while the title is edited, the caller's `editor` in its place (an `InlineInput` and its `errorView`, ink on paper even on the cursor's inverted slip; the lifted copy keeps the title), and `div.meta-row` (mark, tag, chip, `span.gap`, age). While cards may move (`isMovable`, the default; `Board.init` takes `isMovable: false` for a board whose moves wait on something, such as writes not accepted yet) they move between columns and the meta row ends in a quiet `button.iconbtn.grip` (the grip glyph, labelled "Move <label>", `aria-roledescription="draggable"`, `aria-pressed` from `Button.pressedAttribute`, present in both states: `"true"` while held, `"false"` otherwise). A mouse drags the whole slip (`cursor: grab`); touch and keyboard take the grip (`touch-action: none`, so a finger on the slip still scrolls): Space or Enter picks up, the arrow keys jump one column in reading order, Space drops at the top of that column, Escape cancels, and each step is announced through the page's live region in words from the card's `label` and the column titles. The held card dims to 40% in place while a lifted copy follows the pointer on the 8px card shadow, tinted head (`aria-hidden`, `inert`, taking none of the card's attributes); the receiving column outlines its panel with a 2px dashed ink line offset 4px and tints its list head; a drop lands at once, with no animation. The cards and columns are the parent's: `Moved` counts `toIndex` among the destination's other cards and names the cards the moved one now sits between (for a list ordered by neighbours); a drop where the card already was, or while cards may not move, reports nothing; and the parent moves the item, or decides not to. Hover and focus-within tint the card; focus-within rings the whole slip 2px outside. `[aria-current="true"]`, set on the article by the application through `attributes`, is the board's cursor, printed inverted (ink fill, paper text) and keeping its ink on hover and focus-within. Inside it, what prints muted on paper (`.glyph`, `.age`, `.due`, `.tag`, `.done-title`, a quiet icon button) prints in paper at 0.75, as a title bar's meta does, and a tag's border takes `currentColor`; chips, marks and buttons keep their own fills; a late due day or stale age, which cannot be red on ink, becomes a red slip with `on-red` text, its box drawn by a 3px red spread so nothing moves; a ring inside prints in paper; the application moves it (see Keys). The lifted copy prints head-tinted, never inverted.
|
|
1096
|
+
|
|
1097
|
+
### Inputs / Fields
|
|
1098
|
+
|
|
1099
|
+
- **Field** (a View over `@foldkit/ui`'s Input) → `div.field (.boxed, .mono) > label + input + p.field-error + p.field-help`, the label naming the input and the help describing it; a failed field prints why in `p.field-error` (sans, Urgent Red) under the control, which is `aria-invalid` and described by it before its help, and the ErrorSummary above the fields says it again; a required field's control is `aria-required`; the input's `type` the platform's (`"date"` for the browser's date control) when given. **SelectField** (over `@foldkit/ui`'s Select) → `div.field > label + select + p.field-error + p.field-help`, its options in order and a group's under an `optgroup` heading, an option that is listed but not choosable `disabled`; **TextareaField** (over `@foldkit/ui`'s Textarea) → `div.field (.mono) > label + textarea + p.field-error + p.field-help`, opening at `rows` lines. Both name, describe and mark their control as a Field does. **Fieldset** → `fieldset.field > legend.field-label`, then the controls, `p.field-error` and `p.field-help`, its error and its help linked with `aria-describedby`; **FieldGrid** → `div.field-grid` (auto-fit, 160px minimum); **Fields** → `fieldset.stack.gap-m`: a form's fields as one column `--space-m` (14px) apart, a body's gap, on the layout primitives with no rule of its own. `isBusy` (a save in flight) sets `disabled` and `aria-busy` on it, so every control holds at once and nothing changes under the save; the submit Button stays outside it, `isBusy`, so it keeps focus. A disabled control prints its value in Muted Ink; a boxless one also drops its dotted baseline (the mark of a place to type), a boxed one or a select mutes its box and caret.
|
|
1100
|
+
- **Label:** 10px uppercase 0.1em Muted Ink, 4px above; turns red when the field is invalid, as a fieldset's legend does when a control in it is.
|
|
1101
|
+
- **Boxless (default):** full width, sans body, no box, a 1px dotted Muted Ink baseline, `padding: 2px 0`. On keyboard the baseline becomes 2px solid ink; invalid, 2px solid red.
|
|
1102
|
+
- **Boxed:** the field that is the whole job of its form (sign-in, a quick-add field): 2px ink border, `padding: 5px 8px`; keyboard ring 2px outside; invalid turns the border red. A date is always boxed.
|
|
1103
|
+
- **PasswordField** (a View; the caller keeps whether it shows) → a boxed Field whose control is `span.password > input + button.iconbtn.quiet[aria-pressed]`: the toggle ("Show password", pressed while shown; each of two in one form named apart, such as "Show password again"; its glyph turns from `eye` to `eye-off`) sits inside the box at its right end, the input padded 32px clear of it. With `strength` (0 to 4, as the product measures it; the system does not) a new password shows `span.strength` 6px under the box (under its `p.field-error` when it failed): four short cells lit up to the reading, a `meter` named "Password strength", and its word in the age register ("too short", which an empty box is, "weak", "fair", "good", "strong"). Signing in leaves it out.
|
|
1104
|
+
- **Textarea** (TextareaField's): boxless, 13px/1.5 sans, grows with content from 96px, vertical resize with an ink corner grip. **Mono:** system mono at 12px.
|
|
1105
|
+
- **Select** (SelectField's): `appearance: base-select` where supported; 30px minimum, 2px ink border, 11px pixel face, a CSS-drawn caret; the picker is paper with a 2px border and the panel shadow. Invalid, its box turns red.
|
|
1106
|
+
- **Choice** (a View over `@foldkit/ui`'s Checkbox) → `div.choice > button.choice-box[role="checkbox"][aria-checked] + span > span.choice-name + span.choice-note`; **RadioChoices** (Submodel over `@foldkit/ui`'s RadioGroup) → `div.choices[role="radiogroup"] > button.choice[role="radio"][aria-checked] > span.choice-box + span > span.choice-name + span.choice-note`: the option is the button itself, so a click anywhere on its line chooses it and the focus ring is around the line; `RadioChoices.firstOptionId` is the id of the first option, for a link to the group (an ErrorSummary's); **Choices** → `div.choices (.inline)`. 14px ink-bordered squares with a 6px painted cell (radio: 8px diamond). Invalid (`aria-invalid` on the radiogroup, since a radio does not take it), each box and choice's name turns red.
|
|
1107
|
+
- **ErrorSummary** → `div.notice.error.error-summary[tabindex="-1"]`: why a form was not sent, inside the form above its fields: the error Notice with a count headline ("2 fields need attention") and a list of links, ink on a 1px underline (2px on hover), each a fragment link to its failed field, one entry per control (the headline counts entries and each `li` is keyed by its control's id, so several messages for one control are the caller's to join), which the application's fragment handler focuses (`SkipLink.focusFragment`). It is rendered from the errors found at the failed submit, not live state, and holds still while they are put right; each field it names is marked invalid too. With no errors it renders nothing. It carries no `role="alert"` (a live region inserted with its words in it is not reliably read): the form's update returns `Focus` with the summary's id at every failed submit, a transition the submit causes, so focus lands on it once it has rendered and reads it, again at each failed submit though it was already showing. A refusal that arrives after the submit (a server turning a field down while the save is busy) is a failed submit like any other: the form renders the summary from it and focuses it.
|
|
1108
|
+
- **DropZone** (Submodel over `@foldkit/ui`'s FileDrop) → `div.drop-zone ([data-drag-over])`: a 2px dashed ink box, `28px 16px`, centred, a 34px ink-bordered glyph box, a 12px uppercase headline and a 44ch muted sentence, and a `label.btn.small` around a visually hidden file input; solid and head-tinted while a file hovers (FileDrop sets `data-drag-over`).
|
|
1109
|
+
|
|
1110
|
+
### Notices
|
|
1111
|
+
|
|
1112
|
+
**Notice** → `div.notice (.error, .ok, .quiet)`: a 20px ink-bordered `span.glyph` box on amber (`on-status` ink) beside a 12px sans body whose first line is an 11px uppercase 0.06em (`--tracking-head`) `strong`, then an optional action at the right. `padding: 8px 10px`, 2px ink border on paper. Its words wrap anywhere, so a link or a path breaks rather than widening the box. Error: red border and red glyph box with `on-red`, `role="alert"`. OK (a committed change the product wants acknowledged): an ink glyph box with a check, because success is not a status and green means active. Quiet: Rule Grey border, muted text, head-tinted glyph box with On Head and info. The glyph defaults to bang, check when ok and info when quiet; an Outcome and a Banner share the box and its tones (`:is(.notice, .outcome, .banner) > .glyph`). Inside Prose a notice sets at 14px/1.55.
|
|
1113
|
+
|
|
1114
|
+
**Toast** (Submodel over `@foldkit/ui`'s Toast, the page's one stack: `Toast.show` raises a slip from the parent's update, says it once (its `AnnounceSlip` Command) and returns its `entryId` with the step, and `Toast.dismiss` takes it back) → `div.toasts` holding the stack, a polite live region named "Notifications", of `div.notice.toast (.ok, .error, .quiet)` slips, each with a dismiss IconButton labelled "Dismiss: <title>". Render it once, by the hosting rule the Announcer follows; each slip is said exactly once as it arrives. The stack itself is the polite live region (`@foldkit/ui`'s container always is), so a slip added to a stack already on the page is read from there and nothing else says it. A stack mounted in the same render as its slip (it moved host because a Dialog opened or closed in the same update) arrives already holding it, which a live region does not read; then `AnnounceSlip` says it through the Announcer, which sits in the same host. It tells the two apart by comparing the stack's element before and after the render (`Render.afterCommit`). A toast is a Notice on the 3px button shadow: an outcome the reader may not be looking at (a background save, a send that failed, a job that finished). `"Ok"` is ink with a check, `"Error"` red, `"Quiet"` the head-tinted glyph box with info inside an ink border (on the ground every slip is closed in ink, an error in red). Its title is the notice's headline, its body one sans sentence, and it may carry one small action (undo, retry) that also dismisses it. Slips arrive in three steps over `--motion-card` and leave at once. Ok and quiet slips leave after `--toast-hold` (5s), which waits while the pointer or the keyboard is on the stack; an error stays until dismissed or taken back once what failed has recovered. At most three slips that are not errors show; a fourth replaces the oldest of them. Errors are not counted: they stack without limit and take nothing's place. Escape inside the stack dismisses the newest (a Mount on the stack, `BindStack`), and focus moves to the slip now in its place, or back where it came from once the last slip is gone. A pointer or the keyboard on the stack pauses every slip's hold. Every tone, an error included, is said politely, never as an alert. While the system's Dialog is open the stack lives inside the topmost one. A toast never asks a question (that is a ConfirmDialog) and never holds a state (that is a Banner).
|
|
1115
|
+
|
|
1116
|
+
**FlashWord** → `span.flash-word[role="status"]`: the word beside the control the reader just used ("saved", "copied"), in the state size at 0.08em. The caller keeps the word in its Model and takes it back after `FlashWord.HOLD`; the status element stays on the page and only its word changes, so it is heard.
|
|
1117
|
+
|
|
1118
|
+
**The How Long It Matters Rule.** An outcome is said by how long it matters: a FlashWord beside the control the reader just used ("saved"), gone in a beat; a toast for an outcome they may not be looking at, gone after a while; a Banner for a state the whole interface is in, there for as long as it lasts.
|
|
1119
|
+
|
|
1120
|
+
### Tables
|
|
1121
|
+
|
|
1122
|
+
**Table** → `div.table-wrap[role="region"][aria-label][tabindex="0"] > table.table` (the name is on the scrolling region only, so it is announced once): `border-collapse` at 13px; heads 10px uppercase 0.1em On Head on Head Tint, `6px 12px`; cells `8px 12px` over 1px rules, the last row unruled; rows tint head on hover. `isNumeric` columns are right-aligned tabular 11px pixel face. The action column (`td.acts`) and its head are sticky right on paper so buttons stay in reach while a wide table scrolls; on phones the seam is a 2px ink rule. A row `isOff` names (`tr.off`) prints muted with its `.name` struck through. **Stacked:** under 600px each row becomes a block, `10px 12px` between 1px rules, each cell a line preceded by its `data-label` in the label register; the action cell sits 8px below.
|
|
1123
|
+
|
|
1124
|
+
### Properties
|
|
1125
|
+
|
|
1126
|
+
**PropsList** → `dl.props > div > dt + dd + dd.acts`: a three-column grid (`max-content minmax(0, max-content) minmax(max-content, 1fr)`) of 34px rows on 1px rules whose cells stretch so every rule meets at one line: a 10px uppercase 0.1em muted term, a value (inline code on head tint, clipped with an ellipsis, when `isCode`), and its `actions` (a CopyButton, an action) that stands against the longest value rather than at the panel's far edge. On phones the term stacks above the value.
|
|
1127
|
+
|
|
1128
|
+
### Code surface, secret, history and the line diff
|
|
1129
|
+
|
|
1130
|
+
- **CodeSurface** → `pre.code-surface[tabindex="0"][role="group"]`, named by its label: a 2px ink-bordered head-tinted block in 12px/1.6 system mono, `10px 12px`, `tab-size: 2`, wrapping. It takes a string or a highlighter's spans: `tok-keyword`; `tok-string`, `tok-string2`, `tok-url`; `tok-number`, `tok-bool`, `tok-atom`, `tok-literal`; `tok-definition`, `tok-variableName2`, `tok-macroName`, `tok-link` (underlined); `tok-typeName`, `tok-className`, `tok-namespace`; `tok-comment`, `tok-meta`, `tok-punctuation` take their syntax roles; `tok-heading` and `tok-strong` are bold, `tok-emphasis` italic, `tok-inserted` green ink, `tok-deleted` and `tok-invalid` red ink (invalid on a dotted underline). Anything unnamed stays ink. **CodeEditor** (a View over `@foldkit/ui`'s Textarea) → `textarea.code-surface`, spelling, capitals and autocomplete off: 220px minimum, grows to 60vh, vertically resizable, scrolls sideways (wraps on phones).
|
|
1131
|
+
- **Secret** → `div.secret > code[role="group"] + button.btn.small`, the code named by its label: the same box at `6px 6px 6px 12px` with a scrolling nowrap code at 0.02em (wrapping anywhere on a phone, so the end is never hidden) and, in its `actions`, a copy button whose word turns "copied". **CopyButton** (Submodel; `CopyButton.isCopied` is for a parent that follows the copy) → the quiet copy glyph (a check once copied) or, as `"Button"`, a small button whose word turns "copied", for a beat; either says it to screen readers, and a copy that fails says so. It writes through Effect's `Clipboard` service: the application provides `Clipboard.layer` from `@effect/platform-browser` in its `resources`. A `div.save-row` with a `span.push` holds what follows it (the "I've stored it" commit).
|
|
1132
|
+
- **History** → `ol.history-list (.checks) > li (.current, [aria-current="true"]) > span.v + span.who + span.what (.failed)`: ruled rows, the version in 11px pixel face in a fixed `4ch + 8px` box (inverted when current, so nothing moves, a Time in it taking the paper too), who in 10px muted at 10ch (20ch for checks), what in sans, a failed line red with "(failed)" after it for screen readers (`span.sr-only`), so the state never rests on colour. `isWrapped` → `ol.history-list.wrapped` (never `.wrap`, which is Wrap's row), for an activity log whose words matter more than a tidy column, compact enough for a rail's panel: when (a whole timestamp, its box as wide as it, muted and flush with the what under it, padded `0 4px` and inverted only when current) and who share the first line with the action pushed to its end, and what runs on its own line below in the note size (12px/1.45 sans, in ink), wrapping anywhere. Entries are `6px 0` apart over 1px rules with a 2px gap between their lines; there is no rule above the first and no padding before the first or after the last, so the list starts where a body's content starts.
|
|
1133
|
+
- **LineDiff** → `div.diff-wrap > table.diff > tr.add | tr.del | tr.same | tr.fold`: a Myers line diff (jsdiff's `diff`, a runtime dependency) in a 2px ink box on paper that scrolls sideways; 12px/1.6 system-mono lines, each with two 10px pixel-face line numbers behind a 1px rule, a sign column, and the line. Added lines are Head Tint behind an ink `+`; deleted lines muted behind a red `−`; unchanged runs beyond two lines of context fold into a head-tinted row labelled in 10px uppercase On Head. On phones lines wrap. `LineDiff.patchView` draws the same table from one file's unified diff, the lines between its hunks folded into the counted row; anything else prints a note line.
|
|
1134
|
+
|
|
1135
|
+
### Meters and cells
|
|
1136
|
+
|
|
1137
|
+
**Cells** (`Meter.cellsView`) → `span.cells[role="meter"] > span.cell (.on)` (`.short` for 24×8px cells). **Meter** (over `@foldkit/ui`'s Meter) → `li.meter-row (.stale)` in `Meter.listView` → `ul.meters`: a reading against a maximum in the vocabulary of `<meter>` (`value`, `max`): the label (name register) and an optional detail (meta) beside twenty cells lit in ink up to the reading; then the reading's state (the percentage in the 12px pixel face below 80% of max, the amber reading mark from 80%, a red chip at max holding `full` or the percentage; below max it never prints 100% or lights every cell, so 49,999 of 50,000 reads 99% with 19 cells); then the numbers (or `valueText`) in 11px muted tabular figures, formatted in English (`41,200`) so server and browser agree; then `div.meter-foot` (resets, last read; `.late` red). A stale meter (`isStale`: the last reading failed, the numbers are the last known) prints its lit cells muted and its name ends in "(stale)" for screen readers (`span.sr-only`), so the state never rests on colour; the caller says why and when it was last read in the foot line.
|
|
1138
|
+
|
|
1139
|
+
### Stats
|
|
1140
|
+
|
|
1141
|
+
**Stats** (`Stat.listView`) → `dl.stats` of **Stat** → `div.stat > dt.label + dd.stat-value + dd.stat-delta + dd.stat-note`: a row of figures in ruled cells, edge to edge in a flush Panel that names what they measure, never a row of raised cards each holding one big number. Each cell pads `14px 16px` (12px on a phone) behind 1px Rule Grey rules that only ever divide. The label in the label register, wrapping when long; the value in the figure register, formatted in English (41,200), with an optional muted unit; a muted dash ("not known" to screen readers) when it is unknown, the working cursor at the figure's height while it loads. The change prints in ink in the 11px pixel face behind a direction glyph (`arrow-up`, `arrow-down`, `minus`); `tone: "Warn"` boxes it amber with `on-status`, `tone: "Error"` red on paper in a red box. A muted 12px sans note (46ch) says what the change is against.
|
|
1142
|
+
|
|
1143
|
+
### Charts
|
|
1144
|
+
|
|
1145
|
+
**BarChart** and **LineChart** → `.chart`: the picture is hidden from screen readers, who read a visually hidden table of the same numbers (captioned by `label`) in its place; both render on the server, and an empty chart is an EmptyState. A value is an `Option<number>`; `None` or anything not finite is no reading, said as "no reading" in the table and the reading, lighting no cells in a bar chart and breaking a line chart's line at that point. A bar chart's columns (`div.bar-cols`, the default) are for a run over time, its rows (`div.bar-rows`, `isRows`) for categories with long names: cells lit in ink up to each value, unlit in Head Tint, a value above zero lighting at least one; the scale and labels in the label register; readings in 11px muted tabular figures (past twelve columns only a toned one prints); a point's `tone` (`"Warn"` or `"Error"`) puts its reading on the amber mark (`"Warn"`) or in a red chip (`"Error"`) while its cells stay ink. A line chart (`div.line-chart`) is the stepped line (see Shapes) in an SVG stretched to its column with non-scaling strokes and `crispedges`, one series in ink or two (the second muted with smaller cells, named in a key of cells), the scale's top and zero level with the plot's top line and axis, up to eight labels under it (past that every nth). Compared bar charts take the same `max`.
|
|
1146
|
+
|
|
1147
|
+
**Diagrams** are an adapter, not a component: `./adapters/mermaid` exports a View, `Mermaid.view`, whose `DrawDiagram` Mount imports mermaid on demand once the box is on the page, waits for the faces, draws one diagram at a time (mermaid's settings are global) in the box it will live in, and reports `SucceededDrawDiagram { palette }`, `FailedLoadMermaid { palette, reason }` when mermaid itself does not load (not installed, or its code did not arrive), or `FailedDrawDiagram { palette, reason }` when mermaid loaded but the diagram was not drawn (most often a source that does not parse), which the caller keeps to show a Loading line until the drawing lands and an error Notice if it fails, saying which of the two happened. `reason` is the whole diagnostic, several lines for a parse error (the line, a caret under the fault, what was expected), so the Notice prints it as written in a CodeSurface. The box is keyed by `palette` and the source, so passing `SchemeSwitch.palette(model)` draws it again in a new palette, a change of the OS palette under `"System"` included (the switch follows `prefers-color-scheme` in a Subscription). Its theme is read from the page's palette on `<html>` (`themeCSS` written in `var(--…)` so nodes, edges, clusters, actors and notes follow the palette in place; `themeVariables` resolved from the tokens, since mermaid mixes its own shades from them) and its font size from `--type-body`. What an author writes in a diagram is set in the sans; mermaid's own words (a loop's tab, a cluster's name) are chrome in the pixel face. Mermaid is the adopter's optional peer, loaded only in the browser.
|
|
1148
|
+
|
|
1149
|
+
### Images and media
|
|
1150
|
+
|
|
1151
|
+
All three keep their space from the first paint (`size`: `Media.FrameSize.Ratio({ ratio })` or `Media.FrameSize.Pixels({ width, height })`), sit at rest with no print shadow in a 2px ink frame, shrink to their column and never grow past a given width, and take `held` (`"Loading"` or `"Broken"`) to hold a specimen or a known-gone source; a failed one turns its frame red and fills it with an unboxed error Notice saying why, with a retry when `onRetry` (ImageFrame) or `hasRetry` (the players, which report `Retried`) is given. The two players are views of one Submodel, `Media`, which the application keeps a Model of per player: `VideoFrame.view` and `AudioFrame.view` both render a `Media.Model`.
|
|
1152
|
+
|
|
1153
|
+
- **ImageFrame** (a View) → `figure.image > div.image-frame > span.image-wait + img`: a picture cropped to its frame, lazy and decoded off the main thread unless `isEager`. Under the picture sits a static placeholder, Head Tint with the 24px `image` glyph in On Head and no cursor, so the picture paints over it whenever it arrives, with or without script, and a transparent picture shows it through. The caller keeps whether it broke: the frame's Mount reports a failed load as `onBroken`, and the caller passes `held: "Broken"` back, when it says "image not found" with the alt text. `held: "Loading"` (`figure.image.waiting`) hides the picture over the placeholder.
|
|
1154
|
+
- **VideoFrame** (a view of the `Media` Submodel) → `figure.video > div.media-box > div.media-screen + div.media-bar`: a native video, never cropped, over a 2px ink rule and a bar of the system's own controls on one line: play, the place as a row of cells (a native range over them, unseen, built on `@foldkit/ui`'s Input: held, `aria-disabled` with muted cells, while the length is unknown, before the metadata and always for a stream, and natively disabled only while the source is broken), the time in 11px tabular figures ("0:07 / 12:40"), mute, captions (the `captions` glyph, pressed while shown, cues in the sans on ink); there is no full-screen control. Bar buttons tint head on hover and invert while pressed. While it waits for data the loading line sits on a paper slip over the picture.
|
|
1155
|
+
- **AudioFrame** (the other view of it) → `div.audio[role="group"]`: the same bar as one compact strip in a 2px ink box: play, the label in the sans, the cells, the time and mute; while it waits, the time gives way to the loading word and the working cursor.
|
|
1156
|
+
- The **caption** under an image or a video is a muted 12px sans sentence with its credit in the meta register at the right. Space or k plays and pauses and the arrows seek five seconds; every control is named, and its name is its tip.
|
|
1157
|
+
|
|
1158
|
+
### Files
|
|
1159
|
+
|
|
1160
|
+
**FileTree** (Submodel) → `div.file-tree > ul[role="tree"] > li > .file[role="treeitem"]` (`aria-current` for the open file, `aria-expanded` for a folder; the arrows move, open and fold as the ARIA tree pattern asks, and a letter goes to the next name it starts) with `span.glyph`, `span.name` and an optional `span.tag`: a 2px ink-bordered list of 12px system-mono rows, head on hover, inverted when current; folders fold, their children hang on a 1px rule guide.
|
|
1161
|
+
|
|
1162
|
+
### Reading
|
|
1163
|
+
|
|
1164
|
+
**Sheet** → `article.sheet > div.sheet-head > h1.sheet-title + div.sheet-acts`, then `p.byline`, then the body, usually **Prose** → `div.prose`. A sheet is framed as a panel is (paper, a 2px ink box, the 5px print shadow) and takes a panel's place on the page, never sitting inside one: its h1 opens the page, where a panel's h2 title would come before it. What a panel's title bar would say (the path, "earlier version") goes in the byline or the actions. The sheet pads 28px 32px 36px (20px 16px 28px on phones); the byline is 10px pixel meta over a 1px rule. Prose: paragraphs 1em apart; h2 (and an h1, styled as h2) 2em above, h3, h4 (pixel caps), h5 (bold, 13px) and h6 (muted pixel label) 24px above; a document rendered inside a titled container shifts its headings to sit under that title (under a Dialog's h2, Markdown's `#` becomes h3); square list bullets in Muted Ink and ordered-list numbers in the 12px pixel face; inline code on head tint at 0.85em; code blocks as code surfaces at 12.5px; tables in a 2px ink box at 14px; a 1px Rule Grey `hr`; blockquotes muted behind a 1px ink rule. Links are ink with a 1px underline offset 3px that thickens to 2px on hover; `a.missing` (a page that does not exist) is red on a dotted red underline. **Toc** → `nav > ul.toc > li (.l3) > a[href="#id"][aria-current]`, an entry with detail holding `span.toc-entry` (its words over the detail, 2px apart): plain links to the page's headings (`html` keeps `scroll-padding-top` clear of the sticky top bar) in 13px sans (12px muted for h3, indented), one 6px ink cell before the heading being read; every entry keeps the slot so nothing shifts. The heading being read is the caller's, kept by a Subscription entry, `Toc.readingPositionEntry`.
|
|
1165
|
+
|
|
1166
|
+
### Signature: the invert flash and the flash word
|
|
1167
|
+
|
|
1168
|
+
Every committed change (an approval, a drop onto a column, a save, a rename, a rotate, a copy) flashes the changed element: `Announcer.flash(selector)` is an Effect the application runs from its own Command. It runs as a Web Animation, not a class: `filter: invert(1)` held for the middle half of `--motion-flash` (0.7s), so the element goes fully inverted and back with no tween, and a second commit mid-flash restarts it. Under `prefers-reduced-motion: reduce` the flash becomes a 2px ink ring inset 2px, held for the beat and then gone, and every cursor stops. `Announcer.announce(text)`, also an Effect for the application's Command, says what changed through the page's one polite live region, `Announcer.view` (`div.sr-only[role="status"][aria-live="polite"]`, empty in the pre-rendered HTML); words said in the same moment are read together, and the same words twice are read twice. **Hosting rule:** render exactly one live region, and put it where it can be heard. While a Dialog is open the page outside it is inert and hidden from screen readers, so the region renders inside the topmost open dialog (its `toLiveRegionView` view input; ConfirmDialog, KeyHelp and CommandPalette pass theirs through), and in the body otherwise. Derive which from the application's Model (which dialog is open, and which opened last), so the region moves as dialogs open and close. The toast stack follows the same rule. A FlashWord beside the action says the verb (see Notices).
|
|
1169
|
+
|
|
1170
|
+
### Glyph inventory
|
|
1171
|
+
|
|
1172
|
+
**Glyph** renders one of the glyphs in `src/glyph/glyphs.ts` at 12px (any size keeps the cells), by name. They come in families, exported as `glyphFamilies` and shown by family on the specimen's foundations page, each glyph with its meaning in the source: **direction** (carets and arrows: going, moving, ordering), **actions** (what a control does to its thing), **state** (what a thing is in: marks, rings, alerts, visibility, locks, time, the palettes), **media** (playing, recording, sound, captions), **layout** (views, panels and the canvas tools), **documents** (pages, folders and what a page is made of), **people** (who, and how to reach them), **systems** (machines, networks and what runs on them), **versions** (history: branches, commits, diffs) and **readings** (charts, trends and measures).
|
|
1173
|
+
|
|
1174
|
+
### Adopting the system
|
|
1175
|
+
|
|
1176
|
+
Installing: see README.md beside this file. The package is a Foldkit library. It exports `.` (each component as a namespace: `Button`, `Dialog`, `ConfirmDialog`, `KeyHelp`, `Time`, `Scheme` and the rest; simple ones are `X.view(config, h)`, interactive ones are Submodels), `./tokens.css`, `./pixel-paper.css` (imports the tokens), `./fonts/*`, `./scheme` (`schemeScript`, for the document head, with the attribute and the storage key it uses) and `./adapters/mermaid` (`Mermaid.view` and its `DrawDiagram` Mount). The package is compiled JavaScript with its types and its views already branded for Foldkit's view identity, so the adopting app's build needs only `foldkit()`. Its peers are in `package.json`, mermaid optional. Import the `./pixel-paper.css` export once, put `schemeScript` in the head, and provide `Clipboard.layer` as the application's resources where a CopyButton is used.
|
|
1177
|
+
|
|
1178
|
+
**Setting up.** What an application does once:
|
|
1179
|
+
|
|
1180
|
+
- Import the `./pixel-paper.css` export from the client entry, and write `<script>${Scheme.schemeScript}</script>` into the document head.
|
|
1181
|
+
- Provide `Clipboard.layer` from `@effect/platform-browser` as the application's `resources` where a CopyButton is used. The scheme needs no service: SchemeSwitch reads and writes the page itself.
|
|
1182
|
+
- Keep the key platform in the application's Model, as `KeyHint.KeyPlatform` (start with `"Other"`, so the server and the browser render the same), and set it once from `init` with one Command that runs `KeyHint.detectKeyPlatform`. Every KeyHint, Button `keyHint` and KeyHelp reads it from there.
|
|
1183
|
+
- Render the Announcer and the toast stack by the hosting rule above, the Tips layer once, and one KeyHelp per page.
|
|
1184
|
+
- Route fragment links (a SkipLink, an ErrorSummary's links) through the application's URL handler, which runs `SkipLink.focusFragment` for a `#fragment`.
|
|
1185
|
+
|
|
1186
|
+
The peers in `package.json` are pinned to the Foldkit line pixel-paper is built and tested against. Foldkit is pre-1.0, so a caret range admits patches only; a new Foldkit minor is a new pixel-paper release.
|
|
1187
|
+
|
|
1188
|
+
Without Foldkit: link `pixel-paper.css` and write the markup named in each component above. The stylesheet relies on these attributes: `aria-current` (`="page"` on a nav segment, a crumb, a menu link and a place's button; `="true"` on `.row` and `.board-card` for the cursor, given `tabindex="0"` while the others take `-1`, on the open file and on a history's current entry), `aria-selected`, `aria-pressed`, `aria-checked`, `aria-expanded`, `aria-disabled` (on buttons, on a board card's `.title`, on menu items, on a select-all box), `aria-invalid`, `disabled` and `aria-busy` on the Fields `fieldset` while a save is in flight, `aria-hidden="true"` on a button's or menu item's `kbd.key-hint`, `data-label` on stacked-table cells, `data-drag-over` on a drop zone while a file hovers, `data-tip` on glyph-only controls (the words the page's one tip shows), native `<dialog>` for overlays and native `[popover]` for the rail's drawer and tips. Behaviour the components own (board moves, a menu's placement and arrow keys, a context menu's placement and focus, tips, a toolbar's roving focus, the palette cursor, tab arrows, focus into dialogs, an error summary's focus, the flash, the remembered palette, the toast stack's hold and focus, a player's controls, the key help) is the adopter's to write.
|
|
1189
|
+
|
|
1190
|
+
Palette: with no `data-scheme` the page follows `prefers-color-scheme`, live, in CSS alone. `Scheme.SchemeSwitch` is a Submodel with no service to provide: choosing a scheme returns its `PinScheme` Command, which sets or clears `data-scheme` on `<html>` (`"light"` for paper, `"dark"` for plum) and remembers the choice in `localStorage` (storage that refuses still switches the page; the choice is just not kept); `SchemeSwitch.select` does the same from outside the switch (a palette entry). `SchemeSwitch.init()` returns the switch on `"System"`, as the server renders it, with a `ReadScheme` Command the client runs after hydration, which reads the scheme `schemeScript` pinned before first paint; map its Commands into the application's `init`. Its Subscription follows `prefers-color-scheme` (Foldkit's `Dom.streamFromMediaQuery`), so the switch knows the palette `"System"` prints in. Read the choice with `SchemeSwitch.scheme` and the palette the page prints in, `"Paper"` or `"Plum"` (`Scheme.Palette`), with `SchemeSwitch.palette`: the pinned one, or under `"System"` the system's, followed as it changes (`"Paper"` until the browser reports, as the server renders). A scheme chosen in another tab is seen on that page's next load, not live. Put `schemeScript` inline in the page's `<head>` so a remembered palette applies before first paint. A subtree with `color-scheme: dark` prints plum colours inside a paper page (not the dither), which is how the specimen shows both palettes side by side.
|
|
1191
|
+
|
|
1192
|
+
## Building components
|
|
1193
|
+
|
|
1194
|
+
How a component in this package is made. The components follow these rules, and a new one follows them too; a review finding against one of them is a defect, not a style note.
|
|
1195
|
+
|
|
1196
|
+
**The two kinds.** A **View** is a stateless render helper, `X.view(config, h)`: the caller owns any state and passes its Messages in the config. Copy `src/button/`, `src/notice/` or `src/card/` (a container with slots). A **Submodel** has a Model, Message, OutMessage, `init`, `update` and a `view` made with `defineView`, embedded with `h.submodel`. Build it over the matching `@foldkit/ui` component when there is one. Copy `src/dialog/` (over a `@foldkit/ui` component, with a Mount that reads real key events), `src/confirmDialog/` (over another Submodel, with OutMessages) or `src/keyHelp/` (with its own Subscription). Clocks come from the Model (`Time.clockEntry`), keys from `Dom.streamFromKeyBindings`, and browser services through an Effect service the application provides as a Layer in its `resources` (CopyButton's `Clipboard`, from `@effect/platform-browser`); a Command that only touches the page it runs in does so directly, inside its Effect (SchemeSwitch's `PinScheme`).
|
|
1197
|
+
|
|
1198
|
+
Each component is `src/<name>/<name>.ts` with an explicit `index.ts`, a colocated `<name>.test.ts` (a Story for update, a Scene for view), an `export * as` line in `src/index.ts`, a section with an example file on the specimen's components page (`site/src/example/`, and its name in `site/src/covers.ts`, which a test holds against `src/index.ts`), and an entry in this document. The `index.ts` is the namespace's public surface and nothing more: `view` and its config types, and for a Submodel its Model, Message, `init`, `update`, Commands, Mounts and accessors. Only `src/index.ts` imports it: inside `src/` a file, a test included, imports the file that defines what it needs (`../button/button.ts`, `../glyph/glyphs.ts`). What several components share and no application imports has a folder with no index: `src/attributes/` (the `Attributes` type), `src/slot/` (`slotView`, an optional slot in its one element), `src/locale/`, `src/keys/`, `src/subscription/`. `src/focus/` has one: its `Focus` Command, which focuses an element by id, is a namespace of its own for the application's focus moves, with the `Message` it reports for code that returns it without a union of its own, and each Submodel that returns it re-exports it too, so a parent's test resolves it by the child's name.
|
|
1199
|
+
|
|
1200
|
+
1. **Disabled means `@foldkit/ui`'s disabled.** `isDisabled` renders `aria-disabled`, keeps the control focusable and drops its handler. There is no separate "unavailable". A held control (busy, done, disabled) that would submit a form renders `type="button"`; a form's update still ignores a submit while its work is saving, because Enter in a field can submit anyway.
|
|
1201
|
+
2. **Native disabled.** The media seek range is the one native control taken out of the keyboard (`isNativeDisabled`, native `disabled`, while its source is broken), and the Fields `fieldset` the one group (while its save is in flight). Every other unavailable control is rule 1's.
|
|
1202
|
+
3. **No `preventDefault` before the target is known.** A view handler such as `OnKeyDownPreventDefault` prevents the default for every key it maps, wherever the key came from. Key handling that depends on the event's target, `isComposing` or `defaultPrevented` goes in a Mount (`Mount.defineStream`) that reads the real event and prevents the default only when it acts (see `Dialog.BindActionKeys`).
|
|
1203
|
+
4. **Attributes, never a class.** A view takes `attributes?`, or a named part's `<part>Attributes?` (`titleAttributes`, `inputAttributes`, `closeAttributes`), where a caller passes them: something in `src/` or the specimen, an application that adopts a release, or a composite forwarding its own. A field nothing sets is deleted. They go on the root element unless the TSDoc names another, and apply in every branch the view can render. A view's classes are its own, plain strings, and there is no `className`. Passthrough attributes never carry a class (a later `h.Class` replaces the earlier one), and the view spreads them before its own, so its classes and ids win.
|
|
1204
|
+
5. **One vocabulary.** `label` is a control's words (a button's, a field's, a group's name, a dialog's name when no heading says it). `title` is a heading (a card's bar, a notice's or an outcome's caps line). `body` is content. A ConfirmDialog's `title` is its bar, `question` its Outcome's title, `label` its confirming button. Booleans read as questions (`isBusy`, `isOpen`, `hasRetry`) and literal choices are capitalised (`"Primary"`, `"Warn"`). What a Model, a Message or a Command's args may lack is an `Option` field named `maybeX` (`maybeError`, `maybeLastSavedId`), and an accessor that returns one keeps the prefix (`InlineInput.maybeError`, `CommandPalette.maybeCursorId`); optional view config is a plain `?:` field under its own name (`help?`, `full?`).
|
|
1205
|
+
6. **Slots.** A slot is a plain value unless it may carry the application's handlers; then it is a `to<Slot>View` function. In a View's config a content slot (`body`, `detail`, `actions`, `lead`) is a value, `ReadonlyArray<Html | string>`. In a Submodel's view inputs, a markup slot that may carry handlers is a top-level function, `toXView: () => Html` (`toPanelView`, `toAsideView`, `toDetailView`, `toLiveRegionView`, Dialog's `toView`, which receives its attribute groups), never nested Html carrying the parent's handlers, because a Submodel's view runs in its own Message boundary (see Foldkit's `html` and `submodel` modules).
|
|
1206
|
+
7. **Parents never read a child's Model.** A Submodel exports an accessor for what a parent reads (`Dialog.id`, `Dialog.isOpen`, `ConfirmDialog.isOpen`, `KeyHelp.id`, `SchemeSwitch.scheme`), and the parent calls it, including for `slotId`. An accessor nothing outside its own file and test uses is not exported. No field is kept twice.
|
|
1207
|
+
8. **Instances are scoped by id.** The DOM ids and hooks of a reusable Submodel are scoped by its instance id (a board's columns are `${boardId}/${columnId}`), so two instances can share a page. Its Subscription keys are scoped too: `Subscription.lift` keeps a child's keys and `Subscription.aggregate` throws on a key two records share, so a Submodel whose Subscriptions could run twice on one page exports them as a factory taking a key prefix from the parent, `subscriptions(keyPrefix)` (Board, Tips, CommandPalette), which puts every key behind it (`Board.subscriptions("queueBoard")` is keyed `queueBoardPointer`…). The parent never renames a child's keys, and gates a whole lifted record with `when` rather than listing its keys. A Submodel that is one per page by nature (SchemeSwitch, KeyHelp) keeps fixed keys named for it (`schemeSwitchSystemPalette`, `helpKey`).
|
|
1208
|
+
9. **A Scene test for every accessibility contract** a component adds beyond `@foldkit/ui`: roles, accessible names, `aria-pressed`, `aria-describedby`, `aria-invalid`, held and disabled states, alerts. Behaviour that only a real event shows (a Mount's key handling) gets a test with real DOM events in happy-dom, no mocks.
|
|
1209
|
+
10. **Toggles.** `aria-pressed` comes only from `Button.pressedAttribute(isPressed, h)`, present in both states so the control reads as a toggle.
|
|
1210
|
+
11. **Naming code.** Never put `aria-label` on `code` or `pre` alone; give them `role="group"` and a name.
|
|
1211
|
+
12. **Live regions follow the topmost open dialog.** An open Dialog makes the page outside it inert and hidden from screen readers. Anything that must be heard or reached while it is open (the Announcer's live region, the toast stack) renders inside the topmost open dialog, through its `toLiveRegionView: () => Html` (rendered inside the `<dialog>`, outside the card), and in the body otherwise. Every dialog-like Submodel (ConfirmDialog, KeyHelp, CommandPalette) passes `toLiveRegionView` through. A Menu does not trap focus, so it hosts nothing. The application decides which host from its Model (the specimen's `liveRegionHost` in `site/src/main.ts`). A `role="status"` element stays in the DOM with only its contents swapped; it is never mounted already holding its message, which would go unheard.
|
|
1212
|
+
13. **The key platform lives in the application's Model** (`KeyHint.KeyPlatform`, starting `"Other"` so server and browser render alike), set once by one init Command that runs `KeyHint.detectKeyPlatform`. Components take `platform` as an input; none detects it.
|
|
1213
|
+
14. **Effects, not Commands, for page-wide side effects.** A library effect the application triggers (`Announcer.announce`, `Announcer.flash`) is an `Effect` the application runs from its own Command, as Foldkit's `Dom` Effects are, so the result is the application's own `Completed*` Message.
|
|
1214
|
+
15. **A Mount reports through the parent.** A View that owns a Mount takes a required `toParentMessage: (message: X.Message) => ParentMessage` (TopBar, StatusBar, Toolbar, the mermaid adapter), and a fire-and-forget Mount maps to a caller `Completed*` Message the parent may ignore. A bare `onX: Message` is used only where the parent's Model acts on the fact (ImageFrame's `onBroken`). In a Scene, resolve the Mount by name with the lifted Message: `Scene.Mount.resolve({ name: X.Mount.name }, toParentMessage(...))`.
|
|
1215
|
+
16. **Fragment links focus through the application.** A link to `#id` (SkipLink, an ErrorSummary's links) needs the application's URL handler to run `SkipLink.focusFragment` for the fragment, since Foldkit routes the click.
|
|
1216
|
+
17. **Platform services are the application's.** A component that needs a platform service says so in its TSDoc and here (CopyButton writes through Effect's `Clipboard`), and the application provides the Layer in its `resources`.
|
|
1217
|
+
18. **Upstream gaps.** Where `@foldkit/ui` lacks a capability, the workaround carries `// NOTE: @foldkit/ui gap: <what>` at its site, and that note is the record: there is no list beside it. A wrapper that must build a child Message itself says so in the same note, with a lint disable on that line alone.
|
|
1218
|
+
|
|
1219
|
+
## Do's and Don'ts
|
|
1220
|
+
|
|
1221
|
+
### Do:
|
|
1222
|
+
|
|
1223
|
+
- **Do** close every container with a 2px Printer Ink border and divide inside it with 1px Rule Grey; give it a solid offset shadow of 8, 5, 3 or 2px by its class.
|
|
1224
|
+
- **Do** set every control in Departure Mono 400, uppercase, tracked 0.04em to 0.1em by register, and every authored string in the system sans in sentence case.
|
|
1225
|
+
- **Do** pair every amber, green, red and head fill with its `on-status`, `on-red` or `on-head` token, and write a new palette difference as a `light-dark()` token, not a selector.
|
|
1226
|
+
- **Do** draw every icon with Glyph from the 12×12 inventory in `src/glyph/glyphs.ts`, and add new glyphs there as cells; name a glyph-only control with its label, which becomes its tip.
|
|
1227
|
+
- **Do** show quantity as a row of Cells lit in ink; let the reading beside it carry the warning colour.
|
|
1228
|
+
- **Do** confirm every committed change with the two-step invert flash (and a FlashWord where a verb fits), and honour `prefers-reduced-motion` with the still inset ring and stopped cursors.
|
|
1229
|
+
- **Do** invert (ink fill, paper text) to show the current segment, item, file or cursor, including a board's cursor card (`aria-current="true"`), which is neither the ring nor a selection's check cell; draw a list's cursor row instead, with a 1px muted line inside it and no fill (2px while the row has focus); use head tint for hover; keep the keyboard ring 2px ink offset 2px, except on a row, whose ring is its own inset line.
|
|
1230
|
+
- **Do** keep the dither on the ground and the veil only, and let the page scroll rather than a panel.
|
|
1231
|
+
- **Do** use the reading scale (Sheet and Prose: 15px / 1.65 at 68ch, headings at 650) only for authored pages.
|
|
1232
|
+
- **Do** put destructive commits behind a ConfirmDialog, the one place a red button appears.
|
|
1233
|
+
- **Do** say an outcome by how long it matters: a FlashWord beside the control, a toast for an outcome the reader may not be watching, a Banner for a state the whole interface is in.
|
|
1234
|
+
- **Do** give every target at least a 24px square, drawn or invisible.
|
|
1235
|
+
- **Do** space components with Stack, Group and Wrap on `--space-s`, `--space-m` and `--space-l`.
|
|
1236
|
+
- **Do** give every key command one binding and one `KeyHelp.Binding` beside it, so the key, its line in KeyHelp and its hint beside a button are one thing; one key gives one command, and a key that begins a sequence is never also a command alone.
|
|
1237
|
+
- **Do** give a list or a board one cursor, kept in the application's Model and moved by its key bindings (j, k, `g g`, G, h, l, Enter and the arrows), with the commands on the current item bound beside it; what an edit means is the application's.
|
|
1238
|
+
- **Do** name a working screen with one PageHead above its panels.
|
|
1239
|
+
- **Do** use only values from `tokens.css` in anything built on the system.
|
|
1240
|
+
|
|
1241
|
+
### Don't:
|
|
1242
|
+
|
|
1243
|
+
- **Don't** round a corner, blur a shadow, or tint a shadow with anything other than Shadow (or Danger Shadow on a danger button).
|
|
1244
|
+
- **Don't** use amber, green or red for decoration, and don't put an amber or green glyph on paper without its fill.
|
|
1245
|
+
- **Don't** let a content ink (`--term-*`, `--code-*`) out of code, terminals and logs: not on a chip, a border, a state word or a person.
|
|
1246
|
+
- **Don't** colour a meter's cells by level, or highlight a search hit in amber; the warning lives in the reading and a hit is a selection.
|
|
1247
|
+
- **Don't** chart in hues, curves, diagonals or filled areas, or colour a stat's change by its direction; cells and stepped lines stay ink and only a toned reading carries colour.
|
|
1248
|
+
- **Don't** print figures as a row of raised cards each holding one big number; Stats are ruled cells in a flush panel that names what they measure.
|
|
1249
|
+
- **Don't** build an inline arm-then-confirm button ("delete" that turns into "sure?"); the one destructive confirm is a ConfirmDialog with `isDanger`.
|
|
1250
|
+
- **Don't** ask Departure Mono for a weight other than 400, use it for authored text or code, or set chrome in the sans.
|
|
1251
|
+
- **Don't** set a size off the ramp (30, 25, 22, 20, 18, 16, 15, 14, 13, 12, 11, 10) or a fractional pixel size.
|
|
1252
|
+
- **Don't** use a stroke icon, an icon font, an emoji, or any glyph not drawn on the 12×12 cell grid.
|
|
1253
|
+
- **Don't** ease or tween anything; motion is stepped (`steps()`) or instant, and nothing animates on load except the four-beat wordmark cursor, the working cursor of something busy, and the three-step dialog and toast entrances.
|
|
1254
|
+
- **Don't** let the pointer move a keyboard cursor (the palette's rows only shade on hover), tint a cursor row or card on hover (other rows still tint), or let hover reflow a row (tools dim, they do not hide, above 600px).
|
|
1255
|
+
- **Don't** write a plum-only style on a component.
|
|
1256
|
+
- **Don't** put a label above a PageHead's title (a section word, a kicker, an eyebrow); the name and the count beside it say where the reader is.
|
|
1257
|
+
- **Don't** listen for keys outside the application's key bindings (a component's own keys live in a Mount that reads the real event), give a key meaning while the reader types (only `Mod+` keys act from a field), or make a key hint a control's name.
|
|
1258
|
+
- **Don't** put a product's routing, storage or data inside the system; those belong to the product that adopts it.
|