anentrypoint-design 0.0.478 → 0.0.481
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/chat.css +4 -4
- package/colors_and_type.css +33 -3
- package/community.css +22 -8
- package/dist/247420.css +195 -59
- package/dist/247420.js +24 -24
- package/package.json +1 -1
- package/src/community-app.js +4 -2
- package/src/components/community/navigation.js +2 -2
- package/src/components/community/presence.js +10 -4
- package/src/components/community/views.js +8 -2
- package/src/components/content/avatar.js +30 -0
- package/src/components/content.js +2 -2
- package/src/components/overlay-primitives/emoji-picker.js +15 -3
- package/src/components/shell/workspace-shell.js +1 -1
- package/src/components/theme-toggle.js +8 -3
- package/src/css/app-shell/base.css +18 -0
- package/src/css/app-shell/catalog-theme.css +14 -2
- package/src/css/app-shell/chat-polish.css +12 -0
- package/src/css/app-shell/collab.css +5 -0
- package/src/css/app-shell/data-density.css +4 -0
- package/src/css/app-shell/files.css +1 -1
- package/src/css/app-shell/hero-content.css +1 -1
- package/src/css/app-shell/loading-alerts.css +14 -12
- package/src/css/app-shell/otp-input.css +2 -2
- package/src/css/app-shell/plugins-config.css +3 -0
- package/src/css/app-shell/primitives.css +13 -4
- package/src/css/app-shell/responsive.css +13 -0
- package/src/css/app-shell/responsive2-workspace.css +4 -1
- package/src/css/app-shell/states-interactions.css +20 -4
- package/src/kits/os/app-panes.css +3 -2
- package/src/kits/os/icons.js +16 -0
- package/src/kits/os/shell-geometry.js +2 -2
- package/src/kits/os/shell.js +24 -3
- package/src/kits/os/theme.css +251 -78
- package/src/kits/os/wm.css +19 -6
- package/src/kits/spoint/host-join-lobby.css +3 -3
- package/src/web-components/freddie-chat.js +2 -2
- package/types/components.d.ts +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anentrypoint-design",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.481",
|
|
4
4
|
"description": "247420 design system SDK — webjsx + modified ripple-ui, single-file ESM bundle for reproducible use of the AnEntrypoint design.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/247420.js",
|
package/src/community-app.js
CHANGED
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
// audioQueueItems, audioQueueCurrentId, audioQueuePaused,
|
|
16
16
|
// showAuthModal, settingsOpen, voiceSettingsOpen, replyTarget,
|
|
17
17
|
// mobileMenuOpen, // drives the .ca-rail off-canvas drawer on narrow shells
|
|
18
|
-
// canManage
|
|
18
|
+
// canManage, // gates the rail's "+ create channel" affordance
|
|
19
|
+
// forumPosts, // ch.type==='forum': [{id,title,snippet,author,time,replyCount,tags?}]
|
|
20
|
+
// pageHtml, pageAuthor, pageUpdatedAt // ch.type==='page': PageView content + attribution
|
|
19
21
|
// }
|
|
20
22
|
// adapter.subscribe(cb) -> unsubscribe // cb fires when any snapshot field changes
|
|
21
23
|
// adapter.actions = {
|
|
@@ -223,7 +225,7 @@ export function mountCommunityApp(root, adapter = {}) {
|
|
|
223
225
|
const inVoiceChannel = ch.type === 'voice';
|
|
224
226
|
const bodyMain = inVoiceChannel ? voiceView(s)
|
|
225
227
|
: ch.type === 'forum' ? ForumView({ posts: s.forumPosts || [], onSelect: (id) => A.openThread && A.openThread(id), onNewPost: () => A.newForumPost && A.newForumPost() })
|
|
226
|
-
: ch.type === 'page' ? PageView({ title: ch.name, html: s.pageHtml || '', isAdmin: !!s.canManage, onEdit: () => A.editPage && A.editPage() })
|
|
228
|
+
: ch.type === 'page' ? PageView({ title: ch.name, html: s.pageHtml || '', author: s.pageAuthor || '', updatedAt: s.pageUpdatedAt || 0, isAdmin: !!s.canManage, onEdit: () => A.editPage && A.editPage() })
|
|
227
229
|
: chatView(s);
|
|
228
230
|
const showVoiceBanner = s.voiceConnected && s.voiceChannelName && !(inVoiceChannel && s.voiceChannelName === ch.name);
|
|
229
231
|
return h('div', { class: 'ca-app' },
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import * as webjsx from '../../../vendor/webjsx/index.js';
|
|
6
6
|
import { Icon } from '../shell.js';
|
|
7
|
-
import { Avatar, avatarInitial } from '../content.js';
|
|
7
|
+
import { Avatar, avatarInitial, avatarContrastFg } from '../content.js';
|
|
8
8
|
import { UserPanel } from './presence.js';
|
|
9
9
|
const h = webjsx.createElement;
|
|
10
10
|
|
|
@@ -97,7 +97,7 @@ export function ChannelItem({ id, name, type = 'text', active, voiceActive, voic
|
|
|
97
97
|
),
|
|
98
98
|
voiceActive && participants.length ? h('div', { class: 'cm-ch-voice-users' },
|
|
99
99
|
...participants.map(p => h('div', { class: 'cm-ch-voice-user' + (p.speaking ? ' speaking' : '') },
|
|
100
|
-
h('div', { class: 'cm-ch-voice-user-avatar', style: p.color ? `--avatar-bg:${p.color}` : null }, avatarInitial(p.identity)),
|
|
100
|
+
h('div', { class: 'cm-ch-voice-user-avatar', style: p.color ? (avatarContrastFg(p.color) ? `--avatar-bg:${p.color};--avatar-fg:${avatarContrastFg(p.color)}` : `--avatar-bg:${p.color}`) : null }, avatarInitial(p.identity)),
|
|
101
101
|
h('span', { class: 'cm-ch-voice-user-name' }, p.identity)
|
|
102
102
|
))
|
|
103
103
|
) : null
|
|
@@ -4,13 +4,19 @@
|
|
|
4
4
|
|
|
5
5
|
import * as webjsx from '../../../vendor/webjsx/index.js';
|
|
6
6
|
import { Icon } from '../shell.js';
|
|
7
|
-
import { avatarInitial } from '../content.js';
|
|
7
|
+
import { avatarInitial, avatarContrastFg } from '../content.js';
|
|
8
8
|
const h = webjsx.createElement;
|
|
9
9
|
|
|
10
|
+
function avatarStyle(color) {
|
|
11
|
+
if (!color) return null;
|
|
12
|
+
const fg = avatarContrastFg(color);
|
|
13
|
+
return fg ? `--avatar-bg:${color};--avatar-fg:${fg}` : `--avatar-bg:${color}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
10
16
|
export function VoiceUser({ identity, speaking, color } = {}) {
|
|
11
17
|
const initial = avatarInitial(identity);
|
|
12
18
|
return h('div', { class: 'cm-voice-user' + (speaking ? ' speaking' : '') },
|
|
13
|
-
h('div', { class: 'cm-voice-user-avatar', style: color
|
|
19
|
+
h('div', { class: 'cm-voice-user-avatar', style: avatarStyle(color) }, initial),
|
|
14
20
|
h('span', { class: 'cm-voice-user-name' }, identity)
|
|
15
21
|
);
|
|
16
22
|
}
|
|
@@ -29,7 +35,7 @@ export function UserPanel({ name, tag, color, muted, deafened, onMute, onDeafen,
|
|
|
29
35
|
}
|
|
30
36
|
};
|
|
31
37
|
return h('div', { class: 'cm-user-panel' },
|
|
32
|
-
h('div', { class: 'cm-user-avatar', style: color
|
|
38
|
+
h('div', { class: 'cm-user-avatar', style: avatarStyle(color) },
|
|
33
39
|
h('span', { class: 'cm-user-status-dot' }),
|
|
34
40
|
initial
|
|
35
41
|
),
|
|
@@ -48,7 +54,7 @@ export function UserPanel({ name, tag, color, muted, deafened, onMute, onDeafen,
|
|
|
48
54
|
export function MemberItem({ identity, name, color, status = 'online' } = {}) {
|
|
49
55
|
const initial = avatarInitial(name || identity);
|
|
50
56
|
return h('div', { class: 'cm-member-item' },
|
|
51
|
-
h('div', { class: 'cm-member-avatar', style: color
|
|
57
|
+
h('div', { class: 'cm-member-avatar', style: avatarStyle(color) },
|
|
52
58
|
h('span', { class: 'cm-member-status' + (status === 'online' ? ' online' : '') }),
|
|
53
59
|
initial
|
|
54
60
|
),
|
|
@@ -111,10 +111,16 @@ export function ForumView({ posts = [], onSearch, onSort, onSelect, onNewPost, l
|
|
|
111
111
|
);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
export function PageView({ title = '', html = '', isAdmin = false, onEdit } = {}) {
|
|
114
|
+
export function PageView({ title = '', html = '', author = '', updatedAt = 0, isAdmin = false, onEdit } = {}) {
|
|
115
115
|
return h('div', { class: 'cm-page', role: 'document' },
|
|
116
116
|
h('div', { class: 'cm-page-head' },
|
|
117
|
-
h('
|
|
117
|
+
h('div', { class: 'cm-page-head-title' },
|
|
118
|
+
h('h1', { class: 'cm-page-title' }, title || ''),
|
|
119
|
+
(author || updatedAt) ? h('div', { class: 'cm-page-meta' },
|
|
120
|
+
author ? h('span', { class: 'cm-page-author' }, author) : null,
|
|
121
|
+
updatedAt ? h('span', { class: 'cm-page-time' }, fmtRelTime(updatedAt)) : null
|
|
122
|
+
) : null
|
|
123
|
+
),
|
|
118
124
|
isAdmin && onEdit ? h('button', { type: 'button', class: 'cm-page-edit', onclick: onEdit }, 'Edit') : null
|
|
119
125
|
),
|
|
120
126
|
h('div', {
|
|
@@ -16,6 +16,36 @@ export function avatarInitial(name, count = 1) {
|
|
|
16
16
|
return name ? String(name).trim().slice(0, count).toUpperCase() || '?' : '?';
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// avatarContrastFg — picks black or white text against an arbitrary
|
|
20
|
+
// per-user hashed --avatar-bg color, via WCAG relative luminance, so
|
|
21
|
+
// initials clear 4.5:1 regardless of which hue the hash lands on (a
|
|
22
|
+
// single fixed --fg-2 token can't: some hashed hues are light enough
|
|
23
|
+
// that dark-on-dark or light-on-light both existed in the wild).
|
|
24
|
+
// Returns null for an unparsable color so callers can omit the
|
|
25
|
+
// inline style entirely and fall back to the CSS default.
|
|
26
|
+
export function avatarContrastFg(color) {
|
|
27
|
+
if (!color) return null;
|
|
28
|
+
let r, g, b;
|
|
29
|
+
const hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(String(color).trim());
|
|
30
|
+
if (hex) {
|
|
31
|
+
let h = hex[1];
|
|
32
|
+
if (h.length === 3) h = h.split('').map(c => c + c).join('');
|
|
33
|
+
r = parseInt(h.slice(0, 2), 16); g = parseInt(h.slice(2, 4), 16); b = parseInt(h.slice(4, 6), 16);
|
|
34
|
+
} else {
|
|
35
|
+
const rgb = /^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i.exec(String(color).trim());
|
|
36
|
+
if (!rgb) return null;
|
|
37
|
+
r = +rgb[1]; g = +rgb[2]; b = +rgb[3];
|
|
38
|
+
}
|
|
39
|
+
const lin = (c) => { const v = c / 255; return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); };
|
|
40
|
+
const L = 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
|
|
41
|
+
// Contrast against pure black/white; pick whichever side clears more
|
|
42
|
+
// headroom (both landing >=4.5:1 is common, but some hues only clear
|
|
43
|
+
// one side, so max-headroom is the right tiebreak, not a 0.5 cutoff).
|
|
44
|
+
const contrastWhite = 1.05 / (L + 0.05);
|
|
45
|
+
const contrastBlack = (L + 0.05) / 0.05;
|
|
46
|
+
return contrastWhite >= contrastBlack ? '#fff' : '#000';
|
|
47
|
+
}
|
|
48
|
+
|
|
19
49
|
// Avatar — the single letter-fallback/image avatar primitive. `initialsCount`
|
|
20
50
|
// (default 1) controls how many leading characters of `name` become the
|
|
21
51
|
// fallback letters when no `src`/`fallback` is given (community.js's
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// submodule under ./content/, and the public export surface here is unchanged
|
|
7
7
|
// — no consumer import needs to move.
|
|
8
8
|
|
|
9
|
-
import { avatarInitial, Avatar } from './content/avatar.js';
|
|
9
|
+
import { avatarInitial, avatarContrastFg, Avatar } from './content/avatar.js';
|
|
10
10
|
import { Row, RowLink } from './content/row.js';
|
|
11
11
|
import { Panel, Card, PanelFromItems, Section, Receipt, Changelog } from './content/panel.js';
|
|
12
12
|
import { Hero, HeroFromPageData, Marquee, Manifesto, PageHeader } from './content/hero.js';
|
|
@@ -20,7 +20,7 @@ import { Spinner, Skeleton, Alert, FilterPills } from './content/feedback.js';
|
|
|
20
20
|
import { HomeView, ProjectView } from './content/views.js';
|
|
21
21
|
|
|
22
22
|
export {
|
|
23
|
-
avatarInitial, Avatar,
|
|
23
|
+
avatarInitial, avatarContrastFg, Avatar,
|
|
24
24
|
Row, RowLink,
|
|
25
25
|
Panel, Card, PanelFromItems, Section, Receipt, Changelog,
|
|
26
26
|
Hero, HeroFromPageData, Marquee, Manifesto, PageHeader,
|
|
@@ -44,12 +44,18 @@ const ALL_EMOJI = EMOJI_CATEGORIES.flatMap((c) => c.emoji);
|
|
|
44
44
|
export function EmojiPicker({ open, anchorX = 0, anchorY = 0, onSelect, onClose, query = '' } = {}) {
|
|
45
45
|
if (!open) return null;
|
|
46
46
|
let cat = EMOJI_CATEGORIES[0].id;
|
|
47
|
-
let rootEl = null, gridEl = null;
|
|
47
|
+
let rootEl = null, gridEl = null, searchEl = null;
|
|
48
|
+
// Internal search state, seeded from the `query` prop so a consumer that
|
|
49
|
+
// already knows the typed ':smile' trigger text (e.g. a composer keydown
|
|
50
|
+
// handler) can still pre-fill it — but typing in the picker's own input
|
|
51
|
+
// (added here since no consumer previously had anywhere to route text
|
|
52
|
+
// into `query`) is the primary path now.
|
|
53
|
+
let search = query || '';
|
|
48
54
|
const close = () => onClose && onClose();
|
|
49
55
|
|
|
50
56
|
const renderGrid = () => {
|
|
51
57
|
if (!gridEl) return;
|
|
52
|
-
const q = (
|
|
58
|
+
const q = (search || '').trim().toLowerCase();
|
|
53
59
|
const cells = q
|
|
54
60
|
? ALL_EMOJI.filter(([, name]) => name.toLowerCase().includes(q))
|
|
55
61
|
: (EMOJI_CATEGORIES.find(x => x.id === cat) || EMOJI_CATEGORIES[0]).emoji;
|
|
@@ -82,7 +88,13 @@ export function EmojiPicker({ open, anchorX = 0, anchorY = 0, onSelect, onClose,
|
|
|
82
88
|
el._ovEmojiCleanup = _anchoredOverlayLifecycle(el, { anchorX, anchorY, fallbackW: 260, fallbackH: 240, close });
|
|
83
89
|
},
|
|
84
90
|
},
|
|
85
|
-
|
|
91
|
+
h('input', {
|
|
92
|
+
type: 'search', class: 'ov-emoji-search', placeholder: 'Search emoji…',
|
|
93
|
+
'aria-label': 'search emoji', value: search,
|
|
94
|
+
ref: (el) => { searchEl = el; },
|
|
95
|
+
oninput: (e) => { search = e.target.value; renderGrid(); },
|
|
96
|
+
}),
|
|
97
|
+
(search || '').trim() ? null : h('div', { class: 'ov-emoji-tabs', role: 'tablist' },
|
|
86
98
|
...EMOJI_CATEGORIES.map((c) => h('button', {
|
|
87
99
|
type: 'button', class: 'ov-emoji-tab', role: 'tab',
|
|
88
100
|
'aria-selected': c.id === cat ? 'true' : 'false',
|
|
@@ -173,7 +173,7 @@ export function WorkspaceRail({ brand = '247420', action, items = [], footer } =
|
|
|
173
173
|
it.icon ? Icon(it.icon) : h('span', { class: 'ws-rail-item-glyph', 'aria-hidden': 'true' }),
|
|
174
174
|
h('span', { class: 'ws-rail-item-label' }, it.label),
|
|
175
175
|
(it.count != null && it.count !== 0 && it.count !== '0')
|
|
176
|
-
? h('span', { class: 'ws-rail-item-count', 'aria-hidden': 'true' }, String(it.count))
|
|
176
|
+
? h('span', { class: 'ws-rail-item-count ds-badge ds-badge--sm', 'aria-hidden': 'true' }, String(it.count))
|
|
177
177
|
: null,
|
|
178
178
|
it.rail ? h('span', { class: 'ws-rail-item-flag tone-' + it.rail, 'aria-hidden': 'true' }) : null)))),
|
|
179
179
|
footer ? h('div', { class: 'ws-rail-foot' }, footer) : null,
|
|
@@ -67,11 +67,16 @@ export function ThemeToggle({ compact = false, onChange } = {}) {
|
|
|
67
67
|
el.setAttribute('title', titleFor(now));
|
|
68
68
|
const lab = el.querySelector('.ds-theme-toggle-label');
|
|
69
69
|
if (lab) lab.textContent = labelFor(now);
|
|
70
|
+
const disc = el.querySelector('.ds-theme-disc');
|
|
71
|
+
if (disc) disc.setAttribute('data-mode', now);
|
|
70
72
|
})
|
|
71
73
|
},
|
|
72
|
-
// CSS-drawn
|
|
73
|
-
//
|
|
74
|
-
|
|
74
|
+
// CSS-drawn disc so the control still reads as the theme switch when
|
|
75
|
+
// the label is hidden (icon-only rail strip). data-mode selects a
|
|
76
|
+
// per-mode fill (solid light / solid dark / half-and-half for auto)
|
|
77
|
+
// so the icon itself — not just the title tooltip — shows current
|
|
78
|
+
// state at a glance.
|
|
79
|
+
h('span', { class: 'ds-theme-disc', 'data-mode': current, 'aria-hidden': 'true' }),
|
|
75
80
|
h('span', { class: 'ds-theme-toggle-label' }, labelFor(current)));
|
|
76
81
|
}
|
|
77
82
|
|
|
@@ -75,6 +75,24 @@ body {
|
|
|
75
75
|
scrollbar-width: thin;
|
|
76
76
|
scrollbar-color: var(--bg-3) transparent;
|
|
77
77
|
}
|
|
78
|
+
/* Firefox gets themed scrollbars via scrollbar-width/-color above; Chrome,
|
|
79
|
+
Safari, and Edge need the ::-webkit-scrollbar family too, or every
|
|
80
|
+
scrollable node in the app shell falls back to the raw OS scrollbar and
|
|
81
|
+
clashes with the dark UI. Same selector scope, same --bg-3 token. */
|
|
82
|
+
.app::-webkit-scrollbar, .ds-stage::-webkit-scrollbar,
|
|
83
|
+
.app *::-webkit-scrollbar, .ds-stage *::-webkit-scrollbar {
|
|
84
|
+
width: 8px;
|
|
85
|
+
height: 8px;
|
|
86
|
+
}
|
|
87
|
+
.app::-webkit-scrollbar-track, .ds-stage::-webkit-scrollbar-track,
|
|
88
|
+
.app *::-webkit-scrollbar-track, .ds-stage *::-webkit-scrollbar-track {
|
|
89
|
+
background: transparent;
|
|
90
|
+
}
|
|
91
|
+
.app::-webkit-scrollbar-thumb, .ds-stage::-webkit-scrollbar-thumb,
|
|
92
|
+
.app *::-webkit-scrollbar-thumb, .ds-stage *::-webkit-scrollbar-thumb {
|
|
93
|
+
background: var(--bg-3);
|
|
94
|
+
border-radius: var(--r-pill, 999px);
|
|
95
|
+
}
|
|
78
96
|
|
|
79
97
|
h1, .t-h1 { font-family: var(--ff-display); font-size: var(--fs-h1); line-height: var(--lh-snug); letter-spacing: var(--tr-tight); font-weight: 600; margin: 0; }
|
|
80
98
|
h2, .t-h2 { font-family: var(--ff-display); font-size: var(--fs-h2); line-height: var(--lh-snug); letter-spacing: var(--tr-tight); font-weight: 600; margin: 0; }
|
|
@@ -143,11 +143,23 @@
|
|
|
143
143
|
font-family: var(--ff-mono); font-size: var(--fs-xs);
|
|
144
144
|
display: inline-flex; align-items: center; gap: 6px;
|
|
145
145
|
}
|
|
146
|
-
/* CSS-drawn
|
|
147
|
-
legible when its text label is hidden (icon-only rail strip).
|
|
146
|
+
/* CSS-drawn disc mark on the compact theme toggle: keeps the control
|
|
147
|
+
legible when its text label is hidden (icon-only rail strip). Per-mode
|
|
148
|
+
fill gives the icon itself a distinct look for auto/light/dark, so state
|
|
149
|
+
reads without relying on the title tooltip alone. */
|
|
148
150
|
.ds-theme-disc {
|
|
149
151
|
flex: 0 0 auto; width: 14px; height: 14px; border-radius: 50%;
|
|
150
152
|
border: var(--bw-hair) solid var(--fg-3);
|
|
153
|
+
/* auto: half-and-half, matches default (no data-mode) for safety */
|
|
151
154
|
background: linear-gradient(90deg, var(--fg-3) 0 50%, transparent 50% 100%);
|
|
152
155
|
}
|
|
156
|
+
.ds-theme-disc[data-mode="auto"] {
|
|
157
|
+
background: linear-gradient(90deg, var(--fg-3) 0 50%, transparent 50% 100%);
|
|
158
|
+
}
|
|
159
|
+
.ds-theme-disc[data-mode="paper"] {
|
|
160
|
+
background: var(--bg); border-color: var(--fg-3);
|
|
161
|
+
}
|
|
162
|
+
.ds-theme-disc[data-mode="ink"] {
|
|
163
|
+
background: var(--fg-3); border-color: var(--fg-3);
|
|
164
|
+
}
|
|
153
165
|
|
|
@@ -69,6 +69,18 @@
|
|
|
69
69
|
background: color-mix(in oklab, var(--mascot) 26%, var(--bg-2));
|
|
70
70
|
color: var(--mascot-deep);
|
|
71
71
|
}
|
|
72
|
+
/* Plain assistant turns ('them', no aicat persona) previously fell through to
|
|
73
|
+
the bare .chat-avatar gray disc with no role identity at all — 'you' and
|
|
74
|
+
'aicat' both got a tinted background + ring, the default assistant got
|
|
75
|
+
nothing. Give it the same tinted-background treatment via --color-success
|
|
76
|
+
(the conventional assistant/bot association, distinct from the accent hue
|
|
77
|
+
'you' owns and the mascot hue 'aicat' owns) plus a thin identity ring so
|
|
78
|
+
role is readable from the avatar alone, not just bubble position/color. */
|
|
79
|
+
.chat-msg.them:not(.aicat) .chat-avatar {
|
|
80
|
+
background: color-mix(in oklab, var(--color-success) 20%, var(--bg-3));
|
|
81
|
+
color: color-mix(in oklab, var(--color-success) 65%, var(--fg));
|
|
82
|
+
box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--color-success) 35%, transparent);
|
|
83
|
+
}
|
|
72
84
|
|
|
73
85
|
.chat-stack {
|
|
74
86
|
display: flex; flex-direction: column; gap: var(--space-1);
|
|
@@ -79,6 +79,11 @@
|
|
|
79
79
|
border-radius: var(--r-pill);
|
|
80
80
|
background: var(--bg-2);
|
|
81
81
|
}
|
|
82
|
+
/* .ds-collab-chip-avatar (24px identity circle) and .ds-collab-chip-status
|
|
83
|
+
(8px status dot) below are both circular via border-radius: 50% but are
|
|
84
|
+
intentionally distinct: different sizes, different content (initial vs.
|
|
85
|
+
plain fill), different positioning role (layout box vs. absolute badge).
|
|
86
|
+
Not extracting a shared class -- there's nothing substantive to share. */
|
|
82
87
|
.ds-collab-chip-avatar {
|
|
83
88
|
position: relative;
|
|
84
89
|
width: 24px;
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
|
|
15
15
|
/* PhaseWalk */
|
|
16
16
|
.ds-phasewalk { display: inline-flex; gap: var(--space-hair); vertical-align: middle; }
|
|
17
|
+
/* NOTE: a second toggle-switch-shaped implementation exists in
|
|
18
|
+
plugins-config.css:91 (.ds-plugins-toggle) at a different scale (40x22 vs
|
|
19
|
+
this 18x10) — consider consolidating into a shared .ds-switch primitive
|
|
20
|
+
with a size modifier. */
|
|
17
21
|
.ds-phasewalk-seg {
|
|
18
22
|
width: 18px; height: 10px; border-radius: var(--r-hair);
|
|
19
23
|
background: var(--bg-3); position: relative;
|
|
@@ -735,7 +735,7 @@
|
|
|
735
735
|
.ds-preview-code::-webkit-scrollbar,
|
|
736
736
|
.ds-preview-text::-webkit-scrollbar,
|
|
737
737
|
.ds-preview-body::-webkit-scrollbar,
|
|
738
|
-
.ds-modal-body::-webkit-scrollbar { width:
|
|
738
|
+
.ds-modal-body::-webkit-scrollbar { width: 8px; height: 8px; }
|
|
739
739
|
.ds-preview-code::-webkit-scrollbar-thumb,
|
|
740
740
|
.ds-preview-text::-webkit-scrollbar-thumb {
|
|
741
741
|
background: color-mix(in oklab, var(--paper) 28%, transparent);
|
|
@@ -500,7 +500,7 @@ table tr.clickable:focus-visible td { background: var(--bg-2); }
|
|
|
500
500
|
Form
|
|
501
501
|
============================================================ */
|
|
502
502
|
.row-form {
|
|
503
|
-
display: grid; gap:
|
|
503
|
+
display: grid; gap: var(--space-2-75);
|
|
504
504
|
padding: var(--space-5);
|
|
505
505
|
background: var(--bg-2);
|
|
506
506
|
border-radius: var(--r-3);
|
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
/* ============================================================
|
|
3
3
|
Spinner — animated loading indicator
|
|
4
4
|
============================================================ */
|
|
5
|
+
/* Dots spinner — use for small inline/compact loading states. */
|
|
5
6
|
.ds-spinner {
|
|
6
7
|
display: inline-flex;
|
|
7
|
-
gap:
|
|
8
|
+
gap: var(--space-1);
|
|
8
9
|
align-items: center;
|
|
9
10
|
height: 16px;
|
|
10
11
|
}
|
|
11
12
|
.ds-spinner span {
|
|
12
|
-
width:
|
|
13
|
-
height:
|
|
13
|
+
width: var(--space-1);
|
|
14
|
+
height: var(--space-1);
|
|
14
15
|
border-radius: 50%;
|
|
15
16
|
background: var(--accent);
|
|
16
17
|
}
|
|
@@ -21,17 +22,17 @@
|
|
|
21
22
|
.ds-spinner span:nth-child(3) { animation-delay: 0.36s; }
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
.ds-spinner-lg { height: 24px; gap:
|
|
25
|
-
.ds-spinner-lg span { width:
|
|
25
|
+
.ds-spinner-lg { height: 24px; gap: var(--space-1-75); }
|
|
26
|
+
.ds-spinner-lg span { width: var(--space-1-75); height: var(--space-1-75); }
|
|
26
27
|
|
|
27
|
-
.ds-spinner-sm { height: 12px; gap:
|
|
28
|
-
.ds-spinner-sm span { width:
|
|
28
|
+
.ds-spinner-sm { height: 12px; gap: var(--space-hair); }
|
|
29
|
+
.ds-spinner-sm span { width: var(--space-hair); height: var(--space-hair); }
|
|
29
30
|
|
|
30
31
|
.ds-spinner-xs { height: 8px; gap: 1px; }
|
|
31
32
|
.ds-spinner-xs span { width: 1.5px; height: 1.5px; }
|
|
32
33
|
|
|
33
|
-
.ds-spinner-xl { height: 32px; gap:
|
|
34
|
-
.ds-spinner-xl span { width:
|
|
34
|
+
.ds-spinner-xl { height: 32px; gap: var(--space-2); }
|
|
35
|
+
.ds-spinner-xl span { width: var(--space-2); height: var(--space-2); }
|
|
35
36
|
|
|
36
37
|
@keyframes ds-bounce {
|
|
37
38
|
0%, 80%, 100% { transform: translateY(0); }
|
|
@@ -81,7 +82,7 @@
|
|
|
81
82
|
flex-shrink: 0;
|
|
82
83
|
color: var(--alert-tone);
|
|
83
84
|
font-weight: 600;
|
|
84
|
-
min-width:
|
|
85
|
+
min-width: var(--space-3-5);
|
|
85
86
|
text-align: center;
|
|
86
87
|
}
|
|
87
88
|
|
|
@@ -110,11 +111,12 @@
|
|
|
110
111
|
cursor: pointer;
|
|
111
112
|
color: var(--fg-3);
|
|
112
113
|
padding: 0;
|
|
113
|
-
font-size:
|
|
114
|
+
font-size: var(--fs-xs);
|
|
114
115
|
line-height: 1;
|
|
115
|
-
min-width:
|
|
116
|
+
min-width: var(--space-3-5);
|
|
116
117
|
text-align: center;
|
|
117
118
|
transition: color var(--dur-snap) var(--ease);
|
|
118
119
|
}
|
|
119
120
|
.ds-alert-dismiss:hover { color: var(--fg); }
|
|
121
|
+
.ds-alert-dismiss:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
|
|
120
122
|
|
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
.ds-otp-box:focus-visible {
|
|
18
18
|
outline: none;
|
|
19
19
|
border-color: var(--accent);
|
|
20
|
-
box-shadow:
|
|
20
|
+
box-shadow: var(--focus-ring-inset);
|
|
21
21
|
}
|
|
22
22
|
.ds-otp-box-filled { border-color: var(--rule-strong); background: var(--bg-3); }
|
|
23
23
|
.ds-otp-box-error { border-color: var(--red); }
|
|
24
|
-
.ds-otp-box-error:focus-visible { box-shadow:
|
|
24
|
+
.ds-otp-box-error:focus-visible { box-shadow: var(--focus-ring-inset); }
|
|
25
25
|
.ds-otp-box:disabled { opacity: 0.5; cursor: default; }
|
|
26
26
|
|
|
27
27
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -87,6 +87,9 @@
|
|
|
87
87
|
}
|
|
88
88
|
.ds-plugins-version { font-size: var(--fs-micro); color: var(--fg-3); }
|
|
89
89
|
|
|
90
|
+
/* NOTE: a second toggle-switch implementation exists in data-density.css:18
|
|
91
|
+
(.ds-phasewalk-seg) at a different scale (18x10 vs this 40x22) — consider
|
|
92
|
+
consolidating into a shared .ds-switch primitive with a size modifier. */
|
|
90
93
|
.ds-plugins-toggle {
|
|
91
94
|
flex: 0 0 auto; width: 40px; height: 22px; border-radius: 11px; border: none; padding: 0;
|
|
92
95
|
background: var(--rule); position: relative; cursor: pointer;
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
}
|
|
23
23
|
.btn-primary:hover { background: var(--fg); color: var(--green); transform: translateY(-1px); box-shadow: var(--shadow-2); }
|
|
24
24
|
.btn-ghost { background: transparent; color: var(--fg); box-shadow: inset 0 0 0 2px var(--fg); }
|
|
25
|
-
.btn-ghost:hover { background: var(--fg)
|
|
25
|
+
.btn-ghost:hover { background: color-mix(in oklab, var(--fg) 12%, transparent); transform: translateY(-1px); }
|
|
26
26
|
.btn:active, .btn-primary:active, .btn-ghost:active { transform: translateY(1px); box-shadow: none; }
|
|
27
27
|
.btn:focus-visible, .btn-primary:focus-visible, .btn-ghost:focus-visible, .ds-icon-btn:focus-visible {
|
|
28
28
|
outline: var(--focus-w) solid var(--focus-color);
|
|
@@ -54,7 +54,9 @@
|
|
|
54
54
|
border-radius: var(--r-1, 6px);
|
|
55
55
|
background: transparent; color: var(--fg);
|
|
56
56
|
width: 32px; height: 32px;
|
|
57
|
+
transition: background var(--dur-snap) var(--ease), color var(--dur-snap) var(--ease);
|
|
57
58
|
}
|
|
59
|
+
.ds-icon-btn:hover { background: var(--bg-2); }
|
|
58
60
|
.ds-icon-btn-xs { width: 22px; height: 22px; }
|
|
59
61
|
.ds-icon-btn-sm { width: 26px; height: 26px; }
|
|
60
62
|
.ds-icon-btn-base { width: 32px; height: 32px; }
|
|
@@ -64,7 +66,10 @@
|
|
|
64
66
|
.ds-icon-btn-ghost:hover { background: var(--fg); color: var(--bg); }
|
|
65
67
|
.ds-icon-btn-primary { background: var(--accent); color: var(--accent-fg); }
|
|
66
68
|
.ds-icon-btn-primary:hover { background: var(--fg); color: var(--accent-ink); }
|
|
67
|
-
|
|
69
|
+
/* --ink, not --on-color: --on-color is a fixed #fff, which measures 3.07:1 on
|
|
70
|
+
--warn (#FF5A52) -- below the 4.5:1 AA floor. --ink measures 6.27:1. Same
|
|
71
|
+
fix as .btn-primary.danger in app-shell/files.css. */
|
|
72
|
+
.ds-icon-btn-danger { background: var(--warn); color: var(--ink); }
|
|
68
73
|
.ds-icon-btn-danger:hover { filter: brightness(0.92); }
|
|
69
74
|
.ds-icon-btn:active { transform: translateY(1px); }
|
|
70
75
|
.ds-icon-btn:disabled, .ds-icon-btn.is-disabled {
|
|
@@ -78,7 +83,7 @@
|
|
|
78
83
|
border-radius: var(--r-pill);
|
|
79
84
|
background: var(--bg-3); color: var(--fg-2);
|
|
80
85
|
}
|
|
81
|
-
.ds-badge.ds-badge--sm { min-width: 14px; height: 14px; padding: 0 var(--space-1); font-size:
|
|
86
|
+
.ds-badge.ds-badge--sm { min-width: 14px; height: 14px; padding: 0 var(--space-1); font-size: var(--fs-pico); }
|
|
82
87
|
.ds-badge.ds-badge--lg { min-width: 22px; height: 22px; padding: 0 var(--space-2); font-size: var(--fs-tiny); }
|
|
83
88
|
.ds-badge.tone-green, .ds-badge.tone-live, .ds-badge.tone-success {
|
|
84
89
|
background: var(--green-tint); color: var(--green-deep);
|
|
@@ -95,7 +100,7 @@
|
|
|
95
100
|
background: var(--sun); color: var(--ink);
|
|
96
101
|
}
|
|
97
102
|
.ds-badge.tone-blue { background: var(--accent-tint); color: var(--accent-ink); }
|
|
98
|
-
.ds-badge.tone-orange { background: color-mix(in oklab, var(--flame)
|
|
103
|
+
.ds-badge.tone-orange { background: color-mix(in oklab, var(--flame) 22%, var(--bg)); color: color-mix(in oklab, var(--flame) 65%, black); }
|
|
99
104
|
|
|
100
105
|
.chip {
|
|
101
106
|
display: inline-flex; align-items: center; gap: var(--space-1-75);
|
|
@@ -163,6 +168,10 @@
|
|
|
163
168
|
.ds-pill.tone-accent { background: var(--accent-tint); color: var(--accent-ink); }
|
|
164
169
|
.ds-pill.tone-muted { background: transparent; color: var(--fg-3); box-shadow: inset 0 0 0 1px var(--rule); }
|
|
165
170
|
|
|
171
|
+
/* .glyph-* sizes (14/18/22px) are for small inline text-adjacent glyphs
|
|
172
|
+
(single characters/icons sitting in a text flow), a different scale and
|
|
173
|
+
purpose than the .ds-icon-btn-* tap-target scale (22/26/32/40/48px) below --
|
|
174
|
+
the -sm/-lg naming overlap is coincidental, not a shared system. */
|
|
166
175
|
.glyph {
|
|
167
176
|
display: inline-flex; align-items: center; justify-content: center;
|
|
168
177
|
width: 18px; height: 18px;
|
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
Responsive Design — Mobile (480px), Tablet (1024px), Desktop (1440px+)
|
|
4
4
|
============================================================ */
|
|
5
5
|
|
|
6
|
+
/* Breakpoint reference (CSS custom properties can't be used in @media queries,
|
|
7
|
+
so these are documented here for consistency — always use these exact values):
|
|
8
|
+
- Mobile/tablet boundary: 768px (max-width) — used for compact nav/sidebar
|
|
9
|
+
collapse (catalog-theme.css, sidebar-misc.css). Note: kits-appended.css
|
|
10
|
+
uses 760px for the same kind of collapse in a few places — a near-miss
|
|
11
|
+
of this value, worth aligning to 768px when touched.
|
|
12
|
+
- Tablet/desktop boundary: 900px (max-width) / 901px (min-width) — used for
|
|
13
|
+
main layout reflow (hero-content.css, responsive2-workspace.css)
|
|
14
|
+
- Legacy tablet band: 481px–1024px (min-width 481px / max-width 1025px) —
|
|
15
|
+
used in this file (responsive.css) for a specific tablet treatment,
|
|
16
|
+
consider migrating to the 768/900 pair
|
|
17
|
+
*/
|
|
18
|
+
|
|
6
19
|
/* --------------------------------------------------------------------
|
|
7
20
|
Mobile Portrait Breakpoint (480px and below)
|
|
8
21
|
---------------------------------------------------------------------- */
|
|
@@ -281,7 +281,9 @@
|
|
|
281
281
|
.ws-rail-item:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: -2px; }
|
|
282
282
|
.ws-rail-item.active { background: var(--accent-tint); color: var(--fg); box-shadow: inset 2px 0 0 var(--accent); }
|
|
283
283
|
.ws-rail-item-label { flex: 1; overflow: hidden; text-overflow: ellipsis; }
|
|
284
|
-
|
|
284
|
+
/* Shape/color come from the shared .ds-badge component (see app-shell/primitives.css);
|
|
285
|
+
this class is now just the rail-item layout hook (spacing, collapsed-state targeting). */
|
|
286
|
+
.ws-rail-item-count { flex: none; margin-inline-start: var(--space-1); }
|
|
285
287
|
.ws-rail-item-flag { width: 6px; height: 6px; border-radius: 50%; flex: none; }
|
|
286
288
|
.ws-rail-item-flag.tone-flame { background: var(--flame); box-shadow: 0 0 0 2px color-mix(in oklab, var(--flame) 38%, transparent); }
|
|
287
289
|
.ws-rail-foot { margin-top: auto; padding-top: var(--space-2); border-top: var(--bw-hair) solid var(--bg-3); }
|
|
@@ -324,6 +326,7 @@
|
|
|
324
326
|
because the chat thread carries its own --measure gutter. */
|
|
325
327
|
.ws-main { flex: 1; min-height: 0; overflow: auto; display: flex; flex-direction: column; padding: var(--space-4) var(--space-5); }
|
|
326
328
|
.ws-main.ws-main--flush { padding: 0; }
|
|
329
|
+
/* Non-interactive scroll region (content column), not a focusable control — outline intentionally suppressed. */
|
|
327
330
|
.ws-main:focus { outline: none; }
|
|
328
331
|
|
|
329
332
|
/* Right context pane */
|
|
@@ -34,12 +34,26 @@ tr.clickable:focus-visible {
|
|
|
34
34
|
.btn:disabled, .btn-primary:disabled, .btn-ghost:disabled,
|
|
35
35
|
.btn.is-disabled, .btn-primary.is-disabled, .btn-ghost.is-disabled,
|
|
36
36
|
button:disabled, [disabled] {
|
|
37
|
-
opacity: 0.
|
|
37
|
+
opacity: 0.5;
|
|
38
38
|
filter: grayscale(60%);
|
|
39
39
|
cursor: not-allowed;
|
|
40
40
|
pointer-events: none;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/* Systemic hover fallback — button-like elements with no design-system class
|
|
44
|
+
(a bare <button>, [role="button"], or a generic .row/.chip usage) get a
|
|
45
|
+
`transition` declared above (line ~106) but nothing to transition to, so
|
|
46
|
+
they look inert until clicked. This is a low-specificity (0,0,1)/(0,1,1)
|
|
47
|
+
catch-all: any element that also carries `.btn`/`.btn-primary`/`.btn-ghost`/
|
|
48
|
+
`.ds-icon-btn`/etc. keeps its own component `:hover` (those selectors are
|
|
49
|
+
equal-or-higher specificity and, for the shared ones, declared later in
|
|
50
|
+
the cascade), so this never overrides a deliberate per-component hover —
|
|
51
|
+
it only fills the gap for elements that don't have one. */
|
|
52
|
+
button:not(:disabled):hover,
|
|
53
|
+
[role="button"]:not([aria-disabled="true"]):hover {
|
|
54
|
+
background-color: var(--panel-hover, var(--bg-3));
|
|
55
|
+
}
|
|
56
|
+
|
|
43
57
|
/* Loading state — spinning indicator */
|
|
44
58
|
@keyframes spinner-rotate {
|
|
45
59
|
from { transform: rotate(0deg); }
|
|
@@ -47,10 +61,12 @@ button:disabled, [disabled] {
|
|
|
47
61
|
}
|
|
48
62
|
/* Reusable spin utility: rotates the element (e.g. a refresh Icon used as a
|
|
49
63
|
busy indicator) via the shared spinner-rotate keyframe. Respects reduced
|
|
50
|
-
motion. Use instead of a decorative rotation glyph.
|
|
64
|
+
motion. Use instead of a decorative rotation glyph.
|
|
65
|
+
Ring spinner — use for larger blocking/full-region loading states. */
|
|
51
66
|
.ds-spin { animation: spinner-rotate 0.8s linear infinite; transform-origin: center; }
|
|
52
67
|
@media (prefers-reduced-motion: reduce) { .ds-spin { animation: none; } }
|
|
53
68
|
|
|
69
|
+
/* Ring spinner — use for larger blocking/full-region loading states. */
|
|
54
70
|
.loading { position: relative; }
|
|
55
71
|
.loading::after {
|
|
56
72
|
content: '';
|
|
@@ -477,7 +493,7 @@ hr.rule-dotted { border-top: 2px dotted var(--rule-strong); }
|
|
|
477
493
|
background: var(--accent-ink); border-color: var(--accent-ink);
|
|
478
494
|
}
|
|
479
495
|
.ds-toggle[aria-checked="true"] .ds-toggle-knob {
|
|
480
|
-
background: var(--panel-0,
|
|
496
|
+
background: var(--panel-0, var(--bg));
|
|
481
497
|
transform: translateX(18px);
|
|
482
498
|
}
|
|
483
499
|
.ds-toggle:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
@@ -611,7 +627,7 @@ hr.rule-dotted { border-top: 2px dotted var(--rule-strong); }
|
|
|
611
627
|
.ds-field input:not(.input):disabled,
|
|
612
628
|
.ds-field textarea:not(.input):disabled,
|
|
613
629
|
.ds-field .ds-select:disabled {
|
|
614
|
-
opacity: .
|
|
630
|
+
opacity: 0.5;
|
|
615
631
|
cursor: not-allowed;
|
|
616
632
|
}
|
|
617
633
|
.ds-field input:not(.input)[aria-invalid="true"],
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
gap: var(--space-2);
|
|
18
18
|
padding: var(--space-1) var(--space-2);
|
|
19
19
|
background: var(--os-bg-2);
|
|
20
|
+
border-bottom: 1px solid color-mix(in oklab, var(--os-accent) 35%, transparent);
|
|
20
21
|
font-family: var(--os-mono);
|
|
21
22
|
font-size: 11px;
|
|
22
23
|
color: var(--os-fg-2);
|
|
@@ -32,7 +33,7 @@
|
|
|
32
33
|
}
|
|
33
34
|
.terminal-app-status {
|
|
34
35
|
font-family: var(--os-mono);
|
|
35
|
-
color: var(--os-
|
|
36
|
+
color: var(--os-accent);
|
|
36
37
|
flex: 0 0 auto;
|
|
37
38
|
overflow: hidden;
|
|
38
39
|
text-overflow: ellipsis;
|
|
@@ -43,7 +44,7 @@
|
|
|
43
44
|
min-width: 0;
|
|
44
45
|
min-height: 0;
|
|
45
46
|
overflow: hidden;
|
|
46
|
-
background: var(--os-bg-
|
|
47
|
+
background: var(--os-bg-1);
|
|
47
48
|
position: relative;
|
|
48
49
|
}
|
|
49
50
|
.terminal-app-slot > * { width: 100%; height: 100%; }
|