@yanolja-next/design-core 0.0.1 → 0.1.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/AGENTS.md +37 -0
- package/README.md +145 -1
- package/RULES.md +470 -0
- package/catalog.json +1345 -0
- package/dist/catalog.d.ts +11 -0
- package/dist/components/button/ActionRow.d.ts +2 -0
- package/dist/components/button/Button.d.ts +20 -0
- package/dist/components/button/ButtonLink.d.ts +9 -0
- package/dist/components/button/CopyBox.d.ts +7 -0
- package/dist/components/button/CopyButton.d.ts +6 -0
- package/dist/components/button/CopyText.d.ts +10 -0
- package/dist/components/button/ExpandToggle.d.ts +8 -0
- package/dist/components/button/GoLink.d.ts +11 -0
- package/dist/components/button/TextButton.d.ts +3 -0
- package/dist/components/button/ToolButton.d.ts +7 -0
- package/dist/components/button/index.d.ts +19 -0
- package/dist/components/data/card/Card.d.ts +31 -0
- package/dist/components/data/card/index.d.ts +2 -0
- package/dist/components/data/change-group/ChangeGroup.d.ts +10 -0
- package/dist/components/data/change-group/MarkList.d.ts +12 -0
- package/dist/components/data/change-group/index.d.ts +4 -0
- package/dist/components/data/clip-text/ClipText.d.ts +6 -0
- package/dist/components/data/clip-text/index.d.ts +2 -0
- package/dist/components/data/collection-filter/CollectionFilter.d.ts +19 -0
- package/dist/components/data/collection-filter/ToolbarDivider.d.ts +2 -0
- package/dist/components/data/collection-filter/index.d.ts +3 -0
- package/dist/components/data/container/Container.d.ts +26 -0
- package/dist/components/data/container/SectionStack.d.ts +2 -0
- package/dist/components/data/container/index.d.ts +3 -0
- package/dist/components/data/disclosure/Disclosure.d.ts +10 -0
- package/dist/components/data/disclosure/index.d.ts +2 -0
- package/dist/components/data/divided/Divided.d.ts +2 -0
- package/dist/components/data/divided/index.d.ts +1 -0
- package/dist/components/data/empty-state/EmptyState.d.ts +16 -0
- package/dist/components/data/empty-state/index.d.ts +2 -0
- package/dist/components/data/facts/Facts.d.ts +11 -0
- package/dist/components/data/facts/index.d.ts +2 -0
- package/dist/components/data/fail-state/FailLine.d.ts +5 -0
- package/dist/components/data/fail-state/FailState.d.ts +11 -0
- package/dist/components/data/fail-state/index.d.ts +4 -0
- package/dist/components/data/figures/Figures.d.ts +30 -0
- package/dist/components/data/figures/index.d.ts +2 -0
- package/dist/components/data/index.d.ts +38 -0
- package/dist/components/data/info-panel/InfoPanel.d.ts +22 -0
- package/dist/components/data/info-panel/index.d.ts +2 -0
- package/dist/components/data/inline-list/InlineList.d.ts +5 -0
- package/dist/components/data/inline-list/index.d.ts +2 -0
- package/dist/components/data/key-value/KeyValue.d.ts +7 -0
- package/dist/components/data/key-value/KeyValueColumns.d.ts +10 -0
- package/dist/components/data/key-value/index.d.ts +4 -0
- package/dist/components/data/lines/Lines.d.ts +6 -0
- package/dist/components/data/lines/index.d.ts +2 -0
- package/dist/components/data/link-card/LinkCard.d.ts +22 -0
- package/dist/components/data/link-card/index.d.ts +2 -0
- package/dist/components/data/machine-text/MachineText.d.ts +6 -0
- package/dist/components/data/machine-text/index.d.ts +2 -0
- package/dist/components/data/meta/Meta.d.ts +7 -0
- package/dist/components/data/meta/index.d.ts +2 -0
- package/dist/components/data/metric-strip/MetricStrip.d.ts +17 -0
- package/dist/components/data/metric-strip/index.d.ts +2 -0
- package/dist/components/data/more-text/MoreText.d.ts +8 -0
- package/dist/components/data/more-text/index.d.ts +2 -0
- package/dist/components/data/name-text/NameText.d.ts +4 -0
- package/dist/components/data/name-text/index.d.ts +2 -0
- package/dist/components/data/note/Note.d.ts +7 -0
- package/dist/components/data/note/index.d.ts +2 -0
- package/dist/components/data/page-foot/PageFoot.d.ts +3 -0
- package/dist/components/data/page-foot/index.d.ts +1 -0
- package/dist/components/data/pagination/Pagination.d.ts +10 -0
- package/dist/components/data/pagination/index.d.ts +2 -0
- package/dist/components/data/pick-list/PickList.d.ts +24 -0
- package/dist/components/data/pick-list/index.d.ts +2 -0
- package/dist/components/data/prose/PlainText.d.ts +3 -0
- package/dist/components/data/prose/Prose.d.ts +3 -0
- package/dist/components/data/prose/index.d.ts +4 -0
- package/dist/components/data/rail/Rail.d.ts +30 -0
- package/dist/components/data/rail/index.d.ts +1 -0
- package/dist/components/data/skeleton/Skeleton.d.ts +48 -0
- package/dist/components/data/skeleton/index.d.ts +2 -0
- package/dist/components/data/skeleton/placeholder.d.ts +13 -0
- package/dist/components/data/split-panes/SplitPanes.d.ts +3 -0
- package/dist/components/data/split-panes/index.d.ts +2 -0
- package/dist/components/data/steps/Steps.d.ts +10 -0
- package/dist/components/data/steps/index.d.ts +2 -0
- package/dist/components/data/sub-head/SubHead.d.ts +6 -0
- package/dist/components/data/sub-head/index.d.ts +2 -0
- package/dist/components/data/table/Table.d.ts +95 -0
- package/dist/components/data/table/TableClip.d.ts +6 -0
- package/dist/components/data/table/TableMore.d.ts +2 -0
- package/dist/components/data/table/TableSkeleton.d.ts +20 -0
- package/dist/components/data/table/TableSubLine.d.ts +8 -0
- package/dist/components/data/table/index.d.ts +9 -0
- package/dist/components/data/tabs/Tabs.d.ts +14 -0
- package/dist/components/data/tabs/index.d.ts +2 -0
- package/dist/components/data/term-list/TermList.d.ts +10 -0
- package/dist/components/data/term-list/index.d.ts +2 -0
- package/dist/components/data/url-text/UrlText.d.ts +11 -0
- package/dist/components/data/url-text/index.d.ts +2 -0
- package/dist/components/data/usage-bar/UsageBar.d.ts +19 -0
- package/dist/components/data/usage-bar/index.d.ts +2 -0
- package/dist/components/data/veiled/Veiled.d.ts +6 -0
- package/dist/components/data/veiled/index.d.ts +2 -0
- package/dist/components/data/warn-line/WarnLine.d.ts +6 -0
- package/dist/components/data/warn-line/index.d.ts +2 -0
- package/dist/components/forms/badge/Badge.d.ts +10 -0
- package/dist/components/forms/badge/index.d.ts +2 -0
- package/dist/components/forms/checkbox/Checkbox.d.ts +18 -0
- package/dist/components/forms/checkbox/index.d.ts +2 -0
- package/dist/components/forms/chip/Chip.d.ts +14 -0
- package/dist/components/forms/chip/ChipList.d.ts +2 -0
- package/dist/components/forms/chip/index.d.ts +3 -0
- package/dist/components/forms/code-input/CodeInput.d.ts +8 -0
- package/dist/components/forms/code-input/index.d.ts +2 -0
- package/dist/components/forms/combobox/Combobox.d.ts +28 -0
- package/dist/components/forms/combobox/index.d.ts +2 -0
- package/dist/components/forms/control.d.ts +6 -0
- package/dist/components/forms/field/Field.d.ts +32 -0
- package/dist/components/forms/field/index.d.ts +2 -0
- package/dist/components/forms/field-group/FieldGroup.d.ts +6 -0
- package/dist/components/forms/field-group/index.d.ts +2 -0
- package/dist/components/forms/form/Form.d.ts +2 -0
- package/dist/components/forms/form/index.d.ts +1 -0
- package/dist/components/forms/form-actions/FormActions.d.ts +5 -0
- package/dist/components/forms/form-actions/index.d.ts +2 -0
- package/dist/components/forms/index.d.ts +16 -0
- package/dist/components/forms/input/Input.d.ts +12 -0
- package/dist/components/forms/input/index.d.ts +2 -0
- package/dist/components/forms/radio/Radio.d.ts +10 -0
- package/dist/components/forms/radio/index.d.ts +2 -0
- package/dist/components/forms/segmented/Segmented.d.ts +20 -0
- package/dist/components/forms/segmented/index.d.ts +2 -0
- package/dist/components/forms/select/Select.d.ts +30 -0
- package/dist/components/forms/select/SelectText.d.ts +2 -0
- package/dist/components/forms/select/index.d.ts +3 -0
- package/dist/components/forms/status-indicator/StatusIndicator.d.ts +12 -0
- package/dist/components/forms/status-indicator/index.d.ts +2 -0
- package/dist/components/forms/switch/Switch.d.ts +7 -0
- package/dist/components/forms/switch/index.d.ts +2 -0
- package/dist/components/forms/textarea/Textarea.d.ts +5 -0
- package/dist/components/forms/textarea/index.d.ts +2 -0
- package/dist/components/icon/Icon.d.ts +10 -0
- package/dist/components/icon/SwapIcon.d.ts +2 -0
- package/dist/components/icon/icons.d.ts +286 -0
- package/dist/components/icon/index.d.ts +3 -0
- package/dist/components/link/Link.d.ts +12 -0
- package/dist/components/link/TextLink.d.ts +8 -0
- package/dist/components/link/index.d.ts +4 -0
- package/dist/components/overlay/index.d.ts +6 -0
- package/dist/components/overlay/menu/Menu.d.ts +36 -0
- package/dist/components/overlay/menu/index.d.ts +2 -0
- package/dist/components/overlay/modal/ConfirmModal.d.ts +15 -0
- package/dist/components/overlay/modal/Modal.d.ts +34 -0
- package/dist/components/overlay/modal/PanelHeader.d.ts +32 -0
- package/dist/components/overlay/modal/index.d.ts +5 -0
- package/dist/components/overlay/popover/Popover.d.ts +23 -0
- package/dist/components/overlay/popover/index.d.ts +2 -0
- package/dist/components/overlay/sheet/Sheet.d.ts +31 -0
- package/dist/components/overlay/sheet/index.d.ts +2 -0
- package/dist/components/overlay/toast/Toast.d.ts +20 -0
- package/dist/components/overlay/toast/index.d.ts +2 -0
- package/dist/components/overlay/tooltip/InfoTip.d.ts +8 -0
- package/dist/components/overlay/tooltip/TipText.d.ts +6 -0
- package/dist/components/overlay/tooltip/Tooltip.d.ts +14 -0
- package/dist/components/overlay/tooltip/index.d.ts +6 -0
- package/dist/components/reason/LockedPreview.d.ts +2 -0
- package/dist/components/reason/WithReason.d.ts +10 -0
- package/dist/components/reason/index.d.ts +3 -0
- package/dist/components/shell/app-shell/AppShell.d.ts +7 -0
- package/dist/components/shell/app-shell/index.d.ts +4 -0
- package/dist/components/shell/app-shell/sideNav.d.ts +12 -0
- package/dist/components/shell/drawer/Drawer.d.ts +36 -0
- package/dist/components/shell/drawer/index.d.ts +2 -0
- package/dist/components/shell/help-panel/HelpPanel.d.ts +33 -0
- package/dist/components/shell/help-panel/index.d.ts +2 -0
- package/dist/components/shell/index.d.ts +12 -0
- package/dist/components/shell/match/Keycap.d.ts +2 -0
- package/dist/components/shell/match/MatchText.d.ts +4 -0
- package/dist/components/shell/page-head/HeadMore.d.ts +15 -0
- package/dist/components/shell/page-head/PageHead.d.ts +39 -0
- package/dist/components/shell/page-head/index.d.ts +6 -0
- package/dist/components/shell/page-head/pagePath.d.ts +10 -0
- package/dist/components/shell/rank.d.ts +1 -0
- package/dist/components/shell/sidebar/Sidebar.d.ts +40 -0
- package/dist/components/shell/sidebar/index.d.ts +2 -0
- package/dist/components/shell/sign-in-card/SignInCard.d.ts +29 -0
- package/dist/components/shell/sign-in-card/index.d.ts +2 -0
- package/dist/components/shell/slash.d.ts +3 -0
- package/dist/components/shell/standalone-panel/StandalonePanel.d.ts +6 -0
- package/dist/components/shell/standalone-panel/index.d.ts +2 -0
- package/dist/components/shell/topbar/Topbar.d.ts +76 -0
- package/dist/components/shell/topbar/index.d.ts +2 -0
- package/dist/components/viz/chart/BarChart.d.ts +6 -0
- package/dist/components/viz/chart/ChartFrame.d.ts +34 -0
- package/dist/components/viz/chart/ChartHead.d.ts +9 -0
- package/dist/components/viz/chart/ChartLegend.d.ts +17 -0
- package/dist/components/viz/chart/ChartSkeleton.d.ts +6 -0
- package/dist/components/viz/chart/LineChart.d.ts +6 -0
- package/dist/components/viz/chart/Sparkline.d.ts +14 -0
- package/dist/components/viz/chart/TimeSeriesChart.d.ts +7 -0
- package/dist/components/viz/chart/bubble.d.ts +1 -0
- package/dist/components/viz/chart/index.d.ts +16 -0
- package/dist/components/viz/chart/layout.d.ts +212 -0
- package/dist/components/viz/chart/timeSeries.d.ts +44 -0
- package/dist/components/viz/code-block/CodeBlock.d.ts +18 -0
- package/dist/components/viz/code-block/index.d.ts +2 -0
- package/dist/components/viz/index.d.ts +2 -0
- package/dist/i18n/en.d.ts +88 -0
- package/dist/i18n/index.d.ts +15 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +10142 -0
- package/dist/lib/afterPress.d.ts +1 -0
- package/dist/lib/arrival.d.ts +3 -0
- package/dist/lib/cn.d.ts +2 -0
- package/dist/lib/copied.d.ts +5 -0
- package/dist/lib/floating.d.ts +5 -0
- package/dist/lib/focusAfter.d.ts +2 -0
- package/dist/lib/hit.d.ts +1 -0
- package/dist/lib/level.d.ts +1 -0
- package/dist/lib/media.d.ts +3 -0
- package/dist/lib/motion.d.ts +20 -0
- package/dist/lib/pageScroll.d.ts +6 -0
- package/dist/lib/scrollbar.d.ts +1 -0
- package/dist/styles.css +2 -0
- package/llms.txt +201 -0
- package/package.json +69 -5
- package/skill/SKILL.md +38 -0
package/RULES.md
ADDED
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
# Design rules: core
|
|
2
|
+
|
|
3
|
+
These are the rules of the Yanolja NEXT design core (`@yanolja-next/design-core`) for every product that uses it.
|
|
4
|
+
The values are tokens in `design-system/tokens` (`@yanolja-next/design-tokens`). The parts and their intent are in the catalog (`design-system/core/src/catalog.ts`).
|
|
5
|
+
A product adds its own theme, kit and screen rules in its own document. The NEXT Hub console has `console-ui/DESIGN.md`.
|
|
6
|
+
Code comments cite a rule as "DESIGN.md <name>" (E.g., "DESIGN.md P6"). The name is in this file or in `console-ui/DESIGN.md`.
|
|
7
|
+
|
|
8
|
+
## Colors
|
|
9
|
+
|
|
10
|
+
The values are tokens in `design-system/tokens`. This section says where each colour role goes. The console theme (`console-ui/DESIGN.md`, Design G2) adds the material of the sheet, the hero pane, the pills and the floating layer.
|
|
11
|
+
|
|
12
|
+
### Primary
|
|
13
|
+
- **Yanolja Orange, one tone** (#D44119, `--c-accent`): the fill of real primary actions (만들기, 실행/적용, 승인) and the mark of money actually spent (spent bar or line, today's cost, budget usage), and the bare head icon of a panel (Container, dashboard screens only; 4.25:1 on Panel Head), and the icon of the current sidebar item (2026-10-18). Its 45% tint rgba(212,65,25,.45) is the keyboard focus ring (`--c-focus`). Buttons and data marks use the same value so they read as one colour. It is the lightest tone of the brand hue where white 600 text passes 4.5:1 (4.6:1); #E0461A was checked and fails at 4.15:1. Hover and pressed go to #B5370F.
|
|
14
|
+
- **Brand Orange** (`--c-brand`, the logo orange #F54B1E): nothing uses it now (2026-10-18, the progress line is gone). Never text or a surface. There is no logo mark yet; the product is written as the words NEXT Hub.
|
|
15
|
+
|
|
16
|
+
### Neutral
|
|
17
|
+
Values are in `design-system/tokens/src/themes/light.json`. Roles:
|
|
18
|
+
- **Ink** (#111827, `--c-text`, 17.7:1 on white): primary text, headings, values. Links use the same ink (`--c-link`); a resource name reads as a link through weight 500.
|
|
19
|
+
- **Ink 2** (#4B5563, `--c-text-2`): secondary text and icon strokes in controls.
|
|
20
|
+
- **Ink 3** (#636A77, `--c-text-3`): labels, metadata, axis ticks. 4.7:1 on the frame, 5.2:1 on the sheet.
|
|
21
|
+
- **Ink 4** (#9CA3AF, `--c-text-4`): non-text marks only (dots, chevrons, disabled icons). Never text.
|
|
22
|
+
- **Nav text** (#374151, `--c-nav-text`) and **Top text** (#5B6370, `--c-top-text`): text on the frame (sidebar items, group labels, top bar icons).
|
|
23
|
+
- **Mark** (#1F2937, `--c-mark`): filled marks (toggle on, ink button, checkbox on). **Mark soft** (`--c-mark-soft`, ink 6%): count and recommendation badge fill.
|
|
24
|
+
- **Frame** (#ECEEF1, `--c-page`), **Sheet** (#FAFAFB, `--c-sheet-solid`), **Panel White** (#FFFFFF, `--c-panel`): the frame, the one sheet on it, and every floating thing, input and checkbox.
|
|
25
|
+
- **Sunken** (#F0F1F2, `--c-sunken`): segmented well, disabled buttons, selected row. **Row hover** (#F6F7F8, `--c-row-hover`) and **Row on** (#EFF1F4, `--c-row-on`, the keyboard row in search and pickers).
|
|
26
|
+
- **Lines** (ink at 7%, 4.5% and 8%: `--c-line`, `--c-line-soft`, `--c-line-strong`): primary dividers and the line under a table head; table rows and list separators; control rings and chart baselines.
|
|
27
|
+
- **Scrim** (ink 14%, `--c-scrim`): behind modals, sheets and drawers.
|
|
28
|
+
|
|
29
|
+
### Chart series
|
|
30
|
+
**Palette D (2026-10-21, 상훈님 "D 괜찮은듯. 고고").** One token per rank, used by every chart, legend, sparkline and share bar, money or not. Comparison page: https://pages.hub.ynext.cloud/p/k80p4t0k/
|
|
31
|
+
|
|
32
|
+
| Rank | Token | Name | Hex | OKLCH | On white |
|
|
33
|
+
|---|---|---|---|---|---|
|
|
34
|
+
| 1 | `--c-series-1` | Ink | #1F2937 | 27.8% 0.030 257 | 14.7:1 |
|
|
35
|
+
| 2 | `--c-series-2` | Blue | #3069B2 | 52.0% 0.130 256 | 5.5:1 |
|
|
36
|
+
| 3 | `--c-series-3` | Light Blue | #4B9BCF | 66.1% 0.110 240 | 3.05:1 |
|
|
37
|
+
| 4 | `--c-series-4` | Periwinkle | #6579DF | 61.0% 0.155 273 | 3.9:1 |
|
|
38
|
+
| 5 | `--c-series-5` | Slate | #77818C | 59.9% 0.021 251 | 4.0:1 |
|
|
39
|
+
| 6 | `--c-series-6` | Steel | #346067 | 46.0% 0.050 209 | 7.0:1 |
|
|
40
|
+
| rest | `--c-series-rest` | 그 외 grey | #D4D8DD | 88.1% 0.008 254 | 1.4:1 |
|
|
41
|
+
|
|
42
|
+
- **Order rules.** Rank by the measured value of the view (cost on cost views). Rank 1 is ink, then blue, light blue and periwinkle. A lone series is always rank 1. The default is four ranks plus one grey 그 외 series. Use ranks 5 and 6 only when a chart explicitly needs them. Series colours are marks only: never text (rank 3 is just 3:1), never a tint of another rank (no ink 55% or 30%).
|
|
43
|
+
- **Why.** The pairing adapts the Yanolja Research charts: orange against a family of cool blues. Orange is reserved for spend, so a series is never orange and a blue bar never reads as money. Every rank after 1 is at least 0.15 lighter than ink in OKLab L, so rank 1 never reads as another dark mark. Rank 4 was navy #403C85 until 2026-10-21: at L 0.40 it looked the same as ink in bars and 8px legend chips. The old rule that neighbours alternate lightness by 1.5:1 forced rank 4 dark, so it was replaced by a measured distance rule (below).
|
|
44
|
+
- **CVD.** Hue differences inside the blue family collapse for some colour vision deficiencies, and lightness alone separates about four marks. So charts fold rank 5 and beyond into 그 외 by default. Every two ranks (1 to 6 and rest) keep an OKLab x100 distance of 6.5 or more in normal vision and simulated deuteranopia, protanopia and tritanopia (Machado 2009, severity 1.0). The closest pair is rank 3 and rank 4 under tritanopia (6.9). Rank 4 and rank 5 have the same lightness and separate by hue and chroma only (7.6 or more). seriesColors.test.ts checks both rules.
|
|
45
|
+
- **Guards.** `design-system/core/src/components/viz/chart/seriesColors.test.ts` checks the order, 3:1 per rank, that no rank or the rest grey is orange, and that no chart source fades a series. `console-ui/src/chartGuard.spec.ts` fails on `color-mix` over a series token. The ranked swatches of a stacked share bar live in the design system `ShareBar`, and its test checks that each segment and its legend chip use the same series token.
|
|
46
|
+
- Legend chips use the same tokens and the same drawn shape: `chip-s1` to `chip-s6`, `chip-rest`, `chip-spent`, `line-r1` to `line-r6`, `dash-spent`, `band`.
|
|
47
|
+
- 대체됨: V11 (2026-10-16) and the service colour passes (2026-10-19) used ink for rank 1, orange #E5622F for rank 2 and ink at 55% and 30% for ranks 3 and 4. A second service then read as money.
|
|
48
|
+
- Forecasts are one band (`.fc-band`) or one dashed line (`.fc-line`), never hatched bars. The 45-degree hatch survives only on the budget runway.
|
|
49
|
+
- The column still in progress (today) is hollow: a 1.5 solid stroke in its own series colour over Panel White, each stacked segment in its own colour, and the legend says 오늘(진행 중) with the same hollow chip (`chip-partial`). It used to be a 40% tint, which turned the orange series into an apricot surface (banned). A dashed outline was rejected earlier.
|
|
50
|
+
- A chart whose values are all zero says so in place (`empty`, e.g. 이번 달 비용이 아직 없습니다) instead of drawing an empty grid.
|
|
51
|
+
|
|
52
|
+
### Tertiary
|
|
53
|
+
- **Signal Green** (#04773B): the ok state (StatusIndicator ok: 600 text on an OK Soft #D9F2E3 pill, 4.8:1), the live-project dot in the picker and a positive delta figure. The OK Soft pill is one of the two pale fills allowed, and only behind the ok state. The other is Danger Soft.
|
|
54
|
+
- **Block Crimson** (#A3124E, `--c-danger`, 7.6:1): blocking warnings and errors (예산 도달로 차단됨, 차단 예상, 이용 정지) at 600 with the icon in the same colour (the error state was 400 and read weaker than the ok pill until 2026-10-17), and the text and ring of irreversible actions. Hue 335, kept apart from the orange (hue 13). Never a surface. The one pale crimson surface is **Danger Soft** (#FAF3F6, `--c-danger-soft`, crimson 5% on Panel White, 7.0:1 under crimson text). It fills only the error explanation panel (Playground response error). Do not mix it by hand with `color-mix` (2026-10-26).
|
|
55
|
+
|
|
56
|
+
### Named Rules
|
|
57
|
+
**The Banned Marks Rule.** (2026-10-16 V11, replaces the Orange Places Rule) Orange is no longer limited to money. What stays banned: a logo mark (the product is the words NEXT Hub until a logo is designed), a coloured vertical bar on the left edge of anything, and any pale orange, beige, pink or apricot surface.
|
|
58
|
+
|
|
59
|
+
**The Closed Palette Rule.** Only the tokens above exist. No beige or yellow surfaces, no pastel chips or washes, no emoji. Gradients only where G2 puts them: the primary button, the skeleton pill and the frame bloom (2026-10-18); nothing else. The one patterned fill allowed is the 45-degree Hairline Strong hatch on the budget runway's projected segment; charts draw forecasts as a band or a dashed line.
|
|
60
|
+
|
|
61
|
+
## Typography
|
|
62
|
+
|
|
63
|
+
**Display Font:** Pretendard Variable (then -apple-system)
|
|
64
|
+
**Body Font:** Pretendard Variable, same stack, numerals included, tabular figures (`tnum`) everywhere. Korean text sets `word-break: keep-all`.
|
|
65
|
+
**Label/Mono Font:** JetBrains Mono (with ui-monospace, SF Mono), machine identifiers only
|
|
66
|
+
|
|
67
|
+
**Character:** One neutral grotesque for everything human-readable, slightly tightened (-0.01em on text, -0.02em on titles and large figures, -0.03em on the 40 amount) with tabular figures on every number, so columns of money and counts line up like instrument readouts. Mono appears only where a machine wrote the string.
|
|
68
|
+
|
|
69
|
+
### Hierarchy
|
|
70
|
+
Token names equal the rendered px (2026-10-21 rename). Tracking is a token per size (`--ls-12` ... `--ls-40`), in em so each element computes it from its own size. What inherits is the token (`--ls-cur`, default `--ls-13`), never a px value; any rule that changes font-size also sets `--ls-cur`. Weights are 400/500/600 only; `b` and `strong` are 600, 700 is never used.
|
|
71
|
+
|
|
72
|
+
| Size | Line | Role | Tokens |
|
|
73
|
+
|---|---|---|---|
|
|
74
|
+
| 40 | 44 | The one hero amount of a screen (dashboard month cost, budget). 400, "$" at 0.62em | `t-40`, `lh-44` |
|
|
75
|
+
| 24 | 28 | Page title (h1). 500 | `t-24`, `lh-28` |
|
|
76
|
+
| 20 | 26 | Display: the hero figure of a strip or figure group | `t-20`, `lh-26` |
|
|
77
|
+
| 18 | 26 | Phone page title, sheet title, lead figure (`.big`) | `t-18`, `lh-26` |
|
|
78
|
+
| 14 | 22 | Title: panel (Container) and card titles, secondary figures. 500 to 600 | `t-14`, `lh-22` |
|
|
79
|
+
| 13 | 18 (facts 20, reading 22) | Body: UI text, menus, buttons, table cells, key-value rows, reading text. 20 for a fact line under a verdict or a quiet note | `t-13`, `lh-18`, `lh-20`, `lh-22` |
|
|
80
|
+
| 12 | 14 to 18 | Label and caption: field labels, meta, table heads, chart axes and legends, badges. 16 for a label that wraps to two lines | `t-12`, `lh-14`, `lh-16`, `lh-18` |
|
|
81
|
+
|
|
82
|
+
### Named Rules
|
|
83
|
+
**The Seven Sizes Rule.** (2026-10-21) The scale is exactly 12, 13, 14, 18, 20, 24, 40, and 12 is the smallest text. A new size is a design change, not a local fix. A separate landing display scale (`display-16`, `display-32`, `display-44`, `display-56`, 2026-10-26) exists for the group "landing" parts only. App screens never use it.
|
|
84
|
+
|
|
85
|
+
**The Step Down Rule.** In a label and value pair the label is always the quieter one: label 13 Ink 2 400 (KPI, and the summary columns KeyValueColumns over a 14 Ink value, 2026-10-17; they had a 14 Ink 600 label over a 13 Ink 2 value) or 14 Ink 2 400 (key-value rows), value Ink (600 for a hero figure, 400 in a row), the note under a figure 13 Ink 3 400. A status inside a note uses the small StatusIndicator (13). Labels were ink 600 and read as loud as their values.
|
|
86
|
+
|
|
87
|
+
**The Numerals Rule.** Every number on a dashboard has tabular figures (`--f-num`, Pretendard since Design E; Inter 대체됨), including key-value values, counts and axis ticks; Korean falls through to Pretendard in the same stack. Counts carry a thousands comma (`1,284명`).
|
|
88
|
+
|
|
89
|
+
**The Machine Voice Rule.** JetBrains Mono is for strings a machine minted (keys, client IDs, endpoints, IDs) at 12px. Never project names, service names, headings, status sentences or progress text.
|
|
90
|
+
|
|
91
|
+
## Layout
|
|
92
|
+
|
|
93
|
+
The shell is a sticky 54px top bar (`topbar-h`) over a two-column grid: a 212px left navigation (`nav-w`; collapsed, the 50px icon rail `nav-rail`) and a fluid main column (padding 26 top, 28 left and right, 58 bottom). The top bar keeps the logo and the breadcrumb (org/project picker, earlier screens, the current screen) on the left, and the search, icon buttons and the account icon button on the right. Search placement (2026-10-20, 상훈님 option 3 of, Vercel/GitHub style): the search is a compact field fixed 200px wide just left of help and the account button, labelled 검색 with a ⌘K keycap (`/` and ⌘K both open the palette). The breadcrumb uses the whole left side up to 24px before the right group and truncates only when it truly lacks space (earlier screens first, then the scope chips; the last crumb is always visible). Phones keep the search icon. Why: the centred search pinned in the viewport (2026-10-18, 320px) took the breadcrumb's space and cut it early on every screen with earlier screens.
|
|
94
|
+
|
|
95
|
+
The main column holds one of three page heads (`.ph-list` title + count + actions, `.ph-detail` crumb + title + id + meta, `.ph-decision` title + status pill + requester line), then a 12-column board (the 할 일 attention strip that sat between them is 대체됨, removed 2026-10-21) with 16px gutters. Cards size to their content (`align-items: start`); equal heights are opt-in with `.stretch`.
|
|
96
|
+
|
|
97
|
+
Spacing uses the `space` tokens (4, 8, 10, 14, 18, 22, 26, 28, 32, 36, 40, 44, 50, 58 since Scale 0.9). Fixed heights are part of the system: nav rows 34 (`nav-row-h`, also the palette and picker rows), table rows 44 everywhere (`row-h`, with a second line and in dense tables too), controls 28 (`ctl-h`: buttons, segmented, inputs; `.sm` 24 inside tables only), panel head 36 (`head-h`). Card bodies pad 14 (`card-x`, `card-b`).
|
|
98
|
+
|
|
99
|
+
**The Shared Baseline Rule.** Any row that mixes type sizes (card head, key-value, table row, figure group, attention item) aligns on the text baseline, not the box centre. Only text-less items (icons, buttons, segmented controls, marks) centre.
|
|
100
|
+
|
|
101
|
+
**Icons.** Two sizes (16 UI, 14 inside buttons and links) and one 1.5 stroke, status icons included. Status icons differ by shape: info (circle i), warn (triangle !), pending (hourglass), scheduled (clock), done (circle check).
|
|
102
|
+
|
|
103
|
+
Responsive: below 1360px the AI module keeps its 204px fixed height; below 1180px span-4 to span-8 cards and the attention strip stack to full width; below 900px the nav, search, help and bell hide (the hamburger drawer is the only menu) and main padding drops to 16px. At 640px (phone, checked at 390) heads, card heads and meta lines wrap, tables scroll sideways inside their card (the page never scrolls sideways), key-value rows grow, modals become full-width bottom sheets and the right sheet goes full width. App CSS writes these widths as literals (`max-width: 640px` / `min-width: 641px`, 900, 1180, and 1199/1200 for the P1 rail): a CSS variable cannot stand in a media query and the build has no custom media, so the numbers must equal the DS `phone`, `tablet` and `laptop` variants. Each component's phone variant is shown live in `design/design-system.html`.
|
|
104
|
+
|
|
105
|
+
**The 2px Rule.** (Scale 0.9, 2026-10-18; replaces The 4px Rule of 2026-10-17) Every padding, margin, gap, fixed height and width token and line height is a multiple of 2 and comes from a token (`space`, `layout`, `radius`, line heights). Shell geometry, radii and the keyboard-chosen row colour are guarded by `design-system/scripts/geometry.spec.ts`. Icons are 14 (UI) and 12 (inside buttons, links and status), stroke 1.5.
|
|
106
|
+
|
|
107
|
+
**The Strip Width Rule.** The KPI strip decides its columns from its own width (container query), not the screen: under 600px it goes two by two. At a 1180px screen four columns in the wide panel let $2,095.56 run over the next divider.
|
|
108
|
+
|
|
109
|
+
**The Still Board Rule.** Nothing reflows when state changes. Modules reserve their final height up front, feeds keep a fixed row count (new rows slide in from the top, the oldest leaves the bottom), and the picker never moves to make room for anything.
|
|
110
|
+
|
|
111
|
+
## Elevation & Depth
|
|
112
|
+
|
|
113
|
+
The system is flat. Depth at rest is expressed with a 1px ring (`0 0 0 1px`) in Hairline, tightening to Hairline Strong on hover, plus tonal steps between Neutral Page, Panel White and Sunken Gray. Real shadows exist only for things that genuinely float above the page: popovers, the search answer panel, and the right sheet with its 14% ink scrim.
|
|
114
|
+
|
|
115
|
+
### Shadow Vocabulary
|
|
116
|
+
- **Frame sheet** (`--sh-frame-sheet`, Design E): `0 1px 3px rgba(28,25,23,.06), 0 0 0 1px rgba(28,25,23,.05)`, the one white sheet on the frame.
|
|
117
|
+
- **Panel ring** (`--sh-panel`, `0 0 0 1px #E4E4E7`): every card, strip and nav-active pill at rest.
|
|
118
|
+
- **Panel ring hover** (`--sh-panel-hover`): card hover, a .16 ring plus a soft drop shadow (2026-10-20, stronger). **Panel press** (`--sh-panel-press`): the pressed card (catalog card, 바로 시작 tile).
|
|
119
|
+
- **Pop** (`0 0 0 1px #E4E4E7` + `--sh-pop` `0 1px 2px rgba(0,0,0,.06), 0 8px 24px -6px rgba(0,0,0,.14)`): popovers, menus, modals, toasts, search results.
|
|
120
|
+
- **Sheet edge** (`--sh-sheet`, `0 0 0 1px #E4E4E7, -12px 0 32px -12px rgba(0,0,0,.18)`): the right sheet only.
|
|
121
|
+
|
|
122
|
+
### Named Rules
|
|
123
|
+
**The No Decorative Shadow Rule.** 대체됨 in part by Design G2 (2026-10-18): the glass sheet, the one hero pane, pills and the current menu item carry the soft G2 shadows (`--sh-frame-sheet`, `--sh-hero`, `--sh-pill`, `--sh-lift`, `--sh-primary`). Other cards and sections still carry none. Earlier: a shadow means "this is floating over the page right now". Cards, buttons and strips never carry one.
|
|
124
|
+
|
|
125
|
+
## Shapes
|
|
126
|
+
|
|
127
|
+
Design G2 (2026-10-18): buttons, the search, filters, the period control, badges and chips are pills (999); form inputs (Input, Textarea, Select, the key field) and sidebar rows `r-field`, 10 since Scale 0.9 (12 before it; every DS input draws `rounded-field`, so 10 is the rule and no input is 12); menu rows 10 (`r-sm`); popovers, menus, toasts and code blocks 14 (`r-md`); the hero pane 20 (`r-lg`); modals, side sheets, the search palette and the picker 16 (the floating glass layer, see Colors; 2026-10-20 round 7: this line said 20 for modals while the code and the floating layer rule say 16); the sheet 24 (`r-xl`). Outlines are 1px rings or 1px solid borders; dashed strokes are reserved for meaning (a requested but unapproved budget tick, a pending outcome disc, a selected plan line in a forecast chart), never for decoration.
|
|
128
|
+
|
|
129
|
+
**The No Initials Avatar Rule (2026-10-21, 상훈님).** No initials chips for people anywhere: no two-letter pill next to a name in tables, pickers, the activity feed, the head meta or the top bar. People are their name as text (plus the email as the sub line where it helps). The viewer's own row keeps the text marker 나. The top bar account trigger is a plain icon button (the `user` outline icon, the same 28 button and hover as help), named `내 계정 메뉴, {이름}`; the account menu head is the name and email without a chip. A list of owners in the head meta is names joined by commas, then 외 n명. Tables that mix people and teams start person names at the 이름 head line and keep the team mark only for team rows. Org and team marks (`OrgMark`, the team letter tile) are object marks, not people, and stay. The DS has no `Avatar` component; do not add one back.
|
|
130
|
+
|
|
131
|
+
**The No Side Bar Rule.** No coloured vertical bar on the left edge of anything: selection, hover, alerts, cards, list items, notes. Selection is a surface change (Sunken Gray or Panel White) plus a weight step.
|
|
132
|
+
|
|
133
|
+
## Motion
|
|
134
|
+
|
|
135
|
+
(2026-10-22 모션 체계, 상훈님 승인. Replaces the 2026-10-17 Design E motion and the G2 loading glow.) A console, so motion is restrained: it confirms where something went, never decorates, and never makes the user wait. Proposal and frame evidence:. Before this, one sidebar click started 7 to 8 motions of five lengths (pill slide 200, text 150, icon 200, skeleton 150 + 200, glow 300 + 2.4s, glow fade 1.1s), one expo-out curve was used for every size and distance, and the fold relaid the grid every frame for 360ms.
|
|
136
|
+
|
|
137
|
+
Tokens (`design-system/tokens/src/primitive/motion.json`, mirrored for JS in `lib/motion.ts` as `MOTION`, `EASE`, `DELAY`; `tokens.spec.ts` keeps them equal):
|
|
138
|
+
|
|
139
|
+
| Token | Value | Used for |
|
|
140
|
+
|---|---|---|
|
|
141
|
+
| `--dur-instant` | 0 | actions done tens of times a day: menu moves, tabs and segments, keyboard-opened surfaces (`/` search), data refresh, chart hover |
|
|
142
|
+
| `--dur-fast` | 100ms | hover, press, tooltip open and close, small closes (menu, popover, select, picker), sidebar names |
|
|
143
|
+
| `--dur-base` | 150ms | menu, popover, select and picker open; dialog, sheet, scrim and toast close; skeleton fade-in; copy check; chevrons |
|
|
144
|
+
| `--dur-slow` | 200ms | dialog, sheet, scrim and toast open; sidebar fold |
|
|
145
|
+
| `--dur-travel` / `--dur-travel-exit` | 300ms / 200ms | phone bottom sheet and drawer (moves the height or width of the screen) |
|
|
146
|
+
| `--ease-out` | cubic-bezier(.16, 1, .3, 1) | appearing opacity and scale |
|
|
147
|
+
| `--ease-travel` | cubic-bezier(.32, .72, 0, 1) | moves longer than 24px: phone sheet, drawer, sidebar fold |
|
|
148
|
+
| `--ease-exit` | cubic-bezier(.4, 0, 1, 1) | every close, opacity only |
|
|
149
|
+
| `--delay-tooltip` | 400ms | the first tooltip; a neighbour opens at once |
|
|
150
|
+
| `--delay-skeleton` | 200ms | before a skeleton (or a dimmed refresh) shows |
|
|
151
|
+
| `--dur-breathe` | 2000ms (`--ease-breathe`) | the one skeleton pulse |
|
|
152
|
+
| `--dur-highlight` | 1200ms | anchor highlight |
|
|
153
|
+
| `--dur-spin` | 800ms (`--ease-linear`) | button spinner |
|
|
154
|
+
| `--dur-sweep` | 1800ms (`--ease-sweep` cubic-bezier(.45, 0, .55, 1)) | one pass of the progress light along the sheet top edge (85% moving, 15% rest). |
|
|
155
|
+
|
|
156
|
+
Rules:
|
|
157
|
+
1. Actions done tens of times a day (menu moves, tabs, keyboard shortcuts, chart hover) do not move (Emil Kowalski "100+/day: no animation", Linear, Raycast).
|
|
158
|
+
2. Data changes do not move. No chart drawing on first load either; a skeleton is replaced by its content at once (no `animate-reveal`).
|
|
159
|
+
3. Loading speaks through the skeleton and one progress line, never a glow. The skeleton waits 200ms and pulses on one 2s beat.
|
|
160
|
+
**Progress light (2026-10-24, 상훈님 고름 시안 4-B/4-C; trigger rule fixed 2026-10-25).** A short orange light passes along the 2px top edge of the main sheet and follows its rounded corners (`AppShell` `[data-progress-edge]` and `[data-progress]`, `useLoadProgress`; CSS in `styles.css`). The light is 40% of the sheet width. Its centre is `--c-progress` (#D44119) at 80% and it fades to transparent at both ends. It does not pretend to fill: one pass takes `--dur-sweep` and repeats. It appears in `--dur-base` and fades out in `--dur-base` with `--ease-exit`. Transform and opacity only. Reduced motion: no pass, a faint full-width band appears and fades. It is `aria-hidden`. The skeleton statuses stay the semantic signal. See Patterns > Loading for when it runs.
|
|
161
|
+
4. A close is shorter than its open, fades opacity only and uses `--ease-exit`. Only the phone sheet and drawer leave the way they came, on `--ease-travel`.
|
|
162
|
+
5. Length follows size and distance: small panel 150, large panel 200, a move of the screen height 300. Nothing interactive runs over 300.
|
|
163
|
+
6. Large panels scale from .98 at most, small ones from .97; nothing grows from 0, nothing bounces.
|
|
164
|
+
7. The first tooltip waits 400ms; moving to a neighbour opens it at once with no motion.
|
|
165
|
+
8. Only transform and opacity move. Width, height, grid, margin and box-shadow do not, with two exceptions: the dialog step height and the sidebar section name height (inside the sidebar only). Hover shadows change in 100ms.
|
|
166
|
+
9. The sidebar folds by moving the main sheet with a transform; the main width changes once (at the start when folding, at the end when opening). The sidebar is always its open width under the sheet, so nothing in it reflows during the slide.
|
|
167
|
+
10. Values come only from the tokens. Components and app CSS never write `200ms`, `1.6s`, `ease-out` or `cubic-bezier` (Tailwind `duration-(--dur-fast) ease-(--ease-out)`, CSS `var(--dur-*) var(--ease-*)`). `console-ui/src/designGuard.spec.ts` fails on a raw value or a layout transition outside its allowlist, each entry with its reason.
|
|
168
|
+
11. `prefers-reduced-motion: reduce`: everything is instant except opacity fades of 100ms or less. The unlayered rule at the end of `styles.css` ties every length token to `--dur-fast`, limits transitions to opacity, rewrites the moving keyframes as opacity only, and stops the skeleton pulse and the spinner. JS motion (fold slide, dialog step height) checks `prefersReducedMotion`.
|
|
169
|
+
|
|
170
|
+
Where it is not: page changes (other than the progress light while a slow screen loads), the sidebar current item, tabs and segments, the search palette, chart drawing and hover, data arrival and refresh, number counting, anything that would make the user wait.
|
|
171
|
+
|
|
172
|
+
## Component rules
|
|
173
|
+
|
|
174
|
+
These rules came from the Design G2 work in the console. They hold for every product.
|
|
175
|
+
|
|
176
|
+
- **Tables and lists:** in a table card (Patterns P4, 2026-10-20); no head band; head 12/500 Ink 3 with one line under it, rows on .045 lines, first and last cell 14 in (22 inside a flush hero). A row whose name has a second line (`TableName sub`) centres its other cells vertically; single-line rows keep the baseline. Pinned cells and row hover are opaque (#FAFAFB sheet colour, `row-hover` #F6F7F8 hover, 2026-10-20; was #FDFDFD and barely visible; a row that links somewhere, or that opens something in place through `TableRowButton`, goes to Sunken while pressed) so text scrolling under a pinned cell does not show through.
|
|
177
|
+
- **Table column layout (2026-10-18 r5, 상훈님 "GCP 빌링보다도 못하네"):** a few columns must not spread across a wide table (organization home was name | gap | 내 역할 | 이번 달 비용 | gap | 예산 사용률). One rule, `<Table layout="packed">` (the rules live in `design-system/core/src/styles.css`, `table[data-layout='packed']`; until 2026-10-26 they were `.table-packed` in `console-ui/src/shell/shell.css`), at widths over 640:
|
|
178
|
+
- Every column is as wide as its content, 28 apart (the cell after a checkbox or radio keeps the DS gap).
|
|
179
|
+
- Exactly one column (`stretch` on `TableHeaderCell` and on its `TableCell` or `TableName`) takes the slack. An inline bar (budget usage, share) keeps its fixed width and never takes the slack: a growing bar column drew the bar 232, 72 and 545 wide on different screens (`.bar-stretch` removed 2026-10-26). The last text column after the name takes it (role, team description), so the role sits next to the name and the slack lands after the text. Without any other text column, the name takes it.
|
|
180
|
+
- Order: name, text attributes (role, Owner), the stretch column, numbers, date, row action. Numbers never sit left of the stretch column, so they read as one group on the right.
|
|
181
|
+
- The name cell (`TableName`) is content wide up to 360; its second line truncates there and stays on one line.
|
|
182
|
+
- One row height per table: a row holds the name and one sub line; extra facts share a line (organization home: the block note on the left and 예산 under the percent on the right of the same line) instead of adding a third line.
|
|
183
|
+
- The row action is `<TableRowEnd reveal="always">`: content wide, at the right end, a 22 or 24 button centred in the text line so the row stays 44. The default `reveal="hover"` is the chevron or tool that shows only on hover.
|
|
184
|
+
- Phones (640 and below) keep each table's own phone rules (pinned first or last column, hidden columns).
|
|
185
|
+
- **Filters:** a pill `Select` sizes to its text (`w-auto max-w-full`) instead of filling the row; the usage filter caps it at 224 on desktop so it sits next to the period control.
|
|
186
|
+
- **Empty and fail states sit on the text edge (2026-10-18):** `EmptyState` and `FailState` have no inset of their own (they start where the section or page title starts); inside a flush hero they take the hero's 22 (phones 14). A list with no rows says it in a noun phrase ("프로젝트 없음", "오퍼레이션 없음", "조직 결제 꺼짐").
|
|
187
|
+
- **Skeletons on floating panels (2026-10-18):** inside `.bg-panel`, `.glass-float` and `.float-solid` (modals, sheets, menus, popovers) the skeleton pill is a light neutral grey (#EEF0F3 -> #E3E6EA); the white glass pill vanished on white.
|
|
188
|
+
- **Page head (2026-10-18):** the (i) stays beside the first line of a long title instead of dropping to its own line.
|
|
189
|
+
- **Forms a role cannot save (2026-10-18):** the fields are disabled too, and the reason stays the one save-button tooltip.
|
|
190
|
+
- **Motion and interaction table (2026-10-22 모션 체계, 상훈님 승인 "그래 이런 식으로 좀 프로페셔널하게 디테일에 신경 써줘"; replaces the 2026-10-18 table).** Measured per frame with `document.getAnimations` on a local mock, before and after (proposal). Tokens and rules are in Motion below. Exit is always shorter than entry and fades opacity only.
|
|
191
|
+
|
|
192
|
+
| Component | Open / change | Close | Easing | Origin |
|
|
193
|
+
|---|---|---|---|---|
|
|
194
|
+
| Menu, Select list, Popover, top bar menus, project picker panel | opacity + scale .97, 150ms (`--dur-base`) | fade 100ms (`--dur-fast`); Select closes at once (Radix Select has no exit presence) | `--ease-out` in, `--ease-exit` out | Radix popper origin, so it grows out of the trigger side |
|
|
195
|
+
| Tooltip | first one waits 400ms (`--delay-tooltip`), fades in 100ms; a neighbour inside `TooltipGroup` opens at once without motion (Radix skipDelay 300ms) | fade 100ms | `--ease-out` / `--ease-exit` | — |
|
|
196
|
+
| Search palette (`/`) | none: opened from the keyboard, it opens at once | none | — | — |
|
|
197
|
+
| Modal | opacity + scale .98, 200ms (`--dur-slow`) | fade 150ms (`--dur-base`). The dialog stays mounted until the exit ends: Esc, X and cancel play it before the caller is told (`useClosingMotion`, waits 200ms, the longest exit), and the content is held while closing (`useHeldWhileClosing`) | `--ease-out` / `--ease-exit` | centre |
|
|
198
|
+
| Modal step change | height from the old step to the new one, 200ms (desktop); phone redraws the sheet and moves its top edge by transform | — | `--ease-out` | — |
|
|
199
|
+
| Sheet, help panel | 16px from the right + fade, 200ms | fade 150ms | `--ease-out` / `--ease-exit` | — |
|
|
200
|
+
| Phone bottom sheet (Modal, project picker), phone drawer | 300ms up from the bottom / in from the left (`--dur-travel`) | 200ms back the same way (`--dur-travel-exit`) | `--ease-travel` both ways (iOS sheet curve; the moves are the screen height) | bottom / left |
|
|
201
|
+
| Scrim | fade 200ms | fade 150ms | `--ease-out` / `--ease-exit` | — |
|
|
202
|
+
| Toast | 8px up + fade, 200ms | fade 150ms | `--ease-out` / `--ease-exit` | — |
|
|
203
|
+
| Sidebar current item | none: the face, text and icon colours change on the click. No sliding pill | — | — | — |
|
|
204
|
+
| Sidebar fold | the main sheet slides by transform 200ms (`--dur-slow`); the grid column changes once (at the start when folding, at the end when opening), so tables and charts never reflow per frame. Names fade 100ms; section names fold their height inside the sidebar only | same | `--ease-travel` | — |
|
|
205
|
+
| Segmented, tabs | none: face and text change at once | — | — | — |
|
|
206
|
+
| Button press | scale .97 while held, 100ms (`--dur-fast`) | — | `--ease-out` | centre |
|
|
207
|
+
| Tool button press | scale .92 (24px target), 100ms | — | `--ease-out` | centre |
|
|
208
|
+
| Checkbox, radio press | box/dot scale .9 while it or its row label is held, 100ms | — | `--ease-out` | centre |
|
|
209
|
+
| Hover colour and shadow | 100ms (`--dur-fast`, Tailwind's default reads it) | — | `--ease-out` | — |
|
|
210
|
+
| Copy button | icon swaps to a check with icon-in (scale .8 → 1, 150ms), back after 1.5s | — | `--ease-out` | centre |
|
|
211
|
+
| Disclosure chevron | rotate 150ms | — | `--ease-out` | — |
|
|
212
|
+
| Data arrival and refresh | skeleton waits 200ms (`--delay-skeleton`) then fades in 150ms; the content replaces it at once. A refresh that keeps the old values dims them to .55 after the same 200ms, at once (`data-stale` on the panel that holds them: the panel stays, its children dim; design-system `styles.css`). A code box that waits is `CodeBlock loading` | — | — | — |
|
|
213
|
+
| Charts | none: no line drawing, bar rising or area fade on first draw; hover highlight changes at once | — | — | — |
|
|
214
|
+
| Anchor highlight | a 2px Ink 3 outline (offset 2) that holds, then fades over 1.2s (`--dur-highlight`). One look on every screen: the shell marks the place an #anchor names when it appears (`markArrival`, `data-anchor-flash` in design-system `styles.css`). Not orange (Orange places), and not a box-shadow, which replaced the card shadow. Reduced motion: the outline stays while marked | — | `--ease-out` | — |
|
|
215
|
+
| Page change | none: the new screen draws at once. A screen opened before shows its last data at once and refreshes behind it | — | — | — |
|
|
216
|
+
|
|
217
|
+
Focus (2026-10-19, no motion, every row also under reduced motion):
|
|
218
|
+
- Closing a dialog, sheet or menu returns focus to the element that opened it (`useReturnFocus`, also for dialogs opened from the address or state with no Radix trigger). If the opener is gone, the rules below apply.
|
|
219
|
+
- After 다시 시도 on a fail state, focus moves to the heading of the region that loaded again (its `aria-labelledby` heading, else the first heading, else its first focusable). If it fails again, focus moves to the new 다시 시도. (DS `FailState` → `focusAfterRetry`.)
|
|
220
|
+
- After a row is deleted or removed, focus moves to the same action on the row that took its place (the next row), else the previous row, else the heading of the table's region (`focusAfterRemoval`, console `useRemovalFocus`). Rows are found by place, so a list that remounts on reload still works. It waits until the row is gone, no dialog is open and nothing in the region is busy. Applies to every delete flow: API keys, OAuth clients, organization members, direct role removal and team disconnect on 접근 권한, team members and team project disconnect, cards and billing account project disconnect.
|
|
221
|
+
- If the user moved focus somewhere else meanwhile, nothing moves.
|
|
222
|
+
|
|
223
|
+
Floating layers stack: sheet 60, modal 70, toast 90, drawer and top bar menus 120 to 130, Menu, Select list and Popover 130, Tooltip 140 (a disabled-reason tooltip inside a modal footer was under the modal at 50).
|
|
224
|
+
- **Select (2026-10-18, replaces the browser `<select>`, whose open list had no design):** `@radix-ui/react-select`, popper position 6 under the trigger, 8 from the screen edge. The trigger keeps the field look (`controlVariants`: 28 high, radius 10, opaque white, inset shade, orange focus halo; `size="sm"` 24 high at the table text 13/500 that shrinks instead of widening its cell, for the role pill in a table row (2026-10-27; it was 12 and the console overrode it with `.role-select`); `pill` for filter rows) with a 12 chevron on the right. Trigger metrics (2026-10-21, 상훈님 "Viewer ⌄" 알약 정렬): the chevron is the last flex item inside the trigger, never an absolute sibling; side padding is equal on both sides (default and pill 12, `sm` 10); the text to chevron gap is 6; the chevron takes `-mx-[3.5px]` to cancel the blank sides of its drawing (7 of 24 at size 12), so the ink gap is 6 and the ink inset on the right equals the text inset on the left (measured at 4x: left 10.5 to 11.5, gap 5.5 to 6, right 9.25 to 10.25; before: role pill left 6, gap 10, right 10, filter pill left 14, right 10). Call sites and wrappers (CollectionFilter, app CSS) never override trigger padding. Text that sits in the same column as an inline role pill (my own row, read-only rows) starts 10 in, on the pill text line; an unchosen value shows the placeholder in Ink 3 (`<option value="" disabled>` becomes the placeholder, not a choice). The open list is the Menu surface: opaque white, radius 12 (`rounded-md`), padding 6, `--sh-popover`, at least the trigger width and at most the screen width minus 16, at most 320 high (about 11 rows) or the space left, scrolling inside with Radix scroll buttons. Rows are the Menu row (`h-8`, 28 at the 0.9 scale), radius 8, 13 Ink 2, padding 8 left and 32 right; hover and arrow keys tint `bg-hover` and darken the text; the chosen row is 500 Ink with a 12 check on the right. `<optgroup>` becomes a 12 Ink 3 group name (`h-7`) with a hairline between groups. Call sites keep `<option>`/`<optgroup>` children and `onChange(event.target.value)`; `name` sends the value with a form; the trigger carries `data-value` for tests and `data-select` for filter-row styling. Phones use the same list (no OS picker).
|
|
225
|
+
|
|
226
|
+
## Components
|
|
227
|
+
|
|
228
|
+
### Buttons
|
|
229
|
+
Port A (2026-10-20, 상훈님 결정). Source: the Apple HIG alert drawings (reference page https://pages.hub.ynext.cloud/p/l3s9o2t4/). Flat capsules: no gradient, no drop shadow, no inset highlight, weight 500.
|
|
230
|
+
- **Shape:** capsule (radius 999), 28 tall (`--ctl-h`), 14 side padding (`px-4`); with a leading icon 10 on the icon side. Press scales to .97. There is no separate decision size.
|
|
231
|
+
- **Primary:** solid Yanolja Orange (#D44119, `bg-accent`), white 600 label. Hover brightness .94, press .90. Used only for 만들기, 실행/적용, 승인, one per screen. A blocked primary shows as the grey secondary button at 45%.
|
|
232
|
+
- **Decision:** the same solid orange capsule at 36px with a 14px label and 20px side padding, for the one decisive action at the end of a flow. Blocked, it shows as the grey secondary button.
|
|
233
|
+
- **Secondary (default):** grey fill `button-fill` rgba(118,118,128,.12) (Apple tertiarySystemFill), no ring, no shadow, ink label, Ink 2 icon. Hover .18, press .24. 취소 in dialog and sheet footers uses this button, not quiet. Contrast on the fill: Ink 13.4 to 15.0:1, Ink 2 5.0 to 6.4:1 over the page, the sheet and glass dialogs.
|
|
234
|
+
- **Quiet:** no fill, no ring, Ink 2 400 label; hover fills Hover and inks the label. Only for text-only actions outside dialog footers (선택 해제, inline 더 보기). A quiet button without an icon has a Hairline Strong underline offset 3px at rest, the same mark as the text link, and hover darkens it (2026-10-28: 시작하기 접기 and 이 프로젝트에서 숨기기 read as plain text). A quiet button with an icon has no underline, because the icon already shows that it can be pressed. A show and hide toggle (접기, 펼치기) leads with the chevron.
|
|
235
|
+
- **Danger:** white pill with a danger ring and danger label, unchanged by port A.
|
|
236
|
+
- **Ink:** Control Room Ink fill with white label, for a confirming non-money action inside AI flows (e.g. 되돌리기 confirm).
|
|
237
|
+
- **Text link:** Instrument Gray with a Hairline Strong underline offset 3px; hover inks the text and darkens the underline. Alternatives and secondary routes are text links, not boxes.
|
|
238
|
+
- **Link hit area:** a small link (panel head link, 정보) keeps its look but is pressable 8px beyond its text on every side (`before:-inset-2`). A tile that leads somewhere (dashboard 리소스) is one link over the whole tile, hover darkens its ring to Hairline Strong, focus rings the tile.
|
|
239
|
+
|
|
240
|
+
### Chips
|
|
241
|
+
- **Style:** 24px tall, a pill (999, the G2 pill face, see Pills) with a 1px inset Hairline ring, 12px label: an optional Ink 3 name, then the value in ink. No fills, no pastel tints. A removable chip ends in a 20px x button. One part, `Chip` in the design system (2026-10-26: the usage filters drew a sunken chip and the member picker a 13px ring chip).
|
|
242
|
+
- **Recommendation chip:** 32px, 1px solid Control Room Ink border, 600 ink label, preceded by a solid ink 추천 badge (18px, white 11px/600). Hover Neutral Page, pressed Sunken Gray. Applied state drops to a 28px Hairline Strong outline at 600.
|
|
243
|
+
|
|
244
|
+
### Cards / Containers
|
|
245
|
+
- **Corner Style:** 18 (`r-lg`); table cards 14 (`r-card`).
|
|
246
|
+
- **Background:** Panel White on Neutral Page; inner metric footers and table headers on Neutral Page.
|
|
247
|
+
- **Shadow Strategy:** panel ring only (see Elevation & Depth).
|
|
248
|
+
- **Border:** 1px Hairline ring; Hairline Soft dividers inside.
|
|
249
|
+
- **Internal Padding:** 14 on every card (`card-x`, `card-b`), one value. The 36 panel head (`head-h`) holds a 14/500 title (no icon), dot-separated Muted Ink metadata and a right-aligned Instrument Gray "go" link; tools appear on hover only.
|
|
250
|
+
|
|
251
|
+
### Inputs / Fields
|
|
252
|
+
- **Search:** 40px, 8px corners, Panel White with a 1px Hairline Strong ring, 17px Instrument Gray icon, 14px placeholder in Muted Ink, a scope tag on Sunken Gray and a `/` keycap. Hover darkens the ring to Faint Ink; open state is a Muted Ink ring plus a 4px Sunken Gray halo.
|
|
253
|
+
- **Text input / select / textarea:** 28 (same as buttons, `ctl-h`), 10 corners (`r-field`), 1px Hairline Strong ring, Panel White; focus is the search's Muted Ink ring plus 4px Sunken Gray halo; error is an ink ring plus an ink sentence with the outlined warning icon; disabled is Sunken Gray with a Hairline ring. The in-row select (`size="sm"`) is 24px at 13/500.
|
|
254
|
+
- **Checkbox / range:** native controls tinted Control Room Ink (`accent-color`).
|
|
255
|
+
|
|
256
|
+
### Navigation
|
|
257
|
+
- **Left nav columns:** (2026-10-17; Design E: section names have no square and start at the icon column, x 20) every row of the sidebar stands on two columns: a 16px icon column at x 20 and the name column at x 46 (icon + 10). Menu items, section names (brand square centred in the icon column) and the scope name at the top (plain Ink 3 icon, 14/600 Ink name, no tile, no rule under it unless a back link sits above or a bottom row sits below) all start on those two lines. A tiled icon and a rule set the scope name on a third line. Project sidebar (2026-10-23, 상훈님 option 4 of): the top holds only the project name row and the rule under it. The way to the organization is one quiet row "조직 <org name>" (`top-text` tone, the `home` icon that organizations use in search and the org menu, no arrow) under a rule at the sidebar bottom, linking to the org home. It sits outside the menu scroller, so it stays at the bottom when the menu is taller than the window. On the folded rail it is an icon with the menu tooltip, in the phone drawer the last row under a rule (`DrawerFoot`). Before the org name arrives, a 34 skeleton row holds its place. The org view has no bottom row. Both sidebars open with a scope head (2026-10-28, usability review: the bare project name read as an unclear label, and the org sidebar had nothing at that place). The kind (프로젝트 or 조직) sits on the name column in the section label style (12 Ink 3). Under it are the icon (`box` or `org`) and the name. The head is a heading, not a link. The breadcrumb picker changes the scope, and the first menu item opens the scope home.
|
|
258
|
+
- **Left nav:** 34 rows (`nav-row-h`), radius 10 (`r-field`), 13 text, white pill current item at 500, plain section names; collapsed it is the 50 icon rail (`nav-rail`). Earlier: 32px rows (24 line + 4 above and below; 28 until 2026-10-17), 13px Instrument Gray labels with 15px Muted Ink icons, 6px corners; group labels 12px/500 Muted Ink. Hover fills Sunken Gray. Active is Panel White with the 1px Hairline ring, ink text at 500 and an ink icon. Counts sit right-aligned in 12px tabular Muted Ink; a scope tag (조직) is a 12px outlined badge.
|
|
259
|
+
### Implementation
|
|
260
|
+
The design system is four packages in `design-system/`. Tokens are in `design-system/tokens` (`@yanolja-next/design-tokens`), in `src/*.json` (the only source of values). Product-neutral components are in `design-system/core` (`@yanolja-next/design-core`). Parts with console domain meaning are in `design-system/kits/console` (`@nexthub/console-kit`), and intro screen parts are in `design-system/kits/landing` (`@nexthub/landing-kit`). The stories with their screenshot baselines are in `design-system/stories`, next to each part, and in `design-system/__visual__`. The console (`console-ui/`) uses only these packages. Check the package before you write a local rule in a screen. Its README and CONTRIBUTING say how to add a token or a component.
|
|
261
|
+
|
|
262
|
+
### Permissions
|
|
263
|
+
Source is `@nexthub/authz`. Three cases, never mixed up:
|
|
264
|
+
- **Cannot see the data** (a card, a row, a to-do): don't render it (`data-need="cap"`). No empty slot, no lock icon.
|
|
265
|
+
- **Can see, cannot act**: keep the control visible but disabled, with the reason as `title` (`data-need-why`, `data-perm`). At a decision point (modal footer, decision card) the reason is also written beside the button as `.why-no`, since `title` never shows on touch.
|
|
266
|
+
- **Whole screen is read-only**: one `.perm-note` line directly under the page head saying who is viewing and which role can change things.
|
|
267
|
+
|
|
268
|
+
### States
|
|
269
|
+
Numbers say the truth at the edges (2026-10-17): a used percentage is never capped in words (150% over budget) even though the bar stops at its end; a project without a monthly budget (0) says 월 예산 없음 and drops the bar, usage, reach and alert rows; an error rate above 0 but under 0.01% reads 0.01% 미만, never 0.00%. Fixed bars never hide focus: at sheet-scroll widths the sheet (AppShell `main`) has `scroll-padding` top 8 and bottom 32; at window-scroll widths `html` has `scroll-padding-top` (top bar + 8) and `scroll-padding-bottom` (32). A sticky element inside the sheet sets `top: calc(N - var(--s-7))`, because the sheet's top padding (`--s-7`) scrolls away with the content.
|
|
270
|
+
|
|
271
|
+
Every screen draws loading, error and empty (`?state=loading|error|empty`). Empty says what is missing, why it matters and one action (`.empty`, or `.empty-row` inside a table so the head stays). Loading is Sunken bars in the same layout and height as the loaded view (`.sk-card`, `.sk-rows`; a card that keeps its head starts at the hero bar, a chart slot is `.sk.blk`). Since 2026-10-17 the skeleton (`SkeletonArea`, `SkeletonBar`, `SkeletonRows header rowHeight`) waits 200ms (`--delay-skeleton`, 2026-10-22) before it fades in over 150ms, holding its place invisibly, so an answer that comes quickly never flashes it; the pills pulse on the one 2s beat (stopped under reduced motion); the content that replaces it appears at once in the same place (`animate-reveal` is 대체됨, 2026-10-22). A skeleton must match the loaded height: measured with `PerformanceObserver('layout-shift')` on dashboard, usage, budget, API keys, members and organization home, CLS went from 0.03 to 0.12 down to 0.01 or less. Panels whose height depends on data that is not known yet are placed after the block above them settles (the dashboard's 서비스별 and API 추이 wait for the money block, whose month line appears only when blocking), and the sidebar draws its menu once permissions are read. Errors use `.st-fail`. Error stays in place: what failed, what still works, a retry button and the auto-retry note. Prototype notes ("가정한 기능", assumed data) live only in the prototype bar's memo, never in product copy.
|
|
272
|
+
|
|
273
|
+
## Patterns
|
|
274
|
+
|
|
275
|
+
(2026-10-20, 상훈님 결정) One rule per situation: which form a kind of data takes, which container an action uses, and where things sit. Check this list before a new screen or a change. A situation that is not on the list gets a rule here first; do not decide it on the screen. Where an older rule below (Screens, Components) differs, this section wins. Reviews check "패턴 위반" against these numbers. References: Cloudscape, Carbon, Primer, Atlassian, Polaris, Geist (table in the).
|
|
276
|
+
|
|
277
|
+
Each rule: the rule, **Why**, **Where**.
|
|
278
|
+
|
|
279
|
+
### Layout
|
|
280
|
+
|
|
281
|
+
**P1 Page frame.** Body in one column, plus a right rail only when needed. Rail 264, or 300 for code or long values, 36 between. Under 1200 the rail drops under the body. One rail look: no box, no vertical line, group label 12 Ink 3, rows with the label left and the value right, a thin rule between groups. Exception: the info panel of a detail screen (P8) is a white panel, because it is that screen's hero. Sections 36 apart (`--s-10`, the 0.9 scale of 40; every board uses it, 2026-10-26). Section title 13/500 + count + a grey go link on the right. The DS `Container` title is 13/500 too (was 18), and so are the AI connect step titles (2026-10-20 round 7: the doc said 14/500 while every section title in code was 13/500; the step titles were the one 14/600). Token note: token names equal the rendered px since 2026-10-21, so `--t-14` is 14px and `--t-13` is 13px.
|
|
282
|
+
**Why:** the rail had four looks. Polaris keeps the secondary column at the same width and look on every screen.
|
|
283
|
+
|
|
284
|
+
**P1 Rail anatomy (2026-10-21, 상훈님 "이부분이 좀 이상해" on the dashboard rail).** Every rail (dashboard, API keys, members, 접근 권한, activity, OAuth, AI connect, teams, budget) is built from the core parts `RailGroup`, `RailRow`, `RailList` and `RailNote` (catalog entry ListWithRail). Nothing else draws rail rows.
|
|
285
|
+
- Group: a header line with the group name (12, Ink 3) and at most one item at its right end: a GoLink (이번 달 ··· 예산 ›, 최근 활동 ··· 전체 ›, 호출 방법 ··· 서비스 엔드포인트 ›) or one small quiet button (연결된 팀 ··· + 팀 연결). The header stays 16 high, and the link ink ends on the value edge. A thin rule separates groups.
|
|
286
|
+
- Row: label left (13, Ink 2), value right (13/500, Ink, tabular numbers). One row height: 28, and 44 on phones. A qualifier after the value is regular Ink 2 (`note`: $2,637.50 (차단 없을 때)). A row that leads somewhere is a whole-row link (리소스). Recent activity uses the same row (what left, when right).
|
|
287
|
+
- Status: a row with the status as the value. 조직 결제 / ⊖ 꺼짐, 결제 계정 / 미연결, 결제 계정 / 연결됨. One compact action may follow the value (`action`: 결제 사용 설정 ↗ for platform admins).
|
|
288
|
+
- No bare link lines and no button rows under the rows. An empty group shows one muted line only (기록 없음). A rule or hint is one muted line (`RailNote`).
|
|
289
|
+
- Code (호출 예시, MCP 주소) is its own group with a code block under the header.
|
|
290
|
+
- Guard (console): `console-ui/src/shell/SummaryRail.spec.tsx` fails when a screen writes its own rail row classes or a bare link line.
|
|
291
|
+
**Where:** dashboard, members, 접근 권한, API 키, OAuth, 활동, AI 연결, 결제 꺼짐.
|
|
292
|
+
|
|
293
|
+
**P2 Page head.** Title 24/500. A list adds (count) after the title. A detail adds the path above. Meta is one line: period, as-of time, ID, as needed. The as-of time shows once. Right side: at most one primary button (create or add), the rest secondary. The primary is named "{대상} 만들기" or "{대상} 추가", never "추가" alone. The head primary stays when the list is empty. Bulk actions for selected rows go to the table toolbar (P4), not the head. A state text (조직 결제 꺼짐) goes to the meta line or the rail, not the action slot. Every head has the (i) help beside the title, 사용 중인 서비스 and 내 계정 included (2026-10-20 round 8). On phones the head buttons sit under the title and wrap to the next line; they never scroll sideways, so the primary is always visible (round 8: Tella's third button was cut at the sheet edge). A head whose buttons do not fit one phone row folds the secondaries after the first into one ⋯ menu (service detail: 백오피스 열기), so the head keeps one row and does not grow when the object loads (390 CLS 0.046 when the row wrapped). Phone head buttons and phone dialog sheet buttons are 36 high.
|
|
294
|
+
**Why:** Cloudscape headers hold title, count and actions only. Actions that come and go move the head (Still Board).
|
|
295
|
+
**Where:** every screen.
|
|
296
|
+
|
|
297
|
+
**P3 Hero pane.** 0 or 1 per screen (G2). It holds the one thing the screen answers: a judgement (verdict, amount), an exploration (metric tabs + chart) or a task (steps). No pane inside a pane. Tabs inside the hero are a 2px ink underline. A screen that is only a list has no hero. A table card (P4) is not a hero: no glass, no shadow, no loading glow.
|
|
298
|
+
**Why:** the G2 rule, kept apart from the table card by material.
|
|
299
|
+
**Where:** dashboard, usage, budget, organization home, service detail, Playground, AI 연결, billing overview.
|
|
300
|
+
|
|
301
|
+
### Lists and tables
|
|
302
|
+
|
|
303
|
+
**P4 Resource list (table card).** Every table sits in a white table card: opaque, 1px ring (border only), no glass, no shadow, no glow, radius 14. No bare table on the sheet. This is the one exception to "no box except the hero" (The One Hero Rule, The Open Section Rule). Toolbar above the card: search left (240, grows to 560), filter pills, pagination at the right end of the same row. A table with 0 rows has no search row. Selection uses checkboxes when the action can take many rows (team detail included). A table whose actions take exactly one row uses radios: review one request (예산 증액, 사람별 한도, 접근 요청), change one card, detach one project (2026-10-27: the DS `TableSelectCell` type radio is for this case, and a checkbox would let the reviewer pick rows that no action can use). Esc clears either kind, and when rows are selected the same toolbar turns into "n개 선택됨" + bulk actions + 선택 해제. Column order: name (+ one sub line), text attributes, the stretch column, numbers, date, row action (⋯). Hide a column whose values are all the same. Row actions are one ⋯ menu; only a single-value change (role) is an inline select. A total is the last row, its first cell reads 합계 (not "35개"). A pointer to another screen is one line under the table (`.table-more`). Rows are 44 in every table, collection and dense included (dense keeps only 13 names; 2026-10-20 round 6, they were 37 to 38). The head is one height in every table, dense included (2026-10-20 round 7: the usage daily and recent errors heads were 26, every other head 32), and the total row is 44 like a body row (was 40.6). A row whose name has a second line (description, email) is also 44; 56 is only for a row where a value carries a status line under it (the project cost tables and the pending request table: bar plus 10월 26일 차단 예상). The status line is `UsageBar statusLine`, and the Table sets the 56 from it, so a screen sets no row height for it (2026-10-27). So a project row without cost columns is 44 like a member row. A table whose only row is the empty row has no select column (the head text starts on the empty state text). The search row shows whenever the table has rows, at any count (접근 권한 with 3 rows had none while API 키 with 1 row had one). Phones: the table scrolls inside its card only; its scroller contains its inline size so a wide table never widens the page (the budget tab grew to 401 to 467), and the toolbar wraps (search, then filter pills, the pager drops to the next line when the row is full). The phone scroll hint is a 24 icon button (›, and ‹ once scrolled) inside the 32 edge fade at the right end of the head row, named by aria-label and title; it never covers head text (the text pill and its gradient covered 월 예산). Guard: the CollectionFilter story `PhoneToolbarAndWideTable` fails when a phone toolbar or table card overflows, and every full-page story (layout fullscreen, AppShell `PhoneWideTable` with an eight-column table) fails when the document is wider than the viewport at 1440 or 390 (`.storybook/vitest.setup.ts`).
|
|
304
|
+
Round 8 additions (2026-10-20):
|
|
305
|
+
- A row that expands has one affordance: a › chevron left of the name (`ExpandToggle`), rotating 90° when open. Usage 서비스별 and the service detail operations table alike; the count column holds only the number. A team row that expands its members (`TableRow expanded`) toggles from anywhere on the row; while open, the row and its member rows (`TableRow nested`) share one Row hover block, and the member rows start at the name line.
|
|
306
|
+
- A row is 44 even with a button in it (login method 연결): the action cell drops its block padding and centres. Every content line in a cell is a block or a baseline line, never an inline-flex box that adds space under the baseline (the dimmed billing row was 44.5).
|
|
307
|
+
- Phone stacked rows (activity cards) keep the cell inline padding, so text starts 14 inside the card like every other phone table.
|
|
308
|
+
- A name sub line (email) cut with an ellipsis carries its full text in `title`.
|
|
309
|
+
- Narrow table: a table in one half of a side-by-side section pair (budget 알림 단계 beside 예산 변경 기록) keeps the half width even when the partner section is absent. The partner arrives with a later request; filling the width and shrinking on arrival would move the table (layout shift). This is the one table that does not fill the body width or pair with a P5 rail.
|
|
310
|
+
**Why:** 상훈님 example ("표는 카드 안에"). Cloudscape, Geist and Primer all put tables in a bordered container. Bulk actions in the toolbar keep the head stable and hide disabled buttons at rest (Carbon, Gmail).
|
|
311
|
+
**Where:** every table: projects, members, teams, keys, OAuth, used services, 서비스별, budget tab, invoices, activity, operations.
|
|
312
|
+
|
|
313
|
+
**P5 Sparse list.** With few columns or few rows, the rail (P1) is pinned to the right edge, its right edge on the head primary button, and the table card fills the rest of the width (members mock A, 2026-10-20 round 5; replaces "table shrinks to content width", which left about 290 empty at 1440). The rail holds only data the screen already has. A slot that needs a new API is left out, with no placeholder.
|
|
314
|
+
**Why:** The Sparse List Rule (10-19, 1A) joined with the table card.
|
|
315
|
+
**Where:** members, 접근 권한, API 키, OAuth, 활동, teams. All of them go through `SparseListWithRail` (rail on the right edge, table fills the rest, members mock A); `shell/railLayout.spec.ts` fails when a screen uses the raw layout (2026-10-20).
|
|
316
|
+
|
|
317
|
+
**P6 Same data, same columns.**
|
|
318
|
+
- Service table: 서비스 (+ID) | 비중 | 비용 | 호출 | 5xx 오류율 | 평균 지연 | (⋯ or go link). Shared by dashboard, used services, usage. The share column has a fixed width (72 bar + %), the service name takes the spare width; a guard test fails if the share cell stretches (2026-10-20). A 5xx rate of 1% or more is Block Crimson everywhere, the 24-hour figure included (`services/hotRate.spec.ts`). Operation rows only on usage, as a row expansion.
|
|
319
|
+
- Project table: 프로젝트 (+ID) | 내 역할 | Owner | 예산 사용률 (+ one state line) | 이번 달 비용 | 월 예산. Shared by organization home, projects, budget tab. Projects adds 생성일. The description is the name's sub line.
|
|
320
|
+
- Hide a column where every value is "-" (p95). A table cell with no value is "-" ("없음" is for key-value, P9). p95 shows only for an operation with 20 calls or more in the period; the server reads it from a fixed-bucket histogram, so with a few calls it is the bucket edge and can be below the average (2026-10-20 round 6).
|
|
321
|
+
**Why:** the same object showed three ways on three screens and could not be compared. Which screen owns it is still open; the columns can match now.
|
|
322
|
+
**Where:** 서비스별 in 3 places, project table in 3 places.
|
|
323
|
+
|
|
324
|
+
**P7 Card grid.** Only for objects you read before you choose (service catalog, 바로 시작). Managed objects (keys, projects, members) are tables. The whole card is a link: name 14/500, description fixed at two lines, one fact line + a grey chevron at the bottom. Every grid card is the DS `LinkCard` (2026-10-27): the catalog card at size md, the 바로 시작 tile at size sm (14 in, 13/500 name with icon, the fact line as one Ink 3 note). On card hover the chevron moves 2px forward, like GoLink. A tile that opens a dialog is the same part without `href`. The category is a filter only, not on the card.
|
|
325
|
+
**Why:** the same split as GCP Marketplace and the AWS service list. Catalog 5A (10-19) stays.
|
|
326
|
+
**Where:** catalog, 바로 시작.
|
|
327
|
+
|
|
328
|
+
### Facts and numbers
|
|
329
|
+
|
|
330
|
+
**P8 Detail screen.** Head (path, title, one description line with an ellipsis and the full text in `title`, actions; an irreversible head action such as 팀 삭제 is a grey secondary button, red only inside its confirm), then tabs on the left and a 300 info panel on the right (the hero, the DS `InfoPanel`; each section under the first is an `InfoSection`). The panel holds every fact about the object, 담당자 included. No facts outside the panel. Panel section titles are the DS `SubHead` (13/500, the P1 section title; was 14/500 until 2026-10-27); every value in the panel is label above, value below (P9, the DS `KeyValueColumns` in one column, or two columns for the two month figures). A read-only detail opened from a list row (invoice) is a right sheet (P12).
|
|
331
|
+
**Why:** Cloudscape details page: head + summary container. Service detail 6B (10-19) stays, with 담당자 moved into the panel.
|
|
332
|
+
**Where:** service detail, team detail, billing account.
|
|
333
|
+
|
|
334
|
+
**P9 Key-value.** In the body (settings 프로젝트 정보, detail info panel): label above, value below. Grid of 4 columns at most, no vertical dividers, 28 between columns. In the rail: label left, value right, one line. IDs are mono with a copy button after the value (P17). A missing value is "없음", not "-".
|
|
335
|
+
**Why:** Cloudscape key-value pairs (4 columns max). Vertical lines meant two things (settings and the budget figure strip).
|
|
336
|
+
**Where:** project and organization settings, service detail, billing account, AI 연결 연결 정보.
|
|
337
|
+
|
|
338
|
+
**P10 Figure strip and charts.** Figure strip: label 12 Ink 3, value 20/500 tabular, note 12 Ink 3, 3 to 4 cells, 40 gaps only, no vertical lines. A chart sits only in the hero or in a section, one chart per pane. Legend: one line at the top right. Value axis on the right. Two heights: bars 150, line 180. A small trend (24 hours) is a line sparkline with a light area under the value, the full cell width (no bars: 2px bars read as noise). Colours: palette D by rank (ink, blue, light blue, periwinkle, then grey 그 외); one service is ink only.
|
|
339
|
+
**Chart system (2026-10-21, 상훈님 "추천대로 가자": direction A with the hover of B).** Every chart uses the shared design system charts (`BarChart`, `LineChart`, `TimeSeriesChart`, `Sparkline`, `ChartLegend`, `ChartSkeleton`). A screen never draws chart marks itself. `console-ui/src/chartGuard.spec.ts` fails on raw chart SVG in a screen.
|
|
340
|
+
- Lines: value line 2. Spent line has an orange area under it, 10% to 0 vertical gradient. Sparkline line 1.5 with an ink area 13% to 0.
|
|
341
|
+
- Grid: horizontal lines only, ink 6%, top and middle, base line ink 16%. No vertical grid.
|
|
342
|
+
- Budget line: ink (text-2) 1.25 solid, one step stronger than the grid. Its label sits on the line at the left, 11/500 (월 예산 $100).
|
|
343
|
+
- Forecast: the same color at 55%, dashed 5/4, stroke 1.5, an end circle and an end label (예상 $48.28).
|
|
344
|
+
- Bars: 70% of the slot, top corners radius 3, 1px white gap between stacked segments. A day with 0 shows a 2px ink 10% mark on the base line.
|
|
345
|
+
- X ticks: every 7 days counted back from today (14 when 7 slots are narrower than 46px). Today is bold Ink. Phone keeps start, today and end.
|
|
346
|
+
- Today: on a line, an orange dot 3.5 with a white ring 1.5 and a 14% halo of 7. On bars, the hollow bar of its own color.
|
|
347
|
+
- Hover (from B): lines get a vertical crosshair (ink 16%) and a white-centered point 4.5. Bars get the column band and the other bars dim to 32%. A small dark pill above the point or bar shows the value. The tooltip is an opaque white panel, radius 10, shadow 0 8 24 at 12% plus a 6% ring, title 12 Ink 3, rows with a color mark, name 12 Ink 2 and value 12/600 at the right, sorted by value, and a total row when there are two or more series. It sits 14 right of the point and flips left at the edge.
|
|
348
|
+
- Empty: the grid and axes stay, two centered lines (13/500 Ink 2 and 12 Ink 3). The height does not change.
|
|
349
|
+
- Loading: the same height, bar and axis label pills, breathing on the shared 2s skeleton beat between 100% and 55%, no shimmer. Data appears at once.
|
|
350
|
+
- Motion: none (2026-10-22 모션 체계). No line drawing, bar rising or area fade on first draw, and the hover highlight changes at once.
|
|
351
|
+
|
|
352
|
+
**Why:** 10-15 "Grafana/Datadog/Vercel 톤으로 담백하게". Legend place, strip lines and heights differed by screen. 10-21: the budget line read like a grid line, bars and lines differed by screen, and the dark tooltip did not match the light glass.
|
|
353
|
+
**Where:** dashboard, usage, budget, organization home, organization usage, billing overview.
|
|
354
|
+
|
|
355
|
+
**P11 Number and date formats.** Make them in one place only (in the console, `console-ui/src/lib/format`). Money formats are product rules (`console-ui/DESIGN.md` P11).
|
|
356
|
+
- Latency: under 1s 780ms, 1s and above one decimal 10.2s.
|
|
357
|
+
- Dates in text and tables: 10월 26일 (another year: 2025년 12월 3일). Time: 10월 20일 02:21. Period: 10월 1일 ~ 20일. 10/26 only on chart axes.
|
|
358
|
+
- Counts: 1,234. Unit words (개, 명) go in the column name, the cell holds the number. A rail count is the bare number because the label names it (멤버 3). A part of a whole is "used / total" (사용 키 0 / 1, API 키 사용 0 / 1) (2026-10-20).
|
|
359
|
+
**Why:** 10s / 8.2s, $132.69 / $132.70 and "10/26 차단" / "10월 26일" were mixed.
|
|
360
|
+
**Where:** every screen.
|
|
361
|
+
|
|
362
|
+
### Create, edit, delete
|
|
363
|
+
|
|
364
|
+
**P12 Create and edit.**
|
|
365
|
+
- **Modal:** create or edit with 6 inputs or fewer in one group.
|
|
366
|
+
- **Extended modal:** a complex or multi-step create or edit (more inputs, two groups or more) uses a wider modal or a stepped modal, not a dedicated page.
|
|
367
|
+
- **Dedicated page:** only for a long-lived editing surface (settings), never for a create flow.
|
|
368
|
+
- **Right sheet:** never for create or edit. Only a read-only detail opened from a list row (invoice).
|
|
369
|
+
- Inputs in a modal take the full width; cancel + primary at the bottom right. A page form caps inputs at 560, one save button at the left end of the form.
|
|
370
|
+
- Validate on submit or when the field loses focus. Cancel and close never validate.
|
|
371
|
+
- One help line under an input, at the field spacing. A value that reacts to typing (the increase amount change) replaces that help line; it never adds a reserved line between the input and its help (round 8: the amount help sat 30 under its input).
|
|
372
|
+
- A role choice is the DS `Radio` with `description`: the role name in bold and one description line, in the organization, project and team dialogs alike (round 8).
|
|
373
|
+
- Esc closes a dialog on the first press. A combobox inside it keeps the first Esc for its own list only while text is typed (round 8: the add member search opened its list on focus and ate the first Esc).
|
|
374
|
+
**Why:** Cloudscape and Carbon keep modals for a few inputs. All our forms have 1 to 4, so one container is enough, and the sheet stays for viewing. One rule line.
|
|
375
|
+
**Where:** OAuth client create and edit move from the sheet to a modal.
|
|
376
|
+
|
|
377
|
+
**P13 One-time secret.** The next step of the modal that created it. The title becomes "새 {대상}" and the value takes the place of the inputs. Order: "지금 한 번만 표시", value + copy, env line, "저장했음" checkbox. The close button is disabled until the box is checked (reason in a tooltip). Both steps have the same height, so the dialog does not jump.
|
|
378
|
+
**Why:** GitHub tokens and AWS access keys show once inside the create flow. API keys used a modal and OAuth secrets a sheet.
|
|
379
|
+
**Where:** API keys, OAuth client secret, later service keys.
|
|
380
|
+
|
|
381
|
+
**P14 Delete and disconnect.**
|
|
382
|
+
- **Plain confirm:** easy to undo (disconnect, remove a team member, remove a project member, delete an example). A modal with one sentence "what happens to what" + a red button "{동사}".
|
|
383
|
+
- **Type-the-name confirm:** cannot be undone, breaks calls or touches money (delete a key, a client, a team, a card, close a billing account, remove from the organization).
|
|
384
|
+
- Red button at the right end, cancel left of it. After it: a toast (P15) and focus on the next row.
|
|
385
|
+
**Why:** Cloudscape asks for typed confirmation only for severe, irreversible or cascading results. Disconnects had different strengths.
|
|
386
|
+
**Where:** 접근 권한 team disconnect and project member removal become plain confirms.
|
|
387
|
+
|
|
388
|
+
**P15 Result notice.** An action that changed a row or a list (create, delete, role change, connect) gives one toast line "{이름} {대상} {~됨}". Failure: the same place, a red-dot toast. A page form save says "저장됨" beside the save button (no toast). A row that a modal adds to a list on the same screen (Playground 예시로 저장) goes in when the action is pressed, without a row menu, and is replaced by the server's row (a late row moved the card under it, CLS 0.0075, round 6); a failure takes it out. A failure inside a modal keeps the modal open: a red line above the buttons and one cause line under it (server message, else the status, else 서버 연결 실패; `shell/FailureReason`). A whole-screen state (suspension, payment failure) is a verdict (P16), never a banner strip.
|
|
389
|
+
**Why:** Atlassian flags and Carbon toasts confirm what you just did. Primer forms confirm in place. Only two screens told the user.
|
|
390
|
+
**Where:** OAuth, members, teams, team detail, cards, billing account project link.
|
|
391
|
+
|
|
392
|
+
### State and copy
|
|
393
|
+
|
|
394
|
+
**P17 Status, badge, link, copy, code.**
|
|
395
|
+
- Status: dot or shaped icon + text. Green once per pane. Ordinary row states (사용 중, 결제됨) are text only.
|
|
396
|
+
- Badge: outlined pill 12 Ink 3 (나, 기본, 승인 대기). The only coloured badge is "사용 중".
|
|
397
|
+
- To another screen: grey text + chevron (GoLink). To another site: ↗ after the text, never before (a `Button icon="ext"` draws it after its label, 2026-10-20), and it never wraps alone onto its own line (keep it in a nowrap span with the last word; catalog links in running text use `LinkText`, which keeps the part after the last slash or space with ↗). GoLink is one look, 12/500 Ink 2; a GoLink with `target="_blank"` draws ↗ instead of the chevron by itself (admin host 결제 사용 설정 included). Apps never override its size or weight (`goLinkLook.spec.ts`, 2026-10-20). The panel head `link` prop renders a GoLink too, so a head link and a go link in the same panel share the hover face, height and chevron position (2026-10-21: they sat 6 apart on the org home cost card). The chevron takes `-mr-[2.5px]` against the blank right side of its drawing, so its ink sits 8 from the right edge like the text from the left. Any control with a hover or fill face pads evenly on both sides, measured on the ink (crumb name faces 8/8, 2026-10-21; they were 8/6); a leading icon button (`withIcon`) keeps 2 less on the icon side, which reads even because the icon drawing has blank sides. The name cell in a table makes the whole row a link.
|
|
398
|
+
- Copy: IDs and endpoints have the icon after the value, in one unbroken chunk with the last part of the value (after the last /, else the last four characters), so the icon never drops alone onto its own line (`CopyValue`, round 8). Code has it at the top right of the block.
|
|
399
|
+
- One code block: grey #F0F1F2, radius 12, no wrap, horizontal scroll. On phones the copy button sits over the end of the first line, never on its own row. The rail holds examples of two lines or fewer.
|
|
400
|
+
**Why:** three code block looks and two copy places become one.
|
|
401
|
+
**Where:** keys rail, service detail 빠른 시작, AI 연결.
|
|
402
|
+
|
|
403
|
+
**P18 Tabs, segments, period.**
|
|
404
|
+
- Page tabs (the content and the address change: billing 개요/청구서/결제 계정, 요청/프로젝트 예산, service 개요/오퍼레이션/예시 as `?tab=`, an unknown tab opens the first): 2px ink underline tabs.
|
|
405
|
+
- Tabs inside a pane (Playground 요청/응답, AI 연결 clients): the same underline tabs, small size.
|
|
406
|
+
- Segmented well: filters or a range over the same content (period, catalog 전체/사용 중).
|
|
407
|
+
- The usage metric tabs mark the selection with the same ink underline (2026-10-20: orange is only for spend and the primary button; this replaces the 10-19 orange underline).
|
|
408
|
+
**Why:** Carbon and Primer split tabs (switch view) from segments (pick a value). There were four looks.
|
|
409
|
+
**Where:** 예산 및 요청, service detail, Playground, AI 연결, billing.
|
|
410
|
+
|
|
411
|
+
**P19 Empty state.** One component (`EmptyState`), on the text edge, left aligned. Never centred.
|
|
412
|
+
- No rows: inside the table card, "{대상} 없음" + an optional one-line description + an action. The head primary stays. Leave out the empty-state action when it is the same as the head primary.
|
|
413
|
+
- Search or filter with 0 results: the table head stays + "일치하는 {대상} 없음" + clear filters.
|
|
414
|
+
- Inside a pane (no response yet): the same component, `sm`.
|
|
415
|
+
- Exception: Playground 저장한 예시 and 세션 기록 with 0 rows are one line, no table card (2026-10-20).
|
|
416
|
+
**Why:** Cloudscape splits empty (no resources) from no match. There were three looks, and OAuth and teams lost the head primary when empty.
|
|
417
|
+
**Where:** OAuth, teams, activity, Playground response.
|
|
418
|
+
|
|
419
|
+
**P20 Loading and errors.**
|
|
420
|
+
- Loading: a skeleton in the final shape (a table keeps its head and row height, 44 by default; an empty table keeps its column widths), shown after 200ms. Money is never counted up and never fades: the real value appears at once. No glow (대체됨 2026-10-22).
|
|
421
|
+
- Error: in place, "{대상} 불러오기 실패" + 다시 시도. The title always names the object ("불러오기 실패" alone is not allowed).
|
|
422
|
+
- Render error (route error boundary): "화면 표시 실패" in place of the screen, the shell stays. Focus moves to the retry button. 다시 시도 forgets every remembered read and clears remembered filters, so the screen loads its data again (2026-10-20 round 6: it redrew the cached data and failed again). The label is 다시 시도; 거르기 초기화 후 다시 시도 only when the address carries filters.
|
|
423
|
+
- A render error stays inside its screen (`RouteErrorBoundary`): "화면 표시 실패" in the screen's place and one retry that clears remembered filters and the address query first, so a remembered bad filter cannot bring the crash back (2026-10-20).
|
|
424
|
+
- Boot: the top bar (menu button, name, the search pill or the phone search icon, the account spot), the sidebar menu rows as skeleton pills and the body sheet are drawn from the first frame at the real shell's places (inline CSS in index.html, then the same `BootFrame` while the session is checked). Until the menu (permissions) is read, the real sidebar keeps skeleton rows (`Sidebar loading`), never a blank column (2026-10-20 round 6: the header buttons came at 900ms, the sidebar items at 1460ms). Retrying a failed session check keeps the failure card with a spinner on the button; no blank frame between.
|
|
425
|
+
- No permission: cannot see the data, do not draw the slot. Can see but cannot change, a disabled control + one reason tooltip.
|
|
426
|
+
**Why:** The Still Board and Permissions rules already say this. 15 error titles had no object.
|
|
427
|
+
**Where:** 15 error titles, 결제 꺼짐.
|
|
428
|
+
|
|
429
|
+
### Loading
|
|
430
|
+
|
|
431
|
+
(2026-10-25, loading system audit.) One loading system for every screen. P20 still holds; this section says which piece to use and how the progress light, assistive tech and tests read it.
|
|
432
|
+
|
|
433
|
+
**L1 Three pieces.**
|
|
434
|
+
- **Screen skeleton.** While the screen itself is not known yet: the route chunk, the session check, the organization list. The shell draws it: `PageLoading` (title bar and one panel) in the sheet, labelled "화면 불러오는 중", and the boot frame before the shell exists. A screen never draws a blank sheet or a spinner while it waits.
|
|
435
|
+
- **Component skeleton.** While a panel, table, chart, rail group or value waits for its data. It sits in the final layout at the final size: `TableSkeletonRows` in the real table under the real head, `ChartSkeleton` in the chart frame, `RailRow loading`, `SkeletonText` in the text line box, `SkeletonCard`/`SkeletonArea` for a panel. When the data lands the content replaces it in place (no layout shift, CLS under 0.01 per screen).
|
|
436
|
+
- **Inline spinner.** Only inside a button that is doing the work the user asked for (save, more, retry; `Button loading`). Never the only content of a page, panel or dialog. A control that waits for its options (a select, a combobox) stays in place, disabled, with the reason as its description.
|
|
437
|
+
|
|
438
|
+
**L2 Timing.** A skeleton holds its place invisibly for `--delay-skeleton` (200ms), then fades in over `--dur-base`. An answer within 200ms never shows it. The content replaces it at once.
|
|
439
|
+
|
|
440
|
+
**L3 Progress light.** It runs while main shows that the screen is loading. The signal is any `role=status aria-busy` element or any skeleton pill (`.sk-glass`) inside `main`. It starts when that lasts past 200ms and keeps running while any screen-level loading is visible: the boot frame, the screen skeleton, every component skeleton, and pieces that arrive late (the verdict sentence, a forecast in a table row). It ends when the last of them is gone. It never runs for loading outside `main` (the top bar picker, dialogs, sheets, menus), for a panel that waits on the user's own request (`data-panel-busy`, the Playground response), or for a button spinner. A route chunk that was not preloaded is a loading screen too: `ScreenSuspense` draws `PageLoading` in place of the old screen while `lazyPage` waits (the router transition kept the old screen with no signal).
|
|
441
|
+
|
|
442
|
+
**L4 Assistive tech.** Each loading region announces once. One status per screen skeleton, table card (`Table` says it outside the hidden table), panel or dialog. Pieces inside an announced region are bare (`SkeletonBar`, `SkeletonText`, `ChartSkeleton bare`, `TableSkeletonRows` in a `Table`). A loading table or rail group is `aria-hidden` and `inert` (`placeholder.tsx`), so a placeholder is never read or focused as loaded content. Rows appended under loaded rows (더 보기) use `TableSkeletonRows more`: the loaded rows stay readable. Dialogs and sheets are outside `main`; their skeleton carries its own one status.
|
|
443
|
+
|
|
444
|
+
**L5 Tests.** A screen spec waits for its own region, table or heading, then asserts. Do not wait for `status` "불러오는 중" without scoping it to that region: the shell skeleton exists first (it is named "화면 불러오는 중" for this reason). Use `hidden: true` to look into a loading table or rail group. Wait for loaded content with `findBy…` on the content, not on the skeleton going away. To test the progress light, wait for `[data-progress]` `data-state="run"` (real timers), or advance fake timers past 200ms plus one frame.
|
|
445
|
+
|
|
446
|
+
**Why** The light did not run while the first skeletons were on screen: the boot frame and the organization wait had an empty `main`, a chunk wait kept the old screen, and late pieces had no status, so the light ended while skeletons were still visible (2026-10-25 dev, slow network). Screen readers heard "불러오는 중" up to four times for one panel.
|
|
447
|
+
**Where** `design-system` `AppShell` (`useLoadProgress`), `skeleton/*`, `table/TableSkeleton`, `chart/ChartSkeleton`; `console-ui` `shell/PageLoading.tsx` (`PageLoading`, `ScreenSuspense`), `shell/BootFrame.tsx`, `areas/lazyPage.ts`.
|
|
448
|
+
|
|
449
|
+
## Do's and Don'ts
|
|
450
|
+
|
|
451
|
+
### Do:
|
|
452
|
+
- **Do** draw every chart with palette D by rank (ink, blue, light blue, periwinkle, then grey 그 외). A chart with one series is ink only. Orange is for spend only, never a series.
|
|
453
|
+
- **Do** carry links, selection, navigation state and suggestions in ink: Control Room Ink, Instrument Gray, Muted Ink.
|
|
454
|
+
- **Do** mark blocking warnings in Block Crimson #A3124E at 600 with a crimson warning icon.
|
|
455
|
+
- **Do** give each screen one glass hero pane at most and set every other section on the sheet without a box (G2). Tables are the exception: each sits in a table card, border only (Patterns P4).
|
|
456
|
+
- **Do** keep controls at 28px (24 inside tables), panel heads at 36px, table rows at 44px and card padding at 14px.
|
|
457
|
+
- **Do** give every state-changing module its final height up front and crossfade states in place (200ms ease-out).
|
|
458
|
+
- **Do** set every number in tabular figures (Pretendard `tnum`) and keep the type scale at 12/13/14/18/20/24/40.
|
|
459
|
+
- **Do** keep every padding, margin and gap on the 4px grid, and keep labels quieter than their values.
|
|
460
|
+
- **Do** keep every colour a variable so a dark theme can be added later.
|
|
461
|
+
|
|
462
|
+
### Don't:
|
|
463
|
+
- **Don't** put a coloured vertical bar on the left of anything: selection, hover, alerts, cards, list items.
|
|
464
|
+
- **Don't** use pale orange, beige, pink, apricot or yellow surfaces, pastel chips or washes, gradients, or emoji.
|
|
465
|
+
- **Don't** introduce colours outside the defined tokens.
|
|
466
|
+
- **Don't** put a crimson or tinted surface behind a warning; blocking warnings follow The Block Warning Rule.
|
|
467
|
+
- **Don't** set project names, headings or status sentences in JetBrains Mono.
|
|
468
|
+
- **Don't** put a pane inside a pane, glass on glass (a list opened inside a dialog is opaque), a rim + inner pane on a floating surface, or `backdrop-filter` on anything but the floating layer (`.glass-float`, `.glass-scrim`) and the scrolled top bar.
|
|
469
|
+
- **Don't** add shadows to cards or strips; the G2 shadows belong to the sheet, the hero, pills and the current menu item.
|
|
470
|
+
- **Don't** ship a dark theme yet.
|