From adc12d7f0b2468ee1926a36b55840daf270f1770 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 2 Oct 2026 02:40:09 +0800 Subject: [PATCH 1/6] style(ui): console text follows official sub2api The console theme's text now follows how official sub2api renders it, while keeping the console layout, surfaces, brand accent and stacked tables: - Font: the official system-ui stack (Microsoft YaHei UI on zh-CN Windows), no web fonts; Geist / Geist Mono files and the runtime MiSans stylesheet are removed. Mono only for identifiers and code (official stack + CJK fallbacks). - Sizes and weights: official text-sm 14/20, text-xs 12/16; headers 12/500 (usage tables) or 14/500 (TablePageLayout tables), uppercase only on headers and sidebar section titles; KPI values 20/700; no negative tracking. - Colour: class-driven official Tailwind greys (#111827 / #374151 / #4b5563 / meta #616977; dark #f3f4f6 / #d1d5db / #a4abb6), meta clamped to 4.5:1 on every neutral fill; official figure and tag colours return (cost, cache, account cost, latency, group / type / billing tags). The two-tone data text roles are deleted; dark selected rows lift their meta greys. - Layout: no horizontal overflow in Chinese at 1280-1920 (API key and user wrap recipes extended, accounts label gutter 50/56px, status header width); English TablePageLayout headers 12px and wrapping, recipes below 1680px. - Accounts name column measures names at 14px. Owner request 2026-10-01: text still hard to read, follow official sub2api. Co-Authored-By: Claude Opus 5.5 --- .downstream/native-domains.md | 21 +- THIRD_PARTY_NOTICES.md | 4 - .../fonts/geist/Geist-Variable.latin.woff2 | Bin 32968 -> 0 bytes .../geist/GeistMono-Variable.latin.woff2 | Bin 34216 -> 0 bytes frontend/src/assets/fonts/geist/OFL.txt | 92 -- frontend/src/style.css | 93 +- frontend/src/styles/console/controls.css | 146 +-- frontend/src/styles/console/data.css | 616 ++++------ frontend/src/styles/console/overlays.css | 283 ++--- .../src/styles/console/pages/accounts.css | 193 ++- .../styles/console/pages/dashboard-usage.css | 328 ++---- .../src/styles/console/pages/ops-channels.css | 1044 +++++++---------- .../console/pages/settings-ops-public.css | 655 +++++------ frontend/src/styles/console/pages/user.css | 337 +++--- .../pages/users-groups-proxies-keys.css | 552 +++------ frontend/src/styles/console/shell.css | 128 +- frontend/src/styles/console/tables.css | 222 +++- frontend/src/styles/console/tokens.css | 229 ++-- frontend/src/utils/flatTheme.ts | 31 +- frontend/src/views/admin/AccountsView.vue | 12 +- 20 files changed, 1958 insertions(+), 3028 deletions(-) delete mode 100644 frontend/src/assets/fonts/geist/Geist-Variable.latin.woff2 delete mode 100644 frontend/src/assets/fonts/geist/GeistMono-Variable.latin.woff2 delete mode 100644 frontend/src/assets/fonts/geist/OFL.txt diff --git a/.downstream/native-domains.md b/.downstream/native-domains.md index c46c558b8424..795a5b66359e 100644 --- a/.downstream/native-domains.md +++ b/.downstream/native-domains.md @@ -151,14 +151,13 @@ source validation. Health endpoints establish availability only. `observability.theme_enabled` (public `flat_theme_enabled`, default on) toggles `html.flat-theme`; switching it off restores the upstream look. The console look -lives only in the central layer: `frontend/src/styles/flat-theme.css` (tokens, the -Inter and Geist Mono Latin subsets; Chinese uses MiSans loaded at runtime from -Xiaomi's font service by `utils/flatTheme.ts`, falling back to system fonts), -`frontend/tailwind.config.js` (every palette, radius, shadow and gradient resolves -through a CSS variable whose fallback is the upstream value; `primary` is Apple blue -and the hue families map to Apple system colours), `frontend/src/style.css` -(upstream recipes plus a `flat-theme` console block: colour only where upstream has -colour, Liquid Glass only on chrome) and the shared components and layout. Page -files keep upstream class strings; after upstream merges, re-run the ops-repo -de-sweep (`artifacts/tmp/admin-rework/ui/desweep/desweep.py`) instead of restyling -pages. +lives only in the central layer: `frontend/src/styles/console/*.css` (tokens and +the console rules), `frontend/tailwind.config.js` (every palette, radius, shadow +and gradient resolves through a CSS variable whose fallback is the upstream value; +`primary` is the console's green accent and the hue families fold into five status +colours), `frontend/src/style.css` (upstream recipes plus a `flat-theme` block +that restates them for the console) and the shared components and layout. Text +uses the OS UI font stack of `frontend/src/styles/theme.css`, as upstream does; no +web fonts are shipped or loaded. Page files keep upstream class strings; after +upstream merges, re-run the ops-repo de-sweep +(`artifacts/tmp/admin-rework/ui/desweep/desweep.py`) instead of restyling pages. diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index a7082c57d085..b286bc99b548 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -38,10 +38,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -## Geist and Geist Mono - -The frontend includes the Geist and Geist Mono variable fonts (weights 100-900) by Vercel, taken from the npm package [`geist` 1.7.2](https://www.npmjs.com/package/geist) (`dist/fonts/geist-sans/Geist-Variable.woff2` and `dist/fonts/geist-mono/GeistMono-Variable.woff2`) and subset to the Latin range with fontTools (no other glyph changes). The fonts are distributed under the SIL Open Font License 1.1, included at `frontend/src/assets/fonts/geist/OFL.txt`. - ## Lucide The frontend includes icon shapes from [Lucide](https://lucide.dev) 0.469.0, copied without changes from the file `icon-nodes.json` of the npm package [`lucide-static` 0.469.0](https://www.npmjs.com/package/lucide-static/v/0.469.0) into `frontend/src/components/icons/lucide.ts`; `Icon.vue` draws them while the console theme is on. Lucide is distributed under the ISC License below. The Lucide icons derived from Feather are also covered by Feather's MIT License, reproduced after it. diff --git a/frontend/src/assets/fonts/geist/Geist-Variable.latin.woff2 b/frontend/src/assets/fonts/geist/Geist-Variable.latin.woff2 deleted file mode 100644 index 87ca17423f05efb43ed6d555d71a1804cdbda3f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32968 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 svg { height: 0.75rem; } -/* 4.2 DateRangePicker's trigger: the same field (a 14px calendar glyph, the value at 500, the 12px chevron). */ +/* 4.2 DateRangePicker's trigger: the same field (a 14px calendar glyph, the value at upstream's font-medium 500, the + 12px chevron). */ html.flat-theme .date-picker-trigger.date-picker-trigger { gap: 0.375rem; border-radius: var(--ui-radius-md); border-color: rgb(var(--ui-line-strong)); background-color: rgb(var(--ui-surface)); padding: 0.390625rem 0.5625rem 0.390625rem 0.625rem; - font-size: 0.8125rem; - line-height: 1.21875rem; + font-size: 0.875rem; + line-height: 1.25rem; color: rgb(var(--ui-ink)); box-shadow: none; transition-property: color, border-color, box-shadow; @@ -300,16 +295,17 @@ html.flat-theme .date-picker-trigger .date-picker-chevron > svg { height: 0.75rem; } -/* 4.3 Inside the picker (the panel itself is overlays.css'): presets as quiet 30px choices, the current one on the - accent tint (the reference's selected filter); mono field labels; 30px date fields; the apply button is the solid - primary (tokens.css --ui-solid: the accent fill, a deeper step on hover). */ +/* 4.3 Inside the picker (the panel itself is overlays.css'): presets as quiet choices in upstream's type (text-xs + font-medium text-gray-600 dark:text-gray-400), the current one on the accent tint at the same weight (the + reference's selected filter); the field labels are upstream's text-xs font-medium meta labels; 30px date fields; + the apply button is the solid primary (tokens.css --ui-solid: the accent fill, a deeper step on hover). */ html.flat-theme .date-picker-preset.date-picker-preset { border-radius: var(--ui-radius-md); padding: 0.3125rem 0.625rem; - font-size: 0.78125rem; - line-height: 1.171875rem; + font-size: 0.75rem; + line-height: 1rem; font-weight: 500; - color: rgb(var(--ui-muted)); + color: rgb(var(--theme-text-gray-600)); transition-property: color, background-color; transition-duration: var(--ui-duration); transition-timing-function: var(--ui-ease); @@ -323,17 +319,15 @@ html.flat-theme .date-picker-preset.date-picker-preset:hover { html.flat-theme .date-picker-preset.date-picker-preset.date-picker-preset-active { background-color: rgb(var(--ui-primary-soft)); color: var(--ui-link); - font-weight: 600; } html.flat-theme .date-picker-label.date-picker-label { margin-bottom: 0.375rem; - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); line-height: var(--ui-label-leading); font-weight: 500; letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; color: rgb(var(--ui-label)); } @@ -342,8 +336,8 @@ html.flat-theme .date-picker-input.date-picker-input { border-color: rgb(var(--ui-line-strong)); background-color: rgb(var(--ui-surface)); padding: 0.265625rem 0.5rem; - font-size: 0.8125rem; - line-height: 1.21875rem; + font-size: 0.875rem; + line-height: 1.25rem; color: rgb(var(--ui-ink)); box-shadow: none; } @@ -362,9 +356,9 @@ html.flat-theme .date-picker-apply.date-picker-apply:hover { border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-solid)); padding: 0.265625rem 0.75rem; - font-size: 0.8125rem; - line-height: 1.21875rem; - font-weight: 600; + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 500; color: rgb(var(--ui-on-solid)); } @@ -543,9 +537,11 @@ html.flat-theme input:is([type="checkbox"], [type="radio"]):focus-visible { /* 7.1 Segmented controls built from utilities (upstream's pattern: a gray-100 / gray-50 box with a 4px or 2px inset whose children are all buttons, the current one bg-white with a shadow) become the reference's SlidingTabs (REF-CONSOLE §8.5) in palette C: a grey track (its 1px frame the track colour), radius 8, a 2px inset, no gaps; - segments 14 x 6px, 12.5px / 500, muted, ink on hover; the current segment is the raised pill (tokens.css - --ui-thumb: the surface, a soft shadow and a 1px inset ring that marks it at >= 3:1 on the track; a lighter - graphite in dark), its label ink at 600, fading in 200ms (upstream has no thumb to slide). The platform hues of + segments 14 x 6px in upstream's .tab type (14/20/500, text-gray-600 / dark:text-dark-400; upstream's text-xs + switches 12/16), ink on hover; the + current segment is the raised pill (tokens.css --ui-thumb: the surface, a soft shadow and a 1px inset ring that + marks it at >= 3:1 on the track; a lighter graphite in dark), its label ink at the same weight, fading in 200ms + (upstream has no thumb to slide). The platform hues of the account dialogs' segments go with it (monochrome identity, D3). data-ui="seg" opts a fork component into the same look. Perf pass: the utility-built boxes (a gray-100 / gray-50 box with a 4px or 2px inset whose children are all buttons, @@ -568,16 +564,27 @@ html.flat-theme [data-ui~="seg"] > button { border-radius: var(--ui-radius-md); background-color: transparent; padding: var(--ctl-seg-pad-y) var(--ctl-seg-pad-x); - font-size: 0.78125rem; - line-height: 1.171875rem; + font-size: 0.875rem; + line-height: 1.25rem; font-weight: 500; - color: rgb(var(--ui-muted)); + color: rgb(var(--theme-text-gray-600)); box-shadow: none; transition-property: color, background-color; transition-duration: var(--ui-duration-slide); transition-timing-function: var(--ui-ease-out); } +/* upstream's small switches keep their text-xs type (12 / 16), as on official, so they read below the card title they + sit beside: the distribution charts' metric and view switches (text-xs buttons), the monitor's window switch (a + text-xs box) and the dashboard's 最近使用 (Top 12) metric switch (data-ui="seg", text-xs buttons); the rest of the + recipe (inset, thumb, colours) is unchanged */ +html.flat-theme :is(div, nav)[data-ui~="seg-box"][data-ui][data-ui][data-ui]:not(uipad):not(uipad) > button[class~="text-xs"], +html.flat-theme :is(div, nav)[data-ui~="seg-box"][class~="text-xs"][data-ui][data-ui][data-ui]:not(uipad):not(uipad) > button, +html.flat-theme [data-ui~="seg"] > button[class~="text-xs"] { + font-size: 0.75rem; + line-height: 1rem; +} + html.flat-theme :is(div, nav)[data-ui~="seg-box"][data-ui][data-ui][data-ui]:not(uipad):not(uipad) > button:hover, html.flat-theme [data-ui~="seg"] > button:hover { color: rgb(var(--ui-ink)); @@ -587,7 +594,7 @@ html.flat-theme :is(div, nav)[data-ui~="seg-box"][data-ui][data-ui][data-ui]:not html.flat-theme [data-ui~="seg"] > button:is([aria-pressed="true"], [aria-checked="true"], [aria-selected="true"], [data-active]:not([data-active="false"])) { background-color: rgb(var(--ui-thumb)); color: rgb(var(--ui-ink)); - font-weight: 600; + font-weight: 500; box-shadow: var(--ui-thumb-shadow); } @@ -619,7 +626,7 @@ html.flat-theme :is(div, nav)[data-ui~="seg-box"][data-ui][data-ui][data-ui]:not colours"): an unchosen segment draws its platform glyph in the platform's identity colour (tokens.css identity hues: >= 4.5:1 on the surface, so >= 3:1 as a glyph, both themes); the chosen segment is the pill with an ink glyph (7.1). The eleven segments are known by their place (CreateAccountModal's two rows of five and six, the - first one marked data-tour="account-form-platform"); labels stay muted. (wave 2, reported by the accounts agent) */ + first one marked data-tour="account-form-platform"); labels keep the segment grey. (wave 2, reported by the accounts agent) */ html.flat-theme [data-tour="account-form-platform"] > button:nth-child(1) { --ctl-seg-glyph: var(--ui-id-orange); } html.flat-theme [data-tour="account-form-platform"] > button:nth-child(2) { --ctl-seg-glyph: var(--ui-id-green); } html.flat-theme [data-tour="account-form-platform"] > button:nth-child(3) { --ctl-seg-glyph: var(--ui-id-blue); } @@ -674,10 +681,10 @@ html.flat-theme .settings-tab.settings-tab { border-radius: var(--ui-radius-md); background: transparent; padding: var(--ctl-seg-pad-y) var(--ctl-seg-pad-x); - font-size: 0.78125rem; - line-height: 1.171875rem; + font-size: 0.875rem; + line-height: 1.25rem; font-weight: 500; - color: rgb(var(--ui-muted)); + color: rgb(var(--theme-text-gray-600)); box-shadow: none; transition-property: color, background-color; transition-duration: var(--ui-duration-slide); @@ -696,7 +703,7 @@ html.flat-theme .settings-tab.settings-tab:hover { html.flat-theme .settings-tab.settings-tab.settings-tab-active { background-color: rgb(var(--ui-thumb)); color: rgb(var(--ui-ink)); - font-weight: 600; + font-weight: 500; box-shadow: var(--ui-thumb-shadow); } @@ -730,16 +737,16 @@ html.flat-theme .tab.tab-active:is(:focus, :focus-visible) { } /* 7.3 Underline tabs (使用记录's detail tabs, the channel dialog's tabs) take the reference's CodeTabs underline - (REF-CONSOLE §8.15): a 2px accent rule under the current tab, its text ink; the others muted, ink on hover, no - rule; 8 x 10px like its tabs, 12.5px / 500, 14px glyphs. Platform hues on the channel tabs go (monochrome - identity, D3). */ + (REF-CONSOLE §8.15): a 2px accent rule under the current tab, its text ink; the others in the tab grey of the + .tab recipe (#4b5563 / #a4abb6), ink on hover, no rule; 8 x 10px like its tabs, upstream's text-sm font-medium + (14/20/500), 14px glyphs. Platform hues on the channel tabs go (monochrome identity, D3). */ html.flat-theme :is([data-testid="usage-detail-tab"], .channel-tab.channel-tab) { gap: 0.375rem; padding: 0.5rem 0.625rem; - font-size: 0.78125rem; - line-height: 1.171875rem; + font-size: 0.875rem; + line-height: 1.25rem; font-weight: 500; - color: rgb(var(--ui-muted)); + color: rgb(var(--theme-text-gray-600)); transition-property: color, border-color; transition-duration: var(--ui-duration); transition-timing-function: var(--ui-ease); @@ -822,24 +829,24 @@ html.flat-theme button[data-ui~="option-card"][data-ui][data-ui][data-ui][data-u } html.flat-theme button[data-ui~="option-card"][data-ui][data-ui][data-ui][data-ui][data-ui][data-ui]:not(uipad) > div:last-child > span:first-child { - font-size: 0.8125rem; - line-height: 1.21875rem; - font-weight: 600; + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 500; color: rgb(var(--ui-ink)); } html.flat-theme button[data-ui~="option-card"][data-ui][data-ui][data-ui][data-ui][data-ui][data-ui]:not(uipad) > div:last-child > span:last-child:not(:first-child) { font-size: 0.75rem; - line-height: 1.125rem; + line-height: 1rem; color: rgb(var(--ui-muted)); } /* 8.2 Filter pills (data-ui="filter-pill"; the reference's FilterMenu trigger, REF-CONSOLE §8.6): 30px, 10px in, gap 6, - radius 6, the control outline on the surface, 12.5px / 500 ink-soft, ink on hover, a 12px chevron. With a - data-ui="filter-count" badge inside it is active: the accent at 40 % as its edge, the accent tint, accent text and a - Geist Mono 10.5px count. (Perf pass, kept has-test: the badge is rendered only while a filter value is selected, - so the pill's own look depends on runtime content; the test sits in the subject compound of the hooked pill with a - child argument, the badge being the pill's direct child.) */ + radius 6, the control outline on the surface, upstream's control text (14/20/500, ink-soft), ink on hover, a 12px + chevron. With a data-ui="filter-count" badge inside it is active: the accent at 40 % as its edge, the accent tint, + accent text and a 12px count. (Perf pass, kept has-test: the badge is rendered only while a filter value is + selected, so the pill's own look depends on runtime content; the test sits in the subject compound of the hooked + pill with a child argument, the badge being the pill's direct child.) */ html.flat-theme [data-ui~="filter-pill"] { display: inline-flex; align-items: center; @@ -850,8 +857,8 @@ html.flat-theme [data-ui~="filter-pill"] { border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-surface)); padding: 0 0.625rem; - font-size: 0.78125rem; - line-height: 1.171875rem; + font-size: 0.875rem; + line-height: 1.25rem; font-weight: 500; color: rgb(var(--ui-ink-soft)); box-shadow: none; @@ -877,8 +884,7 @@ html.flat-theme [data-ui~="filter-pill"] [data-ui~="filter-count"] { border-radius: 0; background: none; padding: 0; - font-family: var(--ui-font-mono); - font-size: 0.65625rem; + font-size: 0.75rem; line-height: 1rem; font-weight: 400; color: inherit; @@ -933,9 +939,9 @@ html.flat-theme :is([data-testid="fill-related-models"], [data-testid="sync-upst border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-surface)); color: rgb(var(--ui-ink)); - font-size: 0.8125rem; - line-height: 1.21875rem; - font-weight: 600; + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 500; transition-property: color, background-color, border-color; transition-duration: var(--ui-duration); transition-timing-function: var(--ui-ease); diff --git a/frontend/src/styles/console/data.css b/frontend/src/styles/console/data.css index cbf2facb6625..df12b4d1aac8 100644 --- a/frontend/src/styles/console/data.css +++ b/frontend/src/styles/console/data.css @@ -19,7 +19,8 @@ * data-ui="skeleton" components/common/Skeleton.vue root * Opt-in hooks for page owners (wave 2, §6.4): data-ui~="platform-chip" / "platform-text" (an element upstream colours * by platform with Tailwind hue classes keeps that platform colour, decision 2) and data-ui~="category-chip" (a - * category label drawn as the reference's neutral chip: request type, billing mode, protocol, role ...). + * category label: request type, billing mode, protocol, role ...; the reference's chip shape around the tag colours + * its template gives it, as official). * Hooks used from other owners: data-ui~="bulk-bar" (AccountBulkActionsBar, the accounts agent), data-table and * data-column (DataTable), aria-sort on sortable headers. * @@ -50,12 +51,10 @@ html.flat-theme { --ui-table-edge-x: 16px; /* width of DataTable's own selection column (checkbox + insets); the second sticky column starts after it */ --ui-table-select-w: 44px; - /* badges and status pills: Geist Mono uppercase (the reference's 9.5px pills read 10px here; 11px in the - Chinese interface, like the mono label) */ - --ui-badge-size: 10px; + /* badges, status pills and data chips in cells: official .badge and tags are text-xs (12/16), sentence case */ + --ui-badge-size: 12px; --ui-badge-leading: 16px; - /* data chips in cells (the reference's tool / prompt chips, Geist Mono 10-10.5px) */ - --ui-chip-size: 10.5px; + --ui-chip-size: 12px; /* table scrollbars stay visible (sideways scrolling must be discoverable, as the reference's Windows scrollbars are); the thumb is the reference's #8b8b8b grey at >= 3:1 on the surface */ --ui-scroll-thumb: 142 142 142; @@ -78,19 +77,22 @@ html.flat-theme { --ui-row-pick-hover: color-mix(in srgb, rgb(var(--ui-primary-soft)) 100%, rgb(var(--ui-ink)) 3%); /* the bulk bar floats only where its owner places it over content */ --ui-bulk-bar-shadow: none; - /* Table header labels (wave 2, the management agent's decision): the muted grey instead of the label grey, so a - header under an open menu's popover shadow (#f6f6f6-#f8f8f8 behind it) still reads 4.5:1 (#737373 fell to - 4.39); 5.25 on the surface. Dark keeps the label grey (6.3 on the surface). Only table headers; the other mono - labels keep --ui-label. */ + /* Table headers, official's two families (section 1): the usage tables' header, text-gray-500 / + dark:text-gray-400 = the meta grey (#616977 / #a4abb6, 5.53 / 6.94:1 on the surface) at 12/16; and + TablePageLayout's scoped th, text-gray-600 / dark:text-dark-300 (#4b5563 / #d1d5db) at 14/20. */ --ui-table-head: var(--ui-muted); + --ui-table-head-tpl: 75 85 99; + --ui-th-size: 12px; + --ui-th-leading: 16px; /* Badge text (light): each status text a step deeper than the status token, so a badge under a popover shadow (up to 6 % of the shadow colour over its soft fill) keeps 4.5:1 (the ACTIVE success badge fell to 4.45, the - danger badge to 4.41). Dark reads the status tokens (>= 6:1). */ + danger badge to 4.41). Dark reads the status tokens (>= 6:1). The grey badge is official .badge-gray's + text-gray-700 (dark: dark-300, the soft ink). */ --ui-badge-success: 26 122 61; --ui-badge-danger: 190 52 52; --ui-badge-warning: 172 79 9; --ui-badge-link: 19 120 64; - --ui-badge-muted: 104 104 104; + --ui-badge-muted: 55 65 81; /* Time-series chart palette (read by composables/usePresentationColors.ts as `trend`): the reference's usage series (REF §8.14 --usage-series-*) in the hue order of upstream's series (blue, emerald, amber, red, violet, pink, teal, orange, indigo, lime, cyan, purple), so a chart that picks a series by index keeps its hue. Lines @@ -110,23 +112,22 @@ html.flat-theme { --ui-chart-trend-12: #7850c8; } -html.flat-theme:lang(zh) { - --ui-badge-size: 11px; - --ui-chip-size: 11px; -} - html.flat-theme.dark { --ui-table-head: var(--ui-label); + --ui-table-head-tpl: 209 213 219; /* danger text on the dark selected fill (#39433f, hover #404946): Tailwind red-300 #fca5a5, 5.40:1 on the selected fill and 4.88 on its hover (#f87171 is 3.71 / 3.35; the pre-C #f69898 falls to 4.35 on the hover) */ --ui-selected-danger: 252 165 165; + /* meta text in a dark selected row (section 1, D11): #b4bac4, 5.25:1 on the selected fill and 4.71 on its hover + (the meta grey #a4abb6 is 4.43 / 3.97 there), still under the data ink #d1d5db */ + --ui-selected-meta: 180 186 196; /* the dark selected row under the pointer lightens by 4 % of the ink (#404946) */ --ui-row-pick-hover: color-mix(in srgb, rgb(var(--ui-primary-soft)) 100%, rgb(var(--ui-ink)) 4%); --ui-badge-success: var(--ui-success); --ui-badge-danger: var(--ui-danger); --ui-badge-warning: var(--ui-warning); --ui-badge-link: 182 247 200; - --ui-badge-muted: var(--ui-muted); + --ui-badge-muted: var(--ui-ink-soft); --ui-scroll-thumb: 122 122 122; --ui-scroll-thumb-hover: 156 156 156; /* the reference's dark fills: the colour at 42 % over its surface #1a1a1a (kept as they are on palette C's @@ -160,17 +161,19 @@ html.flat-theme.dark { /* ================================================================== 1 · DataTable (desktop and, theme on, phones) */ -/* The card around the table is the layout's (section 2); the wrapper scrolls both ways. */ +/* The card around the table is the layout's (section 2); the wrapper scrolls both ways. The body text is official's + td text-sm (14/20). */ html.flat-theme .table-wrapper > table[data-ui~="data-table"]:is(table) { border-collapse: collapse; - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; } /* Dark: upstream writes table text as text-gray-900 dark:text-white; the gray is the ink, but text-white reads - --theme-on-fill (255 255 255), so figures, key and account names were pure #ffffff at 500-600 (17.4:1, a glare on - the graphite). Inside tables white text is the ink (palette C); a solid fill still sets its own --theme-on-fill - (tailwind.config.js onPrimaryFill, the .btn-primary recipe, controls.css 10.1). */ + --theme-on-fill (255 255 255), so figures, key and account names were pure #ffffff (a glare on the graphite, + owner 2026-09-30). Inside tables white text is the ink: #f3f4f6, official's own body ink (dark:text-gray-100, + 14.58:1 on the surface). A solid fill still sets its own --theme-on-fill (tailwind.config.js onPrimaryFill, the + .btn-primary recipe, controls.css 10.1). Owner option: deleting this rule gives official's pure #fff in tables. */ html.flat-theme.dark .table-wrapper { --theme-on-fill: var(--ui-ink); } @@ -183,26 +186,34 @@ html.flat-theme .table-wrapper > table > tbody.table-body { background-color: rgb(var(--ui-surface)); } -/* Header: 40px, the mono label (REF §8.7: Geist Mono 10/15, .12em, uppercase, ink-faint -> --ui-label) at 400 - (palette C: the labels calm down; the reference's 500), surface fill, 1px structural line underneath. th sits on - the 13px / 19.5px strut, as the reference's does. */ +/* Header: official's (the usage tables' text-xs font-medium uppercase tracking-wider in the meta grey; the + TablePageLayout tables' 14/20 in #4b5563 / #d1d5db, below), on the surface fill with a 1px structural line + underneath; 40px with the 19.5px slot below. */ html.flat-theme .table-wrapper .table-header .sticky-header-cell { padding: var(--ui-table-cell-y) var(--ui-table-cell-x); border-bottom-width: 0; background-color: rgb(var(--ui-surface)); box-shadow: inset 0 -1px 0 rgb(var(--ui-line)); color: rgb(var(--ui-table-head)); - font-family: var(--ui-font-mono); - font-size: var(--ui-label-size); - font-weight: 400; - line-height: 19.5px; - letter-spacing: var(--ui-label-tracking); + font-family: var(--ui-font); + font-size: var(--ui-th-size); + font-weight: 500; + line-height: var(--ui-th-leading); + letter-spacing: 0.05em; text-transform: uppercase; white-space: nowrap; vertical-align: middle; transition: color var(--ui-duration) var(--ui-ease); } +/* TablePageLayout tables (accounts, keys, users, groups, proxies, ...): upstream's layout scopes its th to text-sm + font-medium text-gray-600 dark:text-dark-300 */ +html.flat-theme .table-page-layout .table-wrapper { + --ui-th-size: 14px; + --ui-th-leading: 20px; + --ui-table-head: var(--ui-table-head-tpl); +} + html.flat-theme .table-wrapper .table-header .sticky-header-cell:first-child { padding-left: var(--ui-table-edge-x); } @@ -211,8 +222,7 @@ html.flat-theme .table-wrapper .table-header .sticky-header-cell:last-child { padding-right: var(--ui-table-edge-x); } -/* the reference's header has no hover fill; the label darkens a step on a sortable column (the reference means - to, its .mono-label overrides it) */ +/* the reference's header has no hover fill; the label darkens to the soft ink on a sortable column */ html.flat-theme .table-wrapper .table-header .sticky-header-cell[aria-sort]:hover { background-color: rgb(var(--ui-surface)); } @@ -224,7 +234,7 @@ html.flat-theme .table-wrapper .table-header .sticky-header-cell[aria-sort][data color: rgb(var(--ui-ink-soft)); } -/* header slot content (labels with a help icon) keeps the label's line */ +/* header slot content (labels with a help icon) keeps a 19.5px line, so the header row stays official's 40px */ html.flat-theme .table-wrapper .table-header .sticky-header-cell > div { min-height: 19.5px; } @@ -281,34 +291,19 @@ html.flat-theme .table-wrapper > table > tbody.table-body > tr + tr { border-top-color: rgb(var(--ui-line)); } -/* numbers: right-aligned columns (column.class text-right) use tabular figures; mono cells already are */ +/* numbers: right-aligned columns (column.class text-right) use tabular figures (YaHei UI digits are tabular + already; this aligns them on macOS too) */ html.flat-theme .table-wrapper > table[data-ui~="data-table"]:is(table) :is(th, td)[class~="text-right"] { font-variant-numeric: tabular-nums; } -/* Figures in Geist Mono (REF §8.7: numeric cells Mono 12 / 13, tabular): the columns every table names the same way - for a count, an amount, a price, a rate, an id or a duration. Text in the same cell (a unit, a 「充值」 button) takes - MiSans / Geist through the mono stack's fallbacks. Alignment stays the column's. The same columns drawn as lines of a - stacked cell (tables.css) keep it. */ -html.flat-theme .table-wrapper > table > tbody.table-body > tr > td [data-ui="cell-line"]:is([data-line="id"], [data-line="user_id"], [data-line="group_id"], [data-line="requests"], [data-line="tokens"], [data-line="total_tokens"], [data-line="input_tokens"], [data-line="output_tokens"], [data-line="cache_tokens"], [data-line="cost"], [data-line="actual_cost"], [data-line="balance"], [data-line="balance_after"], [data-line="amount"], [data-line="pay_amount"], [data-line="order_amount"], [data-line="price"], [data-line="rebate_amount"], [data-line="total_rebate"], [data-line="bonus_amount"], [data-line="concurrency"], [data-line="current_concurrency"], [data-line="priority"], [data-line="rate_multiplier"], [data-line="sort_order"], [data-line="latency_ms"], [data-line="status_code"], [data-line="account_count"], [data-line="group_count"], [data-line="validity_days"]) > [data-ui="cell-line-value"], -html.flat-theme .table-wrapper > table > tbody.table-body > tr > td:is([data-column="id"], [data-column="user_id"], [data-column="group_id"], [data-column="requests"], [data-column="tokens"], [data-column="total_tokens"], [data-column="input_tokens"], [data-column="output_tokens"], [data-column="cache_tokens"], [data-column="cost"], [data-column="actual_cost"], [data-column="balance"], [data-column="balance_after"], [data-column="amount"], [data-column="pay_amount"], [data-column="order_amount"], [data-column="price"], [data-column="rebate_amount"], [data-column="total_rebate"], [data-column="bonus_amount"], [data-column="concurrency"], [data-column="current_concurrency"], [data-column="priority"], [data-column="rate_multiplier"], [data-column="sort_order"], [data-column="latency_ms"], [data-column="status_code"], [data-column="account_count"], [data-column="group_count"], [data-column="validity_days"]) { - font-family: var(--ui-font-mono); - font-size: 12.5px; - font-variant-numeric: tabular-nums; -} - /* Small data chips inside cells (upstream's `inline-flex rounded px-2 py-0.5 text-xs` spans: group, request type, - billing mode, tags, cache marks): the reference's tool / prompt chip shape (REF §8.8: 4px radius, 6 / 1 inset, - Geist Mono 10.5px); each keeps its colour family (the status families of tokens.css). A chip that carries a - data-ui hook (platform-chip, category-chip, a page owner's) is its owner's. */ + billing mode, tags, cache marks): the reference's tool / prompt chip shape (REF §8.8: 4px radius, 6 / 1 inset); type + and colour stay the template's (official; the hues fold into the status families of tokens.css). A chip that + carries a data-ui hook (platform-chip, category-chip, a page owner's) is its owner's. */ html.flat-theme .table-wrapper > table > tbody.table-body > tr > td span:not([data-ui]):is([class~="inline-flex"], [class~="inline-block"])[class~="rounded"]:is([class~="px-1"], [class~="px-1.5"], [class~="px-2"]):is([class~="py-0.5"], [class~="py-px"]):is([class~="text-xs"], [class~="text-[10px]"], [class~="text-[11px]"]) { padding: 1px 6px; border-radius: var(--ui-radius-sm); - font-family: var(--ui-font-mono); - font-size: var(--ui-chip-size); - font-weight: 500; - line-height: 16px; - letter-spacing: 0.01em; } /* Rows: hover and keyboard focus inside a data row = --ui-hover (REF §8.7; upstream's gray-50 is the canvas colour @@ -338,39 +333,33 @@ html.flat-theme .table-wrapper .table-body > tr:where([data-row-id]):is([class~= background-color: var(--ui-row-pick-hover); } -/* On the dark selected fill (#39433f; #404946 under the pointer) upstream's secondary greys fall short - (dark:text-dark-500 #a0a0a9 3.95:1 / 3.57 on the hover, dark:text-dark-400 #a3a3ac 4.10 / 3.70, text-gray-500 - #b0b0b9 4.76 / 4.31): in a selected row they take the soft ink (#b8b8c0, 5.20:1; 4.70 on the selected hover). - Light is unchanged. Every selectable table. (wave 2, reported by the management and user-side agents; palette C: - the muted grey the rule used to take fell to 4.31 on the hover.) The labels of stacked cell lines - (data-ui="cell-line-label", DataTable; the tables track draws them in the muted grey) are secondary text too. - DataTable's own selection is the row's class; a page's own select column makes the row publish --ui-row-picked - (dark theme only, so a light-theme selection never restyles the row's cells), which its cells read through a - style query (Chromium 111+, Safari 18+). Firefox has no style queries: it keeps the pre-pass selector in the - @supports (-moz-appearance: none) blocks, which Chromium never loads. Same weights (0,8,4) as the pre-pass rules. - The same row also lifts the data text roles of section 12 (12.4) for its cells, which inherit them: a custom - property needs no style query, so this works in every browser without another relational selector. */ +/* On the dark selected fill (#39433f; #404a46 under the pointer) the meta grey #a4abb6 is 4.43 / 3.97:1: a selected + row lifts its meta greys to --ui-selected-meta (#b4bac4, 5.25 / 4.71), still under the data ink #d1d5db. Custom + properties on the row, inherited by its cells, so no rule needs a relational selector on a cell: a page's own select + column is the checked box this row rule already tests (subject-position :has(), static markup only); DataTable's own + selection is the row's class (below). --ui-row-picked stays for the danger rules (section 1, below). Light is + unchanged (meta #616977 is 5.12 / 4.82 on the light selected fills). */ html.flat-theme.dark .table-wrapper .table-body > tr:where([data-row-id]):has(> td[data-column="select"] input[type="checkbox"]:checked) { --ui-row-picked: 1; - --ui-data-secondary: var(--ui-data-picked); - --ui-data-reference: var(--ui-data-picked); - --ui-data-label: var(--ui-data-picked); -} - -html.flat-theme.dark .table-wrapper .table-body > tr[class~="bg-primary-50/40"][class][class]:is(tr):is(tr) :is([class~="text-gray-400"], [class~="text-gray-500"], [class~="dark:text-dark-400"], [class~="dark:text-dark-500"], [data-ui="cell-line-label"]) { - color: rgb(var(--ui-ink-soft)); -} - -@container style(--ui-row-picked: 1) { - html.flat-theme.dark .table-wrapper .table-body > tr[data-row-id][data-row-id][data-row-id]:is(tr):is(tr) :is([class~="text-gray-400"], [class~="text-gray-500"], [class~="dark:text-dark-400"], [class~="dark:text-dark-500"], [data-ui="cell-line-label"]) { - color: rgb(var(--ui-ink-soft)); - } -} - -@supports (-moz-appearance: none) { - html.flat-theme.dark .table-wrapper .table-body > tr:has(> td[data-column="select"] input[type="checkbox"]:checked) :is([class~="text-gray-400"], [class~="text-gray-500"], [class~="dark:text-dark-400"], [class~="dark:text-dark-500"], [data-ui="cell-line-label"]) { - color: rgb(var(--ui-ink-soft)); - } + --ui-muted: var(--ui-selected-meta); + --ui-faint: var(--ui-selected-meta); + --ui-label: var(--ui-selected-meta); + --theme-text-gray-400: var(--ui-selected-meta); + --theme-text-gray-500: var(--ui-selected-meta); + --theme-text-gray-600: var(--ui-selected-meta); + --theme-text-dark-400: var(--ui-selected-meta); + --theme-text-dark-500: var(--ui-selected-meta); +} + +html.flat-theme.dark .table-wrapper .table-body > tr[class~="bg-primary-50/40"] { + --ui-muted: var(--ui-selected-meta); + --ui-faint: var(--ui-selected-meta); + --ui-label: var(--ui-selected-meta); + --theme-text-gray-400: var(--ui-selected-meta); + --theme-text-gray-500: var(--ui-selected-meta); + --theme-text-gray-600: var(--ui-selected-meta); + --theme-text-dark-400: var(--ui-selected-meta); + --theme-text-dark-500: var(--ui-selected-meta); } /* and the danger red of a failure note in a selected row: #f87171 is 3.71:1 on @@ -643,8 +632,8 @@ html.flat-theme .table-page-layout > .layout-section-scrollable + .layout-sectio /* ================================================================== 3 · plain tables */ /* Upstream's hand-built tables with a grey header band (thead.bg-gray-50: ops, rankings, dialogs): the header reads - as the console's (mono label on the surface, 1px line underneath), the band and upstream's own insets stay out - of it. */ + as the console's (official text-xs font-medium uppercase tracking-wider on the surface, 1px line underneath), the + band and upstream's own insets stay out of it. */ html.flat-theme table > thead[class~="bg-gray-50"] { background-color: transparent; } @@ -664,18 +653,21 @@ html.flat-theme table > thead[class~="bg-gray-50"] > tr > th { background-color: rgb(var(--ui-surface)); box-shadow: inset 0 -1px 0 rgb(var(--ui-line)); color: rgb(var(--ui-table-head)); - font-family: var(--ui-font-mono); - font-size: var(--ui-label-size); + font-family: var(--ui-font); + font-size: 12px; font-weight: 500; - letter-spacing: var(--ui-label-tracking); + line-height: 16px; + letter-spacing: 0.05em; text-transform: uppercase; white-space: nowrap; } /* Distribution tables beside the charts (model / group / endpoint distribution, the spending ranking, the user - dashboard): the reference's usage table (REF §8.7 "/logs usage table", crop usage-card): 12px, header Geist - 10/600 uppercase .025em, rows 35px with 1px lines, names ink 500, figures tabular, the spend in ink, the other - costs muted. Each row carries the colour key of its slice, as the reference's rows carry their bar colour. */ + dashboard): the reference's usage table frame (REF §8.7 "/logs usage table", crop usage-card): 12px rows of 35px + with 1px lines, figures tabular. The header is official's (text-xs, th bold, the meta grey, normal case); names + and figures keep their template colours, as official (names gray-900, blue-600 when they expand; counts gray-600; + the actual spend green; the account cost orange; the standard price gray-400). Each row carries the colour key of + its slice, as the reference's rows carry their bar colour. */ html.flat-theme [class~="max-h-48"][class~="overflow-auto"] > table[class~="text-xs"] { font-size: 12px; line-height: 18px; @@ -690,11 +682,11 @@ html.flat-theme [class~="max-h-48"][class~="overflow-auto"] > table[class~="text background-color: rgb(var(--ui-surface)); box-shadow: inset 0 -1px 0 rgb(var(--ui-line)); color: rgb(var(--ui-muted)); - font-size: 10px; - font-weight: 500; - line-height: 15px; - letter-spacing: 0.025em; - text-transform: uppercase; + font-size: 12px; + font-weight: 700; + line-height: 16px; + letter-spacing: normal; + text-transform: none; vertical-align: bottom; } @@ -725,33 +717,6 @@ html.flat-theme [class~="max-h-48"][class~="overflow-auto"] > table[class~="text background-color: rgb(var(--ui-hover)); } -/* names: ink 500 (upstream draws the expandable ones as blue links; the chevron already says they open) */ -html.flat-theme [class~="max-h-48"][class~="overflow-auto"] > table[class~="text-xs"] > tbody > tr > td:first-child:not([colspan]) { - color: rgb(var(--ui-ink)); - font-weight: 500; -} - -html.flat-theme [class~="max-h-48"][class~="overflow-auto"] > table[class~="text-xs"] > tbody > tr > td:first-child:not([colspan]) svg { - color: rgb(var(--ui-label)); -} - -/* figures (text roles, section 12): requests and tokens are the row's secondary figures, the actual spend its primary - figure, the account cost and the standard price reference values (the title attributes still name them); all of - them Geist Mono, tabular, like the token column (they were Geist next to Mono token counts) */ -html.flat-theme [class~="max-h-48"][class~="overflow-auto"] > table[class~="text-xs"] > tbody > tr > td[class~="text-right"] { - color: rgb(var(--ui-data-secondary)); - font-family: var(--ui-font-mono); - font-variant-numeric: tabular-nums; -} - -html.flat-theme [class~="max-h-48"][class~="overflow-auto"] > table[class~="text-xs"] > tbody > tr > td[class~="text-right"]:is([class~="text-orange-500"], [class~="text-gray-400"]) { - color: rgb(var(--ui-data-reference)); -} - -html.flat-theme [class~="max-h-48"][class~="overflow-auto"] > table[class~="text-xs"] > tbody > tr > td[class~="text-right"]:is([class~="text-green-600"]) { - color: rgb(var(--ui-data-primary)); -} - /* the colour key: an 8px square with 2px corners (REF §8.14 legend and usage rows) in the slice's colour; slices follow the table's order (series 1-12), the ranking's remainder row is the "other" grey */ html.flat-theme [class~="max-h-48"][class~="overflow-auto"] > table[class~="text-xs"] > tbody > tr > td:first-child:not([colspan])::before { @@ -807,7 +772,7 @@ html.flat-theme [class~="max-h-48"][class~="overflow-auto"] > table[class~="text } /* Grouped rows (REF §8.7 group row: the surface-subtle band at 60 %): a model's per-user breakdown opens as a - band under its row, indented, rows 29px, figures muted. */ + band under its row, indented, rows 29px, its 12px text in the template's colours (official). */ html.flat-theme [class~="max-h-48"][class~="overflow-auto"] > table[class~="text-xs"] > tbody > tr > td[colspan] > div[class~="bg-gray-50/50"] { background-color: color-mix(in srgb, rgb(var(--ui-subtle)) 60%, transparent); } @@ -818,22 +783,19 @@ html.flat-theme [class~="max-h-48"][class~="overflow-auto"] > table[class~="text html.flat-theme [class~="max-h-48"][class~="overflow-auto"] > table[class~="text-xs"] > tbody > tr > td[colspan] > div > table > tbody > tr > td { padding: 5px 8px; - color: rgb(var(--ui-muted)); - font-size: 11.5px; + font-size: 12px; } html.flat-theme [class~="max-h-48"][class~="overflow-auto"] > table[class~="text-xs"] > tbody > tr > td[colspan] > div > table > tbody > tr > td:first-child { padding-left: 24px; - color: rgb(var(--ui-ink-soft)); } /* ================================================================== 4 · pagination */ /* The reference ends a list with a 1px line and a quiet row (REF §8.7); our pagination keeps its controls in that - language: 12px muted counts with mono figures, page buttons 30px ghost with mono numbers, the current page the - accent tint with accent figures at 600 and a 1px inset accent ring (palette C: the tint alone is 1.05:1 and the - accent figures about as light as the grey ones, so the ring marks the page at >= 3:1), arrows as 28px icon - buttons. */ + language with official's text: the sentence 14/20 in the soft ink with its counts at 500, page buttons 30px ghost + at 14/500, the current page the accent tint with accent figures at 500 and a 1px inset accent ring (palette C: the + tint alone is 1.05:1, so the ring marks the page at >= 3:1), arrows as 30px icon buttons. */ html.flat-theme [data-ui="pagination"] { gap: 12px; min-height: 44px; @@ -844,14 +806,13 @@ html.flat-theme [data-ui="pagination"] { html.flat-theme [data-ui="pagination"] p, html.flat-theme [data-ui="pagination"] span[class~="text-sm"] { - color: rgb(var(--ui-muted)); - font-size: 12px; - line-height: 18px; + color: rgb(var(--ui-ink-soft)); + font-size: 14px; + line-height: 20px; } html.flat-theme [data-ui="pagination"] p > span[class~="font-medium"] { - color: rgb(var(--ui-ink-soft)); - font-family: var(--ui-font-mono); + color: inherit; font-weight: 500; } @@ -870,12 +831,9 @@ html.flat-theme [data-ui="pagination"] nav[aria-label="Pagination"] > button { border: 0; border-radius: var(--ui-radius-md); background-color: transparent; - /* page numbers are figures: the secondary role (section 12; the sentence around the counts is the label) */ - color: rgb(var(--ui-data-secondary)); - font-family: var(--ui-font-mono); - font-size: 12px; + color: rgb(var(--ui-ink-soft)); + font-size: 14px; font-weight: 500; - font-variant-numeric: tabular-nums; transition: background-color var(--ui-duration) var(--ui-ease), color var(--ui-duration) var(--ui-ease); } @@ -899,7 +857,7 @@ html.flat-theme [data-ui="pagination"] nav[aria-label="Pagination"] > button[ari html.flat-theme [data-ui="pagination"] nav[aria-label="Pagination"] > button[aria-current="page"]:hover { background-color: rgb(var(--ui-primary-soft)); color: var(--ui-link); - font-weight: 600; + font-weight: 500; box-shadow: inset 0 0 0 1px var(--ui-choice-edge); } @@ -912,7 +870,7 @@ html.flat-theme [data-ui="pagination"] nav[aria-label="Pagination"] > button:dis color: rgb(var(--ui-faint)); } -/* phones: previous / next are the reference's secondary buttons (30px, control outline, surface, 13/600) */ +/* phones: previous / next are the reference's secondary buttons (30px, control outline, surface, official 14/500) */ html.flat-theme [data-ui="pagination"] > div:first-child > button { min-height: 30px; padding: 0 12px; @@ -920,8 +878,8 @@ html.flat-theme [data-ui="pagination"] > div:first-child > button { border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-surface)); color: rgb(var(--ui-ink)); - font-size: 13px; - font-weight: 600; + font-size: 14px; + font-weight: 500; } html.flat-theme [data-ui="pagination"] > div:first-child > button:disabled { @@ -931,8 +889,8 @@ html.flat-theme [data-ui="pagination"] > div:first-child > button:disabled { /* ================================================================== 5 · bulk bar (visual language; placement is the page's) */ /* The reference's selection tray (REF §8.12): surface, 1px structural line, 8px radius, 8 / 16 inset; the count - 12.5px ink-soft, the selection links 12.5/600 in the accent, dots muted. It floats (popover shadow) only where - its owner sets --ui-bulk-bar-shadow. */ + 14/500 ink-soft and the selection links 12/500 in the accent (the template's text-sm / text-xs font-medium), dots + muted. It floats (popover shadow) only where its owner sets --ui-bulk-bar-shadow. */ html.flat-theme [data-ui~="bulk-bar"] { border: 1px solid rgb(var(--ui-line)); border-radius: var(--ui-radius-lg); @@ -943,14 +901,14 @@ html.flat-theme [data-ui~="bulk-bar"] { html.flat-theme [data-ui~="bulk-bar"] > div:first-child > span[class~="text-sm"] { color: rgb(var(--ui-ink-soft)); - font-size: 12.5px; + font-size: 14px; font-weight: 500; } html.flat-theme [data-ui~="bulk-bar"] > div:first-child > button[class~="text-xs"] { color: var(--ui-link); - font-size: 12.5px; - font-weight: 600; + font-size: 12px; + font-weight: 500; } html.flat-theme [data-ui~="bulk-bar"] > div:first-child > button[class~="text-xs"]:hover:not(:disabled) { @@ -979,18 +937,18 @@ html.flat-theme [data-ui~="bulk-bar"] .btn:is(.btn-primary, .btn-success, .btn-w /* ================================================================== 6 · badges, status, identity */ -/* .badge*: the reference's pills (REF §8.8: capsule, Geist Mono uppercase, .025em, 6 / 1 inset, soft semantic - fills: FREE = accent-soft, ZDR = subtle, DEMO DATA = purple-soft). */ +/* .badge*: the reference's pills (REF §8.8: capsule, 6 / 1 inset, soft semantic fills: FREE = accent-soft, ZDR = + subtle, DEMO DATA = purple-soft) in official's .badge type (text-xs font-medium, sentence case). */ html.flat-theme .badge { gap: 4px; padding: 1px 7px; border-radius: 9999px; - font-family: var(--ui-font-mono); + font-family: var(--ui-font); font-size: var(--ui-badge-size); font-weight: 500; line-height: var(--ui-badge-leading); - letter-spacing: 0.025em; - text-transform: uppercase; + letter-spacing: normal; + text-transform: none; } html.flat-theme .badge-primary { @@ -1023,7 +981,8 @@ html.flat-theme .badge-purple { color: rgb(var(--ui-purple)); } -/* StatusBadge: the reference's status dot (REF §8.8: 6px dot, 12px ink-soft label, 6px apart) */ +/* StatusBadge: the reference's status dot (REF §8.8: 6px dot, 6px apart) and official's label (text-sm, the soft + ink) */ html.flat-theme [data-ui="status-badge"] { gap: 6px; } @@ -1048,18 +1007,18 @@ html.flat-theme [data-ui="status-badge"] > span:first-child[class~="bg-red-500"] html.flat-theme [data-ui="status-badge"] > span:last-child { color: rgb(var(--ui-ink-soft)); - font-size: 12px; - line-height: 18px; + font-size: 14px; + line-height: 20px; } /* Identity (owner decision 2, wave 2): platforms, groups and plans keep upstream's platform colours, drawn as the - reference's chips (REF §8.8 provider chip: 4px radius, 1px line, Geist Mono 10px uppercase .025em, the logo in the - chip's colour). A chip's colour is the Tailwind hue upstream gives it (the class names in the component: - bg-orange-100, text-emerald-700 ...); that hue's tokens (tokens.css "identity hues": text >= 4.5:1 on the fill, the - surface and the row hover; a soft fill; a 1px line) are set on each hue carrier as --pf-text / --pf-soft / - --pf-line / --pf-sub, and the chip rules read them with a neutral fallback, so a chip without a known hue (a free - plan, an unknown platform) stays the reference's neutral chip. Status stays status: the privacy state, an abnormal - plan, a subscription's days left and the peak-rate window keep the status colours. + reference's chips (REF §8.8 provider chip: 4px radius, 1px line, the logo in the chip's colour) in official's type + (text-xs font-medium, sentence case). A chip's colour is the Tailwind hue upstream gives it (the class names in the + component: bg-orange-100, text-emerald-700 ...); that hue's tokens (tokens.css "identity hues": text >= 4.5:1 on + the fill, the surface and the row hover; a soft fill; a 1px line) are set on each hue carrier as --pf-text / + --pf-soft / --pf-line / --pf-sub, and the chip rules read them with a neutral fallback, so a chip without a known + hue (a free plan, an unknown platform) stays the reference's neutral chip. Status stays status: the privacy state, + an abnormal plan, a subscription's days left and the peak-rate window keep the status colours. Contract for page owners: an element that upstream colours by platform with Tailwind hue classes can opt in with data-ui~="platform-chip" (line, fill, text) or data-ui~="platform-text" (text and glyph only). Hooks: data-ui="platform-badge", data-ui="group-badge", data-ui="group-option" (GroupOptionItem's root, whose rate @@ -1261,17 +1220,17 @@ html.flat-theme [data-ui="platform-badge"] > div[class~="inline-flex"]:where([da } /* 6.1 PlatformTypeBadge, on one line (D5): the platform | type chip, then the plan chip and the privacy chip (each its - own chip: plan and privacy are different kinds), then the expiry in mono. */ + own chip: plan and privacy are different kinds), then the expiry (the template's 10px, faint). */ html.flat-theme [data-ui="platform-badge"] { flex-direction: row; flex-wrap: nowrap; align-items: center; gap: 4px; - font-family: var(--ui-font-mono); - font-size: 10px; + font-family: var(--ui-font); + font-size: 12px; font-weight: 500; line-height: 16px; - letter-spacing: 0.025em; + letter-spacing: normal; white-space: nowrap; } @@ -1287,7 +1246,7 @@ html.flat-theme [data-ui="platform-badge"] > div[class~="inline-flex"] > span { padding: 1px 6px; background-color: transparent; color: rgb(var(--pf-text, var(--ui-ink-soft))); - text-transform: uppercase; + text-transform: none; white-space: nowrap; } @@ -1341,13 +1300,14 @@ html.flat-theme [data-ui="platform-badge"] > div[class~="inline-flex"]:nth-child html.flat-theme [data-ui="platform-badge"] > div:last-child:not(:first-child):not([class~="inline-flex"]) { padding-left: 1px; color: rgb(var(--ui-faint)); - font-size: 10.5px; - letter-spacing: 0; + font-size: 10px; + letter-spacing: normal; } /* 6.2 GroupBadge (group cells, group pickers): the platform's chip (upstream gives standard and subscription groups - their own shade of the platform's hue), 4px radius, 12px name, the logo in the chip's colour; the multiplier a mono - sub-chip on a lighter patch; a subscription's days left and the peak window keep their warning / danger fills. */ + their own shade of the platform's hue), 4px radius, 12/16 name, the logo in the chip's colour; the multiplier a + 10/600 sub-chip (official text-[10px] font-semibold) on a lighter patch; a subscription's days left and the peak + window keep their warning / danger fills. */ html.flat-theme [data-ui="group-badge"] { gap: 5px; padding: 1px 3px 1px 6px; @@ -1357,7 +1317,7 @@ html.flat-theme [data-ui="group-badge"] { color: rgb(var(--pf-text, var(--ui-ink-soft))); font-size: 12px; font-weight: 500; - line-height: 18px; + line-height: 16px; } html.flat-theme [data-ui="group-badge"] > svg { @@ -1370,9 +1330,9 @@ html.flat-theme [data-ui="group-badge"] > span:not(:first-of-type) { border-radius: 3px; background-color: rgb(var(--pf-sub, var(--ui-subtle)) / 0.72); color: rgb(var(--pf-text, var(--ui-muted))); - font-family: var(--ui-font-mono); - font-size: 10.5px; - font-weight: 500; + font-family: var(--ui-font); + font-size: 10px; + font-weight: 600; line-height: 16px; } @@ -1396,15 +1356,14 @@ html.flat-theme [data-ui="group-badge"] > span:not(:first-of-type):is([class~="b color: rgb(var(--ui-warning)); } -/* 6.3 Group pickers: GroupOptionItem's rate pill is the platform's chip in the reference's pill form (Geist Mono, 1px +/* 6.3 Group pickers: GroupOptionItem's rate pill is the platform's chip in the reference's pill form (12/16, 1px line); its peak-window pill stays the warning chip. GroupSelector rows hold GroupBadge (6.2). */ html.flat-theme div[data-ui~="group-option"]:is(div) > div:last-child > div > span[class~="rounded-full"] { padding: 1px 8px; border: 1px solid rgb(var(--pf-line, var(--ui-line))); background-color: rgb(var(--pf-soft, var(--ui-subtle))); color: rgb(var(--pf-text, var(--ui-ink-soft))); - font-family: var(--ui-font-mono); - font-size: 10.5px; + font-size: 12px; font-weight: 500; line-height: 16px; } @@ -1423,25 +1382,23 @@ html.flat-theme [data-ui~="platform-chip"] { border-radius: var(--ui-radius-sm); background-color: rgb(var(--pf-soft, var(--ui-surface))); color: rgb(var(--pf-text, var(--ui-ink-soft))); - font: 500 10px/16px var(--ui-font-mono); - letter-spacing: 0.025em; - text-transform: uppercase; + font: 500 12px/16px var(--ui-font); + letter-spacing: normal; + text-transform: none; } html.flat-theme [data-ui~="platform-text"] { color: rgb(var(--pf-text, var(--ui-ink-soft))); } -/* a category (request type, billing mode, protocol, role ...) is not identity and not status (decision 2): the - reference's neutral chip, whatever hue upstream gave it */ +/* a category (request type, billing mode, protocol, role ...) keeps the tag colours and type its template gives it, + as official (tailwind.config.js folds the hues into AA-safe families), in the reference's chip shape; the + transparent 1px edge keeps the box of the line it had */ html.flat-theme [data-ui~="category-chip"] { padding: 1px 6px; - border: 1px solid rgb(var(--ui-line)); + border: 1px solid transparent; border-radius: var(--ui-radius-sm); - background-color: rgb(var(--ui-subtle)); - color: rgb(var(--ui-ink-soft)); - font: 500 var(--ui-chip-size) / 16px var(--ui-font-mono); - letter-spacing: 0.01em; + letter-spacing: normal; } /* 6.5 Model marks (ModelIcon): the vendors' brand colours (each path's fill attribute), as upstream. A brand colour @@ -1480,7 +1437,6 @@ html.flat-theme .model-icon-fallback { border-radius: var(--ui-radius-sm); background: rgb(var(--ui-subtle)); color: rgb(var(--ui-ink-soft)); - font-family: var(--ui-font-mono); font-weight: 600; text-transform: uppercase; } @@ -1489,10 +1445,10 @@ html.flat-theme .model-icon-fallback { /* Upstream's KPI tiles (admin and user dashboards, usage records, orders: a .card.p-4 in a grid whose first content is a coloured icon tile) take the reference's StatTile (REF §8.13): 1px structural line, 6px radius, surface, - 8 / 12 inset, the mono label, the value 15/500 tabular ink (palette C), the detail 11px muted, 76px; no icon - tile; 10px apart. The grids and tiles are marked (static data-ui="kpi-grid" / "kpi-tile"; "kpi-value" on the RPM - value row); the repeated [data-ui] / [class] and :is(div) keep the weight of the pre-pass selectors, which found - them by their icon tile. */ + 8 / 12 inset, at least 76px, with official's text (the label 12/16/500 meta grey, the value 20/28/700 ink or its + template hue, the detail 12/16 in its template colours); no icon tile; 10px apart. The grids and tiles are marked (static + data-ui="kpi-grid" / "kpi-tile"; "kpi-value" on the RPM value row); the repeated [data-ui] / [class] and :is(div) + keep the weight of the pre-pass selectors, which found them by their icon tile. */ html.flat-theme [class~="grid"][data-ui~="kpi-grid"][data-ui][data-ui][data-ui][data-ui][data-ui][data-ui]:is(div) { gap: 10px; } @@ -1536,81 +1492,45 @@ html.flat-theme [class~="grid"] > [class~="card"][class~="p-4"] div[class~="roun } html.flat-theme [class~="grid"] > [class~="card"][class~="p-4"] div[class~="rounded-lg"][class~="p-2"]:first-child + div > p:first-child { - color: rgb(var(--ui-label)); - font-family: var(--ui-font-mono); + color: rgb(var(--ui-muted)); font-size: var(--ui-label-size); - font-weight: 400; + font-weight: 500; line-height: var(--ui-label-leading); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; } html.flat-theme [class~="grid"] > [class~="card"][class~="p-4"] div[class~="rounded-lg"][class~="p-2"]:first-child + div p[class~="text-xl"] { - color: rgb(var(--ui-kpi-accent)); - font-size: 15px; - font-weight: 500; - line-height: 22.5px; - font-variant-numeric: tabular-nums; + font-size: 20px; + font-weight: 700; + line-height: 28px; } -/* a value upstream colours for decoration (the new-users count, the balance, the usage cost) is ink (D4) */ -html.flat-theme [class~="grid"] > [class~="card"][class~="p-4"] div[class~="rounded-lg"][class~="p-2"]:first-child + div p[class~="text-xl"] > span:first-child:not([class~="text-sm"]) { - color: inherit; -} - -html.flat-theme [class~="grid"] > [class~="card"][class~="p-4"] div[class~="rounded-lg"][class~="p-2"]:first-child + div p[class~="text-xl"] > span[class~="text-sm"] { - color: rgb(var(--ui-muted)); - font-size: 12px; - font-weight: 400; +/* the value's colour is the tile's accent: the ink (official text-gray-900 / dark:text-white; #f3f4f6 in dark, the + console's ink), the danger colour in the danger tile (below). A value upstream draws in a hue keeps its template + colour, as on official: the usage cost (text-green-600), the new-users count and the balance (text-emerald-600). */ +html.flat-theme [class~="grid"] > [class~="card"][class~="p-4"][data-ui~="kpi-tile"] p[class~="text-xl"]:not([class~="text-green-600"], [class~="text-emerald-600"]) { + color: rgb(var(--ui-kpi-accent)); } -/* the detail line(s): 11px muted; the cost triples keep their order, weight marks the actual cost (the titles name - each figure); status words keep their status colour */ +/* the detail line(s): official 12/16 in their template colours (the meta grey, the green actual cost, the orange + account cost, status words) */ html.flat-theme [class~="grid"] > [class~="card"][class~="p-4"] div[class~="rounded-lg"][class~="p-2"]:first-child + div > :is(p, div):not(:first-child):not(div[data-ui~="kpi-value"]):not([class~="text-xl"]) { - color: rgb(var(--ui-muted)); - font-size: 11px; - line-height: 16.5px; -} - -html.flat-theme [class~="grid"] > [class~="card"][class~="p-4"] div[class~="rounded-lg"][class~="p-2"]:first-child + div > p:not(:first-child):not([class~="text-xl"]) :is([class~="text-green-600"], [class~="text-orange-500"], [class~="text-gray-400"], [class~="text-purple-600"]) { - color: rgb(var(--ui-muted)); -} - -html.flat-theme [class~="grid"] > [class~="card"][class~="p-4"] div[class~="rounded-lg"][class~="p-2"]:first-child + div > p:not(:first-child):not([class~="text-xl"]) > [class~="text-green-600"][title]:first-child { - color: rgb(var(--ui-ink-soft)); - font-weight: 500; -} - -html.flat-theme [class~="grid"] > [class~="card"][class~="p-4"] div[class~="rounded-lg"][class~="p-2"]:first-child + div > p[class~="text-green-600"]:not(:first-child):not([class~="text-xl"]) { - color: rgb(var(--ui-success)); -} - -html.flat-theme [class~="grid"] > [class~="card"][class~="p-4"] div[class~="rounded-lg"][class~="p-2"]:first-child + div > p:not(:first-child) > span[class~="text-green-600"]:not([title]) { - color: rgb(var(--ui-success)); -} - -html.flat-theme [class~="grid"] > [class~="card"][class~="p-4"] div[class~="rounded-lg"][class~="p-2"]:first-child + div > p:not(:first-child) > span[class~="text-red-500"] { - color: rgb(var(--ui-danger)); + font-size: 12px; + line-height: 16px; } -/* the performance tile's RPM / TPM lines: value 15/600 then the unit, TPM as the detail */ +/* the performance tile's RPM / TPM lines: the value, then its unit (the template's classes: text-xs units, TPM + text-sm font-semibold in its hue) */ html.flat-theme [class~="grid"] > [class~="card"][class~="p-4"] div[class~="rounded-lg"][class~="p-2"]:first-child + div > div[class~="items-baseline"] { gap: 4px; } html.flat-theme [class~="grid"] > [class~="card"][class~="p-4"] div[class~="rounded-lg"][class~="p-2"]:first-child + div > div[class~="items-baseline"] > span { - color: rgb(var(--ui-muted)); - font-family: var(--ui-font-mono); - font-size: 10px; line-height: 16px; - letter-spacing: 0.025em; } html.flat-theme [class~="grid"] > [class~="card"][class~="p-4"] div[class~="rounded-lg"][class~="p-2"]:first-child + div > div[class~="items-baseline"] > p[class~="text-sm"] { - color: rgb(var(--ui-ink-soft)); - font-size: 11px; - font-weight: 500; - line-height: 16.5px; font-variant-numeric: tabular-nums; } @@ -1618,7 +1538,8 @@ html.flat-theme [class~="grid"] > [class~="card"][class~="p-4"] div[class~="roun danger border and soft fill, the value in the danger colour, the label muted. The KPI tile tests its own detail line (subject-position :has(), child arguments for the two tile shapes: icon tile + text column directly in the card, or inside its first row) and hands the colours to its own rules: --ui-kpi-accent to the value, --ui-label to - the label (the only reader of --ui-label in a tile). Same weight as the pre-pass rule (0,9,5). */ + any label in the tile that reads the label grey (the KPI label reads the meta grey, the same value). Same weight + as the pre-pass rule (0,9,5). */ html.flat-theme [class~="grid"] > [class~="card"][class~="p-4"]:where([data-ui~="kpi-tile"]):has(> * > div[class~="rounded-lg"][class~="p-2"]:first-child + div > p:not(:first-child) > span[class~="text-red-500"], > div[class~="rounded-lg"][class~="p-2"]:first-child + div > p:not(:first-child) > span[class~="text-red-500"]) { --ui-kpi-accent: var(--ui-danger); --ui-label: var(--ui-muted); @@ -1628,7 +1549,8 @@ html.flat-theme [class~="grid"] > [class~="card"][class~="p-4"]:where([data-ui~= /* Quick actions (admin and user dashboards, D4): upstream tints each tile and its hover by hue; they take the reference's option card (REF §8.11 dialog option card: 8px radius, 1px structural line, the subtle fill at 60 %, - hover = control outline; a 28px neutral icon square with a 1px line; title 13/600 ink, description 12.5 muted). + hover = control outline; a 28px neutral icon square with a 1px line); official's text: title 14/20/500 ink, + description 12/16 muted. The buttons are data-ui="quick-action" (they were found by their icon square). */ html.flat-theme button[class~="group"][class~="flex"][class~="items-center"][class~="bg-gray-50"]:is([class~="p-3"], [class~="p-4"])[data-ui~="quick-action"][data-ui][data-ui] { gap: 12px; @@ -1662,15 +1584,15 @@ html.flat-theme button[class~="group"][class~="flex"][class~="items-center"][cla html.flat-theme button[class~="group"][class~="flex"][class~="items-center"][class~="bg-gray-50"]:is([class~="p-3"], [class~="p-4"]) > :nth-child(2) > :first-child { color: rgb(var(--ui-ink)); - font-size: 13px; - font-weight: 600; - line-height: 19.5px; + font-size: 14px; + font-weight: 500; + line-height: 20px; } html.flat-theme button[class~="group"][class~="flex"][class~="items-center"][class~="bg-gray-50"]:is([class~="p-3"], [class~="p-4"]) > :nth-child(2) > :nth-child(2) { color: rgb(var(--ui-muted)); - font-size: 12.5px; - line-height: 1.5; + font-size: 12px; + line-height: 16px; } html.flat-theme button[class~="group"][class~="flex"][class~="items-center"][class~="bg-gray-50"]:is([class~="p-3"], [class~="p-4"]) > svg:last-child { @@ -1700,25 +1622,25 @@ html.flat-theme .stat-card > .stat-icon { } html.flat-theme .stat-label { - color: rgb(var(--ui-label)); - font-family: var(--ui-font-mono); - font-size: var(--ui-label-size); - line-height: var(--ui-label-leading); - letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + color: rgb(var(--ui-muted)); + font-size: 14px; + line-height: 20px; + letter-spacing: normal; + text-transform: none; } html.flat-theme .stat-value { color: rgb(var(--ui-ink)); - font-size: 15px; - font-weight: 600; - line-height: 22.5px; + font-size: 24px; + font-weight: 700; + line-height: 32px; font-variant-numeric: tabular-nums; } html.flat-theme .stat-trend { - font-size: 11px; + font-size: 12px; font-weight: 500; + line-height: 16px; } html.flat-theme .stat-trend-up { @@ -1745,7 +1667,8 @@ html.flat-theme .progress-bar { /* ================================================================== 9 · empty and loading states */ /* EmptyState: the reference's empty panel (REF §8.7: dashed control outline, 8px radius, title 14/500 ink-soft, - description 13px muted at 1.625); the icon tile is the reference's 28px neutral square (dialog option cards). */ + description 14/20 muted, official's text-sm); the icon tile is the reference's 28px neutral square (dialog option + cards). */ html.flat-theme .empty-state { gap: 0; padding: 32px 16px; @@ -1779,8 +1702,8 @@ html.flat-theme .empty-state-title { html.flat-theme .empty-state-description { color: rgb(var(--ui-muted)); - font-size: 13px; - line-height: 1.625; + font-size: 14px; + line-height: 20px; } html.flat-theme .empty-state-description:empty { @@ -1822,24 +1745,21 @@ html.flat-theme :is(.skeleton, [data-ui="skeleton"], [class~="animate-pulse"][cl /* ================================================================== 10 · chart cards */ -/* Cards holding a chart (REF §8.14 Spend over time: 14px inset, the title as a mono label). A chart card is the - card that owns a canvas or the fixed-height chart area it keeps while loading or empty (h-48 / h-64), so the title - does not change with the data; the component marks it data-ui="chart-card" (the dist / trend chart components, the - two account stats modals, the admin dashboard's usage trend card, the payment revenue card, the user dashboard's - model card). :is(div) twice keeps the pre-pass weight. */ +/* Cards holding a chart (REF §8.14 Spend over time: 14px inset; the title official's 14/20/600 ink). A chart card is + the card that owns a canvas or the fixed-height chart area it keeps while loading or empty (h-48 / h-64), so the + title does not change with the data; the component marks it data-ui="chart-card" (the dist / trend chart + components, the two account stats modals, the admin dashboard's usage trend card, the payment revenue card, the + user dashboard's model card). :is(div) twice keeps the pre-pass weight. */ html.flat-theme [class~="card"][class~="p-4"][data-ui~="chart-card"]:is(div):is(div) { padding: 14px; } html.flat-theme [class~="card"][class~="p-4"][data-ui~="chart-card"]:is(div):is(div) > h3[class~="text-sm"][class~="font-semibold"], html.flat-theme [class~="card"][class~="p-4"][data-ui~="chart-card"]:is(div):is(div) > div:first-child > h3[class~="text-sm"][class~="font-semibold"] { - color: rgb(var(--ui-label)); - font-family: var(--ui-font-mono); - font-size: var(--ui-label-size); - font-weight: 400; - line-height: var(--ui-label-leading); - letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + color: rgb(var(--ui-ink)); + font-size: 14px; + font-weight: 600; + line-height: 20px; } /* BEGIN S callouts */ @@ -1869,8 +1789,8 @@ html.flat-theme :where(div:is([class~="bg-primary-50"], [class~="bg-primary-50/5 border-radius: var(--ui-radius-md); } -/* a form inside a callout (the scheduled test's new plan): the mono field labels take the muted grey, which holds - 4.5:1 on the subtle ground (the label grey #737373 reads 4.35 there); dark keeps its label grey (5.7) */ +/* a form inside a callout (the scheduled test's new plan): its labels read the meta grey, which holds 4.5:1 on the + subtle ground (the label grey is the same value today; this keeps it so if the label grey ever changes) */ html.flat-theme:not(.dark) :where(div:is([class~="bg-primary-50"], [class~="bg-primary-50/50"], [class~="bg-primary-50/60"], [class~="bg-primary-50/70"], [class~="bg-primary-50/80"], [class~="bg-primary-50/90"], [class~="bg-blue-50"], [class~="bg-blue-50/50"], [class~="bg-blue-50/60"], [class~="bg-blue-50/70"], [class~="bg-blue-50/80"], [class~="bg-blue-50/90"], [class~="bg-sky-50"], [class~="bg-sky-50/50"], [class~="bg-sky-50/60"], [class~="bg-sky-50/70"], [class~="bg-sky-50/80"], [class~="bg-sky-50/90"], [class~="bg-purple-50"], [class~="bg-purple-50/50"], [class~="bg-purple-50/60"], [class~="bg-purple-50/70"], [class~="bg-purple-50/80"], [class~="bg-purple-50/90"], [class~="bg-indigo-50"], [class~="bg-indigo-50/50"], [class~="bg-indigo-50/60"], [class~="bg-indigo-50/70"], [class~="bg-indigo-50/80"], [class~="bg-indigo-50/90"], [class~="bg-teal-50"], [class~="bg-teal-50/50"], [class~="bg-teal-50/60"], [class~="bg-teal-50/70"], [class~="bg-teal-50/80"], [class~="bg-teal-50/90"], [class~="bg-violet-50"], [class~="bg-violet-50/50"], [class~="bg-violet-50/60"], [class~="bg-violet-50/70"], [class~="bg-violet-50/80"], [class~="bg-violet-50/90"], [class~="bg-cyan-50"], [class~="bg-cyan-50/50"], [class~="bg-cyan-50/60"], [class~="bg-cyan-50/70"], [class~="bg-cyan-50/80"], [class~="bg-cyan-50/90"]):is([class*="rounded"]:not([class~="rounded-full"]), [class~="border"], [class~="card"]):is([class~="p-2"], [class~="p-3"], [class~="p-4"], [class~="p-5"], [class~="p-6"], [class~="py-2"], [class~="py-2.5"], [class~="py-3"], [class~="py-4"], [class~="card"]):not([data-ui], [class*="ring-"], [class~="border-primary-300"], [class~="border-primary-400"], [class~="border-primary-500"], [class~="border-primary-600"], [class~="border-primary-700"], [class~="border-blue-300"], [class~="border-blue-400"], [class~="border-blue-500"], [class~="border-blue-600"], [class~="border-blue-700"], [class~="border-sky-300"], [class~="border-sky-400"], [class~="border-sky-500"], [class~="border-sky-600"], [class~="border-sky-700"], [class~="border-purple-300"], [class~="border-purple-400"], [class~="border-purple-500"], [class~="border-purple-600"], [class~="border-purple-700"], [class~="border-indigo-300"], [class~="border-indigo-400"], [class~="border-indigo-500"], [class~="border-indigo-600"], [class~="border-indigo-700"], [class~="border-teal-300"], [class~="border-teal-400"], [class~="border-teal-500"], [class~="border-teal-600"], [class~="border-teal-700"], [class~="border-violet-300"], [class~="border-violet-400"], [class~="border-violet-500"], [class~="border-violet-600"], [class~="border-violet-700"], [class~="border-cyan-300"], [class~="border-cyan-400"], [class~="border-cyan-500"], [class~="border-cyan-600"], [class~="border-cyan-700"]):not(:is([data-ui="home"], [data-ui="key-usage"], [data-ui="legal"], [data-ui="not-found"], [data-ui="auth-layout"], [data-testid="compact-home"], #overlay-root > div[class~="z-[140]"]) *))[class][class] { --ui-label: var(--ui-muted); } @@ -1902,127 +1822,3 @@ html.flat-theme :where(div:is([class~="bg-primary-50"], [class~="bg-primary-50/5 color: rgb(var(--ui-muted)); } /* END S callouts */ - -/* ================================================================== 12 · text roles in the record tables - - Owner decision 2026-10-01, text option A "two tones" (artifacts/tmp/ui-polish/text/PREVIEW.md, where it was reviewed - and verified against a role table): in the cells and headers of 使用记录 (管理 and 用户, records and error requests), - 运维监控's error dialog, 账号管理, API 密钥, 用户管理 and the distribution tables, and in the accounts toolbar, the - pagination, the records tabs and the IP-region bar, a value is the ink and everything else one grey (tokens.css, data - text roles); before, a row held up to eight greys a step apart. Status, platform / identity, accent and token-glyph - colours stay; every other table keeps its colours. - primary names, e-mails, model, key / account names, token figures, the billed cost, balances, amounts, - priority, the error message; toolbar controls (buttons, selects, filter pills, page size) - labelled a value right after its inline label (管理分类 / 请求路由 / 最近 / 创建 / 过期 / 上游声明倍率 values, the - endpoint path after 入站 / 上游) - secondary unlabelled metadata: times, IPs, User-Agent, ids (#1), the model chain (↳), chips' text, status - words, platform, error category, reasoning effort, the masked key, an account's e-mail, page numbers - reference the account's billed cost (A $), the distribution tables' account cost and standard price - label headers, inline labels, 入站 / 上游, 首字 / 总耗时, units, placeholders (-), counts, the pagination - sentence, idle tabs, empty-value prompts, row-action icons - Inside those boxes the theme's text tokens and upstream's grey text classes resolve to the roles (custom properties - only, declared on each cell so a selected row can lift the tones it holds); an element whose class names another - role is named below or in its page file (accounts.css, dashboard-usage.css, user.css, - users-groups-proxies-keys.css), and the rules that paint a figure, an e-mail, a time or a page number read the role - directly (sections 3 and 4, the page files, ops-channels.css). Static hooks only (data-table, data-column, data-line, data-ui, upstream's static classes); one + joins two - static DataTable hooks (a labelled value). An empty-state row (colspan) is left alone. */ - -/* 12.1 the scope: each cell of the tables above, and the toolbars */ -html.flat-theme :where(:where(:is([data-ui~="usage-records"], [data-ui~="ops-error-list"], [data-ui="user-usage"]) .table-wrapper, .table-wrapper:is([data-table="accounts"], [data-table="keys"], [data-table="users"]), [data-ui="dist-table"]) > table > tbody > tr > td:not([colspan])), -html.flat-theme :where([data-ui="accounts-toolbar"], [data-ui="pagination"], [data-ui~="usage-records"] > div:first-child, [data-ui="user-usage"] > div[class~="border-b"], [data-ui~="records-card"] > div[class~="justify-end"][class~="border-b"]) { - --ui-ink: var(--ui-data-primary); - --ui-ink-soft: var(--ui-data-secondary); - --ui-muted: var(--ui-data-label); - --ui-faint: var(--ui-data-label); - --ui-label: var(--ui-data-label); - --ui-table-head: var(--ui-data-label); - --ui-badge-muted: var(--ui-data-secondary); -} - -/* a cell's own text (TablePageLayout's scoped td colour, text-gray-700 / dark:text-gray-300: the users id, the accounts - usage notes) is a secondary value */ -html.flat-theme :where(:where(:is([data-ui~="usage-records"], [data-ui~="ops-error-list"], [data-ui="user-usage"]) .table-wrapper, .table-wrapper:is([data-table="accounts"], [data-table="keys"], [data-table="users"]), [data-ui="dist-table"]) > table > tbody > tr > td:not([colspan])) { - --theme-text-gray-700: var(--ui-data-secondary); -} - -html.flat-theme.dark :where(:where(:is([data-ui~="usage-records"], [data-ui~="ops-error-list"], [data-ui="user-usage"]) .table-wrapper, .table-wrapper:is([data-table="accounts"], [data-table="keys"], [data-table="users"]), [data-ui="dist-table"]) > table > tbody > tr > td:not([colspan])) { - --theme-text-gray-300: var(--ui-data-secondary); -} - -/* headers: the label; a sortable label under the pointer takes the primary tone (it was the in-between grey) */ -html.flat-theme :where(:where(:is([data-ui~="usage-records"], [data-ui~="ops-error-list"], [data-ui="user-usage"]) .table-wrapper, .table-wrapper:is([data-table="accounts"], [data-table="keys"], [data-table="users"]), [data-ui="dist-table"]) > table > thead > tr > th) { - --ui-ink: var(--ui-data-primary); - --ui-ink-soft: var(--ui-data-primary); - --ui-muted: var(--ui-data-label); - --ui-faint: var(--ui-data-label); - --ui-label: var(--ui-data-label); - --ui-table-head: var(--ui-data-label); -} - -/* 12.2 upstream's grey text classes -> roles: the light class names the template's intent (900 primary, 500-800 - secondary, 400 label); dark mode reads the same role whatever dark: shade the template pairs with it */ -html.flat-theme :where(:where(:is([data-ui~="usage-records"], [data-ui~="ops-error-list"], [data-ui="user-usage"]) .table-wrapper, .table-wrapper:is([data-table="accounts"], [data-table="keys"], [data-table="users"]), [data-ui="dist-table"]) > table > tbody > tr > td:not([colspan]), [data-ui="accounts-toolbar"], [data-ui="pagination"], [data-ui~="usage-records"] > div:first-child, [data-ui="user-usage"] > div[class~="border-b"], [data-ui~="records-card"] > div[class~="justify-end"][class~="border-b"]) :not(td, th):is([class~="text-gray-900"], [class~="text-gray-950"]) { - --theme-text-gray-100: var(--ui-data-primary); --theme-text-gray-200: var(--ui-data-primary); --theme-text-gray-300: var(--ui-data-primary); - --theme-text-gray-400: var(--ui-data-primary); --theme-text-gray-500: var(--ui-data-primary); --theme-text-gray-600: var(--ui-data-primary); - --theme-text-gray-700: var(--ui-data-primary); --theme-text-gray-800: var(--ui-data-primary); --theme-text-gray-900: var(--ui-data-primary); - --theme-text-gray-950: var(--ui-data-primary); --theme-text-dark-100: var(--ui-data-primary); --theme-text-dark-200: var(--ui-data-primary); - --theme-text-dark-300: var(--ui-data-primary); --theme-text-dark-400: var(--ui-data-primary); --theme-text-dark-500: var(--ui-data-primary); - --theme-text-dark-600: var(--ui-data-primary); --ui-ink-soft: var(--ui-data-primary); -} - -html.flat-theme :where(:where(:is([data-ui~="usage-records"], [data-ui~="ops-error-list"], [data-ui="user-usage"]) .table-wrapper, .table-wrapper:is([data-table="accounts"], [data-table="keys"], [data-table="users"]), [data-ui="dist-table"]) > table > tbody > tr > td:not([colspan]), [data-ui="accounts-toolbar"], [data-ui="pagination"], [data-ui~="usage-records"] > div:first-child, [data-ui="user-usage"] > div[class~="border-b"], [data-ui~="records-card"] > div[class~="justify-end"][class~="border-b"]) :not(td, th):is([class~="text-gray-800"], [class~="text-gray-700"], [class~="text-gray-600"], [class~="text-gray-500"], [class~="text-slate-500"], [class~="text-slate-600"]) { - --theme-text-gray-100: var(--ui-data-secondary); --theme-text-gray-200: var(--ui-data-secondary); --theme-text-gray-300: var(--ui-data-secondary); - --theme-text-gray-400: var(--ui-data-secondary); --theme-text-gray-500: var(--ui-data-secondary); --theme-text-gray-600: var(--ui-data-secondary); - --theme-text-gray-700: var(--ui-data-secondary); --theme-text-gray-800: var(--ui-data-secondary); --theme-text-gray-900: var(--ui-data-secondary); - --theme-text-gray-950: var(--ui-data-secondary); --theme-text-dark-100: var(--ui-data-secondary); --theme-text-dark-200: var(--ui-data-secondary); - --theme-text-dark-300: var(--ui-data-secondary); --theme-text-dark-400: var(--ui-data-secondary); --theme-text-dark-500: var(--ui-data-secondary); - --theme-text-dark-600: var(--ui-data-secondary); --ui-ink-soft: var(--ui-data-secondary); -} - -html.flat-theme :where(:where(:is([data-ui~="usage-records"], [data-ui~="ops-error-list"], [data-ui="user-usage"]) .table-wrapper, .table-wrapper:is([data-table="accounts"], [data-table="keys"], [data-table="users"]), [data-ui="dist-table"]) > table > tbody > tr > td:not([colspan]), [data-ui="accounts-toolbar"], [data-ui="pagination"], [data-ui~="usage-records"] > div:first-child, [data-ui="user-usage"] > div[class~="border-b"], [data-ui~="records-card"] > div[class~="justify-end"][class~="border-b"]) :not(td, th):is([class~="text-gray-400"], [class~="text-slate-400"]) { - --theme-text-gray-100: var(--ui-data-label); --theme-text-gray-200: var(--ui-data-label); --theme-text-gray-300: var(--ui-data-label); - --theme-text-gray-400: var(--ui-data-label); --theme-text-gray-500: var(--ui-data-label); --theme-text-gray-600: var(--ui-data-label); - --theme-text-gray-700: var(--ui-data-label); --theme-text-gray-800: var(--ui-data-label); --theme-text-gray-900: var(--ui-data-label); - --theme-text-gray-950: var(--ui-data-label); --theme-text-dark-100: var(--ui-data-label); --theme-text-dark-200: var(--ui-data-label); - --theme-text-dark-300: var(--ui-data-label); --theme-text-dark-400: var(--ui-data-label); --theme-text-dark-500: var(--ui-data-label); - --theme-text-dark-600: var(--ui-data-label); --ui-ink-soft: var(--ui-data-label); -} - -/* 12.3 elements whose role differs from their class (the tables' own ones are in the page files) */ -/* a value right after its own inline label (DataTable's stacked lines: 最近 / 创建 / 过期, a scheduling group ...) is a - labelled value, one step above its label; primary values and placeholders keep their roles (the one sibling - combinator, keyed on DataTable's static data-ui hooks) */ -html.flat-theme :where(:is([data-ui~="usage-records"], [data-ui~="ops-error-list"], [data-ui="user-usage"]) .table-wrapper, .table-wrapper:is([data-table="accounts"], [data-table="keys"], [data-table="users"]), [data-ui="dist-table"]) [data-ui="cell-line-label"] + [data-ui="cell-line-value"] :is([class~="text-gray-800"], [class~="text-gray-700"], [class~="text-gray-600"], [class~="text-gray-500"], [class~="text-slate-500"], [class~="text-slate-600"]) { - --theme-text-gray-100: var(--ui-data-labelled); --theme-text-gray-200: var(--ui-data-labelled); --theme-text-gray-300: var(--ui-data-labelled); - --theme-text-gray-400: var(--ui-data-labelled); --theme-text-gray-500: var(--ui-data-labelled); --theme-text-gray-600: var(--ui-data-labelled); - --theme-text-gray-700: var(--ui-data-labelled); --theme-text-gray-800: var(--ui-data-labelled); --theme-text-gray-900: var(--ui-data-labelled); - --theme-text-gray-950: var(--ui-data-labelled); --theme-text-dark-100: var(--ui-data-labelled); --theme-text-dark-200: var(--ui-data-labelled); - --theme-text-dark-300: var(--ui-data-labelled); --theme-text-dark-400: var(--ui-data-labelled); --theme-text-dark-500: var(--ui-data-labelled); - --theme-text-dark-600: var(--ui-data-labelled); --ui-ink-soft: var(--ui-data-labelled); -} - -/* row actions are icon buttons with a caption: they keep the label tone whatever grey class they carry */ -html.flat-theme :where(:is([data-ui~="usage-records"], [data-ui~="ops-error-list"], [data-ui="user-usage"]) .table-wrapper, .table-wrapper:is([data-table="accounts"], [data-table="keys"], [data-table="users"]), [data-ui="dist-table"]) td[data-column="actions"] :is([class~="text-gray-400"], [class~="text-gray-500"], [class~="text-gray-600"], [class~="text-gray-700"]) { - --theme-text-gray-100: var(--ui-data-label); --theme-text-gray-200: var(--ui-data-label); --theme-text-gray-300: var(--ui-data-label); - --theme-text-gray-400: var(--ui-data-label); --theme-text-gray-500: var(--ui-data-label); --theme-text-gray-600: var(--ui-data-label); - --theme-text-gray-700: var(--ui-data-label); --theme-text-gray-800: var(--ui-data-label); --theme-text-gray-900: var(--ui-data-label); - --theme-text-gray-950: var(--ui-data-label); --theme-text-dark-100: var(--ui-data-label); --theme-text-dark-200: var(--ui-data-label); - --theme-text-dark-300: var(--ui-data-label); --theme-text-dark-400: var(--ui-data-label); --theme-text-dark-500: var(--ui-data-label); - --theme-text-dark-600: var(--ui-data-label); --ui-ink-soft: var(--ui-data-label); -} - -/* stacked-cell line labels stay labels in a selected row too (section 1 draws them there with --ui-ink-soft) */ -html.flat-theme :where(:is([data-ui~="usage-records"], [data-ui~="ops-error-list"], [data-ui="user-usage"]) .table-wrapper, .table-wrapper:is([data-table="accounts"], [data-table="keys"], [data-table="users"]), [data-ui="dist-table"]) [data-ui="cell-line-label"] { - --ui-ink-soft: var(--ui-data-label); -} - -/* 12.4 a selected row (dark): on the selected fill (#39433f, #404946 under the pointer) the grey #a3a3ac is 4.10:1 / - 3.71, so the row lifts its secondary, reference and label tones to --ui-data-picked (#b8b8c0, 5.20 / 4.72), as - section 1 lifts upstream's greys there. DataTable's own selection is a class on the row (below); a page's own - select column is the checked box section 1's row rule already tests, and that rule sets the three roles on the row, - whose cells inherit them (no style query, no further relational selector, the same in Firefox). */ -html.flat-theme.dark :where(:is([data-ui~="usage-records"], [data-ui~="ops-error-list"], [data-ui="user-usage"]) .table-wrapper, .table-wrapper:is([data-table="accounts"], [data-table="keys"], [data-table="users"])) > table > tbody.table-body > tr[class~="bg-primary-50/40"] > td { - --ui-data-secondary: var(--ui-data-picked); - --ui-data-reference: var(--ui-data-picked); - --ui-data-label: var(--ui-data-picked); -} diff --git a/frontend/src/styles/console/overlays.css b/frontend/src/styles/console/overlays.css index af7048456ca4..be6f460977b9 100644 --- a/frontend/src/styles/console/overlays.css +++ b/frontend/src/styles/console/overlays.css @@ -39,12 +39,13 @@ /* ================================================================== 0 · local tokens */ html.flat-theme { - /* menus and lists (FilterMenu, measured): 4px panel inset, 6/12 rows, 12.5/18.75 text, 14px glyphs, 8px gap */ + /* menus and lists (FilterMenu, measured): 4px panel inset, 6/12 rows, 14/20 text (official .dropdown-item + text-sm), 14px glyphs, 8px gap */ --ui-menu-inset: 4px; --ui-menu-row-y: 6px; --ui-menu-row-x: 12px; - --ui-menu-text: 12.5px; - --ui-menu-leading: 18.75px; + --ui-menu-text: 14px; + --ui-menu-leading: 20px; --ui-menu-glyph: 14px; /* dialogs (Modal, measured): 18px sides, 14px header, 16px body; the footer's 12px is derived */ --ui-dialog-x: 18px; @@ -103,9 +104,10 @@ html.flat-theme :is(.select-dropdown-enter-from, .select-dropdown-leave-to, .dat transform: translateY(-4px); } -/* 1.2 Rows. FilterMenu option: 6/12, 12.5/18.75, ink-soft, 8px gap, hover = subtle fill (text unchanged), 150ms. The - same row serves command menus (row "more", user menu), lists (Select, proxy picker) and upstream's utility menus - (their full-width buttons). The named rows share one :is() group (0,3,1), so every state below is written on it. */ +/* 1.2 Rows. FilterMenu option: 6/12, 14/20 (official .dropdown-item), ink-soft, 8px gap, hover = subtle fill (text + unchanged), 150ms. The same row serves command menus (row "more", user menu), lists (Select, proxy picker) and + upstream's utility menus (their full-width buttons). The named rows share one :is() group (0,3,1), so every state + below is written on it. */ html.flat-theme :is(.dropdown-item, .select-dropdown-portal .select-option, .select-dropdown .select-option, .action-menu-content :is(button, a), [data-ui="menu-item"], .account-tools-menu-item), html.flat-theme :is(div, nav, ul).bg-white:is(.absolute, .fixed):is(.shadow-lg, .shadow-xl) :is(button, a).w-full:is(.flex, .text-left):not(.btn) { gap: 8px; @@ -212,14 +214,14 @@ html.flat-theme :is(div, nav, ul).bg-white:is(.absolute, .fixed):is(.shadow-lg, color: var(--ui-link); } -/* 1.4 Group labels: the mono label (Geist Mono 10, 0.12em, uppercase, --ui-label; 11px in Chinese) at 8/12/4, as the - ModelPicker's group rows; separators are the structural line with 4px around. */ +/* 1.4 Group labels: the console label (12/16/500, the label grey, sentence case) at 8/12/4, as the ModelPicker's + group rows; separators are the structural line with 4px around. */ html.flat-theme [data-ui="menu-label"], html.flat-theme :is(.select-dropdown-portal, .select-dropdown) .select-option-group:not(.dropdown-item), html.flat-theme :is(div, nav, ul).bg-white:is(.absolute, .fixed):is(.shadow-lg, .shadow-xl) :is(div, span, p).uppercase:is(.text-xs, .text-\[10px\], .text-\[11px\]):not(button *) { font: var(--ui-label-font); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; color: rgb(var(--ui-label)); } @@ -270,7 +272,7 @@ html.flat-theme [data-ui="menu-check"][data-checked="true"] { } /* 1.6 Select's list (the ModelPicker frame: a search row, then the list): a 36px search row on the structural line, - 13px input, 14px faint glyph; a 4px list inset; a 12px muted empty state. */ + 14px input, 14px faint glyph; a 4px list inset; a 12/16 muted empty state. */ html.flat-theme .select-dropdown-portal .select-search, html.flat-theme .select-dropdown .select-header, html.flat-theme.dark .select-dropdown-portal .select-search, @@ -290,7 +292,7 @@ html.flat-theme :is(.select-dropdown-portal .select-search, .select-dropdown .se html.flat-theme :is(.select-dropdown-portal, .select-dropdown) .select-search-input, html.flat-theme.dark :is(.select-dropdown-portal, .select-dropdown) .select-search-input { min-height: 36px; - font-size: 13px; + font-size: 14px; color: rgb(var(--ui-ink)); } @@ -308,7 +310,7 @@ html.flat-theme :is(.select-dropdown-portal, .select-dropdown) .select-empty, html.flat-theme.dark :is(.select-dropdown-portal, .select-dropdown) .select-empty { padding: 16px 10px; font-size: 12px; - line-height: 18px; + line-height: 16px; color: rgb(var(--ui-muted)); } @@ -359,23 +361,24 @@ html.flat-theme div[data-ui~="account-tools-panel"][data-ui] > div > div.px-2.py padding: 8px var(--ui-menu-row-x) 4px; } -/* 1.8 The user menu's identity block, the mobile balance and the contact row */ +/* 1.8 The user menu's identity block, the mobile balance and the contact row (official's text: the name 14/20/500, + the e-mail and the contact row 12/16) */ html.flat-theme .dropdown > div.border-b:first-child { padding: 10px var(--ui-menu-row-x); } html.flat-theme .dropdown > div.border-b:first-child > :first-child, html.flat-theme.dark .dropdown > div.border-b:first-child > :first-child { - font-size: 13px; - line-height: 19.5px; - font-weight: 600; + font-size: 14px; + line-height: 20px; + font-weight: 500; color: rgb(var(--ui-ink)); } html.flat-theme .dropdown > div.border-b:first-child > :nth-child(2), html.flat-theme.dark .dropdown > div.border-b:first-child > :nth-child(2) { font-size: 12px; - line-height: 18px; + line-height: 16px; color: rgb(var(--ui-muted)); } @@ -398,7 +401,7 @@ html.flat-theme .dropdown > div.border-t.px-4 > div, html.flat-theme.dark .dropdown > div.border-t.px-4 > div { gap: 8px; font-size: 12px; - line-height: 18px; + line-height: 16px; color: rgb(var(--ui-muted)); } @@ -409,8 +412,6 @@ html.flat-theme .dropdown > div.border-t.px-4 > div > svg { html.flat-theme .dropdown > div.border-t.px-4 > div > span:last-child, html.flat-theme.dark .dropdown > div.border-t.px-4 > div > span:last-child { - font-family: var(--ui-font-mono); - font-weight: 400; color: rgb(var(--ui-ink-soft)); } @@ -429,7 +430,7 @@ html.flat-theme :is(div, span).text-white:is(.bg-gray-900, .bg-gray-800):is(.abs box-shadow: var(--ui-shadow-popover); color: rgb(var(--ui-ink-soft)); font-size: 12px; - line-height: 18px; + line-height: 16px; /* the text shades upstream chose for a dark ground, resolved for the light surface; white text is ink */ --theme-on-fill: var(--ui-ink); --theme-text-gray-100: var(--ui-ink); @@ -525,13 +526,13 @@ html.flat-theme :is([role="tooltip"].theme-dark-surface, .theme-dark-surface[id^ } /* 2.3 Breakdown cards (the usage table's cost and token details): the reference's MetricFigure rows, label left, - figure right in Geist Mono tabular ink; the card's section title is its 11px uppercase divider label. */ + figure right in tabular ink; the card's section title is its 12/16 divider label (official text-xs, sentence + case). */ html.flat-theme div.fixed.pointer-events-none > .theme-dark-surface { padding: 8px 12px; } html.flat-theme div.fixed.pointer-events-none > .theme-dark-surface .justify-between > span:last-child { - font-family: var(--ui-font-mono); font-variant-numeric: tabular-nums; font-weight: 400; } @@ -542,11 +543,9 @@ html.flat-theme div.fixed.pointer-events-none > .theme-dark-surface .justify-bet html.flat-theme div.fixed.pointer-events-none > .theme-dark-surface div.font-semibold.mb-1 { margin-bottom: 4px; - font-size: 11px; - line-height: 16.5px; + font-size: 12px; + line-height: 16px; font-weight: 500; - letter-spacing: 0.025em; - text-transform: uppercase; color: rgb(var(--ui-muted)); } @@ -626,7 +625,8 @@ html.flat-theme [role="tooltip"].theme-dark-surface > button.absolute:hover { color: rgb(var(--ui-ink)); } -/* 2.6 Icon-only row actions (behaviour B2.1 places the label left of the button): the bubble's look, 12/16 500 ink. */ +/* 2.6 Icon-only row actions (behaviour B2.1 places the label left of the button): the bubble's frame; the label keeps + its template's text-xs and behaviour.css' ink. */ @media (hover: hover) and (pointer: fine) { html.flat-theme:is(.flat-theme) :is(.table-wrapper:is([data-table="proxies"], [data-table="keys"], [data-table="groups"], [data-table="accounts"], [data-table="users"]), :is([data-ui~="subscriptions-page"], [data-ui~="redeem-page"]) .table-wrapper) td[data-column="actions"] button.flex-col:is(:hover, :focus-visible) > span.text-xs { padding: 6px 10px; @@ -634,22 +634,18 @@ html.flat-theme [role="tooltip"].theme-dark-surface > button.absolute:hover { border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-surface)); box-shadow: var(--ui-shadow-popover); - color: rgb(var(--ui-ink)); - font-size: 12px; - line-height: 16px; - font-weight: 500; } } /* 2.7 White hover cards (the usage KPI explanations, the supported-model card; the header balance card is the shell's): - the breakdown frame, radius 6, control outline, 12/8 inset, 12px. */ + the breakdown frame, radius 6, control outline, 12/8 inset, 12/16 text. */ html.flat-theme :is(div, span).pointer-events-none.bg-white:is(.absolute, .fixed):is(.shadow-lg, .shadow-xl):not([data-ui="header-balance"] > *) { border: 1px solid rgb(var(--ui-line-strong)); border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-surface)); box-shadow: var(--ui-shadow-popover); font-size: 12px; - line-height: 18px; + line-height: 16px; } html.flat-theme :is(div, span).pointer-events-none.bg-white:is(.absolute, .fixed):is(.shadow-lg, .shadow-xl).p-3:not([data-ui="header-balance"] > *) { @@ -697,8 +693,8 @@ html.flat-theme div.fixed.inset-0 > div.min-h-full > div.bg-white:is(.shadow-xl, box-shadow: var(--ui-shadow-overlay); } -/* 3.3 Header: 14/18 inset on the structural line, 8px gap; title 14/20 600 ink (Modal h2). Titles wrap instead of - being cut (nothing hidden). */ +/* 3.3 Header: 14/18 inset on the structural line, 8px gap; title 18/28 600 ink (official .modal-title text-lg + font-semibold). Titles wrap instead of being cut (nothing hidden). */ html.flat-theme .modal-header { gap: 8px; padding: var(--ui-dialog-head-y) var(--ui-dialog-x); @@ -708,10 +704,10 @@ html.flat-theme .modal-header { html.flat-theme .modal-title { min-width: 0; margin: 0; - font-size: 14px; - line-height: 20px; + font-size: 18px; + line-height: 28px; font-weight: 600; - letter-spacing: 0; + letter-spacing: normal; color: rgb(var(--ui-ink)); } @@ -768,13 +764,14 @@ html.flat-theme .modal-footer > div.flex:only-child:is(.pt-2, .pt-3, .pt-4) { padding-top: 0; } -/* 3.6 The confirm dialog: message 13/21 ink-soft; its two utility buttons restated as the .btn recipes (controls' - values: 30px, 0 12px, 13/19.5 600, radius 6, 1px edge; cancel = default, confirm = the solid primary, a danger - confirm = destructive). Perf pass: the message box is hooked ([data-ui="confirm-body"], ConfirmDialog.vue), so no - has-test on .modal-content asks whether the dialog holds the confirm button; the `div` keeps the original 0,5,3. */ +/* 3.6 The confirm dialog: message 14/20 ink-soft (official text-sm); its two utility buttons restated as the .btn + recipes (30px, 0 12px, official 14/20 500, radius 6, 1px edge; cancel = default, confirm = the solid primary, a + danger confirm = destructive). Perf pass: the message box is hooked ([data-ui="confirm-body"], ConfirmDialog.vue), + so no has-test on .modal-content asks whether the dialog holds the confirm button; the `div` keeps the original + 0,5,3. */ html.flat-theme .modal-content .modal-body > div[data-ui~="confirm-body"] > p:first-child { - font-size: 13px; - line-height: 21px; + font-size: 14px; + line-height: 20px; color: rgb(var(--ui-ink-soft)); } @@ -789,9 +786,9 @@ html.flat-theme :is([data-ui="confirm-cancel"], [data-ui="confirm-ok"]) { border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-surface)); color: rgb(var(--ui-ink)); - font-size: 13px; - line-height: 19.5px; - font-weight: 600; + font-size: 14px; + line-height: 20px; + font-weight: 500; box-shadow: none; --tw-ring-shadow: 0 0 #0000; --tw-ring-offset-shadow: 0 0 #0000; @@ -845,14 +842,13 @@ html.flat-theme :is([data-ui="confirm-cancel"], [data-ui="confirm-ok"]):disabled } /* 3.7 Utility-built dialogs without a header strip (TOTP, passkey, payment renewal; the ones led by an h2 take the - Modal's parts, 3.7b): the reference's padded panel (ReadScope, login modal): title 17/600 tight ink, close 28px - ghost glyph button (muted -> ink on the subtle fill). */ + Modal's parts, 3.7b): the reference's padded panel (ReadScope, login modal): title 18/28 600 ink (official + .modal-title), close 28px ghost glyph button (muted -> ink on the subtle fill). */ html.flat-theme div.fixed.inset-0 :is(div, section).bg-white:is(.shadow-xl, .shadow-2xl):not([data-ui]) > :is(h2, h3):is(.text-lg, .text-xl), html.flat-theme div.fixed.inset-0 :is(div, section).bg-white:is(.shadow-xl, .shadow-2xl):not([data-ui]) > div:first-child > :is(h2, h3):is(.text-lg, .text-xl) { - font-size: 17px; - line-height: 25.5px; + font-size: 18px; + line-height: 28px; font-weight: 600; - letter-spacing: -0.025em; color: rgb(var(--ui-ink)); } @@ -880,10 +876,10 @@ html.flat-theme div.fixed.inset-0 :is(div, section).bg-white:is(.shadow-xl, .sha /* 3.7b Utility-built dialogs led by an h2 (兑换码 生成 / 批量修改, 订阅 使用指南, 备份 R2 教程 / 分卷下载) are drawn as the Modal, in the numbers of the settings page's inline dialogs (settings-ops-public.css 10b): body 16 x 18; the title - as the header strip, 14 / 20 600 ink at 14 x 18 on the structural line, on the Modal's 28px row (57px); a close - button the Modal's 28px framed close in that strip; the closing button row as the footer strip, 12 x 18 on the - structural line, its small close button at the control height (55px, the Modal's footer). The h3- and icon-led ones - (TOTP, passkey, payment renewal, the settings page's) keep 3.7 / 10b. (final review V1-01) + as the header strip, 18 / 28 600 ink (the Modal title) at 14 x 18 on the structural line, on the Modal's 28px row + (57px); a close button the Modal's 28px framed close in that strip; the closing button row as the footer strip, + 12 x 18 on the structural line, its small close button at the control height (55px, the Modal's footer). The h3- + and icon-led ones (TOTP, passkey, payment renewal, the settings page's) keep 3.7 / 10b. (final review V1-01) Perf pass: the five scrims are hooked in their templates (data-ui="h2-dlg", plus "h2-dlg-close" on the three whose panel has the top-right close button; the h2 and the close button are unconditional there), so the panel and its parts are plain child selectors; :where() / the repeated [data-ui] / :not(uipad) keep each rule's original @@ -899,10 +895,10 @@ html.flat-theme div:where([data-ui~="h2-dlg"]).fixed.inset-0 > :is(div, section) margin: calc(-1 * var(--ui-dialog-body-y)) calc(-1 * var(--ui-dialog-x)) var(--ui-dialog-body-y); border-bottom: 1px solid rgb(var(--ui-line)); padding: var(--ui-dialog-head-y) var(--ui-dialog-x); - font-size: 0.875rem; - line-height: 1.25rem; + font-size: 1.125rem; + line-height: 1.75rem; font-weight: 600; - letter-spacing: 0; + letter-spacing: normal; color: rgb(var(--ui-ink)); } @@ -936,9 +932,9 @@ html.flat-theme div:where([data-ui~="h2-dlg"]).fixed.inset-0 > :is(div, section) html.flat-theme div:where([data-ui~="h2-dlg"]).fixed.inset-0 > :is(div, section).bg-white.p-6:is(.shadow-xl, .shadow-2xl):not([data-ui]) > div.text-right:last-child > .btn-sm:not(uipad) { min-height: var(--ui-control-h); - padding: 0.265625rem 0.75rem; - font-size: 0.8125rem; - line-height: 1.21875rem; + padding: 0.25rem 0.75rem; + font-size: 0.875rem; + line-height: 1.25rem; } /* 3.8 Motion: the scrim fades in 200ms; the panel settles on the reference's 450ms panel curve from 98% with a 160ms @@ -971,8 +967,8 @@ html.flat-theme .modal-leave-to .modal-content { /* ================================================================== 4 · drawers (derived: the reference has none) */ /* 4.1 The account details drawer's shell (its content is the accounts page's): the dark theme's surface, a 1px - structural edge, the overlay shadow; the header is the dialog's (14/18, 14/20 600 title, mono id, 28px bordered - buttons). */ + structural edge, the overlay shadow; the header is the dialog's strip (14/18 inset, 28px bordered buttons) with a + 14/20 600 title, the #id in the template's mono at 12/16 and the e-mail at 12/16. */ html.flat-theme [data-test="account-details-drawer"] { border-left: 1px solid rgb(var(--ui-line)); background-color: rgb(var(--ui-surface)); @@ -994,16 +990,15 @@ html.flat-theme [data-test="account-details-drawer"] > header h2 { } html.flat-theme [data-test="account-details-drawer"] > header h2 + span { - font-family: var(--ui-font-mono); - font-size: 11px; - line-height: 16.5px; + font-size: 12px; + line-height: 16px; color: rgb(var(--ui-faint)); } html.flat-theme [data-test="account-details-drawer"] > header p { margin-top: 2px; font-size: 12px; - line-height: 18px; + line-height: 16px; color: rgb(var(--ui-muted)); } @@ -1043,10 +1038,10 @@ html.flat-theme [data-test="operation-dock"] { /* ================================================================== 5 · toasts (derived: the reference has none) */ /* 5.1 A toast is the compare tray's floating surface: 1px structural line all round (no coloured edge), radius 8, - popover shadow; 12/14 inset; a 15px status glyph in the status text colour (the only colour); message 13/19.5 - ink-soft (ink when it stands alone), title 13/600 ink; a 24px ghost close; the auto-dismiss timer stays, as a 2px - hairline in the control-outline grey. It arrives with the reference's reveal (200ms, 4px), not a slide from the - edge. */ + popover shadow; 12/14 inset; a 15px status glyph in the status text colour (the only colour); message 14/20 + ink-soft (ink when it stands alone), title 14/600 ink (official text-sm); a 24px ghost close; the auto-dismiss + timer stays, as a 2px hairline in the control-outline grey. It arrives with the reference's reveal (200ms, 4px), + not a slide from the edge. */ html.flat-theme [data-ui="toast"] { border: 1px solid rgb(var(--ui-line)); border-radius: var(--ui-radius-lg); @@ -1072,8 +1067,8 @@ html.flat-theme [data-ui="toast"] > div:first-child > div > div:first-child > sv } html.flat-theme [data-ui="toast"] p { - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; } html.flat-theme [data-ui="toast"] p.font-semibold { @@ -1179,7 +1174,7 @@ html.flat-theme :is([data-ui="announcement-list"], [data-ui="announcement-detail height: 15px; } -/* titles: the list header 14/600 (Modal), the announcement itself 17/600 tight (panel title) */ +/* titles: the list header 14/600, the announcement itself 18/28 600 (the Modal title) */ html.flat-theme [data-ui="announcement-list"] > div:first-child h2 { font-size: 14px; line-height: 20px; @@ -1189,16 +1184,15 @@ html.flat-theme [data-ui="announcement-list"] > div:first-child h2 { html.flat-theme :is([data-ui="announcement-detail"], [data-ui="announcement-popup"]) > div:first-child h2 { margin-bottom: 6px; - font-size: 17px; - line-height: 25.5px; + font-size: 18px; + line-height: 28px; font-weight: 600; - letter-spacing: -0.025em; color: rgb(var(--ui-ink)); } html.flat-theme :is([data-ui="announcement-list"], [data-ui="announcement-detail"], [data-ui="announcement-popup"]) > div:first-child :is(p, time, div.text-sm) { font-size: 12px; - line-height: 18px; + line-height: 16px; color: rgb(var(--ui-muted)); } @@ -1213,19 +1207,17 @@ html.flat-theme :is([data-ui="announcement-detail"], [data-ui="announcement-popu height: 13px; } -/* chips: "unread" and the category label become the reference's small mono pills; pulsing rings stop (the text says - it) */ +/* chips: "unread" and the category label become the reference's small pills (official text-xs font-medium); + pulsing rings stop (the text says it) */ html.flat-theme :is([data-ui="announcement-list"], [data-ui="announcement-detail"], [data-ui="announcement-popup"]) span:is(.bg-gradient-to-r, .bg-blue-100) { gap: 5px; padding: 1px 6px; border-radius: 9999px; background: rgb(var(--ui-primary-soft)); color: var(--ui-link); - font-family: var(--ui-font-mono); - font-size: 10.5px; - line-height: 15.75px; + font-size: 12px; + line-height: 16px; font-weight: 500; - letter-spacing: 0.025em; box-shadow: none; } @@ -1289,9 +1281,9 @@ html.flat-theme [data-ui="announcement-list"] > div:first-child button:not(:last border-radius: var(--ui-radius-md); background: rgb(var(--ui-surface)); color: rgb(var(--ui-ink)); - font-size: 13px; - line-height: 19.5px; - font-weight: 600; + font-size: 14px; + line-height: 20px; + font-weight: 500; box-shadow: none; } @@ -1342,16 +1334,15 @@ html.flat-theme [data-ui="announcement-list"] > div:nth-child(2) > div > div.gro } html.flat-theme [data-ui="announcement-list"] > div:nth-child(2) > div > div.group h3 { - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; font-weight: 500; color: rgb(var(--ui-ink)); } html.flat-theme [data-ui="announcement-list"] > div:nth-child(2) > div > div.group time { - font-family: var(--ui-font-mono); - font-size: 11px; - line-height: 16.5px; + font-size: 12px; + line-height: 16px; color: rgb(var(--ui-faint)); } @@ -1361,7 +1352,8 @@ html.flat-theme [data-ui="announcement-list"] > div:nth-child(2) > div > div.gro color: rgb(var(--ui-faint)); } -/* the empty and loading states: the reference's empty state (14/500 ink-soft, 13 muted), neutral glyph */ +/* the empty and loading states: the reference's empty state (14/500 ink-soft, the description at the template's + 12px, muted), neutral glyph */ html.flat-theme [data-ui="announcement-list"] > div:nth-child(2) > div.flex-col > div.relative > div:first-child { width: 40px; height: 40px; @@ -1386,7 +1378,6 @@ html.flat-theme [data-ui="announcement-list"] > div:nth-child(2) > div.flex-col } html.flat-theme [data-ui="announcement-list"] > div:nth-child(2) > div.flex-col > p + p { - font-size: 13px; color: rgb(var(--ui-muted)); } @@ -1445,9 +1436,9 @@ html.flat-theme :is([data-ui="announcement-detail"], [data-ui="announcement-popu border-radius: var(--ui-radius-md); background: rgb(var(--ui-surface)); color: rgb(var(--ui-ink)); - font-size: 13px; - line-height: 19.5px; - font-weight: 600; + font-size: 14px; + line-height: 20px; + font-weight: 500; box-shadow: none; transform: none; } @@ -1470,58 +1461,25 @@ html.flat-theme :is([data-ui="announcement-detail"], [data-ui="announcement-popu height: 14px; } -/* 6.2 Markdown inside announcements: the reference's docs article (REF §8.16): 13.5/1.625 muted text, 17/600 H2 on 40 / - 12, links ink underlined (offset 2), strong 500 ink, disc lists at 20px with muted markers, mono 12.5 code without a - fill, code blocks and tables on the card frame (radius 8, 1px line), a neutral call-out for quotes, no shadows. */ -html.flat-theme .markdown-body { - font-size: 13.5px; - line-height: 1.625; - color: rgb(var(--ui-muted)); -} - +/* 6.2 Markdown inside announcements: official's typography (styles/announcement-markdown.css: 15px / 1.75 text, + h1-h4 30 / 24 / 20 / 18, its link, marker, code and heading colours) in the reference's docs frame (REF §8.16): + headings on 28 / 10 without rules, underlined links (offset 2), disc lists at 20px, mono code without a fill, code + blocks and tables on the card frame (radius 8, 1px line), a neutral call-out for quotes, no shadows. */ html.flat-theme .markdown-body :is(h1, h2, h3, h4) { margin: 28px 0 10px; padding: 0; border: 0; - color: rgb(var(--ui-ink)); - font-weight: 600; - letter-spacing: -0.025em; } html.flat-theme .markdown-body > :is(h1, h2, h3, h4):first-child { margin-top: 0; } -html.flat-theme .markdown-body h1 { - font-size: 20px; - line-height: 28px; -} - -html.flat-theme .markdown-body h2 { - font-size: 17px; - line-height: 25.5px; -} - -html.flat-theme .markdown-body h3 { - font-size: 15px; - line-height: 22.5px; - letter-spacing: 0; -} - -html.flat-theme .markdown-body h4 { - font-size: 13.5px; - line-height: 21px; - letter-spacing: 0; -} - html.flat-theme .markdown-body p { margin: 0 0 16px; - line-height: 1.625; } html.flat-theme .markdown-body a { - font-weight: 400; - color: rgb(var(--ui-ink)); text-decoration: underline; text-decoration-color: rgb(var(--ui-line-focus)); text-decoration-thickness: 1px; @@ -1543,20 +1501,6 @@ html.flat-theme .markdown-body :is(ul, ol) > li + li { html.flat-theme .markdown-body li { padding-left: 2px; - line-height: 1.625; -} - -html.flat-theme .markdown-body li::marker { - color: rgb(var(--ui-faint)); -} - -html.flat-theme .markdown-body strong { - font-weight: 500; - color: rgb(var(--ui-ink)); -} - -html.flat-theme .markdown-body em { - color: inherit; } html.flat-theme .markdown-body code { @@ -1564,8 +1508,6 @@ html.flat-theme .markdown-body code { border-radius: 0; background: none; font-family: var(--ui-font-mono); - font-size: 12.5px; - color: rgb(var(--ui-ink)); } html.flat-theme .markdown-body pre { @@ -1576,21 +1518,13 @@ html.flat-theme .markdown-body pre { background: rgb(var(--ui-surface)); } -html.flat-theme .markdown-body pre code { - font-size: 12.5px; - line-height: 1.625; - color: rgb(var(--ui-ink)); -} - html.flat-theme .markdown-body blockquote { margin: 16px 0; padding: 12px 14px; border: 1px solid rgb(var(--ui-line)); border-radius: var(--ui-radius-md); background: rgb(var(--ui-surface)); - font-size: 13px; font-style: normal; - color: rgb(var(--ui-muted)); } html.flat-theme .markdown-body blockquote::before { @@ -1613,7 +1547,6 @@ html.flat-theme .markdown-body table { border-radius: var(--ui-radius-lg); border-collapse: separate; border-spacing: 0; - font-size: 12.5px; } html.flat-theme .markdown-body :is(th, td) { @@ -1621,7 +1554,6 @@ html.flat-theme .markdown-body :is(th, td) { border: 0; border-bottom: 1px solid rgb(var(--ui-line)); vertical-align: top; - line-height: 1.625; } html.flat-theme .markdown-body tr:last-child td { @@ -1630,8 +1562,6 @@ html.flat-theme .markdown-body tr:last-child td { html.flat-theme .markdown-body th { background: rgb(var(--ui-subtle)); - font-weight: 500; - color: rgb(var(--ui-faint)); } html.flat-theme .markdown-body thead th:first-child { @@ -1657,9 +1587,10 @@ html.flat-theme .markdown-body img { /* ================================================================== 7 · the onboarding tour (driver.js) */ /* 7.1 The popover is a panel: surface, 1px line, radius 8, overlay shadow (the sign-in panel's frame, derived); title - 17/600 tight ink; text the docs paragraph (13.5/1.625 muted); footer the dialog footer (12/18, structural line, no - tint); progress and key hints in Geist Mono; next = the solid primary, back = default, skip = ghost; close = 28px - ghost. The page dims with the dialog scrim; the highlighted element carries the reference's accent ring (34%). */ + 18/28 600 ink (the Modal title); text 14px / 1.625 muted; footer the dialog footer (12/18, structural line, no + tint); the progress count in the console font, key hints in the mono stack at official's 11px; next = the solid + primary, back = default, skip = ghost, all at the .btn's 14/20 500; close = 28px ghost. The page dims with the + dialog scrim; the highlighted element carries the reference's accent ring (34%). */ html.flat-theme .driver-popover.theme-tour-popover { border: 1px solid rgb(var(--ui-line)) !important; border-radius: var(--ui-radius-lg) !important; @@ -1682,17 +1613,16 @@ html.flat-theme .theme-tour-popover .driver-popover-title { html.flat-theme .theme-tour-popover :is(.driver-popover-title, .driver-popover-title-text), html.flat-theme.dark .theme-tour-popover :is(.driver-popover-title, .driver-popover-title-text) { font-family: var(--ui-font) !important; - font-size: 17px !important; - line-height: 25.5px !important; + font-size: 18px !important; + line-height: 28px !important; font-weight: 600 !important; - letter-spacing: -0.025em !important; color: rgb(var(--ui-ink)) !important; } html.flat-theme .theme-tour-popover .driver-popover-description, html.flat-theme.dark .theme-tour-popover .driver-popover-description { padding: 0 var(--ui-dialog-x) 18px !important; - font-size: 13.5px !important; + font-size: 14px !important; line-height: 1.625 !important; color: rgb(var(--ui-muted)) !important; } @@ -1711,8 +1641,8 @@ html.flat-theme.dark .theme-tour-popover .driver-popover-footer { html.flat-theme .theme-tour-popover .driver-popover-progress-text, html.flat-theme.dark .theme-tour-popover .driver-popover-progress-text { - font-family: var(--ui-font-mono) !important; - font-size: 11px !important; + font-family: var(--ui-font) !important; + font-size: 12px !important; font-weight: 400 !important; font-variant-numeric: tabular-nums; color: rgb(var(--ui-label)) !important; @@ -1738,7 +1668,6 @@ html.flat-theme.dark .theme-tour-popover .shortcut-item kbd { background-color: rgb(var(--ui-subtle)) !important; box-shadow: none !important; font-family: var(--ui-font-mono) !important; - font-size: 10px !important; font-weight: 400 !important; line-height: 16px !important; color: rgb(var(--ui-faint)) !important; @@ -1754,9 +1683,9 @@ html.flat-theme.dark .theme-tour-popover .driver-popover-footer button { color: rgb(var(--ui-ink)) !important; box-shadow: none !important; font-family: var(--ui-font) !important; - font-size: 13px !important; - font-weight: 600 !important; - line-height: 19.5px !important; + font-size: 14px !important; + font-weight: 500 !important; + line-height: 20px !important; transition: color var(--ui-duration) var(--ui-ease), background-color var(--ui-duration) var(--ui-ease), border-color var(--ui-duration) var(--ui-ease) !important; } diff --git a/frontend/src/styles/console/pages/accounts.css b/frontend/src/styles/console/pages/accounts.css index 72f8358b1187..d7ce8fc02f29 100644 --- a/frontend/src/styles/console/pages/accounts.css +++ b/frontend/src/styles/console/pages/accounts.css @@ -63,12 +63,13 @@ html.flat-theme [data-ui="accounts-toolbar"] > [data-ui="accounts-actions"] { } /* Row 1: the search is the reference's (288 wide from 640px, and it gives way first). Its basis is the smallest - width, so row 1 breaks only when even that does not fit (at 1280 the actions leave it about 155px; the reference's - minimum is 176), and it grows back to 288 before the free space goes to the gap in front of the actions. */ + width, so row 1 breaks only when even that does not fit (at 1280 the actions, in the official 14px button type, + leave it about 131px, where 「搜索账号...」 still fits; the reference's minimum is 176), and it grows back to 288 + before the free space goes to the gap in front of the actions. */ html.flat-theme [data-ui="accounts-toolbar"] > [data-ui="account-search"] { - flex: 1 1 150px; + flex: 1 1 128px; width: auto; - min-width: 150px; + min-width: 128px; max-width: 288px; } @@ -114,13 +115,8 @@ html.flat-theme [data-ui="accounts-toolbar"] > [data-ui="account-filters"] { gap: 6px; } -/* The filter pills are controls (text roles, data.css 12: the toolbar's grey is the placeholder's): drawn like the - buttons and selects beside them, in the ink; under the pointer a pill takes the focus line, as the labelled field - pills of 使用记录 do (an active pill keeps its accent edge: this weighs less). */ -html.flat-theme [data-ui="accounts-toolbar"] [data-ui~="filter-pill"] { - --ui-ink-soft: var(--ui-data-primary); -} - +/* The filter pills are controls.css' recipe, in official's control text (ink-soft); under the pointer a pill takes the + focus line, as the labelled field pills of 使用记录 do (an active pill keeps its accent edge: this weighs less). */ html.flat-theme :where([data-ui="accounts-toolbar"]) [data-ui~="filter-pill"]:hover { border-color: rgb(var(--ui-line-focus)); } @@ -161,14 +157,14 @@ html.flat-theme [data-ui="account-filters"] [data-test^="account-filter-chip-"] flex-shrink: 0; } -/* 「清除」 all filters: the reference's ghost 「✕ Clear」 (30px, 8px in, 12.5px / 600 muted, ink on hover). */ +/* 「清除」 all filters: the reference's ghost 「✕ Clear」 (30px, 8px in, the template's 12px / 500, muted, ink on hover). */ html.flat-theme [data-ui="account-filters-clear"] { min-height: var(--ui-control-h); border-radius: var(--ui-radius-md); padding: 0 8px; - font-size: 12.5px; - line-height: 18.75px; - font-weight: 600; + font-size: 12px; + line-height: 16px; + font-weight: 500; color: rgb(var(--ui-muted)); transition: color var(--ui-duration) var(--ui-ease), background-color var(--ui-duration) var(--ui-ease); } @@ -178,20 +174,19 @@ html.flat-theme [data-ui="account-filters-clear"]:hover { color: rgb(var(--ui-ink)); } -/* The result count: 「1,081 models」 in Geist Mono 11 at the right end (REF §8.6), the mono label's grey. */ +/* The result count: 「1,081 models」 at 12px at the right end (REF §8.6), the label grey. */ html.flat-theme [data-ui="accounts-count"] { margin-left: auto; padding-left: 8px; - font-family: var(--ui-font-mono); - font-size: 11px; - line-height: 16.5px; + font-size: 12px; + line-height: 16px; font-variant-numeric: tabular-nums; white-space: nowrap; color: rgb(var(--ui-label)); } /* Filter menus (AccountConsoleFilters): the panel and the options are overlays.css' menu; here only the option - parts: the list scrolls without upstream's inset, 14px native checkboxes (REF §8.4), counts in Mono 11. */ + parts: the list scrolls without upstream's inset, 14px native checkboxes (REF §8.4), counts at 12px. */ html.flat-theme [data-ui="account-filters"] [data-ui="menu"] > div:first-child { padding: 0; } @@ -211,9 +206,8 @@ html.flat-theme [data-ui="account-filters"] [data-ui="menu-item"] > input[type=" /* (counts sit on the rows' hover fill, where the label grey falls to 4.35:1: they take the muted grey) */ html.flat-theme :is([data-ui="account-filter-option-count"], [data-ui="account-folder-menu"] [data-ui="menu"] [data-ui="account-folder-count"]) { - font-family: var(--ui-font-mono); - font-size: 11px; - line-height: 16.5px; + font-size: 12px; + line-height: 16px; font-variant-numeric: tabular-nums; color: rgb(var(--ui-muted)); } @@ -229,8 +223,7 @@ html.flat-theme [data-ui="account-folder-menu"] > [data-ui~="filter-pill"] { html.flat-theme [data-ui="account-folder-menu"] > [data-ui~="filter-pill"] > [data-ui="account-folder-count"] { flex-shrink: 0; - font-family: var(--ui-font-mono); - font-size: 10.5px; + font-size: 12px; line-height: 16px; font-weight: 400; font-variant-numeric: tabular-nums; @@ -434,15 +427,15 @@ html.flat-theme .table-wrapper[data-table="accounts"] :is(th, td)[data-column="s height: 14px; } -/* Name: the account name (13 / 600 ink, REF's model name) and its e-mail (12px muted) on one line, each ending in an - ellipsis with its title. The columns follow upstream's order (select, name, id, platform / type, capacity, status, - scheduling, ...), and status and the scheduling switch must be in view without scrolling: the name column takes - what is left of the content width once select 44 + id 79 + platform 312 (18rem cap, below, + its padding) + +/* Name: the account name (14 / 500 ink, the template's font-medium) and its e-mail (12px meta) on one line, each ending + in an ellipsis with its title. The columns follow upstream's order (select, name, id, platform / type, capacity, + status, scheduling, ...), and status and the scheduling switch must be in view without scrolling: the name column + takes what is left of the content width once select 44 + id 79 + platform 312 (18rem cap, below, + its padding) + capacity 87 + status 64 + scheduling 64 + the sticky actions 127 are placed (the content is the viewport less the - 240px sidebar and 2 x 28 gutters; 12px spare), between 12rem and 16rem: 16rem at 1440, about 12rem at 1280. - Below 1440 the platform column gives up to 48px of its cap (15rem at 1280) and a status with a second line stays - within 6rem (both below), so a page holding a temporarily unschedulable or rate-limited account still shows the - scheduling switch at 1280. */ + 240px sidebar and 2 x 28 gutters; 12px spare), between 12rem and 16rem: 16rem at 1440, about 12rem at 1280. Below + 1440 the platform column gives up to 48px of its cap (15rem at 1280) and a status with a second line stays within + 6rem (both below), so a page holding a temporarily unschedulable or rate-limited account still shows the scheduling + switch at 1280. */ html.flat-theme .table-wrapper[data-table="accounts"] :is(th, td)[data-column="name"] { width: clamp(12rem, calc(100vw - 1086px), 16rem); min-width: clamp(12rem, calc(100vw - 1086px), 16rem); @@ -459,29 +452,22 @@ html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="name"] > d html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="name"] > div:where(:not([data-ui="cell-lines"])) > :first-child { flex: 0 1 auto; min-width: 0; - font-weight: 500; } +/* the e-mail under the name: the template's text-xs meta (12 / 16) */ html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="name"] > div > span + span { flex: 1 1 0; min-width: 3em; font-size: 12px; - line-height: 18px; - /* the e-mail under the name is a secondary value (data.css 12) */ - color: rgb(var(--ui-data-secondary)); } /* Integration review: a name that links to the account's homepage sits in HelpTooltip's root, which keeps its own - ml-1 (the template's -ml-1 loses to it) and whose link carries font-medium: the linked and the plain names start on - the same edge at the same 500 weight (palette C: 600 -> 500). The dotted underline (the link's cue) stays. */ + ml-1 (the template's -ml-1 loses to it): the linked and the plain names start on the same edge, both at the + template's font-medium (500). The dotted underline (the link's cue) stays. */ html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="name"] > div > [data-ui="help-tooltip"]:first-child { margin-left: 0; } -html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="name"] > div > [data-ui="help-tooltip"]:first-child > a { - font-weight: 500; -} - /* Platform / type: the identity chips (in their platform colours, data.css 6.1; nothing here recolours them) and the compact-mode line side by side, in at most 18rem at 1440 and 15rem at 1280 (so the status and scheduling columns after it keep their place): an account whose chips need more (OAuth with plan and privacy, a subscription expiry) @@ -517,7 +503,7 @@ html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="status"] d text-align: center; } -/* Classification / route: the two labelled values on one line; the labels are mono labels. */ +/* Classification / route: the two labelled values on one line; the labels are inline labels (below). */ html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="taxonomy_route"] > div:where(:not([data-ui="cell-lines"])) { display: flex; align-items: center; @@ -539,10 +525,12 @@ html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="taxonomy_r max-width: 18rem; } -/* (in a hovered or selected row the label grey falls to 4.12 / 4.43:1: labels inside rows take the muted grey) */ +/* 管理分类 / 请求路由 (the fork's column; its template draws a 10px uppercase micro label): official's inline label + (UsageTable's 「入站:」), 12 / 16 / 500 in the meta grey, sentence case; tables.css gives them their 50px gutter */ html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="taxonomy_route"] span[class~="uppercase"][class~="text-[10px]"] { font: var(--ui-label-font); letter-spacing: var(--ui-label-tracking); + text-transform: none; color: rgb(var(--ui-muted)); } @@ -635,7 +623,7 @@ html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="actions"] html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="actions"] button[class~="flex-col"] > span[class~="text-xs"] { font-size: 12px; - line-height: 18px; + line-height: 16px; } @media (hover: hover) and (pointer: fine) { @@ -671,28 +659,13 @@ html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="actions"] } } -/* Text roles (data.css 12) of the accounts table whose template class names another role: - - 管理分类 and 请求路由 are inline labels (template spans); the folder and the route summary are their values; - - priority, scheduling weight and the account rate are figures (primary); 「未探测」 is the labelled value of - 上游声明倍率 (its class says label). */ -html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="taxonomy_route"] span[class~="font-medium"][class~="text-gray-700"], -html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="taxonomy_route"] div[class~="truncate"][class~="text-gray-500"], -html.flat-theme .table-wrapper[data-table="accounts"] [data-ui="cell-line"][data-line="upstream_billing_rate"] [class~="font-medium"][class~="text-gray-400"] { - --theme-text-gray-100: var(--ui-data-labelled); --theme-text-gray-200: var(--ui-data-labelled); --theme-text-gray-300: var(--ui-data-labelled); - --theme-text-gray-400: var(--ui-data-labelled); --theme-text-gray-500: var(--ui-data-labelled); --theme-text-gray-600: var(--ui-data-labelled); - --theme-text-gray-700: var(--ui-data-labelled); --theme-text-gray-800: var(--ui-data-labelled); --theme-text-gray-900: var(--ui-data-labelled); - --theme-text-gray-950: var(--ui-data-labelled); --theme-text-dark-100: var(--ui-data-labelled); --theme-text-dark-200: var(--ui-data-labelled); - --theme-text-dark-300: var(--ui-data-labelled); --theme-text-dark-400: var(--ui-data-labelled); --theme-text-dark-500: var(--ui-data-labelled); - --theme-text-dark-600: var(--ui-data-labelled); --ui-ink-soft: var(--ui-data-labelled); -} - -html.flat-theme .table-wrapper[data-table="accounts"] [data-ui="cell-line"]:is([data-line="priority"], [data-line="scheduler_score"], [data-line="rate_multiplier"]) > [data-ui="cell-line-value"] [class~="text-gray-700"] { - --theme-text-gray-100: var(--ui-data-primary); --theme-text-gray-200: var(--ui-data-primary); --theme-text-gray-300: var(--ui-data-primary); - --theme-text-gray-400: var(--ui-data-primary); --theme-text-gray-500: var(--ui-data-primary); --theme-text-gray-600: var(--ui-data-primary); - --theme-text-gray-700: var(--ui-data-primary); --theme-text-gray-800: var(--ui-data-primary); --theme-text-gray-900: var(--ui-data-primary); - --theme-text-gray-950: var(--ui-data-primary); --theme-text-dark-100: var(--ui-data-primary); --theme-text-dark-200: var(--ui-data-primary); - --theme-text-dark-300: var(--ui-data-primary); --theme-text-dark-400: var(--ui-data-primary); --theme-text-dark-500: var(--ui-data-primary); - --theme-text-dark-600: var(--ui-data-primary); --ui-ink-soft: var(--ui-data-primary); +/* The fork's own sentence lines in the 用量 cell (the codex turn tickets, the stale / fetch-failed notes; upstream has + no such lines) read at 12 / 16 like the cell's other text; upstream's micro widgets (the 9-10px bars and chips) + keep their template sizes. */ +html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="usage"] [data-usage-variant="list"] [class~="max-w-[15rem]"] > div[class~="text-[10px]"][class~="leading-4"], +html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="usage"] :is([data-test="usage-stale"], [data-test="usage-fetch-failed"]) { + font-size: 12px; + line-height: 16px; } /* 优先级: upstream's quick-adjust control (AccountPriorityCell: − value +; the two steppers show under the pointer or @@ -721,8 +694,8 @@ html.flat-theme .table-wrapper[data-table="accounts"] [data-testid="account-prio /* ================================================================== 6 · card grid and compact list */ /* Shared parts of the two other views: the 14px checkbox with its 32px hit area (as in the table), the account id - in Geist Mono 11 (the reference's meta text), the mono labels, and the icon buttons (REF §8.3: 28px, radius 6, - muted; the subtle fill and ink on hover). */ + (the template's 10px mono) and the labels (the table's inline labels, 12 / 16 / 500) in the meta grey, and the icon + buttons (REF §8.3: 28px, radius 6, muted; the subtle fill and ink on hover). */ html.flat-theme :is([data-test="account-card-grid"], [data-test="account-compact-list"]) label:has(> input[type="checkbox"]) { width: 32px; min-width: 32px; @@ -743,14 +716,13 @@ html.flat-theme :is([data-test="account-card-grid"], [data-test="account-compact /* (ids and labels sit on hovered and selected fills too: the muted grey keeps 4.5:1 there) */ html.flat-theme :is([data-test="account-card-grid"], [data-test="account-compact-list"]) span[class~="font-mono"][class~="text-[10px]"] { - font-size: 11px; - line-height: 16.5px; color: rgb(var(--ui-muted)); } html.flat-theme :is([data-test="account-card-grid"], [data-test="account-compact-list"]) :is(div, span)[class~="uppercase"][class~="text-[10px]"] { font: var(--ui-label-font); letter-spacing: var(--ui-label-tracking); + text-transform: none; color: rgb(var(--ui-muted)); } @@ -896,16 +868,16 @@ html.flat-theme [data-test="account-details-drawer"] > header + div > section { padding: 16px var(--ui-dialog-x, 18px); } -/* Section titles: the mono label (REF §5: section and field labels). */ +/* Section titles: the label (12 / 16 / 500, sentence case; REF §5: section and field labels). */ html.flat-theme [data-test="account-details-drawer"] > header + div > section > h3, html.flat-theme [data-test="account-details-drawer"] > header + div > section > div:first-child > h3 { font: var(--ui-label-font); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; color: rgb(var(--ui-label)); } -/* Definition lists: the label muted, the value ink; figures stay Geist Mono tabular. */ +/* Definition lists: the label muted, the value ink; the template's mono figures stay tabular. */ html.flat-theme [data-test="account-details-drawer"] > header + div dl > dt { color: rgb(var(--ui-muted)); } @@ -930,8 +902,8 @@ html.flat-theme [data-test="account-details-drawer"] > header + div section labe border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-surface)); padding: 4px 8px; - font-size: 12.5px; - line-height: 18.75px; + font-size: 14px; + line-height: 20px; color: rgb(var(--ui-ink-soft)); } @@ -941,17 +913,17 @@ html.flat-theme [data-test="account-details-drawer"] > header + div section labe } /* The scheduling state beside the status badge (the chip row at the top of the drawer, AccountDetailsDrawer.vue): the - status chip it sits next to (data.css 6 .badge: a capsule, Geist Mono at the badge size, 500, .025em, 1 / 7 inset; - on = the success pill, off = the grey pill), not a 4px Geist 12 rectangle. Final review V1-13. */ + status chip it sits next to (data.css 6 .badge: a capsule, 12 / 16 / 500 sans, sentence case, 1 / 7 inset; on = the + success pill, off = the grey pill), not a 4px rectangle. Final review V1-13. */ html.flat-theme [data-test="account-details-drawer"] > header + div > section:first-child > div:first-child > span[class~="rounded"][class~="px-2"][class~="py-0.5"][class~="text-xs"] { padding: 1px 7px; border-radius: 9999px; - font-family: var(--ui-font-mono); + font-family: var(--ui-font); font-size: var(--ui-badge-size); font-weight: 500; line-height: var(--ui-badge-leading); - letter-spacing: 0.025em; - text-transform: uppercase; + letter-spacing: normal; + text-transform: none; } html.flat-theme [data-test="account-details-drawer"] > header + div > section:first-child > div:first-child > span[class~="rounded"][class~="px-2"][class~="py-0.5"][class~="text-xs"][class~="bg-emerald-50"] { @@ -1019,8 +991,8 @@ html.flat-theme :is([data-ui="account-test"], [data-ui="account-stats"]) > div:f } /* 9.2 Usage statistics, the four headline figures: the reference's KPI card (REF §8.13, the dashboards' StatTile in - data.css 7): surface, 1px structural line, radius 6, 8 / 12 inset, the mono label, the value 15 / 600 tabular ink, - the detail 11px muted; no tint and no icon tile. */ + data.css 7): surface, 1px structural line, radius 6, 8 / 12 inset, the label (12 / 16 / 500), the value at the + template's text-2xl font-bold (24 / 32 / 700) tabular ink, the detail 12px muted; no tint and no icon tile. */ html.flat-theme [data-ui="account-stats"] > div[class~="grid"] > div[class~="card"][class~="bg-gradient-to-br"] { min-height: 76px; padding: 8px 12px; @@ -1042,7 +1014,7 @@ html.flat-theme [data-ui="account-stats"] > div[class~="grid"] > div[class~="car html.flat-theme [data-ui="account-stats"] > div[class~="grid"] > div[class~="card"][class~="bg-gradient-to-br"] > div:first-child > span { font: var(--ui-label-font); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; color: rgb(var(--ui-label)); } @@ -1051,9 +1023,9 @@ html.flat-theme [data-ui="account-stats"] > div[class~="grid"] > div[class~="car } html.flat-theme [data-ui="account-stats"] > div[class~="grid"] > div[class~="card"][class~="bg-gradient-to-br"] > p[class~="text-2xl"] { - font-size: 15px; - font-weight: 600; - line-height: 22.5px; + font-size: 24px; + font-weight: 700; + line-height: 32px; font-variant-numeric: tabular-nums; color: rgb(var(--ui-ink)); } @@ -1061,14 +1033,15 @@ html.flat-theme [data-ui="account-stats"] > div[class~="grid"] > div[class~="car html.flat-theme [data-ui="account-stats"] > div[class~="grid"] > div[class~="card"][class~="bg-gradient-to-br"] > p:not([class~="text-2xl"]), html.flat-theme [data-ui="account-stats"] > div[class~="grid"] > div[class~="card"][class~="bg-gradient-to-br"] > p:not([class~="text-2xl"]) > span { margin-top: 0; - font-size: 11px; - line-height: 16.5px; + font-size: 12px; + line-height: 16px; color: rgb(var(--ui-muted)); } /* 9.3 Usage statistics, the six detail cards (today, highest cost day, highest request day, tokens, performance, - recent activity): the title is the mono section label (REF §5) without its tinted tile, the rows are label (12px - muted) and value (13 / 600 tabular ink); the two values upstream colours for decoration are ink. */ + recent activity): the title is the section label (12 / 16 / 500, REF §5) without its tinted tile, the rows are label + (12px muted) and value (the template's 14 / 600 tabular ink); the two values upstream colours for decoration are + ink. */ html.flat-theme [data-ui="account-stats"] > div[class~="grid"] > div[class~="card"]:not([class~="bg-gradient-to-br"]) { padding: 14px; } @@ -1084,7 +1057,7 @@ html.flat-theme [data-ui="account-stats"] > div[class~="grid"] > div[class~="car html.flat-theme [data-ui="account-stats"] > div[class~="grid"] > div[class~="card"]:not([class~="bg-gradient-to-br"]) > div[class~="mb-3"]:first-child > span { font: var(--ui-label-font); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; color: rgb(var(--ui-label)); } @@ -1095,8 +1068,8 @@ html.flat-theme [data-ui="account-stats"] > div[class~="grid"] > div[class~="car } html.flat-theme [data-ui="account-stats"] > div[class~="grid"] > div[class~="card"]:not([class~="bg-gradient-to-br"]) > div[class~="space-y-2"] > div > span:last-child { - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; font-variant-numeric: tabular-nums; color: rgb(var(--ui-ink)); } @@ -1111,8 +1084,8 @@ html.flat-theme [data-ui="account-oauth-flow"] { background-color: rgb(var(--ui-subtle)); } -/* (its mono field labels take the muted grey, which holds 4.5:1 on the subtle ground: the label grey reads 4.35 there - in light, as in data.css 11's callouts; dark keeps its label grey) */ +/* (its field labels take the muted grey, which holds 4.5:1 on the subtle ground, as in data.css 11's callouts; the label + grey is the same value in the official palette, so this only pins it; dark keeps its label grey) */ html.flat-theme:not(.dark) [data-ui="account-oauth-flow"] { --ui-label: var(--ui-muted); } @@ -1184,8 +1157,8 @@ html.flat-theme [data-ui="account-create-steps"] div[class~="h-0.5"] { /* 9.6 Choices inside the account forms (create, edit, bulk edit); upstream's accent and purple tints go. - A two-way switch whose choices are labels only (the model whitelist / mapping, the bulk form's RPM strategy) is the reference's segmented control (REF §8.5, controls.css 7.1: the grey track, radius 8, the 2px inset; segments - 14 x 6 at 12.5 / 500, muted, ink on hover, the chosen one the raised pill at 600), like the same switch in the - other dialogs (the announcement's 「所有用户 / 按条件」). Final review V1-15. + 14 x 6 at the template's 14 / 500, muted, ink on hover, the chosen one the raised pill, same weight), like the same + switch in the other dialogs (the announcement's 「所有用户 / 按条件」). Final review V1-15. - A pair whose choices carry a description line (the create and edit forms' RPM strategy) is an option card like the account type cards above it (controls.css 8.1): the grouped fill inside the structural line, the chosen one an accent edge 2px wide on the surface. @@ -1208,8 +1181,8 @@ html.flat-theme :is(#create-account-form, #edit-account-form, #bulk-edit-account background-color: transparent; box-shadow: none; padding: var(--ctl-seg-pad-y) var(--ctl-seg-pad-x); - font-size: 0.78125rem; - line-height: 1.171875rem; + font-size: 0.875rem; + line-height: 1.25rem; font-weight: 500; white-space: nowrap; color: rgb(var(--ui-muted)); @@ -1225,7 +1198,7 @@ html.flat-theme :is(#create-account-form, #edit-account-form, #bulk-edit-account html.flat-theme :is(#create-account-form, #edit-account-form, #bulk-edit-account-form) button[class~="flex-1"][class~="rounded-lg"]:is([class~="bg-primary-100"], [class~="bg-purple-100"]):not(:has(div[class~="text-[10px]"])) { background-color: rgb(var(--ui-thumb)); color: rgb(var(--ui-ink)); - font-weight: 600; + font-weight: 500; box-shadow: var(--ui-thumb-shadow); } @@ -1238,9 +1211,9 @@ html.flat-theme :is(#create-account-form, #edit-account-form, #bulk-edit-account border-radius: var(--ui-radius-lg); background-color: rgb(var(--ui-subtle) / 0.6); box-shadow: inset 0 0 0 1px rgb(var(--ui-line)); - font-size: 0.8125rem; - line-height: 1.21875rem; - font-weight: 600; + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 500; color: rgb(var(--ui-ink-soft)); transition-property: color, background-color, box-shadow; transition-duration: var(--ui-duration); @@ -1263,10 +1236,8 @@ html.flat-theme :is(#create-account-form, #edit-account-form, #bulk-edit-account outline-offset: 2px; } -/* (the strategy's hint line: upstream's 10px at 70 % opacity becomes 11px muted) */ +/* (the strategy's hint line: upstream's 10px, muted at full opacity instead of 70 %) */ html.flat-theme :is(#create-account-form, #edit-account-form, #bulk-edit-account-form) button[class~="flex-1"][class~="rounded-lg"] div[class~="text-[10px]"][class~="opacity-70"] { - font-size: 11px; - line-height: 16.5px; font-weight: 400; opacity: 1; color: rgb(var(--ui-muted)); @@ -1276,9 +1247,9 @@ html.flat-theme :is(#create-account-form, #edit-account-form, #bulk-edit-account min-height: var(--ui-control-h); padding: 0 0.75rem; border-radius: var(--ui-radius-md); - font-size: 0.8125rem; - line-height: 1.21875rem; - font-weight: 600; + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 500; } /* (the chosen mode's ink is controls.css 10.1's, shared by every utility-built choice; the others are drawn here) */ diff --git a/frontend/src/styles/console/pages/dashboard-usage.css b/frontend/src/styles/console/pages/dashboard-usage.css index ec41f00f5df1..71db82806931 100644 --- a/frontend/src/styles/console/pages/dashboard-usage.css +++ b/frontend/src/styles/console/pages/dashboard-usage.css @@ -3,8 +3,10 @@ * Pages: 管理控制台 (/admin/dashboard), 使用记录 (/admin/usage: 用量明细 / 错误请求 / 用户排行, the filters, the cleanup * dialog's filters), 仪表盘 (/dashboard) and 使用记录 (/usage). Reference counterpart: the Experiential Labs console's /logs * (REF-CONSOLE §7.1 vertical rhythm, §8.4-§8.7 toolbar controls and tables, §8.12-§8.14 KPI and chart cards; crops - * logs-toolbar, kpi-row, chart-card, usage-card, history-card / -header / -head / -row). Colour follows the owner's - * decision 2 (2026-09-29): status and semantic states and the accent (links) keep their colour; category chips and + * logs-toolbar, kpi-row, chart-card, usage-card, history-card / -header / -head / -row). Text follows official sub2api + * (the OS UI font, the templates' sizes and colours): in the record tables the figures and the category chips take + * their template colours (cost green, cache read sky, cache write amber, the group / type / billing tags); elsewhere + * the owner's decision 2 (2026-09-29) stands: status, semantic states and the accent (links) keep their colour, * decorative figures are neutral. * * Page roots (data-ui hooks, one attribute each, no other template change): @@ -66,8 +68,9 @@ html.flat-theme :is([data-ui="admin-dashboard"], [data-ui="admin-usage"], [data- /* ================================================================== 3 · labelled field pills */ /* Every filter keeps its label, inside its control: the reference's filter trigger ("Price 1 ⌄", REF §8.6: 30px, radius - 6, the control outline on the surface) holding the field label as its mono label (REF §5 .mono-label, --ui-label) - and then the value, so a filter row is one line of 30px controls instead of labels stacked over 34px fields. + 6, the control outline on the surface) holding the field label as the console's label (12 / 16 / 500, sentence + case, --ui-label) and then the value, so a filter row is one line of 30px controls instead of labels stacked over + 34px fields. The pills: the time range and the granularity, the admin filter strip (UsageFilters), the user usage filters. */ html.flat-theme :is([data-ui="admin-dashboard"], [data-ui="admin-usage"], [data-ui="user-dashboard"], [data-ui="user-usage"]) .card[class~="p-4"][data-ui~="time-bar"]:not(uipad[data-uipad]):not(uipad) > div > div[class~="items-center"][class~="gap-2"], html.flat-theme [data-ui="usage-filters"] > div > div[class~="flex-1"] > div, @@ -85,18 +88,17 @@ html.flat-theme [data-ui="user-usage"] > .card[class~="p-6"] > div > div[class~= transition: border-color var(--ui-duration) var(--ui-ease), box-shadow var(--ui-duration) var(--ui-ease); } -/* the label: the mono label at the KPI labels' weight, on one line */ +/* the label: the console's label (the KPI labels' 12 / 16 / 500), on one line */ html.flat-theme :is([data-ui="admin-dashboard"], [data-ui="admin-usage"], [data-ui="user-dashboard"], [data-ui="user-usage"]) .card[class~="p-4"][data-ui~="time-bar"]:not(uipad[data-uipad]):not(uipad) > div > div[class~="items-center"][class~="gap-2"] > span:first-child, html.flat-theme [data-ui="usage-filters"] > div > div[class~="flex-1"] > div > label, html.flat-theme [data-ui="user-usage"] > .card[class~="p-6"] > div > div[class~="flex-1"] > div > label { flex: none; margin: 0; - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); line-height: var(--ui-label-leading); - font-weight: 400; + font-weight: var(--ui-label-weight); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; white-space: nowrap; color: rgb(var(--ui-label)); } @@ -133,8 +135,8 @@ html.flat-theme [data-ui="usage-filters"] > div > div[class~="flex-1"] > div > i border-radius: 0 var(--ui-radius-md) var(--ui-radius-md) 0; background-color: transparent; box-shadow: none; - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; } /* states: hover and an open menu take the focus line (the reference's house-select); keyboard focus on a trigger and any @@ -178,7 +180,7 @@ html.flat-theme [data-ui="usage-filters"] > div > div[class~="flex-1"] > div > b width: 20px; height: 20px; border-radius: var(--ui-radius-sm); - font-size: 11px; + font-size: 12px; line-height: 1; color: rgb(var(--ui-muted)); translate: 0 -50%; @@ -266,16 +268,15 @@ html.flat-theme [data-ui="usage-filters"] > div > div[class~="flex-1"] > div > d margin-top: 0; } -/* a result's id: Geist Mono in the faint text, after the name */ +/* a result's id: the template's 12px in the faint text, after the name */ html.flat-theme [data-ui="usage-filters"] > div > div[class~="flex-1"] > div > div[class~="absolute"] > button > span:last-child:not(:first-child) { - font-family: var(--ui-font-mono); - font-size: 11px; + font-size: 12px; color: rgb(var(--ui-faint)); } /* ================================================================== 6 · 管理控制台: quick actions */ -/* The quick-action card becomes one row (MAPPING §4.5: about 44px): its title as the mono label, then each action as +/* The quick-action card becomes one row (MAPPING §4.5: about 44px): its title as the label, then each action as a one-line option card (the reference's dialog option card, REF §8.11: radius 8, 1px structural line, the subtle fill at 60 %, the control outline on hover; a 26px neutral icon square) with its title and description side by side and the chevron. Nothing is dropped; the description wraps under the title only where the row is too narrow. */ @@ -293,12 +294,11 @@ html.flat-theme [data-ui="admin-dashboard"] > .card[class~="p-4"]:not(uipad[data } html.flat-theme [data-ui="admin-dashboard"] > .card[class~="p-4"]:not(uipad[data-uipad][data-uipad]) > div:first-child > h2 { - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); line-height: var(--ui-label-leading); - font-weight: 400; + font-weight: var(--ui-label-weight); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; white-space: nowrap; color: rgb(var(--ui-label)); } @@ -350,7 +350,7 @@ html.flat-theme [data-ui="admin-usage"] > .card[data-ui~="usage-records"]:not(ui } /* The IP-location bar above a table (UsageTable's own, IpGeoBatchToolbar in the error list): 6 / 16, the count 12px - muted, the batch action a link-weight accent text button (REF §8.12 tray links: 12.5 / 600, underline on hover). */ + muted, the batch action an accent text button (the template's 12 / 500, underline on hover). */ html.flat-theme :is([data-ui="admin-usage"], [data-ui="user-usage"]) div[class~="justify-end"][class~="border-b"]:not(uipad[data-uipad][data-uipad]) { gap: 12px; padding: 6px 16px; @@ -367,9 +367,9 @@ html.flat-theme :is([data-ui="admin-usage"], [data-ui="user-usage"]) div[class~= padding: 2px 0; border-radius: var(--ui-radius-sm); background-color: transparent; - font-size: 12.5px; - line-height: 18.75px; - font-weight: 600; + font-size: 12px; + line-height: 16px; + font-weight: 500; color: var(--ui-link); } @@ -381,80 +381,11 @@ html.flat-theme :is([data-ui="admin-usage"], [data-ui="user-usage"]) div[class~= /* ================================================================== 9 · the usage records table */ -/* Category chips (decision 2): the group, the request type, native compaction, the billing mode and the cache-TTL chips - (1h in the cell, 5m / 1h in the token card) carry data.css' data-ui~="category-chip" (the reference's neutral chip). - One request type is a state, not a category: a request stopped by the upstream's security policy (「安全策略」, - upstream's red chip) keeps the danger chip, on both records tables. The long-context x2, the overridden cache TTL (R), - the model audit line and a deleted user keep their status colours (no hook). */ -html.flat-theme :is(.table-wrapper[data-table="usage"], [data-ui="user-usage"] .table-wrapper) [data-ui~="category-chip"][class~="bg-red-100"] { - border-color: rgb(var(--ui-danger) / 0.3); - background-color: rgb(var(--ui-danger-soft)); - color: rgb(var(--ui-danger)); -} - -html.flat-theme :is(.table-wrapper[data-table="usage"], [data-ui="user-usage"] .table-wrapper, div[class~="fixed"][class~="pointer-events-none"]) [data-ui~="category-chip"] { - box-shadow: none; -} - -/* Texture of the cells (REF §8.7 /logs request table: the model 13/500 ink, ids and paths Geist Mono): the requested - model at the row's 13px (the mapping chain under it stays 12px muted), the user id after the e-mail and the endpoint - paths in Geist Mono; the 「入站 / 上游」 labels stay sans. Same on the error lists of both pages. */ -html.flat-theme .table-wrapper[data-table="usage"] td[data-column="model"] > :where(:not([data-ui="cell-lines"])) :is(span, div)[class~="font-medium"][class~="text-gray-900"] { - font-size: 13px; - line-height: 19.5px; -} - -html.flat-theme :is(.table-wrapper[data-table="usage"], [data-ui="admin-usage"] .table-wrapper) td[data-column="user"] > div > span:last-child { - font-family: var(--ui-font-mono); - font-size: 12px; -} - -html.flat-theme :is(.table-wrapper[data-table="usage"], [data-ui="admin-usage"] .table-wrapper, [data-ui="user-usage"] .table-wrapper) [data-ui="cell-line"][data-line="endpoint"] div > span[class~="ml-1"], -html.flat-theme :is(.table-wrapper[data-table="usage"], [data-ui="admin-usage"] .table-wrapper, [data-ui="user-usage"] .table-wrapper) td[data-column="endpoint"] div > span[class~="ml-1"] { - font-family: var(--ui-font-mono); - font-size: 11.5px; - color: rgb(var(--ui-ink-soft)); -} - -/* Hierarchy by colour and weight instead of more black (palette C, then the text roles of data.css 12): the requested - model and the billed cost are the row's ink (500); every token figure (input, output, cache, image) the ink at 400; - the key and account names 400 (the primary role), the reasoning effort the secondary one; the endpoint's - 「入站 / 上游」 labels 400; times and IPs tabular figures. The token glyphs get upstream's hues back as identity-safe - text colours (input emerald, output violet, cache read sky, cache write amber, image fuchsia / pink, indigo), so the - figures stay neutral and the colour says which figure is which; the account's billed cost is a reference value (the - grey). The key, account, reasoning and endpoint rules also name their lines of a stacked cell - ([data-ui="cell-line"][data-line], the table-width track's stacked layout), as that track does for the rules it - carries over. */ -html.flat-theme .table-wrapper[data-table="usage"] td:is([data-column="api_key"], [data-column="account"], [data-column="reasoning_effort"]) span[class~="text-gray-900"], -html.flat-theme .table-wrapper[data-table="usage"] [data-ui="cell-line"]:is([data-line="api_key"], [data-line="account"], [data-line="reasoning_effort"]) > [data-ui="cell-line-value"] span[class~="text-gray-900"] { - color: rgb(var(--ui-ink-soft)); - font-weight: 400; -} - -/* (text roles, data.css 12: every token figure is a primary figure, in the ink like the billed cost, Geist Mono 13 / 400 - tabular; the billed cost keeps 500, the one weighted figure of the row. They were ink-soft Mono 13 / 500.) */ -html.flat-theme .table-wrapper[data-table="usage"] td[data-column="tokens"] span[class~="font-medium"][class~="text-gray-900"], -html.flat-theme .table-wrapper[data-table="usage"] td[data-column="tokens"] :is([class~="text-sky-600"], [class~="text-amber-600"], [class~="text-fuchsia-600"], [class~="text-pink-600"]) { - color: rgb(var(--ui-data-primary)); - font-family: var(--ui-font-mono); - font-size: 13px; - font-weight: 400; - font-variant-numeric: tabular-nums; -} - -html.flat-theme .table-wrapper[data-table="usage"] td[data-column="cost"] span[class~="text-green-600"] { - color: rgb(var(--ui-ink)); -} - -html.flat-theme :is(.table-wrapper[data-table="usage"], [data-ui="admin-usage"] .table-wrapper, [data-ui="user-usage"] .table-wrapper) td[data-column="endpoint"] div > span[class~="font-medium"], -html.flat-theme :is(.table-wrapper[data-table="usage"], [data-ui="admin-usage"] .table-wrapper, [data-ui="user-usage"] .table-wrapper) [data-ui="cell-line"][data-line="endpoint"] div > span[class~="font-medium"] { - font-weight: 400; -} - -html.flat-theme .table-wrapper[data-table="usage"] td:is([data-column="created_at"], [data-column="ip_address"]) span { - font-variant-numeric: tabular-nums; -} - +/* The cells follow official sub2api's classes (data.css: the 14 / 20 cell text; the category chips are the templates' + own tags; the figures their template colours: tokens ink 500, cost green, cache write amber, the account's billed + cost orange, latency by severity). Here only the token glyphs, upstream's hues as identity-safe text colours (input + emerald, output violet, cache read sky, cache write amber, image fuchsia / pink, indigo), and the cache-read figure + in its glyph's sky (official's sky-600 / 400 at AA) instead of the hue fold's info blue. */ html.flat-theme .table-wrapper[data-table="usage"] td[data-column="tokens"] svg[class~="text-emerald-500"] { color: rgb(var(--ui-id-emerald)); } @@ -467,6 +398,10 @@ html.flat-theme .table-wrapper[data-table="usage"] td[data-column="tokens"] svg[ color: rgb(var(--ui-id-sky)); } +html.flat-theme .table-wrapper[data-table="usage"] td[data-column="tokens"] [class~="text-sky-600"] { + color: rgb(var(--ui-id-sky)); +} + html.flat-theme .table-wrapper[data-table="usage"] td[data-column="tokens"] svg[class~="text-amber-500"] { color: rgb(var(--ui-id-amber)); } @@ -483,117 +418,37 @@ html.flat-theme .table-wrapper[data-table="usage"] td[data-column="tokens"] svg[ color: rgb(var(--ui-id-indigo)); } -/* the account's billed cost (A $) is a reference value next to the billed cost: the grey, a secondary figure one pixel - under the row's 13px figures (it was 11px) */ -html.flat-theme .table-wrapper[data-table="usage"] td[data-column="cost"] div[class~="text-orange-500"] { - color: rgb(var(--ui-data-reference)); - font-size: 12px; - line-height: 18px; -} - -/* Text roles (data.css 12) of the records and error lists whose class names another role: - - reasoning effort qualifies the model: secondary (its class says primary; dark: its dark:text-white reads - --theme-on-fill, the ink inside tables, so that is moved too, on these text leaves only); - - 入站 / 上游: the endpoint path is the labelled value, 「入站:」 / 「上游:」 its label (their classes say secondary); - - error lists: the message is the row's content (primary); platform and category are secondary. - The secondary figures (latency, IP) are Geist Mono 12, a pixel under the primary 13px figures (latency was Geist - sans 12 / 500, the IP Mono 13). */ -html.flat-theme .table-wrapper[data-table="usage"] :is(td[data-column="reasoning_effort"], [data-ui="cell-line"][data-line="reasoning_effort"]) :is(span, div)[class~="text-gray-900"] { - --theme-text-gray-100: var(--ui-data-secondary); --theme-text-gray-200: var(--ui-data-secondary); --theme-text-gray-300: var(--ui-data-secondary); - --theme-text-gray-400: var(--ui-data-secondary); --theme-text-gray-500: var(--ui-data-secondary); --theme-text-gray-600: var(--ui-data-secondary); - --theme-text-gray-700: var(--ui-data-secondary); --theme-text-gray-800: var(--ui-data-secondary); --theme-text-gray-900: var(--ui-data-secondary); - --theme-text-gray-950: var(--ui-data-secondary); --theme-text-dark-100: var(--ui-data-secondary); --theme-text-dark-200: var(--ui-data-secondary); - --theme-text-dark-300: var(--ui-data-secondary); --theme-text-dark-400: var(--ui-data-secondary); --theme-text-dark-500: var(--ui-data-secondary); - --theme-text-dark-600: var(--ui-data-secondary); --ui-ink-soft: var(--ui-data-secondary); -} - -html.flat-theme.dark .table-wrapper[data-table="usage"] :is(td[data-column="reasoning_effort"], [data-ui="cell-line"][data-line="reasoning_effort"]) :is(span, div)[class~="text-gray-900"] { - --theme-on-fill: var(--ui-data-secondary); -} - -html.flat-theme :is(.table-wrapper[data-table="usage"], :is([data-ui~="usage-records"], [data-ui~="ops-error-list"], [data-ui="user-usage"]) .table-wrapper:not([data-table="usage"])) :is(td[data-column="endpoint"], [data-ui="cell-line"][data-line="endpoint"]) div[class~="break-all"] { - --theme-text-gray-100: var(--ui-data-labelled); --theme-text-gray-200: var(--ui-data-labelled); --theme-text-gray-300: var(--ui-data-labelled); - --theme-text-gray-400: var(--ui-data-labelled); --theme-text-gray-500: var(--ui-data-labelled); --theme-text-gray-600: var(--ui-data-labelled); - --theme-text-gray-700: var(--ui-data-labelled); --theme-text-gray-800: var(--ui-data-labelled); --theme-text-gray-900: var(--ui-data-labelled); - --theme-text-gray-950: var(--ui-data-labelled); --theme-text-dark-100: var(--ui-data-labelled); --theme-text-dark-200: var(--ui-data-labelled); - --theme-text-dark-300: var(--ui-data-labelled); --theme-text-dark-400: var(--ui-data-labelled); --theme-text-dark-500: var(--ui-data-labelled); - --theme-text-dark-600: var(--ui-data-labelled); --ui-ink-soft: var(--ui-data-labelled); -} - -html.flat-theme :is(.table-wrapper[data-table="usage"], :is([data-ui~="usage-records"], [data-ui~="ops-error-list"], [data-ui="user-usage"]) .table-wrapper:not([data-table="usage"])) :is(td[data-column="endpoint"], [data-ui="cell-line"][data-line="endpoint"]) div[class~="break-all"] > span[class~="font-medium"] { - --theme-text-gray-100: var(--ui-data-label); --theme-text-gray-200: var(--ui-data-label); --theme-text-gray-300: var(--ui-data-label); - --theme-text-gray-400: var(--ui-data-label); --theme-text-gray-500: var(--ui-data-label); --theme-text-gray-600: var(--ui-data-label); - --theme-text-gray-700: var(--ui-data-label); --theme-text-gray-800: var(--ui-data-label); --theme-text-gray-900: var(--ui-data-label); - --theme-text-gray-950: var(--ui-data-label); --theme-text-dark-100: var(--ui-data-label); --theme-text-dark-200: var(--ui-data-label); - --theme-text-dark-300: var(--ui-data-label); --theme-text-dark-400: var(--ui-data-label); --theme-text-dark-500: var(--ui-data-label); - --theme-text-dark-600: var(--ui-data-label); --ui-ink-soft: var(--ui-data-label); -} - -html.flat-theme :is([data-ui~="usage-records"], [data-ui~="ops-error-list"], [data-ui="user-usage"]) .table-wrapper:not([data-table="usage"]) td[data-column="message"] > span[class~="text-gray-600"] { - --theme-text-gray-100: var(--ui-data-primary); --theme-text-gray-200: var(--ui-data-primary); --theme-text-gray-300: var(--ui-data-primary); - --theme-text-gray-400: var(--ui-data-primary); --theme-text-gray-500: var(--ui-data-primary); --theme-text-gray-600: var(--ui-data-primary); - --theme-text-gray-700: var(--ui-data-primary); --theme-text-gray-800: var(--ui-data-primary); --theme-text-gray-900: var(--ui-data-primary); - --theme-text-gray-950: var(--ui-data-primary); --theme-text-dark-100: var(--ui-data-primary); --theme-text-dark-200: var(--ui-data-primary); - --theme-text-dark-300: var(--ui-data-primary); --theme-text-dark-400: var(--ui-data-primary); --theme-text-dark-500: var(--ui-data-primary); - --theme-text-dark-600: var(--ui-data-primary); --ui-ink-soft: var(--ui-data-primary); -} - -html.flat-theme :is([data-ui~="usage-records"], [data-ui~="ops-error-list"], [data-ui="user-usage"]) .table-wrapper:not([data-table="usage"]) :is(td[data-column="platform"], td[data-column="category"]) > span[class~="text-gray-900"], -html.flat-theme :is([data-ui~="usage-records"], [data-ui~="ops-error-list"], [data-ui="user-usage"]) .table-wrapper:not([data-table="usage"]) [data-ui="cell-line"]:is([data-line="platform"], [data-line="category"]) > [data-ui="cell-line-value"] > span[class~="text-gray-900"] { - --theme-text-gray-100: var(--ui-data-secondary); --theme-text-gray-200: var(--ui-data-secondary); --theme-text-gray-300: var(--ui-data-secondary); - --theme-text-gray-400: var(--ui-data-secondary); --theme-text-gray-500: var(--ui-data-secondary); --theme-text-gray-600: var(--ui-data-secondary); - --theme-text-gray-700: var(--ui-data-secondary); --theme-text-gray-800: var(--ui-data-secondary); --theme-text-gray-900: var(--ui-data-secondary); - --theme-text-gray-950: var(--ui-data-secondary); --theme-text-dark-100: var(--ui-data-secondary); --theme-text-dark-200: var(--ui-data-secondary); - --theme-text-dark-300: var(--ui-data-secondary); --theme-text-dark-400: var(--ui-data-secondary); --theme-text-dark-500: var(--ui-data-secondary); - --theme-text-dark-600: var(--ui-data-secondary); --ui-ink-soft: var(--ui-data-secondary); -} - -html.flat-theme.dark :is([data-ui~="usage-records"], [data-ui~="ops-error-list"], [data-ui="user-usage"]) .table-wrapper:not([data-table="usage"]) :is(td[data-column="platform"], td[data-column="category"]) > span[class~="text-gray-900"], -html.flat-theme.dark :is([data-ui~="usage-records"], [data-ui~="ops-error-list"], [data-ui="user-usage"]) .table-wrapper:not([data-table="usage"]) [data-ui="cell-line"]:is([data-line="platform"], [data-line="category"]) > [data-ui="cell-line-value"] > span[class~="text-gray-900"] { - --theme-on-fill: var(--ui-data-secondary); -} - -html.flat-theme .table-wrapper[data-table="usage"] td[data-column="latency"] span[class~="tabular-nums"] { - font-family: var(--ui-font-mono); - font-size: 12px; -} - -html.flat-theme :is(.table-wrapper[data-table="usage"], :is([data-ui~="usage-records"], [data-ui~="ops-error-list"], [data-ui="user-usage"]) .table-wrapper:not([data-table="usage"])) td:is([data-column="ip_address"], [data-column="client_ip"]) span[class~="font-mono"] { - font-size: 12px; -} - /* The token and cost breakdown cards (UsageTable's two portals; the frame is overlays.css 2.3): the billed amounts and - the total are figures, so ink at 600 instead of green / blue (decision 2), like the billed cost in the row; the - overridden-TTL line keeps its status colour. */ + the total are figures, so ink at 600 instead of green / blue (decision 2); the overridden-TTL line keeps its status + colour. */ html.flat-theme div[class~="fixed"][class~="pointer-events-none"] > .theme-dark-surface [class~="justify-between"] > span:last-child:is([class~="text-green-400"], [class~="text-blue-400"]) { color: rgb(var(--ui-ink)); font-weight: 600; } -/* the detail triggers (token and cost breakdowns): the reference's cursor-help info glyph, faint -> ink, no tinted disc */ +/* the detail triggers (token and cost breakdowns): the reference's cursor-help info glyph without the tinted disc; the + glyph keeps its template colours (official: grey, the info hue on hover) */ html.flat-theme .table-wrapper[data-table="usage"] td:is([data-column="tokens"], [data-column="cost"]) div[class~="group"][class~="relative"] > div[class~="rounded-full"] { background-color: transparent; - color: rgb(var(--ui-faint)); - transition: color var(--ui-duration) var(--ui-ease); } html.flat-theme .table-wrapper[data-table="usage"] td:is([data-column="tokens"], [data-column="cost"]) div[class~="group"][class~="relative"] > div[class~="rounded-full"] > svg { width: 14px; height: 14px; - color: inherit; } html.flat-theme .table-wrapper[data-table="usage"] td:is([data-column="tokens"], [data-column="cost"]) div[class~="group"][class~="relative"]:hover > div[class~="rounded-full"] { background-color: transparent; - color: rgb(var(--ui-ink)); } /* ================================================================== 10 · 使用记录 (管理): 用户排行 */ /* UserTokenRanking: its bar (6 / 16, the explanation and the count 12px muted, the Top-N select at the toolbar's 30px) - and its table in the reference's table language (data.css section 3 gives the mono header): cells 10 / 12 (16 at the - edges), 42px rows, hover fill; figures Geist Mono ink-soft, the total and the cost ink (decision 2: the cost is not - green); the top three ranks a neutral disc, the sorted column's label a step darker with its arrow (not the accent). */ + and its table in the reference's table language (data.css section 3 gives the header): cells 10 / 12 (16 at the + edges), 42px rows, hover fill; the cells' text is official's (the template's classes: figures 14px tabular grey, the + total ink 500, the cost green); the top three ranks a 22px neutral disc (subtle fill, ink; upstream's gold and bronze + would fold into one warning colour), its number the template's text-xs font-semibold; the sorted column's label a + step darker with its arrow (not the accent). */ html.flat-theme [data-ui="admin-usage"] div[data-ui~="token-ranking"]:not(uipad[data-uipad]):not(uipad):not(uipad) > div[class~="border-b"] { padding: 6px 16px; border-bottom-color: rgb(var(--ui-line)); @@ -650,83 +505,44 @@ html.flat-theme [data-ui="admin-usage"] div[data-ui~="token-ranking"] > div[clas padding-right: 16px; } -html.flat-theme [data-ui="admin-usage"] div[data-ui~="token-ranking"] > div[class~="overflow-x-auto"] > table > tbody > tr > td[class~="text-right"] { - font-family: var(--ui-font-mono); - font-size: 12.5px; - font-variant-numeric: tabular-nums; - color: rgb(var(--ui-ink-soft)); -} - -html.flat-theme [data-ui="admin-usage"] div[data-ui~="token-ranking"] > div[class~="overflow-x-auto"] > table > tbody > tr > td[class~="text-right"]:is([class~="font-medium"], [class~="text-green-600"]) { - font-weight: 500; - color: rgb(var(--ui-ink)); -} - -html.flat-theme [data-ui="admin-usage"] div[data-ui~="token-ranking"] > div[class~="overflow-x-auto"] > table > tbody > tr > td[class~="font-medium"]:not([class~="text-right"]) { - font-size: 13px; - color: rgb(var(--ui-ink)); -} - -html.flat-theme [data-ui="admin-usage"] div[data-ui~="token-ranking"] > div[class~="overflow-x-auto"] > table > tbody > tr > td[class~="font-medium"]:not([class~="text-right"]) > span { - font-family: var(--ui-font-mono); - font-size: 12px; - color: rgb(var(--ui-muted)); -} - -html.flat-theme [data-ui="admin-usage"] div[data-ui~="token-ranking"] > div[class~="overflow-x-auto"] > table > tbody > tr > td:first-child > span { - font-family: var(--ui-font-mono); - font-size: 12px; - font-variant-numeric: tabular-nums; - color: rgb(var(--ui-muted)); -} - html.flat-theme [data-ui="admin-usage"] div[data-ui~="token-ranking"] > div[class~="overflow-x-auto"] > table > tbody > tr > td:first-child > span[class~="rounded-full"] { width: 22px; height: 22px; background-color: rgb(var(--ui-subtle)); - font-size: 11px; - font-weight: 600; color: rgb(var(--ui-ink)); } /* ================================================================== 11 · 使用记录 (管理): 错误请求 */ /* OpsErrorLogTable in the records card: its error type and group chips carry data.css' category chip (hooks added by the - ops page group); the status code and the severity keep their status colours. The detail button is an icon button, - muted -> ink (not the accent). */ -html.flat-theme [data-ui="admin-usage"] .table-wrapper:not([data-table]) td[data-column="actions"] > button { - color: rgb(var(--ui-muted)); -} - + ops page group); the status code and the severity keep their status colours. The detail button is an icon button + in its template colours (official: the grey glyph, the accent on hover) over the subtle hover fill. */ html.flat-theme [data-ui="admin-usage"] .table-wrapper:not([data-table]) td[data-column="actions"] > button:hover { background-color: rgb(var(--ui-subtle)); - color: rgb(var(--ui-ink)); } /* ================================================================== 12 · 仪表盘 (用户) */ /* 按平台拆分: the reference's detail stat blocks (REF §8.13: radius 8, 1px structural line, surface, 12 / 14 inset); the - section title the mono label with its count in Geist Mono at the right (REF §10.19); a platform's name 13/600, its - total spend ink 600 (a value, not a state: decision 2); the rows' figures Geist Mono; the quota title the mono label; - the bars keep their status colours (data.css section 8). */ + section title the label with its count (12px) at the right (REF §10.19); a platform's name 14/600, its total spend + ink 600 (a value, not a state: decision 2); the rows' figures the template's mono; the quota title the label; the + bars keep their status colours (data.css section 8). */ html.flat-theme [data-ui="user-dashboard"] .card[class~="p-4"][data-ui~="platform-breakdown"] { padding: 14px; } html.flat-theme [data-ui="user-dashboard"] .card[class~="p-4"][data-ui~="platform-breakdown"] > div:first-child > h3 { - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); line-height: var(--ui-label-leading); - font-weight: 400; + font-weight: var(--ui-label-weight); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; color: rgb(var(--ui-label)); } html.flat-theme [data-ui="user-dashboard"] .card[class~="p-4"][data-ui~="platform-breakdown"] > div:first-child > span { - font-family: var(--ui-font-mono); - font-size: 11px; - line-height: 16.5px; + font-size: 12px; + line-height: 16px; color: rgb(var(--ui-muted)); } @@ -747,13 +563,14 @@ html.flat-theme [data-ui="user-dashboard"] [data-testid="platform-card"][class~= } html.flat-theme [data-ui="user-dashboard"] [data-testid="platform-card"] > div:first-child > span:first-child { - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; } -/* the total spend: ink at 600 (the reference's spend figures) instead of upstream's purple; the mono figure stays */ +/* the total spend: ink at 600 (the reference's spend figures) instead of upstream's purple; the template's mono figure + stays */ html.flat-theme [data-ui="user-dashboard"] [data-testid="platform-card"] span[class~="text-purple-600"] { - font-size: 13px; + font-size: 14px; font-weight: 600; font-variant-numeric: tabular-nums; color: rgb(var(--ui-ink)); @@ -765,10 +582,11 @@ html.flat-theme [data-ui="user-dashboard"] [data-testid="platform-card"] > div:n } html.flat-theme [data-ui="user-dashboard"] [data-testid="platform-card"] p[class~="uppercase"][class~="text-[10px]"] { - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); line-height: var(--ui-label-leading); + font-weight: var(--ui-label-weight); letter-spacing: var(--ui-label-tracking); + text-transform: none; color: rgb(var(--ui-label)); } @@ -776,10 +594,10 @@ html.flat-theme [data-ui="user-dashboard"] [data-testid="platform-card"] > div[c border-color: rgb(var(--ui-line)); } -/* 最近使用 and 快捷操作 (the bottom row): card heads as the reference's card head (REF §5: 13/600 ink, 12 / 16, a 1px line +/* 最近使用 and 快捷操作 (the bottom row): card heads as the reference's card head (REF §5: 14/600 ink, 12 / 16, a 1px line under it). The recent requests are the reference's request rows (REF §8.7 /logs request table: rows divided by 1px - lines, hover fill, the model 13/500 ink, the time 12px muted, figures Geist Mono): no grey tiles; the model glyph sits - in a 28px neutral square instead of the accent tint; the billed cost ink (decision 2), the standard price muted; the + lines, hover fill, the model 14/500 ink, the time 12px muted, figures tabular): no grey tiles; the model glyph sits in + a 28px neutral square instead of the accent tint; the billed cost ink (decision 2), the standard price muted; the 「查看全部」 link keeps the accent (a link). */ html.flat-theme [data-ui="user-dashboard"] [class~="lg:grid-cols-3"] .card > div[class~="border-b"]:first-child { padding: 12px 16px; @@ -787,8 +605,8 @@ html.flat-theme [data-ui="user-dashboard"] [class~="lg:grid-cols-3"] .card > div } html.flat-theme [data-ui="user-dashboard"] [class~="lg:grid-cols-3"] .card > div[class~="border-b"]:first-child > h2 { - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; font-weight: 600; } @@ -838,8 +656,8 @@ html.flat-theme [data-ui="user-dashboard"] [class~="lg:col-span-2"] .card > div[ } html.flat-theme [data-ui="user-dashboard"] [class~="lg:col-span-2"] .card > div[class~="p-6"] > div[class~="space-y-3"] > div[class~="rounded-xl"] > div:first-child p:first-child { - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; font-weight: 500; color: rgb(var(--ui-ink)); } @@ -851,13 +669,12 @@ html.flat-theme [data-ui="user-dashboard"] [class~="lg:col-span-2"] .card > div[ } html.flat-theme [data-ui="user-dashboard"] [class~="lg:col-span-2"] .card > div[class~="p-6"] > div[class~="space-y-3"] > div[class~="rounded-xl"] > div:last-child > p { - font-family: var(--ui-font-mono); font-variant-numeric: tabular-nums; } html.flat-theme [data-ui="user-dashboard"] [class~="lg:col-span-2"] .card > div[class~="p-6"] > div[class~="space-y-3"] > div[class~="rounded-xl"] > div:last-child > p:first-child { - font-size: 12.5px; - line-height: 18.75px; + font-size: 14px; + line-height: 20px; font-weight: 500; } @@ -870,16 +687,16 @@ html.flat-theme [data-ui="user-dashboard"] [class~="lg:col-span-2"] .card > div[ } html.flat-theme [data-ui="user-dashboard"] [class~="lg:col-span-2"] .card > div[class~="p-6"] > div[class~="space-y-3"] > div[class~="rounded-xl"] > div:last-child > p:last-child { - font-size: 11.5px; - line-height: 17px; + font-size: 12px; + line-height: 16px; color: rgb(var(--ui-muted)); } html.flat-theme [data-ui="user-dashboard"] [class~="lg:col-span-2"] .card > div[class~="p-6"] > div[class~="space-y-3"] > a { padding: 10px 16px; - font-size: 12.5px; - line-height: 18.75px; - font-weight: 600; + font-size: 14px; + line-height: 20px; + font-weight: 500; color: var(--ui-link); } @@ -900,7 +717,8 @@ html.flat-theme [data-ui="user-dashboard"] [class~="lg:col-span-1"] .card > div[ /* ================================================================== 13 · 使用记录 (用户): tabs, table card, pagination */ /* 用量明细 / 错误请求: upstream's .tab buttons over a 1px line read as the reference's underline tabs (CodeTabs, REF §8.15: - the current tab ink with a 2px accent rule; the others muted, ink on hover), as on the admin records card. */ + the current tab ink with a 2px accent rule; the others in the .tab grey, upstream's text-gray-600 (#4b5563 / + #a4abb6), ink on hover), as on the admin records card (controls.css 7.3). */ html.flat-theme [data-ui="user-usage"] > div[class~="border-b"]:not(uipad[data-uipad]) { gap: 0; border-color: rgb(var(--ui-line)); @@ -912,7 +730,7 @@ html.flat-theme [data-ui="user-usage"] > div[class~="border-b"]:not(uipad[data-u border-bottom: 2px solid transparent; border-radius: 0; background-color: transparent; - color: rgb(var(--ui-muted)); + color: rgb(var(--theme-text-gray-600)); } html.flat-theme [data-ui="user-usage"] > div[class~="border-b"]:not(uipad[data-uipad]) > button.tab:hover { diff --git a/frontend/src/styles/console/pages/ops-channels.css b/frontend/src/styles/console/pages/ops-channels.css index 50caa44eeeff..e3816722855d 100644 --- a/frontend/src/styles/console/pages/ops-channels.css +++ b/frontend/src/styles/console/pages/ops-channels.css @@ -77,7 +77,7 @@ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where( } /* The page title is the header row's (AppHeader: 「运维监控 · 运维监控与排障」, shown from 1024px); there the page's own h1 - stays for screen readers only. Fullscreen has no header row: its title is the page title (REF §5: 20/600). */ + stays for screen readers only. Fullscreen has no header row: its title is the page title (shell.css: 18/28/600). */ @media (min-width: 1024px) { html.flat-theme div[data-ui~="ops-dashboard"]:not([class~="p-4"]) > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div:first-child > div > h1 { position: absolute; @@ -92,18 +92,18 @@ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where( } } -/* below 1024px the header row shows no title: the page's own title is the card title (13/600) */ +/* below 1024px the header row shows no title: the page's own title is the card title (14/600) */ html.flat-theme div[data-ui~="ops-dashboard"]:not([class~="p-4"]) > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div:first-child > div > h1 { gap: 0; - font-size: 13px; + font-size: 14px; font-weight: 600; - line-height: 19.5px; + line-height: 20px; color: rgb(var(--ui-ink)); } html.flat-theme div[data-ui~="ops-dashboard"][class~="p-4"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div:first-child > div > h1 { gap: 0; - font-size: 20px; + font-size: 18px; font-weight: 600; line-height: 28px; color: rgb(var(--ui-ink)); @@ -113,13 +113,12 @@ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where( display: none; } -/* the status line: the reference's meta (Mono 11, faint), the ready dot a 6px status dot */ +/* the status line: upstream's text-xs meta (12/16, muted), the ready dot a 6px status dot */ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div:first-child > div > div[class~="mt-1"] { margin-top: 0; gap: 8px; - font-family: var(--ui-font-mono); - font-size: 11px; - line-height: 16.5px; + font-size: 12px; + line-height: 16px; font-variant-numeric: tabular-nums; color: rgb(var(--ui-muted)); } @@ -160,8 +159,8 @@ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where( background-color: rgb(var(--ui-line)); } -/* refresh, alert rules, settings, fullscreen: the reference's default button (surface, control outline, ink 13/600, - radius 6), icon-only ones square; no hue */ +/* refresh, alert rules, settings, fullscreen: the console's default button (surface, control outline, ink in the + .btn type 14/20/500, radius 6), icon-only ones square; no hue */ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div:first-child > div:last-child > button { gap: 6px; height: var(--ui-control-h); @@ -171,9 +170,9 @@ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where( border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-surface)); color: rgb(var(--ui-ink)); - font-size: 13px; - font-weight: 600; - line-height: 19.5px; + font-size: 14px; + font-weight: 500; + line-height: 20px; transition: color var(--ui-duration) var(--ui-ease), border-color var(--ui-duration) var(--ui-ease), background-color var(--ui-duration) var(--ui-ease); } @@ -245,7 +244,7 @@ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where( } /* health: the gauge keeps its status colour (it is the status); the score is a figure (600, tabular), its caption the - mono label; hovering opens the diagnosis */ + label (12/16/500, sentence case); hovering opens the diagnosis */ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:first-child > div > div[class~="group"] { padding-top: 4px; padding-bottom: 4px; @@ -267,7 +266,6 @@ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where( font-size: 24px; line-height: 30px; font-variant-numeric: tabular-nums; - letter-spacing: -0.01em; } html.flat-theme div[data-ui~="ops-dashboard"][class~="p-4"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:first-child > div > div[class~="group"] > div[class~="relative"] > div[class~="absolute"] > span:first-child { @@ -277,22 +275,14 @@ html.flat-theme div[data-ui~="ops-dashboard"][class~="p-4"] > div[class~="rounde html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:first-child > div > div[class~="group"] > div[class~="relative"] > div[class~="absolute"] > span:last-child, html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:first-child > div > div[class~="group"] > div[class~="mt-4"] > div:first-child { - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); - font-weight: 400; + font-weight: var(--ui-label-weight); line-height: var(--ui-label-leading); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; color: rgb(var(--ui-label)); } -/* hovered (the diagnosis opens) the panel takes the grouped fill; the two captions go muted there in light (the label - grey is 4.35:1 on that fill, the muted grey 4.8:1; dark keeps the label grey, 5.7:1) */ -html.flat-theme:not(.dark) div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:first-child > div > div[class~="group"]:hover > div[class~="relative"] > div[class~="absolute"] > span:last-child, -html.flat-theme:not(.dark) div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:first-child > div > div[class~="group"]:hover > div[class~="mt-4"] > div:first-child { - color: rgb(var(--ui-muted)); -} - html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:first-child > div > div[class~="group"] > div[class~="mt-4"] { margin-top: 10px; } @@ -301,7 +291,7 @@ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where( margin-top: 2px; font-size: 12px; font-weight: 600; - line-height: 18px; + line-height: 16px; } /* the diagnosis popover: the reference's detail popover (radius 6, control outline, surface, the popover shadow, @@ -318,9 +308,9 @@ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where( margin-bottom: 10px; padding-bottom: 8px; border-color: rgb(var(--ui-line)); - font-size: 13px; + font-size: 14px; font-weight: 600; - line-height: 19.5px; + line-height: 20px; color: rgb(var(--ui-ink)); } @@ -334,24 +324,22 @@ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where( } html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:first-child > div > div[class~="group"] > div[class~="absolute"] > div > div > div > div:last-child > div:nth-child(2) { - font-size: 11px; color: rgb(var(--ui-muted)); } html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:first-child > div > div[class~="group"] > div[class~="absolute"] > div > div > div > div:last-child > div[class~="text-blue-600"] { - font-size: 11px; color: rgb(var(--ui-ink-soft)); } html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:first-child > div > div[class~="group"] > div[class~="absolute"] > div > div[class~="mt-3"] { border-color: rgb(var(--ui-line)); - font-size: 11px; color: rgb(var(--ui-faint)); } -/* realtime: the title a mono label (the decorative ping and the animated pulse line go: they carry no data), the window +/* realtime: the title a label (the decorative ping and the animated pulse line go: they carry no data), the window choice the reference's time segmented control (REF §8.5, crop segmented-time: controls.css 7.1's frame and segment - metrics, on one line), the current QPS / TPS figures 20/600, peak and average 13/600, units Mono 10 */ + metrics, on one line, at 12px: upstream draws these at 9-10px), the current QPS / TPS figures 20/600, peak and + average 14/600, units 12/500 */ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:first-child > div > div:last-child > div:first-child { margin-bottom: 10px; } @@ -367,12 +355,11 @@ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where( html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:first-child > div > div:last-child > div:first-child h3, html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:first-child > div > div:last-child > div:last-child div[class~="uppercase"][class~="font-bold"] { - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); - font-weight: 400; + font-weight: var(--ui-label-weight); line-height: var(--ui-label-leading); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; color: rgb(var(--ui-label)); } @@ -389,11 +376,11 @@ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where( padding: var(--ctl-seg-pad-y) var(--ctl-seg-pad-x); border-radius: var(--ui-radius-md); background-color: transparent; - color: rgb(var(--ui-muted)); + color: rgb(var(--theme-text-gray-600)); font-family: var(--ui-font); - font-size: 0.78125rem; + font-size: 0.75rem; font-weight: 500; - line-height: 1.171875rem; + line-height: 1rem; white-space: nowrap; transition: color var(--ui-duration-slide) var(--ui-ease-out), background-color var(--ui-duration-slide) var(--ui-ease-out); } @@ -405,7 +392,7 @@ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where( html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:first-child > div > div:last-child > div:first-child > div:last-child > button[class~="bg-blue-500"] { background-color: rgb(var(--ui-thumb)); color: rgb(var(--ui-ink)); - font-weight: 600; + font-weight: 500; box-shadow: var(--ui-thumb-shadow); } @@ -422,23 +409,22 @@ html.flat-theme div[data-ui~="ops-dashboard"]:not([class~="p-4"]) > div[class~=" html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:first-child > div > div:last-child > div:last-child > div:first-child span[class~="font-bold"], html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:first-child > div > div:last-child > div:last-child > div[class~="grid"] span[class~="text-xs"] { - font-family: var(--ui-font-mono); - font-size: 10px; + font-size: 12px; font-weight: 500; - letter-spacing: 0.025em; color: rgb(var(--ui-muted)); } html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:first-child > div > div:last-child > div:last-child > div[class~="grid"] > div > div:last-child { margin-top: 2px; - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; } /* 2.3 the six metric tiles: the reference's StatTile (REF §8.13: surface, 1px structural line, radius 6, 8 / 12 in; the - mono label, the figure 15/600 tabular, the detail rows 12px with Mono figures). A value upstream colours green only - because it is inside its threshold is ink (the reference colours a figure only when something is wrong); warning - and critical keep their colour, and a critical tile is the reference's alert tile (danger line and soft fill). */ + label 12/16/500, the figure in upstream's size (text-3xl, 30/36) at the KPI weight 700, tabular, the detail rows + upstream's text-xs). A value upstream colours green only because it is inside its threshold is ink (the reference + colours a figure only when something is wrong); warning and critical keep their colour, and a critical tile is the + reference's alert tile (danger line and soft fill). */ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:last-child > div[class~="rounded-2xl"] { display: flex; flex-direction: column; @@ -454,12 +440,11 @@ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where( html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:last-child > div[class~="rounded-2xl"] > div:first-child > div > span:first-child, html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="border-t"] > div > div > div:first-child div[class~="uppercase"] { - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); - font-weight: 400; + font-weight: var(--ui-label-weight); line-height: var(--ui-label-leading); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; color: rgb(var(--ops-tile-label, var(--ui-label))); } @@ -481,9 +466,8 @@ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where( background-color: rgb(var(--ui-dot-bad)); } -/* 「明细」: a link (the accent text, underline on hover), not a blue label */ +/* 「明细」: a link (the accent text, underline on hover) at upstream's text-[10px], not a blue label */ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) button[class~="text-[10px]"][class~="text-blue-500"] { - font-size: 11.5px; font-weight: 500; line-height: 16px; color: var(--ui-link); @@ -491,21 +475,16 @@ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where( text-decoration-thickness: 1px; } -/* the tile's figure: 15/600 tabular; fullscreen 22 (the wall display) */ +/* the tile's figure: upstream's text-3xl (30/36) at the KPI weight 700, tabular, also fullscreen (as upstream) */ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:last-child > div[class~="rounded-2xl"] :is(div[class~="text-3xl"]) { margin-top: 2px; - font-size: 15px; - font-weight: 600; - line-height: 22.5px; + font-size: 30px; + font-weight: 700; + line-height: 36px; font-variant-numeric: tabular-nums; letter-spacing: 0; } -html.flat-theme div[data-ui~="ops-dashboard"][class~="p-4"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:last-child > div[class~="rounded-2xl"] :is(div[class~="text-3xl"]) { - font-size: 22px; - line-height: 30px; -} - html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:last-child > div[class~="rounded-2xl"] > div[class~="items-baseline"] { margin-top: 2px; gap: 4px; @@ -516,17 +495,17 @@ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where( } html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:last-child > div[class~="rounded-2xl"] > div[class~="items-baseline"] > span { - font-family: var(--ui-font-mono); - font-size: 10.5px; + font-size: 12px; font-weight: 400; color: rgb(var(--ui-muted)); } -/* the detail rows: label 12px muted, figure Mono 12 ink-soft (REF §8.9 breakdown rows), 2px apart */ +/* the detail rows: upstream's text-xs (12/16), label muted, figure 12/500 ink-soft tabular (REF §8.9 breakdown rows), + 2px apart */ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:last-child > div[class~="rounded-2xl"] > div:is([class~="space-y-2"], [class~="space-y-1"], [class~="grid"], [class~="mt-3"]):not([class~="h-2"]) { margin-top: 6px; font-size: 12px; - line-height: 18px; + line-height: 16px; } html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:last-child > div[class~="rounded-2xl"] > div[class~="space-y-2"] > :not([hidden]) ~ :not([hidden]), @@ -543,7 +522,6 @@ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where( } html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:last-child > div[class~="rounded-2xl"] > div > div > span[class~="font-bold"] { - font-family: var(--ui-font-mono); font-size: 12px; font-weight: 500; font-variant-numeric: tabular-nums; @@ -554,8 +532,6 @@ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where( } html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="grid"] > div:last-child > div[class~="rounded-2xl"] > div > div > span[class~="text-gray-400"] { - font-family: var(--ui-font-mono); - font-size: 10.5px; color: rgb(var(--ui-muted)); } @@ -608,7 +584,8 @@ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where( } /* 2.4 the system tiles (CPU, memory, database, Redis, goroutines, jobs): a KPI row of their own, 10px apart, each the - reference's StatTile (label, figure 15/600, the detail 11px muted with Mono figures) */ + reference's StatTile (label, the figure in upstream's text-lg 18/28 at the KPI weight 700, the detail upstream's + text-[10px] muted with its font-mono figures) */ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="border-t"] { margin-top: 0; padding-top: 0; @@ -631,21 +608,15 @@ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where( html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="border-t"] > div[class~="grid"] > div > div[class~="text-lg"] { margin-top: 2px; - font-size: 15px; - font-weight: 600; - line-height: 22.5px; - font-variant-numeric: tabular-nums; -} - -html.flat-theme div[data-ui~="ops-dashboard"][class~="p-4"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="border-t"] > div[class~="grid"] > div > div[class~="text-lg"] { - font-size: 20px; + font-size: 18px; + font-weight: 700; line-height: 28px; + font-variant-numeric: tabular-nums; } html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"]:where([data-ui~="ops-header"]) > div[class~="border-t"] > div[class~="grid"] > div > div[class~="text-[10px]"] { margin-top: 2px; - font-size: 11px; - line-height: 16.5px; + line-height: 16px; color: rgb(var(--ui-muted)); } @@ -677,43 +648,40 @@ html.flat-theme [data-ui~="ops-dashboard"] > div > section[class~="card"] > div[ margin-bottom: 12px; } -/* titles: a chart card's title is the mono label (REF §8.14 "SPEND OVER TIME"); a list or table card's title is the - reference's card title 13/600 ink with its description 12px muted ("Request history"). The coloured glyph before a - title is decoration and goes; the help icon stays. */ +/* titles: every card's title, charts included, is upstream's card title (text-sm, here 14/20/600 ink, as the + dashboard's chart cards) with its description upstream's text-xs muted ("Request history"). The coloured glyph + before a title is decoration and goes; the help icon stays. */ html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])) h3 > svg:first-child { display: none; } html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"]))[data-ui~="ops-chart"]:not(uipad[data-uipad]):not(uipad) h3 { gap: 0; - font-family: var(--ui-font-mono); - font-size: var(--ui-label-size); - font-weight: 400; - line-height: var(--ui-label-leading); - letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; - color: rgb(var(--ui-label)); + font-size: 14px; + font-weight: 600; + line-height: 20px; + color: rgb(var(--ui-ink)); } html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])):not([data-ui~="ops-chart"]):not(uipad[data-uipad]):not(uipad) h3, html.flat-theme [data-ui~="ops-dashboard"] section:is([class~="rounded-2xl"], [class~="card"]) h3 { gap: 0; - font-size: 13px; + font-size: 14px; font-weight: 600; - line-height: 19.5px; + line-height: 20px; color: rgb(var(--ui-ink)); } html.flat-theme div[data-ui~="ops-dashboard"] :is(div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])), section:is([class~="rounded-2xl"], [class~="card"])) > div:first-child > div > p[class~="text-xs"] { margin-top: 2px; font-size: 12px; - line-height: 18px; + line-height: 16px; color: rgb(var(--ui-muted)); } /* the head's small buttons (明细 / 重置 / 下载 / 请求错误 / 上游错误, the concurrency view toggle and refresh, the alert - refresh): the reference's default button at the small size (26px, radius 6, control outline, surface, 12/600, - ink-soft, ink on hover) */ + refresh): the console's default button at the small size (26px, radius 6, control outline, surface, the .btn-sm + type 12/16/500, ink-soft, ink on hover) */ html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])) > div:first-child button:is([class~="border-gray-200"], [class~="bg-gray-100"], [class~="bg-blue-100"]) { gap: 6px; min-width: 26px; @@ -724,8 +692,8 @@ html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~=" background-color: rgb(var(--ui-surface)); color: rgb(var(--ui-ink-soft)); font-size: 12px; - font-weight: 600; - line-height: 18px; + font-weight: 500; + line-height: 16px; transition: color var(--ui-duration) var(--ui-ease), border-color var(--ui-duration) var(--ui-ease), background-color var(--ui-duration) var(--ui-ease); } @@ -758,11 +726,12 @@ html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~=" height: 13px; } -/* chart legends in HTML (the throughput head): the reference's legend (REF §8.14: 8px squares with 2px corners, 11px) */ +/* chart legends in HTML (the throughput head): the reference's legend (REF §8.14: 8px squares with 2px corners) in + upstream's text-xs */ html.flat-theme [data-ui~="ops-dashboard"] [data-testid="throughput-chart-toolbar"] { gap: 8px; - font-size: 11px; - line-height: 16.5px; + font-size: 12px; + line-height: 16px; color: rgb(var(--ui-muted)); } @@ -773,7 +742,7 @@ html.flat-theme [data-ui~="ops-dashboard"] [data-testid="throughput-chart-toolba } /* the throughput drilldown chips (a platform or a group and its request count): the reference's provider chip shape - (radius 4, the control outline, Geist Mono 10.5 uppercase ink-soft, the count faint) */ + (radius 4, the control outline, upstream's text-[11px] at 500 ink-soft, the count faint) */ html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])) > div[class~="mb-3"][class~="flex-wrap"] { gap: 6px; margin-bottom: 10px; @@ -786,11 +755,8 @@ html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~=" border-radius: var(--ui-radius-sm); background-color: rgb(var(--ui-surface)); color: rgb(var(--ui-ink-soft)); - font-family: var(--ui-font-mono); - font-size: 10.5px; font-weight: 500; line-height: 16px; - letter-spacing: 0.025em; transition: color var(--ui-duration) var(--ui-ease), border-color var(--ui-duration) var(--ui-ease); } @@ -805,7 +771,7 @@ html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~=" } /* the error distribution's summary under the doughnut: 「最高: 上游」 ink-soft with the slice's own colour kept on the - value, the legend as the reference's (8px squares, 11px muted) */ + value, the legend as the reference's (8px squares) in upstream's text-xs muted */ html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])) div[class~="mt-4"][class~="flex-col"][class~="items-center"] { margin-top: 10px; gap: 6px; @@ -823,8 +789,8 @@ html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~=" html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])) div[class~="mt-4"][class~="flex-col"][class~="items-center"] > div[class~="flex-wrap"] > div { gap: 6px; - font-size: 11px; - line-height: 16.5px; + font-size: 12px; + line-height: 16px; } html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])) div[class~="mt-4"][class~="flex-col"][class~="items-center"] > div[class~="flex-wrap"] > div > span:first-child { @@ -846,12 +812,12 @@ html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~=" } /* 3.2 Concurrency / queue: the list sits flush in the card like the reference's tables (no inner frame; a group band - for the dimension and its count, then rows divided by 1px lines); each row: name 12/600, the used / total figure - Mono ink-soft, the load share Mono (normal ink-soft; the load colours stay on the bar and from 50 % up), the 6px bar - on the grouped fill, the account and queue chips as the reference's small status chips. The band's label and count - are muted, not the label grey: on the band's fill the label grey is 4.50:1, the muted grey 5.0:1. The list is the - only bordered flex column directly in an ops card (OpsConcurrencyCard's data area, which always starts with the band; - the disabled hint is a dashed box without flex-col). */ + for the dimension and its count, then rows divided by 1px lines); each row: the name at its upstream text-[11px] + in 600 ink, the used / total figure (upstream's font-mono, at its text-[10px] / text-[11px]) ink-soft, the load + share tabular (normal ink-soft; the load colours stay on the bar and from 50 % up), the 6px bar on the grouped + fill, the account and queue chips as the reference's small status chips. The band's label and count are muted. + The list is the only bordered flex column directly in an ops card (OpsConcurrencyCard's data area, which always + starts with the band; the disabled hint is a dashed box without flex-col). */ html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])) > div[class~="rounded-xl"][class~="border"][class~="flex-col"]:not(uipad) { margin: 0 -14px -14px; border-width: 1px 0 0; @@ -866,17 +832,15 @@ html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~=" } html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])) > div[class~="rounded-xl"][class~="border"] > div[class~="bg-gray-50"]:first-child > span:first-child { - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); - font-weight: 400; + font-weight: var(--ui-label-weight); line-height: var(--ui-label-leading); letter-spacing: var(--ui-label-tracking); + text-transform: none; color: rgb(var(--ui-muted)); } html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])) > div[class~="rounded-xl"][class~="border"] > div[class~="bg-gray-50"]:first-child > span:last-child { - font-family: var(--ui-font-mono); - font-size: 11px; font-variant-numeric: tabular-nums; color: rgb(var(--ui-muted)); } @@ -901,22 +865,18 @@ html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~=" } html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])) > div[class~="rounded-xl"][class~="border"] > div[class~="custom-scrollbar"] :is(span, div)[class~="text-[11px]"][class~="font-bold"]:not([class~="font-mono"]) { - font-size: 12px; font-weight: 600; line-height: 18px; color: rgb(var(--ui-ink)); } html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])) > div[class~="rounded-xl"][class~="border"] > div[class~="custom-scrollbar"] span[class~="font-mono"] { - font-size: 11.5px; font-weight: 500; font-variant-numeric: tabular-nums; color: rgb(var(--ui-ink-soft)); } html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])) > div[class~="rounded-xl"][class~="border"] > div[class~="custom-scrollbar"] > div > div:first-child > div:last-child > span[class~="font-bold"]:not([class~="font-mono"]) { - font-family: var(--ui-font-mono); - font-size: 11.5px; font-weight: 500; font-variant-numeric: tabular-nums; } @@ -938,11 +898,11 @@ html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~=" background-color: rgb(var(--ui-subtle)); } -/* the account line under the bar: 11px, the available count ink 600 (a figure, not a status), the share muted */ +/* the account line under the bar: upstream's text-[10px], the available count ink 600 (a figure, not a status), the + share muted */ html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])) > div[class~="rounded-xl"][class~="border"] > div[class~="custom-scrollbar"] > div > div[class~="flex-wrap"] { gap: 4px 10px; - font-size: 11px; - line-height: 16.5px; + line-height: 16px; } html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])) > div[class~="rounded-xl"][class~="border"] > div[class~="custom-scrollbar"] > div > div[class~="flex-wrap"] > div > svg { @@ -952,7 +912,6 @@ html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~=" } html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])) > div[class~="rounded-xl"][class~="border"] > div[class~="custom-scrollbar"] > div > div[class~="flex-wrap"] > div > span { - font-family: var(--ui-font-mono); font-variant-numeric: tabular-nums; color: rgb(var(--ui-muted)); } @@ -963,15 +922,13 @@ html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~=" } /* the small status chips in the rows (rate limited = warning, errors = danger, queued = the reference's backlog tone, - available / cooling down): the reference's Chip (capsule, Geist Mono, soft status fill) */ + available / cooling down): the reference's Chip (capsule, soft status fill) in the badge type (12/16/500) */ html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])) > div[class~="rounded-xl"][class~="border"] > div[class~="custom-scrollbar"] span:is([class~="bg-amber-100"], [class~="bg-red-100"], [class~="bg-purple-100"], [class~="bg-green-100"], [class~="bg-gray-100"]) { padding: 0 6px; border-radius: 9999px; - font-family: var(--ui-font-mono); - font-size: var(--ui-badge-size, 10px); + font-size: var(--ui-badge-size, 12px); font-weight: 500; line-height: 16px; - letter-spacing: 0.01em; } html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])) > div[class~="rounded-xl"][class~="border"] > div[class~="custom-scrollbar"] span[class~="bg-amber-100"] { @@ -1003,12 +960,12 @@ html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~=" html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])) > div[class~="border-dashed"] { border-color: rgb(var(--ui-line-strong)); border-radius: var(--ui-radius-lg); - font-size: 13px; + font-size: 14px; color: rgb(var(--ui-muted)); } html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])) > div[class~="rounded-xl"][class~="border"] > div[class~="flex-1"][class~="items-center"] { - font-size: 13px; + font-size: 14px; color: rgb(var(--ui-muted)); } @@ -1022,21 +979,20 @@ html.flat-theme div[data-ui~="ops-dashboard"] :is(div[class~="rounded-3xl"][clas } /* 3.3 Hand-built tables on the page and in its dialogs (alert events and their history, system logs, OpenAI token - stats, request details, alert rules): the reference's table (REF §8.7): the mono label header on the surface with a - 1px line under it, in DataTable's head colour --ui-table-head (thead.bg-gray-50 is data.css' rule; the white-headed - ones are drawn the same here), cells 10 / 12 - (16 at the edges), 13px text, rows divided by 1px lines, hover = --ui-hover where the row opens something. DataTables - (the error lists) keep data.css. */ + stats, request details, alert rules): the reference's table (REF §8.7): upstream's usage-table header (12/16/500, + uppercase, 0.05em) on the surface with a 1px line under it, in DataTable's head colour --ui-table-head + (thead.bg-gray-50 is data.css' rule; the white-headed ones are drawn the same here), cells 10 / 12 (16 at the + edges), 14/20 text, rows divided by 1px lines, hover = --ui-hover where the row opens something. DataTables (the + error lists) keep data.css. */ html.flat-theme :is([data-ui~="ops-dashboard"], [data-ui~="ops-dialog"], [data-ui~="risk-page"], [data-ui~="risk-settings"], [data-ui~="prompt-audit-page"]) table:not(.table-wrapper > table) > thead > tr > th { padding: 10px 12px; background-color: rgb(var(--ui-surface)); box-shadow: inset 0 -1px 0 rgb(var(--ui-line)); color: rgb(var(--ui-table-head)); - font-family: var(--ui-font-mono); - font-size: var(--ui-label-size); + font-size: 12px; font-weight: 500; - line-height: 19.5px; - letter-spacing: var(--ui-label-tracking); + line-height: 16px; + letter-spacing: 0.05em; text-transform: uppercase; white-space: nowrap; } @@ -1061,11 +1017,16 @@ html.flat-theme :is([data-ui~="ops-dashboard"], [data-ui~="ops-dialog"], [data-u html.flat-theme :is([data-ui~="ops-dashboard"], [data-ui~="ops-dialog"], [data-ui~="risk-page"], [data-ui~="risk-settings"], [data-ui~="prompt-audit-page"]) table:not(.table-wrapper > table) > tbody > tr > td { padding: 10px 12px; - font-size: 13px; - line-height: 19.5px; vertical-align: middle; } +/* the body text is upstream's td default (text-sm, 14/20) on the table, so a cell's own size class (text-xs, + text-[11px], text-sm) still applies */ +html.flat-theme :is([data-ui~="ops-dashboard"], [data-ui~="ops-dialog"], [data-ui~="risk-page"], [data-ui~="risk-settings"], [data-ui~="prompt-audit-page"]) table:not(.table-wrapper > table) { + font-size: 14px; + line-height: 20px; +} + html.flat-theme :is([data-ui~="ops-dashboard"], [data-ui~="ops-dialog"], [data-ui~="risk-page"], [data-ui~="risk-settings"], [data-ui~="prompt-audit-page"]) table:not(.table-wrapper > table) > :is(thead, tbody) > tr > :first-child { padding-left: 16px; } @@ -1078,35 +1039,13 @@ html.flat-theme :is([data-ui~="ops-dashboard"], [data-ui~="ops-dialog"], [data-u background-color: rgb(var(--ui-hover)); } -/* the cells' own text sizes (text-xs, 11px) read as the reference's: 13px for text, Mono 12 for times, ids and figures, - 12px muted for the second line */ -html.flat-theme :is([data-ui~="ops-dashboard"], [data-ui~="ops-dialog"], [data-ui~="risk-page"], [data-ui~="risk-settings"], [data-ui~="prompt-audit-page"]) table:not(.table-wrapper > table) > tbody > tr > td[class~="text-xs"] { - font-size: 13px; -} - +/* the cells' own sizes, weights and colours are upstream's (text-xs cells 12px, the text-[10px] / text-[11px] second + lines, font-bold figures, template font-mono identifiers); times and identifiers keep tabular figures */ html.flat-theme :is([data-ui~="ops-dashboard"], [data-ui~="ops-dialog"], [data-ui~="risk-page"], [data-ui~="risk-settings"], [data-ui~="prompt-audit-page"]) table:not(.table-wrapper > table) > tbody > tr > td[class~="whitespace-nowrap"][class~="text-xs"]:first-child, html.flat-theme :is([data-ui~="ops-dashboard"], [data-ui~="ops-dialog"], [data-ui~="risk-page"], [data-ui~="risk-settings"], [data-ui~="prompt-audit-page"]) table:not(.table-wrapper > table) > tbody > tr > td span[class~="font-mono"] { - font-family: var(--ui-font-mono); - font-size: 12px; font-variant-numeric: tabular-nums; } -html.flat-theme :is([data-ui~="ops-dashboard"], [data-ui~="ops-dialog"], [data-ui~="risk-page"], [data-ui~="risk-settings"], [data-ui~="prompt-audit-page"]) table:not(.table-wrapper > table) > tbody > tr > td :is(div, span)[class~="text-[11px]"] { - font-size: 12px; - line-height: 18px; -} - -html.flat-theme :is([data-ui~="ops-dashboard"], [data-ui~="ops-dialog"], [data-ui~="risk-page"], [data-ui~="risk-settings"], [data-ui~="prompt-audit-page"]) table:not(.table-wrapper > table) > tbody > tr > td :is(div)[class~="text-[10px]"] { - font-family: var(--ui-font-mono); - font-size: 11px; - line-height: 16.5px; - color: rgb(var(--ui-faint)); -} - -html.flat-theme :is([data-ui~="ops-dashboard"], [data-ui~="ops-dialog"], [data-ui~="risk-page"], [data-ui~="risk-settings"], [data-ui~="prompt-audit-page"]) table:not(.table-wrapper > table) > tbody > tr > td :is(div, span)[class~="font-bold"]:not([class~="rounded-full"]):not([class~="rounded-md"]) { - font-weight: 600; -} - /* 3.3a The message row of an empty hand-built table (prompt audit events, risk control records: one colspan cell) is the shared empty state (data.css §9, the reference's "Nothing matches"): a panel 16px inside the table, the dashed control outline, radius 8, 120px, the message 14/500 ink-soft in its middle. A table wider than its scroll box (the @@ -1145,20 +1084,19 @@ html.flat-theme :is([data-ui~="prompt-audit-page"], [data-ui~="risk-page"]) div[ } /* 3.4 Status chips of these pages (alert severity P0-P3 and state, log level, request kind, sink counters): the - reference's Chip (REF §8.8: a capsule, Geist Mono uppercase, a soft status fill, no ring); tones: critical / error / - failed = danger, warning / warn / P1 = warning, info / P2 = info, resolved / success = success, the rest neutral. */ + reference's Chip (REF §8.8: a capsule, a soft status fill, no ring) in upstream's .badge type (12/16/500, sentence + case); tones: critical / error / failed = danger, warning / warn / P1 = warning, info / P2 = info, resolved / + success = success, the rest neutral. */ html.flat-theme :is([data-ui~="ops-dashboard"], [data-ui~="ops-dialog"], [data-ui~="risk-page"], [data-ui~="risk-settings"], [data-ui~="prompt-audit-page"]) span[class~="rounded-full"]:is([class~="text-[10px]"], [class~="text-xs"], [class~="font-semibold"]):is([class*="bg-red-"], [class*="bg-amber-"], [class*="bg-orange-"], [class*="bg-yellow-"], [class*="bg-blue-"], [class*="bg-green-"], [class*="bg-gray-"], [class*="bg-slate-"]):not([class~="h-1.5"], [class~="h-2"]) { display: inline-flex; align-items: center; padding: 1px 7px; border-radius: 9999px; box-shadow: none; - font-family: var(--ui-font-mono); - font-size: var(--ui-badge-size, 10px); + font-size: var(--ui-badge-size, 12px); font-weight: 500; line-height: var(--ui-badge-leading, 16px); - letter-spacing: 0.025em; - text-transform: uppercase; + text-transform: none; } html.flat-theme :is([data-ui~="ops-dashboard"], [data-ui~="ops-dialog"], [data-ui~="risk-page"], [data-ui~="risk-settings"], [data-ui~="prompt-audit-page"]) span[class~="rounded-full"]:is([class*="bg-red-"]):not([class~="h-1.5"], [class~="h-2"]) { @@ -1201,8 +1139,8 @@ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"][class~ html.flat-theme div[data-ui~="ops-dashboard"] > div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])) > div[class~="mb-4"]:first-child > div:last-child > button { height: var(--ui-control-h); padding: 0 12px; - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; } html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])) > div[class~="overflow-hidden"][class~="rounded-xl"][class~="border"] { @@ -1212,24 +1150,6 @@ html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~=" border-radius: 0 0 var(--ui-radius-lg) var(--ui-radius-lg); } -/* the title cell keeps its two lines: the title 13/600 ink, the rule text 12px muted */ -html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])) table > tbody > tr > td[class~="min-w-[260px]"] > div:first-child { - font-size: 13px; - line-height: 19.5px; - color: rgb(var(--ui-ink)); -} - -html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])) table > tbody > tr > td[class~="min-w-[260px]"] > div:last-child:not(:first-child) { - color: rgb(var(--ui-muted)); -} - -/* the e-mail column: the state glyph keeps success / muted, the word 12px ink-soft */ -html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])) table > tbody > tr > td[class~="text-right"] span[class~="text-[11px]"] { - font-size: 12px; - font-weight: 500; - color: rgb(var(--ui-ink-soft)); -} - /* the list's end marker and loading line */ html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~="ring-1"]:where(:not([data-ui~="ops-header"])) div[class~="max-h-[600px]"] > div:last-child:is([class~="py-3"]) { border-top: 1px solid rgb(var(--ui-line)); @@ -1237,9 +1157,10 @@ html.flat-theme div[data-ui~="ops-dashboard"] div[class~="rounded-3xl"][class~=" color: rgb(var(--ui-muted)); } -/* 3.6 System logs: the same card; the sink counters are the reference's small chips (radius 4, Mono 11); the runtime - configuration is a grouped panel (1px line, radius 8, the grouped fill); every field label (they wrap their - controls) is the mono label above its 34px field; the log table sits flush like the events. */ +/* 3.6 System logs: the same card; the sink counters are the reference's small chips (radius 4, upstream's text-xs at + 500, tabular); the runtime configuration is a grouped panel (1px line, radius 8, the grouped fill); every field + label (they wrap their controls) is the label (12/16/500) above its 34px field; the log table sits flush like the + events. */ html.flat-theme [data-ui~="ops-dashboard"] > section[class~="rounded-2xl"] > div:first-child > div:last-child { gap: 6px; } @@ -1247,10 +1168,9 @@ html.flat-theme [data-ui~="ops-dashboard"] > section[class~="rounded-2xl"] > div html.flat-theme [data-ui~="ops-dashboard"] > section[class~="rounded-2xl"] > div:first-child > div:last-child > span[class~="rounded-md"] { padding: 1px 7px; border-radius: var(--ui-radius-sm); - font-family: var(--ui-font-mono); - font-size: 11px; + font-size: 12px; font-weight: 500; - line-height: 18px; + line-height: 16px; font-variant-numeric: tabular-nums; } @@ -1285,19 +1205,18 @@ html.flat-theme [data-ui~="ops-dashboard"] > section[class~="rounded-2xl"] > div } html.flat-theme [data-ui~="ops-dashboard"] > section[class~="rounded-2xl"] > div[class~="rounded-xl"][class~="bg-gray-50"] > div:first-child > div:first-child { - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); - font-weight: 400; + font-weight: var(--ui-label-weight); line-height: var(--ui-label-leading); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; color: rgb(var(--ui-label)); } html.flat-theme [data-ui~="ops-dashboard"] > section[class~="rounded-2xl"] > div[class~="rounded-xl"][class~="bg-gray-50"] > p { margin-top: 8px; font-size: 12px; - line-height: 18px; + line-height: 16px; color: rgb(var(--ui-muted)); } @@ -1305,15 +1224,15 @@ html.flat-theme [data-ui~="ops-dashboard"] > section[class~="rounded-2xl"] > div color: rgb(var(--ui-danger)); } -/* the wrapping labels: the text is the mono label; the control and the hint under it keep their own type */ +/* the wrapping labels: the text is the label at upstream's own weight (its text-xs label is 400, which the wrapped + control inherits); the control and the hint under it keep their own type */ html.flat-theme [data-ui~="ops-dashboard"] > section[class~="rounded-2xl"] div[class~="grid"] > label[class~="text-xs"]:not([class~="inline-flex"]) { display: block; - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); font-weight: 400; line-height: var(--ui-label-leading); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; color: rgb(var(--ui-label)); } @@ -1326,15 +1245,15 @@ html.flat-theme [data-ui~="ops-dashboard"] > section[class~="rounded-2xl"] div[c html.flat-theme [data-ui~="ops-dashboard"] > section[class~="rounded-2xl"] div[class~="grid"] > label[class~="text-xs"]:not([class~="inline-flex"]) > span[class~="block"] { font-size: 12px; - line-height: 18px; + line-height: 16px; color: rgb(var(--ui-muted)); } -/* the switches of the runtime configuration: 12px ink-soft beside the box */ +/* the switches of the runtime configuration: upstream's text-xs (12/16) ink-soft beside the box */ html.flat-theme [data-ui~="ops-dashboard"] > section[class~="rounded-2xl"] label[class~="inline-flex"][class~="text-xs"] { gap: 6px; font-size: 12px; - line-height: 18px; + line-height: 16px; color: rgb(var(--ui-ink-soft)); } @@ -1348,8 +1267,8 @@ html.flat-theme [data-ui~="ops-dashboard"] > section[class~="rounded-2xl"] > div margin-bottom: 14px; } -/* the log table: flush in the card; time and host in Mono 12, the detail in Mono 12 ink-soft (log lines are machine - text, as the reference's labels and ids are Mono); rows top-aligned (a detail may wrap) */ +/* the log table: flush in the card; the cells keep upstream's text-xs classes (time, host and detail tabular); rows + top-aligned (a detail may wrap) */ html.flat-theme [data-ui~="ops-dashboard"] > section[class~="rounded-2xl"] > div[class~="overflow-hidden"][class~="rounded-xl"][class~="border"] { margin: 0 -14px -14px; border-width: 1px 0 0; @@ -1368,15 +1287,10 @@ html.flat-theme [data-ui~="ops-dashboard"] > section[class~="rounded-2xl"] table } html.flat-theme [data-ui~="ops-dashboard"] > section[class~="rounded-2xl"] table > tbody > tr > td:is(:nth-child(1), :nth-child(2), :nth-child(4)) { - font-family: var(--ui-font-mono); - font-size: 12px; - line-height: 19.5px; font-variant-numeric: tabular-nums; - color: rgb(var(--ui-ink-soft)); } html.flat-theme [data-ui~="ops-dashboard"] > section[class~="rounded-2xl"] table > tbody > tr > td:nth-child(1) { - color: rgb(var(--ui-muted)); white-space: nowrap; } @@ -1385,11 +1299,11 @@ html.flat-theme [data-ui~="ops-dashboard"] > section[class~="rounded-2xl"] table } html.flat-theme [data-ui~="ops-dashboard"] > section[class~="rounded-2xl"] > div[class~="overflow-hidden"] > div[class~="text-center"] { - font-size: 13px; + font-size: 14px; color: rgb(var(--ui-muted)); } -/* 3.7 OpenAI token stats (shown when the dashboard setting is on): the same card and flush table; figures Mono */ +/* 3.7 OpenAI token stats (shown when the dashboard setting is on): the same card and flush table; figures tabular */ html.flat-theme [data-ui~="ops-dashboard"] section[class~="card"] > div[class~="space-y-3"] > div[class~="overflow-hidden"][class~="rounded-xl"] { margin: 0 -14px; border-width: 1px 0; @@ -1402,26 +1316,16 @@ html.flat-theme [data-ui~="ops-dashboard"] section[class~="card"] > div[class~=" } html.flat-theme [data-ui~="ops-dashboard"] section[class~="card"] > div[class~="space-y-3"] > div[class~="overflow-hidden"] table > tbody > tr > td:not(:first-child) { - font-family: var(--ui-font-mono); - font-size: 12.5px; font-variant-numeric: tabular-nums; - color: rgb(var(--ui-ink-soft)); -} - -html.flat-theme [data-ui~="ops-dashboard"] section[class~="card"] > div[class~="space-y-3"] > div[class~="overflow-hidden"] table > tbody > tr > td:first-child { - font-weight: 500; - color: rgb(var(--ui-ink)); } html.flat-theme [data-ui~="ops-dashboard"] section[class~="card"] > div[class~="space-y-3"] > div:last-child[class~="text-xs"] { - font-family: var(--ui-font-mono); - font-size: 11px; + font-size: 12px; color: rgb(var(--ui-label)); } html.flat-theme [data-ui~="ops-dashboard"] section[class~="card"] > div:first-child > div:last-child > span[class~="text-xs"] { - font-family: var(--ui-font-mono); - font-size: 11px; + font-size: 12px; color: rgb(var(--ui-label)); } @@ -1443,10 +1347,11 @@ html.flat-theme [data-ui~="ops-dashboard"] section[class~="card"] > div:first-ch data-ui="ops-dialog ops-jobs" OpsDashboardHeader.vue background jobs data-ui="ops-dialog ops-custom-range" OpsDashboardHeader.vue custom time range The dialog frame, header and footer are overlays.css'; the rules here draw the bodies in the reference's language - (REF §8.11 Modal body; panels = the grouped fill with a 1px line; labels = the mono label; figures Mono). */ + (REF §8.11 Modal body; panels = the grouped fill with a 1px line; labels = the label, 12/16/500 sentence case; + figures tabular). */ /* 4.1 Panels that upstream fills with gray-50 (rounded-xl / rounded-2xl): the reference's grouped panel (1px structural - line, radius 8, the grouped fill at 60 %, 12 / 14 in); section titles 13/600 ink, small uppercase captions the mono + line, radius 8, the grouped fill at 60 %, 12 / 14 in); section titles 14/600 ink, small uppercase captions the label. */ html.flat-theme [data-ui~="ops-dialog"] :is(div, details)[class~="bg-gray-50"]:is([class~="rounded-xl"], [class~="rounded-2xl"]):not([class~="p-4"][class~="dark:bg-dark-900"]:only-child) { border: 1px solid rgb(var(--ui-line)); @@ -1461,26 +1366,26 @@ html.flat-theme [data-ui~="ops-dialog"] div[class~="bg-gray-50"][class~="rounded html.flat-theme [data-ui~="ops-dialog"] :is(h3, h4)[class~="uppercase"][class~="tracking-wider"], html.flat-theme [data-ui~="ops-dialog"] div[class~="text-xs"][class~="font-bold"][class~="uppercase"][class~="tracking-wider"] { - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); - font-weight: 400; + font-weight: var(--ui-label-weight); line-height: var(--ui-label-leading); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; color: rgb(var(--ui-label)); } html.flat-theme [data-ui~="ops-dialog"] h4[class~="text-sm"][class~="font-semibold"], html.flat-theme [data-ui~="ops-dialog"] summary[class~="text-sm"][class~="font-semibold"] { - font-size: 13px; + font-size: 14px; font-weight: 600; - line-height: 19.5px; + line-height: 20px; color: rgb(var(--ui-ink)); } /* the key-value tiles (error detail summary, alert event times): the reference's detail stat block (REF §8.13: surface, - 1px line, radius 6, 8 / 12 in, the mono label, the value 13px ink, Mono where upstream sets it); their two grids carry - data-ui="ops-kv-grid" (OpsErrorDetailModal.vue:16, OpsAlertEventsCard.vue:608) */ + 1px line, radius 6, 8 / 12 in, the label, the value upstream's text-sm font-medium ink, in its font-mono where + upstream sets it); their two grids carry data-ui="ops-kv-grid" (OpsErrorDetailModal.vue:16, + OpsAlertEventsCard.vue:608) */ html.flat-theme [data-ui~="ops-dialog"] div[class~="grid"] > div[class~="rounded-xl"][class~="bg-gray-50"][class~="p-4"] { padding: 8px 12px 10px; border: 1px solid rgb(var(--ui-line)); @@ -1494,35 +1399,30 @@ html.flat-theme [data-ui~="ops-dialog"] div[class~="grid"][data-ui~="ops-kv-grid html.flat-theme [data-ui~="ops-dialog"] div[class~="grid"] > div[class~="rounded-xl"][class~="bg-gray-50"][class~="p-4"] > div:nth-child(2) { margin-top: 4px; - font-size: 13px; + font-size: 14px; font-weight: 500; - line-height: 19.5px; + line-height: 20px; color: rgb(var(--ui-ink)); } -html.flat-theme [data-ui~="ops-dialog"] div[class~="grid"] > div[class~="rounded-xl"][class~="bg-gray-50"][class~="p-4"] > div:nth-child(2)[class~="font-mono"], -html.flat-theme [data-ui~="ops-dialog"] div[class~="grid"] > div[class~="rounded-xl"][class~="bg-gray-50"][class~="p-4"] > div:nth-child(2) span[class~="font-mono"] { - font-size: 12.5px; -} - html.flat-theme [data-ui~="ops-dialog"] div[class~="grid"] > div[class~="rounded-xl"][class~="bg-gray-50"][class~="p-4"] > div:nth-child(2) span[class~="text-primary-600"] { color: rgb(var(--ui-ink)); } -/* status codes in the tiles: the reference's chip (capsule, Geist Mono, soft status fill, no ring / shadow) */ +/* status codes in the tiles: the reference's chip (capsule, soft status fill, no ring / shadow) in upstream's + text-xs */ html.flat-theme [data-ui~="ops-dialog"] span[class~="rounded-lg"][class~="font-black"][class~="ring-1"] { padding: 1px 8px; border-radius: 9999px; box-shadow: none; - font-family: var(--ui-font-mono); - font-size: 11px; + font-size: 12px; font-weight: 600; - line-height: 18px; + line-height: 16px; } /* 4.2 the error detail: 16px between blocks; the root cause is the reference's warning notice (REF §8.16: the warning - line and soft fill, radius 6); the payloads are code blocks (REF §8.15: radius 8, 1px line, surface, Mono 12 at - 1.6); the correlated upstream errors are cards on the panel */ + line and soft fill, radius 6); the payloads are code blocks (REF §8.15: radius 8, 1px line, surface, the mono stack + 12 at 1.6); the correlated upstream errors are cards on the panel */ html.flat-theme [data-ui~="ops-error-detail"] { padding: 0; } @@ -1538,7 +1438,7 @@ html.flat-theme [data-ui~="ops-error-detail"] > div[class~="bg-amber-50"] { background-color: rgb(var(--ui-warning-soft)); } -/* (both hooks: over 4.1's mono-label rule for uppercase headings, which upstream's h3 here also carries) */ +/* (both hooks: over 4.1's label rule for uppercase headings, which upstream's h3 here also carries) */ html.flat-theme [data-ui~="ops-dialog"][data-ui~="ops-error-detail"] > div[class~="bg-amber-50"] > h3 { font-family: var(--ui-font); font-size: 12px; @@ -1551,7 +1451,7 @@ html.flat-theme [data-ui~="ops-dialog"][data-ui~="ops-error-detail"] > div[class html.flat-theme [data-ui~="ops-error-detail"] > div[class~="bg-amber-50"] > div { margin-top: 6px; - font-size: 13px; + font-size: 14px; font-weight: 400; line-height: 1.625; color: rgb(var(--ui-ink)); @@ -1575,7 +1475,6 @@ html.flat-theme [data-ui~="ops-error-detail"] > div > div[class~="mt-4"][class~= } html.flat-theme [data-ui~="ops-error-detail"] div[class~="text-xs"][class~="font-black"] { - font-family: var(--ui-font-mono); font-weight: 600; color: rgb(var(--ui-ink)); } @@ -1592,7 +1491,6 @@ html.flat-theme [data-ui~="ops-error-detail"] span[class~="rounded-md"][class~=" html.flat-theme [data-ui~="ops-error-detail"] button[class~="text-primary-700"] { border-radius: var(--ui-radius-sm); color: var(--ui-link); - font-size: 11.5px; font-weight: 500; } @@ -1600,30 +1498,29 @@ html.flat-theme [data-ui~="ops-error-detail"] button[class~="text-primary-700"]: background-color: rgb(var(--ui-subtle)); } -/* the Codex continuation section: its title a card title (13/600), its records the grouped panel */ +/* the Codex continuation section: its title a card title (14/600), its records the grouped panel */ html.flat-theme [data-ui~="ops-error-detail"] > section > h2 { - font-size: 13px; + font-size: 14px; font-weight: 600; - line-height: 19.5px; + line-height: 20px; color: rgb(var(--ui-ink)); } html.flat-theme [data-ui~="ops-error-detail"] > section > div[data-test="codex-diagnostic-record"] { border-radius: var(--ui-radius-lg); background-color: color-mix(in srgb, rgb(var(--ui-subtle)) 60%, rgb(var(--ui-surface))); - font-size: 13px; + font-size: 14px; } html.flat-theme [data-ui~="ops-error-detail"] > section > div[data-test="codex-diagnostic-record"] th { - font-family: var(--ui-font-mono); - font-size: var(--ui-label-size); - letter-spacing: var(--ui-label-tracking); + font-size: 12px; + letter-spacing: 0.05em; text-transform: uppercase; color: rgb(var(--ui-table-head)); } /* 4.3 the error lists: filters in one 30px row under a 1px line (the search the reference's search field: control - outline, surface, 13px, the 13px glyph 10px in; the Selects and reset 30px); the count a mono meta line */ + outline, surface, 14px, the 13px glyph 10px in; the Selects and reset 30px); the count a 12px meta line */ html.flat-theme [data-ui~="ops-error-list"] > div:first-child { margin-bottom: 12px; padding-bottom: 12px; @@ -1642,9 +1539,9 @@ html.flat-theme [data-ui~="ops-error-list"] > div:first-child input[type="text"] background-color: rgb(var(--ui-surface)); box-shadow: none; color: rgb(var(--ui-ink)); - font-size: 13px; + font-size: 14px; font-weight: 400; - line-height: 19.5px; + line-height: 20px; } html.flat-theme [data-ui~="ops-error-list"] > div:first-child input[type="text"]:focus { @@ -1670,7 +1567,7 @@ html.flat-theme [data-ui~="ops-error-list"] > div:first-child div[class~="absolu html.flat-theme [data-ui~="ops-error-list"] > div:first-child .select-trigger.select-trigger { padding-top: 0.265625rem; padding-bottom: 0.265625rem; - font-size: 13px; + font-size: 14px; } html.flat-theme [data-ui~="ops-error-list"] > div:first-child button[class~="bg-gray-100"] { @@ -1680,8 +1577,8 @@ html.flat-theme [data-ui~="ops-error-list"] > div:first-child button[class~="bg- border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-surface)); color: rgb(var(--ui-ink)); - font-size: 13px; - font-weight: 600; + font-size: 14px; + font-weight: 500; } html.flat-theme [data-ui~="ops-error-list"] > div:first-child button[class~="bg-gray-100"]:hover { @@ -1691,23 +1588,15 @@ html.flat-theme [data-ui~="ops-error-list"] > div:first-child button[class~="bg- html.flat-theme [data-ui~="ops-error-list"] > div:last-child > div[class~="mb-2"] { margin-bottom: 8px; - font-family: var(--ui-font-mono); - font-size: 11px; - line-height: 16.5px; - font-variant-numeric: tabular-nums; - color: rgb(var(--ui-label)); -} - -/* the list's cells: time Mono, a secondary value (data.css 12, as in the other lists); the type and group chips are - categories (data.css' category-chip hook, added to OpsErrorLogTable.vue: the reference's neutral chip); the detail - action a 28px icon button */ -html.flat-theme [data-ui~="ops-error-list"] .table-wrapper td[data-column="created_at"] > span { - font-family: var(--ui-font-mono); font-size: 12px; + line-height: 16px; font-variant-numeric: tabular-nums; - color: rgb(var(--ui-data-secondary)); + color: rgb(var(--ui-label)); } +/* the list's cells keep upstream's classes (the time text-sm text-gray-600); the type and group chips are categories + (data.css' category-chip hook, added to OpsErrorLogTable.vue: the template's tag colours); the detail action a 28px + icon button */ html.flat-theme [data-ui~="ops-error-list"] .table-wrapper td[data-column="actions"] > button { width: 28px; height: 28px; @@ -1728,16 +1617,16 @@ html.flat-theme [data-ui~="ops-error-list"] .table-wrapper td[data-column="actio height: 15px; } -/* 4.4 request details: the range a mono meta line; the table in a 1px frame (radius 8); the id copy the reference's - snippet copy button (Mono 10.5, control outline, radius 6); 「查看错误」 the destructive small button (danger text, - danger line at 30 %, surface; the soft fill on hover); the empty state the dashed frame */ +/* 4.4 request details: the range a 12px meta line; the table in a 1px frame (radius 8), its cells in upstream's + classes (figures tabular); the id copy the reference's snippet copy button (upstream's text-[10px] at 500, control + outline, radius 6); 「查看错误」 the destructive small button (danger text, danger line at 30 %, surface; the soft fill + on hover); the empty state the dashed frame */ html.flat-theme [data-ui~="ops-request-details"] > div:first-child { margin-bottom: 12px; } html.flat-theme [data-ui~="ops-request-details"] > div:first-child > div[class~="text-xs"] { - font-family: var(--ui-font-mono); - font-size: 11px; + font-size: 12px; font-variant-numeric: tabular-nums; color: rgb(var(--ui-label)); } @@ -1747,36 +1636,16 @@ html.flat-theme [data-ui~="ops-request-details"] div[class~="rounded-xl"][class~ border-radius: var(--ui-radius-lg); } -html.flat-theme [data-ui~="ops-request-details"] table > tbody > tr > td:nth-child(3) { - font-family: var(--ui-font-mono); - font-size: 11.5px; - letter-spacing: 0.025em; - color: rgb(var(--ui-ink-soft)); -} - -html.flat-theme [data-ui~="ops-request-details"] table > tbody > tr > td:nth-child(4) { - color: rgb(var(--ui-ink)); -} - html.flat-theme [data-ui~="ops-request-details"] table > tbody > tr > td:is(:nth-child(5), :nth-child(6)) { - font-family: var(--ui-font-mono); - font-size: 12.5px; font-variant-numeric: tabular-nums; } -html.flat-theme [data-ui~="ops-request-details"] :is(td, div) span[class~="font-mono"][class~="text-[11px]"] { - font-size: 12px; - color: rgb(var(--ui-ink-soft)); -} - html.flat-theme [data-ui~="ops-dialog"] button[class~="bg-gray-100"][class~="text-[10px]"][class~="font-bold"] { padding: 2px 7px; border: 1px solid rgb(var(--ui-line-strong)); border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-surface)); color: rgb(var(--ui-ink-soft)); - font-family: var(--ui-font-mono); - font-size: 10.5px; font-weight: 500; line-height: 16px; transition: color var(--ui-duration) var(--ui-ease), border-color var(--ui-duration) var(--ui-ease); @@ -1796,7 +1665,7 @@ html.flat-theme [data-ui~="ops-dialog"] button[class~="bg-red-50"][class~="text- background-color: rgb(var(--ui-surface)); color: rgb(var(--ui-danger)); font-size: 12px; - font-weight: 600; + font-weight: 500; transition: background-color var(--ui-duration) var(--ui-ease), border-color var(--ui-duration) var(--ui-ease); } @@ -1814,7 +1683,7 @@ html.flat-theme [data-ui~="ops-dialog"] svg[class~="animate-spin"][class~="text- color: rgb(var(--ui-ink-soft)); } -/* 4.5 settings: sections are the grouped panel; the toggle rows' labels 13px (upstream's bare labels were 16px); +/* 4.5 settings: sections are the grouped panel; the toggle rows' labels 14px (upstream's bare labels were 16px); recipients the reference's neutral chip (an address is a value, not a category or a state); the validation notice the warning notice */ html.flat-theme [data-ui~="ops-settings"][class~="space-y-6"] > :not([hidden]) ~ :not([hidden]) { @@ -1822,29 +1691,28 @@ html.flat-theme [data-ui~="ops-settings"][class~="space-y-6"] > :not([hidden]) ~ } html.flat-theme [data-ui~="ops-settings"] label[class~="font-medium"]:not([class~="input-label"]):not([class~="text-sm"]) { - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; color: rgb(var(--ui-ink)); } html.flat-theme [data-ui~="ops-settings"] label[class~="text-sm"][class~="font-medium"]:not([class~="block"]) { - font-size: 13px; + font-size: 14px; color: rgb(var(--ui-ink)); } html.flat-theme [data-ui~="ops-settings"] h5[class~="text-xs"][class~="font-semibold"] { - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); - font-weight: 400; + font-weight: var(--ui-label-weight); line-height: var(--ui-label-leading); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; color: rgb(var(--ui-label)); } html.flat-theme [data-ui~="ops-settings"] p[class~="text-xs"] { font-size: 12px; - line-height: 18px; + line-height: 16px; color: rgb(var(--ui-muted)); } @@ -1879,7 +1747,7 @@ html.flat-theme [data-ui~="ops-dialog"] div[class~="border-amber-200"][class~="b border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-warning-soft)); color: rgb(var(--ui-ink)); - font-size: 12.5px; + font-size: 12px; } html.flat-theme [data-ui~="ops-dialog"] div[class~="border-amber-200"][class~="bg-amber-50"] > div:first-child { @@ -1909,16 +1777,16 @@ html.flat-theme [data-ui~="ops-alert-rules"] > div:first-child { } html.flat-theme [data-ui~="ops-alert-rules"] > div:first-child h3 { - font-size: 13px; + font-size: 14px; font-weight: 600; - line-height: 19.5px; + line-height: 20px; color: rgb(var(--ui-ink)); } html.flat-theme [data-ui~="ops-alert-rules"] > div:first-child p { margin-top: 2px; font-size: 12px; - line-height: 18px; + line-height: 16px; color: rgb(var(--ui-muted)); } @@ -1934,7 +1802,7 @@ html.flat-theme [data-ui~="ops-alert-rules"] > div:first-child > div:last-child background-color: rgb(var(--ui-surface)); color: rgb(var(--ui-ink)); font-size: 12px; - font-weight: 600; + font-weight: 500; } html.flat-theme [data-ui~="ops-alert-rules"] > div:first-child > div:last-child > button[class~="bg-gray-100"]:hover:not(:disabled) { @@ -1947,28 +1815,6 @@ html.flat-theme [data-ui~="ops-alert-rules"] > div[class~="rounded-xl"][class~=" border-radius: var(--ui-radius-lg); } -html.flat-theme [data-ui~="ops-alert-rules"] table > tbody > tr > td:first-child > div:first-child { - font-size: 13px; - font-weight: 600; - color: rgb(var(--ui-ink)); -} - -html.flat-theme [data-ui~="ops-alert-rules"] table > tbody > tr > td:first-child > div[class~="text-[11px]"] { - color: rgb(var(--ui-muted)); -} - -html.flat-theme [data-ui~="ops-alert-rules"] table > tbody > tr > td:nth-child(2) { - font-family: var(--ui-font-mono); - font-size: 12px; - color: rgb(var(--ui-ink-soft)); -} - -html.flat-theme [data-ui~="ops-alert-rules"] table > tbody > tr > td:nth-child(3) { - font-family: var(--ui-font-mono); - font-size: 12px; - font-weight: 600; -} - html.flat-theme [data-ui~="ops-alert-rules"] table > tbody > tr > td:last-child { white-space: nowrap; } @@ -1979,7 +1825,7 @@ html.flat-theme [data-ui~="ops-alert-rule-editor"] > div[class~="bg-red-50"] { border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-danger-soft)); color: rgb(var(--ui-danger)); - font-size: 12.5px; + font-size: 12px; } html.flat-theme [data-ui~="ops-alert-rule-editor"] div[class~="rounded-xl"][class~="bg-gray-50"][class~="px-4"] { @@ -1990,7 +1836,7 @@ html.flat-theme [data-ui~="ops-alert-rule-editor"] div[class~="rounded-xl"][clas } html.flat-theme [data-ui~="ops-alert-rule-editor"] div[class~="rounded-xl"][class~="bg-gray-50"][class~="px-4"] > span { - font-size: 13px; + font-size: 14px; font-weight: 500; color: rgb(var(--ui-ink)); } @@ -2002,7 +1848,7 @@ html.flat-theme [data-ui~="ops-alert-event"][class~="space-y-5"] > :not([hidden] } html.flat-theme [data-ui~="ops-alert-event"] > div:first-child > div > div:first-child > div[class~="text-sm"] { - font-size: 13px; + font-size: 14px; font-weight: 600; color: rgb(var(--ui-ink)); } @@ -2015,9 +1861,8 @@ html.flat-theme [data-ui~="ops-alert-event"] div[class~="ring-1"][class~="bg-whi } html.flat-theme [data-ui~="ops-alert-event"] div[class~="ring-1"][class~="bg-white"][class~="rounded-lg"] > span { - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); - font-weight: 400; + font-weight: var(--ui-label-weight); letter-spacing: var(--ui-label-tracking); color: rgb(var(--ui-label)); } @@ -2030,7 +1875,7 @@ html.flat-theme [data-ui~="ops-alert-event"] a[class~="ring-1"][class~="rounded- box-shadow: none; color: rgb(var(--ui-ink)); font-size: 12px; - font-weight: 600; + font-weight: 500; } html.flat-theme [data-ui~="ops-alert-event"] a[class~="ring-1"][class~="rounded-md"]:hover { @@ -2045,7 +1890,7 @@ html.flat-theme [data-ui~="ops-alert-event"] > div[class~="rounded-xl"][class~=" } html.flat-theme [data-ui~="ops-alert-event"] > div[class~="rounded-xl"][class~="border"][class~="bg-white"] > div:first-child > div > div:first-child { - font-size: 13px; + font-size: 14px; font-weight: 600; } @@ -2055,11 +1900,11 @@ html.flat-theme [data-ui~="ops-alert-event"] div[class~="rounded-lg"][class~="bo } html.flat-theme [data-ui~="ops-alert-event"] div[class~="rounded-lg"][class~="border"][class~="overflow-hidden"] td { - font-family: var(--ui-font-mono); font-size: 12px; } -/* 4.8 background jobs: each job a card on the dialog (1px line, radius 8, 12 / 14 in); the name 13/600, times Mono */ +/* 4.8 background jobs: each job a card on the dialog (1px line, radius 8, 12 / 14 in); the name upstream's text-sm + 600, the times its text-xs meta with tabular figures (its font-mono spans keep the mono stack) */ html.flat-theme [data-ui~="ops-jobs"][class~="space-y-3"] > :not([hidden]) ~ :not([hidden]) { margin-top: 8px; } @@ -2071,15 +1916,13 @@ html.flat-theme [data-ui~="ops-jobs"] > div { } html.flat-theme [data-ui~="ops-jobs"] > div > div:first-child > div:first-child { - font-family: var(--ui-font-mono); - font-size: 12.5px; + font-size: 14px; font-weight: 600; color: rgb(var(--ui-ink)); } html.flat-theme [data-ui~="ops-jobs"] > div > div:first-child > div:last-child { - font-family: var(--ui-font-mono); - font-size: 11px; + font-size: 12px; font-variant-numeric: tabular-nums; color: rgb(var(--ui-muted)); } @@ -2108,7 +1951,7 @@ html.flat-theme [data-ui~="ops-custom-range"] input[type="datetime-local"] { padding: 6px 10px; border-radius: var(--ui-radius-md); box-shadow: none; - font-size: 13px; + font-size: 14px; } html.flat-theme [data-ui~="ops-custom-range"] input[type="datetime-local"]:focus { @@ -2128,8 +1971,8 @@ html.flat-theme [data-ui~="ops-custom-range"] > div:last-child > button { border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-surface)); color: rgb(var(--ui-ink)); - font-size: 13px; - font-weight: 600; + font-size: 14px; + font-weight: 500; } html.flat-theme [data-ui~="ops-custom-range"] > div:last-child > button[class~="bg-blue-500"] { @@ -2156,8 +1999,9 @@ html.flat-theme [data-ui~="ops-custom-range"] > div:last-child > button:focus-vi (data-ui="platform-chip"); ChannelMonitorView.vue — the provider chip (platform-chip), the check mode (category-chip). */ /* 5.1 Row actions drawn as a glyph over a caption (flex-col, 67px rows): the reference's ghost text button on one line - (REF §8.3 ghost: 28px, 8px in, 12.5/600 ink-soft, the grouped fill and ink on hover), so rows keep the reference's 42px; - a destructive action takes the danger colour on hover. The caption stays visible (touch and mouse alike). */ + (REF §8.3 ghost: 28px, 8px in, ink-soft, the grouped fill and ink on hover; the caption keeps upstream's text-xs), so + rows keep the reference's 42px; a destructive action takes the danger colour on hover. The caption stays visible + (touch and mouse alike). */ html.flat-theme :is([data-ui~="channels-page"], [data-ui~="channel-monitor-page"]) .table-wrapper td[data-column="actions"] > div { gap: 2px; } @@ -2173,9 +2017,6 @@ html.flat-theme :is([data-ui~="channels-page"], [data-ui~="channel-monitor-page" } html.flat-theme :is([data-ui~="channels-page"], [data-ui~="channel-monitor-page"]) .table-wrapper td[data-column="actions"] button[class~="flex-col"] > span { - font-size: 12.5px; - font-weight: 600; - line-height: 18px; white-space: nowrap; } @@ -2198,16 +2039,9 @@ html.flat-theme :is([data-ui~="channels-page"], [data-ui~="channel-monitor-page" opacity: 0.55; } -/* 5.2 渠道定价: the channel name 13/600 ink, the description muted; the dialog's platform tabs keep the platform logo's - colour (identity, decision 2) while the tab label follows the tab state (controls.css underline tabs) */ -html.flat-theme [data-ui~="channels-page"] .table-wrapper td[data-column="name"] > span { - font-weight: 600; -} - -html.flat-theme [data-ui~="channels-page"] .table-wrapper td[data-column="description"] > span { - font-size: 13px; - color: rgb(var(--ui-muted)); -} +/* 5.2 渠道定价: the channel name and the description keep upstream's classes; the dialog's platform tabs keep the + platform logo's colour (identity, decision 2) while the tab label follows the tab state (controls.css underline + tabs) */ /* the dialog's tab strip spans the body edge to edge: upstream's -mx-6 / px-6 assume a 24px body inset, the console's dialog body is 18px in (overlays.css --ui-dialog-x), so the strip stood 6px out on each side and the body scrolled @@ -2243,7 +2077,7 @@ html.flat-theme .modal-content .channel-dialog-body label[class~="inline-flex"][ background-color: rgb(var(--ui-primary-soft)); } -/* the group box: a well (1px line, radius 8, the grouped fill); counts Mono faint */ +/* the group box: a well (1px line, radius 8, the grouped fill); counts faint, tabular */ html.flat-theme .modal-content .channel-dialog-body div[class~="max-h-40"][class~="overflow-auto"][class~="rounded-lg"] { border-color: rgb(var(--ui-line)); border-radius: var(--ui-radius-lg); @@ -2251,7 +2085,6 @@ html.flat-theme .modal-content .channel-dialog-body div[class~="max-h-40"][class } html.flat-theme .modal-content .channel-dialog-body div[class~="max-h-40"] label > span[class~="text-[10px]"][class~="text-gray-400"] { - font-family: var(--ui-font-mono); font-variant-numeric: tabular-nums; color: rgb(var(--ui-faint)); } @@ -2271,9 +2104,10 @@ html.flat-theme .modal-content .channel-dialog-body div[class~="rounded-lg"][cla border-radius: var(--ui-radius-lg); } -/* 5.3 渠道监控 (admin): the page head as the reference's page title block (no card: title 20/600, the description 13px - muted, the V2 / V1 switch the segmented control); the table as the other table pages. The monitor name 13/600; the - 7-day availability and latency figures Mono. */ +/* 5.3 渠道监控 (admin): the page head as the reference's page title block (no card: title 18/600 as the header row's, + the description upstream's .page-description 14px muted, the V2 / V1 switch the segmented control); the table as + the other table pages. The monitor name keeps upstream's classes; the 7-day availability and latency figures are + tabular. */ html.flat-theme [data-ui~="channel-monitor-page"][class~="space-y-6"] > :not([hidden]) ~ :not([hidden]) { margin-top: var(--ui-shell-gap, 12px); } @@ -2288,7 +2122,7 @@ html.flat-theme [data-ui~="channel-monitor-page"] > header.page-header { html.flat-theme [data-ui~="channel-monitor-page"] > header.page-header > h1 { gap: 0; - font-size: 20px; + font-size: 18px; font-weight: 600; line-height: 28px; color: rgb(var(--ui-ink)); @@ -2317,8 +2151,8 @@ html.flat-theme [data-ui~="channel-monitor-page"] > header.page-header > h1 > sp } html.flat-theme [data-ui~="channel-monitor-page"] > header.page-header > p.page-description { - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; color: rgb(var(--ui-muted)); } @@ -2348,21 +2182,14 @@ html.flat-theme [data-ui~="channel-monitor-page"] > header.page-header > div[cla } } -html.flat-theme [data-ui~="channel-monitor-page"] .table-wrapper td[data-column="name"] span[class~="font-medium"] { - font-weight: 600; -} - html.flat-theme [data-ui~="channel-monitor-page"] .table-wrapper td:is([data-column="availability_7d"], [data-column="latency"]) > span { - font-family: var(--ui-font-mono); - font-size: 12.5px; font-variant-numeric: tabular-nums; - color: rgb(var(--ui-ink-soft)); } /* 5.3a 渠道监控 · V2 数据监控配置 (MonitorSettingsPanel, the section under the head): flush with the page (upstream's 4 / 8px inset goes), its blocks 12px apart. The head and every card are the reference's card: the 1px structural line (the shared card rule drops upstream's ring and !border-0 keeps the border at 0, so an inset ring draws it), radius 8. - The head's title is a card title (13/600; upstream's 20/900 outranked the page title) without the decorative blue + The head's title is a card title (14/600; upstream's 20/900 outranked the page title) without the decorative blue tile (as the page title's), its description 12px muted, 14 / 16 in; rows and card heads 16px in; the V1-mode note is the reference's warning notice (REF §8.16: the warning line, the warning fill, radius 6). */ html.flat-theme [data-ui~="channel-monitor-page"] > section[class~="max-w-6xl"] { @@ -2382,9 +2209,9 @@ html.flat-theme [data-ui~="channel-monitor-page"] > section[class~="max-w-6xl"] html.flat-theme [data-ui~="channel-monitor-page"] > section[class~="max-w-6xl"] > header.page-header h2.page-title { gap: 0; - font-size: 13px; + font-size: 14px; font-weight: 600; - line-height: 19.5px; + line-height: 20px; color: rgb(var(--ui-ink)); } @@ -2395,7 +2222,7 @@ html.flat-theme [data-ui~="channel-monitor-page"] > section[class~="max-w-6xl"] html.flat-theme [data-ui~="channel-monitor-page"] > section[class~="max-w-6xl"] > header.page-header p.page-description { margin-top: 2px; font-size: 12px; - line-height: 18px; + line-height: 16px; color: rgb(var(--ui-muted)); } @@ -2405,7 +2232,7 @@ html.flat-theme [data-ui~="channel-monitor-page"] > section[class~="max-w-6xl"] border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-warning-soft)); color: rgb(var(--ui-ink)); - font-size: 13px; + font-size: 14px; line-height: 1.625; } @@ -2430,10 +2257,10 @@ html.flat-theme [data-ui~="channel-monitor-page"] > section[class~="max-w-6xl"] } /* 5.4 操作日志: the filters become the table pages' toolbar (no card): each labelled field is the reference's filter - trigger holding its label as the mono label at 400 muted under the 13/600 ink value (the labelled field pills of - 使用记录, dashboard-usage.css §3: a label inside its control stays light; a field label above a field is 600), 30px, - 8px apart from the left (upstream's justify-between spread the first line 36px apart once the field group was - flattened), the actions at the row's end */ + trigger holding its label (12/16 at 400, muted, sentence case) before the 14px ink value (the labelled field pills of + 使用记录, dashboard-usage.css §3: a label inside its control stays light), 30px, 8px apart from the left (upstream's + justify-between spread the first line 36px apart once the field group was flattened), the actions at the row's + end */ html.flat-theme [data-ui~="audit-page"] > .layout-section-fixed > .card[class~="p-4"] { padding: 0; border: 0; @@ -2469,12 +2296,11 @@ html.flat-theme [data-ui~="audit-page"] > .layout-section-fixed > .card > div > html.flat-theme [data-ui~="audit-page"] > .layout-section-fixed > .card > div > div:first-child > div > label.input-label { flex: none; margin: 0; - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); font-weight: 400; line-height: var(--ui-label-leading); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; white-space: nowrap; color: rgb(var(--ui-label)); } @@ -2497,8 +2323,8 @@ html.flat-theme [data-ui~="audit-page"] > .layout-section-fixed > .card > div > border-radius: 0 var(--ui-radius-md) var(--ui-radius-md) 0; background-color: transparent; box-shadow: none; - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; } /* the keyword field's glyph: its label names it, the glyph goes */ @@ -2538,44 +2364,19 @@ html.flat-theme [data-ui~="audit-page"] > .layout-section-fixed > .card > div > gap: 8px; } -/* the log table: the time Mono muted; the operator (e-mail 13/500 ink, role and sign-in 12px muted); the action Mono - 12.5 ink over its method and path Mono 11.5 muted; the result the reference's status chip (capsule, Mono, soft fill, - the 6px dot) */ +/* the log table: the cells keep upstream's classes (the time tabular; the operator's e-mail and role, the action + over its method and path); the result the reference's status chip (capsule, soft fill, the 6px dot) in upstream's + text-xs */ html.flat-theme [data-ui~="audit-page"] .table-wrapper td[data-column="created_at"] > span { - font-family: var(--ui-font-mono); - font-size: 12px; font-variant-numeric: tabular-nums; - color: rgb(var(--ui-muted)); -} - -html.flat-theme [data-ui~="audit-page"] .table-wrapper td[data-column="actor"] > div > div:first-child { - font-weight: 500; - color: rgb(var(--ui-ink)); -} - -html.flat-theme [data-ui~="audit-page"] .table-wrapper td[data-column="actor"] > div > div:last-child, -html.flat-theme [data-ui~="audit-page"] .table-wrapper td[data-column="action"] > div > div:last-child { - font-size: 12px; - line-height: 18px; - color: rgb(var(--ui-muted)); -} - -html.flat-theme [data-ui~="audit-page"] .table-wrapper td[data-column="action"] > div > div:first-child { - font-size: 12.5px; - color: rgb(var(--ui-ink)); -} - -html.flat-theme [data-ui~="audit-page"] .table-wrapper td[data-column="action"] > div > div:last-child { - font-size: 11.5px; } html.flat-theme :is([data-ui~="audit-page"], [data-ui~="audit-detail"]) span[class~="rounded-full"][class~="gap-1.5"][class~="font-semibold"] { gap: 6px; padding: 1px 8px; - font-family: var(--ui-font-mono); - font-size: 11px; + font-size: 12px; font-weight: 500; - line-height: 18px; + line-height: 16px; font-variant-numeric: tabular-nums; } @@ -2613,7 +2414,6 @@ html.flat-theme :is([data-ui~="audit-page"], [data-ui~="audit-detail"]) span[cla html.flat-theme [data-ui~="audit-page"] .table-wrapper td[data-column="actions"] > button { gap: 5px; - font-size: 12.5px; font-weight: 500; color: var(--ui-link); } @@ -2631,9 +2431,10 @@ html.flat-theme [data-ui~="audit-page"] .table-wrapper td[colspan] > div[class~= color: rgb(var(--ui-faint)); } -/* 5.5 the audit detail dialog: the head panel (the grouped panel; the status chip, the action Mono 14/600 ink; the method - a Mono chip beside the path in a 1px field; the meta 12px muted with Mono ids), the operator / sign-in / IP tiles, the - User-Agent and bodies as code blocks, their captions the mono label */ +/* 5.5 the audit detail dialog: the head panel (the grouped panel; the status chip, the action in its template font-mono + 14/600 ink; the method a chip beside the path in a 1px field; the meta 12px muted with its font-mono ids), the + operator / sign-in / IP tiles, the User-Agent and bodies as code blocks, their captions the label (12/16/500, + sentence case) */ html.flat-theme [data-ui~="audit-detail"][class~="space-y-5"] > :not([hidden]) ~ :not([hidden]) { margin-top: 12px; } @@ -2683,19 +2484,18 @@ html.flat-theme [data-ui~="audit-detail"] > div[class~="grid"] > div[class~="rou } html.flat-theme [data-ui~="audit-detail"] :is(div, h4)[class~="text-xs"][class~="font-bold"][class~="uppercase"] { - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); - font-weight: 400; + font-weight: var(--ui-label-weight); line-height: var(--ui-label-leading); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; color: rgb(var(--ui-label)); } html.flat-theme [data-ui~="audit-detail"] > div[class~="grid"] > div > div:nth-child(2) { margin-top: 4px; - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; } html.flat-theme [data-ui~="audit-detail"] > section > :is(div, pre)[class~="rounded-xl"][class~="bg-gray-50"] { @@ -2709,17 +2509,11 @@ html.flat-theme [data-ui~="audit-detail"] > section > :is(div, pre)[class~="roun } /* 5.6 邀请 / 返利 / 提取: a toolbar row (search, two date fields, refresh, the offline withdraw entry at its end); the - records use DataTable (data.css). Order numbers and codes Mono. */ + records use DataTable (data.css), their cells in upstream's classes (order numbers and codes in their font-mono). */ html.flat-theme [data-ui~="affiliate-page"] > .layout-section-fixed > div { gap: 8px; } -html.flat-theme [data-ui~="affiliate-page"] .table-wrapper td[data-column="order"] > div > div:last-child { - font-family: var(--ui-font-mono); - font-size: 11.5px; - color: rgb(var(--ui-muted)); -} - /* 5.7 渠道监控's dialogs (MonitorFormDialog: #channel-monitor-form; MonitorTemplateManagerDialog): the choice buttons (check mode, API mode, provider) are the reference's dialog option cards (REF §8.11: radius 8, the structural line, the grouped fill at 60 %, the control outline on hover); the chosen one carries the accent edge 2px wide (palette C, as @@ -2749,14 +2543,14 @@ html.flat-theme .modal-content div[class~="grid"] > button[data-ui~="platform-te } html.flat-theme #channel-monitor-form button[aria-pressed] > span:first-child { - font-size: 13px; + font-size: 14px; font-weight: 600; color: rgb(var(--ui-ink)); } html.flat-theme #channel-monitor-form button[aria-pressed] > span:last-child:not(:first-child) { font-size: 12px; - line-height: 18px; + line-height: 16px; color: rgb(var(--ui-muted)); } @@ -2772,10 +2566,10 @@ html.flat-theme :is(#channel-monitor-form, .modal-content) div[class~="rounded-l /* 5.8 请求模板管理 (MonitorTemplateManagerDialog; hooks: data-ui="template-tabs" on the tab strip, data-ui="template-list" on the template list; its provider tablist is the only flex-wrap gap-1 tablist): the provider - tabs are the shared underline tabs (controls.css 7.3, the channel dialog's: 8 x 10, 12.5 / 600, muted, ink on - hover; the current one ink over the 2px accent rule), their counts the small Mono count; a template's mode and API - mode are categories (decision 2): the reference's neutral chip (data.css category-chip), whatever hue upstream gave - them (merge amber, replace purple, Responses blue, Chat Completions green). */ + tabs are the shared underline tabs (controls.css 7.3, the channel dialog's: 8 x 10, upstream's text-sm font-medium, + the tab grey, ink on hover; the current one ink over the 2px accent rule), their counts a small neutral count; a template's + mode and API mode are the reference's neutral chip (12/16/500), whatever hue upstream gave them (merge amber, replace + purple, Responses blue, Chat Completions green). */ html.flat-theme .modal-content div[class~="mb-4"][class~="border-b"][data-ui~="template-tabs"]:not(uipad[data-uipad][data-uipad]) { border-color: rgb(var(--ui-line)); } @@ -2783,10 +2577,10 @@ html.flat-theme .modal-content div[class~="mb-4"][class~="border-b"][data-ui~="t html.flat-theme .modal-content div[role="tablist"][class~="flex"][class~="flex-wrap"][class~="gap-1"] > button[role="tab"][class~="border-b-2"] { gap: 0.375rem; padding: 0.5rem 0.625rem; - font-size: 0.78125rem; - font-weight: 600; - line-height: 1.171875rem; - color: rgb(var(--ui-muted)); + font-size: 0.875rem; + font-weight: 500; + line-height: 1.25rem; + color: rgb(var(--theme-text-gray-600)); transition-property: color, border-color; transition-duration: var(--ui-duration); transition-timing-function: var(--ui-ease); @@ -2805,8 +2599,7 @@ html.flat-theme .modal-content div[role="tablist"][class~="flex"][class~="flex-w padding: 0 6px; background-color: rgb(var(--ui-subtle)); color: rgb(var(--ui-ink-soft)); - font-family: var(--ui-font-mono); - font-size: 11px; + font-size: 12px; font-weight: 500; line-height: 16px; } @@ -2817,8 +2610,7 @@ html.flat-theme .modal-content [data-ui~="template-list"]:not(uipad[data-uipad][ border-radius: var(--ui-radius-sm); background-color: rgb(var(--ui-subtle)); color: rgb(var(--ui-ink-soft)); - font: 500 var(--ui-chip-size) / 16px var(--ui-font-mono); - letter-spacing: 0.01em; + font: 500 var(--ui-chip-size) / 16px var(--ui-font); } /* ================================================================== 6 · channel status (/monitor, v1 and v2) */ @@ -2828,8 +2620,8 @@ html.flat-theme .modal-content [data-ui~="template-list"]:not(uipad[data-uipad][ table's wrapper), data-ui="monitor-v2" views/user/ChannelStatusV2View.vue (root), data-ui="monitor-matrix" features/channel-monitor-v2/RelayPulseMatrix.vue (its card). Identity: MonitorCard.vue's logo tile (data-ui="platform-text") and provider chip (platform-chip); the group chip is a category (category-chip). - Reference: the /models uptime column (REF §8.7: a 3px capsule per sample, the Mono figure in the state's ink) and the - /logs toolbar and KPI tiles. */ + Reference: the /models uptime column (REF §8.7: a 3px capsule per sample, the tabular figure in the state's ink) and + the /logs toolbar and KPI tiles. */ /* 6.1 the toolbar row: window (segmented, controls.css), the overall state as the reference's status chip, refresh and auto-refresh as the default button, 30px */ @@ -2843,11 +2635,11 @@ html.flat-theme [data-ui~="monitor-hero"] > div { html.flat-theme [data-ui~="monitor-hero"] > div > span[class~="rounded-full"] { padding: 1px 8px; - font-family: var(--ui-font-mono); - font-size: var(--ui-badge-size, 10px); + font-size: var(--ui-badge-size, 12px); font-weight: 500; line-height: 18px; - letter-spacing: 0.06em; + letter-spacing: normal; + text-transform: none; } html.flat-theme [data-ui~="monitor-hero"] > div > span[class~="rounded-full"] > span[class~="w-1.5"] { @@ -2878,8 +2670,8 @@ html.flat-theme [data-ui~="monitor-hero"] > div > button[class~="h-8"] svg { /* 6.2 the monitor cards: the reference's card (surface, 1px structural line, radius 8, 14 / 16 in, no blur, no lift) in a 12px grid; the head = the provider's logo square (REF §8.8: 28px, 1px line, radius 6, the logo in its platform colour), - the name 13/600, the provider chip (identity), the model Mono 11.5 muted, the group chip (neutral); the state the - reference's status chip */ + the name 14/600, the provider chip (identity), the model in its template font-mono 12px muted, the group chip; the + state the reference's status chip */ html.flat-theme [data-ui~="monitor-grid"] > div[class~="grid"] { gap: var(--ui-shell-gap, 12px); } @@ -2927,9 +2719,9 @@ html.flat-theme [data-ui~="monitor-grid"] > div[class~="grid"] > button > div:fi } html.flat-theme [data-ui~="monitor-grid"] > div[class~="grid"] > button > div:first-child > div > div:first-child { - font-size: 13px; + font-size: 14px; font-weight: 600; - line-height: 19.5px; + line-height: 20px; color: rgb(var(--ui-ink)); } @@ -2939,23 +2731,22 @@ html.flat-theme [data-ui~="monitor-grid"] > div[class~="grid"] > button > div:fi } html.flat-theme [data-ui~="monitor-grid"] > div[class~="grid"] > button > div:first-child > div > div:last-child > span[class~="font-mono"] { - font-size: 11.5px; + font-size: 12px; color: rgb(var(--ui-muted)); } html.flat-theme [data-ui~="monitor-grid"] > div[class~="grid"] > button > div:first-child > span[class~="rounded-full"], html.flat-theme [data-ui~="monitor-detail"] td span[class~="rounded-full"] { padding: 1px 7px; - font-family: var(--ui-font-mono); - font-size: var(--ui-badge-size, 10px); + font-size: var(--ui-badge-size, 12px); font-weight: 500; line-height: var(--ui-badge-leading, 16px); - letter-spacing: 0.025em; - text-transform: uppercase; + text-transform: none; } /* the two figures (dialog latency, endpoint ping): the reference's StatTile in small (1px line, radius 6, 8 / 10 in; the - mono label with its 12px glyph; the figure Geist Mono 15/600 tabular, the unit Mono 10.5 muted) */ + label with its 12px glyph; the figure in upstream's text-lg 18/28/700 and its template font-mono, tabular, the unit + 12px muted) */ html.flat-theme [data-ui~="monitor-grid"] > div[class~="grid"] > button > div[class~="grid-cols-2"] { margin-top: 12px; gap: 8px; @@ -2970,25 +2761,25 @@ html.flat-theme [data-ui~="monitor-grid"] > div[class~="grid"] > button > div[cl html.flat-theme [data-ui~="monitor-grid"] > div[class~="grid"] > button > div[class~="grid-cols-2"] > div > div:first-child { gap: 5px; - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); - font-weight: 400; + font-weight: var(--ui-label-weight); line-height: var(--ui-label-leading); letter-spacing: var(--ui-label-tracking); + text-transform: none; color: rgb(var(--ui-label)); } html.flat-theme [data-ui~="monitor-grid"] > div[class~="grid"] > button > div[class~="grid-cols-2"] > div > div:last-child { margin-top: 2px; - font-size: 15px; - font-weight: 600; - line-height: 22.5px; + font-size: 18px; + font-weight: 700; + line-height: 28px; color: rgb(var(--ui-ink)); } html.flat-theme [data-ui~="monitor-grid"] > div[class~="grid"] > button > div[class~="grid-cols-2"] > div > div:last-child > span { margin-left: 2px; - font-size: 10.5px; + font-size: 12px; font-weight: 400; color: rgb(var(--ui-muted)); } @@ -3002,7 +2793,7 @@ html.flat-theme [data-ui~="monitor-grid"] > div[class~="grid"] > button > div[cl margin-top: 12px; } -/* availability: the label the mono label; the figure 22/600 tabular, keeping upstream's red-to-green hue for the value +/* availability: the label the label; the figure 22/600 tabular, keeping upstream's red-to-green hue for the value (inline hsl(h 72% 42%), h = 1.2 x percent), darkened in light and lifted to pastel in dark (the reference's uptime inks: light and deep on white, pastel on dark), so every hue from 0 % to 100 % reads >= 4.5:1 on the card (worst: 4.8:1 yellow at 50 % on white, 6.9:1 red at 0 % on the dark surface; upstream's own colour is 3.9:1 / 2.4:1 there). @@ -3013,10 +2804,10 @@ html.flat-theme [data-ui~="monitor-grid"] > div[class~="grid"] > button > div[cl } html.flat-theme [data-ui~="monitor-grid"] > div[class~="grid"] > button > div[class~="mt-3"][class~="items-end"] > div:first-child { - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); line-height: var(--ui-label-leading); letter-spacing: var(--ui-label-tracking); + text-transform: none; color: rgb(var(--ui-label)); } @@ -3024,12 +2815,11 @@ html.flat-theme [data-ui~="monitor-grid"] > div[class~="grid"] > button > div[cl font-size: 22px; font-weight: 600; line-height: 28px; - letter-spacing: -0.01em; filter: brightness(0.64); } html.flat-theme [data-ui~="monitor-grid"] > div[class~="grid"] > button > div[class~="mt-3"][class~="items-end"] > div:last-child > span:last-child { - font-size: 13px; + font-size: 14px; font-weight: 600; filter: brightness(0.64); } @@ -3039,14 +2829,13 @@ html.flat-theme.dark [data-ui~="monitor-grid"] > div[class~="grid"] > button > d } html.flat-theme [data-ui~="monitor-grid"] > div[class~="grid"] > button > div[class~="mt-3"][class~="items-end"] + div[class~="text-right"] { - font-family: var(--ui-font-mono); - font-size: 11px; color: rgb(var(--ui-muted)); } /* the 60-sample history: the reference's uptime bars (REF §8.7: capsules 3px wide, 2px apart, bottom-aligned); upstream's height code stays (full = up, 65 % = degraded, 35 % = failed, 15 % = not tested) and the fills are the status dots' - colours (>= 3:1 on the card, the reference's pastel uptime fills are 1.5:1); captions Mono 10, faint */ + colours (>= 3:1 on the card, the reference's pastel uptime fills are 1.5:1); captions in upstream's micro sizes, + faint */ html.flat-theme [data-ui~="monitor-grid"] > div[class~="grid"] > button > div[class~="mt-4"][class~="pt-3"] { margin-top: 12px; padding-top: 10px; @@ -3054,11 +2843,11 @@ html.flat-theme [data-ui~="monitor-grid"] > div[class~="grid"] > button > div[cl html.flat-theme [data-ui~="monitor-grid"] > div[class~="grid"] > button > div[class~="mt-4"][class~="pt-3"] > div:first-child { margin-bottom: 8px; - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); - font-weight: 400; + font-weight: var(--ui-label-weight); line-height: var(--ui-label-leading); letter-spacing: var(--ui-label-tracking); + text-transform: none; color: rgb(var(--ui-label)); } @@ -3089,9 +2878,8 @@ html.flat-theme [data-ui~="monitor-grid"] > div[class~="grid"] > button > div[cl html.flat-theme [data-ui~="monitor-grid"] > div[class~="grid"] > button > div[class~="mt-4"][class~="pt-3"] > div:last-child { margin-top: 4px; - font-family: var(--ui-font-mono); - font-size: 10px; - letter-spacing: 0.06em; + letter-spacing: normal; + text-transform: none; color: rgb(var(--ui-label)); } @@ -3100,7 +2888,6 @@ html.flat-theme [data-ui~="monitor-grid"] > div[class~="grid"] > button > div[cl height: 18px; border-color: rgb(var(--ui-line-strong)); border-radius: var(--ui-radius-sm); - font-family: var(--ui-font-mono); color: rgb(var(--ui-label)); } @@ -3109,18 +2896,18 @@ html.flat-theme [data-ui~="monitor-grid"] > div[class~="grid"] > div[class~="ani animation: none; } -/* the model detail table: the reference's table in the dialog (mono header, 13px cells, Mono figures) */ +/* the model detail table: the reference's table in the dialog (upstream's usage-table header 12/16/500, uppercase, + 0.05em; the cells in upstream's classes on its text-sm table, figures tabular) */ html.flat-theme [data-ui~="monitor-detail"] table > thead { border-color: rgb(var(--ui-line)); } html.flat-theme [data-ui~="monitor-detail"] table > thead > tr > th { padding: 10px 12px 10px 0; - font-family: var(--ui-font-mono); - font-size: var(--ui-label-size); + font-size: 12px; font-weight: 500; - line-height: 19.5px; - letter-spacing: var(--ui-label-tracking); + line-height: 16px; + letter-spacing: 0.05em; color: rgb(var(--ui-table-head)); white-space: nowrap; } @@ -3131,15 +2918,10 @@ html.flat-theme [data-ui~="monitor-detail"] table > tbody > tr { html.flat-theme [data-ui~="monitor-detail"] table > tbody > tr > td { padding: 10px 12px 10px 0; - font-size: 13px; - line-height: 19.5px; } html.flat-theme [data-ui~="monitor-detail"] table > tbody > tr > td:not(:first-child):not(:nth-child(2)) { - font-family: var(--ui-font-mono); - font-size: 12.5px; font-variant-numeric: tabular-nums; - color: rgb(var(--ui-ink-soft)); } /* 6.3 channel status v2 (the pulse matrix view; production runs v1, frames with sample data are labelled sample). @@ -3159,8 +2941,8 @@ html.flat-theme [data-ui~="monitor-v2"] :is(section, div)[class~="ring-1"] { box-shadow: 0 0 0 1px rgb(var(--ui-line)); } -/* the sticky head card: title 13/600 (the decorative glyph tile goes), the update line Mono 11, refresh the 30px default - button; the toolbar row 10 / 16 in, 8px apart */ +/* the sticky head card: title 14/600 (the decorative glyph tile goes), the update line upstream's text-xs (tabular), + refresh the 30px default button; the toolbar row 10 / 16 in, 8px apart */ html.flat-theme [data-ui~="monitor-v2"] > section:first-child > header.page-header { padding: 12px 16px; border-color: rgb(var(--ui-line)); @@ -3168,9 +2950,9 @@ html.flat-theme [data-ui~="monitor-v2"] > section:first-child > header.page-head html.flat-theme [data-ui~="monitor-v2"] > section:first-child > header.page-header h1 { gap: 0; - font-size: 13px; + font-size: 14px; font-weight: 600; - line-height: 19.5px; + line-height: 20px; color: rgb(var(--ui-ink)); } @@ -3181,8 +2963,7 @@ html.flat-theme [data-ui~="monitor-v2"] > section:first-child > header.page-head html.flat-theme [data-ui~="monitor-v2"] > section:first-child > header.page-header .page-description { margin-top: 2px; gap: 8px; - font-family: var(--ui-font-mono); - font-size: 11px; + font-size: 12px; font-variant-numeric: tabular-nums; color: rgb(var(--ui-muted)); } @@ -3217,15 +2998,15 @@ html.flat-theme [data-ui~="monitor-v2"] > section:first-child > .monitor-toolbar padding: 10px 16px; } -/* its three segmented controls (window, view, metric) take the recipe's segments (controls.css 7.1: 14 x 6, 12.5 / - 600); upstream compacts them with !px-2 !py-1 text-xs, restated with !important (the only way past them). At that - size the controls no longer fit upstream's single scrolling row (1136px at 1440): from 1024 the row wraps like the - other toolbars (8px apart, no hidden overflow) and the decorative dividers go (the reference's toolbar separates its - controls by the gap alone); below 1024 upstream's scrolling strip stays. */ +/* its three segmented controls (window, view, metric) take the recipe's segment box (controls.css 7.1: 14 x 6) at + upstream's own text-xs (12/16); upstream compacts them with !px-2 !py-1, restated with !important (the only way past + them). At that size the controls no longer fit upstream's single scrolling row (1136px at 1440): from 1024 the row + wraps like the other toolbars (8px apart, no hidden overflow) and the decorative dividers go (the reference's + toolbar separates its controls by the gap alone); below 1024 upstream's scrolling strip stays. */ html.flat-theme [data-ui~="monitor-v2"] > section:first-child > .monitor-toolbar .tabs > .tab { padding: var(--ctl-seg-pad-y) var(--ctl-seg-pad-x) !important; - font-size: 0.78125rem; - line-height: 1.171875rem; + font-size: 0.75rem; + line-height: 1rem; } @media (min-width: 1024px) { @@ -3258,21 +3039,21 @@ html.flat-theme [data-ui~="monitor-v2"] > section[class~="grid"] > .stat-card > margin-top: 5px; } -/* the tile's label at the KPI tiles' weight (data.css §7, the ops tiles: 400; upstream font-bold) */ +/* the tile's label at the .stat-label recipe's own weight (upstream text-sm, 400; this template adds font-bold) */ html.flat-theme [data-ui~="monitor-v2"] > section[class~="grid"] > .stat-card .stat-label { font-weight: 400; } html.flat-theme [data-ui~="monitor-v2"] > section[class~="grid"] > .stat-card > div > :is(small, div)[class~="text-[11px]"] { margin-top: 2px; - font-size: 11px; color: rgb(var(--ui-muted)); } -/* 6.4 the pulse matrix (RelayPulseMatrix): the card 14px in; the title 13/600 (glyph goes), the bucket chip and the - reset button 26px; the matrix sits on the card (no inner well), its header the mono label, rows divided by 1px lines, - summary figures Mono 12; each bucket a capsule like the reference's uptime bars, coloured by health in the reference's - uptime palette (good / warn / bad with the steps between; no traffic = the grouped fill) */ +/* 6.4 the pulse matrix (RelayPulseMatrix): the card 14px in; the title 14/600 (glyph goes), the bucket chip and the + reset button 26px; the matrix sits on the card (no inner well), its header the usage-table header (12px, 0.05em), + rows divided by 1px lines, summary figures 12px tabular; each bucket a capsule like the reference's uptime bars, + coloured by health in the reference's uptime palette (good / warn / bad with the steps between; no traffic = the + grouped fill) */ html.flat-theme [data-ui~="monitor-v2"] section[class~="card"][data-ui~="monitor-matrix"] { padding: 14px !important; border-radius: var(--ui-radius-lg) !important; @@ -3280,9 +3061,9 @@ html.flat-theme [data-ui~="monitor-v2"] section[class~="card"][data-ui~="monitor html.flat-theme [data-ui~="monitor-v2"] section[class~="card"][data-ui~="monitor-matrix"] h2 { gap: 0; - font-size: 13px; + font-size: 14px; font-weight: 600; - line-height: 19.5px; + line-height: 20px; color: rgb(var(--ui-ink)); } @@ -3304,7 +3085,7 @@ html.flat-theme [data-ui~="monitor-v2"] section[class~="card"][data-ui~="monitor background-color: rgb(var(--ui-surface)); color: rgb(var(--ui-ink-soft)); font-size: 12px; - font-weight: 600; + font-weight: 500; } html.flat-theme [data-ui~="monitor-v2"] .matrix-scroll { @@ -3317,10 +3098,9 @@ html.flat-theme [data-ui~="monitor-v2"] .matrix-header { border-bottom: 1px solid rgb(var(--ui-line)); background-color: rgb(var(--ui-surface)); color: rgb(var(--ui-table-head)); - font-family: var(--ui-font-mono); - font-size: var(--ui-label-size); + font-size: 12px; font-weight: 500; - letter-spacing: var(--ui-label-tracking); + letter-spacing: 0.05em; } html.flat-theme [data-ui~="monitor-v2"] .matrix-row:not(.matrix-header) { @@ -3333,13 +3113,12 @@ html.flat-theme [data-ui~="monitor-v2"] .matrix-row .summary-value { } html.flat-theme [data-ui~="monitor-v2"] .matrix-row .dimension-cell > strong { - font-size: 12.5px; + font-size: 12px; font-weight: 600; color: rgb(var(--ui-ink)); } html.flat-theme [data-ui~="monitor-v2"] .matrix-row .summary-value { - font-family: var(--ui-font-mono); font-size: 12px; font-weight: 400; color: rgb(var(--ui-ink-soft)); @@ -3408,7 +3187,6 @@ html.flat-theme [data-ui~="monitor-v2"] section[class~="card"][data-ui~="monitor } html.flat-theme [data-ui~="monitor-v2"] section[class~="card"][data-ui~="monitor-matrix"] .card-body > div:last-child :is(div, span)[class~="text-[11px]"] { - font-size: 11px; color: rgb(var(--ui-muted)); } @@ -3432,8 +3210,8 @@ html.flat-theme .matrix-floating-tooltip .matrix-floating-tooltip-title.matrix-f } /* 6.5 the detail tabs card (models / error reasons / users): the ring line, the head 12 / 16 in; the tables the - reference's (mono header on the surface, 13px cells, 1px lines, Mono figures); error reasons as rows on the grouped - fill with a danger bar (no gradient) */ + reference's (the usage-table header on the surface, the .table recipe's text-sm cells, 1px lines, tabular figures); + error reasons as rows on the grouped fill with a danger bar (no gradient) */ html.flat-theme [data-ui~="monitor-v2"] > section[class~="card"]:last-child > div:first-child { padding: 12px 16px 0; border-color: rgb(var(--ui-line)); @@ -3456,11 +3234,10 @@ html.flat-theme [data-ui~="monitor-v2"] .monitor-table > thead > tr > th { background-color: rgb(var(--ui-surface)); box-shadow: inset 0 -1px 0 rgb(var(--ui-line)); color: rgb(var(--ui-table-head)); - font-family: var(--ui-font-mono); - font-size: var(--ui-label-size); + font-size: 12px; font-weight: 500; - line-height: 19.5px; - letter-spacing: var(--ui-label-tracking); + line-height: 16px; + letter-spacing: 0.05em; text-transform: uppercase; white-space: nowrap; } @@ -3468,8 +3245,6 @@ html.flat-theme [data-ui~="monitor-v2"] .monitor-table > thead > tr > th { html.flat-theme [data-ui~="monitor-v2"] .monitor-table > tbody > tr > td { padding: 8px 12px; border-color: rgb(var(--ui-line)); - font-size: 13px; - line-height: 19.5px; font-variant-numeric: tabular-nums; } @@ -3477,12 +3252,6 @@ html.flat-theme [data-ui~="monitor-v2"] .monitor-table > :is(thead, tbody) > tr padding-left: 16px; } -html.flat-theme [data-ui~="monitor-v2"] .monitor-table > tbody > tr > td > small, -html.flat-theme [data-ui~="monitor-v2"] .monitor-table > tbody > tr > td span[class~="text-xs"][class~="block"] { - font-size: 11.5px; - color: rgb(var(--ui-muted)); -} - html.flat-theme [data-ui~="monitor-v2"] .monitor-table > tbody > tr[class~="cursor-pointer"]:hover { background-color: rgb(var(--ui-hover)); } @@ -3500,8 +3269,7 @@ html.flat-theme [data-ui~="monitor-v2"] > section[class~="card"]:last-child div[ } html.flat-theme [data-ui~="monitor-v2"] > section[class~="card"]:last-child div[class~="rounded-2xl"][class~="bg-gray-50"] small { - font-family: var(--ui-font-mono); - font-size: 11.5px; + font-size: 12px; } /* ================================================================== 7 · orders (payment dashboard, orders, plans) */ @@ -3519,8 +3287,9 @@ html.flat-theme [data-ui~="payment-dashboard"] div[class~="grid"][class~="gap-6" gap: var(--ui-shell-gap, 12px); } -/* the 7 / 30 / 90-day choice: the reference's segmented control (controls.css 7.1's track and segment metrics; the - current segment the raised pill); the head row's only bordered box, its buttons a constant list */ +/* the 7 / 30 / 90-day choice: the reference's segmented control (controls.css 7.1's track and segment metrics at + upstream's text-xs, the segment grey; the current segment the raised pill); the head row's only bordered box, its + buttons a constant list */ html.flat-theme [data-ui~="payment-dashboard"] > div:first-child div[class~="rounded-lg"][class~="border"]:not(uipad) { gap: 0; padding: var(--ctl-seg-inset); @@ -3532,10 +3301,10 @@ html.flat-theme [data-ui~="payment-dashboard"] > div:first-child div[class~="rou html.flat-theme [data-ui~="payment-dashboard"] > div:first-child div[class~="rounded-lg"][class~="border"] > button { padding: var(--ctl-seg-pad-y) var(--ctl-seg-pad-x); border-radius: var(--ui-radius-md); - color: rgb(var(--ui-muted)); - font-size: 0.78125rem; + color: rgb(var(--theme-text-gray-600)); + font-size: 0.75rem; font-weight: 500; - line-height: 1.171875rem; + line-height: 1rem; transition: color var(--ui-duration-slide) var(--ui-ease-out), background-color var(--ui-duration-slide) var(--ui-ease-out); } @@ -3547,7 +3316,7 @@ html.flat-theme [data-ui~="payment-dashboard"] > div:first-child div[class~="rou html.flat-theme [data-ui~="payment-dashboard"] > div:first-child div[class~="rounded-lg"][class~="border"] > button[class~="bg-primary-600"] { background-color: rgb(var(--ui-thumb)); color: rgb(var(--ui-ink)); - font-weight: 600; + font-weight: 500; box-shadow: var(--ui-thumb-shadow); } @@ -3555,17 +3324,17 @@ html.flat-theme [data-ui~="payment-dashboard"] > div:first-child > div { gap: 8px; } -/* the distribution and top-users cards: 14px in, the title 13/600; the ranks' medal tints are decoration: a neutral - Mono rank square (radius 4, the grouped fill) */ +/* the distribution and top-users cards: 14px in, the title 14/600; the ranks' medal tints are decoration: a neutral + rank square (radius 4, the grouped fill, upstream's text-xs) */ html.flat-theme [data-ui~="payment-dashboard"] div[class~="grid"] > div.card[class~="p-4"] { padding: 14px; } html.flat-theme [data-ui~="payment-dashboard"] div[class~="grid"] > div.card[class~="p-4"] > h3 { margin-bottom: 12px; - font-size: 13px; + font-size: 14px; font-weight: 600; - line-height: 19.5px; + line-height: 20px; } html.flat-theme [data-ui~="payment-dashboard"] div[class~="grid"] > .card[class~="p-4"] span[class~="rounded-full"][class~="h-6"][class~="w-6"] { @@ -3575,8 +3344,7 @@ html.flat-theme [data-ui~="payment-dashboard"] div[class~="grid"] > .card[class~ border-radius: var(--ui-radius-sm); background-color: rgb(var(--ui-subtle)); color: rgb(var(--ui-ink-soft)); - font-family: var(--ui-font-mono); - font-size: 11px; + font-size: 12px; font-weight: 600; } @@ -3586,7 +3354,6 @@ html.flat-theme [data-ui~="payment-dashboard"] div[class~="grid"] > .card[class~ html.flat-theme [data-ui~="payment-dashboard"] div[class~="grid"] > .card[class~="p-4"] :is(div, span)[class~="text-right"] > span[class~="font-medium"], html.flat-theme [data-ui~="payment-dashboard"] div[class~="grid"] > .card[class~="p-4"] div[class~="justify-between"] > span[class~="font-medium"]:last-child { - font-family: var(--ui-font-mono); font-variant-numeric: tabular-nums; } @@ -3627,14 +3394,12 @@ html.flat-theme [data-ui~="orders-page"] > .card[class~="p-4"]:first-child :is(. padding-bottom: 0.265625rem; } -/* the order rows' actions: the reference's ghost text button (28px, 8px in, 12.5/600); each keeps its colour family - (view neutral, cancel warning, refund danger, retry info, approve purple: states of the order) */ +/* the order rows' actions: the reference's ghost text button (28px, 8px in, upstream's text-xs font-medium); each keeps + its colour family (view neutral, cancel warning, refund danger, retry info, approve purple: states of the order) */ html.flat-theme [data-ui~="orders-page"] .table-wrapper td[data-column="actions"] button[class~="rounded-md"][class~="px-2"] { height: 28px; padding: 0 8px; border-radius: var(--ui-radius-md); - font-size: 12.5px; - font-weight: 600; } html.flat-theme [data-ui~="orders-page"] .table-wrapper td[data-column="actions"] button[class~="rounded-md"][class~="text-gray-600"] { @@ -3646,7 +3411,8 @@ html.flat-theme [data-ui~="orders-page"] .table-wrapper td[data-column="actions" color: rgb(var(--ui-ink)); } -/* plans: the row actions as the other table pages (one line), the price Mono; the for-sale switch is controls.css' */ +/* plans: the row actions as the other table pages (one line, the caption upstream's text-xs), the price tabular; the + for-sale switch is controls.css' */ html.flat-theme [data-ui~="plans-page"] .table-wrapper td[data-column="actions"] > div { gap: 2px; } @@ -3661,8 +3427,6 @@ html.flat-theme [data-ui~="plans-page"] .table-wrapper td[data-column="actions"] } html.flat-theme [data-ui~="plans-page"] .table-wrapper td[data-column="actions"] button[class~="flex-col"] > span { - font-size: 12.5px; - font-weight: 600; white-space: nowrap; } @@ -3682,7 +3446,6 @@ html.flat-theme [data-ui~="plans-page"] .table-wrapper td[data-column="actions"] } html.flat-theme [data-ui~="plans-page"] .table-wrapper td[data-column="price"] > div > span:first-child { - font-family: var(--ui-font-mono); font-variant-numeric: tabular-nums; } @@ -3698,16 +3461,17 @@ html.flat-theme [data-ui~="risk-page"] div[class~="grid"]:is([class~="gap-6"], [ gap: var(--ui-shell-gap, 12px); } -/* the page head: the reference's page title block (20/600, the description 13px muted), actions 30px */ +/* the page head: the reference's page title block (18/600 as the header row's title, the description 14px muted), + actions 30px */ html.flat-theme [data-ui~="risk-page"] > div:first-child > div:first-child h1 { - font-size: 20px; + font-size: 18px; font-weight: 600; line-height: 28px; } html.flat-theme [data-ui~="risk-page"] > div:first-child > div:first-child p { margin-top: 2px; - font-size: 13px; + font-size: 14px; color: rgb(var(--ui-muted)); } @@ -3725,13 +3489,12 @@ html.flat-theme [data-ui~="risk-page"] > div:first-child > div:first-child p { } html.flat-theme [data-ui~="risk-page"] > p[data-test="active-audit-engine"] { - font-family: var(--ui-font-mono); - font-size: 11.5px; color: rgb(var(--ui-muted)); } /* the four overview tiles: the reference's StatTile (1px line, radius 6, 8 / 12 in; the coloured glyph tile goes (D4); - the label the mono label, the value 15/600, its note 11px muted, the state chip keeps its status tone) */ + the label the label, the value upstream's text-xl font-semibold (20/28/600), its note 12px muted, the state chip + keeps its status tone) */ html.flat-theme [data-ui~="risk-page"] > div[class~="grid"] > div[class~="rounded-lg"][class~="border"][class~="shadow-sm"] { min-height: 76px; padding: 8px 12px; @@ -3745,27 +3508,26 @@ html.flat-theme [data-ui~="risk-page"] > div[class~="grid"] > div[class~="rounde } html.flat-theme [data-ui~="risk-page"] > div[class~="grid"] > div[class~="rounded-lg"][class~="border"][class~="shadow-sm"] p[class~="text-xs"][class~="font-medium"] { - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); - font-weight: 400; + font-weight: var(--ui-label-weight); line-height: var(--ui-label-leading); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; color: rgb(var(--ui-label)); } html.flat-theme [data-ui~="risk-page"] > div[class~="grid"] > div[class~="rounded-lg"][class~="border"][class~="shadow-sm"] p[class~="text-xl"] { - font-size: 15px; + font-size: 20px; font-weight: 600; - line-height: 22.5px; + line-height: 28px; } html.flat-theme [data-ui~="risk-page"] > div[class~="grid"] > div[class~="rounded-lg"][class~="border"][class~="shadow-sm"] p[class~="text-xl"] + p { - font-size: 11px; + font-size: 12px; color: rgb(var(--ui-muted)); } -/* the runtime cards: the reference's card (the head 12 / 16 in over a 1px line, the title 13/600 and its description +/* the runtime cards: the reference's card (the head 12 / 16 in over a 1px line, the title 14/600 and its description 12px muted, the body 14 / 16 in) */ html.flat-theme [data-ui~="risk-page"] .card > div[class~="border-b"][class~="px-6"][class~="py-4"] { gap: 10px; @@ -3774,9 +3536,9 @@ html.flat-theme [data-ui~="risk-page"] .card > div[class~="border-b"][class~="px } html.flat-theme [data-ui~="risk-page"] .card > div[class~="border-b"] h2 { - font-size: 13px; + font-size: 14px; font-weight: 600; - line-height: 19.5px; + line-height: 20px; } html.flat-theme [data-ui~="risk-page"] .card > div[class~="border-b"] h2 + p { @@ -3794,8 +3556,8 @@ html.flat-theme [data-ui~="risk-page"] .card > div[class~="grid"][class~="p-6"] padding: 14px 16px 16px; } -/* the metric tiles (pre-block sync, worker counts): neutral StatTiles (the tints per metric are decoration); values 15/600 - tabular in ink, labels the mono label */ +/* the metric tiles (pre-block sync, worker counts): neutral StatTiles (the tints per metric are decoration); values in + upstream's text-2xl font-semibold (24/32/600) tabular in ink, labels the label */ html.flat-theme [data-ui~="risk-page"] [data-test="pre-block-metric-grid"] { gap: 10px; } @@ -3810,7 +3572,6 @@ html.flat-theme [data-ui~="risk-page"] .card div[class~="grid"][class~="grid-col html.flat-theme [data-ui~="risk-page"] [data-test="pre-block-metric-grid"] > div > p:first-child, html.flat-theme [data-ui~="risk-page"] .card div[class~="grid"][class~="grid-cols-2"][class~="gap-3"] > div[class~="rounded-lg"][class~="p-4"] > p:first-child { - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); line-height: var(--ui-label-leading); letter-spacing: var(--ui-label-tracking); @@ -3820,25 +3581,25 @@ html.flat-theme [data-ui~="risk-page"] .card div[class~="grid"][class~="grid-col html.flat-theme [data-ui~="risk-page"] [data-test="pre-block-metric-grid"] > div > p:nth-child(2), html.flat-theme [data-ui~="risk-page"] .card div[class~="grid"][class~="grid-cols-2"][class~="gap-3"] > div[class~="rounded-lg"][class~="p-4"] > p:nth-child(2) { margin-top: 2px; - font-size: 15px; + font-size: 24px; font-weight: 600; - line-height: 22.5px; + line-height: 32px; font-variant-numeric: tabular-nums; color: rgb(var(--ui-ink)); } html.flat-theme [data-ui~="risk-page"] [data-test="pre-block-metric-grid"] > div > p:nth-child(3) { - font-size: 11px; + font-size: 12px; color: rgb(var(--ui-muted)); } -/* the API-key load list and its empty line: rows on the grouped fill (radius 6); key figures Mono; the load bar the - capsule on the grouped fill in ink */ +/* the API-key load list and its empty line: rows on the grouped fill (radius 6); key figures in their template + font-mono; the load bar the capsule on the grouped fill in ink */ html.flat-theme [data-ui~="risk-page"] [data-test="pre-block-api-key-load-list"] > div { border: 1px solid rgb(var(--ui-line)); border-radius: var(--ui-radius-md); background-color: color-mix(in srgb, rgb(var(--ui-subtle)) 60%, rgb(var(--ui-surface))); - font-size: 13px; + font-size: 14px; } /* no key load yet: the shared empty state (data.css §9) */ @@ -3868,7 +3629,8 @@ html.flat-theme [data-ui~="risk-page"] .card div[class~="rounded-lg"][class~="bo border-radius: var(--ui-radius-lg); } -/* the records card: the model-filter line a grouped strip (radius 6), its models Mono chips; filters 30px */ +/* the records card: the model-filter line a grouped strip (radius 6), its models chips in their template font-mono; + filters 30px */ html.flat-theme [data-ui~="risk-page"] .card div[class~="rounded-lg"][class~="border"][class~="bg-gray-50"][class~="px-3"] { border-color: rgb(var(--ui-line)); border-radius: var(--ui-radius-md); @@ -3879,7 +3641,7 @@ html.flat-theme [data-ui~="risk-page"] .card span[class~="rounded-md"][class~="b border: 1px solid rgb(var(--ui-line)); border-radius: var(--ui-radius-sm); box-shadow: none; - font-size: 11px; + font-size: 12px; } html.flat-theme [data-ui~="risk-page"] .card > div[class~="border-b"] :is(.select-trigger.select-trigger, input.input) { @@ -3887,15 +3649,11 @@ html.flat-theme [data-ui~="risk-page"] .card > div[class~="border-b"] :is(.selec padding-bottom: 0.265625rem; } -/* the result chip (block / flag / pass …): the reference's status chip shape, its tone kept */ +/* the result chip (block / flag / pass …): the reference's status chip shape in upstream's text-xs font-medium, its + tone kept */ html.flat-theme [data-ui~="risk-page"] table > tbody > tr > td > span[class~="rounded-md"][class~="px-2"][class~="py-1"] { padding: 1px 7px; border-radius: 9999px; - font-family: var(--ui-font-mono); - font-size: var(--ui-badge-size, 10px); - font-weight: 500; - line-height: 16px; - letter-spacing: 0.025em; } /* the settings dialog: its tab strip is the segmented control; panels the grouped panel */ @@ -3912,10 +3670,10 @@ html.flat-theme [data-ui~="risk-settings"] > div:first-child { html.flat-theme [data-ui~="risk-settings"] > div:first-child > button { padding: 6px 14px; border-radius: var(--ui-radius-md); - color: rgb(var(--ui-muted)); - font-size: 12.5px; + color: rgb(var(--theme-text-gray-600)); + font-size: 14px; font-weight: 500; - line-height: 18.75px; + line-height: 20px; transition: color var(--ui-duration-slide) var(--ui-ease-out), background-color var(--ui-duration-slide) var(--ui-ease-out); } @@ -3927,7 +3685,7 @@ html.flat-theme [data-ui~="risk-settings"] > div:first-child > button:hover { html.flat-theme [data-ui~="risk-settings"] > div:first-child > button[class~="bg-primary-50"] { background-color: rgb(var(--ui-thumb)); color: rgb(var(--ui-ink)); - font-weight: 600; + font-weight: 500; box-shadow: var(--ui-thumb-shadow); } @@ -3948,7 +3706,7 @@ html.flat-theme [data-ui~="risk-settings"] span[class~="h-10"][class~="w-10"][cl } /* no key health yet: the shared empty state (data.css §9: dashed control outline, radius 8, title 14/500 ink-soft, - hint 13px muted, the glyph muted) */ + hint 14px muted, the glyph muted) */ html.flat-theme [data-ui~="risk-settings"] div[class~="border-dashed"][class~="min-h-32"] { padding: 32px 16px; border-color: rgb(var(--ui-line-strong)); @@ -3969,13 +3727,14 @@ html.flat-theme [data-ui~="risk-settings"] div[class~="border-dashed"][class~="m html.flat-theme [data-ui~="risk-settings"] div[class~="border-dashed"][class~="min-h-32"] > p[class~="text-xs"] { margin-top: 4px; - font-size: 13px; + font-size: 14px; line-height: 1.625; color: rgb(var(--ui-muted)); } -/* 8.1 风控中心 settings: the API-key write mode is a segmented control (controls.css 7.1's track and segment metrics; - 「增量添加」 the raised pill, 「覆盖保存」 keeps the warning fill: it replaces the saved keys) */ +/* 8.1 风控中心 settings: the API-key write mode is a segmented control (controls.css 7.1's track and segment metrics at + upstream's text-xs, the segment grey; 「增量添加」 the raised pill, 「覆盖保存」 keeps the warning fill: it replaces the + saved keys) */ html.flat-theme [data-ui~="risk-settings"] div[class~="inline-flex"][class~="rounded-lg"][class~="bg-white"][class~="p-1"]:not(uipad) { padding: var(--ctl-seg-inset); border: 1px solid rgb(var(--ctl-seg-frame)); @@ -3988,20 +3747,20 @@ html.flat-theme [data-ui~="risk-settings"] div[class~="inline-flex"][class~="rou padding: var(--ctl-seg-pad-y) var(--ctl-seg-pad-x); border-radius: var(--ui-radius-md); box-shadow: none; - font-size: 0.78125rem; + font-size: 0.75rem; font-weight: 500; - line-height: 1.171875rem; + line-height: 1rem; } html.flat-theme [data-ui~="risk-settings"] div[class~="inline-flex"][class~="rounded-lg"][class~="bg-white"][class~="p-1"] > button[class~="bg-primary-500"] { background-color: rgb(var(--ui-thumb)); color: rgb(var(--ui-ink)); - font-weight: 600; + font-weight: 500; box-shadow: var(--ui-thumb-shadow); } html.flat-theme [data-ui~="risk-settings"] div[class~="inline-flex"][class~="rounded-lg"][class~="bg-white"][class~="p-1"] > button:not([class~="bg-primary-500"]):not([class~="bg-amber-500"]) { - color: rgb(var(--ui-muted)); + color: rgb(var(--theme-text-gray-600)); } html.flat-theme [data-ui~="risk-settings"] div[class~="inline-flex"][class~="rounded-lg"][class~="bg-white"][class~="p-1"] > button:not([class~="bg-primary-500"]):not([class~="bg-amber-500"]):hover { @@ -4012,33 +3771,33 @@ html.flat-theme [data-ui~="risk-settings"] div[class~="inline-flex"][class~="rou /* ================================================================== 9 · 提示词审计 (prompt audit) */ /* Root: data-ui="prompt-audit-page" features/prompt-audit/PromptAuditView.vue (risk control's feature flag, flipped in - the browser only). The page head: the eyebrow is the mono label (not the accent), the title 20/600, the description - 13px muted; the workspace card 16px in; each section's title 13/600 with its description 13px muted; tiles and wells - the reference's (1px line, radius 6 / 8); the save bar a surface band on a 1px line, no shadow. */ + the browser only). The page head: the eyebrow is the label (not the accent), the title 18/600 (its template + tracking-tight kept), the description 14px muted; the workspace card 16px in; each section's title 14/600 with its + description 14px muted; tiles and wells the reference's (1px line, radius 6 / 8); the save bar a surface band on a 1px + line, no shadow. */ html.flat-theme [data-ui~="prompt-audit-page"] > header { margin-bottom: 12px; } html.flat-theme [data-ui~="prompt-audit-page"] > header p[class~="uppercase"] { - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); - font-weight: 400; + font-weight: var(--ui-label-weight); line-height: var(--ui-label-leading); letter-spacing: var(--ui-label-tracking); + text-transform: none; color: rgb(var(--ui-label)); } html.flat-theme [data-ui~="prompt-audit-page"] > header h1 { margin-top: 4px; - font-size: 20px; + font-size: 18px; font-weight: 600; line-height: 28px; - letter-spacing: 0; } html.flat-theme [data-ui~="prompt-audit-page"] > header h1 + p { margin-top: 2px; - font-size: 13px; + font-size: 14px; } /* from 1024px the header row prints the title and the whole description (it wraps there, shell.css): the eyebrow, @@ -4056,8 +3815,7 @@ html.flat-theme [data-ui~="prompt-audit-page"] > header h1 + p { } html.flat-theme [data-ui~="prompt-audit-page"] > header > div:last-child { - font-family: var(--ui-font-mono); - font-size: 11px; + font-size: 12px; font-variant-numeric: tabular-nums; } @@ -4073,9 +3831,9 @@ html.flat-theme [data-ui~="prompt-audit-page"] > main.card { html.flat-theme [data-ui~="prompt-audit-page"] section > div:first-child h2, html.flat-theme [data-ui~="prompt-audit-page"] section > div:first-child > div > h2 { - font-size: 13px; + font-size: 14px; font-weight: 600; - line-height: 19.5px; + line-height: 20px; } html.flat-theme [data-ui~="prompt-audit-page"] section[class~="py-6"] { @@ -4092,10 +3850,10 @@ html.flat-theme [data-ui~="prompt-audit-page"] dl > div[class~="rounded-xl"][cla } html.flat-theme [data-ui~="prompt-audit-page"] dl > div[class~="rounded-xl"][class~="border"] > dt { - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); line-height: var(--ui-label-leading); letter-spacing: var(--ui-label-tracking); + text-transform: none; color: rgb(var(--ui-label)); } @@ -4112,25 +3870,21 @@ html.flat-theme [data-ui~="prompt-audit-page"] section div[class~="rounded-lg"][ background-color: color-mix(in srgb, rgb(var(--ui-subtle)) 60%, rgb(var(--ui-surface))); } -html.flat-theme [data-ui~="prompt-audit-page"] section div[class~="rounded-lg"][class~="bg-gray-50"] > p:nth-child(2) { - font-family: var(--ui-font-mono); -} - html.flat-theme [data-ui~="prompt-audit-page"] section div[class~="border-dashed"] { border-color: rgb(var(--ui-line-strong)); border-radius: var(--ui-radius-lg); color: rgb(var(--ui-muted)); } -/* the endpoint list: the header row the mono label; rows hover the reference's fill (no accent edge); the limit chips Mono */ +/* the endpoint list: the header row the usage-table header (12px/500, uppercase, 0.05em); rows hover the reference's + fill (no accent edge); the limit chips 12px tabular */ html.flat-theme [data-ui~="prompt-audit-page"] section div[class~="xl:grid"][class~="uppercase"] { border-left-width: 0; background-color: rgb(var(--ui-surface)); color: rgb(var(--ui-table-head)); - font-family: var(--ui-font-mono); - font-size: var(--ui-label-size); + font-size: 12px; font-weight: 500; - letter-spacing: var(--ui-label-tracking); + letter-spacing: 0.05em; } html.flat-theme [data-ui~="prompt-audit-page"] section article[class~="group"] { @@ -4146,28 +3900,26 @@ html.flat-theme [data-ui~="prompt-audit-page"] section span[class~="rounded-md"] border-radius: var(--ui-radius-sm); background-color: rgb(var(--ui-subtle)); color: rgb(var(--ui-ink-soft)); - font-family: var(--ui-font-mono); - font-size: 11px; + font-size: 12px; } -/* the events' filters: wrapping labels with the label text as the field label above the 34px control (.input-label: the - mono label at 600); search and reset in the same row are the form's 34px buttons (.btn-md metrics; upstream btn-sm - 26px sat against 34px fields) */ +/* the events' filters: wrapping labels with the label text as the field label above the 34px control (upstream's + text-xs label: the label, 12/16/500, sentence case); search and reset in the same row are the form's 34px buttons + (.btn-md metrics; upstream btn-sm 26px sat against 34px fields) */ html.flat-theme [data-ui~="prompt-audit-page"] form[class~="grid"] > label[class~="text-xs"] > span:first-child { - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); - font-weight: 600; + font-weight: var(--ui-label-weight); line-height: var(--ui-label-leading); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; color: rgb(var(--ui-label)); } html.flat-theme [data-ui~="prompt-audit-page"] form[class~="grid"] > div[class~="items-end"] > .btn { min-height: var(--ui-select-h); padding: 0.390625rem 0.875rem; - font-size: 0.8125rem; - line-height: 1.21875rem; + font-size: 0.875rem; + line-height: 1.25rem; } html.flat-theme [data-ui~="prompt-audit-page"] div[class~="overflow-x-auto"][class~="rounded-xl"][class~="border"] { diff --git a/frontend/src/styles/console/pages/settings-ops-public.css b/frontend/src/styles/console/pages/settings-ops-public.css index 0ae4b481c77b..e5bdbf15e030 100644 --- a/frontend/src/styles/console/pages/settings-ops-public.css +++ b/frontend/src/styles/console/pages/settings-ops-public.css @@ -3,7 +3,7 @@ /* public: sign-in, sign-up and password pages (AuthLayout), home, legal documents, 404 and key usage. * Owner: wave 1 A6. Source: the reference console's /signin (artifacts/tmp/ui-el/ref/public/REF-PUBLIC.md §1) for * the auth pages; the marketing site's structure (§2: top bar, hairline lattice, plain page) drawn with the console - * tokens and Geist for home, legal, 404 and key usage. No brand element, logo or copy of theirs. + * tokens and the system font for home, legal, 404 and key usage. No brand element, logo or copy of theirs. * * Template hooks (upstream templates otherwise unchanged): * components/layout/AuthLayout.vue:2 data-ui="auth-layout" (page root) @@ -20,37 +20,15 @@ * renders after AuthLayout, the agreement dialog is the z-[140] layer in #overlay-root (unique to LoginAgreementPrompt). */ html.flat-theme { - /* the reference's small mono text: field labels and text buttons 10/15 (Chinese 11/16, as the mono label) */ - --pub-mono-size: 10px; - --pub-mono-leading: 15px; - /* Text on the sign-in background. The pixel grid can sit behind it (its faintest step is #f8f8f8), where the - mono-label grey #737373 reads 4.47:1; muted #6c6c6c reads 4.84:1 there. Dark: the label grey holds (6.5:1). */ - --pub-label: var(--ui-muted); + /* the small captions (the site subtitle, the text buttons, the mode link row, the copyright, "OR", the kickers): + 12/16 (upstream's text-xs), sentence case, normal tracking; on the sign-in background (the pixel grid can sit + behind them, its faintest step #f8f8f8) they are muted, 5.21:1 there */ + --pub-caption-size: 12px; + --pub-caption-leading: 16px; /* The outline of the sign-in fields: the reference's structural line (#ededed). Each field is identified by its label above and its placeholder inside (the console fields' rule: A2 recipe + behaviour B3.2, which still draws the 3:1 form-control line on an empty field without a visible placeholder); focus draws the accent edge. */ --pub-field-line: var(--ui-line); - /* Tracking of the mono captions: the reference's values for Latin capitals (field labels 0.12em; "OR" and the text - buttons 0.15em; small meta lines 0.06-0.08em). Captions that carry interface text take the CJK label tracking in - the Chinese interface (tokens.css), where the Latin values read as spaced-out characters. Captions that carry the - site's own name, subtitle or the fixed English copyright keep their literal values. */ - --pub-track-label: 0.12em; - --pub-track-caps: 0.15em; - --pub-track-meta: 0.08em; - --pub-track-hint: 0.06em; -} - -html.flat-theme:lang(zh) { - --pub-mono-size: 11px; - --pub-mono-leading: 16px; - --pub-track-label: var(--ui-label-tracking); - --pub-track-caps: var(--ui-label-tracking); - --pub-track-meta: var(--ui-label-tracking); - --pub-track-hint: var(--ui-label-tracking); -} - -html.flat-theme.dark { - --pub-label: var(--ui-label); } /* ------------------------------------------------------------------ auth · page and background */ @@ -88,8 +66,8 @@ html.flat-theme [data-ui="auth-column"] { padding: 0 24px; } -/* brand: a 44px accent tile (radius 12; palette C marks) with the site logo, then the site name as a mono wordmark; - the site subtitle sits under them as a mono caption */ +/* brand: a 44px accent tile (radius 12; palette C marks) with the site logo, then the site name as a quiet wordmark + (16/24/600, muted); the site subtitle sits under them as a caption */ html.flat-theme [data-ui="auth-column"] > div[class~="mb-8"] { display: flex; flex-wrap: wrap; @@ -113,9 +91,7 @@ html.flat-theme [data-ui="auth-column"] > div[class~="mb-8"] > h1[class~="text-g min-width: 0; margin: 0; overflow-wrap: anywhere; - font: 600 15px/22.5px var(--ui-font-mono); - letter-spacing: 0.18em; - text-transform: uppercase; + font: 600 16px/24px var(--ui-font); text-align: left; color: rgb(var(--ui-muted)); -webkit-text-fill-color: currentColor; @@ -125,9 +101,7 @@ html.flat-theme [data-ui="auth-column"] > div[class~="mb-8"] > h1[class~="text-g html.flat-theme [data-ui="auth-column"] > div[class~="mb-8"] > p { flex-basis: 100%; margin-top: 12px; - font: 400 var(--pub-mono-size) / var(--pub-mono-leading) var(--ui-font-mono); - letter-spacing: 0.12em; - text-transform: uppercase; + font: 400 var(--pub-caption-size) / var(--pub-caption-leading) var(--ui-font); color: rgb(var(--ui-muted)); } @@ -144,9 +118,7 @@ html.flat-theme [data-ui="auth-column"] > [class~="card-glass"] { /* below the column: the mode link row (register / sign-in) and the copyright line */ html.flat-theme [data-ui="auth-column"] > div[class~="mt-6"] { margin-top: 22px; - font: 400 var(--pub-mono-size) / var(--pub-mono-leading) var(--ui-font-mono); - letter-spacing: var(--pub-track-caps); - text-transform: uppercase; + font: 400 var(--pub-caption-size) / var(--pub-caption-leading) var(--ui-font); color: rgb(var(--ui-muted)); } @@ -169,16 +141,13 @@ html.flat-theme [data-ui="auth-column"] > div[class~="mt-6"] :is(a, button):hove html.flat-theme [data-ui="auth-column"] > div[class~="mt-8"] { margin-top: 40px; - font: 400 var(--pub-mono-size) / var(--pub-mono-leading) var(--ui-font-mono); - letter-spacing: 0.12em; - text-transform: uppercase; + font: 400 var(--pub-caption-size) / var(--pub-caption-leading) var(--ui-font); color: rgb(var(--ui-muted)); } /* ------------------------------------------------------------------ auth · title and rhythm */ html.flat-theme [data-ui="auth-column"] [class~="card-glass"] > [class~="space-y-6"] > div[class~="text-center"]:first-child > h2 { font: 600 28px/42px var(--ui-font); - letter-spacing: -0.025em; color: rgb(var(--ui-ink)); } @@ -219,20 +188,20 @@ html.flat-theme [data-ui="auth-column"] div[class~="space-y-3"] > div[class~="ga } /* ------------------------------------------------------------------ auth · fields */ +/* the field label is the console's (upstream's .input-label: 14/20/500 ink-soft, sentence case); its hint muted */ html.flat-theme [data-ui="auth-column"] label.input-label { display: block; margin-bottom: 8px; - font: 600 var(--pub-mono-size) / var(--pub-mono-leading) var(--ui-font-mono); - letter-spacing: var(--pub-track-label); - text-transform: uppercase; + font: 500 14px/20px var(--ui-font); + letter-spacing: normal; + text-transform: none; text-align: left; - color: rgb(var(--pub-label)); + color: rgb(var(--ui-ink-soft)); } html.flat-theme [data-ui="auth-column"] label.input-label > span { margin-left: 6px; font-weight: 400; - letter-spacing: var(--pub-track-hint); color: rgb(var(--ui-muted)); } @@ -334,15 +303,15 @@ html.flat-theme [data-ui="auth-column"] div[class~="relative"] > button[class~=" /* hints under a field (password rule, code hint) */ html.flat-theme [data-ui="auth-column"] .input-hint { margin-top: 6px; - font: 400 12px/18px var(--ui-font); + font: 400 12px/16px var(--ui-font); text-align: left; color: rgb(var(--ui-muted)); } -/* the verification code: mono digits in the standard 42px field */ +/* the verification code: digits in the mono stack at upstream's text-xl (its tracking-[0.5em] applies) in the + standard 42px field */ html.flat-theme [data-ui="auth-column"] input#code.input { - font: 500 15px/20px var(--ui-font-mono); - letter-spacing: 0.3em; + font: 500 20px/20px var(--ui-font-mono); text-align: left; } @@ -360,7 +329,6 @@ html.flat-theme [data-ui="auth-column"] :is(button, a).btn.btn-primary { background: rgb(var(--ui-solid)); color: rgb(var(--ui-on-solid)); font: 600 14px/20px var(--ui-font); - letter-spacing: -0.025em; box-shadow: none; transform: none; transition: background-color var(--ui-duration) var(--ui-ease), opacity var(--ui-duration) var(--ui-ease); @@ -449,20 +417,18 @@ html.flat-theme [data-ui="auth-column"] .btn:active { transform: none; } -/* "OR": hairlines and the mono word between them */ +/* "OR": hairlines and the word between them (a caption) */ html.flat-theme [data-ui="auth-column"] div[class~="h-px"][class~="flex-1"] { background-color: rgb(var(--ui-line)); } html.flat-theme [data-ui="auth-column"] div[class~="h-px"][class~="flex-1"] + span { - font: 600 11px/16.5px var(--ui-font-mono); - letter-spacing: var(--pub-track-caps); - text-transform: uppercase; + font: 600 var(--pub-caption-size) / var(--pub-caption-leading) var(--ui-font); color: rgb(var(--ui-muted)); } -/* text buttons: forgot password, back to sign-in, request a new link, resend code, back to sign-up (mono, - uppercase, muted; ink on hover) */ +/* text buttons: forgot password, back to sign-in, request a new link, resend code, back to sign-up (captions, + muted; ink on hover) */ html.flat-theme [data-ui="auth-column"] div[class~="mt-1"][class~="justify-between"] { margin-top: 10px; } @@ -482,9 +448,7 @@ html.flat-theme [data-ui="auth-column"] > div[class~="mt-6"] > button { align-items: center; gap: 6px; margin-left: 0; - font: 400 var(--pub-mono-size) / var(--pub-mono-leading) var(--ui-font-mono); - letter-spacing: var(--pub-track-caps); - text-transform: uppercase; + font: 400 var(--pub-caption-size) / var(--pub-caption-leading) var(--ui-font); text-decoration: none; color: rgb(var(--ui-muted)); transition: color var(--ui-duration) var(--ui-ease); @@ -505,7 +469,7 @@ html.flat-theme [data-ui="auth-column"] > div[class~="mt-6"] > button svg { } /* ------------------------------------------------------------------ auth · notices and states */ -/* agreement, checkbox mode: native 14px box in the accent, 12.5px line, document links in ink, underlined */ +/* agreement, checkbox mode: native 14px box in the accent, upstream's 13px line, document links in ink, underlined */ html.flat-theme [data-ui="auth-column"] #login-agreement-consent { width: 14px; height: 14px; @@ -513,7 +477,6 @@ html.flat-theme [data-ui="auth-column"] #login-agreement-consent { } html.flat-theme [data-ui="auth-column"] #login-agreement-consent + div > p { - font: 400 12.5px/18px var(--ui-font); color: rgb(var(--ui-muted)); } @@ -534,7 +497,7 @@ html.flat-theme [data-ui="auth-column"] #login-agreement-consent + div a:hover { text-decoration-color: currentColor; } -/* agreement not accepted yet (modal mode): the reference's notice box (subtle fill, hairline, 13px ink) with a +/* agreement not accepted yet (modal mode): the reference's notice box (subtle fill, hairline, 14px ink) with a small default button */ html.flat-theme [data-ui="auth-column"] div[class~="border-primary-100"][class~="p-3"] { padding: 10px 12px 10px 16px; @@ -542,8 +505,8 @@ html.flat-theme [data-ui="auth-column"] div[class~="border-primary-100"][class~= border-radius: var(--ui-radius-md); background: rgb(var(--ui-subtle)); color: rgb(var(--ui-ink)); - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; } html.flat-theme [data-ui="auth-column"] div[class~="border-primary-100"][class~="p-3"] p[class~="font-medium"] { @@ -584,13 +547,13 @@ html.flat-theme [data-ui="auth-column"] div[class~="border-amber-200"][class~="p border: 1px solid rgb(var(--ui-warning) / 0.3); border-radius: var(--ui-radius-md); background: rgb(var(--ui-warning-soft)); - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; } html.flat-theme [data-ui="auth-column"] div[class~="border-amber-200"][class~="p-4"] :is(p, div[class~="text-sm"]) { - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; color: rgb(var(--ui-warning)); } @@ -608,14 +571,14 @@ html.flat-theme [data-ui="auth-column"] div[class~="bg-green-50"][class~="px-3"] border: 1px solid rgb(var(--ui-success) / 0.25); border-radius: var(--ui-radius-md); background: rgb(var(--ui-success-soft)); - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; } html.flat-theme [data-ui="auth-column"] div[class~="border-green-200"][class~="p-4"] p, html.flat-theme [data-ui="auth-column"] div[class~="bg-green-50"][class~="px-3"][class~="py-2"] span { - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; color: rgb(var(--ui-success)); } @@ -667,13 +630,13 @@ html.flat-theme [data-ui="auth-column"] div[class~="rounded-xl"][class~="p-6"][c } html.flat-theme [data-ui="auth-column"] div[class~="rounded-xl"][class~="p-6"] h3 { - font: 600 15px/22px var(--ui-font); + font: 600 18px/28px var(--ui-font); color: rgb(var(--ui-ink)); } html.flat-theme [data-ui="auth-column"] div[class~="rounded-xl"][class~="p-6"] h3 + p { margin-top: 4px; - font: 400 13px/19.5px var(--ui-font); + font: 400 14px/20px var(--ui-font); color: rgb(var(--ui-muted)); } @@ -712,19 +675,18 @@ html.flat-theme [data-ui="auth-layout"] ~ div[class~="fixed"][class~="z-50"] div html.flat-theme [data-ui="auth-layout"] ~ div[class~="fixed"][class~="z-50"] div[class~="max-w-md"] h3 { margin-top: 16px; font: 600 19px/26px var(--ui-font); - letter-spacing: -0.025em; color: rgb(var(--ui-ink)); } html.flat-theme [data-ui="auth-layout"] ~ div[class~="fixed"][class~="z-50"] div[class~="max-w-md"] h3 + p { margin-top: 6px; - font: 400 13px/19.5px var(--ui-font); + font: 400 14px/20px var(--ui-font); color: rgb(var(--ui-muted)); } html.flat-theme [data-ui="auth-layout"] ~ div[class~="fixed"][class~="z-50"] div[class~="max-w-md"] h3 + p + p { margin-top: 4px; - font: 500 12.5px/18px var(--ui-font-mono); + font: 500 14px/20px var(--ui-font); color: rgb(var(--ui-ink)); } @@ -735,7 +697,7 @@ html.flat-theme [data-ui="auth-layout"] ~ div[class~="fixed"][class~="z-50"] div border-radius: var(--ui-radius-md); background: rgb(var(--ui-canvas)); color: rgb(var(--ui-ink)); - font: 500 18px/24px var(--ui-font-mono); + font: 500 18px/24px var(--ui-font); box-shadow: none; transition: border-color var(--ui-duration) var(--ui-ease), box-shadow var(--ui-duration) var(--ui-ease); } @@ -812,7 +774,6 @@ html.flat-theme body > :where(#overlay-root) > div[class~="z-[140]"] > div > div html.flat-theme body > :where(#overlay-root) > div[class~="z-[140]"] h2 { font: 600 17px/25.5px var(--ui-font); - letter-spacing: -0.025em; color: rgb(var(--ui-ink)); } @@ -821,13 +782,12 @@ html.flat-theme body > :where(#overlay-root) > div[class~="z-[140]"] h2 + span { border-radius: var(--ui-radius-sm); background: rgb(var(--ui-subtle)); color: rgb(var(--ui-muted)); - font: 400 10.5px/15.75px var(--ui-font-mono); - letter-spacing: 0.025em; + font: 400 12px/16px var(--ui-font); } html.flat-theme body > :where(#overlay-root) > div[class~="z-[140]"] > div > div:first-child p { margin-top: 4px; - font: 400 13px/19.5px var(--ui-font); + font: 400 14px/20px var(--ui-font); color: rgb(var(--ui-muted)); } @@ -836,9 +796,9 @@ html.flat-theme body > :where(#overlay-root) > div[class~="z-[140]"] > div > div } html.flat-theme body > :where(#overlay-root) > div[class~="z-[140]"] > div > div[class~="max-h-[58vh]"] > div:first-child > p { - font: 500 var(--pub-mono-size) / var(--pub-mono-leading) var(--ui-font-mono); - letter-spacing: var(--pub-track-label); - text-transform: uppercase; + font: var(--ui-label-font); + letter-spacing: var(--ui-label-tracking); + text-transform: none; color: rgb(var(--ui-label)); } @@ -877,7 +837,7 @@ html.flat-theme body > :where(#overlay-root) > div[class~="z-[140]"] a[class~="m } html.flat-theme body > :where(#overlay-root) > div[class~="z-[140]"] a[class~="min-h-[72px]"] > span:nth-child(2) > span { - font: 600 13px/19.5px var(--ui-font); + font: 600 14px/20px var(--ui-font); color: rgb(var(--ui-ink)); } @@ -943,7 +903,7 @@ html.flat-theme body > :where(#overlay-root) > div[class~="z-[140]"] :is(a, butt /* ------------------------------------------------------------------ public pages · shared structure */ /* Home, legal documents and key usage share the marketing site's top bar (sticky, 64px, a hairline under it, the canvas at 92 % with a 10px blur), its 1240px content width with a clamp(16px, 3vw, 32px) gutter and its footer - (a hairline, mono labels); drawn with the console tokens and Geist. */ + (a hairline, captions); drawn with the console tokens and the system font. */ html.flat-theme :is([data-ui="home"], [data-ui="legal"], [data-ui="key-usage"], [data-ui="not-found"]) { --pub-gutter: clamp(16px, 3vw, 32px); --pub-width: 1240px; @@ -970,7 +930,7 @@ html.flat-theme :is([data-ui="home"], [data-ui="legal"], [data-ui="key-usage"]) gap: 12px; } -/* brand: the site logo at 32px (radius 8) and, where the page prints it, the site name as a mono wordmark */ +/* brand: the site logo at 32px (radius 8) and, where the page prints it, the site name (14/20/500 ink) */ html.flat-theme :is([data-ui="home"], [data-ui="key-usage"]) > header > nav > :first-child div[class~="h-10"], html.flat-theme [data-ui="legal"] > header > div > a > span[class~="h-10"] { width: 32px; @@ -987,9 +947,7 @@ html.flat-theme [data-ui="legal"] > header > div > a:first-child { html.flat-theme [data-ui="key-usage"] > header > nav > a > span, html.flat-theme [data-ui="legal"] > header > div > a:first-child > span:not([class~="h-10"]) { - font: 500 13px/20px var(--ui-font-mono); - letter-spacing: 0.08em; - text-transform: uppercase; + font: 500 14px/20px var(--ui-font); color: rgb(var(--ui-ink)); } @@ -1008,7 +966,7 @@ html.flat-theme :is([data-ui="home"], [data-ui="key-usage"]) > header > nav > di gap: 6px; border-radius: var(--ui-radius-md); color: rgb(var(--ui-muted)); - font: 500 13px/20px var(--ui-font); + font: 500 14px/20px var(--ui-font); transition: color var(--ui-duration) var(--ui-ease), background-color var(--ui-duration) var(--ui-ease); } @@ -1028,8 +986,7 @@ html.flat-theme :is([data-ui="home"], [data-ui="key-usage"]) > header > nav > di gap: 6px; border-radius: var(--ui-radius-md); color: rgb(var(--ui-muted)); - font: 500 12.5px/18px var(--ui-font-mono); - letter-spacing: 0.04em; + font: 500 14px/20px var(--ui-font); transition: color var(--ui-duration) var(--ui-ease), background-color var(--ui-duration) var(--ui-ease); } @@ -1039,10 +996,10 @@ html.flat-theme :is([data-ui="home"], [data-ui="key-usage"]) > header > nav > di } html.flat-theme :is([data-ui="home"], [data-ui="key-usage"]) > header > nav > div:last-child > div[class~="relative"] > button > span[class~="text-base"] { - font-size: 12.5px; + font-size: 14px; } -/* sign in / console: the console's small solid primary (30px, radius 6, 13 / 600; palette C) */ +/* sign in / console: the console's small solid primary (30px, radius 6, the .btn type 14/20/500; palette C) */ html.flat-theme [data-ui="home"] > header > nav > div:last-child > a[class~="bg-gray-900"], html.flat-theme [data-ui="legal"] > header > div > a[class~="bg-primary-600"] { display: inline-flex; @@ -1055,7 +1012,7 @@ html.flat-theme [data-ui="legal"] > header > div > a[class~="bg-primary-600"] { border-radius: var(--ui-radius-md); background: rgb(var(--ui-solid)); color: rgb(var(--ui-on-solid)); - font: 600 13px/19.5px var(--ui-font); + font: 500 14px/20px var(--ui-font); box-shadow: none; transition: background-color var(--ui-duration) var(--ui-ease); } @@ -1067,9 +1024,9 @@ html.flat-theme [data-ui="legal"] > header > div > a[class~="bg-primary-600"]:ho } html.flat-theme [data-ui="home"] > header > nav > div:last-child > a[class~="bg-gray-900"] > span:not([class~="rounded-full"]) { - font-size: 13px; - line-height: 19.5px; - font-weight: 600; + font-size: 14px; + line-height: 20px; + font-weight: 500; color: rgb(var(--ui-on-solid)); } @@ -1079,7 +1036,8 @@ html.flat-theme [data-ui="home"] > header > nav > div:last-child > a[class~="bg- height: 18px; background: rgb(var(--ui-on-solid) / 0.18); color: rgb(var(--ui-on-solid)); - font: 600 10px/18px var(--ui-font-mono); + font-weight: 600; + line-height: 18px; } html.flat-theme [data-ui="home"] > header > nav > div:last-child > a[class~="bg-gray-900"] > svg { @@ -1093,7 +1051,7 @@ html.flat-theme :is([data-ui="home"], [data-ui="legal"], [data-ui="key-usage"], outline-offset: 2px; } -/* footer: a hairline, the copyright as a mono label on the left, links on the right */ +/* footer: a hairline, the copyright as a caption on the left, links on the right */ html.flat-theme :is([data-ui="home"], [data-ui="key-usage"]) > footer { padding: 0; border-top: 1px solid rgb(var(--ui-line)); @@ -1108,14 +1066,12 @@ html.flat-theme :is([data-ui="home"], [data-ui="key-usage"]) > footer > div { } html.flat-theme :is([data-ui="home"], [data-ui="key-usage"]) > footer p { - font: 400 12px/18px var(--ui-font-mono); - letter-spacing: 0.06em; - text-transform: uppercase; + font: 400 var(--pub-caption-size) / var(--pub-caption-leading) var(--ui-font); color: rgb(var(--ui-muted)); } html.flat-theme :is([data-ui="home"], [data-ui="key-usage"]) > footer a { - font: 400 13px/20px var(--ui-font); + font: 400 14px/20px var(--ui-font); color: rgb(var(--ui-muted)); transition: color var(--ui-duration) var(--ui-ease); } @@ -1173,7 +1129,6 @@ html.flat-theme [data-ui="home"] > main > div > div:first-child { html.flat-theme [data-ui="home"] > main > div > div:first-child h1 { margin-bottom: 20px; font: 500 clamp(40px, 4.4vw, 56px) / 1.05 var(--ui-font); - letter-spacing: -0.035em; color: rgb(var(--ui-ink)); } @@ -1215,8 +1170,8 @@ html.flat-theme [data-ui="home"] > main > div > div:first-child .btn.btn-primary margin: 0; } -/* the terminal: a console code block (surface, hairline, radius 8, a subtle tab bar with a mono label), code in - Geist Mono 12.5 in the reference's syntax roles; no tilt, glow or traffic-light colours */ +/* the terminal: a console code block (surface, hairline, radius 8, a subtle tab bar with its title), code in the + mono stack at upstream's 14px in the reference's syntax roles; no tilt, glow or traffic-light colours */ html.flat-theme [data-ui="home"] .terminal-window { border: 1px solid rgb(var(--ui-line)); border-radius: var(--ui-radius-lg); @@ -1246,18 +1201,18 @@ html.flat-theme [data-ui="home"] .terminal-buttons span { background: rgb(var(--ui-line-strong)); } -/* the tab bar is the grouped fill (#f5f5f5), where the label grey reads 4.35:1: muted (4.74:1) */ +/* on the tab bar's grouped fill the title is muted (5.03:1) */ html.flat-theme [data-ui="home"] .terminal-title { margin-right: 38px; font: 400 var(--ui-label-size) / var(--ui-label-leading) var(--ui-font-mono); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; color: rgb(var(--ui-muted)); } html.flat-theme [data-ui="home"] .terminal-body { padding: 18px 20px 20px; - font: 400 12.5px/24px var(--ui-font-mono); + font: 400 14px/24px var(--ui-font-mono); color: rgb(var(--ui-ink)); } @@ -1290,8 +1245,7 @@ html.flat-theme [data-ui="home"] .code-success { border-radius: var(--ui-radius-sm); background: rgb(var(--ui-success-soft)); color: rgb(var(--ui-success)); - font: 600 11px/16px var(--ui-font-mono); - letter-spacing: 0.025em; + font: 600 12px/16px var(--ui-font-mono); } html.flat-theme [data-ui="home"] .cursor { @@ -1300,7 +1254,7 @@ html.flat-theme [data-ui="home"] .cursor { background: rgb(var(--ui-ink)); } -/* the three highlights under the hero: mono chips on the content edge */ +/* the three highlights under the hero: chips on the content edge */ html.flat-theme [data-ui="home"] > main > div > div:nth-child(2) { margin-bottom: 0; padding: 0 var(--pub-gutter) clamp(56px, 9vh, 88px); @@ -1332,9 +1286,7 @@ html.flat-theme [data-ui="home"] > main > div > div:nth-child(2) > div > svg { } html.flat-theme [data-ui="home"] > main > div > div:nth-child(2) > div > span { - font: 500 11px/16px var(--ui-font-mono); - letter-spacing: var(--pub-track-hint); - text-transform: uppercase; + font: 500 12px/16px var(--ui-font); color: rgb(var(--ui-ink-soft)); } @@ -1417,7 +1369,6 @@ html.flat-theme [data-ui="home"] > main > div > div[class~="md:grid-cols-3"] > d html.flat-theme [data-ui="home"] > main > div > div[class~="md:grid-cols-3"] h3 { margin-bottom: 10px; font: 500 20px/26px var(--ui-font); - letter-spacing: -0.02em; color: rgb(var(--ui-ink)); } @@ -1437,12 +1388,11 @@ html.flat-theme [data-ui="home"] > main > div > div[class~="mb-8"][class~="text- html.flat-theme [data-ui="home"] > main > div > div[class~="mb-8"][class~="text-center"] h2 { margin-bottom: 12px; font: 500 clamp(28px, 2.6vw, 36px) / 1.15 var(--ui-font); - letter-spacing: -0.025em; color: rgb(var(--ui-ink)); } html.flat-theme [data-ui="home"] > main > div > div[class~="mb-8"][class~="text-center"] p { - font: 400 15px/24px var(--ui-font); + font: 400 14px/24px var(--ui-font); color: rgb(var(--ui-muted)); } @@ -1480,12 +1430,12 @@ html.flat-theme [data-ui="home"] > main > div > div[class~="mb-16"] > div > div } html.flat-theme [data-ui="home"] > main > div > div[class~="mb-16"] > div > div > span { - font: 600 11px/16px var(--ui-font-mono); + font: 600 12px/16px var(--ui-font); color: rgb(var(--ui-ink-soft)); } html.flat-theme [data-ui="home"] > main > div > div[class~="mb-16"] > div > span:not([class~="rounded"]) { - font: 500 13px/19.5px var(--ui-font); + font: 500 14px/20px var(--ui-font); color: rgb(var(--ui-ink)); } @@ -1495,9 +1445,8 @@ html.flat-theme [data-ui="home"] > main > div > div[class~="mb-16"] > div > span border-radius: 9999px; background: rgb(var(--ui-primary-soft)); color: var(--ui-link); - font: 500 9.5px/14.25px var(--ui-font-mono); - letter-spacing: 0.025em; - text-transform: uppercase; + font-weight: 500; + line-height: 14px; } html.flat-theme [data-ui="home"] > main > div > div[class~="mb-16"] > div > span[class~="rounded"][class~="bg-gray-100"] { @@ -1556,9 +1505,7 @@ html.flat-theme [data-testid="compact-home"] > header > nav > div:first-child > } html.flat-theme [data-testid="compact-home"] > header > nav > div:first-child > span { - font: 500 13px/20px var(--ui-font-mono); - letter-spacing: 0.08em; - text-transform: uppercase; + font: 500 14px/20px var(--ui-font); color: rgb(var(--ui-ink)); } @@ -1572,7 +1519,7 @@ html.flat-theme [data-testid="compact-home"] > header > nav > div:last-child > : padding: 0 8px; border-radius: var(--ui-radius-md); color: rgb(var(--ui-muted)); - font: 500 13px/20px var(--ui-font); + font: 500 14px/20px var(--ui-font); transition: color var(--ui-duration) var(--ui-ease), background-color var(--ui-duration) var(--ui-ease); } @@ -1591,7 +1538,7 @@ html.flat-theme [data-testid="compact-home"] > header > nav > div:last-child > d padding: 0 8px; border-radius: var(--ui-radius-md); color: rgb(var(--ui-muted)); - font: 500 12.5px/18px var(--ui-font-mono); + font: 500 14px/20px var(--ui-font); } html.flat-theme [data-testid="compact-home"] > header > nav > div:last-child > div[class~="relative"] > button:hover { @@ -1607,7 +1554,7 @@ html.flat-theme [data-testid="compact-home"] :is(header a[class~="bg-gray-900"], border-radius: var(--ui-radius-md); background: rgb(var(--ui-solid)); color: rgb(var(--ui-on-solid)); - font: 600 13px/19.5px var(--ui-font); + font: 500 14px/20px var(--ui-font); transition: background-color var(--ui-duration) var(--ui-ease); } @@ -1633,7 +1580,6 @@ html.flat-theme [data-testid="compact-home"] > main > div > img { html.flat-theme [data-testid="compact-home"] > main h1 { font: 500 clamp(36px, 4vw, 48px) / 1.1 var(--ui-font); - letter-spacing: -0.03em; color: rgb(var(--ui-ink)); } @@ -1646,9 +1592,7 @@ html.flat-theme [data-testid="compact-home"] > main h1 + p { html.flat-theme [data-testid="compact-home"] > footer { padding: 20px clamp(16px, 3vw, 32px); border-top: 1px solid rgb(var(--ui-line)); - font: 400 12px/18px var(--ui-font-mono); - letter-spacing: 0.06em; - text-transform: uppercase; + font: 400 var(--pub-caption-size) / var(--pub-caption-leading) var(--ui-font); color: rgb(var(--ui-muted)); } @@ -1658,8 +1602,8 @@ html.flat-theme [data-testid="compact-home"] :is(a, button):focus-visible { } /* ------------------------------------------------------------------ legal documents */ -/* the marketing site's plain page: a 760px column, a mono kicker (the document type), a 44 / 500 title, a mono meta - line, 14.5 / 1.75 body text; the type's glyph tile is decoration and goes */ +/* the marketing site's plain page: a 760px column, a kicker (the document type), a 44 / 500 title, a meta line, + 14.5 / 1.75 body text; the type's glyph tile is decoration and goes */ html.flat-theme [data-ui="legal"] > main { max-width: calc(760px + 2 * var(--pub-gutter)); padding: clamp(48px, 9vh, 96px) var(--pub-gutter) clamp(72px, 10vh, 112px); @@ -1676,22 +1620,19 @@ html.flat-theme [data-ui="legal"] > main > article > div:first-child > div > spa } html.flat-theme [data-ui="legal"] > main > article > div:first-child p:first-child { - font: 400 12px/18px var(--ui-font-mono); - letter-spacing: var(--pub-track-meta); - text-transform: uppercase; + font: 400 var(--pub-caption-size) / var(--pub-caption-leading) var(--ui-font); color: rgb(var(--ui-muted)); } html.flat-theme [data-ui="legal"] > main > article > div:first-child h1 { margin-top: 14px; font: 500 clamp(30px, 3.4vw, 44px) / 1.1 var(--ui-font); - letter-spacing: -0.03em; color: rgb(var(--ui-ink)); } html.flat-theme [data-ui="legal"] > main > article > div:first-child h1 + p { margin-top: 16px; - font: 400 13px/20px var(--ui-font-mono); + font: 400 14px/20px var(--ui-font); color: rgb(var(--ui-muted)); } @@ -1718,14 +1659,12 @@ html.flat-theme [data-ui="legal"] .legal-document-content h1 { padding-bottom: 0; border-bottom: 0; font: 500 24px/32px var(--ui-font); - letter-spacing: -0.02em; color: rgb(var(--ui-ink)); } html.flat-theme [data-ui="legal"] .legal-document-content h2 { margin: 40px 0 12px; font: 600 17px/25.5px var(--ui-font); - letter-spacing: -0.025em; color: rgb(var(--ui-ink)); } @@ -1772,7 +1711,7 @@ html.flat-theme [data-ui="legal"] .legal-document-content code { padding: 0; border-radius: 0; background: none; - font: 400 12.5px/1.6 var(--ui-font-mono); + font: 400 14px/1.6 var(--ui-font-mono); color: rgb(var(--ui-ink)); } @@ -1783,14 +1722,14 @@ html.flat-theme [data-ui="legal"] .legal-document-content pre { border-radius: var(--ui-radius-lg); background: rgb(var(--ui-surface)); color: rgb(var(--ui-ink)); - font: 400 12.5px/1.625 var(--ui-font-mono); + font: 400 14px/1.625 var(--ui-font-mono); } html.flat-theme [data-ui="legal"] .legal-document-content table { margin: 16px 0; border: 1px solid rgb(var(--ui-line)); border-radius: var(--ui-radius-lg); - font-size: 12.5px; + font-size: 14px; } html.flat-theme [data-ui="legal"] .legal-document-content th { @@ -1823,7 +1762,7 @@ html.flat-theme [data-ui="legal"] article > div[class~="border-dashed"] { border: 1px dashed rgb(var(--ui-line-strong)); border-radius: var(--ui-radius-lg); background: transparent; - font: 400 13px/19.5px var(--ui-font); + font: 400 14px/20px var(--ui-font); color: rgb(var(--ui-muted)); } @@ -1833,11 +1772,11 @@ html.flat-theme [data-ui="legal"] > main > section { } html.flat-theme [data-ui="legal"] > main > section h1 { - font: 600 15px/22px var(--ui-font); + font: 600 18px/28px var(--ui-font); } html.flat-theme [data-ui="legal"] > main > section p { - font: 400 13px/19.5px var(--ui-font); + font: 400 14px/20px var(--ui-font); } html.flat-theme [data-ui="legal"] > main > section span[class~="h-10"] { @@ -1857,7 +1796,7 @@ html.flat-theme [data-ui="legal"] > main > div > div[class~="animate-spin"] { } /* ------------------------------------------------------------------ 404 */ -/* the marketing site's 404: a plain left-aligned column (a mono kicker "404", a 44 / 500 title, one muted line, the +/* the marketing site's 404: a plain left-aligned column (a kicker "404", a 44 / 500 title, one muted line, the actions), no bar or footer; the giant watermark, the glowing tile and the orbs go */ html.flat-theme [data-ui="not-found"] { padding: clamp(48px, 10vh, 120px) var(--pub-gutter); @@ -1881,8 +1820,7 @@ html.flat-theme [data-ui="not-found"] > div[class~="z-10"] > div:first-child > d } html.flat-theme [data-ui="not-found"] > div[class~="z-10"] > div:first-child span { - font: 400 12px/18px var(--ui-font-mono); - letter-spacing: 0.08em; + font: 400 var(--pub-caption-size) / var(--pub-caption-leading) var(--ui-font); color: rgb(var(--ui-muted)); } @@ -1897,12 +1835,11 @@ html.flat-theme [data-ui="not-found"] > div[class~="z-10"] > div:nth-child(2) { html.flat-theme [data-ui="not-found"] h1 { margin-bottom: 16px; font: 500 clamp(30px, 3.4vw, 44px) / 1.1 var(--ui-font); - letter-spacing: -0.03em; color: rgb(var(--ui-ink)); } html.flat-theme [data-ui="not-found"] h1 + p { - font: 400 15px/24px var(--ui-font); + font: 400 16px/24px var(--ui-font); color: rgb(var(--ui-muted)); } @@ -1956,7 +1893,7 @@ html.flat-theme [data-ui="not-found"] .btn.btn-secondary svg { html.flat-theme [data-ui="not-found"] > div[class~="z-10"] > p:last-child { margin-top: 28px; - font: 400 13px/19.5px var(--ui-font); + font: 400 14px/20px var(--ui-font); color: rgb(var(--ui-muted)); } @@ -1968,9 +1905,9 @@ html.flat-theme [data-ui="not-found"] > div[class~="z-10"] > p:last-child a { } /* ------------------------------------------------------------------ key usage */ -/* the console's language on a public page: a centred 40 / 500 title, one 42px field with its solid button, KPI-style - cards (hairline, radius 8, mono labels), hairline tables with mono numbers; ring strokes in the accent (palette C - marks) */ +/* the console's language on a public page: a centred 40 / 500 title (its template tracking-tight kept), one 42px + field with its solid button, KPI-style cards (hairline, radius 8, labels), hairline tables with tabular numbers; + ring strokes in the accent (palette C marks) */ html.flat-theme [data-ui="key-usage"] > main { padding: clamp(56px, 9vh, 96px) var(--pub-gutter) clamp(72px, 10vh, 112px); } @@ -1982,12 +1919,11 @@ html.flat-theme [data-ui="key-usage"] > main > div:first-child { html.flat-theme [data-ui="key-usage"] > main > div:first-child h1 { margin-bottom: 12px; font: 500 clamp(30px, 3.4vw, 40px) / 1.15 var(--ui-font); - letter-spacing: -0.03em; color: rgb(var(--ui-ink)); } html.flat-theme [data-ui="key-usage"] > main > div:first-child p { - font: 400 15px/24px var(--ui-font); + font: 400 16px/24px var(--ui-font); color: rgb(var(--ui-muted)); } @@ -2010,7 +1946,7 @@ html.flat-theme [data-ui="key-usage"] input.input-ring { border-radius: var(--ui-radius-md); background: rgb(var(--ui-surface)); color: rgb(var(--ui-ink)); - font: 400 13px/20px var(--ui-font-mono); + font: 400 14px/20px var(--ui-font); transition: border-color var(--ui-duration) var(--ui-ease), box-shadow var(--ui-duration) var(--ui-ease); } @@ -2075,21 +2011,21 @@ html.flat-theme [data-ui="key-usage"] > main > div:nth-child(2) > div:first-chil html.flat-theme [data-ui="key-usage"] > main > div:nth-child(2) > p { margin-top: 12px; - font: 400 12px/18px var(--ui-font); + font: 400 12px/16px var(--ui-font); color: rgb(var(--ui-muted)); } -/* the range picker after a query: a mono label, 28px range buttons (the current one a chosen option: the accent - tint, accent text and the accent edge, palette C) */ +/* the range picker after a query: a label, 28px range buttons in upstream's text-xs (the current one a chosen option: + the accent tint, accent text and the accent edge, palette C) */ html.flat-theme [data-ui="key-usage"] > main > div:nth-child(2) > div[class~="mt-4"] > div { gap: 6px; } html.flat-theme [data-ui="key-usage"] > main > div:nth-child(2) > div[class~="mt-4"] > div > span { margin-right: 4px; - font: 400 var(--ui-label-size) / var(--ui-label-leading) var(--ui-font-mono); + font: var(--ui-label-font); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; color: rgb(var(--ui-label)); } @@ -2100,7 +2036,7 @@ html.flat-theme [data-ui="key-usage"] > main > div:nth-child(2) > div[class~="mt border-radius: var(--ui-radius-md); background: rgb(var(--ui-surface)); color: rgb(var(--ui-ink-soft)); - font: 600 12.5px/18px var(--ui-font); + font: 500 12px/16px var(--ui-font); transition: border-color var(--ui-duration) var(--ui-ease), color var(--ui-duration) var(--ui-ease); } @@ -2118,7 +2054,7 @@ html.flat-theme [data-ui="key-usage"] > main > div:nth-child(2) > div[class~="mt html.flat-theme [data-ui="key-usage"] > main > div:nth-child(2) > div[class~="mt-4"] input.input-ring { height: 28px; padding: 0 8px; - font: 400 12.5px/18px var(--ui-font-mono); + font: 400 12px/16px var(--ui-font); } /* results */ @@ -2150,12 +2086,12 @@ html.flat-theme [data-ui="key-usage"] div[class~="inline-flex"][class~="rounded- } html.flat-theme [data-ui="key-usage"] div[class~="inline-flex"][class~="rounded-full"][class~="px-5"] > span:nth-child(2) { - font: 600 12.5px/18px var(--ui-font); + font: 500 14px/20px var(--ui-font); color: rgb(var(--ui-ink)); } html.flat-theme [data-ui="key-usage"] div[class~="inline-flex"][class~="rounded-full"][class~="px-5"] > span:nth-child(n + 3) { - font: 400 12.5px/18px var(--ui-font); + font: 400 14px/20px var(--ui-font); color: rgb(var(--ui-muted)); } @@ -2177,11 +2113,11 @@ html.flat-theme [data-ui="key-usage"] main div[class~="rounded-2xl"]:hover { box-shadow: none; } -/* card titles: the mono label */ +/* card titles: the label (12/16/500, sentence case) */ html.flat-theme [data-ui="key-usage"] main div[class~="rounded-2xl"] h3 { - font: 500 var(--ui-label-size) / var(--ui-label-leading) var(--ui-font-mono); + font: var(--ui-label-font); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; color: rgb(var(--ui-label)); } @@ -2196,7 +2132,7 @@ html.flat-theme [data-ui="key-usage"] main div[class~="rounded-2xl"][class~="p-8 } /* rings: the track in the grouped fill, the progress in the accent (palette C marks; the ring colours were - decoration: each ring has its title); the figures in the ring in mono */ + decoration: each ring has its title); the figures in the ring tabular */ html.flat-theme [data-ui="key-usage"] main svg[class~="w-44"] > circle:first-child { stroke: rgb(var(--ui-subtle)); } @@ -2220,33 +2156,31 @@ html.flat-theme [data-ui="key-usage"] main svg[class~="w-44"] + div { html.flat-theme [data-ui="key-usage"] main svg[class~="w-44"] + div > span[class~="text-3xl"] { font: 600 30px/36px var(--ui-font); - letter-spacing: -0.02em; color: rgb(var(--ui-ink)); } html.flat-theme [data-ui="key-usage"] main svg[class~="w-44"] + div > span[class~="text-2xl"] { - font: 600 22px/28px var(--ui-font-mono); - letter-spacing: -0.01em; + font: 600 22px/28px var(--ui-font); } html.flat-theme [data-ui="key-usage"] main svg[class~="w-44"] + div > span[class~="text-xs"] { - font: 400 var(--ui-label-size) / var(--ui-label-leading) var(--ui-font-mono); + font: var(--ui-label-font); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; color: rgb(var(--ui-label)); } html.flat-theme [data-ui="key-usage"] main svg[class~="w-44"] + div > span[class~="text-sm"] { - font: 600 13px/19.5px var(--ui-font-mono); + font: 600 14px/20px var(--ui-font); } html.flat-theme [data-ui="key-usage"] main svg[class~="w-44"] + div > p { - font: 400 11px/16.5px var(--ui-font-mono); + font: 400 12px/16px var(--ui-font); color: rgb(var(--ui-muted)); } -/* card heads with a hairline under them; detail rows: a neutral 26px glyph tile, a 13px label, the mono value - (status colours kept) */ +/* card heads with a hairline under them; detail rows: a neutral 26px glyph tile, a 14px label, the value 14/600 + tabular (status colours kept) */ html.flat-theme [data-ui="key-usage"] main div[class~="rounded-2xl"] > div[class~="px-8"][class~="py-5"] { padding: 12px 20px; border-bottom: 1px solid rgb(var(--ui-line)); @@ -2272,15 +2206,15 @@ html.flat-theme [data-ui="key-usage"] main div[class~="divide-y"] > div > div > } html.flat-theme [data-ui="key-usage"] main div[class~="divide-y"] > div > div > span { - font: 400 13px/19.5px var(--ui-font); + font: 400 14px/20px var(--ui-font); color: rgb(var(--ui-ink-soft)); } html.flat-theme [data-ui="key-usage"] main div[class~="divide-y"] > div > span { - font: 600 13px/19.5px var(--ui-font-mono); + font: 600 14px/20px var(--ui-font); } -/* token totals: already a hairline lattice upstream; mono labels and figures */ +/* token totals: already a hairline lattice upstream; labels, and figures in upstream's text-sm 600 */ html.flat-theme [data-ui="key-usage"] main div[class~="gap-px"] { background: rgb(var(--ui-line)); } @@ -2292,18 +2226,19 @@ html.flat-theme [data-ui="key-usage"] main div[class~="gap-px"] > div { html.flat-theme [data-ui="key-usage"] main div[class~="gap-px"] > div > div:first-child { margin-bottom: 4px; - font: 400 var(--ui-label-size) / var(--ui-label-leading) var(--ui-font-mono); + font: var(--ui-label-font); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; color: rgb(var(--ui-label)); } html.flat-theme [data-ui="key-usage"] main div[class~="gap-px"] > div > div:last-child { - font: 600 15px/22.5px var(--ui-font-mono); + font: 600 14px/20px var(--ui-font); color: rgb(var(--ui-ink)); } -/* the 7 / 30 / 90-day switch: the console's segmented control (the grey track, the raised pill) */ +/* the 7 / 30 / 90-day switch: the console's segmented control (the grey track, the raised pill) at upstream's + text-xs, the segment grey */ html.flat-theme [data-ui="key-usage"] main div[class~="inline-flex"][class~="rounded-lg"][class~="p-0.5"] { padding: 2px; border: 1px solid rgb(var(--ui-track)); @@ -2314,8 +2249,8 @@ html.flat-theme [data-ui="key-usage"] main div[class~="inline-flex"][class~="rou html.flat-theme [data-ui="key-usage"] main div[class~="inline-flex"][class~="rounded-lg"][class~="p-0.5"] > button { padding: 5px 12px; border-radius: var(--ui-radius-md); - color: rgb(var(--ui-muted)); - font: 500 12.5px/18px var(--ui-font); + color: rgb(var(--theme-text-gray-600)); + font: 500 12px/16px var(--ui-font); transition: color var(--ui-duration-slide) var(--ui-ease-out), background-color var(--ui-duration-slide) var(--ui-ease-out); } @@ -2327,11 +2262,12 @@ html.flat-theme [data-ui="key-usage"] main div[class~="inline-flex"][class~="rou html.flat-theme [data-ui="key-usage"] main div[class~="inline-flex"][class~="rounded-lg"][class~="p-0.5"] > button[class~="bg-primary-500"] { background: rgb(var(--ui-thumb)); color: rgb(var(--ui-ink)); - font-weight: 600; + font-weight: 500; box-shadow: var(--ui-thumb-shadow); } -/* tables: mono labels in the head, 12.5px mono cells, hairline rows */ +/* tables: the usage-table header (12/16/500, uppercase, 0.05em), the cells in upstream's classes (text-sm tabular), + hairline rows */ html.flat-theme [data-ui="key-usage"] main table thead tr { border-color: rgb(var(--ui-line)); background: rgb(var(--ui-surface)); @@ -2339,8 +2275,8 @@ html.flat-theme [data-ui="key-usage"] main table thead tr { html.flat-theme [data-ui="key-usage"] main table th { padding: 12px; - font: 500 var(--ui-label-size) / var(--ui-label-leading) var(--ui-font-mono); - letter-spacing: var(--ui-label-tracking); + font: 500 12px/16px var(--ui-font); + letter-spacing: 0.05em; text-transform: uppercase; color: rgb(var(--ui-label)); white-space: nowrap; @@ -2348,8 +2284,6 @@ html.flat-theme [data-ui="key-usage"] main table th { html.flat-theme [data-ui="key-usage"] main table td { padding: 10px 12px; - font: 400 12.5px/19.5px var(--ui-font-mono); - color: rgb(var(--ui-ink-soft)); } html.flat-theme [data-ui="key-usage"] main table :is(th, td):first-child { @@ -2360,17 +2294,13 @@ html.flat-theme [data-ui="key-usage"] main table :is(th, td):last-child { padding-right: 20px; } -html.flat-theme [data-ui="key-usage"] main table td:is(:first-child, :last-child) { - color: rgb(var(--ui-ink)); -} - html.flat-theme [data-ui="key-usage"] main table tbody tr { border-color: rgb(var(--ui-line)); } html.flat-theme [data-ui="key-usage"] main div[class~="rounded-2xl"] > div[class~="px-8"][class~="py-8"] { padding: 32px 20px; - font: 400 13px/19.5px var(--ui-font); + font: 400 14px/20px var(--ui-font); color: rgb(var(--ui-muted)); } @@ -2389,11 +2319,12 @@ html.flat-theme [data-ui="key-usage"] .skeleton { * Source: the reference console's settings language (artifacts/tmp/ui-el/ref/console/REF-CONSOLE.md §5, §8.3-§8.5, * §8.11, §8.16 and its public recipes: Modal, ConnectModal, DemoBanner, PromoField, TopUpModal, Toggle, AddChoice) * and its docs pages for long-form text (ref-docs-*: callouts, tables, code). A long form reads as the reference's - * panels: section cards (the surface, 1px structural line, radius 8) with a header strip (13 / 19.5 title, 12.5px - * muted description, 14 x 18 inset like its Modal) and a 16 x 18 body; mono field labels (controls.css), 34px - * fields, 12px help text; switch rows with a 13 / 600 title and a 12px description beside a right-aligned switch, - * hairlines between rows; nested groups as the reference's route cards; notices as its callouts. Colour stays for - * states only (warning / info notices, errors, the accent on links, focus and switches that are on; WAVE2 decision 2). + * panels: section cards (the surface, 1px structural line, radius 8) with a header strip (14 / 20 / 600 title, + * upstream's text-sm muted description, 14 x 18 inset like its Modal) and a 16 x 18 body; upstream's field labels + * (controls.css, style.css), 34px fields, 12px help text; switch rows with a 14 / 500 title and a 12px description + * beside a right-aligned switch, hairlines between rows; nested groups as the reference's route cards; notices as its + * callouts. Colour stays for states only (warning / info notices, errors, the accent on links, focus and switches that + * are on; WAVE2 decision 2). * * Template hooks (one attribute each, upstream markup otherwise unchanged): * views/admin/SettingsView.vue:3 data-ui="settings" (page root) @@ -2425,10 +2356,10 @@ html.flat-theme :is([data-ui~="settings"], [data-ui~="system-prompts"], [data-ui /* the space between the fields and rows of a card, and a row's half gap around its hairline */ --set-gap: 1rem; --set-row: 0.875rem; - /* text that carries no size of its own (a bare label, a status line) is the console's 13 / 19.5 body text, not the - document's 16px */ - font-size: 0.8125rem; - line-height: 1.21875rem; + /* text that carries no size of its own (a bare label, a status line) is the console's 14 / 20 body text + (upstream's text-sm), not the document's 16px */ + font-size: 0.875rem; + line-height: 1.25rem; } /* ================================================================== settings · 1 page rhythm, tab strip */ @@ -2478,9 +2409,9 @@ html.flat-theme [data-ui~="settings"] .settings-tabs-shell.settings-tabs-shell:: /* ================================================================== settings · 2 section cards */ -/* 2.1 Header strip: the reference Modal / panel header (px 18, py 14, the structural line under it); title 13 / 19.5 - 600 ink (the reference's page card titles, "Usage", "Request history"; 14 / 600 is its dialog title), description - 12.5 / 18.75 muted two pixels under it. Upstream: 24px insets, an 18px title. */ +/* 2.1 Header strip: the reference Modal / panel header (px 18, py 14, the structural line under it); title 14 / 20 + 600 ink (the console's card title), description upstream's text-sm (14 / 20) muted two pixels under it. Upstream: + 24px insets, an 18px title. */ html.flat-theme [data-ui~="settings"] .card > div:first-child[class~="border-b"]:is([class~="px-6"], [class~="px-4"]) { gap: 0.75rem; padding: var(--set-head-py) var(--set-px); @@ -2488,8 +2419,8 @@ html.flat-theme [data-ui~="settings"] .card > div:first-child[class~="border-b"] html.flat-theme [data-ui~="settings"] .card > div:first-child[class~="border-b"] :is(h2, h3) { gap: 0.5rem; - font-size: 0.8125rem; - line-height: 1.21875rem; + font-size: 0.875rem; + line-height: 1.25rem; font-weight: 600; letter-spacing: 0; color: rgb(var(--ui-ink)); @@ -2497,8 +2428,8 @@ html.flat-theme [data-ui~="settings"] .card > div:first-child[class~="border-b"] html.flat-theme [data-ui~="settings"] .card > div:first-child[class~="border-b"] :is(h2 + p, h3 + p, h2 + div, div > p) { margin-top: 0.125rem; - font-size: 0.78125rem; - line-height: 1.171875rem; + font-size: 0.875rem; + line-height: 1.25rem; color: rgb(var(--ui-muted)); } @@ -2528,8 +2459,8 @@ html.flat-theme [data-ui~="settings"] .card > div:is([class~="p-6"], [class~="px /* ================================================================== settings · 3 rows, titles, labels, help */ /* 3.1 Switch rows: a title and its description on the left, the switch on the right, 16px apart at least (upstream - has no gap: a long description ran into the switch). Title 13 / 19.5 600 ink (the reference's card and option titles; - upstream's bare label is 16px), description 12 / 18 muted. + has no gap: a long description ran into the switch). Title 14 / 20 at upstream's font-medium 500, ink (upstream's + bare label is 16px), description 12 / 18 muted. The row is found by its own hook (data-ui~="setting-row", on the flex wrapper that holds the switch), not by `:has()` on a bare `div`: a generic `:has()` anchor merges every descendant into one invalidation set, and the anchor's whole subtree is then re-styled whenever any of those descendants may have changed. The hook keeps the same rows (a switch @@ -2546,9 +2477,9 @@ html.flat-theme [data-ui~="settings"] div[class~="flex"][class~="justify-between html.flat-theme [data-ui~="settings"] div[class~="flex"][class~="justify-between"][data-ui~="setting-row"]:not(uipad[data-uipad]) > div:first-child > :is(label, span, h3, h4, p):first-child:not([class~="font-mono"]), html.flat-theme [data-ui~="settings"] div[class~="flex"][class~="justify-between"][data-ui~="setting-row"]:not(uipad[data-uipad]) > :is(label, span, h3, h4):first-child:not([class~="font-mono"]) { font-family: var(--ui-font); - font-size: 0.8125rem; - line-height: 1.21875rem; - font-weight: 600; + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 500; letter-spacing: 0; text-transform: none; color: rgb(var(--ui-ink)); @@ -2565,7 +2496,7 @@ html.flat-theme [data-ui~="settings"] div[class~="flex"][class~="justify-between } /* 3.2 Titles inside a card: the bare row labels (label.font-medium, 16px upstream) and the sub-section headings of any - size are the reference's 13 / 600 ink card title; dialogs drawn inside the page keep theirs. A label followed by its + size are the console's 14 / 600 ink card title; dialogs drawn inside the page keep theirs. A label followed by its description (not by a field) is such a title too. The `:has()` stays in the subject compound on this class-qualified label and asks only about its next sibling, so it neither styles a descendant of the anchor nor makes the anchor's subtree part of the invalidation set. */ @@ -2582,8 +2513,8 @@ html.flat-theme [data-ui~="settings"] .card label[class~="text-sm"][class~="font html.flat-theme [data-ui~="settings"] .card :where(label[class~="font-medium"][class~="text-gray-900"], h3, h4):not(:where([class~="font-mono"], [class~="fixed"] *, .modal-content *)) { font-family: var(--ui-font); - font-size: 0.8125rem; - line-height: 1.21875rem; + font-size: 0.875rem; + line-height: 1.25rem; font-weight: 600; letter-spacing: 0; text-transform: none; @@ -2598,25 +2529,18 @@ html.flat-theme [data-ui~="settings"] .card :where(label[class~="font-medium"][c color: rgb(var(--ui-muted)); } -/* 3.4 Field labels. The recipe and the block utility labels are the mono label already (controls.css 3.5, style.css - .input-label); the inline ones that sit right before their field (label[for] + input.mt-2, the grok / TTFT fields, - the small grey labels of the search provider and the backup list) join them, so a form has one label language. */ +/* 3.4 Field labels. The recipe and the block utility labels keep upstream's label type (style.css .input-label, + controls.css 3.5); the inline ones that sit right before their field (label[for] + input.mt-2, the grok / TTFT fields, the small grey labels of the search provider and the backup list) keep their own classes + too (text-sm font-medium, or the small text-xs grey labels) and stand on their own line above the field. */ html.flat-theme [data-ui~="settings"] label[class~="text-sm"][class~="font-medium"]:not([class~="block"]):has(+ :is(input, select, textarea, div)), html.flat-theme [data-ui~="settings"] label[class~="text-xs"]:is([class~="text-gray-500"], [class~="text-gray-600"]):has(+ :is(input, select, textarea, div)) { display: block; - font-family: var(--ui-font-mono); - font-size: var(--ui-label-size); - line-height: var(--ui-label-leading); - font-weight: 600; - letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; - color: rgb(var(--ui-label)); } -/* 3.5 Help text under a field: 12 / 18 muted, 6px under it (upstream mt-1 / mt-1.5 mix). */ +/* 3.5 Help text under a field: upstream's text-xs (12 / 16) muted, 6px under it (upstream mt-1 / mt-1.5 mix). */ html.flat-theme [data-ui~="settings"] :is(input, select, textarea, .relative, [class~="flex"]) + p[class~="text-xs"]:is([class~="text-gray-500"], [class~="text-gray-400"]) { margin-top: 0.375rem; - line-height: 1.125rem; + line-height: 1rem; } /* 3.6 Hairline rows and sub-sections: the structural line with one row gap (14px) on each side of it; upstream @@ -2651,7 +2575,7 @@ html.flat-theme [data-ui~="settings"] .card div[class~="rounded"][class~="border padding: 0.625rem 0.875rem; } -/* 4.2 Notices: the reference's callout (radius 6, 12 x 14, 13px with a relaxed line) in the colour of its state +/* 4.2 Notices: the reference's callout (radius 6, 12 x 14, 14px with a relaxed line) in the colour of its state (warning, success, danger): the soft fill and the state colour at 30 % as the edge, in dark too (the soft tokens are the state at 18 %, not upstream's translucent 900 shades). Their text keeps the state colour upstream gives it (the warning is the message). A notice that holds a switch row stays a row. Blue / sky info boxes are not a state: @@ -2690,7 +2614,8 @@ html.flat-theme [data-ui~="settings"] :is(div, p)[class~="border"][class*="bg-am } /* 4.3 Dashed "add" buttons (add an endpoint, a menu item, a rule): a quiet 34px outline in the control line, dashed, - radius 6, 12.5 / 600 muted, ink on hover; not the accent (the reference adds with a default button). */ + radius 6, upstream's text-sm at the button weight 500, muted, ink on hover; not the accent (the reference adds with + a default button). */ html.flat-theme [data-ui~="settings"] button[class~="border-dashed"] { min-height: var(--ui-select-h); gap: 0.375rem; @@ -2698,9 +2623,9 @@ html.flat-theme [data-ui~="settings"] button[class~="border-dashed"] { border-color: rgb(var(--ui-line-strong)); border-radius: var(--ui-radius-md); padding: 0.3125rem 0.75rem; - font-size: 0.78125rem; - line-height: 1.171875rem; - font-weight: 600; + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 500; color: rgb(var(--ui-muted)); transition-property: color, border-color, background-color; transition-duration: var(--ui-duration); @@ -2718,13 +2643,14 @@ html.flat-theme [data-ui~="settings"] button[class~="border-dashed"] svg { height: 0.875rem; } -/* dashed empty boxes ("no rule yet") are the reference's empty state: dashed control outline, radius 8, 12.5px muted */ +/* dashed empty boxes ("no rule yet") are the reference's empty state: dashed control outline, radius 8, upstream's + text-sm muted */ html.flat-theme [data-ui~="settings"] div[class~="border-dashed"] { border-width: 1px; border-color: rgb(var(--ui-line-strong)); border-radius: var(--ui-radius-lg); - font-size: 0.78125rem; - line-height: 1.171875rem; + font-size: 0.875rem; + line-height: 1.25rem; color: rgb(var(--ui-muted)); } @@ -2753,15 +2679,14 @@ html.flat-theme [data-ui~="settings"] button[class~="text-red-400"]:is([class~=" height: 0.9375rem; } -/* 4.5 Quick-fill chips (model patterns): the reference's value chip, Geist Mono 11 ink-soft, the control outline, - radius 4; ink on hover (upstream turns them green). */ +/* 4.5 Quick-fill chips (model patterns): the reference's value chip, upstream's text-xs ink-soft, the control + outline, radius 4; ink on hover (upstream turns them green). */ html.flat-theme [data-ui~="settings"] button[class~="rounded"][class~="border"][class~="px-2"][class~="py-0.5"][class~="text-xs"] { border-color: rgb(var(--ui-line-strong)); border-radius: var(--ui-radius-sm); background-color: rgb(var(--ui-surface)); padding: 0.0625rem 0.375rem; - font-family: var(--ui-font-mono); - font-size: 0.6875rem; + font-size: 0.75rem; line-height: 1.125rem; color: rgb(var(--ui-ink-soft)); } @@ -2772,8 +2697,8 @@ html.flat-theme [data-ui~="settings"] button[class~="rounded"][class~="border"][ color: rgb(var(--ui-ink)); } -/* 4.6 Values set as code inside a card (callback URLs, the admin key): Geist Mono 12 ink-soft on the grouped fill, - radius 4 (the reference's value chip). */ +/* 4.6 Values set as code inside a card (callback URLs, the admin key): the mono stack at 12px, ink-soft on the + grouped fill, radius 4 (the reference's value chip). */ html.flat-theme [data-ui~="settings"] .card code[class~="rounded"]:is([class~="bg-gray-50"], [class~="bg-gray-100"]) { border-radius: var(--ui-radius-sm); background-color: rgb(var(--ui-subtle)); @@ -2783,13 +2708,13 @@ html.flat-theme [data-ui~="settings"] .card code[class~="rounded"]:is([class~="b } /* 4.7 Header-less tables in a card (the platform quota matrix, the source overrides): the reference's table header, - the mono label at 500 with the structural line under it; 8px between rows. */ + upstream's usage-table header (12 / 16 / 500, uppercase, 0.05em) with the structural line under it; 8px between + rows. */ html.flat-theme [data-ui~="settings"] .card table > thead:not([class~="bg-gray-50"]) > tr > th { - font-family: var(--ui-font-mono); - font-size: var(--ui-label-size); - line-height: var(--ui-label-leading); + font-size: 12px; + line-height: 16px; font-weight: 500; - letter-spacing: var(--ui-label-tracking); + letter-spacing: 0.05em; text-transform: uppercase; color: rgb(var(--ui-label)); } @@ -2813,11 +2738,11 @@ html.flat-theme [data-ui~="settings"] .card > div[class~="p-6"] > :is([data-test padding-top: var(--set-row); } -/* the observation panel's checkbox options: 13px ink beside the 14px box, 8px apart */ +/* the observation panel's checkbox options: 14px ink beside the 14px box, 8px apart */ html.flat-theme [data-ui~="settings"] [data-testid="observability-settings"] > label { gap: 0.5rem; - font-size: 0.8125rem; - line-height: 1.21875rem; + font-size: 0.875rem; + line-height: 1.25rem; color: rgb(var(--ui-ink)); } @@ -2826,9 +2751,9 @@ html.flat-theme [data-ui~="settings"] [data-testid="observability-settings"] > : } /* 5.2 The model catalogue reads as the reference's docs reference list: a bordered scroll box (radius 8, 14px in), - one entry per hairline; the model id in Geist Mono 12.5 / 600 ink, the provider / product in mono 11 muted; the - facts as a two-column definition list, 12 / 18, labels muted, values ink-soft; the count as the reference's mono - meta line. Every field stays shown. */ + one entry per hairline; the model id 14 / 600 ink (upstream text-sm), the provider / product 12px muted; the facts + as a two-column definition list, 12 / 18, labels muted, values ink-soft; the count as a 12px meta line. Every field + stays shown. */ html.flat-theme [data-ui~="settings"] [data-testid="official-model-catalog-list"] { border-color: rgb(var(--ui-line)); border-radius: var(--ui-radius-lg); @@ -2840,17 +2765,15 @@ html.flat-theme [data-ui~="settings"] [data-testid="official-model-catalog-entry } html.flat-theme [data-ui~="settings"] [data-testid="official-model-catalog-entry"] > p:first-child { - font-family: var(--ui-font-mono); - font-size: 0.78125rem; - line-height: 1.171875rem; + font-size: 0.875rem; + line-height: 1.25rem; font-weight: 600; color: rgb(var(--ui-ink)); } html.flat-theme [data-ui~="settings"] [data-testid="official-model-catalog-entry"] > p:nth-child(2) { - font-family: var(--ui-font-mono); - font-size: 0.6875rem; - line-height: 1.03125rem; + font-size: 0.75rem; + line-height: 1rem; color: rgb(var(--ui-muted)); } @@ -2871,9 +2794,8 @@ html.flat-theme [data-ui~="settings"] [data-testid="official-model-catalog-entry } html.flat-theme [data-ui~="settings"] [data-testid="official-model-catalog"] > p[class~="text-xs"] { - font-family: var(--ui-font-mono); - font-size: 0.6875rem; - line-height: 1.03125rem; + font-size: 0.75rem; + line-height: 1rem; color: rgb(var(--ui-label)); } @@ -2892,27 +2814,27 @@ html.flat-theme [data-ui~="settings"] .card[class~="p-6"] > div:first-child[clas } html.flat-theme [data-ui~="settings"] .card[class~="p-6"] > div:first-child[class~="mb-4"] h3 { - font-size: 0.8125rem; - line-height: 1.21875rem; + font-size: 0.875rem; + line-height: 1.25rem; } html.flat-theme [data-ui~="settings"] .card[class~="p-6"] > div:first-child[class~="mb-4"] h3 + p { - font-size: 0.78125rem; - line-height: 1.171875rem; + font-size: 0.875rem; + line-height: 1.25rem; } -/* the backup list: the reference's table header (mono label, the line under it), 42px rows, figures in Geist Mono */ +/* the backup list: the reference's table header (upstream's usage-table header, the line under it), 42px rows, + figures in upstream's classes */ html.flat-theme [data-ui~="settings"] .card[class~="p-6"] table > thead > tr { border-color: rgb(var(--ui-line)); } html.flat-theme [data-ui~="settings"] .card[class~="p-6"] table > thead > tr > th { padding-block: 0.625rem; - font-family: var(--ui-font-mono); - font-size: var(--ui-label-size); - line-height: var(--ui-label-leading); + font-size: 12px; + line-height: 16px; font-weight: 500; - letter-spacing: var(--ui-label-tracking); + letter-spacing: 0.05em; color: rgb(var(--ui-label)); } @@ -2926,8 +2848,8 @@ html.flat-theme [data-ui~="settings"] [data-testid="backup-schedule"] > div[clas border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-subtle) / 0.6); padding: 0.625rem 0.875rem; - font-size: 0.78125rem; - line-height: 1.171875rem; + font-size: 0.875rem; + line-height: 1.25rem; color: rgb(var(--ui-ink-soft)); } @@ -2940,36 +2862,37 @@ html.flat-theme [data-ui~="settings"] [data-testid="backup-schedule"] > div[clas description, not a state: the shared neutral callout (data.css §11 gives its line, fill and text; here only its radius and inset), its category and delivery chips neutral (upstream tints it with the accent and colours the delivery kind). The HTML body is set as the reference's code - (Geist Mono 12.5 on a 1.625 line). Placeholders are value chips: the structural outline, Geist Mono 11, ink on - hover (not green). The live preview panel's header is a panel header (10 x 14, 13 / 600 title). */ + (its template font-mono at 14px on a 1.625 line). Placeholders are value chips: the structural outline, their + template font-mono at 12px, ink on hover (not green). The live preview panel's header is a panel header (10 x 14, + 14 / 600 title). */ html.flat-theme [data-ui~="settings"] .card div[class~="rounded-lg"][class~="border"][class~="border-primary-100"] { border-radius: var(--ui-radius-lg); padding: 0.75rem 0.875rem; } html.flat-theme [data-ui~="settings"] .card div[class~="border-primary-100"] > div:first-child > div { - font-size: 0.8125rem; - line-height: 1.21875rem; + font-size: 0.875rem; + line-height: 1.25rem; } html.flat-theme [data-ui~="settings"] .card div[class~="border-primary-100"] > div:first-child > span[class~="rounded-full"] { border: 1px solid rgb(var(--ui-line-strong)); background-color: rgb(var(--ui-surface)); padding: 0.0625rem 0.5rem; - font-size: 0.6875rem; - line-height: 1.03125rem; + font-size: 0.75rem; + line-height: 1rem; font-weight: 500; color: rgb(var(--ui-ink-soft)); box-shadow: none; } html.flat-theme [data-ui~="settings"] .card div[class~="border-primary-100"] > p { - font-size: 0.78125rem; + font-size: 0.875rem; line-height: 1.25rem; } html.flat-theme [data-ui~="settings"] .card textarea#email-template-html { - font-size: 0.78125rem; + font-size: 0.875rem; line-height: 1.625; } @@ -2977,8 +2900,8 @@ html.flat-theme [data-ui~="settings"] .card button[class~="rounded-full"][class~ border-color: rgb(var(--ui-line-strong)); background-color: rgb(var(--ui-surface)); padding: 0.125rem 0.625rem; - font-size: 0.6875rem; - line-height: 1.03125rem; + font-size: 0.75rem; + line-height: 1rem; color: rgb(var(--ui-ink-soft)); transition-property: color, border-color; transition-duration: var(--ui-duration); @@ -3053,13 +2976,13 @@ html.flat-theme [data-ui~="settings"] .card div[class~="overflow-x-auto"] > tabl } } -/* 7.2 The intro under the title: the reference's page intro (13px on a relaxed line, muted, 780px at most); a +/* 7.2 The intro under the title: the reference's page intro (14px on a relaxed line, muted, 780px at most); a wrapped intro keeps more than one character on its last line. */ html.flat-theme :is([data-ui~="system-prompts"], [data-ui~="reasoning-recovery"]) > header p, html.flat-theme [data-ui~="codex-runtime"] > h2:first-child + p, html.flat-theme [data-ui~="plugins"] > section:first-child > div:first-child > p { max-width: 48.75rem; - font-size: 0.8125rem; + font-size: 0.875rem; line-height: 1.625; color: rgb(var(--ui-muted)); text-wrap: pretty; @@ -3077,9 +3000,9 @@ html.flat-theme [data-ui~="plugins"] > section:first-child > div:first-child > p } } -/* the page title where it stays (below 1024px, or an untranslated route title): the reference's 20 / 28 page title */ +/* the page title where it stays (below 1024px, or an untranslated route title): the header row's 18 / 28 page title */ html.flat-theme :is([data-ui~="system-prompts"] > header h1, [data-ui~="codex-runtime"] > h2:first-child, [data-ui~="plugins"] > section:first-child h2) { - font-size: 1.25rem; + font-size: 1.125rem; line-height: 1.75rem; font-weight: 600; color: rgb(var(--ui-ink)); @@ -3110,8 +3033,8 @@ html.flat-theme :is([data-ui~="system-prompts"], [data-ui~="reasoning-recovery"] padding: var(--set-body-py) var(--set-px) calc(var(--set-body-py) + 0.125rem); } -/* 8.2 The switch line (switch, title, hint on one row) and the default prompt: title 13 / 600 ink, hint 12 muted; - the field label is the mono label; the usage line in ink-soft with its figures tabular; the notes 12 muted. */ +/* 8.2 The switch line (switch, title, hint on one row) and the default prompt: title 14 / 600 ink, hint 12 muted; + the field label follows upstream text-sm font-medium; the usage line in ink-soft with its figures tabular; the notes 12 muted. */ html.flat-theme :is([data-ui~="system-prompts"], [data-ui~="reasoning-recovery"]) > section:first-of-type > label:first-child > span[class~="font-medium"] { font-weight: 600; color: rgb(var(--ui-ink)); @@ -3125,13 +3048,12 @@ html.flat-theme [data-ui~="system-prompts"] > section:first-of-type > label:firs html.flat-theme [data-ui~="system-prompts"] label > span[class~="block"]:first-child { margin-bottom: 0.5rem; - font-family: var(--ui-font-mono); - font-size: var(--ui-label-size); - line-height: var(--ui-label-leading); - font-weight: 600; - letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; - color: rgb(var(--ui-label)); + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 500; + letter-spacing: normal; + text-transform: none; + color: rgb(var(--ui-ink-soft)); } html.flat-theme [data-ui~="system-prompts"] label[class~="space-y-1"] > :not([hidden]) ~ :not([hidden]) { @@ -3155,9 +3077,9 @@ html.flat-theme :is([data-ui~="system-prompts"], [data-ui~="reasoning-recovery"] line-height: 1.125rem; } -/* 8.3 The library card: its title row is the card's header strip (14 x 18, the line under it, 13 / 600 title, the - add button at the right); each prompt is a row under a hairline; the body is the reference's code (Geist Mono - 12.5 on a 1.625 line); the byte count and the users in the mono meta line. */ +/* 8.3 The library card: its title row is the card's header strip (14 x 18, the line under it, 14 / 600 title, the + add button at the right); each prompt is a row under a hairline; the body is the reference's code (its template + font-mono at text-xs, on a 1.625 line); the byte count and the users in a 12px meta line. */ html.flat-theme [data-ui~="system-prompts"] > section:last-of-type > div:first-child { margin: calc(var(--set-body-py) * -1) calc(var(--set-px) * -1) 0; border-bottom: 1px solid rgb(var(--ui-line)); @@ -3165,8 +3087,8 @@ html.flat-theme [data-ui~="system-prompts"] > section:last-of-type > div:first-c } html.flat-theme [data-ui~="system-prompts"] > section:last-of-type > div:first-child > h2 { - font-size: 0.8125rem; - line-height: 1.21875rem; + font-size: 0.875rem; + line-height: 1.25rem; font-weight: 600; color: rgb(var(--ui-ink)); } @@ -3190,15 +3112,14 @@ html.flat-theme [data-ui~="system-prompts"] article > div[class~="items-end"] > } html.flat-theme [data-ui~="system-prompts"] article textarea.input { - font-size: 0.78125rem; + font-size: 0.75rem; line-height: 1.625; } html.flat-theme [data-ui~="system-prompts"] article > p[class~="text-xs"] { gap: 0.25rem 1rem; - font-family: var(--ui-font-mono); - font-size: 0.6875rem; - line-height: 1.03125rem; + font-size: 0.75rem; + line-height: 1rem; font-variant-numeric: tabular-nums; color: rgb(var(--ui-label)); } @@ -3210,16 +3131,15 @@ html.flat-theme [data-ui~="system-prompts"] article > p[class~="text-xs"] > [cla /* ================================================================== 8b · the per-account binding (accounts dialogs) */ /* AccountSystemPromptBinding.vue, inside the account row menu's dialog, the bulk dialog and the account edit dialog: - the same field language (mono labels over 34px selects, 12px muted notes), the effect line in ink-soft. */ + the same field language (field labels over 34px selects, 12px muted notes), the effect line in ink-soft. */ html.flat-theme [data-test="account-system-prompt-binding"] label > span[class~="block"]:first-child { margin-bottom: 0.5rem; - font-family: var(--ui-font-mono); - font-size: var(--ui-label-size); - line-height: var(--ui-label-leading); - font-weight: 600; - letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; - color: rgb(var(--ui-label)); + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 500; + letter-spacing: normal; + text-transform: none; + color: rgb(var(--ui-ink-soft)); } html.flat-theme [data-test="account-system-prompt-binding"] label[class~="space-y-1"] > :not([hidden]) ~ :not([hidden]) { @@ -3243,8 +3163,8 @@ html.flat-theme [data-test="account-system-prompt-binding"] > div[class~="items- } html.flat-theme [data-test="account-system-prompt-binding"] > h3 { - font-size: 0.8125rem; - line-height: 1.21875rem; + font-size: 0.875rem; + line-height: 1.25rem; font-weight: 600; color: rgb(var(--ui-ink)); } @@ -3313,7 +3233,7 @@ html.flat-theme [data-ui~="plugins"] > p[class~="text-xs"] { } /* 10.2 The runtime notice is the shared neutral callout (data.css §11); the empty state is the reference's dashed - panel (radius 8, the control outline, a 14 / 500 title, 13px muted text, a neutral glyph). */ + panel (radius 8, the control outline, a 14 / 500 title, 14px muted text, a neutral glyph). */ html.flat-theme [data-ui~="plugins"] > div[class~="border-dashed"] { border-color: rgb(var(--ui-line-strong)); border-radius: var(--ui-radius-lg); @@ -3333,42 +3253,43 @@ html.flat-theme [data-ui~="plugins"] > div[class~="border-dashed"] > p:first-of- } html.flat-theme [data-ui~="plugins"] > div[class~="border-dashed"] > p:last-of-type { - font-size: 0.8125rem; + font-size: 0.875rem; line-height: 1.625; color: rgb(var(--ui-muted)); } -/* 10.3 Plugin cards: section cards; the head is the header strip (14 x 18, the line under it) with the name - 13 / 600, the version in Geist Mono 11 muted, the state as a small chip; the facts as a two-column definition - list (12 / 18, labels muted, values ink-soft, identifiers in Geist Mono); section labels are the mono label. */ +/* 10.3 Plugin cards: section cards; the head is the header strip (14 x 18, the line under + it) with the name 14 / 600, the version in its template font-mono 12px muted, the id and state as small chips + (upstream's text-xs); the facts as a two-column definition list (12 / 18, labels muted, values ink-soft, + identifiers in their template font-mono); section labels are the label (12 / 16 / 500, sentence case); JSON is the + reference's snippet (the page ground, radius 6, its template font-mono at 12px on 1.6). */ html.flat-theme [data-ui~="plugins"] article.card > div:first-child[class~="border-b"] { padding: var(--set-head-py) var(--set-px); } html.flat-theme [data-ui~="plugins"] article.card h3 { - font-size: 0.8125rem; - line-height: 1.21875rem; + font-size: 0.875rem; + line-height: 1.25rem; font-weight: 600; color: rgb(var(--ui-ink)); } html.flat-theme [data-ui~="plugins"] article.card h3 ~ span[class~="font-mono"] { - font-size: 0.6875rem; - line-height: 1.03125rem; + font-size: 0.75rem; + line-height: 1rem; color: rgb(var(--ui-muted)); } html.flat-theme [data-ui~="plugins"] article.card h3 ~ span[class~="rounded"] { border-radius: var(--ui-radius-sm); padding: 0.0625rem 0.375rem; - font-family: var(--ui-font-mono); - font-size: 0.6875rem; - line-height: 1.03125rem; + font-size: 0.75rem; + line-height: 1rem; } html.flat-theme [data-ui~="plugins"] article.card > div:first-child p { - font-size: 0.78125rem; - line-height: 1.171875rem; + font-size: 0.75rem; + line-height: 1rem; } html.flat-theme [data-ui~="plugins"] article.card > div:not(:first-child)[class~="p-5"] { @@ -3391,11 +3312,11 @@ html.flat-theme [data-ui~="plugins"] article.card dl > dd { } html.flat-theme [data-ui~="plugins"] article.card p[class~="uppercase"][class~="text-xs"] { - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); line-height: var(--ui-label-leading); - font-weight: 600; + font-weight: var(--ui-label-weight); letter-spacing: var(--ui-label-tracking); + text-transform: none; color: rgb(var(--ui-label)); } @@ -3472,8 +3393,8 @@ html.flat-theme :is([data-ui~="settings"], #provider-form) button[class~="rounde color: var(--ui-link); } -/* the dialog's groups and headings follow the settings form: the custom-method group is a route card; headings 13 / - 600 ink; the inline group captions (payment mode, methods) are the mono label */ +/* the dialog's groups and headings follow the settings form: the custom-method group is a route card; headings 14 / + 600 ink; the inline group captions (payment mode, methods) are the label (12 / 16 / 500, sentence case) */ html.flat-theme #provider-form div[class~="rounded-lg"][class~="border"][class~="p-3"] { border-color: rgb(var(--ui-line)); border-radius: var(--ui-radius-lg); @@ -3481,37 +3402,36 @@ html.flat-theme #provider-form div[class~="rounded-lg"][class~="border"][class~= } html.flat-theme #provider-form :is(h4, h5) { - font-size: 0.8125rem; - line-height: 1.21875rem; + font-size: 0.875rem; + line-height: 1.25rem; font-weight: 600; color: rgb(var(--ui-ink)); } html.flat-theme #provider-form > div[class~="flex-wrap"] > div > span[class~="text-xs"] { - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); line-height: var(--ui-label-leading); - font-weight: 600; + font-weight: var(--ui-label-weight); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; color: rgb(var(--ui-label)); } /* ================================================================== 12 · the Cloudflare R2 guide (BackupView dialog) */ -/* A how-to in a dialog reads as the reference's docs page: 13px text on a relaxed line; the step titles 13 / 600 ink; - the step numbers a neutral mark (a 22px circle with the control outline, Geist Mono 11 ink-soft; upstream tints them - with the accent, decision 2); code and the settings table as its code and docs table (Geist Mono, the structural - line, the grouped fill on the key column); the Secret warning keeps its state; the free-tier line is a neutral - callout. Hook: BackupView.vue:291 data-ui="r2-guide" (the dialog's outer layer; the panel keeps the overlays' - utility-dialog look). */ +/* A how-to in a dialog reads as the reference's docs page: 14px text on a relaxed line; the step titles 14 / 600 ink; + the step numbers a neutral mark (a 22px circle with the control outline, 12px ink-soft; upstream tints them with the + accent, decision 2); code and the settings table as its code and docs table (code in the mono stack, the + structural line, the grouped fill on the key column); the Secret warning keeps its state; the free-tier line is a + neutral callout. Hook: BackupView.vue:291 data-ui="r2-guide" (the dialog's outer layer; the panel keeps the + overlays' utility-dialog look). */ html.flat-theme [data-ui~="r2-guide"] :is(p, li) { - font-size: 0.8125rem; + font-size: 0.875rem; line-height: 1.625; } html.flat-theme [data-ui~="r2-guide"] h3 { - font-size: 0.8125rem; + font-size: 0.875rem; line-height: 1.375rem; font-weight: 600; color: rgb(var(--ui-ink)); @@ -3522,8 +3442,7 @@ html.flat-theme [data-ui~="r2-guide"] h3 > span[class~="rounded-full"] { height: 1.375rem; border: 1px solid rgb(var(--ui-line-strong)); background-color: rgb(var(--ui-surface)); - font-family: var(--ui-font-mono); - font-size: 0.6875rem; + font-size: 0.75rem; font-weight: 600; color: rgb(var(--ui-ink-soft)); } @@ -3552,7 +3471,7 @@ html.flat-theme [data-ui~="r2-guide"] div[class~="rounded-lg"][class~="border"]: } html.flat-theme [data-ui~="r2-guide"] table { - font-size: 0.78125rem; + font-size: 0.875rem; line-height: 1.25rem; } @@ -3562,8 +3481,6 @@ html.flat-theme [data-ui~="r2-guide"] table tr { html.flat-theme [data-ui~="r2-guide"] table td:first-child { background-color: rgb(var(--ui-subtle) / 0.6); - font-weight: 500; - color: rgb(var(--ui-ink-soft)); } html.flat-theme [data-ui~="r2-guide"] table td:last-child code { @@ -3575,7 +3492,7 @@ html.flat-theme [data-ui~="r2-guide"] div[class~="rounded-lg"][class~="bg-amber- border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-warning-soft)); padding: 0.625rem 0.875rem; - font-size: 0.78125rem; + font-size: 0.75rem; line-height: 1.25rem; } @@ -3584,7 +3501,7 @@ html.flat-theme [data-ui~="r2-guide"] div[class~="rounded-lg"][class~="bg-green- border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-surface)); padding: 0.75rem 0.875rem; - font-size: 0.8125rem; + font-size: 0.875rem; line-height: 1.625; color: rgb(var(--ui-muted)); } diff --git a/frontend/src/styles/console/pages/user.css b/frontend/src/styles/console/pages/user.css index 73f866413444..ec41631e2a1e 100644 --- a/frontend/src/styles/console/pages/user.css +++ b/frontend/src/styles/console/pages/user.css @@ -6,7 +6,8 @@ * StatTile and the detail stat block (§8.13), the docs callout and lists (§8.16), the snippet and CodeTabs (§8.15), * the dialog option card (§8.11), the filter pill (§8.6), list rows on 1px lines (the benchmarks card). * Owner decision 2 (2026-09-29): colour only for status, the accent and platform identity (data.css §6.4 hooks); - * balances, KPI tiles, icon tiles and decorative numbers are neutral. + * balances, KPI tiles, icon tiles and decorative numbers are neutral. Text follows official sub2api (2026-10): the OS + * UI font, the templates' sizes and weights; table cells keep their template text. * * Hooks added for this file (one data-ui attribute each, no other template change): * data-ui="user-redeem" views/user/RedeemView.vue:3 page root @@ -53,9 +54,9 @@ html.flat-theme [data-testid="profile-shell"] [class~="space-y-6"] > :not([hidde } /* Cards on these pages: the reference's panel inset (16px, REF §6 p-4) instead of upstream's 24; a header bar is - 12 / 16 on the structural line with the title 14 / 600 (the reference's modal and card titles) and its note - 12.5px muted. The card bodies: the profile's at 0,6,2 without its overview head (4.1 keeps that one flush, final - review V3-04); the redeem page's at 0,5,2, so that the page's own insets further down, of the same weight, win + 12 / 16 on the structural line with the title 14 / 600 (the reference's modal and card titles) and its note at the + template's 14px, muted. The card bodies: the profile's at 0,6,2 without its overview head (4.1 keeps that one flush, + final review V3-04); the redeem page's at 0,5,2, so that the page's own insets further down, of the same weight, win (3.3 the note, 3.4 the recent activity, 3.5 the result panels; final review V3-10). */ html.flat-theme :is([data-ui="user-redeem"], [data-ui="user-affiliate"], [data-ui="user-purchase"], [data-testid="profile-shell"]) .card:is([class~="p-6"], [class~="p-5"]), html.flat-theme [data-testid="profile-shell"] .card:not([data-testid="profile-overview-hero"]) > div:is([class~="p-6"], [class~="px-6"][class~="py-6"]), @@ -80,8 +81,8 @@ html.flat-theme :is([data-ui="user-redeem"], [data-testid="profile-shell"]) .car html.flat-theme :is([data-ui="user-affiliate"], [data-testid="profile-shell"]) .card :is(h2, h3):is([class~="text-lg"], [class~="text-base"]) + p { margin-top: 2px; color: rgb(var(--ui-muted)); - font-size: 12.5px; - line-height: 18.75px; + font-size: 14px; + line-height: 20px; } /* ================================================================== 2 · API keys (/keys) */ @@ -116,61 +117,26 @@ html.flat-theme :is([data-ui="user-affiliate"], [data-testid="profile-shell"]) . } } -/* 2.1 Table cells: money in Geist Mono tabular like every figure of the reference (§10.6). */ -html.flat-theme .table-wrapper[data-table="keys"] td[data-column="usage"] span[class~="font-medium"] { - font-family: var(--ui-font-mono); - font-size: 12.5px; - font-weight: 500; - font-variant-numeric: tabular-nums; -} - -/* the labels (今日 / 近30天): the label role of the table's text (data.css 12; #66666e is 4.95:1 on a hovered row) */ -html.flat-theme .table-wrapper[data-table="keys"] td[data-column="usage"] span[class~="text-gray-500"] { - color: rgb(var(--ui-data-label)); - font-size: 12px; -} - -/* text roles (data.css 12) whose class names another role: 「选择分组」 is an empty-value prompt (a label); 「永久有效」 is - a value (secondary, its class says label) */ -html.flat-theme .table-wrapper[data-table="keys"] td[data-column="group"] [class~="text-gray-500"] { - --theme-text-gray-100: var(--ui-data-label); --theme-text-gray-200: var(--ui-data-label); --theme-text-gray-300: var(--ui-data-label); - --theme-text-gray-400: var(--ui-data-label); --theme-text-gray-500: var(--ui-data-label); --theme-text-gray-600: var(--ui-data-label); - --theme-text-gray-700: var(--ui-data-label); --theme-text-gray-800: var(--ui-data-label); --theme-text-gray-900: var(--ui-data-label); - --theme-text-gray-950: var(--ui-data-label); --theme-text-dark-100: var(--ui-data-label); --theme-text-dark-200: var(--ui-data-label); - --theme-text-dark-300: var(--ui-data-label); --theme-text-dark-400: var(--ui-data-label); --theme-text-dark-500: var(--ui-data-label); - --theme-text-dark-600: var(--ui-data-label); --ui-ink-soft: var(--ui-data-label); -} - -html.flat-theme .table-wrapper[data-table="keys"] td[data-column="expires_at"] > span[class~="text-gray-400"] { - --theme-text-gray-100: var(--ui-data-secondary); --theme-text-gray-200: var(--ui-data-secondary); --theme-text-gray-300: var(--ui-data-secondary); - --theme-text-gray-400: var(--ui-data-secondary); --theme-text-gray-500: var(--ui-data-secondary); --theme-text-gray-600: var(--ui-data-secondary); - --theme-text-gray-700: var(--ui-data-secondary); --theme-text-gray-800: var(--ui-data-secondary); --theme-text-gray-900: var(--ui-data-secondary); - --theme-text-gray-950: var(--ui-data-secondary); --theme-text-dark-100: var(--ui-data-secondary); --theme-text-dark-200: var(--ui-data-secondary); - --theme-text-dark-300: var(--ui-data-secondary); --theme-text-dark-400: var(--ui-data-secondary); --theme-text-dark-500: var(--ui-data-secondary); - --theme-text-dark-600: var(--ui-data-secondary); --ui-ink-soft: var(--ui-data-secondary); -} - -/* the live-concurrency count: the reference's cell chip (4px, Mono), a running count keeps its success tint */ +/* 2.1 Table cells: the key usage (今日 / 近30天) and the group and expiry texts are the templates' (official). The + live-concurrency count is the reference's cell chip (4px) in the template's text; a running count keeps its success + tint. */ html.flat-theme .table-wrapper[data-table="keys"] td[data-column="current_concurrency"] > span { min-width: 28px; height: 22px; padding: 0 6px; border-radius: var(--ui-radius-sm); - font-family: var(--ui-font-mono); - font-size: 12px; - font-weight: 500; - line-height: 20px; } -/* 2.2 The selection row above the table (keys chosen for bulk edit): the reference tray's count, 12.5px ink-soft. */ +/* 2.2 The selection row above the table (keys chosen for bulk edit): the reference tray's count, the template's 14px, + ink-soft. */ html.flat-theme .table-page-layout[data-ui~="user-keys"] > .layout-section-fixed div[data-ui~="keys-bulk-bar"][class~="flex-wrap"][class~="text-sm"]:not([data-uipad]) > span { color: rgb(var(--ui-ink-soft)); - font-size: 12.5px; + font-size: 14px; } /* 2.25 The group picker (the group cell's popover, teleported to the body; its frame and rows are the console's menus, overlays.css): its search row is the ModelPicker's, as Select's (overlays 1.6): 36px on the structural line, the - glyph 14px faint, the field without a box of its own, 13px ink, the placeholder faint (upstream's placeholder-gray-400 + glyph 14px faint, the field without a box of its own, 14px ink, the placeholder faint (upstream's placeholder-gray-400 reads the grey fill ramp: 2.4:1 light, 3.3:1 dark); a keyboard focus draws the row's line in the accent (the row's only focusable element is a text field, which is :focus-visible whenever it is focused: :focus-within). */ html.flat-theme div[class~="fixed"][class~="z-[100000020]"] > div[class~="border-b"]:first-child { @@ -198,7 +164,7 @@ html.flat-theme div[class~="fixed"][class~="z-[100000020]"] > div[class~="border background-color: transparent; box-shadow: none; color: rgb(var(--ui-ink)); - font-size: 13px; + font-size: 14px; } html.flat-theme div[class~="fixed"][class~="z-[100000020]"] > div[class~="border-b"]:first-child input::placeholder { @@ -238,9 +204,9 @@ html.flat-theme #key-form fieldset[data-tour="key-form-provider"] label > input[ html.flat-theme #key-form fieldset[data-tour="key-form-provider"] label > input[type="radio"] + span > span[class~="text-sm"] { color: rgb(var(--ui-ink)); - font-size: 13px; + font-size: 14px; font-weight: 600; - line-height: 19.5px; + line-height: 20px; } html.flat-theme #key-form fieldset[data-tour="key-form-provider"] [data-ui~="platform-chip"] { @@ -264,7 +230,7 @@ html.flat-theme #key-form fieldset[data-tour="key-form-provider"] > p { } /* Expiry presets (7 / 30 / 90 days, custom date): the reference's filter pills (§8.6: 30px, the control outline, - 12.5 / 600 ink-soft; chosen = accent 40 % edge on the accent-soft fill, accent text). */ + the template's 14px at the pills' 500, ink-soft; chosen = accent 40 % edge on the accent-soft fill, accent text). */ html.flat-theme #key-form div[class~="flex-wrap"][class~="gap-2"] > button[type="button"][class~="rounded-lg"][class~="px-3"][class~="py-1.5"] { min-height: var(--ui-control-h); padding: 0 10px; @@ -272,9 +238,9 @@ html.flat-theme #key-form div[class~="flex-wrap"][class~="gap-2"] > button[type= border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-surface)); color: rgb(var(--ui-ink-soft)); - font-size: 12.5px; - font-weight: 600; - line-height: 18.75px; + font-size: 14px; + font-weight: 500; + line-height: 20px; transition-property: color, background-color, border-color; transition-duration: var(--ui-duration); transition-timing-function: var(--ui-ease); @@ -290,19 +256,18 @@ html.flat-theme #key-form div[class~="flex-wrap"][class~="gap-2"] > button[type= color: var(--ui-link); } -/* read-outs in the edit dialog (quota and window usage): a subtle well with Mono figures */ +/* read-outs in the edit dialog (quota and window usage): a subtle well with tabular figures at the template's 14px */ html.flat-theme #key-form div[class~="rounded-lg"][class~="bg-gray-100"][class~="px-3"][class~="py-2"] { border: 1px solid rgb(var(--ui-line)); border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-subtle) / 0.6); - font-family: var(--ui-font-mono); - font-size: 12.5px; + font-size: 14px; font-variant-numeric: tabular-nums; } /* 2.4 Use-key dialog (data-ui="use-key"). Client and shell tabs: the reference's CodeTabs underline (§8.15), as the - console's other underline tabs (controls.css 7.3): 8 / 10, 12.5 / 600, muted, ink on hover; the current one ink - over a 2px accent rule. On a phone the strip scrolls without a bar and fades out on the right (the reference's + console's other underline tabs (controls.css 7.3): 8 / 10, the template's 14 / 500, muted, ink on hover; the current + one ink over a 2px accent rule. On a phone the strip scrolls without a bar and fades out on the right (the reference's mobile tab strip: a 2.5rem mask). */ html.flat-theme [data-ui="use-key"] > div[class~="overflow-x-auto"][class~="border-b"] { border-bottom-color: rgb(var(--ui-line)); @@ -321,9 +286,9 @@ html.flat-theme [data-ui="use-key"] > div[class~="overflow-x-auto"][class~="bord padding: 8px 10px; border-bottom-color: transparent; color: rgb(var(--ui-muted)); - font-size: 12.5px; - font-weight: 600; - line-height: 18.75px; + font-size: 14px; + font-weight: 500; + line-height: 20px; transition-property: color, border-color; transition-duration: var(--ui-duration); transition-timing-function: var(--ui-ease); @@ -351,11 +316,11 @@ html.flat-theme [data-ui="use-key"] > div[class~="overflow-x-auto"][class~="bord } } -/* Config files: the reference's code block with its tab bar (§8.15 CodeTabs: 8px, the structural line, the surface; - the bar on the grouped fill with the file in Mono 11 and a quiet Copy that turns accent once copied; code Mono - 12.5 / 1.625 ink). The syntax takes the reference's roles: keyword purple, string accent, operator muted, comment - faint (upright: the comments are Chinese, and an oblique CJK face is synthesised), names ink. Upstream drew a dark - terminal here; the account test terminal stays dark. */ +/* Config files: the reference's code block with its tab bar (§8.15 CodeTabs: 8px, the structural line, the surface; the + bar on the grouped fill with the file path (the template's 12px mono) and a quiet Copy that turns accent once copied; + the code in the mono stack at the template's 14px, 1.625, ink). The syntax takes the reference's roles: keyword + purple, string accent, operator muted, comment faint (upright: the comments are Chinese, and an oblique CJK face is + synthesised), names ink. Upstream drew a dark terminal here; the account test terminal stays dark. */ html.flat-theme [data-ui="use-key"] div[class~="theme-dark-surface"][class~="bg-gray-900"] { border: 1px solid rgb(var(--ui-line)); border-radius: var(--ui-radius-lg); @@ -371,9 +336,8 @@ html.flat-theme [data-ui="use-key"] div[class~="theme-dark-surface"][class~="bg- html.flat-theme [data-ui="use-key"] div[class~="theme-dark-surface"][class~="bg-gray-900"] > div:first-child > span { color: rgb(var(--ui-muted)); - font-family: var(--ui-font-mono); - font-size: 11px; - line-height: 16.5px; + font-size: 12px; + line-height: 16px; } html.flat-theme [data-ui="use-key"] div[class~="theme-dark-surface"][class~="bg-gray-900"] > div:first-child > button { @@ -382,9 +346,9 @@ html.flat-theme [data-ui="use-key"] div[class~="theme-dark-surface"][class~="bg- border-radius: var(--ui-radius-md); background-color: transparent; color: rgb(var(--ui-muted)); - font-size: 11px; + font-size: 12px; font-weight: 500; - line-height: 16.5px; + line-height: 16px; transition-property: color, background-color; transition-duration: var(--ui-duration); transition-timing-function: var(--ui-ease); @@ -409,8 +373,8 @@ html.flat-theme [data-ui="use-key"] div[class~="theme-dark-surface"][class~="bg- padding: 14px 16px; color: rgb(var(--ui-ink)); font-family: var(--ui-font-mono); - font-size: 12.5px; - line-height: 20.3125px; + font-size: 14px; + line-height: 22.75px; } html.flat-theme [data-ui="use-key"] div[class~="theme-dark-surface"][class~="bg-gray-900"] > pre span[class~="text-emerald-300"] { @@ -434,8 +398,8 @@ html.flat-theme [data-ui="use-key"] div[class~="theme-dark-surface"][class~="bg- } /* notes: the platform note is the console's neutral callout (data.css §11: the line, the subtle ground, ink-soft - words, the glyph muted); here only the reference's docs-callout geometry (§8.16: 6px, 12 / 14, 13px at 1.625, a - 15px glyph). The missing-group warning keeps its warning colours in the same shape. */ + words, the glyph muted); here only the reference's docs-callout geometry (§8.16: 6px, 12 / 14, the template's + 14 / 20, a 15px glyph). The missing-group warning keeps its warning colours in the same shape. */ html.flat-theme [data-ui="use-key"] > div[class~="bg-blue-50"] { gap: 10px; padding: 12px 14px; @@ -449,8 +413,8 @@ html.flat-theme [data-ui="use-key"] > div[class~="bg-blue-50"] > svg { } html.flat-theme [data-ui="use-key"] > div[class~="bg-blue-50"] > p { - font-size: 13px; - line-height: 21px; + font-size: 14px; + line-height: 20px; } html.flat-theme [data-ui="use-key"] > div[class~="bg-yellow-50"] { @@ -468,9 +432,9 @@ html.flat-theme [data-ui="use-key"] [data-testid="codex-model-catalog"] { /* ================================================================== 3 · redeem (/redeem) */ -/* 3.1 The balance: the reference's stat block (§8.13 detail page: the mono label over a Mono value), neutral - (decision 2: the balance card is not identity). Upstream's green gradient and its icon tile go; the value is - the page's figure (Geist Mono 26 / 600) and the concurrency its note. */ +/* 3.1 The balance: the reference's stat block (§8.13 detail page: the label over the value), neutral (decision 2: the + balance card is not identity). Upstream's green gradient and its icon tile go; the value is the page's figure + (26 / 600 tabular) and the concurrency its note. */ html.flat-theme [data-ui="user-redeem"] > .card:first-child > div[class~="bg-gradient-to-br"] { display: flex; flex-direction: column; @@ -489,15 +453,14 @@ html.flat-theme [data-ui="user-redeem"] > .card:first-child > div[class~="bg-gra color: rgb(var(--ui-label)); font: var(--ui-label-font); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; } html.flat-theme [data-ui="user-redeem"] > .card:first-child > div[class~="bg-gradient-to-br"] > p[class~="text-4xl"] { max-width: 100%; margin-top: 6px; color: rgb(var(--ui-ink)); - font: 600 26px/34px var(--ui-font-mono); - letter-spacing: -0.02em; + font: 600 26px/34px var(--ui-font); font-variant-numeric: tabular-nums; overflow-wrap: anywhere; } @@ -540,7 +503,8 @@ html.flat-theme [data-ui="user-redeem"] form button[type="submit"][class~="w-ful /* 3.3 About codes, the support contact: the console's neutral callout (data.css §11 gives the line, the subtle ground, the ink-soft words, the neutral glyph square); this file keeps the reference's docs-callout geometry (§8.16: 6px, - 12 / 14, 13px at 1.625, list markers faint, a 28px glyph square) and the contact as a Mono value chip. */ + 12 / 14, the template's 14 / 20, list markers faint, a 28px glyph square) and the contact as a value chip (the + template's 12 / 500). */ html.flat-theme :is([data-ui="user-redeem"], [data-testid="profile-shell"]) > .card[class~="bg-primary-50"] { border-radius: var(--ui-radius-md); } @@ -565,16 +529,16 @@ html.flat-theme [data-ui="user-redeem"] > .card[class~="bg-primary-50"] div[clas } html.flat-theme [data-ui="user-redeem"] > .card[class~="bg-primary-50"] h3 { - font-size: 13px; + font-size: 14px; font-weight: 600; - line-height: 21px; + line-height: 20px; } html.flat-theme [data-ui="user-redeem"] > .card[class~="bg-primary-50"] ul { margin-top: 2px; padding-left: 18px; - font-size: 13px; - line-height: 21px; + font-size: 14px; + line-height: 20px; list-style-position: outside; } @@ -592,11 +556,11 @@ html.flat-theme [data-ui="user-redeem"] > .card[class~="bg-primary-50"] ul span[ border-radius: var(--ui-radius-sm); background-color: rgb(var(--ui-surface)); color: rgb(var(--ui-ink)); - font: 500 11.5px/18px var(--ui-font-mono); + font: 500 12px/16px var(--ui-font); } -/* 3.4 Recent activity: the reference's list inside a card (the benchmarks card: rows on 1px lines, the name 13px - ink, the meta muted, the figure Mono 13 / 600 on the right). Type tiles are the neutral 28px square; a balance +/* 3.4 Recent activity: the reference's list inside a card (the benchmarks card: rows on 1px lines, the name 14px + ink, the meta muted, the figure 14 / 600 tabular on the right). Type tiles are the neutral 28px square; a balance change keeps its sign colour (credit success, debit danger), the other types are ink (decision 2). */ html.flat-theme [data-ui="user-redeem"] > .card:last-child > div[class~="p-6"] { padding: 4px 16px 14px; @@ -636,8 +600,8 @@ html.flat-theme [data-ui="user-redeem"] > .card:last-child div[class~="space-y-3 html.flat-theme [data-ui="user-redeem"] > .card:last-child div[class~="space-y-3"] > div > div:first-child p[class~="font-medium"] { color: rgb(var(--ui-ink)); - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; } html.flat-theme [data-ui="user-redeem"] > .card:last-child div[class~="space-y-3"] > div > div:first-child p[class~="text-xs"] { @@ -647,10 +611,9 @@ html.flat-theme [data-ui="user-redeem"] > .card:last-child div[class~="space-y-3 } html.flat-theme [data-ui="user-redeem"] > .card:last-child div[class~="space-y-3"] > div > div[class~="text-right"] > p:first-child { - font-family: var(--ui-font-mono); - font-size: 13px; + font-size: 14px; font-weight: 600; - line-height: 19.5px; + line-height: 20px; font-variant-numeric: tabular-nums; } @@ -660,8 +623,8 @@ html.flat-theme [data-ui="user-redeem"] > .card:last-child div[class~="space-y-3 html.flat-theme [data-ui="user-redeem"] > .card:last-child div[class~="space-y-3"] > div > div[class~="text-right"] > p:not(:first-child) { color: rgb(var(--ui-faint)); - font-size: 11px; - line-height: 16.5px; + font-size: 12px; + line-height: 16px; } html.flat-theme [data-ui="user-redeem"] > .card:last-child div[class~="space-y-3"] > div > div[class~="text-right"] > p[class~="font-mono"] { @@ -696,8 +659,7 @@ html.flat-theme [data-ui="user-redeem"] > .card:last-child div[class~="mt-4"][cl min-height: var(--ui-control-h); padding-top: 0; padding-bottom: 0; - font-family: var(--ui-font-mono); - font-size: 12.5px; + font-size: 14px; } /* 3.5 After a redeem: the success and failure panels keep their status colours in the callout shape (6px, 12 / 14, @@ -725,9 +687,9 @@ html.flat-theme [data-ui="user-redeem"] > .card:is([class~="bg-emerald-50"], [cl /* 4.1 The overview: the reference's detail-page head (§7.1 detail: a title row, then the stat blocks on the canvas, 20px apart) instead of upstream's tinted hero card. The avatar is the reference's letter square (§8.8: - the control line, the grouped fill, Geist Mono 600 ink-soft) at 48px; the name is the page title 20 / 600; the - role chip is a category (neutral), the status keeps its colour; the figures are detail stat blocks (8px, 1px - line, 12 / 16, the mono label, Mono 15 / 600), as wide as their content like the reference's. */ + the control line, the grouped fill, 600 ink-soft) at 48px; the name is the page title 20 / 600; the role chip is a + category (neutral), the status keeps its colour; the figures are detail stat blocks (8px, 1px line, 12 / 16, the + label, the template's 18 / 28 / 600 value), as wide as their content like the reference's. */ html.flat-theme [data-testid="profile-overview-hero"] { overflow: visible; border: 0; @@ -770,7 +732,7 @@ html.flat-theme [data-testid="profile-basics-panel"] div[class~="h-16"][class~=" background-image: none; box-shadow: none; color: rgb(var(--ui-ink-soft)); - font: 600 18px/1 var(--ui-font-mono); + font: 600 18px/1 var(--ui-font); } html.flat-theme [data-testid="profile-overview-hero"] > div > div > div:last-child > div:first-child > :not([hidden]) ~ :not([hidden]) { @@ -791,8 +753,8 @@ html.flat-theme [data-testid="profile-overview-hero"] .badge.badge-primary { html.flat-theme [data-testid="profile-overview-hero"] p[class~="truncate"][class~="text-sm"] { color: rgb(var(--ui-muted)); - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; } html.flat-theme [data-testid="profile-overview-hero"] span[class~="rounded-full"][class~="ring-1"] { @@ -826,13 +788,13 @@ html.flat-theme [data-testid^="profile-overview-metric-"] > p:first-child { color: rgb(var(--ui-label)); font: var(--ui-label-font); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; } html.flat-theme [data-testid^="profile-overview-metric-"] > p:last-child { margin-top: 0; color: rgb(var(--ui-ink)); - font: 600 15px/22.5px var(--ui-font-mono); + font: 600 18px/28px var(--ui-font); font-variant-numeric: tabular-nums; } @@ -861,14 +823,14 @@ html.flat-theme [data-testid="profile-basics-panel"] > div[class~="grid"] > div[ html.flat-theme [data-testid="profile-basics-panel"] > div[class~="grid"] > div[class~="rounded-3xl"] p[class~="text-sm"][class~="font-semibold"] { color: rgb(var(--ui-ink)); - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; } /* sign-in bindings: rows on the surface (8px, 1px line, 12 / 14); a provider's letter square is identity (the shared platform-chip hook: the provider's own hue at >= 4.5:1 on its fill; upstream's folded warning tint on LinuxDo was 4.43), the e-mail square has no hue and falls back to the neutral chip; the square is 32px at 6px, the letter - Geist Mono 12 / 600 */ + 12 / 600 */ html.flat-theme [data-testid="profile-auth-bindings-panel"] div[class~="rounded-2xl"][class~="border"][class~="p-4"] { padding: 12px 14px; border-color: rgb(var(--ui-line)); @@ -882,7 +844,7 @@ html.flat-theme [data-testid="profile-auth-bindings-panel"] div[class~="h-11"][c height: 32px; padding: 0; border-radius: var(--ui-radius-md); - font: 600 12px/1 var(--ui-font-mono); + font: 600 12px/1 var(--ui-font); letter-spacing: 0; } @@ -893,14 +855,14 @@ html.flat-theme [data-testid="profile-auth-bindings-panel"] div[class~="h-11"][c html.flat-theme [data-testid="profile-auth-bindings-panel"] div[class~="rounded-2xl"][class~="border"][class~="p-4"] h3 { color: rgb(var(--ui-ink)); - font-size: 13px; - font-weight: 600; - line-height: 19.5px; + font-size: 14px; + font-weight: 500; + line-height: 20px; } html.flat-theme [data-testid="profile-auth-bindings-panel"] div[class~="rounded-2xl"][class~="border"][class~="p-4"] :is(p, div)[class~="text-sm"] { - font-size: 12.5px; - line-height: 18.75px; + font-size: 14px; + line-height: 20px; } html.flat-theme [data-testid="profile-auth-bindings-panel"] div[class~="rounded-2xl"][class~="border"][class~="p-4"] div[class~="space-y-3"] > :not([hidden]) ~ :not([hidden]) { @@ -914,11 +876,11 @@ html.flat-theme [data-testid="profile-shell"] section.card div[class~="rounded-2 border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-surface)); color: rgb(var(--ui-muted)); - font-size: 12.5px; + font-size: 14px; } /* 4.3 Support contact: the console's neutral callout (data.css §11) at the docs-callout geometry, the chat glyph in a - 32px square, the contact in Mono */ + 32px square, the contact at the template's 14 / 500 */ html.flat-theme [data-testid="profile-shell"] > .card[class~="bg-primary-50"] { padding: 12px 14px; } @@ -942,13 +904,13 @@ html.flat-theme [data-testid="profile-shell"] > .card[class~="bg-primary-50"] > } html.flat-theme [data-testid="profile-shell"] > .card[class~="bg-primary-50"] h3 { - font-size: 13px; + font-size: 14px; font-weight: 600; - line-height: 19.5px; + line-height: 20px; } html.flat-theme [data-testid="profile-shell"] > .card[class~="bg-primary-50"] h3 + p { - font: 500 12.5px/18.75px var(--ui-font-mono); + font: 500 14px/20px var(--ui-font); } /* 4.4 Two-factor and passkeys: the state glyph sits in the reference's neutral 32px square (6px, 1px line); an @@ -979,21 +941,21 @@ html.flat-theme [data-testid="profile-shell"] > .card div[class~="flex-shrink-0" html.flat-theme [data-testid="profile-shell"] > .card div[class~="flex-shrink-0"][class~="rounded-full"][class~="p-3"] + div > p[class~="font-medium"] { color: rgb(var(--ui-ink)); - font-size: 13px; - font-weight: 600; - line-height: 19.5px; + font-size: 14px; + font-weight: 500; + line-height: 20px; } html.flat-theme [data-testid="profile-shell"] > .card div[class~="flex-shrink-0"][class~="rounded-full"][class~="p-3"] + div > p[class~="text-sm"] { - font-size: 12.5px; - line-height: 18.75px; + font-size: 14px; + line-height: 20px; } html.flat-theme [data-testid="profile-shell"] > .card div[class~="rounded-lg"][class~="border-dashed"] { border-color: rgb(var(--ui-line-strong)); border-radius: var(--ui-radius-lg); color: rgb(var(--ui-muted)); - font-size: 13px; + font-size: 14px; } html.flat-theme [data-testid="profile-shell"] > .card svg[class~="text-primary-500"] { @@ -1008,9 +970,10 @@ html.flat-theme [data-testid="profile-shell"] > .card form[class~="rounded-lg"][ /* ================================================================== 5 · affiliate (/affiliate) */ -/* 5.1 The four figures: the reference's StatTile (§8.13 /logs: 1px line, 6px, surface, 8 / 12, the mono label, the - value 15 / 600 tabular ink, the note 11px muted, 76px; tiles 10px apart). The rebate rate's glyph and the green - figures are decoration (decision 2): neutral; a frozen amount keeps its warning colour. */ +/* 5.1 The four figures: the reference's StatTile (§8.13 /logs: 1px line, 6px, surface, 8 / 12, the label, the value + at the template's text-2xl font-semibold (24 / 32 / 600) tabular ink, the note 12px muted, 76px; tiles 10px apart). + The rebate rate's glyph and the green figures are decoration (decision 2): neutral; a frozen amount keeps its + warning colour. */ html.flat-theme [data-ui="user-affiliate"] > div[class~="grid"]:not([data-uipad][data-uipad]) { gap: 10px; } @@ -1031,7 +994,7 @@ html.flat-theme [data-ui="user-affiliate"] > div[class~="grid"] > .card[class~=" color: rgb(var(--ui-label)); font: var(--ui-label-font); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; } html.flat-theme [data-ui="user-affiliate"] > div[class~="grid"] > .card[class~="p-5"] > p:first-child > svg { @@ -1041,9 +1004,9 @@ html.flat-theme [data-ui="user-affiliate"] > div[class~="grid"] > .card[class~=" html.flat-theme [data-ui="user-affiliate"] > div[class~="grid"] > .card[class~="p-5"] > p[class~="text-2xl"] { margin-top: 0; color: rgb(var(--ui-ink)); - font-size: 15px; + font-size: 24px; font-weight: 600; - line-height: 22.5px; + line-height: 32px; font-variant-numeric: tabular-nums; } @@ -1057,21 +1020,21 @@ html.flat-theme [data-ui="user-affiliate"] > div[class~="grid"] > .card[class~=" html.flat-theme [data-ui="user-affiliate"] > div[class~="grid"] > .card[class~="p-5"] > p[class~="text-xs"] { margin-top: 0; color: rgb(var(--ui-muted)); - font-size: 11px; - line-height: 16.5px; + font-size: 12px; + line-height: 16px; } html.flat-theme [data-ui="user-affiliate"] > div[class~="grid"] > .card[class~="p-5"] > p[class~="text-xs"][class~="text-amber-600"] { color: rgb(var(--ui-warning)); } -/* 5.2 Invite code and link: mono labels over the reference's snippet well (§8.15 snippet: 6px, the canvas, Mono - 12.5), the copy buttons as they are; the how-to is the neutral docs callout. */ +/* 5.2 Invite code and link: labels over the reference's snippet well (§8.15 snippet: 6px, the canvas, the code in the + mono stack at the template's 14px), the copy buttons as they are; the how-to is the neutral docs callout. */ html.flat-theme [data-ui="user-affiliate"] > .card div[class~="space-y-2"] > p[class~="text-sm"][class~="font-medium"] { color: rgb(var(--ui-label)); font: var(--ui-label-font); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; } html.flat-theme [data-ui="user-affiliate"] > .card div[class~="space-y-2"] > div[class~="rounded-xl"][class~="border"] { @@ -1085,13 +1048,13 @@ html.flat-theme [data-ui="user-affiliate"] > .card div[class~="space-y-2"] > div html.flat-theme [data-ui="user-affiliate"] > .card div[class~="space-y-2"] > div[class~="rounded-xl"][class~="border"] > code { color: rgb(var(--ui-ink)); font-family: var(--ui-font-mono); - font-size: 12.5px; + font-size: 14px; line-height: 20px; } html.flat-theme [data-ui="user-affiliate"] > .card div[class~="space-y-2"] > div[class~="rounded-xl"][class~="border"] > code[class~="font-semibold"] { font-weight: 600; - letter-spacing: 0.04em; + letter-spacing: normal; } html.flat-theme [data-ui="user-affiliate"] > .card div[class~="space-y-2"] > div[class~="rounded-xl"][class~="border"] > code:not([class~="font-semibold"]) { @@ -1111,28 +1074,29 @@ html.flat-theme [data-ui="user-affiliate"] > .card > div[class~="rounded-xl"][cl } html.flat-theme [data-ui="user-affiliate"] > .card > div[class~="rounded-xl"][class~="bg-primary-50"] > p { - font-size: 13px; - font-weight: 600; - line-height: 21px; + font-size: 14px; + font-weight: 500; + line-height: 20px; } html.flat-theme [data-ui="user-affiliate"] > .card > div[class~="rounded-xl"][class~="bg-primary-50"] > ul { margin-top: 2px; - font-size: 13px; - line-height: 21px; + font-size: 14px; + line-height: 20px; } html.flat-theme [data-ui="user-affiliate"] > .card > div[class~="rounded-xl"][class~="bg-primary-50"] > ul > :not([hidden]) ~ :not([hidden]) { margin-top: 2px; } -/* 5.3 Transfer: a warning note keeps its colour at the reference's note size; the invitees' empty list is the - reference's dashed panel; the rebate figures in the list are Mono ink (decision 2). */ +/* 5.3 Transfer: a warning note keeps its colour at the template's 14px; the invitees' empty list is the reference's + dashed panel; the list's header is the console's table header (12 / 16 / 500, uppercase, 0.05em) and its rebate + figures keep their template green (a credit), tabular. */ html.flat-theme [data-ui="user-affiliate"] > .card > p[class~="text-amber-600"] { margin-top: 8px; color: rgb(var(--ui-warning)); - font-size: 12.5px; - line-height: 18.75px; + font-size: 14px; + line-height: 20px; } html.flat-theme [data-ui="user-affiliate"] > .card > div[class~="border-dashed"] { @@ -1141,7 +1105,7 @@ html.flat-theme [data-ui="user-affiliate"] > .card > div[class~="border-dashed"] border-color: rgb(var(--ui-line-strong)); border-radius: var(--ui-radius-lg); color: rgb(var(--ui-muted)); - font-size: 13px; + font-size: 14px; } html.flat-theme [data-ui="user-affiliate"] > .card table thead tr { @@ -1152,7 +1116,7 @@ html.flat-theme [data-ui="user-affiliate"] > .card table thead th { padding: 10px 12px; color: rgb(var(--ui-label)); font: var(--ui-label-font); - letter-spacing: var(--ui-label-tracking); + letter-spacing: 0.05em; text-transform: uppercase; } @@ -1161,8 +1125,6 @@ html.flat-theme [data-ui="user-affiliate"] > .card table tbody tr { } html.flat-theme [data-ui="user-affiliate"] > .card table tbody td[class~="text-emerald-600"] { - color: rgb(var(--ui-ink)); - font-family: var(--ui-font-mono); font-variant-numeric: tabular-nums; } @@ -1184,22 +1146,22 @@ html.flat-theme [data-ui="user-purchase"] > .card[class~="p-5"] > p[class~="text color: rgb(var(--ui-label)); font: var(--ui-label-font); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; } html.flat-theme [data-ui="user-purchase"] > .card[class~="p-5"] > p[class~="text-base"] { margin-top: 4px; color: rgb(var(--ui-ink)); - font-size: 15px; + font-size: 16px; font-weight: 600; - line-height: 22.5px; + line-height: 24px; } html.flat-theme [data-ui="user-purchase"] > .card[class~="p-5"] > p[class~="text-green-600"] { color: rgb(var(--ui-muted)); - font-size: 12.5px; + font-size: 14px; font-weight: 400; - line-height: 18.75px; + line-height: 20px; font-variant-numeric: tabular-nums; } @@ -1209,15 +1171,14 @@ html.flat-theme [data-ui="user-purchase"] > .card[class~="py-16"] { html.flat-theme [data-ui="user-purchase"] > .card[class~="py-16"] > :is(p, svg) { color: rgb(var(--ui-muted)); - font-size: 13px; + font-size: 14px; } -/* the confirm step (a chosen plan): the price and the rate are figures, ink Mono (decision 2: a figure is not +/* the confirm step (a chosen plan): the price and the rate are figures, ink 600 tabular (decision 2: a figure is not identity; the plan's platform chip keeps its colour); the totals' pay figure ink too */ html.flat-theme [data-ui="user-purchase"] > .card span[class~="text-3xl"][class~="font-bold"], html.flat-theme [data-ui="user-purchase"] > .card span[class~="text-lg"][class~="font-bold"] { color: rgb(var(--ui-ink)); - font-family: var(--ui-font-mono); font-weight: 600; font-variant-numeric: tabular-nums; } @@ -1235,7 +1196,8 @@ html.flat-theme [data-ui="user-subscriptions"] [data-ui~="platform-text"][class~ } /* the plan cards (SubscriptionPlanCard): the reference's card (8px, the structural line, no lift or shadow), the - platform rule on top 2px in the platform colour, the price a Mono figure, subscribe = the positive CTA (accent) */ + platform rule on top 2px in the platform colour, the price a 22 / 600 figure (the template's tracking-tight), + subscribe = the positive CTA (accent) */ html.flat-theme [data-ui="user-purchase"] div[data-ui~="plan-card"][data-ui~="plan-card"] { border-color: rgb(var(--ui-line)); border-radius: var(--ui-radius-lg); @@ -1251,8 +1213,7 @@ html.flat-theme [data-ui="user-purchase"] [data-ui~="platform-text"][class~="h-1 html.flat-theme [data-ui="user-purchase"] div[data-ui~="plan-card"][data-ui~="plan-card"] span[class~="text-2xl"][class~="font-extrabold"] { color: rgb(var(--ui-ink)); - font: 600 22px/28px var(--ui-font-mono); - letter-spacing: -0.02em; + font: 600 22px/28px var(--ui-font); } html.flat-theme [data-ui="user-purchase"] div[data-ui~="plan-card"][data-ui~="plan-card"] > div > button[type="button"][class~="w-full"] { @@ -1299,7 +1260,7 @@ html.flat-theme [data-ui="user-orders"] > .table-wrapper + [data-ui="pagination" } /* 6.3 My subscriptions: the empty page is the reference's empty panel (dashed control outline, 8px, the 28px - neutral glyph square, title 14 / 500 ink-soft, note 13px muted), like the console's other empty states. */ + neutral glyph square, title 14 / 500 ink-soft, note 14px muted), like the console's other empty states. */ html.flat-theme [data-ui="user-subscriptions"] > .card[class~="p-12"] { padding: 40px 16px; border: 1px dashed rgb(var(--ui-line-strong)); @@ -1332,8 +1293,8 @@ html.flat-theme [data-ui="user-subscriptions"] > .card[class~="p-12"] > h3 { html.flat-theme [data-ui="user-subscriptions"] > .card[class~="p-12"] > p { color: rgb(var(--ui-muted)); - font-size: 13px; - line-height: 1.625; + font-size: 14px; + line-height: 20px; } /* the subscription cards (one per plan held): the reference's card, the platform chip and dot are identity, the @@ -1371,8 +1332,8 @@ html.flat-theme .custom-page-layout > .card { } /* 7.3 Model plaza (standalone, data-ui="model-plaza"): the public pages' top bar (settings-ops-public.css: sticky - 64px, a hairline, the canvas at 92 % with a 10px blur; the logo 32px, the site name as a Mono wordmark; the - console's small ink button); the title is the reference's docs H1 (28 / 600, -0.025em) over a 14px muted lead; + 64px, a hairline, the canvas at 92 % with a 10px blur; the logo 32px, the site name 14 / 500 ink; the console's small + ink button); the title is the reference's docs H1 (28 / 600, the template's tracking-tight) over a 14px muted lead; the load error keeps its danger colours in the callout shape. */ html.flat-theme [data-ui="model-plaza"] { background-color: rgb(var(--ui-canvas)); @@ -1406,9 +1367,9 @@ html.flat-theme [data-ui="model-plaza"] > header > div > div:first-child > span[ html.flat-theme [data-ui="model-plaza"] > header > div > div:first-child > span[class~="truncate"] { color: rgb(var(--ui-ink)); - font: 500 13px/20px var(--ui-font-mono); - letter-spacing: 0.08em; - text-transform: uppercase; + font: 500 14px/20px var(--ui-font); + letter-spacing: normal; + text-transform: none; } html.flat-theme [data-ui="model-plaza"] > header > div > a { @@ -1419,9 +1380,9 @@ html.flat-theme [data-ui="model-plaza"] > header > div > a { background-color: rgb(var(--ui-solid)); background-image: none; color: rgb(var(--ui-on-solid)); - font-size: 13px; + font-size: 14px; font-weight: 600; - line-height: 19.5px; + line-height: 20px; box-shadow: none; transform: none; } @@ -1449,7 +1410,6 @@ html.flat-theme [data-ui="model-plaza"] > main > div > div:first-child > h1 { font-size: 28px; font-weight: 600; line-height: 42px; - letter-spacing: -0.025em; } html.flat-theme [data-ui="model-plaza"] > main > div > div:first-child > h1 + p { @@ -1465,13 +1425,13 @@ html.flat-theme [data-ui="model-plaza"] div[class~="rounded-2xl"][class~="border border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-danger-soft)); color: rgb(var(--ui-danger)); - font-size: 13px; + font-size: 14px; } -/* 7.5 Available channels (a plain table, not DataTable): the header is the console's table header (data.css: 40px, - Geist Mono 500 at the label size on a 19.5px line, the table-head grey, the surface under it, the structural - line; REF §8.7), the empty and loading states the console's (a 20px faint glyph, the title 14 / 500 ink-soft, - 48px of air). */ +/* 7.5 Available channels (a plain table, not DataTable): the header is the console's table header of the + TablePageLayout family (data.css: 40px, 500 at the family's size and line, uppercase, 0.05em, its grey, the surface + under it, the structural line; REF §8.7), the empty and loading states the console's (a 20px faint glyph, the title + 14 / 500 ink-soft, 48px of air). */ html.flat-theme [data-ui="user-available-channels"] .table-wrapper > table > thead > tr { border-bottom-color: rgb(var(--ui-line)); background-color: transparent; @@ -1482,8 +1442,8 @@ html.flat-theme [data-ui="user-available-channels"] .table-wrapper > table > the padding: 10px 12px; background-color: rgb(var(--ui-surface)); color: rgb(var(--ui-table-head)); - font: 500 var(--ui-label-size) / 19.5px var(--ui-font-mono); - letter-spacing: var(--ui-label-tracking); + font: 500 var(--ui-th-size) / var(--ui-th-leading) var(--ui-font); + letter-spacing: 0.05em; text-transform: uppercase; text-align: left; } @@ -1532,7 +1492,7 @@ html.flat-theme [data-ui="user-redeem"] svg[class~="animate-spin"][class~="text- a percentage and a bar, gone once covered). It is a state, but the state of a running job, not of a channel: the reference's status chip gives "running" the neutral tone (surface-subtle, ink-faint; colour is kept for outcomes: passed, failed, rate-limited, complete). So it takes the console's neutral callout ground, the title ink, the note - ink-soft, the percentage Mono, and an accent bar on the structural line (palette C marks); the channels' own health + ink-soft, the percentage tabular, and an accent bar on the structural line (palette C marks); the channels' own health keeps its colours. (The coordinator asked this file to decide; the page's other rules are the ops and channels agent's.) */ html.flat-theme [data-ui="monitor-v2"] div[role="status"][class~="bg-blue-50/90"] { @@ -1542,8 +1502,8 @@ html.flat-theme [data-ui="monitor-v2"] div[role="status"][class~="bg-blue-50/90" html.flat-theme [data-ui="monitor-v2"] div[role="status"][class~="bg-blue-50/90"] p[class~="font-semibold"] { color: rgb(var(--ui-ink)); - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; } html.flat-theme [data-ui="monitor-v2"] div[role="status"][class~="bg-blue-50/90"] p[class~="text-xs"] { @@ -1552,7 +1512,6 @@ html.flat-theme [data-ui="monitor-v2"] div[role="status"][class~="bg-blue-50/90" html.flat-theme [data-ui="monitor-v2"] div[role="status"][class~="bg-blue-50/90"] span[class~="tabular-nums"] { color: rgb(var(--ui-ink-soft)); - font-family: var(--ui-font-mono); } html.flat-theme [data-ui="monitor-v2"] div[role="status"][class~="bg-blue-50/90"] div[role="progressbar"] { diff --git a/frontend/src/styles/console/pages/users-groups-proxies-keys.css b/frontend/src/styles/console/pages/users-groups-proxies-keys.css index 146748f9acfa..d0f12f1d3404 100644 --- a/frontend/src/styles/console/pages/users-groups-proxies-keys.css +++ b/frontend/src/styles/console/pages/users-groups-proxies-keys.css @@ -13,9 +13,11 @@ * data-ui~="platform-chip" / "category-chip" contract), the header and the gutter (shell.css), the approved two-line cells * (behaviour.css B1.4-B1.10) and the icon-only actions of groups and proxies (behaviour.css B2.1, overlays.css 2.6). * - * Owner decisions applied here (WAVE2, 2026-09-29): platform identity keeps its platform colours; a category (role, billing - * type, exclusive / public, protocol, code type, notify mode) is the neutral chip; colour otherwise only for status and the - * accent (links, positive CTAs, selection); decorative number colours and tinted identity cards are neutral. + * Owner decisions applied here (WAVE2, 2026-09-29): platform identity keeps its platform colours; colour otherwise only for + * status and the accent (links, positive CTAs, selection); tinted identity cards are neutral. Text follows official + * sub2api (2026-10): the table cells' text, figures and category chips are the templates' own (official sizes, weights + * and colours; data.css draws the category chips as the templates' tags), so the rules here set only layout there, + * apart from the row actions, the identity tiles and the few chips the theme redraws. * * Page roots (data-ui on each view's , one attribute each; "mgmt-page" is the group's shared token): * users-page, groups-page, proxies-page, subscriptions-page, redeem-page, promo-page, announcements-page. @@ -185,7 +187,7 @@ html.flat-theme [data-ui~="mgmt-page"] .table-wrapper td[data-column="actions"] html.flat-theme [data-ui~="mgmt-page"] .table-wrapper td[data-column="actions"] button[class~="flex-col"] > span[class~="text-xs"] { font-size: 12px; - line-height: 18px; + line-height: 16px; white-space: nowrap; } @@ -207,15 +209,9 @@ html.flat-theme :is([data-ui~="announcements-page"], [data-ui~="promo-page"]) .t /* ================================================================== 3 · shared cell parts */ -/* Names: the reference's first-column name, 13 / 600 ink (REF §5 模型名). */ -html.flat-theme :is([data-ui~="users-page"] td[data-column="email"] > div > span, [data-ui~="groups-page"] td[data-column="name"] > span, [data-ui~="proxies-page"] td[data-column="name"] > span, [data-ui~="announcements-page"] td[data-column="title"] > div > div:first-child > span) { - font-weight: 600; - color: rgb(var(--ui-ink)); -} - /* A user's letter avatar is identity, not a state: the reference's letter tile (REF §8.8: a small square, the control - line, the subtle fill, Geist Mono 600 in ink-soft) instead of upstream's accent-tinted circle; the letter stays. Users - table (email) and subscriptions table (user). */ + line, the subtle fill, the letter 12 / 600 in ink-soft) instead of upstream's accent-tinted circle; the letter stays. + Users table (email) and subscriptions table (user). */ html.flat-theme :is([data-ui~="users-page"] td[data-column="email"], [data-ui~="subscriptions-page"] td[data-column="user"]) > div > div[class~="rounded-full"][class~="bg-primary-100"] { flex: none; width: 1.5rem; @@ -226,12 +222,12 @@ html.flat-theme :is([data-ui~="users-page"] td[data-column="email"], [data-ui~=" } html.flat-theme :is([data-ui~="users-page"] td[data-column="email"], [data-ui~="subscriptions-page"] td[data-column="user"]) > div > div[class~="rounded-full"][class~="bg-primary-100"] > span { - font: 600 11px/1 var(--ui-font-mono); + font: 600 12px/1 var(--ui-font); color: rgb(var(--ui-ink-soft)); } -/* Status as the reference's status dot (REF §8.8: 6px dot, 12px ink-soft label, 6px apart; StatusBadge's look), for the - hand-built dot of the users table. */ +/* Status as the reference's status dot (REF §8.8: 6px dot, 6px from the template's 14px label; StatusBadge's look), + for the hand-built dot of the users table. */ html.flat-theme [data-ui~="users-page"] td[data-column="status"] > div { gap: 6px; } @@ -250,22 +246,11 @@ html.flat-theme [data-ui~="users-page"] td[data-column="status"] > div > span:fi background-color: rgb(var(--ui-dot-bad)); } -html.flat-theme [data-ui~="users-page"] td[data-column="status"] > div > span:last-child { - color: rgb(var(--ui-ink-soft)); - font-size: 12px; - line-height: 18px; -} - -/* Figures in Geist Mono tabular (REF §8.7 numeric cells), for the columns data.css does not name. */ +/* Figures tabular (REF §8.7 numeric cells), for the columns data.css does not name; their type is the template's. */ html.flat-theme :is([data-ui~="redeem-page"] td[data-column="value"] > span, [data-ui~="promo-page"] td[data-column="usage"] > span, [data-ui~="groups-page"] td[data-column="usage"] span[class~="font-medium"], [data-ui~="groups-page"] td[data-column="account_count"] span[class~="font-medium"], [data-ui~="groups-page"] td[data-column="billing_type"] span:is([class*="text-emerald-"], [class*="text-amber-"], [class*="text-red-"], [class~="text-gray-400"], [class~="text-gray-600"]):not([data-ui])) { - font-family: var(--ui-font-mono); font-variant-numeric: tabular-nums; } -html.flat-theme :is([data-ui~="redeem-page"] td[data-column="value"] > span, [data-ui~="promo-page"] td[data-column="usage"] > span) { - font-size: 12.5px; -} - /* Copy buttons beside a code (redeem codes, promo codes, proxy addresses): a quiet glyph, faint to ink on hover; the copied check keeps the success colour. */ html.flat-theme :is([data-ui~="redeem-page"], [data-ui~="promo-page"]) td[data-column="code"] > div > button:not([class~="text-green-500"]), @@ -288,35 +273,23 @@ html.flat-theme :is([data-ui~="redeem-page"], [data-ui~="promo-page"]) td[data-c height: 14px; } -/* codes themselves: Geist Mono 12.5 ink (REF §8.7 model id column) */ +/* codes themselves: the template's mono code at 14px, ink (REF §8.7 model id column) */ html.flat-theme :is([data-ui~="redeem-page"], [data-ui~="promo-page"]) td[data-column="code"] code { - font-size: 12.5px; + font-size: 14px; color: rgb(var(--ui-ink)); } /* ================================================================== 4 · users table */ -/* the username is a name: the primary role of the table's text (data.css 12; its class says secondary) */ -html.flat-theme .table-wrapper[data-table="users"] td[data-column="username"] > span[class~="text-gray-700"] { - --theme-text-gray-100: var(--ui-data-primary); --theme-text-gray-200: var(--ui-data-primary); --theme-text-gray-300: var(--ui-data-primary); - --theme-text-gray-400: var(--ui-data-primary); --theme-text-gray-500: var(--ui-data-primary); --theme-text-gray-600: var(--ui-data-primary); - --theme-text-gray-700: var(--ui-data-primary); --theme-text-gray-800: var(--ui-data-primary); --theme-text-gray-900: var(--ui-data-primary); - --theme-text-gray-950: var(--ui-data-primary); --theme-text-dark-100: var(--ui-data-primary); --theme-text-dark-200: var(--ui-data-primary); - --theme-text-dark-300: var(--ui-data-primary); --theme-text-dark-400: var(--ui-data-primary); --theme-text-dark-500: var(--ui-data-primary); - --theme-text-dark-600: var(--ui-data-primary); --ui-ink-soft: var(--ui-data-primary); -} - -/* Balance: the amount is a link to the balance history (the dashed underline in the control line, ink on hover); 「充值」 - under it (B1.8) is the reference's link, the accent text at 12px, underlined on hover (upstream's hover fill goes). */ +/* Balance: the amount is a link to the balance history (the template's 500 ink and accent hover, the dashed underline + in the control line); 「充值」 under it (B1.8) is the reference's link, the accent text at 12px, underlined on hover + (upstream's hover fill goes). */ html.flat-theme [data-ui~="users-page"] td[data-column="balance"] div[class~="group"] > button { - color: rgb(var(--ui-ink)); - font-weight: 500; text-decoration-color: rgb(var(--ui-line-focus)); text-underline-offset: 3px; } html.flat-theme [data-ui~="users-page"] td[data-column="balance"] div[class~="group"] > button:hover { - color: rgb(var(--ui-ink)); text-decoration-color: currentColor; } @@ -337,25 +310,13 @@ html.flat-theme [data-ui~="users-page"] td[data-column="balance"] > div > button text-underline-offset: 2px; } -/* Groups (optional column): exclusive / public are categories, so the shield, globe and count are neutral. */ -html.flat-theme [data-ui~="users-page"] td[data-column="groups"] svg { - color: rgb(var(--ui-faint)); -} - -html.flat-theme [data-ui~="users-page"] td[data-column="groups"] span[class~="font-medium"] { - color: rgb(var(--ui-ink)); - font-family: var(--ui-font-mono); -} - -/* 「无订阅」 (optional subscriptions column): the reference's neutral value chip (4px radius, subtle fill, muted). */ +/* 「无订阅」 (optional subscriptions column): the reference's neutral value chip (4px radius, subtle fill) holding the + template's 12px meta text. */ html.flat-theme [data-ui~="users-page"] td[data-column="subscriptions"] > span[class~="rounded-md"] { gap: 4px; padding: 1px 6px; border-radius: var(--ui-radius-sm); background-color: rgb(var(--ui-subtle)); - color: rgb(var(--ui-muted)); - font-size: 11px; - line-height: 16px; } html.flat-theme [data-ui~="users-page"] td[data-column="subscriptions"] > span[class~="rounded-md"] > svg { @@ -365,28 +326,10 @@ html.flat-theme [data-ui~="users-page"] td[data-column="subscriptions"] > span[c /* ================================================================== 5 · groups table */ -/* Billing type: the category chip (data.css) and, for a subscription group, its limits in 12px muted lines. */ -html.flat-theme [data-ui~="groups-page"] td[data-column="billing_type"] > div > div { - color: rgb(var(--ui-muted)); - font-size: 12px; - line-height: 16px; -} - -/* Accounts: 「可用 / 限流 / 总量」 as label + figure lines (12px, the figure Geist Mono ink; the rate-limited figure keeps the - warning colour); upstream's grey 「个账号」 boxes are plain unit text (a unit is not a chip). */ -html.flat-theme [data-ui~="groups-page"] td[data-column="account_count"] > div { - font-size: 12px; - line-height: 16px; -} - -html.flat-theme [data-ui~="groups-page"] td[data-column="account_count"] > div > div > span:first-child { - color: rgb(var(--ui-muted)); -} - -html.flat-theme [data-ui~="groups-page"] td[data-column="account_count"] > div > div > span[class~="font-medium"]:not([class*="text-amber-"]) { - color: rgb(var(--ui-ink)); -} - +/* Billing type: the category chip (data.css) and, for a subscription group, its limits in the template's 12px meta + lines. Accounts: 「可用 / 限流 / 总量」 as the template's label + figure lines (12px; the available figure green, the + rate-limited one the warning colour, the total grey); upstream's grey 「个账号」 boxes are plain unit text (a unit is + not a chip). */ html.flat-theme [data-ui~="groups-page"] td[data-column="account_count"] > div > div > span[class~="font-medium"][class*="text-amber-"] { color: rgb(var(--ui-warning)); } @@ -400,31 +343,16 @@ html.flat-theme [data-ui~="groups-page"] td[data-column="account_count"] > div > font-weight: 400; } -/* Usage: 「今日 / 昨日 / 累计」 lines, 12 / 16 (the reference's fine print keeps these stacked figures as tight as - upstream's, so the row does not grow); the label muted, the figure Geist Mono ink-soft. */ -html.flat-theme [data-ui~="groups-page"] td[data-column="usage"] > div > div { - font-size: 12px; - line-height: 16px; -} - -html.flat-theme [data-ui~="groups-page"] td[data-column="usage"] > div > div > span:first-child { - color: rgb(var(--ui-muted)); -} - -html.flat-theme [data-ui~="groups-page"] td[data-column="usage"] span[class~="font-medium"] { - color: rgb(var(--ui-ink-soft)); -} - /* ================================================================== 6 · IP management (proxies) table */ -/* Address: the value chip (style.css .code) at the reference's Mono 12; the copy and format glyphs under it (B1.10). */ +/* Address: the value chip (style.css .code) at the template's 12px mono; the copy and format glyphs under it (B1.10). */ html.flat-theme [data-ui~="proxies-page"] td[data-column="address"] code { padding: 1px 6px; font-size: 12px; line-height: 18px; } -/* Credentials: the user name in ink-soft, the masked password in Geist Mono muted. */ +/* Credentials: the user name in ink-soft, the masked password (the template's mono) muted. */ html.flat-theme [data-ui~="proxies-page"] td[data-column="auth"] > div > div > span:first-child:not([class~="font-mono"]) { color: rgb(var(--ui-ink-soft)); } @@ -433,33 +361,22 @@ html.flat-theme [data-ui~="proxies-page"] td[data-column="auth"] span[class~="fo color: rgb(var(--ui-muted)); } -/* Accounts using the proxy: the reference's prompt chip (REF §8.7 /logs: a chip button, Geist Mono on the subtle fill, - radius 4, 2 / 6 inset, ink on hover); the zero count is the same chip, not a button. The count is a figure, not a - status: no accent. */ +/* Accounts using the proxy: the reference's prompt chip (REF §8.7 /logs: a chip button on the subtle fill, radius 4, + 2 / 6 inset, the hover fill) holding the template's 12 / 500 text (official: the count that opens the list in the + accent, the zero count grey); the zero count is the same chip, not a button. */ html.flat-theme [data-ui~="proxies-page"] [data-ui="cell-line"][data-line="account_count"] > [data-ui="cell-line-value"] > :is(button, span)[class~="inline-flex"], html.flat-theme [data-ui~="proxies-page"] td[data-column="account_count"] > :is(button, span)[class~="inline-flex"] { padding: 2px 6px; border-radius: var(--ui-radius-sm); background-color: rgb(var(--ui-subtle)); - color: rgb(var(--ui-ink-soft)); - font-family: var(--ui-font-mono); - font-size: 11px; - line-height: 16px; - font-weight: 500; font-variant-numeric: tabular-nums; white-space: nowrap; transition: color var(--ui-duration) var(--ui-ease), background-color var(--ui-duration) var(--ui-ease); } -html.flat-theme [data-ui~="proxies-page"] [data-ui="cell-line"][data-line="account_count"] > [data-ui="cell-line-value"] > span[class~="inline-flex"], -html.flat-theme [data-ui~="proxies-page"] td[data-column="account_count"] > span[class~="inline-flex"] { - color: rgb(var(--ui-muted)); -} - html.flat-theme [data-ui~="proxies-page"] [data-ui="cell-line"][data-line="account_count"] > [data-ui="cell-line-value"] > button[class~="inline-flex"]:hover, html.flat-theme [data-ui~="proxies-page"] td[data-column="account_count"] > button[class~="inline-flex"]:hover { background-color: rgb(var(--ui-hover)); - color: rgb(var(--ui-ink)); } /* Latency and quality: the status chips at their own width (upstream's column stretched them into bars) and the latency @@ -475,72 +392,32 @@ html.flat-theme [data-ui~="proxies-page"] td[data-column="latency"] > div > span html.flat-theme [data-ui~="proxies-page"] td[data-column="latency"] > div > div { gap: 6px; - color: rgb(var(--ui-muted)); - font-size: 12px; - line-height: 18px; } -/* Expiry and created: 12px, the date ink-soft, 「剩余 N 天」 muted unless it is a warning / danger chip. */ +/* Expiry and created: the template's 12px dates (official greys; 「剩余 N 天」 a warning / danger chip when due), tabular. */ html.flat-theme [data-ui~="proxies-page"] [data-ui="cell-line"][data-line="created_at"] > [data-ui="cell-line-value"] > span, html.flat-theme [data-ui~="proxies-page"] td[data-column="expiry"] > div > span:first-child, html.flat-theme [data-ui~="proxies-page"] td[data-column="created_at"] > span { - color: rgb(var(--ui-ink-soft)); font-variant-numeric: tabular-nums; } -html.flat-theme [data-ui~="proxies-page"] td[data-column="expiry"] > div > span[class~="text-gray-500"], -html.flat-theme [data-ui~="proxies-page"] td[data-column="expiry"] > span { - color: rgb(var(--ui-muted)); -} - /* The copy-format menu (address cell) sets each format on one line where it fits. Upstream's 360px cap broke a copyable proxy URL after "bp-" or inside the address; 34rem holds a SOCKS5 URL with credentials, and the value still wraps - anywhere as the fallback. The rows' names are the mono label, the values Geist Mono ink-soft. */ + anywhere as the fallback. The rows keep their template text (the name 11px grey, the value 12px mono). */ html.flat-theme [data-ui~="proxies-page"] .table-wrapper td [role="menu"][class~="max-w-[360px]"] { max-width: min(34rem, calc(100vw - 2rem)); } -html.flat-theme [data-ui~="proxies-page"] td[data-column="address"] [role="menu"] > button > span:first-child { - color: rgb(var(--ui-muted)); - font-size: 11px; - line-height: 16px; -} - -html.flat-theme [data-ui~="proxies-page"] td[data-column="address"] [role="menu"] > button > span:last-child { - color: rgb(var(--ui-ink-soft)); - font-size: 12px; - line-height: 18px; -} - -html.flat-theme [data-ui~="proxies-page"] td[data-column="address"] [role="menu"] > button:hover > span:last-child { - color: rgb(var(--ui-ink)); -} - /* ================================================================== 7 · subscriptions table */ -/* Usage: a label, the reference's usage bar (8px capsule on the subtle track, REF §8.14; the fill keeps its status - colour), the amounts in Geist Mono; the reset line muted (upstream's blue is decoration). Unlimited is the neutral - chip (the gradient goes). */ -html.flat-theme [data-ui~="subscriptions-page"] td[data-column="usage"] :is(.usage-label, .usage-amount) { - color: rgb(var(--ui-muted)); - font-size: 12px; -} - -html.flat-theme [data-ui~="subscriptions-page"] td[data-column="usage"] .usage-amount { - color: rgb(var(--ui-ink-soft)); - font-family: var(--ui-font-mono); -} - +/* Usage: the template's label and amounts (12px, tabular), the reference's usage bar (8px capsule on the subtle track, + REF §8.14; the fill keeps its status colour), the template's 10px reset line. Unlimited is the neutral chip (the + gradient goes). */ html.flat-theme [data-ui~="subscriptions-page"] td[data-column="usage"] .usage-row div[class~="h-1.5"][class~="flex-1"] { height: 6px; background-color: rgb(var(--ui-subtle)); } -html.flat-theme [data-ui~="subscriptions-page"] td[data-column="usage"] .reset-info { - color: rgb(var(--ui-muted)); - font-size: 11px; -} - html.flat-theme [data-ui~="subscriptions-page"] td[data-column="usage"] div[class~="bg-gradient-to-r"] { display: inline-flex; gap: 6px; @@ -556,13 +433,8 @@ html.flat-theme [data-ui~="subscriptions-page"] td[data-column="usage"] div[clas line-height: 18px; } -html.flat-theme [data-ui~="subscriptions-page"] td[data-column="user"] a { - font-weight: 600; - color: rgb(var(--ui-ink)); -} - /* Selection bar (above the table while rows are selected): the reference's tray language, neutral (REF §8.12: surface, - 1px line, radius 8, 8 / 16 inset; the count ink-soft 12.5), not upstream's accent-tinted box. */ + 1px line, radius 8, 8 / 16 inset; the count ink-soft at the template's 14 / 500), not upstream's accent-tinted box. */ html.flat-theme [data-ui~="subscriptions-page"] [data-test="subscription-bulk-actions"] { padding: 8px 12px 8px 16px; border: 1px solid rgb(var(--ui-line)); @@ -572,7 +444,7 @@ html.flat-theme [data-ui~="subscriptions-page"] [data-test="subscription-bulk-ac html.flat-theme [data-ui~="subscriptions-page"] [data-test="subscription-bulk-actions"] > div > span:first-child { color: rgb(var(--ui-ink-soft)); - font-size: 12.5px; + font-size: 14px; font-weight: 500; } @@ -584,8 +456,8 @@ html.flat-theme [data-ui~="subscriptions-page"] [data-test="subscription-bulk-ac /* While codes are selected upstream puts an accent-tinted box above the pagination. It becomes a strip of the table card between the rows and the pagination footer (the reference's compare tray takes the space under the list, REF §8.12): - the card's 1px sides, the surface, 8 / 16 inset; the count 12.5 ink-soft, 「清空选择」 the accent link, the batch - action the default button (decision: a tray's actions are neutral). */ + the card's 1px sides, the surface, 8 / 16 inset; the count ink-soft at the template's 14 / 500, 「清空选择」 the accent + link at the template's 12 / 500, the batch action the default button (decision: a tray's actions are neutral). */ html.flat-theme [data-ui~="redeem-page"] > .layout-section-fixed:last-child > div[class~="bg-primary-50"]:first-child { margin: 0; padding: 7px 12px 7px 16px; @@ -597,15 +469,15 @@ html.flat-theme [data-ui~="redeem-page"] > .layout-section-fixed:last-child > di html.flat-theme [data-ui~="redeem-page"] > .layout-section-fixed:last-child > div[class~="bg-primary-50"]:first-child > span { color: rgb(var(--ui-ink-soft)); - font-size: 12.5px; - line-height: 18.75px; + font-size: 14px; + line-height: 20px; font-weight: 500; } html.flat-theme [data-ui~="redeem-page"] > .layout-section-fixed:last-child > div[class~="bg-primary-50"]:first-child > div > button:not(.btn) { color: var(--ui-link); - font-size: 12.5px; - font-weight: 600; + font-size: 12px; + font-weight: 500; } html.flat-theme [data-ui~="redeem-page"] > .layout-section-fixed:last-child > div[class~="bg-primary-50"]:first-child > div > button:not(.btn):hover { @@ -623,46 +495,32 @@ html.flat-theme [data-ui~="redeem-page"] > .layout-section-fixed:last-child > di background-color: rgb(var(--ui-subtle)); } -/* ================================================================== 8 · announcements table */ - -/* Title and its meta line (「#3 · 2026/09/04 21:41:44」 in 12px muted, figures in Geist Mono); the time window as label + - value lines (the label muted, the value ink-soft). */ -html.flat-theme [data-ui~="announcements-page"] td[data-column="title"] > div > div:last-child { - color: rgb(var(--ui-muted)); - font-family: var(--ui-font-mono); - font-size: 11px; - line-height: 16.5px; -} - -html.flat-theme [data-ui~="announcements-page"] td[data-column="timeRange"] > div { - color: rgb(var(--ui-ink-soft)); -} +/* ================================================================== 8 · announcements table -html.flat-theme [data-ui~="announcements-page"] td[data-column="timeRange"] span[class~="font-medium"] { - color: rgb(var(--ui-muted)); - font-weight: 400; -} + The title, its meta line (「#3 · 2026/09/04 21:41:44」) and the time window (label + value lines) are the template's + text (official): no rule here. */ /* ================================================================== 9 · dialogs: shared parts */ -/* The dialog frame (scrim, panel, header, close, footer) is overlays.css' reference Modal; here the content these pages put - in it, in the Modal's texture (REF §8.11 Add-model: 16 / 18 body, option cards on the grouped fill, 28px icon squares, - 13/600 titles, 12.5 muted text). Dialog hooks (data-ui on the first content element, one attribute each): - UserApiKeysModal user-api-keys, UserAllowedGroupsModal user-allowed-groups, UserBalanceHistoryModal user-balance-history, - UserPlatformQuotaModal user-platform-quota, GroupReplaceModal group-replace, UserAttributesConfigModal - user-attributes-config, GroupRateMultipliersModal group-rate-multipliers, GroupRPMOverridesModal group-rpm-overrides, - AnnouncementReadStatusDialog announcement-read-status, AnnouncementTargetingEditor announcement-targeting, the proxy - dialog's tab strip proxy-create-tabs, the redeem result's body redeem-result-body, the subscription guide's title - subscription-guide-title; and, so that no rule has to find them by their content (PERF, 2026-09-29), the redeem - result's header redeem-result-head, the generate-codes form redeem-generate-form, quick add's parse result - proxy-parse-result, the quality report's summary proxy-quality-summary, each row of the group sort dialog - group-sort-row, each choice of group replace replace-option (GroupReplaceModal.vue) and the two targeting choices - targeting-option (AnnouncementTargetingEditor.vue). The others are found by their form ids. */ +/* The dialog frame (scrim, panel, header, close, footer) is overlays.css' reference Modal; here the content these pages + put in it, in the Modal's texture (REF §8.11 Add-model: 16 / 18 body, option cards on the grouped fill, 28px icon + squares, 14/600 titles, muted text at the templates' sizes). Dialog hooks (data-ui on the first content element, one + attribute each): UserApiKeysModal user-api-keys, UserAllowedGroupsModal user-allowed-groups, UserBalanceHistoryModal + user-balance-history, UserPlatformQuotaModal user-platform-quota, GroupReplaceModal group-replace, + UserAttributesConfigModal user-attributes-config, GroupRateMultipliersModal group-rate-multipliers, + GroupRPMOverridesModal group-rpm-overrides, AnnouncementReadStatusDialog announcement-read-status, + AnnouncementTargetingEditor announcement-targeting, the proxy dialog's tab strip proxy-create-tabs, the redeem + result's body redeem-result-body, the subscription guide's title subscription-guide-title; and, so that no rule has + to find them by their content (PERF, 2026-09-29), the redeem result's header redeem-result-head, the generate-codes + form redeem-generate-form, quick add's parse result proxy-parse-result, the quality report's summary + proxy-quality-summary, each row of the group sort dialog group-sort-row, each choice of group replace replace-option + (GroupReplaceModal.vue) and the two targeting choices targeting-option (AnnouncementTargetingEditor.vue). The others + are found by their form ids. */ /* 9.1 The user card at the top of the user dialogs (deposit / refund, balance history, API keys, allowed groups): the reference's option card (1px line, radius 8, the grouped fill at 60 %, 12 / 14 inset), its avatar the 28px icon square - holding the letter (Geist Mono 600, ink-soft), the e-mail 13/600 ink, the second line 12px muted. Upstream's tinted - circle and gradient banner are decoration (decision 2). */ + holding the letter (12 / 600, ink-soft), the e-mail 14px ink at the template's weight, the second line 12px muted. + Upstream's tinted circle and gradient banner are decoration (decision 2). */ html.flat-theme :is(#balance-form, [data-ui="user-api-keys"], [data-ui="user-balance-history"], [data-ui="user-allowed-groups"]) > div:first-child { gap: 12px; padding: 12px 14px; @@ -683,14 +541,13 @@ html.flat-theme :is(#balance-form, [data-ui="user-api-keys"], [data-ui="user-bal html.flat-theme :is(#balance-form, [data-ui="user-api-keys"], [data-ui="user-balance-history"], [data-ui="user-allowed-groups"]) > div:first-child div[class~="rounded-full"]:is([class~="bg-primary-100"], [class~="bg-white"]):is([class~="h-10"], [class~="h-14"]) > span { color: rgb(var(--ui-ink-soft)); - font: 600 12px/1 var(--ui-font-mono); + font: 600 12px/1 var(--ui-font); } html.flat-theme :is(#balance-form, [data-ui="user-api-keys"], [data-ui="user-balance-history"], [data-ui="user-allowed-groups"]) > div:first-child :is(p[class~="font-medium"], p[class~="font-semibold"]):first-child { color: rgb(var(--ui-ink)); - font-size: 13px; - line-height: 19.5px; - font-weight: 600; + font-size: 14px; + line-height: 20px; } html.flat-theme :is(#balance-form, [data-ui="user-api-keys"], [data-ui="user-allowed-groups"]) > div:first-child p:is([class~="text-sm"], [class~="mt-1"]):not(:first-child) { @@ -781,20 +638,19 @@ html.flat-theme [data-ui="group-replace"] label[data-ui~="replace-option"]:where outline-offset: 2px; } -/* 9.3 Section titles inside these dialogs: the reference's card title (13/600 ink), counts in Geist Mono 11 muted; the - coloured bullet in front of a section (exclusive purple, public green) is a category, drawn neutral. */ +/* 9.3 Section titles inside these dialogs: the reference's card title (14/600 ink), counts 12px muted; the coloured + bullet in front of a section (exclusive purple, public green) is a category, drawn neutral. */ html.flat-theme [data-ui="user-allowed-groups"] h4, html.flat-theme :is([data-ui="group-rate-multipliers"], [data-ui="group-rpm-overrides"]) h4 { color: rgb(var(--ui-ink)); - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; font-weight: 600; } html.flat-theme [data-ui="user-allowed-groups"] h4 + span { color: rgb(var(--ui-muted)); - font-family: var(--ui-font-mono); - font-size: 11px; + font-size: 12px; } html.flat-theme [data-ui="user-allowed-groups"] div[class~="rounded-full"][class~="h-1.5"][class~="w-1.5"] { @@ -802,8 +658,8 @@ html.flat-theme [data-ui="user-allowed-groups"] div[class~="rounded-full"][class } /* 9.4 Numeric fields that upstream draws with their own utilities (the allowed groups' rate, the per-user rate and RPM - rows): the reference's field at the row's compact height (30px), radius 6, the 3:1 form-control line, 13px Geist Mono - figures; focus = the accent edge 2px wide (controls.css §3.1). */ + rows): the reference's field at the row's compact height (30px), radius 6, the 3:1 form-control line, the template's + 14px figures; focus = the accent edge 2px wide (controls.css §3.1). */ html.flat-theme [data-ui="user-allowed-groups"] input[type="number"][class~="hide-spinner"], html.flat-theme :is([data-ui="group-rate-multipliers"], [data-ui="group-rpm-overrides"]) td input[type="number"] { height: var(--ui-control-h); @@ -812,8 +668,7 @@ html.flat-theme :is([data-ui="group-rate-multipliers"], [data-ui="group-rpm-over border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-surface)); color: rgb(var(--ui-ink)); - font-family: var(--ui-font-mono); - font-size: 13px; + font-size: 14px; font-weight: 500; box-shadow: none; } @@ -825,18 +680,17 @@ html.flat-theme :is([data-ui="group-rate-multipliers"], [data-ui="group-rpm-over box-shadow: inset 0 0 0 1px rgb(var(--ui-focus)); } -/* 9.5 Tables inside these dialogs (platform quotas, per-user rates and RPM): the reference's table header (the mono label, - 1px line under it, no band) and hairline rows. */ +/* 9.5 Tables inside these dialogs (platform quotas, per-user rates and RPM): the console's table header (12 / 16 / 500, + uppercase, 0.05em; 1px line under it, no band) and hairline rows. */ html.flat-theme [data-ui="user-platform-quota"] table > thead > tr > th, html.flat-theme :is([data-ui="group-rate-multipliers"], [data-ui="group-rpm-overrides"]) table > thead > tr > th { background-color: rgb(var(--ui-surface)); box-shadow: inset 0 -1px 0 rgb(var(--ui-line)); color: rgb(var(--ui-label)); - font-family: var(--ui-font-mono); font-size: var(--ui-label-size); line-height: var(--ui-label-leading); font-weight: 500; - letter-spacing: var(--ui-label-tracking); + letter-spacing: 0.05em; text-transform: uppercase; white-space: nowrap; } @@ -876,7 +730,6 @@ html.flat-theme form[data-ui="redeem-generate-form"]:not(uipad):not(uipad) > div html.flat-theme #balance-form > div[class~="border-blue-200"] span[class~="font-bold"] { color: rgb(var(--ui-ink)); - font-family: var(--ui-font-mono); font-weight: 600; } @@ -886,7 +739,7 @@ html.flat-theme [data-ui="user-platform-quota"] > div[class~="border-amber-200"] border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-warning-soft)); color: rgb(var(--ui-warning)); - font-size: 13px; + font-size: 14px; } /* 9.7 A positive CTA is the accent button (the deposit's confirm; decision 2): upstream's emerald fill becomes the accent @@ -902,58 +755,54 @@ html.flat-theme .modal-footer button.btn[form="balance-form"][class~="bg-emerald background-color: rgb(var(--ui-primary-hover)); } -/* the amount field's 「$」 prefix: muted, in the field's own figures */ +/* the amount field's 「$」 prefix: muted, at the field's 14px */ html.flat-theme #balance-form div[class~="relative"] > div[class~="absolute"][class~="left-3"] { color: rgb(var(--ui-muted)); - font-family: var(--ui-font-mono); - font-size: 13px; + font-size: 14px; } /* ================================================================== 10 · user dialogs */ -/* 10.1 Balance history: the user card's second line (notes, total recharged) in 12px muted with the total in Geist Mono - ink (upstream's emerald is decoration); the current balance 15/600 Mono (the reference's statistic figure) under its - mono label. */ +/* 10.1 Balance history: the user card's second line (notes, total recharged) in 12px muted with the total in ink + (upstream's emerald is decoration); the current balance at the template's text-xl font-bold (20 / 28 / 700, tabular) + under its label. */ html.flat-theme [data-ui="user-balance-history"] > div:first-child > div + div { border-color: rgb(var(--ui-line)); } html.flat-theme [data-ui="user-balance-history"] > div:first-child span[class~="font-semibold"][class*="text-emerald-"] { color: rgb(var(--ui-ink)); - font-family: var(--ui-font-mono); font-weight: 500; } html.flat-theme [data-ui="user-balance-history"] > div:first-child p[class~="text-xl"] { color: rgb(var(--ui-ink)); - font-family: var(--ui-font-mono); - font-size: 15px; - line-height: 22.5px; - font-weight: 600; + font-size: 20px; + line-height: 28px; + font-weight: 700; font-variant-numeric: tabular-nums; } html.flat-theme [data-ui="user-balance-history"] > div:first-child div[class~="text-right"] > p:first-child { - /* the mono label on the card's grouped fill: the muted grey keeps a margin over 4.5:1 there (the label grey is 4.50) */ + /* the label on the card's grouped fill: the muted grey keeps 4.5:1 there */ color: rgb(var(--ui-muted)); font: var(--ui-label-font); letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; + text-transform: none; } -/* the username beside the e-mail: a neutral value chip, not the accent tint */ +/* the username beside the e-mail: a neutral value chip (the template's 12px), not the accent tint */ html.flat-theme [data-ui="user-balance-history"] > div:first-child span[class~="bg-primary-50"] { padding: 1px 6px; border-radius: var(--ui-radius-sm); background-color: rgb(var(--ui-subtle)); color: rgb(var(--ui-ink-soft)); - font-family: var(--ui-font-mono); - font-size: 11px; + font-size: 12px; line-height: 16px; } -/* the filter row's deposit / refund buttons are the default button (REF §8.3: 30px, radius 6, the control outline, 13/600 - ink); their + / − glyphs keep the distinction in ink-soft */ +/* the filter row's deposit / refund buttons are the default button (REF §8.3: 30px, radius 6, the control outline, the + buttons' 14 / 500 ink); their + / − glyphs keep the distinction in ink-soft */ html.flat-theme [data-ui="user-balance-history"] > div:nth-child(2) > button { min-height: var(--ui-control-h); padding: 0 12px; @@ -961,9 +810,9 @@ html.flat-theme [data-ui="user-balance-history"] > div:nth-child(2) > button { border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-surface)); color: rgb(var(--ui-ink)); - font-size: 13px; - line-height: 19.5px; - font-weight: 600; + font-size: 14px; + line-height: 20px; + font-weight: 500; } html.flat-theme [data-ui="user-balance-history"] > div:nth-child(2) > button:hover { @@ -978,8 +827,8 @@ html.flat-theme [data-ui="user-balance-history"] > div:nth-child(2) > button > s } /* history entries: rows of the reference's list (1px line, radius 8, 12 / 14 inset), the type glyph in the 28px neutral - square (the per-type tints are decoration), the title 13/500 ink, notes 12 muted, the time Geist Mono 11 muted; the - figure Geist Mono 13/600, a credit in the success colour and a debit in danger (their meaning), other kinds ink. */ + square (the per-type tints are decoration), the title 14/500 ink, notes 12 muted, the time 12px muted; the figure + 14/600 tabular, a credit in the success colour and a debit in danger (their meaning), other kinds ink. */ html.flat-theme [data-ui="user-balance-history"] div[class~="overflow-y-auto"] > div[class~="border"] { padding: 12px 14px; border-color: rgb(var(--ui-line)); @@ -1002,20 +851,18 @@ html.flat-theme [data-ui="user-balance-history"] div[class~="overflow-y-auto"] > html.flat-theme [data-ui="user-balance-history"] div[class~="overflow-y-auto"] > div[class~="border"] p[class~="font-medium"] { color: rgb(var(--ui-ink)); - font-size: 13px; + font-size: 14px; font-weight: 500; } html.flat-theme [data-ui="user-balance-history"] div[class~="overflow-y-auto"] > div[class~="border"] div:not([class~="text-right"]) > p[class~="text-gray-400"] { color: rgb(var(--ui-muted)); - font-family: var(--ui-font-mono); - font-size: 11px; + font-size: 12px; } html.flat-theme [data-ui="user-balance-history"] div[class~="overflow-y-auto"] > div[class~="border"] div[class~="text-right"] > p:first-child { color: rgb(var(--ui-ink)); - font-family: var(--ui-font-mono); - font-size: 13px; + font-size: 14px; font-variant-numeric: tabular-nums; } @@ -1029,12 +876,13 @@ html.flat-theme [data-ui="user-balance-history"] div[class~="overflow-y-auto"] > html.flat-theme [data-ui="user-balance-history"] div[class~="overflow-y-auto"] > div[class~="border"] div[class~="text-right"] > p + p { color: rgb(var(--ui-muted)); - font-size: 11px; + font-size: 12px; } -/* 10.2 API keys: each key a row of the reference's list (1px line, radius 8, 12 / 14 inset); the name 13/600 ink, the key - in Geist Mono 12.5 muted, the meta line 12px muted; the group button the reference's quiet trigger (the subtle fill on - hover), its chevron faint. The group picker's panel and rows are overlays.css' menu, its chips data.css' identity. */ +/* 10.2 API keys: each key a row of the reference's list (1px line, radius 8, 12 / 14 inset); the name 14/500 ink, the key + in the template's mono (14px; its lease line 12px) muted, the meta line 12px muted; the group button the reference's + quiet trigger (the subtle fill on hover), its chevron faint. The group picker's panel and rows are overlays.css' menu, + its chips data.css' identity. */ html.flat-theme [data-ui="user-api-keys"] > div[class~="overflow-y-auto"] > div[class~="border"] { padding: 12px 14px; border-color: rgb(var(--ui-line)); @@ -1043,14 +891,12 @@ html.flat-theme [data-ui="user-api-keys"] > div[class~="overflow-y-auto"] > div[ html.flat-theme [data-ui="user-api-keys"] > div[class~="overflow-y-auto"] > div[class~="border"] span[class~="font-medium"]:first-child { color: rgb(var(--ui-ink)); - font-size: 13px; - font-weight: 600; + font-size: 14px; + font-weight: 500; } html.flat-theme [data-ui="user-api-keys"] > div[class~="overflow-y-auto"] > div[class~="border"] p[class~="font-mono"] { color: rgb(var(--ui-muted)); - font-size: 12.5px; - line-height: 19px; } html.flat-theme [data-ui="user-api-keys"] > div[class~="overflow-y-auto"] > div[class~="border"] > div[class~="mt-3"] { @@ -1079,12 +925,12 @@ html.flat-theme [data-ui="user-api-keys"] > div[class~="overflow-y-auto"] > div[ outline-offset: 2px; } -/* 10.3 Allowed groups: each row's name 13/600, the platform line 12px muted (upstream shows the platform without a hue, +/* 10.3 Allowed groups: each row's name 14/600, the platform line 12px muted (upstream shows the platform without a hue, so it stays neutral), the 「专属」 mark a category chip, the custom-rate label 12px muted. */ html.flat-theme [data-ui="user-allowed-groups"] div[class~="grid"] > div span[class~="text-base"] { color: rgb(var(--ui-ink)); - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; font-weight: 600; } @@ -1094,7 +940,7 @@ html.flat-theme [data-ui="user-allowed-groups"] div[class~="grid"] > div span[cl border-radius: var(--ui-radius-sm); background-color: rgb(var(--ui-subtle)); color: rgb(var(--ui-ink-soft)); - font: 500 var(--ui-chip-size) / 16px var(--ui-font-mono); + font: 500 var(--ui-chip-size) / 16px var(--ui-font); } html.flat-theme [data-ui="user-allowed-groups"] div[class~="grid"] > div div[class~="mt-1.5"] { @@ -1107,7 +953,6 @@ html.flat-theme [data-ui="user-allowed-groups"] div[class~="grid"] > div div[cla html.flat-theme [data-ui="user-allowed-groups"] div[class~="grid"] > div div[class~="mt-1.5"] span[class~="font-medium"] { color: rgb(var(--ui-ink-soft)); - font-family: var(--ui-font-mono); } html.flat-theme [data-ui="user-allowed-groups"] div[class~="grid"] > div label[class~="text-sm"] { @@ -1120,15 +965,16 @@ html.flat-theme [data-ui="user-allowed-groups"] > div + div > div > p[class~="te color: rgb(var(--ui-muted)); } -/* 10.4 Platform quotas: platform names Geist Mono 12.5 ink, the fields at the row height (30px), the reset glyph faint to - ink, usage Geist Mono 12 muted. */ +/* 10.4 Platform quotas: platform names in the template's mono at 14px ink, the fields at the row height (30px), the + reset glyph a 20px target in its template colours (grey, the warning hue on hover), usage the template's 12px grey, + tabular. */ html.flat-theme [data-ui="user-platform-quota"] table td { vertical-align: middle; } html.flat-theme [data-ui="user-platform-quota"] table td[class~="font-mono"] { color: rgb(var(--ui-ink)); - font-size: 12.5px; + font-size: 14px; } html.flat-theme [data-ui="user-platform-quota"] table td input.input { @@ -1142,28 +988,24 @@ html.flat-theme [data-ui="user-platform-quota"] table td button { width: 20px; height: 20px; border-radius: var(--ui-radius-sm); - color: rgb(var(--ui-faint)); transition: color var(--ui-duration) var(--ui-ease), background-color var(--ui-duration) var(--ui-ease); } html.flat-theme [data-ui="user-platform-quota"] table td button:not(:disabled):hover { background-color: rgb(var(--ui-subtle)); - color: rgb(var(--ui-ink)); } html.flat-theme [data-ui="user-platform-quota"] table td:last-child { - color: rgb(var(--ui-muted)); - font-family: var(--ui-font-mono); font-variant-numeric: tabular-nums; } html.flat-theme [data-ui="user-platform-quota"] > p { color: rgb(var(--ui-muted)); - font-size: 13px; + font-size: 14px; } /* 10.5 Group replacement: the current group in a neutral card (its shield is the exclusive category, neutral), the arrow - faint, the chosen target 13/600 ink; each choice's platform in Geist Mono 11 muted. */ + faint, the groups at the template's 500 in ink (the choices 14px); each choice's platform 12px muted. */ html.flat-theme [data-ui="group-replace"] > div[class~="rounded-lg"][class~="bg-gray-50"] { padding: 10px 14px; border-color: rgb(var(--ui-line)); @@ -1179,29 +1021,28 @@ html.flat-theme [data-ui="group-replace"] > div[class~="rounded-lg"][class~="bg- html.flat-theme [data-ui="group-replace"] > div[class~="rounded-lg"][class~="bg-gray-50"] span[class~="font-medium"] { color: rgb(var(--ui-ink)); - font-weight: 600; + font-weight: 500; } html.flat-theme [data-ui="group-replace"] label[class~="border-2"] span[class~="font-medium"] { color: rgb(var(--ui-ink)); - font-size: 13px; - font-weight: 600; + font-size: 14px; + font-weight: 500; } html.flat-theme [data-ui="group-replace"] label[class~="border-2"] span[class~="text-xs"] { color: rgb(var(--ui-muted)); - font-family: var(--ui-font-mono); - font-size: 11px; + font-size: 12px; } html.flat-theme [data-ui="group-replace"] > p { color: rgb(var(--ui-muted)); - font-size: 13px; + font-size: 14px; line-height: 20px; } -/* 10.6 User attributes: rows of the reference's list; the key a Geist Mono value chip; the empty state the reference's - dashed panel with its glyph at 20px (REF §8.7 empty state). */ +/* 10.6 User attributes: rows of the reference's list; the key a value chip (the template's 12px mono); the empty state + the reference's dashed panel with its glyph at 20px (REF §8.7 empty state). */ html.flat-theme [data-ui="user-attributes-config"] > div[class~="overflow-y-auto"] > div[class~="border"] { padding: 10px 12px; border-color: rgb(var(--ui-line)); @@ -1213,7 +1054,7 @@ html.flat-theme [data-ui="user-attributes-config"] span[class~="font-mono"][clas border-radius: var(--ui-radius-sm); background-color: rgb(var(--ui-subtle)); color: rgb(var(--ui-ink-soft)); - font-size: 11px; + font-size: 12px; } html.flat-theme [data-ui="user-attributes-config"] > div[class~="py-12"][class~="text-center"] { @@ -1245,29 +1086,20 @@ html.flat-theme [data-ui="user-attributes-config"] > div[class~="py-12"][class~= html.flat-theme [data-ui="user-attributes-config"] > div[class~="py-12"][class~="text-center"] > p + p { color: rgb(var(--ui-muted)); - font-size: 13px; + font-size: 14px; } html.flat-theme [data-ui="user-attributes-config"] > div:first-child > p { color: rgb(var(--ui-muted)); - font-size: 13px; + font-size: 14px; } /* ================================================================== 11 · group dialogs */ /* 11.1 Create / edit: the field labels upstream writes without the label recipe (「从分组复制账号」, 「专属分组」 … with their - help glyph) read as the form's other labels (the mono label); the help glyph is the faint info glyph (overlays 2.5). - The groups picked to copy accounts from are the reference's removable value chips (as the accounts filter chips). */ -html.flat-theme :is(#create-group-form, #edit-group-form) div[class~="mb-1.5"][class~="flex"][class~="items-center"] > label[class~="text-sm"][class~="font-medium"] { - color: rgb(var(--ui-label)); - font-family: var(--ui-font-mono); - font-size: var(--ui-label-size); - line-height: var(--ui-label-leading); - font-weight: 600; - letter-spacing: var(--ui-label-tracking); - text-transform: uppercase; -} - + help glyph) already read as the form's other labels (their template's text-sm font-medium text-gray-700 is the label + recipe's official 14 / 20 / 500 ink-soft); the help glyph is the faint info glyph (overlays 2.5). The groups picked to + copy accounts from are the reference's removable value chips (as the accounts filter chips). */ html.flat-theme :is(#create-group-form, #edit-group-form) div[class~="group"][class~="relative"][class~="inline-flex"] > svg[class~="cursor-help"] { width: 14px; height: 14px; @@ -1305,20 +1137,20 @@ html.flat-theme :is(#create-group-form, #edit-group-form, [data-testid="codex-ma } /* the form's section titles (web search pricing, OpenAI Fast, Live, messages dispatch, image pricing, model allowlist ...): - one size, the reference's card title 13/600 ink (upstream mixes 14 and 16px, 500 and 600) */ + one size, the reference's card title 14/600 ink (upstream mixes 14 and 16px, 500 and 600) */ html.flat-theme :is(#create-group-form, #edit-group-form) div[class~="border-t"] > h4[class~="font-medium"], html.flat-theme :is(#create-group-form, #edit-group-form) div[class~="border-t"] > label[class~="block"][class~="mb-2"][class~="font-medium"]:not(.input-label), html.flat-theme :is(#create-group-form, #edit-group-form) div[class~="border-t"][data-testid$="model-allowlist"] > div:first-child > div:first-child > label[class~="text-sm"] { color: rgb(var(--ui-ink)); - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; font-weight: 600; } -/* the exclusive / public switch's state word beside it: 13px ink-soft (upstream muted grey) */ +/* the exclusive / public switch's state word beside it: 14px ink-soft (upstream muted grey) */ html.flat-theme :is(#create-group-form, #edit-group-form) div[class~="flex"][class~="items-center"][class~="gap-3"] > button[class~="rounded-full"][class~="border-2"] + span { color: rgb(var(--ui-ink-soft)); - font-size: 13px; + font-size: 14px; } /* 11.2 Per-user rates and RPM: the group strip a neutral card (1px line, radius 8, the grouped fill), the platform in its @@ -1330,7 +1162,7 @@ html.flat-theme :is([data-ui="group-rate-multipliers"], [data-ui="group-rpm-over border: 1px solid rgb(var(--ui-line)); border-radius: var(--ui-radius-lg); background: color-mix(in srgb, rgb(var(--ui-subtle)) 60%, rgb(var(--ui-surface))); - font-size: 13px; + font-size: 14px; } html.flat-theme :is([data-ui="group-rate-multipliers"], [data-ui="group-rpm-overrides"]) > div:first-child > span[class~="text-gray-400"] { @@ -1348,7 +1180,6 @@ html.flat-theme :is([data-ui="group-rate-multipliers"], [data-ui="group-rpm-over html.flat-theme :is([data-ui="group-rate-multipliers"], [data-ui="group-rpm-overrides"]) > div:first-child > span:last-child { color: rgb(var(--ui-muted)); - font-family: var(--ui-font-mono); font-size: 12px; } @@ -1360,12 +1191,11 @@ html.flat-theme :is([data-ui="group-rate-multipliers"], [data-ui="group-rpm-over html.flat-theme :is([data-ui="group-rate-multipliers"], [data-ui="group-rpm-overrides"]) table span[class~="rounded-full"]:is([class~="bg-green-100"], [class~="bg-gray-100"]) { padding: 1px 7px; - font-family: var(--ui-font-mono); - font-size: 10.5px; + font-size: 12px; line-height: 16px; font-weight: 500; - letter-spacing: 0.025em; - text-transform: uppercase; + letter-spacing: normal; + text-transform: none; } html.flat-theme :is([data-ui="group-rate-multipliers"], [data-ui="group-rpm-overrides"]) div[class~="overflow-hidden"][class~="rounded-lg"][class~="border"] { @@ -1382,8 +1212,8 @@ html.flat-theme :is([data-ui="group-rate-multipliers"], [data-ui="group-rpm-over } /* 11.3 Sort order: each group a draggable row of the reference's list (1px line, radius 8, no lift shadow), its handle faint, - the id Geist Mono 11 muted; the platform chip is the identity chip (data.css). The row carries the hook group-sort-row - (GroupsView's sort dialog); `div >` keeps the weight of the former platform-chip test. */ + the id 12px muted, the name 14/500 ink; the platform chip is the identity chip (data.css). The row carries the hook + group-sort-row (GroupsView's sort dialog); `div >` keeps the weight of the former platform-chip test. */ html.flat-theme .modal-body div > div[data-ui="group-sort-row"][class~="cursor-grab"][class~="rounded-lg"][class~="border"] { padding: 10px 12px; border-color: rgb(var(--ui-line)); @@ -1402,19 +1232,18 @@ html.flat-theme .modal-body div > div[data-ui="group-sort-row"][class~="cursor-g html.flat-theme .modal-body div > div[data-ui="group-sort-row"][class~="cursor-grab"][class~="rounded-lg"][class~="border"] > div:last-child { color: rgb(var(--ui-muted)); - font-family: var(--ui-font-mono); - font-size: 11px; + font-size: 12px; } html.flat-theme .modal-body div > div[data-ui="group-sort-row"][class~="cursor-grab"][class~="rounded-lg"][class~="border"] div[class~="font-medium"] { color: rgb(var(--ui-ink)); - font-size: 13px; - font-weight: 600; + font-size: 14px; + font-weight: 500; } /* 11.4 OpenAI Messages dispatch (create / edit, 「允许 /v1/messages 调度」 on): the family-mapping card and the exact-override card are one kind of card, the reference's (1px structural line, radius 8, no shadow; a header band on the grouped fill - at 60 % over the line, the title 13/600 ink, the hint 12px muted). Upstream tints the exact-override card with the + at 60 % over the line, the title 14/600 ink, the hint 12px muted). Upstream tints the exact-override card with the accent (primary-50 band, primary dot, title, hint and border) only to set it apart from the family card, whose band is grey with a blue dot: that is decoration, not a state (nothing succeeded, nothing is selected), so both are neutral and their titles tell them apart (decision 2). The mapping rows are the list rows (no lift shadow), the empty state and the @@ -1441,8 +1270,8 @@ html.flat-theme :is(#create-group-form, #edit-group-form) div[class~="relative"] html.flat-theme :is(#create-group-form, #edit-group-form) div[class~="relative"][class~="overflow-hidden"][class~="rounded-xl"][class~="border"] > div[class~="border-b"]:first-child label { color: rgb(var(--ui-ink)); - font-size: 13px; - line-height: 19.5px; + font-size: 14px; + line-height: 20px; font-weight: 600; } @@ -1463,7 +1292,7 @@ html.flat-theme :is(#create-group-form, #edit-group-form) div[class~="relative"] border-radius: var(--ui-radius-lg); background-color: rgb(var(--ui-surface)); color: rgb(var(--ui-muted)); - font-size: 13px; + font-size: 14px; } html.flat-theme :is(#create-group-form, #edit-group-form) div[class~="relative"][class~="overflow-hidden"][class~="rounded-xl"][class~="border"] div[class~="group"][class~="rounded-xl"][class~="border"][class~="p-4"] { @@ -1504,7 +1333,7 @@ html.flat-theme :is(#create-group-form, #edit-group-form) div[class~="relative"] /* ================================================================== 12 · IP management dialogs */ /* 12.1 Standard / quick add: the reference's code tabs (REF §8.15: a 2px accent rule under the current tab, its text ink; - the others muted, ink on hover; 8 / 10 inset, 12.5 / 600, 14px glyphs), on the structural line. */ + the others muted, ink on hover; 8 / 10 inset, the template's 14 / 500, 14px glyphs), on the structural line. */ html.flat-theme [data-ui="proxy-create-tabs"] { margin-bottom: 16px; border-color: rgb(var(--ui-line)); @@ -1517,9 +1346,9 @@ html.flat-theme [data-ui="proxy-create-tabs"] > div:first-child > button { padding: 8px 10px; border-bottom-color: transparent; color: rgb(var(--ui-muted)); - font-size: 12.5px; - line-height: 18.75px; - font-weight: 600; + font-size: 14px; + line-height: 20px; + font-weight: 500; transition: color var(--ui-duration) var(--ui-ease), border-color var(--ui-duration) var(--ui-ease); } @@ -1544,8 +1373,9 @@ html.flat-theme [data-ui="proxy-create-tabs"] > div:first-child > button:focus-v } /* 12.2 Expiry presets (7 / 30 / 90 / 180 days): one control, the reference's segmented choice (REF §8.5 in palette C: - the grey track, 2px inset, 12.5 / 500 segments, the matching preset the raised pill at 600), as its 24 hours / - 7 days / 30 days. The pill keeps the segment's transparent 1px edge (its fill and ring sit inside it). */ + the grey track, 2px inset, segments at the template's btn-sm 12 / 16 / 500, the matching preset the raised pill, + same weight), as its 24 hours / 7 days / 30 days. The pill keeps the segment's transparent 1px edge (its fill and ring + sit inside it). */ html.flat-theme :is(#create-proxy-form, #edit-proxy-form) div[class~="mb-2"][class~="flex-wrap"]:not(uipad[data-uipad]) { display: inline-flex; gap: 0; @@ -1563,8 +1393,8 @@ html.flat-theme :is(#create-proxy-form, #edit-proxy-form) div[class~="mb-2"][cla border-radius: var(--ui-radius-md); background-color: transparent; color: rgb(var(--ui-muted)); - font-size: 12.5px; - line-height: 18.75px; + font-size: 12px; + line-height: 16px; font-weight: 500; transition: color var(--ui-duration-slide) var(--ui-ease-out), background-color var(--ui-duration-slide) var(--ui-ease-out); } @@ -1580,13 +1410,13 @@ html.flat-theme :is(#create-proxy-form, #edit-proxy-form) div[class~="mb-2"][cla background-color: rgb(var(--ui-thumb)); background-clip: padding-box; color: rgb(var(--ui-ink)); - font-weight: 600; + font-weight: 500; box-shadow: var(--ui-thumb-shadow); } /* 12.3 Quick add's parse result and the quality report's summary: neutral cards (1px line, the grouped fill), the report's - score 15/600 Geist Mono (the reference's statistic figure); its table is data.css' plain table. Hooks (ProxiesView): - proxy-parse-result, proxy-quality-summary. */ + score at the template's text-2xl font-semibold (24 / 32 / 600, tabular); its table is data.css' plain table. Hooks + (ProxiesView): proxy-parse-result, proxy-quality-summary. */ html.flat-theme .modal-body div[data-ui="proxy-parse-result"][class~="rounded-lg"][class~="bg-gray-50"][class~="p-4"]:not(uipad[data-uipad]):not(uipad):not(uipad) { padding: 10px 14px; border: 1px solid rgb(var(--ui-line)); @@ -1602,15 +1432,14 @@ html.flat-theme .modal-body > div > div[data-ui="proxy-quality-summary"][class~= } html.flat-theme .modal-body > div > div[data-ui="proxy-quality-summary"][class~="rounded-lg"][class~="border"][class~="bg-gray-50"][class~="p-4"]:not(uipad):not(uipad) div[class~="text-2xl"][class~="font-semibold"] { - font-family: var(--ui-font-mono); - font-size: 15px; - line-height: 22.5px; + font-size: 24px; + line-height: 32px; font-variant-numeric: tabular-nums; } /* ================================================================== 13 · subscription, redeem, promo and announcement dialogs */ -/* 13.1 Adjust subscription: the facts card neutral (1px line, radius 8, the grouped fill), 13px muted with ink values. */ +/* 13.1 Adjust subscription: the facts card neutral (1px line, radius 8, the grouped fill), 14px muted with ink values. */ html.flat-theme #extend-subscription-form > div:first-child { padding: 12px 14px; border: 1px solid rgb(var(--ui-line)); @@ -1620,7 +1449,7 @@ html.flat-theme #extend-subscription-form > div:first-child { html.flat-theme #extend-subscription-form > div:first-child > p { color: rgb(var(--ui-muted)); - font-size: 13px; + font-size: 14px; } html.flat-theme #extend-subscription-form > div:first-child span[class~="font-medium"] { @@ -1632,11 +1461,11 @@ html.flat-theme #extend-subscription-form > div:first-child span[class~="font-me html.flat-theme #assign-subscription-form [data-test="assign-users"] li { border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-subtle)); - font-size: 13px; + font-size: 14px; } /* 13.2 The subscription guide: steps numbered in the reference's letter square (20px, radius 4, 1px line, the grouped fill, - Geist Mono 600 ink-soft), not accent circles; lists in the docs' 13px muted; the steps' table keeps its label column on + 12 / 600 ink-soft), not accent circles; lists in the template's 14px muted; the steps' table keeps its label column on the grouped fill. */ html.flat-theme [data-ui="subscription-guide-title"] ~ div h3 > span[class~="rounded-full"][class~="bg-primary-100"] { width: 20px; @@ -1645,20 +1474,19 @@ html.flat-theme [data-ui="subscription-guide-title"] ~ div h3 > span[class~="rou border-radius: var(--ui-radius-sm); background-color: rgb(var(--ui-subtle)); color: rgb(var(--ui-ink-soft)); - font-family: var(--ui-font-mono); - font-size: 11px; + font-size: 12px; font-weight: 600; } html.flat-theme [data-ui="subscription-guide-title"] + p { color: rgb(var(--ui-muted)); - font-size: 13px; + font-size: 14px; } html.flat-theme [data-ui="subscription-guide-title"] ~ div ol { color: rgb(var(--ui-muted)); - font-size: 13px; - line-height: 1.625; + font-size: 14px; + line-height: 20px; } html.flat-theme [data-ui="subscription-guide-title"] ~ div ol > li::marker { @@ -1676,7 +1504,8 @@ html.flat-theme [data-ui="subscription-guide-title"] ~ div div[class~="overflow- } /* 13.3 Generate codes: the code-expiry choices are one control, the reference's segmented choice (palette C: the grey - track, 2px inset, 12.5 / 500 segments, the chosen one the raised pill at 600, inside its transparent 1px edge) + track, 2px inset, segments at the template's 14px at 500, the chosen one the raised pill, same weight, inside its + transparent 1px edge) instead of accent-tinted boxes. The frame is the grid of the generate form (hook redeem-generate-form, RedeemView); the pad keeps the former button test's weight (0,8,3). */ html.flat-theme form[data-ui="redeem-generate-form"] > :where(div) > div[class~="grid"][class~="grid-cols-2"][class~="sm:grid-cols-5"]:not([data-uipad][data-uipad][data-uipad]) { @@ -1693,8 +1522,8 @@ html.flat-theme div[class~="grid"][class~="grid-cols-2"][class~="sm:grid-cols-5" border-radius: var(--ui-radius-md); background-color: transparent; color: rgb(var(--ui-muted)); - font-size: 12.5px; - line-height: 18.75px; + font-size: 14px; + line-height: 20px; font-weight: 500; transition: color var(--ui-duration-slide) var(--ui-ease-out), background-color var(--ui-duration-slide) var(--ui-ease-out); } @@ -1709,7 +1538,7 @@ html.flat-theme div[class~="grid"][class~="grid-cols-2"][class~="sm:grid-cols-5" background-color: rgb(var(--ui-thumb)); background-clip: padding-box; color: rgb(var(--ui-ink)); - font-weight: 600; + font-weight: 500; box-shadow: var(--ui-thumb-shadow); } @@ -1720,7 +1549,8 @@ html.flat-theme div[class~="grid"][class~="grid-cols-2"][class~="sm:grid-cols-5" /* 13.4 Generated codes: the header is the dialog header (14 / 18 inset on the structural line, title 14/600, the count 12px muted), its success mark the 28px square in the success pair (a status), the close the 28px bordered button; the - codes the reference's snippet (Geist Mono 12.5 on the canvas, radius 6, 12 inset); the footer the dialog footer. Hooks + codes the reference's snippet (the template's 14px mono on the canvas, radius 6, 12 inset); the footer the dialog + footer. Hooks (RedeemView): redeem-result-head on the header, redeem-result-body on the body; the footer is the body's next sibling. `div > …:first-child` / `div > … + div:last-child` keep the weight of the former anchor. */ html.flat-theme div > div[data-ui="redeem-result-head"]:first-child { @@ -1787,7 +1617,7 @@ html.flat-theme [data-ui="redeem-result-body"] textarea { border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-canvas)); color: rgb(var(--ui-ink)); - font-size: 12.5px; + font-size: 14px; line-height: 1.625; } @@ -1802,7 +1632,7 @@ html.flat-theme div > [data-ui="redeem-result-body"] + div:last-child { 用户 / 按条件」 is an either / or, drawn as the reference's segmented choice (the radios stay in the page for keyboards and screen readers, visually hidden; the chosen label is the raised pill on the grey track, palette C; keyboard focus rings the segment); the condition - groups are cards on the surface without shadow; 「OR」 / 「AND」 counts in Geist Mono. */ + groups are cards on the surface without shadow; 「OR」 / 「AND」 counts 12px. */ html.flat-theme [data-ui="announcement-targeting"] { padding: 14px; border-color: rgb(var(--ui-line)); @@ -1812,8 +1642,8 @@ html.flat-theme [data-ui="announcement-targeting"] { html.flat-theme [data-ui="announcement-targeting"] > div:first-child > div:first-child > div:first-child { color: rgb(var(--ui-ink)); - font-size: 13px; - font-weight: 600; + font-size: 14px; + font-weight: 500; } html.flat-theme [data-ui="announcement-targeting"] > div:first-child > div:first-child > div + div { @@ -1837,8 +1667,8 @@ html.flat-theme [data-ui="announcement-targeting"] > div:first-child > div:last- padding: 6px 14px; border-radius: var(--ui-radius-md); color: rgb(var(--ui-muted)); - font-size: 12.5px; - line-height: 18.75px; + font-size: 14px; + line-height: 20px; font-weight: 500; cursor: pointer; transition: color var(--ui-duration-slide) var(--ui-ease-out), background-color var(--ui-duration-slide) var(--ui-ease-out); @@ -1851,7 +1681,7 @@ html.flat-theme [data-ui="announcement-targeting"] > div:first-child > div:last- html.flat-theme [data-ui="announcement-targeting"] > div:first-child > div:last-child > label:where([data-ui~="targeting-option"]):has(> input[type="radio"]:checked) { background-color: rgb(var(--ui-thumb)); color: rgb(var(--ui-ink)); - font-weight: 600; + font-weight: 500; box-shadow: var(--ui-thumb-shadow); } @@ -1890,15 +1720,14 @@ html.flat-theme [data-ui="announcement-targeting"] .input-label { } html.flat-theme [data-ui="announcement-targeting"] div[class~="text-sm"][class~="font-medium"] > span[class~="text-xs"] { - font-family: var(--ui-font-mono); - font-size: 11px; + font-size: 12px; } html.flat-theme [data-ui="announcement-targeting"] div[class~="border-dashed"] { border-color: rgb(var(--ui-line-strong)); border-radius: var(--ui-radius-lg); color: rgb(var(--ui-muted)); - font-size: 13px; + font-size: 14px; } /* 13.6 Promo codes: a label's 「(留空自动生成)」-style hint reads as part of the label, at its size, in the muted grey. */ @@ -1910,8 +1739,8 @@ html.flat-theme :is(#create-promo-form, #edit-promo-form) .input-label > span[cl } /* 13.6b Promo code usages: rows of the reference's list (1px line, radius 8, 10 / 12 inset); the user glyph in the 28px neutral - square (upstream's green circle is decoration), the e-mail 13/500 ink, the time Geist Mono 11 muted; the credited amount - keeps the success colour (a credit), in Geist Mono. Hook: data-ui="promo-usages" on the list. */ + square (upstream's green circle is decoration), the e-mail the template's 14/500 ink, the time 12px muted; the credited + amount keeps the success colour (a credit), 14px tabular. Hook: data-ui="promo-usages" on the list. */ html.flat-theme [data-ui="promo-usages"] > div[class~="border"] { padding: 10px 12px; border-color: rgb(var(--ui-line)); @@ -1934,18 +1763,11 @@ html.flat-theme [data-ui="promo-usages"] > div[class~="border"] div[class~="roun html.flat-theme [data-ui="promo-usages"] > div[class~="border"] p[class~="text-xs"] { color: rgb(var(--ui-muted)); - font-family: var(--ui-font-mono); - font-size: 11px; + font-size: 12px; } html.flat-theme [data-ui="promo-usages"] > div[class~="border"] div[class~="text-right"] > span { color: rgb(var(--ui-success)); - font-family: var(--ui-font-mono); - font-size: 13px; + font-size: 14px; font-variant-numeric: tabular-nums; } - -/* 13.7 Read status: e-mails 13/500 ink (data.css draws the table). */ -html.flat-theme [data-ui="announcement-read-status"] td[data-column="email"] > span { - font-weight: 500; -} diff --git a/frontend/src/styles/console/shell.css b/frontend/src/styles/console/shell.css index af90f0a683cc..4c4cb782fa7f 100644 --- a/frontend/src/styles/console/shell.css +++ b/frontend/src/styles/console/shell.css @@ -33,17 +33,17 @@ html.flat-theme { --ui-shell-header-h: calc(var(--ui-shell-pad-y) + var(--ui-shell-row)); --ui-shell-pad-top: var(--ui-shell-gap); --ui-shell-pad-bottom: var(--ui-shell-pad-y); - /* Sidebar text and icons are ink at an alpha (REF §8.1: text 50 %, icons 30 %). With palette C's softer ink the - alphas rise so the nav keeps its greys: 66 % text (4.98-5.21:1 on the canvas, the hover fill and the active - tint) and 50 % icons (3.11-3.19:1). Dark (graphite): 56 % text (4.55:1 on hover, 5.27:1 on the canvas), 40 % - icons (3.07-3.34:1). */ - --ui-shell-nav-fg: 0.66; - --ui-shell-nav-icon: 0.5; + /* Sidebar text, solid official colours (upstream's .sidebar-link text-gray-600 / dark:text-dark-300 and + .sidebar-section-title text-gray-400 / dark:text-dark-500): links #4b5563 (7.24:1 on the canvas, 6.56:1 on the + active tint), section titles the AA meta grey #616977 (official #9ca3af is 2.54:1). Icons draw in their link's + colour. Dark: links #d1d5db (11.78:1), section titles #a4abb6. */ + --ui-shell-nav-text: 75 85 99; + --ui-shell-nav-section: 97 105 119; } html.flat-theme.dark { - --ui-shell-nav-fg: 0.56; - --ui-shell-nav-icon: 0.4; + --ui-shell-nav-text: 209 213 219; + --ui-shell-nav-section: 164 171 182; } /* --ui-header-h (tokens.css) is the sticky header keyboard focus must clear (behaviour.css B3.4): nothing sticks on @@ -161,8 +161,8 @@ html.flat-theme .sidebar.w-\[72px\] { padding-right: clamp(0.5rem, 1.5vw, 0.75rem); } -/* Brand: the 20px mark 8px in from the edge (x = 24), then the site name 13/600 and, under it, the version in mono - (the reference's row is a mark and a word; ours also carries the version, so the lockup is two lines). */ +/* Brand: the 20px mark 8px in from the edge (x = 24), then the site name 14/600 and, under it, the version (the + reference's row is a mark and a word; ours also carries the version, so the lockup is two lines). */ html.flat-theme .sidebar .sidebar-header { height: auto; min-height: 30px; @@ -194,12 +194,13 @@ html.flat-theme .sidebar .sidebar-brand { html.flat-theme .sidebar .sidebar-brand-title, html.flat-theme .sidebar .sidebar-brand-title:hover { - font: 600 13px/16px var(--ui-font); + font: 600 14px/20px var(--ui-font); color: rgb(var(--ui-ink)); } -/* VersionBadge: the admin button and the plain text for users, as mono meta (REF §5: Mono 10.5 captions). The - update notice keeps its dot and turns the text to the warning colour. Its panel is a popover (tokens). */ +/* VersionBadge: the admin button and the plain text for users, as meta (upstream text-xs font-medium: 12/16/500, + faint). The update notice keeps its dot and turns the text to the warning colour. Its panel is a popover + (tokens). */ html.flat-theme .sidebar .sidebar-brand > .relative { display: flex; min-width: 0; @@ -213,7 +214,7 @@ html.flat-theme .sidebar .sidebar-brand > .relative > :is(button, span) { padding: 0; border-radius: var(--ui-radius-sm); background: transparent; - font: 400 10.5px/14px var(--ui-font-mono); + font: 500 12px/16px var(--ui-font); color: rgb(var(--ui-faint)); transition: color var(--ui-duration) var(--ui-ease); } @@ -260,15 +261,16 @@ html.flat-theme .sidebar .sidebar-section:last-child { margin-bottom: 0; } -/* Section titles are the mono label (docs navigation groups: 8px above the first item, flush with the item box). */ +/* Section titles: upstream's text-xs font-semibold uppercase tracking-wider, 12/16/600, 0.05em, in the section grey + (8px above the first item, flush with the item box). */ html.flat-theme .sidebar .sidebar-section-title { - min-height: var(--ui-label-leading); + min-height: 16px; margin: 0 0 8px; padding: 0; - font: var(--ui-label-font); - letter-spacing: var(--ui-label-tracking); + font: 600 12px/16px var(--ui-font); + letter-spacing: 0.05em; text-transform: uppercase; - color: rgb(var(--ui-label)); + color: rgb(var(--ui-shell-nav-section)); } html.flat-theme .sidebar .sidebar-section-title::after { @@ -277,10 +279,11 @@ html.flat-theme .sidebar .sidebar-section-title::after { background: rgb(var(--ui-line)); } -/* Items: min 32px (13px on a 19.5px line + 7px above and below = 33.5), 10px inset, 6px radius, 8px from the 15px - icon to the label, 1px apart. Text and icon are ink at the nav alphas; hover lays ink at 2 % and lifts the text to - 70 %; the current item only gets the accent at 8 % (REF §8.1: its text and icon keep their colour). Focus is the - browser's ring, as on the reference. */ +/* Items: min 32px (14px on a 20px line + 7px above and below = 34), 10px inset, 6px radius, 8px from the 15px icon + to the label, 1px apart. Text is upstream's text-sm font-medium (14/20/500) in the nav grey, the icon in the + text's colour; hover lays ink at 2 % and turns the text ink (upstream hover:text-gray-900); the current item is + the accent text (upstream text-primary-600, our --ui-link) on the accent at 8 %. Focus is the browser's ring, as + on the reference. */ html.flat-theme .sidebar .sidebar-link { gap: 8px; min-height: 32px; @@ -288,8 +291,8 @@ html.flat-theme .sidebar .sidebar-link { padding: 7px 10px; border-radius: var(--ui-radius-md); background-color: transparent; - font: 400 13px/19.5px var(--ui-font); - color: rgb(var(--ui-ink) / var(--ui-shell-nav-fg)); + font: 500 14px/20px var(--ui-font); + color: rgb(var(--ui-shell-nav-text)); transition: color var(--ui-duration) var(--ui-ease), background-color var(--ui-duration) var(--ui-ease); @@ -297,20 +300,20 @@ html.flat-theme .sidebar .sidebar-link { html.flat-theme .sidebar .sidebar-link:hover { background-color: rgb(var(--ui-ink) / 0.02); - color: rgb(var(--ui-ink) / 0.7); + color: rgb(var(--ui-ink)); } html.flat-theme .sidebar .sidebar-link.sidebar-link-active, html.flat-theme .sidebar .sidebar-link.sidebar-link-active:hover { background-color: rgb(var(--ui-primary) / 0.08); - color: rgb(var(--ui-ink) / var(--ui-shell-nav-fg)); + color: var(--ui-link); } html.flat-theme .sidebar .sidebar-link > :is(svg, .sidebar-svg-icon) { flex: 0 0 15px; width: 15px; height: 15px; - color: rgb(var(--ui-ink) / var(--ui-shell-nav-icon)); + color: currentColor; } html.flat-theme .sidebar .sidebar-svg-icon svg { @@ -318,11 +321,11 @@ html.flat-theme .sidebar .sidebar-svg-icon svg { height: 15px; } -/* a group's chevron sits at the row's end in the icon colour */ +/* a group's chevron sits at the row's end in the text's colour */ html.flat-theme .sidebar .sidebar-label-flex > svg { width: 13px; height: 13px; - color: rgb(var(--ui-ink) / var(--ui-shell-nav-icon)); + color: currentColor; } /* A group's children hang from a 1px guide under the parent's icon. */ @@ -339,7 +342,7 @@ html.flat-theme .sidebar .sidebar-section > div.border-l > .sidebar-link { } /* Collapsed rail (64px): 35 x 32 icon items centred (REF §8.1). The folded label (already aria-hidden) gives up its - line box too, so the item is the reference's 32px rather than 33.5. */ + line box too, so the item is the reference's 32px rather than 34. */ html.flat-theme .sidebar .sidebar-link.sidebar-link-collapsed { justify-content: center; gap: 0; @@ -355,8 +358,8 @@ html.flat-theme .sidebar .sidebar-link.sidebar-link-collapsed > .sidebar-label-c } /* Bottom: a 1px rule, 12px, then the theme switch as a row with the collapse control at its end: the reference's - panel-left glyph, 28 x 28, ink 30 % -> 60 % on hover with no fill (REF §8.1; 46 % here for 3:1). The collapse - label stays for screen readers. */ + panel-left glyph, 28 x 28, in the nav grey -> ink on hover with no fill (REF §8.1). The collapse label stays for + screen readers. */ html.flat-theme .sidebar > div.mt-auto { display: flex; align-items: center; @@ -380,12 +383,12 @@ html.flat-theme .sidebar > div.mt-auto > button.sidebar-link:last-child:not(:fir margin: 0; padding: 0; gap: 0; - color: rgb(var(--ui-ink) / var(--ui-shell-nav-icon)); + color: rgb(var(--ui-shell-nav-text)); } html.flat-theme .sidebar > div.mt-auto > button.sidebar-link:last-child:not(:first-child):hover { background-color: transparent; - color: rgb(var(--ui-ink) / 0.6); + color: rgb(var(--ui-ink)); } html.flat-theme .sidebar > div.mt-auto > button.sidebar-link:last-child:not(:first-child) > svg { @@ -535,8 +538,9 @@ html.flat-theme .app-main > header > div > div:last-child { gap: 10px; } - /* Title · description: the page title 20/28 600 (REF §8.2), the description 13px muted on the same baseline - after a faint dot. A description longer than the row wraps, its lines under its first word (nothing is cut; the + /* Title · description: the page title 18/28 600 (upstream text-lg font-semibold), the description 12/16 muted + (upstream text-xs) on the same baseline after a faint dot. A description longer than the row wraps, its lines + under its first word (nothing is cut; the ellipsis hid the end of 操作日志's and 提示词审计's notes). Perf pass: the wrapper is hooked ([data-ui="header-title"], AppHeader.vue) instead of found by a has-h1 test on a bare div; :where() + :not(uipad) keep the original 0,2,4. */ @@ -549,7 +553,7 @@ html.flat-theme .app-main > header > div > div:last-child { html.flat-theme .app-main > header h1 { flex: 0 0 auto; - font: 600 20px/28px var(--ui-font); + font: 600 18px/28px var(--ui-font); letter-spacing: 0; white-space: nowrap; color: rgb(var(--ui-ink)); @@ -559,7 +563,7 @@ html.flat-theme .app-main > header h1 + p { display: flex; align-items: baseline; min-width: 0; - font: 400 13px/19.5px var(--ui-font); + font: 400 12px/16px var(--ui-font); overflow-wrap: anywhere; color: rgb(var(--ui-muted)); } @@ -614,7 +618,8 @@ html.flat-theme .app-main > header :is(button[data-ui="announcement-bell"], [dat height: 6px; } -/* Docs and model plaza: ghost text links (the reference's header links: 12.5/500 muted -> ink) with a 13px glyph. */ +/* Docs and model plaza: ghost text links (upstream text-sm font-medium text-gray-600 dark:text-dark-400: 14/20/500, + #4b5563 / #a4abb6 -> ink) with a 13px glyph. */ html.flat-theme .app-main > header > div > div:last-child > a { align-items: center; gap: 6px; @@ -622,8 +627,8 @@ html.flat-theme .app-main > header > div > div:last-child > a { padding: 0 8px; border-radius: var(--ui-radius-md); background-color: transparent; - font: 500 12.5px/18.75px var(--ui-font); - color: rgb(var(--ui-muted)); + font: 500 14px/20px var(--ui-font); + color: rgb(var(--theme-text-gray-600)); transition: color var(--ui-duration) var(--ui-ease), background-color var(--ui-duration) var(--ui-ease); @@ -640,7 +645,8 @@ html.flat-theme .app-main > header > div > div:last-child > a > svg { } /* The language switch is a dropdown trigger: the reference's filter pill (REF §8.6: 30px, 10px inset, 6px gap, 1px - control outline, surface fill, 12.5/600 ink-soft -> ink, 12px chevron). Its menu is overlays.css'. */ + control outline, surface fill, ink-soft -> ink, 12px chevron) with upstream's text-sm font-medium (14/20/500). Its + menu is overlays.css'. */ html.flat-theme .app-main > header [data-ui="header-locale"] > button { gap: 6px; height: 30px; @@ -648,7 +654,7 @@ html.flat-theme .app-main > header [data-ui="header-locale"] > button { border: 1px solid rgb(var(--ui-line-strong)); border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-surface)); - font: 600 12.5px/18.75px var(--ui-font); + font: 500 14px/20px var(--ui-font); color: rgb(var(--ui-ink-soft)); transition: color var(--ui-duration) var(--ui-ease); } @@ -659,7 +665,7 @@ html.flat-theme .app-main > header [data-ui="header-locale"] > button:hover { } html.flat-theme .app-main > header [data-ui="header-locale"] > button > span.text-base { - font-size: 13px; + font-size: 14px; line-height: 1; } @@ -669,7 +675,8 @@ html.flat-theme .app-main > header [data-ui="header-locale"] > button > svg { color: inherit; } -/* Subscriptions: the same outlined 30px control; the dots keep their state colours, the count is mono. */ +/* Subscriptions: the same outlined 30px control; the dots keep their state colours, the count is upstream's text-xs + font-medium (12/16/500) with tabular figures. */ html.flat-theme .app-main > header [data-ui="header-subscription"] > button { gap: 6px; height: 30px; @@ -697,14 +704,15 @@ html.flat-theme .app-main > header [data-ui="header-subscription"] > button div. } html.flat-theme .app-main > header [data-ui="header-subscription"] > button span.text-xs { - font: 500 11px/16.5px var(--ui-font-mono); + font: 500 12px/16px var(--ui-font); font-variant-numeric: tabular-nums; color: inherit; } -/* Balance: a mono chip (WAVE1 D1, D4: neutral, no tint): wallet glyph, the amount in Geist Mono tabular, the frozen - amount as a warning pill; the hover card is the reference's figure breakdown (REF §8.9: 6px radius, control outline, - surface, 12 / 8 inset, 12px rows with mono tabular values). */ +/* Balance: an outlined chip (WAVE1 D1: no tint behind it): wallet glyph, the amount in upstream's own classes + (text-sm font-semibold text-primary-700 / dark:text-primary-300: 14/20/600 accent, tabular), the frozen amount as a + warning pill (upstream text-xs font-medium); the hover card is the reference's figure breakdown (REF §8.9: 6px + radius, control outline, surface, 12 / 8 inset, upstream's text-xs rows with tabular values). */ html.flat-theme .app-main > header [data-ui="header-balance"] { align-items: center; gap: 6px; @@ -722,16 +730,14 @@ html.flat-theme .app-main > header [data-ui="header-balance"] > svg { } html.flat-theme .app-main > header [data-ui="header-balance"] > span.text-sm { - font: 500 12.5px/18.75px var(--ui-font-mono); font-variant-numeric: tabular-nums; - color: rgb(var(--ui-ink)); } html.flat-theme .app-main > header [data-ui="header-balance"] > span.rounded-full { padding: 1px 6px; border-radius: 9999px; background-color: rgb(var(--ui-warning-soft)); - font: 500 10.5px/15.75px var(--ui-font-mono); + font: 500 12px/16px var(--ui-font); font-variant-numeric: tabular-nums; color: rgb(var(--ui-warning)); } @@ -744,7 +750,7 @@ html.flat-theme .app-main > header [data-ui="header-balance"] > div.absolute { border-radius: var(--ui-radius-md); background-color: rgb(var(--ui-surface)); box-shadow: var(--ui-shadow-popover); - font: 400 12px/18px var(--ui-font); + font: 400 12px/16px var(--ui-font); } html.flat-theme .app-main > header [data-ui="header-balance"] > div.absolute span { @@ -752,7 +758,6 @@ html.flat-theme .app-main > header [data-ui="header-balance"] > div.absolute spa } html.flat-theme .app-main > header [data-ui="header-balance"] > div.absolute span:last-child { - font-family: var(--ui-font-mono); font-variant-numeric: tabular-nums; } @@ -764,15 +769,16 @@ html.flat-theme .app-main > header [data-ui="header-balance"] > div.absolute > d border-top-color: rgb(var(--ui-line)); } -/* The account menu trigger: the reference's account row (the sidebar "Log in": a 20px ring, 13/500, ink 70 % -> ink, - ink 2 % on hover), with the name and role on one line. The menu is overlays.css'. */ +/* The account menu trigger: the reference's account row (the sidebar "Log in": a 20px ring, ink 2 % on hover), with + the name and role on one line in upstream's classes: the name text-sm font-medium ink (14/20/500), the role + text-xs muted (12/16). The menu is overlays.css'. */ html.flat-theme .app-main > header [data-ui="header-user"] > button { gap: 8px; height: 30px; padding: 0 8px 0 5px; border-radius: var(--ui-radius-md); background-color: transparent; - color: rgb(var(--ui-ink) / 0.7); + color: rgb(var(--ui-ink)); transition: color var(--ui-duration) var(--ui-ease), background-color var(--ui-duration) var(--ui-ease); @@ -790,7 +796,7 @@ html.flat-theme .app-main > header [data-ui="header-user"] > button > div:first- border-radius: 9999px; background: rgb(var(--ui-surface)); box-shadow: none; - font: 600 9px/1 var(--ui-font-mono); + font: 500 12px/1 var(--ui-font); color: rgb(var(--ui-ink-soft)); } @@ -803,12 +809,12 @@ html.flat-theme .app-main > header [data-ui="header-user"] > button > div:first- } html.flat-theme .app-main > header [data-ui="header-user"] > button > div:nth-child(2) > div:first-child { - font: 500 13px/19.5px var(--ui-font); + font: 500 14px/20px var(--ui-font); color: inherit; } html.flat-theme .app-main > header [data-ui="header-user"] > button > div:nth-child(2) > div:last-child { - font: 400 12px/18px var(--ui-font); + font: 400 12px/16px var(--ui-font); color: rgb(var(--ui-muted)); } diff --git a/frontend/src/styles/console/tables.css b/frontend/src/styles/console/tables.css index e87cdaa296b8..977aaaddb48b 100644 --- a/frontend/src/styles/console/tables.css +++ b/frontend/src/styles/console/tables.css @@ -1,10 +1,11 @@ /* Console theme · tables: the table layout that keeps rows readable without sideways scrolling (owner decision * 2026-09-30, option B of the ui-tune table-width round: rows may grow taller, nothing is hidden, no column or value is - * cut, the Experiential Labs look of type, radii, colours and headers is unchanged). + * cut). * * Plain CSS after the page files; every selector starts with html.flat-theme (theme off = upstream, and DataTable only - * stacks cells while the theme is on). Layout only: the colours are the other files' (the few colour values here are - * roles already used for the same element elsewhere: the platform chip's divider, the header's hover ink). + * stacks cells while the theme is on). Layout only: the text is official sub2api's (the templates' classes, data.css' + * headers); here only the stacked lines' base text and their line label (section 1), and colour roles already used for + * the same element elsewhere (the platform chip's divider, the header's hover ink). * * Stacked cells (DataTable, Column.stack; components/common/columnStack.ts): related columns become lines of one cell, * each part keeping its own cell slot, header label, sort control and help tip. Hooks (static, from the column @@ -23,16 +24,17 @@ * 错误请求(管理)below 1800px; 使用记录(用户), 错误请求(用户)and IP管理 below 1400px (stackBelow in each view's column * definitions). Above that width a table is the flat table of section 4. Theme rules keyed on a merged column * (td[data-column="K"]) carry over to its line: where the line looks exactly as the column did, the column's own rule - * also names the line (data.css, behaviour.css, accounts.css, dashboard-usage.css, users-groups-proxies-keys.css); - * where the line lays out differently, the line's rule is here (section 3). Host-cell rules of the form `> div` skip - * the lines container (:where(:not(cell-lines))). + * also names the line (behaviour.css, accounts.css, users-groups-proxies-keys.css; the record tables' text is the + * templates' own, so no text rule needs to); where the line lays out differently, the line's rule is here (section 3). + * Host-cell rules of the form `> div` skip the lines container (:where(:not(cell-lines))). * * Performance: no :has(), no sibling combinator, no :nth-child(of); the hooks never change at runtime. */ /* ================================================================== 1 · the stack hooks (every stacked table) */ /* The lines of a stacked cell, under the host's own content. The container holds off what a host cell sets for its - own content (data.css draws 优先级 in Geist Mono 12.5), so each line looks as its own column did. */ + own content (a size, weight or figure style), so each line starts from official's td text (text-sm, 14 / 20, 400) + and looks as its own column did. */ html.flat-theme .table-wrapper td[data-stack] > div[data-ui="cell-lines"] { display: flex; flex-direction: column; @@ -43,10 +45,10 @@ html.flat-theme .table-wrapper td[data-stack] > div[data-ui="cell-lines"] { max-width: none; margin-top: 6px; font-family: var(--ui-font); - font-size: 13px; + font-size: 14px; font-weight: 400; font-variant-numeric: normal; - line-height: 19.5px; + line-height: 20px; } /* nothing above the lines: the host's content is their first (labelled) line, or the host column is hidden */ @@ -64,7 +66,6 @@ html.flat-theme .table-wrapper td[data-stack] > div[data-ui="cell-lines"] > div[ max-width: 100%; margin: 0; padding: 0; - font-weight: 400; } /* chips of the same kind share one line (分组 · 类型 · 计费, 位置 · 账号数) */ @@ -81,13 +82,14 @@ html.flat-theme .table-wrapper div[data-ui="cell-line"] > div[data-ui="cell-line max-width: 100%; } -/* the line label: the console's mono label, as 管理分类 / 请求路由 already are in the accounts table */ +/* the line label: official's inline label (UsageTable's 「入站:」: 12 / 16 / 500, the meta grey, sentence case), as + 管理分类 / 请求路由 are in the accounts table */ html.flat-theme .table-wrapper div[data-ui="cell-line"] > span[data-ui="cell-line-label"] { flex: none; font: var(--ui-label-font); letter-spacing: var(--ui-label-tracking); - line-height: 18px; - text-transform: uppercase; + line-height: 16px; + text-transform: none; white-space: nowrap; color: rgb(var(--ui-muted)); } @@ -98,7 +100,7 @@ html.flat-theme .table-wrapper div[data-ui="cell-line"] > span[data-ui="cell-lin html.flat-theme .table-wrapper[data-table="accounts"] .table-header .sticky-header-cell { white-space: normal; word-break: keep-all; - line-height: 15px; + line-height: var(--ui-th-leading); vertical-align: bottom; } @@ -107,6 +109,12 @@ html.flat-theme .table-wrapper[data-table="accounts"] .table-header .sticky-head row-gap: 2px; } +/* 状态 at the 14px header size (29px) + the 4px gap + the 11px sort box must share one line, or the box wraps alone and + 状态 floats a line above its neighbours */ +html.flat-theme .table-wrapper[data-table="accounts"] .table-header .sticky-header-cell[data-column="status"] > div { + min-width: 44px; +} + html.flat-theme .table-wrapper .table-header .sticky-header-cell[data-stack] > div > div[data-ui="th-subs"] { display: flex; flex-basis: 100%; @@ -336,8 +344,9 @@ html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="status"] > line-height: 0; } -/* ---- 管理分类 / 请求路由 / 路由分组 (/ 代理): labelled lines, the mono label in a 46px gutter, the value beside it (a value - that wraps keeps its indent); the group badges flow and show their whole names */ +/* ---- 管理分类 / 请求路由 / 路由分组 (/ 代理): labelled lines, the label (12 / 16 / 500) in a 50px gutter (four 12px CJK + glyphs are 48px), the value beside it, 56px in (a value that wraps keeps its indent); the group badges flow and show + their whole names. English puts the label over its value at every width (end of this section). */ html.flat-theme .table-wrapper[data-table="accounts"] th[data-column="taxonomy_route"] { min-width: 10rem; } @@ -353,7 +362,7 @@ html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="taxonomy_r html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="taxonomy_route"] > div:not([data-ui="cell-lines"]) > div, html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="taxonomy_route"] > div[data-ui="cell-lines"] > div[data-ui="cell-line"] { margin-top: 0; - padding-left: 52px; + padding-left: 56px; } html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="taxonomy_route"] > div:not([data-ui="cell-lines"]) > div:first-child { @@ -377,10 +386,10 @@ html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="taxonomy_r html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="taxonomy_route"] > div[data-ui="cell-lines"] > div[data-ui="cell-line"] > span[data-ui="cell-line-label"] { display: inline-block; flex: none; - width: 46px; - margin-left: -52px; + width: 50px; + margin-left: -56px; margin-right: 6px; - line-height: 18px; + line-height: 16px; } html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="taxonomy_route"] > div:not([data-ui="cell-lines"]) > div > span[class~="uppercase"][class~="text-[10px]"] { @@ -544,10 +553,45 @@ html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="actions"] } } +/* ---- English (:lang(en), the app's other locale): the taxonomy labels (Management Classification, Request Routing, + Routing Groups: 90-155px at 12 / 500) fit no gutter (the 50px box holds four CJK glyphs; from 1400px they ran over + their values), so they sit over their values at every width, as below 1400px. Below 1440px they may also wrap at a + space and the column keeps the stacked layout's 7rem floor, so the one-line 155px label no longer sets the column's + width (live data: at 1280 the table scrolls 28px instead of 79, at 1400 it fits instead of scrolling 18px); from + 1440px, where the table fits, a label keeps one line. Chinese is unchanged. */ +html.flat-theme:lang(en) .table-wrapper[data-table="accounts"] td[data-column="taxonomy_route"] > div:not([data-ui="cell-lines"]) > div, +html.flat-theme:lang(en) .table-wrapper[data-table="accounts"] td[data-column="taxonomy_route"] > div[data-ui="cell-lines"] > div[data-ui="cell-line"] { + padding-left: 0; +} + +html.flat-theme:lang(en) .table-wrapper[data-table="accounts"] td[data-column="taxonomy_route"] > div[data-ui="cell-lines"] > div[data-ui="cell-line"] { + flex-direction: column; +} + +html.flat-theme:lang(en) .table-wrapper[data-table="accounts"] td[data-column="taxonomy_route"] > div:not([data-ui="cell-lines"]) > div > span[class~="uppercase"][class~="text-[10px]"], +html.flat-theme:lang(en) .table-wrapper[data-table="accounts"] td[data-column="taxonomy_route"] > div[data-ui="cell-lines"] > div[data-ui="cell-line"] > span[data-ui="cell-line-label"] { + display: block; + width: auto; + margin-left: 0; + margin-right: 0; +} + +@media (max-width: 1439.98px) { + html.flat-theme:lang(en) .table-wrapper[data-table="accounts"] th[data-column="taxonomy_route"] { + min-width: 7rem; + } + + html.flat-theme:lang(en) .table-wrapper[data-table="accounts"] td[data-column="taxonomy_route"] > div:not([data-ui="cell-lines"]) > div > span[class~="uppercase"][class~="text-[10px]"], + html.flat-theme:lang(en) .table-wrapper[data-table="accounts"] td[data-column="taxonomy_route"] > div[data-ui="cell-lines"] > div[data-ui="cell-line"] > span[data-ui="cell-line-label"] { + flex: 0 1 auto; + max-width: 100%; + white-space: normal; + } +} + /* ================================================================== 3 · lines whose layout differs from their column's - (the columns' own rules name the lines where they look the same: data.css figures, behaviour.css B1.5, - accounts.css groups / capacity / today's stats, dashboard-usage.css endpoint paths, users-groups-proxies-keys.css - the account-count chip and the created time) */ + (the columns' own rules name the lines where they look the same: behaviour.css B1.5, accounts.css groups / capacity / + today's stats, users-groups-proxies-keys.css the account-count chip and the created time) */ /* behaviour.css B1.4: the created time as date / time, a block here so the line's label sits by the date */ html.flat-theme .table-wrapper div[data-ui="cell-line"][data-line="created_at"] > div[data-ui="cell-line-value"] > span { @@ -603,7 +647,7 @@ html.flat-theme :is(.table-wrapper[data-table="usage"], .table-wrapper[data-tabl html.flat-theme :is(.table-wrapper[data-table="usage"], .table-wrapper[data-table="proxies"], [data-ui~="usage-records"] .table-wrapper:not([data-table="usage"]), [data-ui~="ops-error-list"] .table-wrapper, [data-ui="user-usage"] .table-wrapper:not([data-table="usage"])) .table-header .sticky-header-cell { white-space: normal; word-break: keep-all; - line-height: 15px; + line-height: var(--ui-th-leading); vertical-align: bottom; } @@ -792,6 +836,13 @@ html.flat-theme .table-wrapper[data-table="proxies"] th[data-column="location"] min-width: 6.5rem; } +/* English: the account count chip (「12 accounts」, 12px) keeps 3px either side instead of 6, so the flat table fits at + 1440 (6px wider than its wrapper with the full inset); Chinese keeps the inset */ +html.flat-theme:lang(en) .table-wrapper[data-table="proxies"] :is(td[data-column="account_count"], div[data-ui="cell-line"][data-line="account_count"] > div[data-ui="cell-line-value"]) > :is(button, span)[class~="inline-flex"] { + padding-left: 3px; + padding-right: 3px; +} + html.flat-theme .table-wrapper[data-table="proxies"] td[data-column="actions"] > div { display: grid; grid-template-columns: repeat(2, 28px); @@ -851,32 +902,56 @@ html.flat-theme .table-wrapper[data-table="proxies"] div[data-ui="cell-line"][da align-items: flex-start; } -/* ================================================================== 6 · below 1400px: the tables that fit at 1440 but not at - 1280 (用户管理, 分组管理, API 密钥, 操作日志, 渠道管理): the same recipe, lighter (8px insets, 12 at the edges; cells may - wrap at word boundaries; chips, badges and figures stay whole; the row actions wrap three a row). Mono text is NOT an - atom here: behaviour.css B1.12 wraps 操作日志's action and method + path, and that stays so. API 密钥 needs about 1437px - on one line, so its recipe reaches up to 1440px. From there up (the others from 1400px) these tables are unchanged. */ +/* ================================================================== 6 · the TablePageLayout tables that wrap below a + width (用户管理, 分组管理, API 密钥, 操作日志, 渠道管理): one recipe, lighter than section 4's (8px insets, 12 at the + edges; cells may wrap at word boundaries; chips, badges and figures stay whole; the row actions wrap three a row). + Mono text is NOT an atom here: behaviour.css B1.12 wraps 操作日志's action and method + path, and that stays so. + Above its width a table is unchanged, except that 渠道管理 lets its free-text description wrap at every width + (below). With official's 14px text and 14px headers (data.css) the recipe applies: + Chinese: 分组管理, 操作日志 and 渠道管理 below 1400px (in the recipe's 100px action box 渠道管理's two text buttons + would stack, so from 1400px only its description wraps); 用户管理 below 1440px (8px too wide at 1400); API 密钥 + below 1536px (about 1450px on one line). + English (:lang(en): the app's other locale, i18n sets ): all five below 1680px, and at every width the + TablePageLayout headers are 12 / 16 (official DataTable's size) and may wrap at a space, bottom-aligned: English + uppercase single words (USERNAME, CONCURRENCY, RATE MULTIPLIER) set the column floors, where official scrolls + sideways. Chinese headers keep one line (behaviour.css B1.9's 「最后活跃 / 时间」). Below 1400px English API 密钥 + lets its usage labels break at a space (end of this section). At 1280 English 用户管理, API 密钥 and 分组管理 still + overflow by a few dozen pixels (one-word columns): accepted. */ + +/* 渠道管理 at every width: the table may shrink to its wrapper and only the free-text description wraps, so a long + description does not push the table sideways where the recipe does not apply (a 64-character one made it 15px too + wide at 1400px in 14px text); the two text buttons stay side by side */ +html.flat-theme [data-ui~="channels-page"] .table-wrapper > table[data-ui~="data-table"]:is(table) { + min-width: 0; + width: 100%; +} + +html.flat-theme [data-ui~="channels-page"] .table-wrapper > table > tbody.table-body > tr > td[data-column="description"] { + white-space: normal; + overflow-wrap: break-word; +} + @media (max-width: 1399.98px) { - html.flat-theme :is(.table-wrapper:is([data-table="users"], [data-table="groups"]), [data-ui~="audit-page"] .table-wrapper, [data-ui~="channels-page"] .table-wrapper) { + html.flat-theme :is(.table-wrapper[data-table="groups"], [data-ui~="audit-page"] .table-wrapper, [data-ui~="channels-page"] .table-wrapper) { --ui-table-cell-x: 8px; --ui-table-edge-x: 12px; } - html.flat-theme :is(.table-wrapper:is([data-table="users"], [data-table="groups"]), [data-ui~="audit-page"] .table-wrapper, [data-ui~="channels-page"] .table-wrapper) > table[data-ui~="data-table"]:is(table) { + html.flat-theme :is(.table-wrapper[data-table="groups"], [data-ui~="audit-page"] .table-wrapper, [data-ui~="channels-page"] .table-wrapper) > table[data-ui~="data-table"]:is(table) { min-width: 0; width: 100%; } - html.flat-theme :is(.table-wrapper:is([data-table="users"], [data-table="groups"]), [data-ui~="audit-page"] .table-wrapper, [data-ui~="channels-page"] .table-wrapper) > table > tbody.table-body > tr > td { + html.flat-theme :is(.table-wrapper[data-table="groups"], [data-ui~="audit-page"] .table-wrapper, [data-ui~="channels-page"] .table-wrapper) > table > tbody.table-body > tr > td { white-space: normal; overflow-wrap: break-word; } - html.flat-theme :is(.table-wrapper:is([data-table="users"], [data-table="groups"]), [data-ui~="audit-page"] .table-wrapper, [data-ui~="channels-page"] .table-wrapper) > table > tbody.table-body > tr > td :is(.badge, [data-ui~="category-chip"], [data-ui="group-badge"], [data-ui="platform-chip"], [class~="rounded"]:is([class~="px-1"], [class~="px-1.5"], [class~="px-2"]), [class~="rounded-md"]:is([class~="px-1.5"], [class~="px-2"]), [class~="rounded-full"][class~="px-2"], [class~="tabular-nums"]) { + html.flat-theme :is(.table-wrapper[data-table="groups"], [data-ui~="audit-page"] .table-wrapper, [data-ui~="channels-page"] .table-wrapper) > table > tbody.table-body > tr > td :is(.badge, [data-ui~="category-chip"], [data-ui="group-badge"], [data-ui="platform-chip"], [class~="rounded"]:is([class~="px-1"], [class~="px-1.5"], [class~="px-2"]), [class~="rounded-md"]:is([class~="px-1.5"], [class~="px-2"]), [class~="rounded-full"][class~="px-2"], [class~="tabular-nums"]) { white-space: nowrap; } - html.flat-theme :is(.table-wrapper:is([data-table="users"], [data-table="groups"]), [data-ui~="audit-page"] .table-wrapper, [data-ui~="channels-page"] .table-wrapper) td[data-column="actions"] > div { + html.flat-theme :is(.table-wrapper[data-table="groups"], [data-ui~="audit-page"] .table-wrapper, [data-ui~="channels-page"] .table-wrapper) td[data-column="actions"] > div { flex-wrap: wrap; max-width: 100px; row-gap: 2px; @@ -884,6 +959,33 @@ html.flat-theme .table-wrapper[data-table="proxies"] div[data-ui="cell-line"][da } @media (max-width: 1439.98px) { + html.flat-theme .table-wrapper[data-table="users"] { + --ui-table-cell-x: 8px; + --ui-table-edge-x: 12px; + } + + html.flat-theme .table-wrapper[data-table="users"] > table[data-ui~="data-table"]:is(table) { + min-width: 0; + width: 100%; + } + + html.flat-theme .table-wrapper[data-table="users"] > table > tbody.table-body > tr > td { + white-space: normal; + overflow-wrap: break-word; + } + + html.flat-theme .table-wrapper[data-table="users"] > table > tbody.table-body > tr > td :is(.badge, [data-ui~="category-chip"], [data-ui="group-badge"], [data-ui="platform-chip"], [class~="rounded"]:is([class~="px-1"], [class~="px-1.5"], [class~="px-2"]), [class~="rounded-md"]:is([class~="px-1.5"], [class~="px-2"]), [class~="rounded-full"][class~="px-2"], [class~="tabular-nums"]) { + white-space: nowrap; + } + + html.flat-theme .table-wrapper[data-table="users"] td[data-column="actions"] > div { + flex-wrap: wrap; + max-width: 100px; + row-gap: 2px; + } +} + +@media (max-width: 1535.98px) { html.flat-theme .table-wrapper[data-table="keys"] { --ui-table-cell-x: 8px; --ui-table-edge-x: 12px; @@ -914,3 +1016,55 @@ html.flat-theme .table-wrapper[data-table="proxies"] div[data-ui="cell-line"][da white-space: nowrap; } } + +/* English: the TablePageLayout headers at 12 / 16 (after data.css' 14 / 20 for the family) and wrapping at a space */ +html.flat-theme:lang(en) .table-page-layout .table-wrapper { + --ui-th-size: 12px; + --ui-th-leading: 16px; +} + +html.flat-theme:lang(en) .table-page-layout .table-wrapper .table-header .sticky-header-cell { + white-space: normal; + vertical-align: bottom; +} + +@media (max-width: 1679.98px) { + html.flat-theme:lang(en) :is(.table-wrapper:is([data-table="users"], [data-table="groups"], [data-table="keys"]), [data-ui~="audit-page"] .table-wrapper, [data-ui~="channels-page"] .table-wrapper) { + --ui-table-cell-x: 8px; + --ui-table-edge-x: 12px; + } + + html.flat-theme:lang(en) :is(.table-wrapper:is([data-table="users"], [data-table="groups"], [data-table="keys"]), [data-ui~="audit-page"] .table-wrapper, [data-ui~="channels-page"] .table-wrapper) > table[data-ui~="data-table"]:is(table) { + min-width: 0; + width: 100%; + } + + html.flat-theme:lang(en) :is(.table-wrapper:is([data-table="users"], [data-table="groups"], [data-table="keys"]), [data-ui~="audit-page"] .table-wrapper, [data-ui~="channels-page"] .table-wrapper) > table > tbody.table-body > tr > td { + white-space: normal; + overflow-wrap: break-word; + } + + html.flat-theme:lang(en) :is(.table-wrapper:is([data-table="users"], [data-table="groups"], [data-table="keys"]), [data-ui~="audit-page"] .table-wrapper, [data-ui~="channels-page"] .table-wrapper) > table > tbody.table-body > tr > td :is(.badge, [data-ui~="category-chip"], [data-ui="group-badge"], [data-ui="platform-chip"], [class~="rounded"]:is([class~="px-1"], [class~="px-1.5"], [class~="px-2"]), [class~="rounded-md"]:is([class~="px-1.5"], [class~="px-2"]), [class~="rounded-full"][class~="px-2"], [class~="tabular-nums"]) { + white-space: nowrap; + } + + html.flat-theme:lang(en) :is(.table-wrapper:is([data-table="users"], [data-table="groups"], [data-table="keys"]), [data-ui~="audit-page"] .table-wrapper, [data-ui~="channels-page"] .table-wrapper) td[data-column="actions"] > div { + flex-wrap: wrap; + max-width: 100px; + row-gap: 2px; + } + + html.flat-theme:lang(en) .table-wrapper[data-table="keys"] td[data-column="usage"] :is(span, div), + html.flat-theme:lang(en) .table-wrapper[data-table="keys"] td[data-column="key"] code { + white-space: nowrap; + } +} + +/* English API 密钥 below 1400px: the label of a usage line may break at its space (「Last / 30d:」 beside its figure; the + figures stay whole), so the column gives back the width its official 14px lines added (measured: at 1280 the table + scrolls 70px instead of 86; at 1366 the row actions fit two a row instead of one) */ +@media (max-width: 1399.98px) { + html.flat-theme:lang(en) .table-wrapper[data-table="keys"] td[data-column="usage"] div[class~="flex"] > span:first-child { + white-space: normal; + } +} diff --git a/frontend/src/styles/console/tokens.css b/frontend/src/styles/console/tokens.css index 401ab5fff46d..6604dd02d15d 100644 --- a/frontend/src/styles/console/tokens.css +++ b/frontend/src/styles/console/tokens.css @@ -16,56 +16,6 @@ * and identity soft fills composited over the surface they sit on (#212125), the status soft fills and the uptime * bars as composited over the reference's surface (#1a1a1a). */ -/* ------------------------------------------------------------------ fonts */ -/* Geist 1.800 and Geist Mono 1.700 (npm `geist` 1.7.2, SIL OFL 1.1: assets/fonts/geist/OFL.txt), variable - weight 100-900, Latin subset; the reference serves the same outlines (REF-CONSOLE §4). Downloaded only while a - rule uses the family, i.e. while the theme is on. Chinese uses MiSans, loaded at runtime from Xiaomi's font - service by utils/flatTheme.ts. */ -@font-face { - font-family: 'Geist'; - src: url('../../assets/fonts/geist/Geist-Variable.latin.woff2') format('woff2'); - font-style: normal; - font-weight: 100 900; - font-display: swap; - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, - U+0329, U+2000-206F, U+20AC, U+2122, U+2190-2193, U+2212, U+2215, U+FEFF, U+FFFD; -} - -@font-face { - font-family: 'Geist Mono'; - src: url('../../assets/fonts/geist/GeistMono-Variable.latin.woff2') format('woff2'); - font-style: normal; - font-weight: 100 900; - font-display: swap; - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, - U+0329, U+2000-206F, U+20AC, U+2122, U+2190-2193, U+2212, U+2215, U+FEFF, U+FFFD; -} - -/* The reference's metric-matched stand-ins (the local Arial sized to Geist's metrics) cover the Latin text while - the files load, so nothing reflows when they arrive and the text never flashes in MiSans' own Latin glyphs. - Same range as the fonts: a glyph Geist does not have skips them. */ -@font-face { - font-family: 'Geist Fallback'; - src: local('Arial'); - ascent-override: 95.94%; - descent-override: 28.16%; - line-gap-override: 0%; - size-adjust: 104.76%; - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, - U+0329, U+2000-206F, U+20AC, U+2122, U+2190-2193, U+2212, U+2215, U+FEFF, U+FFFD; -} - -@font-face { - font-family: 'Geist Mono Fallback'; - src: local('Arial'); - ascent-override: 74.67%; - descent-override: 21.92%; - line-gap-override: 0%; - size-adjust: 134.59%; - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, - U+0329, U+2000-206F, U+20AC, U+2122, U+2190-2193, U+2212, U+2215, U+FEFF, U+FFFD; -} - /* ------------------------------------------------------------------ roles (light) */ /* Palette C (owner approval 2026-09-30; artifacts/tmp/ui-tune/palette/PREVIEW.md): the neutrals are shadcn's zinc (a faint cool tint) instead of pure grey, the ink is softened off black, colour carries meaning (the accent does @@ -89,27 +39,14 @@ --ui-line-strong: 220 220 224; --ui-line-focus: 185 185 192; --ui-control-line: 140 140 148; - /* Text (zinc). ink #1f1f23 (16.4:1 on white; #0a0a0a read black and heavy in dense rows); ink-soft #52525b - (values, filter pills, tooltip descriptions, secondary figures); muted and faint (placeholders, captions) - #66666e: these hold >= 4.5:1 on every neutral fill (surface, canvas, the grouped fill, hover). --ui-label, the - mono label's grey, is the muted grey too (owner 2026-10-01, text option A: the KPI, field and section labels - and the table headers are one grey; the former #6e6e76 was 4.40:1 on the row hover, #66666e is 4.95). */ - --ui-ink: 31 31 35; - --ui-ink-soft: 82 82 91; - --ui-muted: 102 102 110; - --ui-faint: 102 102 110; - --ui-label: 102 102 110; - /* Data text roles (owner 2026-10-01, text option A "two tones": artifacts/tmp/ui-polish/text/PREVIEW.md), read by - data.css 12 inside the cells, headers and toolbars of the record tables: a value is the ink, everything else one - grey, both existing tones (no grey in between). primary = names, figures, the billed cost, controls; labelled = - a value after its inline label; secondary = unlabelled metadata (times, IPs, ids, chips, status words); - reference = the account's billed cost, a distribution's account cost and standard price; label = headers, - inline labels, units, placeholders, counts. */ - --ui-data-primary: 31 31 35; - --ui-data-labelled: 31 31 35; - --ui-data-secondary: 102 102 110; - --ui-data-reference: 102 102 110; - --ui-data-label: 102 102 110; + /* Text: official sub2api text (Tailwind gray); meta and faint are #6b7280 / #9ca3af moved to the nearest value + holding 4.5:1 on every neutral fill, gray-200 included (#616977: 4.52:1 on the gray-200 fill, 5.53 on white). + ink = gray-900, ink-soft = gray-700 (data and control text); faint and the label are the meta grey. */ + --ui-ink: 17 24 39; + --ui-ink-soft: 55 65 81; + --ui-muted: 97 105 119; + --ui-faint: 97 105 119; + --ui-label: 97 105 119; /* Accent (the green #168a49 carries white at 4.41:1). The fill is #168848 (white 4.52:1, REF-CONSOLE §13); accent text and links are #147c42, the nearest at >= 4.5:1 on every neutral fill and on the soft fill. */ --ui-primary: 22 136 72; @@ -174,38 +111,24 @@ --ui-duration: 150ms; --ui-duration-slide: 200ms; --ui-duration-sidebar: 160ms; - /* type: Geist for Latin and figures, MiSans for Chinese (400 / 500 / 600 match Geist's stroke weights) */ - --ui-font: 'Geist', 'Geist Fallback', 'MiSans', 'PingFang SC', 'Microsoft YaHei UI', 'Microsoft YaHei', system-ui, - -apple-system, sans-serif; - --ui-font-mono: 'Geist Mono', 'Geist Mono Fallback', 'MiSans', ui-monospace, monospace; - /* The mono label (.mono-label: table headers, KPI labels, group rows, section titles, field labels): - Geist Mono 10/15, 0.12em, uppercase, --ui-label; table headers use weight 500. */ - --ui-label-size: 10px; - --ui-label-leading: 15px; - --ui-label-tracking: 0.12em; - --ui-label-weight: 400; - --ui-label-font: var(--ui-label-weight) var(--ui-label-size) / var(--ui-label-leading) var(--ui-font-mono); - /* Tailwind text-sm: the reference's 13px body text on its 19.5px line */ - --theme-text-sm: 0.8125rem; - --theme-text-sm-leading: 1.21875rem; - accent-color: rgb(var(--ui-primary)); - /* a weight MiSans is not loaded in is never smeared into a synthetic bold */ - font-synthesis-weight: none; -} - -/* Chinese has no capitals and 10px CJK is hard to read: labels in the Chinese interface are 11px - (ui-el MAPPING §2.8). i18n keeps on the active locale. The reference's 0.12em tracking is a Latin - capitals device; on full-width CJK glyphs it reads as spaced-out characters ("管 理 分 类"), so the Chinese interface - tracks its labels at 0.04em (picked by eye against 0.02-0.06; the English interface keeps 0.12em). */ -:root.flat-theme:lang(zh) { - --ui-label-size: 11px; + /* type: official sub2api. --ui-font is styles/theme.css' official stack (the OS UI font, no web fonts: Microsoft + YaHei UI for every script on Windows zh-CN, SF + PingFang on macOS). Mono only for identifiers and code: the + official stack with PingFang / YaHei before the generic, so Chinese in a mono run never falls to NSimSun. */ + --ui-font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'PingFang SC', 'Microsoft YaHei', monospace; + /* the label (KPI, field, menu, section and stacked-cell labels): official text-xs font-medium, sentence case. + Table headers and sidebar section titles add uppercase + 0.05em themselves (data.css, shell.css). */ + --ui-label-size: 12px; --ui-label-leading: 16px; - --ui-label-tracking: 0.04em; + --ui-label-tracking: normal; + --ui-label-weight: 500; + --ui-label-font: var(--ui-label-weight) var(--ui-label-size) / var(--ui-label-leading) var(--ui-font); + accent-color: rgb(var(--ui-primary)); } /* ------------------------------------------------------------------ roles (dark) */ /* Palette C: layered graphite lifted off black (Linear's ground): canvas #1a1a1d (L* 9.4, was #0f0f0f at 4.3), - surface #212125 (L* 12.9, was 9.3), grouped #29292e, hover #2f2f35, a faint cool tint; zinc text greys. */ + surface #212125 (L* 12.9, was 9.3), grouped #29292e, hover #2f2f35, a faint cool tint; the text greys are + official sub2api's (Tailwind gray). */ :root.flat-theme.dark { --ui-canvas: 26 26 29; --ui-surface: 33 33 37; @@ -219,21 +142,13 @@ --ui-line-strong: 74 74 82; --ui-line-focus: 110 110 120; --ui-control-line: 128 128 138; - --ui-ink: 232 232 236; - --ui-ink-soft: 184 184 192; - --ui-muted: 176 176 185; - /* faint and the label hold >= 4.5:1 on the canvas, the surface, the grouped fill and hover */ - --ui-faint: 160 160 169; - --ui-label: 163 163 172; - /* data text roles (see light): the ink and the label grey #a3a3ac, the table headers' grey (6.41:1 on the surface, - 5.32 on the row hover). On the selected fill (#39433f, 4.10:1) a row lifts its grey to the soft ink #b8b8c0 - (--ui-data-picked: 5.20:1, 4.72 under the pointer; data.css 12). */ - --ui-data-primary: 232 232 236; - --ui-data-labelled: 232 232 236; - --ui-data-secondary: 163 163 172; - --ui-data-reference: 163 163 172; - --ui-data-label: 163 163 172; - --ui-data-picked: 184 184 192; + /* official dark text (gray-100 / 300 / 400), meta clamped to 4.5:1 on the dark-600 fill (#a4abb6: 4.52:1 there, + 6.94 on the surface); faint and the label are the meta grey */ + --ui-ink: 243 244 246; + --ui-ink-soft: 209 213 219; + --ui-muted: 164 171 182; + --ui-faint: 164 171 182; + --ui-label: 164 171 182; /* the mint accent with ink on it; soft = mint at 16 % over the surface */ --ui-primary: 182 247 200; --ui-primary-hover: 166 225 183; @@ -292,16 +207,16 @@ --theme-line-gray-200: 232 232 235; --theme-line-gray-300: 220 220 224; --theme-line-gray-400: 185 185 192; - /* text: every shade upstream uses for secondary text meets 4.5:1 on the neutral fills; 300 stays the - reference's decorative muted-2 (upstream's own use: dividers, idle sort arrows); 900 / 950 are the ink */ + /* text: official Tailwind gray, 400 / 500 AA-clamped (#616977, >= 4.5:1 on gray-200 fills); 300 stays the + reference's decorative #a1a1aa (upstream's own use: dividers, idle sort arrows, the few unit hints) */ --theme-text-gray-300: 161 161 170; - --theme-text-gray-400: 102 102 110; - --theme-text-gray-500: 99 99 107; /* >= 4.5:1 on gray-200 fills (4.87) */ - --theme-text-gray-600: 91 91 99; - --theme-text-gray-700: 63 63 70; - --theme-text-gray-800: 39 39 42; - --theme-text-gray-900: 31 31 35; - --theme-text-gray-950: 31 31 35; + --theme-text-gray-400: 97 105 119; + --theme-text-gray-500: 97 105 119; + --theme-text-gray-600: 75 85 99; + --theme-text-gray-700: 55 65 81; + --theme-text-gray-800: 31 41 55; + --theme-text-gray-900: 17 24 39; + --theme-text-gray-950: 17 24 39; /* dark-*: 950 canvas, 900 well, 800 surface, 700 grouped fill; the light end is text on dark */ --theme-color-dark-50: 242 242 242; @@ -331,21 +246,23 @@ --theme-text-dark-600: 122 122 122; } -/* Dark mode: upstream pairs the light text shades (100-500) with dark:, and the plain 600-900 shades appear - where a template has no dark variant, so every text shade is a readable light grey (zinc: 400 is ink-soft, 500 - and 600 muted, >= 4.5:1 on the dark-600 fill); lines follow the graphite surfaces. Upstream's dark-* family - follows the surfaces too: 950 canvas, 900 well, 800 surface, 700 grouped fill. */ +/* Dark mode: upstream pairs the light text shades (100-500) with dark:, and the plain 600-950 shades appear where + a template has no dark variant, so every text shade is a readable official grey: 100-300 are gray-100 / 200 / 300, + 400 and 500 the meta grey (>= 4.5:1 on the dark-600 fill), 600-950 the readable fallbacks of official's own + pairing (600 <-> 400, 700 <-> 300, 800 <-> 200, 900 <-> 100). Lines follow the graphite surfaces. Upstream's dark-* + family follows the surfaces too (950 canvas, 900 well, 800 surface, 700 grouped fill); its text shades are the + cool-gray equivalents of upstream's slate, so no blue-tinted grey sits next to the neutral ones. */ :root.flat-theme.dark { - --theme-text-gray-100: 232 232 236; - --theme-text-gray-200: 221 221 226; - --theme-text-gray-300: 204 204 210; - --theme-text-gray-400: 184 184 192; - --theme-text-gray-500: 176 176 185; - --theme-text-gray-600: 176 176 185; - --theme-text-gray-700: 204 204 210; - --theme-text-gray-800: 221 221 226; - --theme-text-gray-900: 232 232 236; - --theme-text-gray-950: 232 232 236; + --theme-text-gray-100: 243 244 246; + --theme-text-gray-200: 229 231 235; + --theme-text-gray-300: 209 213 219; + --theme-text-gray-400: 164 171 182; + --theme-text-gray-500: 164 171 182; + --theme-text-gray-600: 164 171 182; + --theme-text-gray-700: 209 213 219; + --theme-text-gray-800: 229 231 235; + --theme-text-gray-900: 243 244 246; + --theme-text-gray-950: 243 244 246; --theme-line-gray-100: 47 47 53; --theme-line-gray-200: 58 58 65; --theme-line-gray-300: 74 74 82; @@ -362,12 +279,19 @@ --theme-line-dark-600: 74 74 82; --theme-line-dark-500: 104 104 114; --theme-line-dark-400: 128 128 138; - --theme-text-dark-100: 232 232 236; - --theme-text-dark-200: 214 214 219; - --theme-text-dark-300: 184 184 192; - --theme-text-dark-400: 163 163 172; - --theme-text-dark-500: 160 160 169; - --theme-text-dark-600: 128 128 138; + --theme-text-dark-100: 243 244 246; + --theme-text-dark-200: 229 231 235; + --theme-text-dark-300: 209 213 219; + --theme-text-dark-400: 164 171 182; + --theme-text-dark-500: 164 171 182; + /* dark:text-dark-600: upstream's separators and empty-state glyphs (decorative, as official) */ + --theme-text-dark-600: 75 85 99; +} + +/* upstream dims separators and empty-state glyphs in dark with dark:text-gray-600 (#4b5563, decorative, as official); + the shade itself stays a readable grey for templates that have no dark variant */ +:root.flat-theme.dark [class~="dark:text-gray-600"] { + --theme-text-gray-600: 75 85 99; } /* Secondary text in the light theme's boxes that stay dark (terminal output, dark tooltips, code panes), which the @@ -561,8 +485,9 @@ surface and on the row hover; the logo glyph inherits it, so >= 3:1), a soft fill and a 1px line, in the reference's chip. Upstream's shades: fill = the 50/100 midpoint (zinc 100), line = the 500 at 32 %, text = the 700 (orange a touch deeper for the hover row, amber and green the 800); dark: fill = the 500 at 14 % and line at 40 % over the - surface, text = the 300. Only the identity rules in data.css §6 read them; every other upstream hue keeps folding - into the status families above (decision 2: colour only for identity, status and the accent). */ + surface, text = the 300. The identity rules in data.css §6 read them (and the usage table's cache-read figure its + glyph's sky, dashboard-usage.css); every other upstream hue keeps folding into the status families above + (tailwind.config.js HUE_FAMILIES), so figures and category tags show official's colours through the fold. */ :root.flat-theme { --ui-id-orange: 190 64 13; --ui-id-orange-soft: 255 242 225; @@ -765,13 +690,13 @@ #9ca3af; status marks for charts drawn with upstream hue literals. The distribution charts list every value in a table beside the chart, so the series are not the only carrier of the data. */ :root.flat-theme { - --theme-chart-text: #52525b; + --theme-chart-text: #374151; --theme-chart-grid: #e8e8eb; - --theme-chart-axis: #66666e; + --theme-chart-axis: #616977; --theme-chart-axis-grid: #f4f4f5; --theme-chart-tooltip: #ffffff; - --theme-chart-title: #1f1f23; - --theme-chart-body: #66666e; + --theme-chart-title: #111827; + --theme-chart-body: #616977; --theme-chart-gray: #9ca3af; --theme-chart-series-1: #3975d8; --theme-chart-series-2: #168848; @@ -795,13 +720,13 @@ } :root.flat-theme.dark { - --theme-chart-text: #b8b8c0; + --theme-chart-text: #d1d5db; --theme-chart-grid: #3a3a41; - --theme-chart-axis: #b0b0b9; + --theme-chart-axis: #a4abb6; --theme-chart-axis-grid: #29292e; --theme-chart-tooltip: #252529; - --theme-chart-title: #e8e8ec; - --theme-chart-body: #b0b0b9; + --theme-chart-title: #f3f4f6; + --theme-chart-body: #a4abb6; --theme-chart-series-1: #6d9eeb; --theme-chart-series-2: #b6f7c8; --theme-chart-series-3: #d98a1c; diff --git a/frontend/src/utils/flatTheme.ts b/frontend/src/utils/flatTheme.ts index 83a2f39e8663..0bbbb4dad478 100644 --- a/frontend/src/utils/flatTheme.ts +++ b/frontend/src/utils/flatTheme.ts @@ -53,42 +53,13 @@ function watchUnlabelledFields(on: boolean): void { } } -// MiSans (Chinese text of the console theme) is not shipped with the app: it is loaded from Xiaomi's -// official font service (one stylesheet with the 400, 500 and 600 weights that match Geist's strokes, -// unicode-range slices, font-display: swap) while the console theme is on, and removed when it is -// switched off. The CSP allows both hosts (backend config DefaultCSPPolicy). If the service is -// unreachable, the font stack in styles/console/tokens.css falls back to PingFang SC / Microsoft YaHei -// and the other system fonts. -const MISANS_FONT_ORIGIN = 'https://cdn-file.hyperos.mi.com' -const MISANS_STYLESHEET = 'https://font.sec.miui.com/font/css?family=MiSans:400,500,600:Chinese_Simplify,Latin' - -function syncMiSansStylesheet(on: boolean): void { - if (typeof document === 'undefined' || !document.head) return - const existing = document.head.querySelectorAll('link[data-misans]') - if (!on) { - existing.forEach((link) => link.remove()) - return - } - if (existing.length) return - const preconnect = document.createElement('link') - preconnect.rel = 'preconnect' - preconnect.href = MISANS_FONT_ORIGIN - preconnect.crossOrigin = 'anonymous' - preconnect.dataset.misans = 'preconnect' - const stylesheet = document.createElement('link') - stylesheet.rel = 'stylesheet' - stylesheet.href = MISANS_STYLESHEET - stylesheet.dataset.misans = 'stylesheet' - document.head.append(preconnect, stylesheet) -} - // The console theme (styles/console/*.css) applies while carries the flat-theme class. // It follows the public flat_theme_enabled setting and stays on when the setting is absent. +// It loads no web fonts: text uses the OS UI font stack of styles/theme.css, as upstream does. export function applyFlatTheme(enabled: boolean | undefined): void { active.value = enabled !== false document.documentElement.classList.toggle('flat-theme', active.value) watchUnlabelledFields(active.value) - syncMiSansStylesheet(active.value) } /** Reactive: true while the console theme is on. */ diff --git a/frontend/src/views/admin/AccountsView.vue b/frontend/src/views/admin/AccountsView.vue index 044ef136135e..789c048981b8 100644 --- a/frontend/src/views/admin/AccountsView.vue +++ b/frontend/src/views/admin/AccountsView.vue @@ -2219,11 +2219,11 @@ const nameParts = (value: unknown): string[] => flatThemeActive.value ? nameWrapParts(value) : [value == null ? '' : String(value)] // Console theme: the name column follows the page's names (styles/console/tables.css, 名称; softWrap.ts -// nameColumnWidth). The page's account names (13px / 500), e-mails (12px / 400, accounts.css) and #ids (Geist Mono 12) -// are measured in the cells' fonts on a canvas, piece by piece, and the column takes the width at which they leave the -// least blank, an extra line counting as 40px of it: one long name or e-mail takes a third line at its word joins -// instead of widening the column for the whole page. Measured from the page's rows, not the rendered ones, so a -// virtualised list keeps one width while it scrolls; the web fonts arriving re-measure. +// nameColumnWidth). The page's account names (14px / 500), e-mails (12px / 400, accounts.css) and #ids (the mono +// stack, 12px) are measured in the cells' fonts on a canvas, piece by piece, and the column takes the width at which +// they leave the least blank, an extra line counting as 40px of it: one long name or e-mail takes a third line at its +// word joins instead of widening the column for the whole page. Measured from the page's rows, not the rendered ones, +// so a virtualised list keeps one width while it scrolls; re-measured when fonts load. const nameFontEpoch = ref(0) const nameRunsCache = new Map() let nameCanvas: CanvasRenderingContext2D | null | undefined @@ -2256,7 +2256,7 @@ const accountNameColumnStyle = computed | undefined>(() = const showId = !hiddenColumns.has('id') && !!mono const cells: NameCell[] = accounts.value.map((row) => { const texts: NameRuns[] = [] - if (row.name) texts.push(measureAccountName(ctx, `500 13px ${family}`, String(row.name))) + if (row.name) texts.push(measureAccountName(ctx, `500 14px ${family}`, String(row.name))) const email = accountDisplayEmail(row) if (email) texts.push(measureAccountName(ctx, `400 12px ${family}`, email)) let fixed = 0 From 5e8ac3b818e785dd3dee220e597b0d31c2f7bcb1 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 7 Oct 2026 15:33:47 +0800 Subject: [PATCH 2/6] chore: normalize audit checker to declared LF format --- tools/check_pnpm_audit_exceptions.py | 492 +++++++++++++-------------- 1 file changed, 246 insertions(+), 246 deletions(-) diff --git a/tools/check_pnpm_audit_exceptions.py b/tools/check_pnpm_audit_exceptions.py index a8d5453733d6..d7c8cf91b2b4 100644 --- a/tools/check_pnpm_audit_exceptions.py +++ b/tools/check_pnpm_audit_exceptions.py @@ -1,247 +1,247 @@ -#!/usr/bin/env python3 -import argparse -import json -import sys -from datetime import date - - -HIGH_SEVERITIES = {"high", "critical"} -REQUIRED_FIELDS = {"package", "advisory", "severity", "mitigation", "expires_on"} - - -def split_kv(line: str) -> tuple[str, str]: - # 解析 "key: value" 形式的简单 YAML 行,并去除引号。 - key, value = line.split(":", 1) - value = value.strip() - if (value.startswith('"') and value.endswith('"')) or ( - value.startswith("'") and value.endswith("'") - ): - value = value[1:-1] - return key.strip(), value - - -def parse_exceptions(path: str) -> list[dict]: - # 轻量解析异常清单,避免引入额外依赖。 - exceptions = [] - current = None - with open(path, "r", encoding="utf-8") as handle: - for raw in handle: - line = raw.strip() - if not line or line.startswith("#"): - continue - if line.startswith("version:") or line.startswith("exceptions:"): - continue - if line.startswith("- "): - if current: - exceptions.append(current) - current = {} - line = line[2:].strip() - if line: - key, value = split_kv(line) - current[key] = value - continue - if current is not None and ":" in line: - key, value = split_kv(line) - current[key] = value - if current: - exceptions.append(current) - return exceptions - - -def pick_advisory_id(advisory: dict) -> str | None: - # 优先使用可稳定匹配的标识(GHSA/URL/CVE),避免误匹配到其他同名漏洞。 - return ( - advisory.get("github_advisory_id") - or advisory.get("url") - or (advisory.get("cves") or [None])[0] - or (str(advisory.get("id")) if advisory.get("id") is not None else None) - or advisory.get("title") - or advisory.get("advisory") - or advisory.get("overview") - ) - - -def iter_vulns(data: dict): - # 兼容 pnpm audit 的不同输出结构(advisories / vulnerabilities),并提取 advisory 标识。 - advisories = data.get("advisories") - if isinstance(advisories, dict): - for advisory in advisories.values(): - name = advisory.get("module_name") or advisory.get("name") - severity = advisory.get("severity") - advisory_id = pick_advisory_id(advisory) - title = ( - advisory.get("title") - or advisory.get("advisory") - or advisory.get("overview") - or advisory.get("url") - ) - yield name, severity, advisory_id, title - - vulnerabilities = data.get("vulnerabilities") - if isinstance(vulnerabilities, dict): - for name, vuln in vulnerabilities.items(): - severity = vuln.get("severity") - via = vuln.get("via", []) - titles = [] - advisories = [] - if isinstance(via, list): - for item in via: - if isinstance(item, dict): - advisories.append( - item.get("github_advisory_id") - or item.get("url") - or item.get("source") - or item.get("title") - or item.get("name") - ) - titles.append( - item.get("title") - or item.get("url") - or item.get("advisory") - or item.get("source") - ) - elif isinstance(item, str): - advisories.append(item) - titles.append(item) - elif isinstance(via, str): - advisories.append(via) - titles.append(via) - title = "; ".join([t for t in titles if t]) - for advisory_id in [a for a in advisories if a]: - yield name, severity, advisory_id, title - - -def normalize_severity(severity: str) -> str: - # 统一大小写,避免比较失败。 - return (severity or "").strip().lower() - - -def normalize_package(name: str) -> str: - # 包名只去掉首尾空白,保留原始大小写,同时兼容非字符串输入。 - if name is None: - return "" - return str(name).strip() - - -def normalize_advisory(advisory: str) -> str: - # advisory 统一为小写匹配,避免 GHSA/URL 因大小写差异导致漏匹配。 - # pnpm 的 source 字段可能是数字,这里统一转为字符串以保证可比较。 - if advisory is None: - return "" - return str(advisory).strip().lower() - - -def parse_date(value: str) -> date | None: - # 仅接受 ISO8601 日期格式,非法值视为无效。 - try: - return date.fromisoformat(value) - except ValueError: - return None - - -def main() -> int: - parser = argparse.ArgumentParser() - parser.add_argument("--audit", required=True) - parser.add_argument("--exceptions", required=True) - args = parser.parse_args() - - with open(args.audit, "r", encoding="utf-8") as handle: - audit = json.load(handle) - - # 读取异常清单并建立索引,便于快速匹配包名 + advisory。 - exceptions = parse_exceptions(args.exceptions) - exception_index = {} - errors = [] - - for exc in exceptions: - missing = [field for field in REQUIRED_FIELDS if not exc.get(field)] - if missing: - errors.append( - f"Exception missing required fields {missing}: {exc.get('package', '')}" - ) - continue - exc_severity = normalize_severity(exc.get("severity")) - exc_package = normalize_package(exc.get("package")) - exc_advisory = normalize_advisory(exc.get("advisory")) - exc_date = parse_date(exc.get("expires_on")) - if exc_date is None: - errors.append( - f"Exception has invalid expires_on date: {exc.get('package', '')}" - ) - continue - if not exc_package or not exc_advisory: - errors.append("Exception missing package or advisory value") - continue - key = (exc_package, exc_advisory) - if key in exception_index: - errors.append( - f"Duplicate exception for {exc_package} advisory {exc.get('advisory')}" - ) - continue - exception_index[key] = { - "raw": exc, - "severity": exc_severity, - "expires_on": exc_date, - } - - today = date.today() - missing_exceptions = [] - expired_exceptions = [] - - # 去重处理:同一包名 + advisory 可能在不同字段重复出现。 - seen = set() - for name, severity, advisory_id, title in iter_vulns(audit): - sev = normalize_severity(severity) - if sev not in HIGH_SEVERITIES or not name: - continue - advisory_key = normalize_advisory(advisory_id) - if not advisory_key: - errors.append( - f"High/Critical vulnerability missing advisory id: {name} ({sev})" - ) - continue - key = (normalize_package(name), advisory_key) - if key in seen: - continue - seen.add(key) - exc = exception_index.get(key) - if exc is None: - missing_exceptions.append((name, sev, advisory_id, title)) - continue - if exc["severity"] and exc["severity"] != sev: - errors.append( - "Exception severity mismatch: " - f"{name} ({advisory_id}) expected {sev}, got {exc['severity']}" - ) - if exc["expires_on"] and exc["expires_on"] < today: - expired_exceptions.append( - (name, sev, advisory_id, exc["expires_on"].isoformat()) - ) - - if missing_exceptions: - errors.append("High/Critical vulnerabilities missing exceptions:") - for name, sev, advisory_id, title in missing_exceptions: - label = f"{name} ({sev})" - if advisory_id: - label = f"{label} [{advisory_id}]" - if title: - label = f"{label}: {title}" - errors.append(f"- {label}") - - if expired_exceptions: - errors.append("Exceptions expired:") - for name, sev, advisory_id, expires_on in expired_exceptions: - errors.append( - f"- {name} ({sev}) [{advisory_id}] expired on {expires_on}" - ) - - if errors: - sys.stderr.write("\n".join(errors) + "\n") - return 1 - - print("Audit exceptions validated.") - return 0 - - -if __name__ == "__main__": +#!/usr/bin/env python3 +import argparse +import json +import sys +from datetime import date + + +HIGH_SEVERITIES = {"high", "critical"} +REQUIRED_FIELDS = {"package", "advisory", "severity", "mitigation", "expires_on"} + + +def split_kv(line: str) -> tuple[str, str]: + # 解析 "key: value" 形式的简单 YAML 行,并去除引号。 + key, value = line.split(":", 1) + value = value.strip() + if (value.startswith('"') and value.endswith('"')) or ( + value.startswith("'") and value.endswith("'") + ): + value = value[1:-1] + return key.strip(), value + + +def parse_exceptions(path: str) -> list[dict]: + # 轻量解析异常清单,避免引入额外依赖。 + exceptions = [] + current = None + with open(path, "r", encoding="utf-8") as handle: + for raw in handle: + line = raw.strip() + if not line or line.startswith("#"): + continue + if line.startswith("version:") or line.startswith("exceptions:"): + continue + if line.startswith("- "): + if current: + exceptions.append(current) + current = {} + line = line[2:].strip() + if line: + key, value = split_kv(line) + current[key] = value + continue + if current is not None and ":" in line: + key, value = split_kv(line) + current[key] = value + if current: + exceptions.append(current) + return exceptions + + +def pick_advisory_id(advisory: dict) -> str | None: + # 优先使用可稳定匹配的标识(GHSA/URL/CVE),避免误匹配到其他同名漏洞。 + return ( + advisory.get("github_advisory_id") + or advisory.get("url") + or (advisory.get("cves") or [None])[0] + or (str(advisory.get("id")) if advisory.get("id") is not None else None) + or advisory.get("title") + or advisory.get("advisory") + or advisory.get("overview") + ) + + +def iter_vulns(data: dict): + # 兼容 pnpm audit 的不同输出结构(advisories / vulnerabilities),并提取 advisory 标识。 + advisories = data.get("advisories") + if isinstance(advisories, dict): + for advisory in advisories.values(): + name = advisory.get("module_name") or advisory.get("name") + severity = advisory.get("severity") + advisory_id = pick_advisory_id(advisory) + title = ( + advisory.get("title") + or advisory.get("advisory") + or advisory.get("overview") + or advisory.get("url") + ) + yield name, severity, advisory_id, title + + vulnerabilities = data.get("vulnerabilities") + if isinstance(vulnerabilities, dict): + for name, vuln in vulnerabilities.items(): + severity = vuln.get("severity") + via = vuln.get("via", []) + titles = [] + advisories = [] + if isinstance(via, list): + for item in via: + if isinstance(item, dict): + advisories.append( + item.get("github_advisory_id") + or item.get("url") + or item.get("source") + or item.get("title") + or item.get("name") + ) + titles.append( + item.get("title") + or item.get("url") + or item.get("advisory") + or item.get("source") + ) + elif isinstance(item, str): + advisories.append(item) + titles.append(item) + elif isinstance(via, str): + advisories.append(via) + titles.append(via) + title = "; ".join([t for t in titles if t]) + for advisory_id in [a for a in advisories if a]: + yield name, severity, advisory_id, title + + +def normalize_severity(severity: str) -> str: + # 统一大小写,避免比较失败。 + return (severity or "").strip().lower() + + +def normalize_package(name: str) -> str: + # 包名只去掉首尾空白,保留原始大小写,同时兼容非字符串输入。 + if name is None: + return "" + return str(name).strip() + + +def normalize_advisory(advisory: str) -> str: + # advisory 统一为小写匹配,避免 GHSA/URL 因大小写差异导致漏匹配。 + # pnpm 的 source 字段可能是数字,这里统一转为字符串以保证可比较。 + if advisory is None: + return "" + return str(advisory).strip().lower() + + +def parse_date(value: str) -> date | None: + # 仅接受 ISO8601 日期格式,非法值视为无效。 + try: + return date.fromisoformat(value) + except ValueError: + return None + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--audit", required=True) + parser.add_argument("--exceptions", required=True) + args = parser.parse_args() + + with open(args.audit, "r", encoding="utf-8") as handle: + audit = json.load(handle) + + # 读取异常清单并建立索引,便于快速匹配包名 + advisory。 + exceptions = parse_exceptions(args.exceptions) + exception_index = {} + errors = [] + + for exc in exceptions: + missing = [field for field in REQUIRED_FIELDS if not exc.get(field)] + if missing: + errors.append( + f"Exception missing required fields {missing}: {exc.get('package', '')}" + ) + continue + exc_severity = normalize_severity(exc.get("severity")) + exc_package = normalize_package(exc.get("package")) + exc_advisory = normalize_advisory(exc.get("advisory")) + exc_date = parse_date(exc.get("expires_on")) + if exc_date is None: + errors.append( + f"Exception has invalid expires_on date: {exc.get('package', '')}" + ) + continue + if not exc_package or not exc_advisory: + errors.append("Exception missing package or advisory value") + continue + key = (exc_package, exc_advisory) + if key in exception_index: + errors.append( + f"Duplicate exception for {exc_package} advisory {exc.get('advisory')}" + ) + continue + exception_index[key] = { + "raw": exc, + "severity": exc_severity, + "expires_on": exc_date, + } + + today = date.today() + missing_exceptions = [] + expired_exceptions = [] + + # 去重处理:同一包名 + advisory 可能在不同字段重复出现。 + seen = set() + for name, severity, advisory_id, title in iter_vulns(audit): + sev = normalize_severity(severity) + if sev not in HIGH_SEVERITIES or not name: + continue + advisory_key = normalize_advisory(advisory_id) + if not advisory_key: + errors.append( + f"High/Critical vulnerability missing advisory id: {name} ({sev})" + ) + continue + key = (normalize_package(name), advisory_key) + if key in seen: + continue + seen.add(key) + exc = exception_index.get(key) + if exc is None: + missing_exceptions.append((name, sev, advisory_id, title)) + continue + if exc["severity"] and exc["severity"] != sev: + errors.append( + "Exception severity mismatch: " + f"{name} ({advisory_id}) expected {sev}, got {exc['severity']}" + ) + if exc["expires_on"] and exc["expires_on"] < today: + expired_exceptions.append( + (name, sev, advisory_id, exc["expires_on"].isoformat()) + ) + + if missing_exceptions: + errors.append("High/Critical vulnerabilities missing exceptions:") + for name, sev, advisory_id, title in missing_exceptions: + label = f"{name} ({sev})" + if advisory_id: + label = f"{label} [{advisory_id}]" + if title: + label = f"{label}: {title}" + errors.append(f"- {label}") + + if expired_exceptions: + errors.append("Exceptions expired:") + for name, sev, advisory_id, expires_on in expired_exceptions: + errors.append( + f"- {name} ({sev}) [{advisory_id}] expired on {expires_on}" + ) + + if errors: + sys.stderr.write("\n".join(errors) + "\n") + return 1 + + print("Audit exceptions validated.") + return 0 + + +if __name__ == "__main__": raise SystemExit(main()) \ No newline at end of file From 42b8d63a1a383a157934fb9f6f8cbd43daa3e18f Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 7 Oct 2026 15:39:05 +0800 Subject: [PATCH 3/6] fix(ui): keep priority controls on current text tokens --- frontend/src/styles/console/pages/accounts.css | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/src/styles/console/pages/accounts.css b/frontend/src/styles/console/pages/accounts.css index d7ce8fc02f29..2f2b7cc33d8a 100644 --- a/frontend/src/styles/console/pages/accounts.css +++ b/frontend/src/styles/console/pages/accounts.css @@ -659,8 +659,8 @@ html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="actions"] } } -/* The fork's own sentence lines in the 用量 cell (the codex turn tickets, the stale / fetch-failed notes; upstream has - no such lines) read at 12 / 16 like the cell's other text; upstream's micro widgets (the 9-10px bars and chips) +/* The fork's own sentence lines in the 用量 cell (the stale / fetch-failed notes; upstream has no such lines) + read at 12 / 16 like the cell's other text; upstream's micro widgets (the 9-10px bars and chips) keep their template sizes. */ html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="usage"] [data-usage-variant="list"] [class~="max-w-[15rem]"] > div[class~="text-[10px]"][class~="leading-4"], html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="usage"] :is([data-test="usage-stale"], [data-test="usage-fetch-failed"]) { @@ -670,9 +670,9 @@ html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="usage"] :i /* 优先级: upstream's quick-adjust control (AccountPriorityCell: − value +; the two steppers show under the pointer or on focus, a click on the value turns it into an input, a change saves by itself). It is the value of the 优先级 line - and sits under its label, as the column cannot hold both. The figure keeps the primary tone (above). The steppers - are icon buttons, so they take the label tone whatever grey class they carry and the ink under the pointer (the row - actions' rule, data.css 12.3); one at the end of the range is the theme's disabled .55 (on the glyph: the button's + and sits under its label, as the column cannot hold both. The figure keeps its template's semantic text class. + The steppers are icon buttons, so they take the muted tone and the ink under the pointer; one at the end of the + range is the theme's disabled .55 (on the glyph: the button's own opacity is what shows and hides it). The control's 28px box gives 3px back above and below, so the line keeps the pitch of the lines around it. Reached through the component's own static data-testid attributes. */ html.flat-theme .table-wrapper[data-table="accounts"] [data-testid="account-priority-cell"] { @@ -680,11 +680,11 @@ html.flat-theme .table-wrapper[data-table="accounts"] [data-testid="account-prio } html.flat-theme .table-wrapper[data-table="accounts"] [data-testid="account-priority-cell"] > button:is([data-testid="account-priority-decrement"], [data-testid="account-priority-increment"]) { - color: rgb(var(--ui-data-label)); + color: rgb(var(--ui-muted)); } html.flat-theme .table-wrapper[data-table="accounts"] [data-testid="account-priority-cell"] > button:is([data-testid="account-priority-decrement"], [data-testid="account-priority-increment"]):hover:not(:disabled) { - color: rgb(var(--ui-data-primary)); + color: rgb(var(--ui-ink)); } html.flat-theme .table-wrapper[data-table="accounts"] [data-testid="account-priority-cell"] > button:is([data-testid="account-priority-decrement"], [data-testid="account-priority-increment"]):disabled > svg { From 8a6b25cd2cc9e248431a836c65246d6d15882d2c Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 7 Oct 2026 15:40:34 +0800 Subject: [PATCH 4/6] fix(ui): preserve readable empty table values in dark mode --- frontend/src/styles/console/tokens.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/styles/console/tokens.css b/frontend/src/styles/console/tokens.css index 6604dd02d15d..eb9c22626577 100644 --- a/frontend/src/styles/console/tokens.css +++ b/frontend/src/styles/console/tokens.css @@ -288,9 +288,9 @@ --theme-text-dark-600: 75 85 99; } -/* upstream dims separators and empty-state glyphs in dark with dark:text-gray-600 (#4b5563, decorative, as official); - the shade itself stays a readable grey for templates that have no dark variant */ -:root.flat-theme.dark [class~="dark:text-gray-600"] { +/* Upstream dims separators and decorative glyphs with dark:text-gray-600 (#4b5563). Limit that shade to those + elements: the same utility on a table's empty-value text must keep the readable meta grey. */ +:root.flat-theme.dark [class~="dark:text-gray-600"]:is(svg, span[class~="text-gray-300"], div[class~="flex"][class~="items-center"][class~="justify-center"]) { --theme-text-gray-600: 75 85 99; } From 30b9807ec4a55e865fd68ad9194470ec8166f353 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 7 Oct 2026 16:01:10 +0800 Subject: [PATCH 5/6] fix(ui): wrap capacity badges within their account column --- frontend/src/styles/console/tables.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/frontend/src/styles/console/tables.css b/frontend/src/styles/console/tables.css index 977aaaddb48b..26c69964e58f 100644 --- a/frontend/src/styles/console/tables.css +++ b/frontend/src/styles/console/tables.css @@ -317,6 +317,13 @@ html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="platform_t white-space: normal; } +/* Capacity stays in the platform cell; wrap between its whole badges so their combined width does not set the + column's minimum after switching to the system font. Figures and each badge keep their own nowrap rule. */ +html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="platform_type"] [data-ui="cell-line"][data-line="capacity"] > [data-ui="cell-line-value"] > div { + flex-wrap: wrap; + row-gap: 4px; +} + /* ---- 状态 / 调度: the badge, its countdown (「自动恢复」 whole), the 429 / model / error marks, then the switch */ html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="status"] > div:not([data-ui="cell-lines"]), html.flat-theme .table-wrapper[data-table="accounts"] td[data-column="status"] > div:not([data-ui="cell-lines"]) > div[class~="flex"][class~="items-center"] { From fae707220aa3d43cdd7d5a6ce91610e86f33e04a Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 7 Oct 2026 16:03:49 +0800 Subject: [PATCH 6/6] fix(ui): let the English rate label wrap at word boundaries --- frontend/src/styles/console/tables.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/frontend/src/styles/console/tables.css b/frontend/src/styles/console/tables.css index 26c69964e58f..c9aa58efc5e5 100644 --- a/frontend/src/styles/console/tables.css +++ b/frontend/src/styles/console/tables.css @@ -583,6 +583,13 @@ html.flat-theme:lang(en) .table-wrapper[data-table="accounts"] td[data-column="t margin-right: 0; } +/* The longer English rate label may wrap at spaces when the column narrows; the rate and its controls stay whole. */ +html.flat-theme:lang(en) .table-wrapper[data-table="accounts"] td[data-column="priority"] [data-ui="cell-line"][data-line="upstream_billing_rate"] > [data-ui="cell-line-label"] { + flex: 0 1 auto; + max-width: 100%; + white-space: normal; +} + @media (max-width: 1439.98px) { html.flat-theme:lang(en) .table-wrapper[data-table="accounts"] th[data-column="taxonomy_route"] { min-width: 7rem;