birdclaw 0.4.1 → 0.5.1
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/CHANGELOG.md +52 -0
- package/README.md +113 -7
- package/bin/birdclaw.mjs +50 -11
- package/package.json +30 -28
- package/playwright.config.ts +1 -0
- package/public/birdclaw-mark.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +2 -2
- package/scripts/browser-perf.mjs +399 -0
- package/scripts/build-docs-site.mjs +940 -0
- package/scripts/docs-site-assets.mjs +311 -0
- package/scripts/run-vitest.mjs +21 -0
- package/scripts/sanitize-node-options.mjs +23 -0
- package/scripts/start-test-server.mjs +29 -0
- package/src/cli.ts +496 -19
- package/src/components/AppNav.tsx +66 -29
- package/src/components/AvatarChip.tsx +10 -5
- package/src/components/BrandMark.tsx +67 -0
- package/src/components/ConversationThread.tsx +126 -0
- package/src/components/DmWorkspace.tsx +118 -105
- package/src/components/EmbeddedTweetCard.tsx +20 -14
- package/src/components/FeedState.tsx +147 -0
- package/src/components/InboxCard.tsx +104 -90
- package/src/components/LinkPreviewCard.tsx +270 -0
- package/src/components/ProfilePreview.tsx +8 -3
- package/src/components/SavedTimelineView.tsx +89 -71
- package/src/components/SyncNowButton.tsx +105 -0
- package/src/components/ThemeSlider.tsx +10 -59
- package/src/components/TimelineCard.tsx +326 -86
- package/src/components/TimelineRouteFrame.tsx +156 -0
- package/src/components/TweetMediaGrid.tsx +120 -23
- package/src/components/TweetRichText.tsx +19 -4
- package/src/components/useTimelineRouteData.ts +137 -0
- package/src/lib/api-client.ts +229 -0
- package/src/lib/archive-finder.ts +24 -20
- package/src/lib/archive-import.ts +1582 -67
- package/src/lib/authored-live.ts +1074 -0
- package/src/lib/backup.ts +316 -14
- package/src/lib/bird-actions.ts +1 -10
- package/src/lib/bird-command.ts +57 -0
- package/src/lib/bird.ts +89 -5
- package/src/lib/config.ts +1 -1
- package/src/lib/conversation-surface.ts +174 -0
- package/src/lib/db.ts +193 -4
- package/src/lib/follow-graph.ts +1053 -0
- package/src/lib/link-index.ts +11 -98
- package/src/lib/link-insights.ts +834 -0
- package/src/lib/link-preview-metadata.ts +334 -0
- package/src/lib/media-fetch.ts +787 -0
- package/src/lib/media-includes.ts +165 -0
- package/src/lib/mention-threads-live.ts +535 -43
- package/src/lib/mentions-live.ts +623 -19
- package/src/lib/moderation-target.ts +6 -0
- package/src/lib/profile-hydration.ts +1 -1
- package/src/lib/profile-resolver.ts +115 -1
- package/src/lib/queries.ts +326 -35
- package/src/lib/seed.ts +127 -8
- package/src/lib/timeline-collections-live.ts +145 -22
- package/src/lib/timeline-live.ts +10 -15
- package/src/lib/tweet-account-edges.ts +9 -2
- package/src/lib/tweet-render.ts +97 -0
- package/src/lib/types.ts +185 -2
- package/src/lib/ui.ts +383 -147
- package/src/lib/url-expansion-store.ts +110 -0
- package/src/lib/url-expansion.ts +20 -3
- package/src/lib/web-sync.ts +443 -0
- package/src/lib/x-profile.ts +7 -2
- package/src/lib/xurl.ts +296 -12
- package/src/routeTree.gen.ts +126 -0
- package/src/routes/__root.tsx +7 -3
- package/src/routes/api/conversation.tsx +34 -0
- package/src/routes/api/link-insights.tsx +79 -0
- package/src/routes/api/link-preview.tsx +43 -0
- package/src/routes/api/profile-hydrate.tsx +51 -0
- package/src/routes/api/sync.tsx +59 -0
- package/src/routes/blocks.tsx +111 -86
- package/src/routes/dms.tsx +172 -87
- package/src/routes/inbox.tsx +98 -86
- package/src/routes/index.tsx +22 -115
- package/src/routes/links.tsx +928 -0
- package/src/routes/mentions.tsx +22 -115
- package/src/styles.css +169 -43
- package/vite.config.ts +8 -0
package/src/lib/ui.ts
CHANGED
|
@@ -3,268 +3,504 @@ export function cx(...values: Array<string | false | null | undefined>) {
|
|
|
3
3
|
}
|
|
4
4
|
|
|
5
5
|
export const bodyClass =
|
|
6
|
-
"min-h-screen bg-[var(--bg)] font-sans text-[var(--ink)] antialiased
|
|
6
|
+
"min-h-screen bg-[var(--bg)] font-sans text-[var(--ink)] antialiased text-[15px] leading-[1.4]";
|
|
7
7
|
|
|
8
|
+
/* App shell — sidebar left, single feed column center, optional aside right. */
|
|
8
9
|
export const siteShellClass =
|
|
9
|
-
"mx-auto
|
|
10
|
+
"mx-auto flex min-h-screen w-full max-w-[1280px] gap-0";
|
|
10
11
|
|
|
11
|
-
export const
|
|
12
|
+
export const sidebarShellClass =
|
|
13
|
+
"sticky top-0 z-30 flex h-screen w-[72px] shrink-0 flex-col justify-between border-r border-[var(--line)] bg-[var(--bg)] px-2 py-3 min-[1100px]:w-[260px] min-[1100px]:px-3";
|
|
12
14
|
|
|
13
|
-
export const
|
|
15
|
+
export const sidebarBrandClass =
|
|
16
|
+
"flex items-center gap-2.5 px-2 py-2 text-[var(--ink)] min-[1100px]:px-3";
|
|
14
17
|
|
|
15
|
-
export const
|
|
18
|
+
export const sidebarBrandMarkClass = "grid size-10 place-items-center";
|
|
16
19
|
|
|
17
|
-
export const
|
|
20
|
+
export const sidebarBrandCopyClass =
|
|
21
|
+
"hidden flex-col leading-tight min-[1100px]:flex";
|
|
18
22
|
|
|
19
|
-
export const
|
|
23
|
+
export const sidebarBrandTitleClass = "text-[15px] font-bold tracking-tight";
|
|
20
24
|
|
|
21
|
-
export const
|
|
22
|
-
"m-0 mb-2 text-[11px] uppercase tracking-[0.24em] text-[var(--ink-soft)]";
|
|
23
|
-
|
|
24
|
-
export const brandMarkClass =
|
|
25
|
-
"m-0 max-w-[12ch] font-display text-[clamp(1.8rem,3vw,3.2rem)] leading-[0.95] font-semibold";
|
|
25
|
+
export const sidebarBrandTaglineClass = "text-[12px] text-[var(--ink-soft)]";
|
|
26
26
|
|
|
27
|
-
export const
|
|
28
|
-
"flex items-end justify-between gap-6 border-b border-[var(--line)] py-[18px] pb-5 max-[760px]:flex-col max-[760px]:items-start";
|
|
27
|
+
export const sidebarNavClass = "mt-2 flex flex-col gap-0.5";
|
|
29
28
|
|
|
30
|
-
export const
|
|
29
|
+
export const sidebarFooterClass = "flex flex-col gap-2 pb-1";
|
|
31
30
|
|
|
32
31
|
export const navLinkClass =
|
|
33
|
-
"nav-link
|
|
32
|
+
"nav-link group flex items-center justify-center gap-4 rounded-full px-3 py-2.5 text-[15px] text-[var(--ink)] transition-colors duration-150 hover:bg-[var(--bg-hover)] min-[1100px]:justify-start";
|
|
34
33
|
|
|
35
|
-
export const navLinkActiveClass =
|
|
36
|
-
"nav-link-active border-[var(--line-strong)] bg-[var(--panel)] text-[var(--ink)] shadow-[0_16px_42px_var(--shadow)]";
|
|
34
|
+
export const navLinkActiveClass = "nav-link-active font-bold";
|
|
37
35
|
|
|
38
|
-
export const
|
|
39
|
-
"flex items-end justify-between gap-6 py-7 pb-6 max-[760px]:flex-col max-[760px]:items-start";
|
|
36
|
+
export const navLinkIconClass = "shrink-0";
|
|
40
37
|
|
|
41
|
-
export const
|
|
38
|
+
export const navLinkLabelClass = "sr-only min-[1100px]:not-sr-only";
|
|
42
39
|
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
/* Main column. */
|
|
41
|
+
export const mainColumnClass =
|
|
42
|
+
"flex w-full min-w-0 max-w-[680px] flex-1 flex-col border-x border-[var(--line)] bg-[var(--bg)]";
|
|
45
43
|
|
|
46
|
-
export const
|
|
47
|
-
"hero-copy mt-3.5 max-w-[46ch] text-base text-[var(--ink-soft)]";
|
|
44
|
+
export const pageWrapClass = "flex w-full min-w-0 flex-1 justify-start";
|
|
48
45
|
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
/* Sticky page header at top of the main column. */
|
|
47
|
+
export const pageHeaderClass =
|
|
48
|
+
"sticky top-0 z-20 flex flex-col gap-0 border-b border-[var(--line)] bg-[color:color-mix(in_srgb,var(--bg)_85%,transparent)] backdrop-blur";
|
|
51
49
|
|
|
52
|
-
export const
|
|
50
|
+
export const pageHeaderRowClass =
|
|
51
|
+
"flex items-center justify-between gap-3 px-4 pt-3 pb-2";
|
|
53
52
|
|
|
54
|
-
export const
|
|
53
|
+
export const pageTitleClass = "m-0 text-[20px] font-bold tracking-tight";
|
|
55
54
|
|
|
56
|
-
export const
|
|
57
|
-
"text-field w-full rounded-2xl border border-[var(--line)] bg-[var(--panel)] px-3.5 py-3 text-[var(--ink)] shadow-[0_18px_40px_var(--shadow)] outline-none transition duration-180 placeholder:text-[var(--ink-soft)] focus:border-[var(--line-strong)] disabled:cursor-default disabled:opacity-55";
|
|
55
|
+
export const pageSubtitleClass = "text-[13px] text-[var(--ink-soft)]";
|
|
58
56
|
|
|
59
|
-
export const
|
|
57
|
+
export const pageHeaderActionsClass = "flex items-center gap-2";
|
|
60
58
|
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
/* Sticky tab strip Twitter-style. */
|
|
60
|
+
export const tabStripClass =
|
|
61
|
+
"flex w-full items-stretch border-b border-[var(--line)] overflow-x-auto";
|
|
63
62
|
|
|
64
|
-
export const
|
|
65
|
-
"
|
|
63
|
+
export const tabButtonClass =
|
|
64
|
+
"relative flex flex-1 min-w-[88px] items-center justify-center px-3 py-3.5 text-[14px] font-medium text-[var(--ink-soft)] transition-colors duration-150 hover:bg-[var(--bg-hover)]";
|
|
66
65
|
|
|
67
|
-
export const
|
|
68
|
-
"segment rounded-full border-0 bg-transparent px-3.5 py-2.5 text-[var(--ink-soft)] transition duration-180";
|
|
66
|
+
export const tabButtonActiveClass = "text-[var(--ink)] font-bold";
|
|
69
67
|
|
|
70
|
-
export const
|
|
71
|
-
"
|
|
68
|
+
export const tabButtonIndicatorClass =
|
|
69
|
+
"pointer-events-none absolute inset-x-0 bottom-0 mx-auto h-[3px] w-12 rounded-full bg-[var(--accent)]";
|
|
72
70
|
|
|
73
|
-
|
|
71
|
+
/* Feed rows: flat, hairline divider, no boxed cards. */
|
|
72
|
+
export const feedClass = "flex flex-col";
|
|
74
73
|
|
|
75
|
-
export const
|
|
76
|
-
"
|
|
74
|
+
export const feedRowClass =
|
|
75
|
+
"flex w-full gap-3 border-b border-[var(--line)] px-4 py-3 transition-colors duration-150 hover:bg-[var(--bg-hover)]";
|
|
77
76
|
|
|
78
|
-
export const
|
|
77
|
+
export const feedRowBodyClass = "flex min-w-0 flex-1 flex-col gap-1";
|
|
79
78
|
|
|
80
|
-
export const
|
|
81
|
-
"flex
|
|
79
|
+
export const feedRowHeaderClass =
|
|
80
|
+
"flex min-w-0 items-center gap-1.5 text-[15px]";
|
|
82
81
|
|
|
83
|
-
export const
|
|
84
|
-
"
|
|
82
|
+
export const feedRowNameClass =
|
|
83
|
+
"truncate font-bold text-[var(--ink)] hover:underline";
|
|
85
84
|
|
|
86
|
-
export const
|
|
85
|
+
export const feedRowHandleClass = "truncate text-[var(--ink-soft)]";
|
|
87
86
|
|
|
88
|
-
export const
|
|
89
|
-
"flex flex-wrap items-center gap-2.5 text-[0.92rem] text-[var(--ink-soft)]";
|
|
87
|
+
export const feedRowDotClass = "text-[var(--ink-soft)]";
|
|
90
88
|
|
|
91
|
-
export const
|
|
92
|
-
"
|
|
89
|
+
export const feedRowTimestampClass =
|
|
90
|
+
"shrink-0 text-[var(--ink-soft)] hover:underline";
|
|
93
91
|
|
|
94
|
-
export const
|
|
95
|
-
"
|
|
92
|
+
export const feedRowMetaClass =
|
|
93
|
+
"flex flex-wrap items-center gap-2 text-[13px] text-[var(--ink-soft)]";
|
|
96
94
|
|
|
97
|
-
export const
|
|
95
|
+
export const feedRowActionsClass =
|
|
96
|
+
"mt-2 flex max-w-md items-center justify-between text-[var(--ink-soft)]";
|
|
98
97
|
|
|
99
|
-
export const
|
|
100
|
-
"
|
|
98
|
+
export const feedActionButtonClass =
|
|
99
|
+
"action-button group/action inline-flex items-center gap-1 rounded-full border-0 bg-transparent px-2 py-1 text-[13px] text-[var(--ink-soft)] transition-colors duration-150 hover:text-[var(--accent)] disabled:cursor-default disabled:opacity-55";
|
|
101
100
|
|
|
102
|
-
export const
|
|
103
|
-
"pill-soft bg-[var(--accent-soft)] text-[var(--accent)]";
|
|
101
|
+
export const feedActionLikeClass = "hover:text-[var(--like)]";
|
|
104
102
|
|
|
105
|
-
export const
|
|
106
|
-
"
|
|
103
|
+
export const feedActionIconWrapClass =
|
|
104
|
+
"grid size-[34px] place-items-center rounded-full transition-colors duration-150 group-hover/action:bg-[var(--accent-soft)]";
|
|
107
105
|
|
|
108
|
-
export const
|
|
106
|
+
export const feedActionIconWrapLikeClass =
|
|
107
|
+
"group-hover/action:bg-[var(--like-soft)]";
|
|
109
108
|
|
|
110
|
-
export const
|
|
109
|
+
export const feedActionIconClass = "size-[18px]";
|
|
111
110
|
|
|
112
|
-
export const
|
|
111
|
+
export const feedRowTextClass =
|
|
112
|
+
"whitespace-pre-wrap break-words text-[15px] leading-[1.45] text-[var(--ink)] [overflow-wrap:anywhere]";
|
|
113
113
|
|
|
114
|
-
export const
|
|
115
|
-
"
|
|
114
|
+
export const feedRowBadgeClass =
|
|
115
|
+
"inline-flex items-center gap-1 rounded-full bg-[var(--accent-soft)] px-2 py-0.5 text-[12px] font-semibold text-[var(--accent)]";
|
|
116
116
|
|
|
117
|
-
export const
|
|
118
|
-
"
|
|
117
|
+
export const feedRowBadgeAlertClass =
|
|
118
|
+
"bg-[var(--alert-soft)] text-[var(--alert)]";
|
|
119
|
+
|
|
120
|
+
export const feedRowBadgeNeutralClass =
|
|
121
|
+
"bg-[var(--bg-active)] text-[var(--ink-soft)]";
|
|
122
|
+
|
|
123
|
+
export const feedRowStatePillClass =
|
|
124
|
+
"inline-flex shrink-0 items-center gap-1 rounded-full border border-[var(--line)] bg-[var(--bg-active)] px-2 py-0.5 text-[12px] font-semibold text-[var(--ink-soft)]";
|
|
125
|
+
|
|
126
|
+
export const feedRowStatePillActiveClass =
|
|
127
|
+
"border-[color:color-mix(in_srgb,var(--accent)_35%,var(--line))] bg-[var(--accent-soft)] text-[var(--accent)]";
|
|
128
|
+
|
|
129
|
+
export const feedRowStatePillOpenClass =
|
|
130
|
+
"border-[color:color-mix(in_srgb,var(--ink-soft)_34%,var(--line))] bg-transparent text-[var(--ink-soft)]";
|
|
119
131
|
|
|
120
|
-
|
|
132
|
+
/* Forms / inputs. */
|
|
133
|
+
export const searchFieldShellClass =
|
|
134
|
+
"flex items-center gap-2 rounded-full border border-transparent bg-[var(--bg-active)] px-4 py-2 transition-colors focus-within:border-[var(--accent)] focus-within:bg-[var(--bg)] focus-within:shadow-[0_0_0_1px_var(--accent)]";
|
|
121
135
|
|
|
122
|
-
export const
|
|
123
|
-
"
|
|
136
|
+
export const searchFieldIconClass =
|
|
137
|
+
"size-[18px] text-[var(--ink-soft)] shrink-0";
|
|
124
138
|
|
|
125
|
-
export const
|
|
139
|
+
export const searchFieldInputClass =
|
|
140
|
+
"min-w-0 flex-1 border-0 bg-transparent text-[14px] text-[var(--ink)] outline-none placeholder:text-[var(--ink-soft)]";
|
|
126
141
|
|
|
142
|
+
export const textFieldClass =
|
|
143
|
+
"w-full rounded-md border border-[var(--line)] bg-[var(--bg)] px-3 py-2 text-[14px] text-[var(--ink)] outline-none transition-colors duration-150 placeholder:text-[var(--ink-soft)] focus:border-[var(--accent)] focus:shadow-[0_0_0_1px_var(--accent)] disabled:cursor-default disabled:opacity-55";
|
|
144
|
+
|
|
145
|
+
export const textFieldWideClass = "w-full";
|
|
146
|
+
|
|
147
|
+
export const textFieldShortClass = "w-[120px]";
|
|
148
|
+
|
|
149
|
+
export const selectFieldClass = cx(textFieldClass, "appearance-none pr-8");
|
|
150
|
+
|
|
151
|
+
/* Buttons. */
|
|
152
|
+
export const primaryButtonClass =
|
|
153
|
+
"inline-flex items-center justify-center gap-1.5 rounded-full border-0 bg-[var(--accent)] px-4 py-1.5 text-[14px] font-bold text-white shadow-none transition-colors duration-150 hover:bg-[var(--accent-hover)] active:bg-[var(--accent-press)] disabled:cursor-default disabled:opacity-55";
|
|
154
|
+
|
|
155
|
+
export const primaryButtonLargeClass =
|
|
156
|
+
"inline-flex items-center justify-center gap-1.5 rounded-full border-0 bg-[var(--accent)] px-5 py-2.5 text-[15px] font-bold text-white shadow-none transition-colors duration-150 hover:bg-[var(--accent-hover)] active:bg-[var(--accent-press)] disabled:cursor-default disabled:opacity-55";
|
|
157
|
+
|
|
158
|
+
export const secondaryButtonClass =
|
|
159
|
+
"inline-flex items-center justify-center gap-1.5 rounded-full border border-[var(--line-strong)] bg-transparent px-4 py-1.5 text-[14px] font-bold text-[var(--ink)] transition-colors duration-150 hover:bg-[var(--bg-hover)] disabled:cursor-default disabled:opacity-55";
|
|
160
|
+
|
|
161
|
+
export const dangerButtonClass =
|
|
162
|
+
"inline-flex items-center justify-center gap-1.5 rounded-full border border-[var(--alert)] bg-transparent px-4 py-1.5 text-[14px] font-bold text-[var(--alert)] transition-colors duration-150 hover:bg-[var(--alert-soft)] disabled:cursor-default disabled:opacity-55";
|
|
163
|
+
|
|
164
|
+
export const actionButtonClass = primaryButtonClass;
|
|
165
|
+
|
|
166
|
+
/* Legacy segmented control retained for routes that still use it (compact pill). */
|
|
167
|
+
export const segmentedClass =
|
|
168
|
+
"inline-flex shrink-0 items-center gap-1 rounded-full border border-[var(--line)] bg-[var(--bg)] p-1";
|
|
169
|
+
|
|
170
|
+
export const segmentClass =
|
|
171
|
+
"rounded-full border-0 bg-transparent px-3 py-1 text-[13px] font-medium text-[var(--ink-soft)] capitalize transition-colors duration-150";
|
|
172
|
+
|
|
173
|
+
export const segmentActiveClass = "bg-[var(--bg-active)] text-[var(--ink)]";
|
|
174
|
+
|
|
175
|
+
/* Composer (reply textarea). */
|
|
127
176
|
export const composerShellClass =
|
|
128
|
-
"mt-2
|
|
177
|
+
"mt-2 flex flex-col gap-2 border-t border-[var(--line)] pt-3";
|
|
129
178
|
|
|
130
179
|
export const composerInputClass =
|
|
131
|
-
"
|
|
180
|
+
"min-h-[88px] w-full resize-y rounded-md border border-[var(--line)] bg-[var(--bg)] px-3 py-2.5 text-[15px] text-[var(--ink)] outline-none transition-colors duration-150 placeholder:text-[var(--ink-soft)] focus:border-[var(--accent)] focus:shadow-[0_0_0_1px_var(--accent)]";
|
|
132
181
|
|
|
133
|
-
export const composerBarClass =
|
|
134
|
-
"flex items-center justify-between gap-4 pt-3 max-[760px]:flex-col max-[760px]:items-start";
|
|
182
|
+
export const composerBarClass = "flex items-center justify-between gap-3";
|
|
135
183
|
|
|
136
|
-
|
|
137
|
-
|
|
184
|
+
/* Empty / loading. */
|
|
185
|
+
export const emptyStateClass = "px-6 py-10 text-center text-[var(--ink-soft)]";
|
|
138
186
|
|
|
139
|
-
export const
|
|
187
|
+
export const errorCopyClass =
|
|
188
|
+
"mx-4 my-3 whitespace-pre-wrap break-words rounded-md border border-[var(--alert)] bg-[var(--alert-soft)] px-3 py-2 text-[14px] text-[var(--alert)]";
|
|
140
189
|
|
|
141
|
-
export const
|
|
142
|
-
"
|
|
190
|
+
export const statusCopyClass =
|
|
191
|
+
"mx-4 my-3 rounded-md border border-[var(--line)] bg-[var(--bg-elevated)] px-3 py-2 text-[13px] text-[var(--ink-soft)]";
|
|
143
192
|
|
|
144
|
-
|
|
145
|
-
|
|
193
|
+
/* Avatars. */
|
|
194
|
+
export const avatarChipClass =
|
|
195
|
+
"avatar-chip inline-grid size-10 shrink-0 place-items-center overflow-hidden rounded-full text-[14px] font-bold text-white";
|
|
146
196
|
|
|
147
|
-
export const
|
|
148
|
-
"embedded-tweet-author flex flex-wrap items-center gap-2.5 text-[var(--ink-soft)]";
|
|
197
|
+
export const avatarChipLargeClass = "avatar-chip-large size-16 text-[18px]";
|
|
149
198
|
|
|
150
|
-
export const
|
|
151
|
-
"embedded-tweet-copy m-0 text-[0.98rem] leading-[1.55]";
|
|
199
|
+
export const avatarChipSmallClass = "avatar-chip-small size-8 text-[12px]";
|
|
152
200
|
|
|
153
|
-
|
|
154
|
-
|
|
201
|
+
/* Embedded tweet / link preview (rounded box, used inside feed row body). */
|
|
202
|
+
export const embeddedCardClass =
|
|
203
|
+
"mt-2 overflow-hidden rounded-2xl border border-[var(--line)] transition-colors duration-150 hover:bg-[var(--bg-hover)]";
|
|
155
204
|
|
|
156
|
-
export const
|
|
205
|
+
export const embeddedCardBodyClass = "flex flex-col gap-2 px-3 py-2.5";
|
|
157
206
|
|
|
158
|
-
export const
|
|
159
|
-
"dm-list-item grid w-full grid-cols-[auto_1fr_auto] gap-3 rounded-[18px] border-0 bg-transparent p-3.5 text-left transition duration-180 hover:bg-[color:color-mix(in_srgb,var(--panel-strong)_72%,transparent)]";
|
|
207
|
+
export const embeddedCardHeaderClass = "flex items-center gap-1.5 text-[14px]";
|
|
160
208
|
|
|
161
|
-
export const
|
|
162
|
-
"dm-list-item-active bg-[color:color-mix(in_srgb,var(--panel-strong)_86%,transparent)]";
|
|
209
|
+
export const embeddedCardNameClass = "font-bold text-[var(--ink)] truncate";
|
|
163
210
|
|
|
164
|
-
export const
|
|
211
|
+
export const embeddedCardHandleClass = "text-[var(--ink-soft)] truncate";
|
|
165
212
|
|
|
166
|
-
export const
|
|
167
|
-
"
|
|
213
|
+
export const embeddedCardLabelClass =
|
|
214
|
+
"text-[12px] uppercase tracking-wide text-[var(--ink-soft)]";
|
|
168
215
|
|
|
169
|
-
export const
|
|
170
|
-
"
|
|
216
|
+
export const embeddedCardCopyClass =
|
|
217
|
+
"whitespace-pre-wrap text-[14px] leading-[1.4] text-[var(--ink)] [overflow-wrap:anywhere]";
|
|
171
218
|
|
|
172
|
-
export const
|
|
173
|
-
"
|
|
219
|
+
export const linkPreviewCardClass =
|
|
220
|
+
"group/link-preview mt-2 flex min-w-0 overflow-hidden rounded-2xl border border-[var(--line)] bg-[var(--bg-card)] transition-colors duration-150 hover:bg-[var(--bg-hover)]";
|
|
174
221
|
|
|
175
|
-
export const
|
|
222
|
+
export const linkPreviewTitleClass =
|
|
223
|
+
"line-clamp-2 font-bold leading-tight text-[var(--ink)]";
|
|
176
224
|
|
|
177
|
-
export const
|
|
225
|
+
export const linkPreviewDescClass =
|
|
226
|
+
"line-clamp-2 text-[14px] leading-snug text-[var(--ink-soft)]";
|
|
178
227
|
|
|
179
|
-
export const
|
|
180
|
-
"
|
|
228
|
+
export const linkPreviewHostClass =
|
|
229
|
+
"text-[13px] text-[var(--ink-soft)] truncate";
|
|
181
230
|
|
|
182
|
-
|
|
183
|
-
|
|
231
|
+
/* DM grid. */
|
|
232
|
+
export const dmShellClass =
|
|
233
|
+
"grid h-[calc(100vh-56px)] min-h-[520px] grid-cols-1 min-[1100px]:grid-cols-[320px_minmax(0,1fr)]";
|
|
184
234
|
|
|
185
|
-
export const
|
|
235
|
+
export const dmListClass =
|
|
236
|
+
"flex flex-col overflow-y-auto border-r border-[var(--line)]";
|
|
237
|
+
|
|
238
|
+
export const dmListItemClass =
|
|
239
|
+
"flex w-full items-start gap-3 border-b border-[var(--line)] border-0 bg-transparent px-3 py-3 text-left transition-colors duration-150 hover:bg-[var(--bg-hover)]";
|
|
186
240
|
|
|
187
|
-
export const
|
|
241
|
+
export const dmListItemActiveClass = "bg-[var(--bg-active)]";
|
|
188
242
|
|
|
189
|
-
export const
|
|
243
|
+
export const dmListBodyClass = "flex min-w-0 flex-1 flex-col gap-1";
|
|
190
244
|
|
|
191
|
-
export const
|
|
245
|
+
export const dmListHeaderClass = "flex items-center justify-between gap-2";
|
|
192
246
|
|
|
193
|
-
export const
|
|
194
|
-
"
|
|
247
|
+
export const dmListNameClass =
|
|
248
|
+
"truncate text-[14px] font-bold text-[var(--ink)]";
|
|
195
249
|
|
|
196
|
-
export const
|
|
197
|
-
"rounded-[18px] rounded-bl-[6px] bg-[color:color-mix(in_srgb,var(--panel-strong)_86%,transparent)] px-4 py-3.5 leading-[1.55]";
|
|
250
|
+
export const dmListHandleClass = "truncate text-[13px] text-[var(--ink-soft)]";
|
|
198
251
|
|
|
199
|
-
export const
|
|
200
|
-
"rounded-bl-[18px] rounded-br-[6px] bg-[var(--accent-soft)]";
|
|
252
|
+
export const dmListPreviewClass = "truncate text-[13px] text-[var(--ink-soft)]";
|
|
201
253
|
|
|
202
|
-
export const
|
|
254
|
+
export const dmListTimestampClass =
|
|
255
|
+
"shrink-0 text-[12px] text-[var(--ink-soft)]";
|
|
203
256
|
|
|
204
|
-
export const
|
|
257
|
+
export const dmThreadClass = "flex min-w-0 flex-col";
|
|
205
258
|
|
|
206
|
-
export const
|
|
259
|
+
export const dmThreadHeaderClass =
|
|
260
|
+
"sticky top-0 z-10 flex items-center justify-between gap-3 border-b border-[var(--line)] bg-[color:color-mix(in_srgb,var(--bg)_85%,transparent)] px-4 py-3 backdrop-blur";
|
|
207
261
|
|
|
208
|
-
export const
|
|
262
|
+
export const dmThreadTitleClass = "flex items-center gap-3 min-w-0";
|
|
209
263
|
|
|
210
|
-
export const
|
|
264
|
+
export const dmThreadNameClass = "truncate text-[16px] font-bold";
|
|
211
265
|
|
|
212
|
-
export const
|
|
266
|
+
export const dmThreadSubtitleClass =
|
|
267
|
+
"truncate text-[13px] text-[var(--ink-soft)]";
|
|
213
268
|
|
|
214
|
-
export const
|
|
215
|
-
"
|
|
269
|
+
export const dmMessagesClass =
|
|
270
|
+
"flex flex-1 flex-col gap-2 overflow-y-auto px-4 py-4";
|
|
216
271
|
|
|
217
|
-
export const
|
|
218
|
-
"avatar-chip-large mb-3.5 size-16 rounded-[20px]";
|
|
272
|
+
export const dmMessageRowClass = "flex max-w-[78%] flex-col gap-1";
|
|
219
273
|
|
|
220
|
-
export const
|
|
274
|
+
export const dmMessageRowOutboundClass = "self-end items-end";
|
|
221
275
|
|
|
222
|
-
export const
|
|
276
|
+
export const dmMessageBubbleClass =
|
|
277
|
+
"rounded-2xl rounded-bl-md bg-[var(--bg-active)] px-3.5 py-2 text-[15px] leading-[1.4] text-[var(--ink)]";
|
|
278
|
+
|
|
279
|
+
export const dmMessageBubbleOutboundClass =
|
|
280
|
+
"rounded-bl-2xl rounded-br-md bg-[var(--accent)] text-white";
|
|
281
|
+
|
|
282
|
+
export const dmMessageMetaClass =
|
|
283
|
+
"flex items-center gap-2 text-[12px] text-[var(--ink-soft)]";
|
|
284
|
+
|
|
285
|
+
export const dmComposerShellClass =
|
|
286
|
+
"flex flex-col gap-2 border-t border-[var(--line)] px-4 py-3";
|
|
287
|
+
|
|
288
|
+
/* Profile hovercard. */
|
|
289
|
+
export const profilePreviewClass = "relative inline-flex";
|
|
223
290
|
|
|
224
291
|
export const profilePreviewTriggerClass =
|
|
225
292
|
"profile-preview-trigger inline-flex text-inherit";
|
|
226
293
|
|
|
227
294
|
export const profilePreviewCardClass =
|
|
228
|
-
"
|
|
295
|
+
"pointer-events-none absolute left-0 top-[calc(100%+8px)] z-30 grid w-[280px] translate-y-1 gap-2 rounded-2xl border border-[var(--line)] bg-[var(--bg-elevated)] p-3 opacity-0 shadow-[0_8px_28px_var(--shadow-strong)] transition-all duration-150 group-hover:pointer-events-auto group-hover:translate-y-0 group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:translate-y-0 group-focus-within:opacity-100";
|
|
229
296
|
|
|
230
|
-
export const profilePreviewHeaderClass =
|
|
231
|
-
|
|
297
|
+
export const profilePreviewHeaderClass = "flex items-center gap-3";
|
|
298
|
+
|
|
299
|
+
export const profilePreviewNameClass =
|
|
300
|
+
"block text-[15px] font-bold text-[var(--ink)]";
|
|
232
301
|
|
|
233
302
|
export const profilePreviewHandleClass =
|
|
234
|
-
"
|
|
303
|
+
"block text-[14px] text-[var(--ink-soft)]";
|
|
235
304
|
|
|
236
305
|
export const profilePreviewBioClass =
|
|
237
|
-
"
|
|
306
|
+
"block text-[14px] leading-[1.4] text-[var(--ink)]";
|
|
238
307
|
|
|
239
308
|
export const profilePreviewMetaClass =
|
|
240
|
-
"
|
|
309
|
+
"block text-[13px] text-[var(--ink-soft)]";
|
|
241
310
|
|
|
242
|
-
|
|
243
|
-
|
|
311
|
+
/* Rich tweet text. */
|
|
312
|
+
export const tweetLinkClass = "text-[var(--accent)] hover:underline";
|
|
244
313
|
|
|
245
|
-
export const tweetMentionClass =
|
|
246
|
-
"tweet-mention font-semibold text-[color:color-mix(in_srgb,var(--accent)_78%,var(--ink))]";
|
|
314
|
+
export const tweetMentionClass = "text-[var(--accent)] hover:underline";
|
|
247
315
|
|
|
248
316
|
export const tweetHashtagClass = tweetMentionClass;
|
|
249
317
|
|
|
318
|
+
/* Media grid (Twitter-style with rounded corners). */
|
|
250
319
|
export function tweetMediaGridClass(count: number) {
|
|
251
|
-
const
|
|
320
|
+
const capped = Math.min(count, 4);
|
|
321
|
+
const layout =
|
|
252
322
|
count === 1
|
|
253
323
|
? "grid-cols-1"
|
|
254
324
|
: count === 3
|
|
255
325
|
? "grid-cols-[1.3fr_1fr]"
|
|
256
|
-
:
|
|
326
|
+
: count === 4
|
|
327
|
+
? "grid-cols-2 grid-rows-2"
|
|
328
|
+
: "grid-cols-2";
|
|
257
329
|
|
|
258
330
|
return cx(
|
|
259
|
-
`tweet-media-grid tweet-media-grid-${
|
|
260
|
-
"
|
|
261
|
-
|
|
331
|
+
`tweet-media-grid tweet-media-grid-${String(capped)}`,
|
|
332
|
+
"mt-2 grid gap-0.5 overflow-hidden rounded-2xl border border-[var(--line)]",
|
|
333
|
+
layout,
|
|
262
334
|
);
|
|
263
335
|
}
|
|
264
336
|
|
|
265
337
|
export function tweetMediaTileClass(index: number, count: number) {
|
|
266
338
|
return cx(
|
|
267
|
-
"tweet-media-tile overflow-hidden
|
|
268
|
-
count ===
|
|
339
|
+
"tweet-media-tile relative block overflow-hidden border-0 bg-[var(--bg-active)] p-0 text-left",
|
|
340
|
+
count === 1 && "aspect-[16/10]",
|
|
341
|
+
count === 2 && "aspect-square",
|
|
342
|
+
count === 3 && index === 0 && "row-span-2 aspect-[3/4]",
|
|
343
|
+
count === 3 && index !== 0 && "aspect-square",
|
|
344
|
+
count === 4 && "aspect-square",
|
|
269
345
|
);
|
|
270
346
|
}
|
|
347
|
+
|
|
348
|
+
/* Inbox / triage card. */
|
|
349
|
+
export const inboxAnalysisClass =
|
|
350
|
+
"mt-2 rounded-xl border border-[var(--line)] bg-[var(--bg-hover)] px-3 py-2.5 text-[14px] text-[var(--ink-soft)] [&_p]:mt-1 [&_p]:mb-0";
|
|
351
|
+
|
|
352
|
+
/* Block list item. */
|
|
353
|
+
export const blockRowClass =
|
|
354
|
+
"flex items-start gap-3 border-b border-[var(--line)] px-4 py-3 transition-colors duration-150 hover:bg-[var(--bg-hover)]";
|
|
355
|
+
|
|
356
|
+
export const blockRowBodyClass = "flex min-w-0 flex-1 flex-col gap-1";
|
|
357
|
+
|
|
358
|
+
/* Aside (optional right column for stats). Hidden on small screens. */
|
|
359
|
+
export const asideShellClass =
|
|
360
|
+
"sticky top-0 hidden h-screen w-[300px] shrink-0 flex-col gap-4 overflow-y-auto px-4 py-4 min-[1180px]:flex";
|
|
361
|
+
|
|
362
|
+
export const asideCardClass =
|
|
363
|
+
"rounded-2xl border border-[var(--line)] bg-[var(--bg-elevated)] p-4";
|
|
364
|
+
|
|
365
|
+
export const asideTitleClass = "text-[15px] font-bold text-[var(--ink)]";
|
|
366
|
+
|
|
367
|
+
export const asideStatRowClass =
|
|
368
|
+
"flex items-baseline justify-between border-t border-[var(--line)] py-2 first:border-t-0 first:pt-0";
|
|
369
|
+
|
|
370
|
+
export const asideStatTermClass = "text-[13px] text-[var(--ink-soft)]";
|
|
371
|
+
|
|
372
|
+
export const asideStatValueClass = "text-[14px] font-bold text-[var(--ink)]";
|
|
373
|
+
|
|
374
|
+
/* Misc legacy aliases kept for compatibility while routes/components migrate. */
|
|
375
|
+
export const eyebrowClass =
|
|
376
|
+
"text-[12px] font-semibold uppercase tracking-wide text-[var(--ink-soft)]";
|
|
377
|
+
|
|
378
|
+
export const brandMarkClass = sidebarBrandTitleClass;
|
|
379
|
+
|
|
380
|
+
export const navClass = sidebarShellClass;
|
|
381
|
+
|
|
382
|
+
export const navLinksClass = sidebarNavClass;
|
|
383
|
+
|
|
384
|
+
export const heroShellClass = "flex flex-col gap-1 px-4 pt-3 pb-2";
|
|
385
|
+
|
|
386
|
+
export const heroShellDmClass = "";
|
|
387
|
+
|
|
388
|
+
export const heroControlsClass = "flex flex-wrap items-center gap-2 px-4 py-2";
|
|
389
|
+
|
|
390
|
+
export const heroControlsDmClass = "";
|
|
391
|
+
|
|
392
|
+
export const heroControlsBlocksClass = "";
|
|
393
|
+
|
|
394
|
+
export const heroTitleClass = pageTitleClass;
|
|
395
|
+
|
|
396
|
+
export const heroCopyClass = pageSubtitleClass;
|
|
397
|
+
|
|
398
|
+
export const surfaceCardClass =
|
|
399
|
+
"rounded-2xl border border-[var(--line)] bg-[var(--bg-elevated)]";
|
|
400
|
+
|
|
401
|
+
export const contentCardClass = feedRowClass;
|
|
402
|
+
|
|
403
|
+
export const cardHeaderClass = "flex items-start justify-between gap-3";
|
|
404
|
+
|
|
405
|
+
export const cardFooterClass = "flex items-center justify-between gap-3";
|
|
406
|
+
|
|
407
|
+
export const identityBlockClass = "flex items-start gap-3";
|
|
408
|
+
|
|
409
|
+
export const identityRowClass =
|
|
410
|
+
"flex flex-wrap items-center gap-1.5 text-[15px] text-[var(--ink-soft)]";
|
|
411
|
+
|
|
412
|
+
export const metaRowClass =
|
|
413
|
+
"flex flex-wrap items-center gap-1.5 text-[13px] text-[var(--ink-soft)]";
|
|
414
|
+
|
|
415
|
+
export const mutedDotClass =
|
|
416
|
+
"inline-block size-1 shrink-0 rounded-full bg-[var(--ink-soft)]";
|
|
417
|
+
|
|
418
|
+
export const metaStackClass = "flex flex-col items-end gap-1";
|
|
419
|
+
|
|
420
|
+
export const pillClass =
|
|
421
|
+
"inline-flex items-center rounded-full px-2 py-0.5 text-[12px] font-semibold";
|
|
422
|
+
|
|
423
|
+
export const pillSoftClass = "bg-[var(--accent-soft)] text-[var(--accent)]";
|
|
424
|
+
|
|
425
|
+
export const pillAlertClass = "bg-[var(--alert-soft)] text-[var(--alert)]";
|
|
426
|
+
|
|
427
|
+
export const timestampClass = "text-[13px] text-[var(--ink-soft)]";
|
|
428
|
+
|
|
429
|
+
export const bodyCopyClass = feedRowTextClass;
|
|
430
|
+
|
|
431
|
+
export const inboxTitleClass = "text-[16px] font-bold text-[var(--ink)]";
|
|
432
|
+
|
|
433
|
+
export const metricRowClass =
|
|
434
|
+
"flex flex-wrap items-center gap-3 text-[13px] text-[var(--ink-soft)]";
|
|
435
|
+
|
|
436
|
+
export const actionRowClass = "flex items-center gap-2";
|
|
437
|
+
|
|
438
|
+
export const feedPageClass = "flex w-full min-w-0 flex-col";
|
|
439
|
+
|
|
440
|
+
export const timelineLaneClass = feedClass;
|
|
441
|
+
|
|
442
|
+
export const inboxLaneClass = feedClass;
|
|
443
|
+
|
|
444
|
+
export const dmPageClass = "flex w-full min-w-0 flex-col";
|
|
445
|
+
|
|
446
|
+
export const stackGridClass = "flex flex-col";
|
|
447
|
+
|
|
448
|
+
/* Embedded tweet legacy aliases. */
|
|
449
|
+
export const embeddedTweetCardClass = embeddedCardClass;
|
|
450
|
+
|
|
451
|
+
export const embeddedTweetLabelClass = embeddedCardLabelClass;
|
|
452
|
+
|
|
453
|
+
export const embeddedTweetHeaderClass = embeddedCardHeaderClass;
|
|
454
|
+
|
|
455
|
+
export const embeddedTweetAuthorClass = "flex flex-wrap items-center gap-1.5";
|
|
456
|
+
|
|
457
|
+
export const embeddedTweetCopyClass = embeddedCardCopyClass;
|
|
458
|
+
|
|
459
|
+
/* DM legacy aliases (still used by DmWorkspace until rewrite). */
|
|
460
|
+
export const dmGridClass = dmShellClass;
|
|
461
|
+
|
|
462
|
+
export const dmListItemNonActive = "";
|
|
463
|
+
|
|
464
|
+
export const dmListCopyClass = dmListBodyClass;
|
|
465
|
+
|
|
466
|
+
export const dmPreviewTextClass = dmListPreviewClass;
|
|
467
|
+
|
|
468
|
+
export const threadShellClass = dmThreadClass;
|
|
469
|
+
|
|
470
|
+
export const threadHeaderClass = dmThreadHeaderClass;
|
|
471
|
+
|
|
472
|
+
export const threadTitleClass = dmThreadNameClass;
|
|
473
|
+
|
|
474
|
+
export const threadSubtitleClass = dmThreadSubtitleClass;
|
|
475
|
+
|
|
476
|
+
export const threadBioClass = "text-[14px] text-[var(--ink-soft)]";
|
|
477
|
+
|
|
478
|
+
export const threadDetailColumnClass =
|
|
479
|
+
"flex flex-col gap-3 rounded-2xl border border-[var(--line)] bg-[var(--bg-elevated)] p-4";
|
|
480
|
+
|
|
481
|
+
export const threadDetailHeaderClass = "flex items-center gap-3";
|
|
482
|
+
|
|
483
|
+
export const messageStackClass = dmMessagesClass;
|
|
484
|
+
|
|
485
|
+
export const messageRowClass = dmMessageRowClass;
|
|
486
|
+
|
|
487
|
+
export const messageRowOutboundClass = dmMessageRowOutboundClass;
|
|
488
|
+
|
|
489
|
+
export const messageMetaClass = dmMessageMetaClass;
|
|
490
|
+
|
|
491
|
+
export const messageBubbleClass = dmMessageBubbleClass;
|
|
492
|
+
|
|
493
|
+
export const messageBubbleOutboundClass = dmMessageBubbleOutboundClass;
|
|
494
|
+
|
|
495
|
+
export const contextHandleClass = "text-[14px] text-[var(--ink-soft)]";
|
|
496
|
+
|
|
497
|
+
export const contextBioClass = "text-[14px] leading-[1.4] text-[var(--ink)]";
|
|
498
|
+
|
|
499
|
+
export const contextStatsClass = "flex flex-col gap-0";
|
|
500
|
+
|
|
501
|
+
export const contextStatRowClass =
|
|
502
|
+
"flex items-baseline justify-between border-t border-[var(--line)] py-2 first:border-t-0 first:pt-0";
|
|
503
|
+
|
|
504
|
+
export const contextStatTermClass = "text-[13px] text-[var(--ink-soft)]";
|
|
505
|
+
|
|
506
|
+
export const contextStatValueClass = "text-[14px] font-bold text-[var(--ink)]";
|