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