@atmo-dev/kit 0.8.0 → 0.10.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.
Files changed (94) hide show
  1. package/README.md +27 -368
  2. package/dist/components/AccountMenu.svelte +102 -0
  3. package/dist/components/AccountMenu.svelte.d.ts +19 -0
  4. package/dist/components/Avatar.svelte +1 -1
  5. package/dist/components/Badge.svelte +3 -3
  6. package/dist/components/Button.svelte +5 -4
  7. package/dist/components/Checkbox.svelte +31 -1
  8. package/dist/{events → components}/DatePicker.svelte +3 -3
  9. package/dist/components/ExternalEmbed.svelte +10 -7
  10. package/dist/components/GrainAlbumPicker.svelte +18 -9
  11. package/dist/components/GrainAlbumView.svelte +15 -6
  12. package/dist/components/Heading.svelte +1 -1
  13. package/dist/components/IconRail.svelte +11 -9
  14. package/dist/components/InlineEditor.svelte +3 -3
  15. package/dist/components/InsetImage.svelte +1 -1
  16. package/dist/components/Label.svelte +1 -1
  17. package/dist/components/LinkCard.svelte +7 -5
  18. package/dist/components/Modal.svelte +3 -3
  19. package/dist/components/NestedComments.svelte +10 -7
  20. package/dist/components/PhotoGrid.svelte +1 -1
  21. package/dist/components/Popover.svelte +1 -1
  22. package/dist/components/PostComposer.svelte +2 -2
  23. package/dist/components/PostContent.svelte +12 -10
  24. package/dist/components/PostVideo.svelte +2 -2
  25. package/dist/components/RichText.svelte +19 -7
  26. package/dist/components/Select.svelte +5 -2
  27. package/dist/components/Select.svelte.d.ts +2 -0
  28. package/dist/components/SocialClientPicker.svelte +1 -1
  29. package/dist/components/StreamplacePicker.svelte +1 -1
  30. package/dist/components/Subheading.svelte +4 -2
  31. package/dist/components/Surface.svelte +3 -2
  32. package/dist/components/Tabs.svelte +2 -2
  33. package/dist/{events → components}/TimePicker.svelte +10 -7
  34. package/dist/{events → components}/TimezonePicker.svelte +15 -7
  35. package/dist/components/ToggleGroup.svelte +1 -1
  36. package/dist/components/ToggleGroupItem.svelte +1 -1
  37. package/dist/components/UserProfile.svelte +16 -9
  38. package/dist/components/WorkspaceSidebar.svelte +1 -1
  39. package/dist/components/legal/LegalDocument.svelte +9 -3
  40. package/dist/components/legal/LegalFooter.svelte +2 -2
  41. package/dist/components/legal/links.js +2 -9
  42. package/dist/components/login/ActorSearchField.svelte +10 -6
  43. package/dist/components/login/AtmosphereApps.svelte +1 -1
  44. package/dist/components/login/LoginForm.svelte +15 -9
  45. package/dist/components/login/PermissionFields.svelte +9 -2
  46. package/dist/components/login/RecentAccounts.svelte +5 -3
  47. package/dist/events/EventAttendees.svelte +9 -3
  48. package/dist/events/EventCard.svelte +7 -5
  49. package/dist/events/EventComments.svelte +3 -3
  50. package/dist/events/EventEditor.svelte +20 -9
  51. package/dist/events/EventRsvp.svelte +10 -6
  52. package/dist/events/EventView.svelte +21 -7
  53. package/dist/events/StarterPackButton.svelte +3 -3
  54. package/dist/events/ThemePicker.svelte +7 -7
  55. package/dist/events/ThumbnailPresets.svelte +1 -1
  56. package/dist/events/VodPlayer.svelte +1 -1
  57. package/dist/events/editor/LinksSection.svelte +7 -3
  58. package/dist/events/editor/LocationSection.svelte +1 -1
  59. package/dist/events/editor/RecurringModal.svelte +6 -4
  60. package/dist/events/editor/ThemeSection.svelte +3 -1
  61. package/dist/events/event-view/EventDateBlock.svelte +3 -3
  62. package/dist/events/event-view/EventHostedBy.svelte +6 -2
  63. package/dist/events/event-view/EventLinksList.svelte +13 -4
  64. package/dist/events/event-view/EventLocationBlock.svelte +9 -5
  65. package/dist/events/event-view/EventLocationMap.svelte +4 -2
  66. package/dist/events/event-view/ExternalRsvpNotice.svelte +20 -16
  67. package/dist/events/event-view/locations.js +1 -11
  68. package/dist/events/schedule/ConferenceTimetable.svelte +7 -7
  69. package/dist/events/schedule/DaySchedule.svelte +2 -2
  70. package/dist/events/schedule/ScheduleEventCell.svelte +2 -2
  71. package/dist/events/themes/Matrix.svelte +12 -6
  72. package/dist/events.d.ts +5 -5
  73. package/dist/events.js +6 -5
  74. package/dist/index.d.ts +7 -0
  75. package/dist/index.js +7 -0
  76. package/dist/richtext/document.d.ts +2 -1
  77. package/dist/richtext/document.js +3 -13
  78. package/dist/social/external-embeds.js +1 -1
  79. package/dist/social/mention-suggestions.js +4 -4
  80. package/dist/social/profile.js +1 -1
  81. package/dist/styles.d.ts +2 -2
  82. package/dist/styles.js +2 -2
  83. package/dist/theme.css +23 -44
  84. package/dist/theme.js +4 -12
  85. package/dist/urls.d.ts +9 -0
  86. package/dist/urls.js +39 -0
  87. package/package.json +5 -1
  88. /package/dist/{events → components}/DatePicker.svelte.d.ts +0 -0
  89. /package/dist/{events → components}/DateTimePicker.svelte +0 -0
  90. /package/dist/{events → components}/DateTimePicker.svelte.d.ts +0 -0
  91. /package/dist/{events → components}/ImageDropper.svelte +0 -0
  92. /package/dist/{events → components}/ImageDropper.svelte.d.ts +0 -0
  93. /package/dist/{events → components}/TimePicker.svelte.d.ts +0 -0
  94. /package/dist/{events → components}/TimezonePicker.svelte.d.ts +0 -0
@@ -3,7 +3,7 @@
3
3
  import { DatePicker } from 'bits-ui';
4
4
  import { CalendarDate, type DateValue } from '@internationalized/date';
5
5
  import { untrack } from 'svelte';
6
- import { Icon } from '../index.js';
6
+ import Icon from './Icon.svelte';
7
7
  import { ChevronLeft, ChevronRight } from '@sketchyicons/svelte';
8
8
 
9
9
  let {
@@ -124,7 +124,7 @@
124
124
  >
125
125
  <DatePicker.Trigger
126
126
  aria-label={`${label}: ${displayText || 'Select date'}`}
127
- class="flex h-11 min-w-0 flex-1 cursor-pointer items-center rounded-ui border border-line bg-recess px-2 text-xs text-ink shadow-(--ui-recess-shadow) transition-colors hover:border-control-hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus min-[360px]:px-3 min-[360px]:text-sm"
127
+ class="flex h-11 min-w-0 flex-1 cursor-pointer items-center rounded-ui border border-base-300 bg-[color-mix(in_srgb,var(--base-100)_75%,var(--base-200))] px-2 text-xs text-base-950 shadow-[inset_0_2px_4px_color-mix(in_srgb,var(--base-950)_8%,transparent),inset_0_5px_12px_color-mix(in_srgb,var(--base-950)_2%,transparent)] transition-colors hover:border-base-400 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent-700 min-[360px]:px-3 min-[360px]:text-sm dark:border-[color-mix(in_srgb,var(--base-800)_80%,var(--base-950))] dark:bg-base-950 dark:text-base-50 dark:shadow-[inset_0_2px_4px_#0006,inset_0_5px_12px_#0002] dark:hover:border-base-700 dark:focus-visible:outline-accent-300"
128
128
  >
129
129
  <span class="truncate select-none">
130
130
  {#if displayText}
@@ -138,7 +138,7 @@
138
138
 
139
139
  <DatePicker.Content
140
140
  sideOffset={8}
141
- class="z-50 max-w-[calc(100vw-2rem)] rounded-panel border border-line bg-page p-3 text-ink"
141
+ class="z-50 max-w-[calc(100vw-2rem)] rounded-panel border border-base-300 bg-base-50 p-3 text-base-950 dark:border-[color-mix(in_srgb,var(--base-800)_80%,var(--base-950))] dark:bg-base-900 dark:text-base-50"
142
142
  >
143
143
  <DatePicker.Calendar>
144
144
  {#snippet children({ months, weekdays })}
@@ -6,7 +6,7 @@
6
6
  type ExternalEmbedProvider,
7
7
  type ExternalPostEmbed
8
8
  } from '../social/external-embeds.js';
9
- import { safeWebUrl } from '../social/richtext.js';
9
+ import { safeHttpUrl as safeWebUrl } from '../urls.js';
10
10
  import { focus } from '../styles.js';
11
11
  import Icon from './Icon.svelte';
12
12
  import LinkCard from './LinkCard.svelte';
@@ -62,7 +62,9 @@
62
62
  </script>
63
63
 
64
64
  {#if match && failed !== match}
65
- <div class="relative overflow-hidden rounded-ui border border-line">
65
+ <div
66
+ class="relative overflow-hidden rounded-ui border border-base-300 dark:border-[color-mix(in_srgb,var(--base-800)_80%,var(--base-950))]"
67
+ >
66
68
  {#if active || (autoplay && match.player.type === 'video' && paused === match)}
67
69
  {#if match.player.type === 'iframe'}
68
70
  <iframe
@@ -105,7 +107,7 @@
105
107
  {/if}
106
108
  {:else if autoplay}
107
109
  <div
108
- class="relative max-h-[32rem] w-full bg-surface"
110
+ class="relative max-h-[32rem] w-full bg-base-50 dark:bg-base-900"
109
111
  style:aspect-ratio={match.player.aspectRatio ?? 16 / 9}
110
112
  >
111
113
  {#if safeWebUrl(embed.image)}<img
@@ -121,7 +123,7 @@
121
123
  type="button"
122
124
  aria-label={`Play ${label} on ${match.provider.label}`}
123
125
  onclick={play}
124
- class={`relative flex max-h-[32rem] w-full items-center justify-center overflow-hidden bg-surface text-ink ${focus}`}
126
+ class={`relative flex max-h-[32rem] w-full items-center justify-center overflow-hidden bg-base-50 text-base-950 dark:bg-base-900 dark:text-base-50 ${focus}`}
125
127
  style:aspect-ratio={match.player.aspectRatio ?? 16 / 9}
126
128
  >
127
129
  {#if safeWebUrl(embed.image)}<img
@@ -131,7 +133,7 @@
131
133
  class="absolute inset-0 size-full object-cover"
132
134
  />{/if}
133
135
  <span
134
- class="relative inline-flex items-center gap-2 rounded-full border border-line bg-surface px-4 py-2 text-sm font-medium"
136
+ class="relative inline-flex items-center gap-2 rounded-full border border-base-300 bg-base-50 px-4 py-2 text-sm font-medium dark:border-[color-mix(in_srgb,var(--base-800)_80%,var(--base-950))] dark:bg-base-900"
135
137
  >
136
138
  <Icon img={Play} size={18} />Play · {match.provider.label}
137
139
  </span>
@@ -154,7 +156,7 @@
154
156
  href={safeWebUrl(embed.uri)}
155
157
  target="_blank"
156
158
  rel="noopener noreferrer"
157
- class={`min-w-0 truncate rounded-ui text-muted hover:text-action ${focus}`}
159
+ class={`min-w-0 truncate rounded-ui text-base-600 hover:text-accent-700 dark:text-base-400 dark:hover:text-accent-300 ${focus}`}
158
160
  >{embed.title || `Open on ${match.provider.label}`}</a
159
161
  >
160
162
  {#if active}<button
@@ -162,7 +164,8 @@
162
164
  bind:this={stopButton}
163
165
  onclick={stop}
164
166
  aria-label={`Stop ${match.provider.label} embed`}
165
- class={`shrink-0 rounded-ui text-muted hover:text-action ${focus}`}>Stop</button
167
+ class={`shrink-0 rounded-ui text-base-600 hover:text-accent-700 dark:text-base-400 dark:hover:text-accent-300 ${focus}`}
168
+ >Stop</button
166
169
  >{/if}
167
170
  </div>{/if}
168
171
  </div>
@@ -162,10 +162,12 @@
162
162
  />{/if}
163
163
  <div class="min-w-0 flex-1">
164
164
  <p class="truncate font-medium">{preview.title}</p>
165
- <p class="text-sm break-words text-muted">
165
+ <p class="text-sm break-words text-base-600 dark:text-base-400">
166
166
  @{preview.creator.handle} · {preview.photoCount} photos
167
167
  </p>
168
- {#if preview.contentWarning}<p class="text-xs text-muted">Content warning</p>{/if}
168
+ {#if preview.contentWarning}<p class="text-xs text-base-600 dark:text-base-400">
169
+ Content warning
170
+ </p>{/if}
169
171
  </div>
170
172
  <Button disabled={busy || added(preview)} onclick={() => preview && choose(preview)}
171
173
  >{added(preview) ? 'Added' : 'Add album'}</Button
@@ -178,7 +180,7 @@
178
180
  {#each albums as album (album.uri)}
179
181
  <button
180
182
  type="button"
181
- class="flex w-full items-center gap-3 rounded-lg p-2 text-left hover:bg-recess disabled:opacity-50"
183
+ class="flex w-full items-center gap-3 rounded-lg p-2 text-left hover:bg-[color-mix(in_srgb,var(--base-100)_75%,var(--base-200))] disabled:opacity-50 dark:hover:bg-base-950"
182
184
  disabled={busy || added(album)}
183
185
  onclick={() => choose(album)}
184
186
  aria-label={`${added(album) ? 'Added' : 'Add'} ${album.title}`}
@@ -189,24 +191,31 @@
189
191
  loading="lazy"
190
192
  class="size-14 shrink-0 rounded-md object-cover"
191
193
  />{:else}<span
192
- class="flex size-14 shrink-0 items-center justify-center rounded-md bg-recess"
194
+ class="flex size-14 shrink-0 items-center justify-center rounded-md bg-[color-mix(in_srgb,var(--base-100)_75%,var(--base-200))] dark:bg-base-950"
193
195
  ><Icon img={Camera} size={20} /></span
194
196
  >{/if}
195
197
  <span class="min-w-0 flex-1"
196
198
  ><span class="block truncate font-medium">{album.title}</span><span
197
- class="text-sm text-muted"
199
+ class="text-sm text-base-600 dark:text-base-400"
198
200
  >{album.photoCount} photos{album.contentWarning ? ' · Content warning' : ''}</span
199
201
  ></span
200
202
  >
201
- {#if added(album)}<span class="text-xs text-muted">Added</span>{/if}
203
+ {#if added(album)}<span class="text-xs text-base-600 dark:text-base-400">Added</span>{/if}
202
204
  </button>
203
205
  {/each}
204
206
  </div>
205
- {#if listing}<p class="mt-3 text-sm text-muted" role="status">
207
+ {#if listing}<p class="mt-3 text-sm text-base-600 dark:text-base-400" role="status">
206
208
  Loading albums…
207
- </p>{:else if listError}<p class="mt-3 text-sm text-muted" role="status">{listError}</p>
209
+ </p>{:else if listError}<p
210
+ class="mt-3 text-sm text-base-600 dark:text-base-400"
211
+ role="status"
212
+ >
213
+ {listError}
214
+ </p>
208
215
  <Button variant="ghost" size="sm" onclick={() => loadPage(!!cursor)}>Try again</Button
209
- >{:else if !albums.length}<p class="text-sm text-muted">No albums yet.</p>{/if}
216
+ >{:else if !albums.length}<p class="text-sm text-base-600 dark:text-base-400">
217
+ No albums yet.
218
+ </p>{/if}
210
219
  {#if cursor && !listError}<Button
211
220
  variant="ghost"
212
221
  class="mt-3"
@@ -67,7 +67,7 @@
67
67
  href={`https://grain.social/profile/${encodeURIComponent(gallery.creator.did)}`}
68
68
  target="_blank"
69
69
  rel="noopener noreferrer"
70
- class="text-sm break-all text-muted hover:text-action"
70
+ class="text-sm break-all text-base-600 hover:text-accent-700 dark:text-base-400 dark:hover:text-accent-300"
71
71
  >{gallery.creator.displayName || gallery.creator.handle} · @{gallery.creator.handle}</a
72
72
  >{/if}
73
73
  </div>
@@ -75,20 +75,29 @@
75
75
  href={gallery?.url || ref?.url}
76
76
  target="_blank"
77
77
  rel="noopener noreferrer"
78
- class="text-sm text-action hover:underline">Open on Grain</a
78
+ class="text-sm text-accent-700 hover:underline dark:text-accent-300">Open on Grain</a
79
79
  >{/if}
80
80
  </div>
81
- {#if loading}<p class="py-6 text-sm text-muted" role="status">Loading photos…</p>
82
- {:else if failed || !loadAlbum}<p class="mb-2 text-sm text-muted" role="status">
81
+ {#if loading}<p class="py-6 text-sm text-base-600 dark:text-base-400" role="status">
82
+ Loading photos…
83
+ </p>
84
+ {:else if failed || !loadAlbum}<p
85
+ class="mb-2 text-sm text-base-600 dark:text-base-400"
86
+ role="status"
87
+ >
83
88
  Photos are unavailable. You can still open the album on Grain.
84
89
  </p>
85
90
  {#if loadAlbum}<Button variant="ghost" size="sm" onclick={() => retry++}>Try again</Button>{/if}
86
91
  {:else if gallery}
87
- {#if gallery.contentWarning && !revealed}<div class="rounded-lg border border-line p-4">
92
+ {#if gallery.contentWarning && !revealed}<div
93
+ class="rounded-lg border border-base-300 p-4 dark:border-[color-mix(in_srgb,var(--base-800)_80%,var(--base-950))]"
94
+ >
88
95
  <p class="mb-3 text-sm">This album has a content warning.</p>
89
96
  <Button variant="secondary" size="sm" onclick={() => (revealed = true)}>Show photos</Button>
90
97
  </div>
91
- {:else if !photos.length}<p class="py-4 text-sm text-muted">No photos in this album yet.</p>
98
+ {:else if !photos.length}<p class="py-4 text-sm text-base-600 dark:text-base-400">
99
+ No photos in this album yet.
100
+ </p>
92
101
  {:else}<PhotoGrid {photos} label={gallery.title} />{/if}
93
102
  {/if}
94
103
  </section>
@@ -18,7 +18,7 @@
18
18
  {...rest}
19
19
  bind:this={ref}
20
20
  class={cn(
21
- 'm-0 text-[clamp(2rem,4vw,2.75rem)] leading-[1.15] font-medium tracking-[-.035em] text-ink',
21
+ 'm-0 text-[clamp(2rem,4vw,2.75rem)] leading-[1.15] font-medium tracking-[-.035em] text-base-950 dark:text-base-50',
22
22
  className
23
23
  )}>{@render children?.()}</svelte:element
24
24
  >
@@ -87,15 +87,17 @@
87
87
  class={cn(
88
88
  'min-h-11 w-full justify-start gap-3 px-3 py-3 text-sm',
89
89
  (!item.variant || item.variant === 'ghost') &&
90
- 'aria-[current=page]:bg-recess aria-[current=page]:text-action',
90
+ 'aria-[current=page]:bg-[color-mix(in_srgb,var(--base-100)_75%,var(--base-200))] aria-[current=page]:text-accent-700 dark:aria-[current=page]:bg-base-950 dark:aria-[current=page]:text-accent-300',
91
91
  item.primary && 'mt-3',
92
- item.primary && (!item.variant || item.variant === 'ghost') && 'text-action'
92
+ item.primary &&
93
+ (!item.variant || item.variant === 'ghost') &&
94
+ 'text-accent-700 dark:text-accent-300'
93
95
  )}
94
96
  >
95
97
  <Icon img={item.icon} size={22} />
96
98
  <span class="min-w-0 flex-1 text-left">{item.label}</span>
97
99
  {#if item.badge}
98
- <span class="text-xs text-action">
100
+ <span class="text-xs text-accent-700 dark:text-accent-300">
99
101
  {#if typeof item.badge === 'number'}{item.badge}<span class="sr-only"> unread</span>
100
102
  {:else}<span class="sr-only">Unread</span><span aria-hidden="true">•</span>{/if}
101
103
  </span>
@@ -115,7 +117,7 @@
115
117
  data-slot="icon-rail"
116
118
  data-backed={backed}
117
119
  class={cn(
118
- 'fixed top-[var(--atmo-nav-top,0px)] bottom-0 left-0 z-40 flex w-18 flex-col items-center bg-transparent pt-6 pb-5 max-sm:top-auto max-sm:right-0 max-sm:h-[calc(62px+env(safe-area-inset-bottom))] max-sm:w-full max-sm:flex-row max-sm:justify-around max-sm:bg-page max-sm:px-2 max-sm:pt-1.5 max-sm:pb-[calc(6px+env(safe-area-inset-bottom))]',
120
+ 'fixed top-[var(--atmo-nav-top,0px)] bottom-0 left-0 z-40 flex w-18 flex-col items-center bg-transparent pt-6 pb-5 max-sm:top-auto max-sm:right-0 max-sm:h-[calc(62px+env(safe-area-inset-bottom))] max-sm:w-full max-sm:flex-row max-sm:justify-around max-sm:bg-base-50 max-sm:px-2 max-sm:pt-1.5 max-sm:pb-[calc(6px+env(safe-area-inset-bottom))] dark:max-sm:bg-base-925',
119
121
  mobile === 'drawer' && 'max-sm:hidden',
120
122
  className
121
123
  )}
@@ -139,18 +141,18 @@
139
141
  class={cn(
140
142
  'relative size-11 rounded-(--atmo-radius-button) max-sm:size-10',
141
143
  (!item.variant || item.variant === 'ghost') &&
142
- 'text-muted hover:bg-recess/55 aria-[current=page]:text-action',
144
+ 'text-base-600 hover:bg-[color-mix(in_srgb,var(--base-100)_75%,var(--base-200))]/55 aria-[current=page]:text-accent-700 dark:text-base-400 dark:hover:bg-base-950/55 dark:aria-[current=page]:text-accent-300',
143
145
  item.primary && 'mt-2 max-sm:mt-0',
144
146
  item.primary &&
145
147
  (!item.variant || item.variant === 'ghost') &&
146
- 'text-action hover:text-action',
148
+ 'text-accent-700 hover:text-accent-700 dark:text-accent-300 dark:hover:text-accent-300',
147
149
  backed &&
148
150
  (!item.variant || item.variant === 'ghost') &&
149
- 'bg-recess/45 hover:bg-recess/75'
151
+ 'bg-[color-mix(in_srgb,var(--base-100)_75%,var(--base-200))]/45 hover:bg-[color-mix(in_srgb,var(--base-100)_75%,var(--base-200))]/75 dark:bg-base-950/45 dark:hover:bg-base-950/75'
150
152
  )}
151
153
  >
152
154
  <Icon img={item.icon} size={22} />{#if item.badge}<span
153
- class="absolute top-2 right-1.5 size-1.5 rounded-full bg-action"
155
+ class="absolute top-2 right-1.5 size-1.5 rounded-full bg-accent-700 dark:bg-accent-300"
154
156
  aria-hidden="true"
155
157
  ></span>{/if}
156
158
  </Button>
@@ -160,7 +162,7 @@
160
162
  side="right"
161
163
  sideOffset={10}
162
164
  collisionPadding={12}
163
- class="z-60 max-w-[calc(100vw-2rem)] rounded-lg border border-line bg-field px-2.5 py-1.5 font-sans text-xs text-ink"
165
+ class="z-60 max-w-[calc(100vw-2rem)] rounded-lg border border-base-300 bg-[color-mix(in_srgb,var(--base-100)_75%,var(--base-200))] px-2.5 py-1.5 font-sans text-xs text-base-950 dark:border-[color-mix(in_srgb,var(--base-800)_80%,var(--base-950))] dark:bg-base-975 dark:text-base-50"
164
166
  >{item.label}</Tooltip.Content
165
167
  ></Tooltip.Root
166
168
  >
@@ -49,7 +49,7 @@
49
49
  ...(searchActors
50
50
  ? [
51
51
  Mention.configure({
52
- HTMLAttributes: { class: 'text-action' },
52
+ HTMLAttributes: { class: 'text-accent-700 dark:text-accent-300' },
53
53
  suggestion: mentionSuggestions(searchActors, () => host)
54
54
  })
55
55
  ]
@@ -66,7 +66,7 @@
66
66
  ? { 'aria-autocomplete': 'list', 'aria-expanded': 'false', 'aria-haspopup': 'listbox' }
67
67
  : { 'aria-multiline': String(!singleLine) }),
68
68
  class:
69
- 'min-h-[1.5em] outline-none whitespace-pre-wrap break-words [&_p.is-editor-empty:first-child]:before:content-[attr(data-placeholder)] [&_p.is-editor-empty:first-child]:before:text-muted [&_p.is-editor-empty:first-child]:before:float-left [&_p.is-editor-empty:first-child]:before:pointer-events-none [&_p.is-editor-empty:first-child]:before:h-0'
69
+ 'min-h-[1.5em] outline-none whitespace-pre-wrap break-words [&_p.is-editor-empty:first-child]:before:content-[attr(data-placeholder)] [&_p.is-editor-empty:first-child]:before:text-base-600 dark:[&_p.is-editor-empty:first-child]:before:text-base-400 [&_p.is-editor-empty:first-child]:before:float-left [&_p.is-editor-empty:first-child]:before:pointer-events-none [&_p.is-editor-empty:first-child]:before:h-0'
70
70
  },
71
71
  handleKeyDown: (view, event) =>
72
72
  singleLine &&
@@ -111,7 +111,7 @@
111
111
  <div
112
112
  bind:this={host}
113
113
  class={cn(
114
- 'relative min-w-0 rounded-sm focus-within:outline-2 focus-within:outline-offset-4 focus-within:outline-focus',
114
+ 'relative min-w-0 rounded-sm focus-within:outline-2 focus-within:outline-offset-4 focus-within:outline-accent-700 dark:focus-within:outline-accent-300',
115
115
  className
116
116
  )}
117
117
  data-slot="inline-editor"
@@ -18,7 +18,7 @@
18
18
 
19
19
  <figure
20
20
  class={cn(
21
- 'relative m-0 aspect-(--ak-image-ratio) overflow-hidden rounded-ui border border-line after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:shadow-[inset_0_2px_7px_#0005]',
21
+ 'relative m-0 aspect-(--ak-image-ratio) overflow-hidden rounded-ui border border-base-300 after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:shadow-[inset_0_2px_7px_#0005] dark:border-[color-mix(in_srgb,var(--base-800)_80%,var(--base-950))]',
22
22
  className
23
23
  )}
24
24
  {style}
@@ -13,7 +13,7 @@
13
13
  {...rest}
14
14
  bind:this={ref}
15
15
  class={cn(
16
- 'inline-flex items-center gap-2.5 text-[.8125rem] leading-normal font-medium text-ink',
16
+ 'inline-flex items-center gap-2.5 text-[.8125rem] leading-normal font-medium text-base-950 dark:text-base-50',
17
17
  className
18
18
  )}>{@render children?.()}</label
19
19
  >
@@ -2,7 +2,7 @@
2
2
  import type { HTMLAnchorAttributes } from 'svelte/elements';
3
3
  import { cn } from '../utils.js';
4
4
  import { focus } from '../styles.js';
5
- import { safeWebUrl } from '../social/richtext.js';
5
+ import { safeHttpUrl as safeWebUrl } from '../urls.js';
6
6
  let {
7
7
  href,
8
8
  meta,
@@ -20,7 +20,7 @@
20
20
  target="_blank"
21
21
  rel="noopener noreferrer"
22
22
  class={cn(
23
- 'block overflow-hidden rounded-ui border border-line bg-surface text-ink no-underline hover:border-control-line',
23
+ 'block overflow-hidden rounded-ui border border-base-300 bg-base-50 text-base-950 no-underline hover:border-base-300 dark:border-[color-mix(in_srgb,var(--base-800)_80%,var(--base-950))] dark:bg-base-900 dark:text-base-50 dark:hover:border-base-800',
24
24
  focus,
25
25
  className
26
26
  )}
@@ -32,15 +32,17 @@
32
32
  class="aspect-[1.91/1] w-full object-cover"
33
33
  loading="lazy"
34
34
  /><span
35
- class="pointer-events-none absolute inset-0 [box-shadow:inset_0_0_0_1px_var(--ui-shadow),inset_0_2px_5px_var(--ui-shadow)]"
35
+ class="pointer-events-none absolute inset-0 shadow-[inset_0_0_0_1px_color-mix(in_srgb,var(--base-950)_8%,transparent),inset_0_2px_5px_color-mix(in_srgb,var(--base-950)_8%,transparent)] dark:shadow-[inset_0_0_0_1px_#0006,inset_0_2px_5px_#0006]"
36
36
  ></span>
37
37
  </div>{/if}
38
38
  <div class="p-3">
39
39
  <strong class="block text-sm font-medium">{meta.title || href}</strong>
40
- {#if meta.description}<p class="mt-1 line-clamp-2 text-xs leading-relaxed text-muted">
40
+ {#if meta.description}<p
41
+ class="mt-1 line-clamp-2 text-xs leading-relaxed text-base-600 dark:text-base-400"
42
+ >
41
43
  {meta.description}
42
44
  </p>{/if}
43
- <span class="mt-1 block truncate text-[.6875rem] text-muted"
45
+ <span class="mt-1 block truncate text-[.6875rem] text-base-600 dark:text-base-400"
44
46
  >{href.replace(/^https?:\/\//, '')}</span
45
47
  >
46
48
  </div>
@@ -49,7 +49,7 @@
49
49
  escapeKeydownBehavior={dismissible ? 'close' : 'ignore'}
50
50
  style={[inherited, style].filter(Boolean).join(';')}
51
51
  class={cn(
52
- 'fixed top-1/2 left-1/2 z-50 max-h-[calc(100dvh-3rem)] w-[calc(100%-2rem)] max-w-lg -translate-x-1/2 -translate-y-1/2 overflow-y-auto overscroll-contain rounded-panel border border-line bg-page p-6 font-sans text-ink outline-none sm:p-7',
52
+ 'fixed top-1/2 left-1/2 z-50 max-h-[calc(100dvh-3rem)] w-[calc(100%-2rem)] max-w-lg -translate-x-1/2 -translate-y-1/2 overflow-y-auto overscroll-contain rounded-panel border border-base-300 bg-base-50 p-6 font-sans text-base-950 outline-none sm:p-7 dark:border-[color-mix(in_srgb,var(--base-800)_80%,var(--base-950))] dark:bg-base-925 dark:text-base-50',
53
53
  className
54
54
  )}
55
55
  onOpenAutoFocus={(event) => {
@@ -74,7 +74,7 @@
74
74
  {title}
75
75
  </h2>{/snippet}
76
76
  </Primitive.Title>{#if description}<Primitive.Description
77
- class="mt-2.5 text-[.8125rem] leading-relaxed text-muted"
77
+ class="mt-2.5 text-[.8125rem] leading-relaxed text-base-600 dark:text-base-400"
78
78
  >{description}</Primitive.Description
79
79
  >{/if}
80
80
  </div>
@@ -82,7 +82,7 @@
82
82
  disabled={!dismissible}
83
83
  aria-label="Close dialog"
84
84
  class={cn(
85
- '-mt-1 -mr-2 inline-grid size-9 shrink-0 cursor-pointer place-items-center rounded-ui text-muted hover:bg-recess hover:text-ink disabled:pointer-events-none disabled:opacity-40',
85
+ '-mt-1 -mr-2 inline-grid size-9 shrink-0 cursor-pointer place-items-center rounded-ui text-base-600 hover:bg-[color-mix(in_srgb,var(--base-100)_75%,var(--base-200))] hover:text-base-950 disabled:pointer-events-none disabled:opacity-40 dark:text-base-400 dark:hover:bg-base-950 dark:hover:text-base-50',
86
86
  focus
87
87
  )}><Icon img={X} size={19} /></Primitive.Close
88
88
  >
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { PostData } from '../social/posts.js';
3
3
  import type { ExternalEmbedProvider } from '../social/external-embeds.js';
4
- import { safeWebUrl } from '../social/richtext.js';
4
+ import { safeHttpUrl as safeWebUrl } from '../urls.js';
5
5
  import { socialPostUrl, socialProfileUrl } from '../social/urls.js';
6
6
  import type { PreferredSocialClient } from '../social/preferred-client.js';
7
7
  import { Heart, MessageCircle, Repeat2 } from '@sketchyicons/svelte';
@@ -39,11 +39,12 @@
39
39
  {href}
40
40
  target="_blank"
41
41
  rel="noopener noreferrer"
42
- class="font-medium text-ink hover:underline">{comment.author.name}</a
43
- ><span class="truncate text-muted">@{comment.author.handle}</span
42
+ class="font-medium text-base-950 hover:underline dark:text-base-50"
43
+ >{comment.author.name}</a
44
+ ><span class="truncate text-base-600 dark:text-base-400">@{comment.author.handle}</span
44
45
  >{#if comment.createdAt && !Number.isNaN(Date.parse(comment.createdAt))}<time
45
46
  datetime={comment.createdAt}
46
- class="text-[.6875rem] text-muted"
47
+ class="text-[.6875rem] text-base-600 dark:text-base-400"
47
48
  >{new Date(comment.createdAt).toLocaleDateString(undefined, {
48
49
  month: 'short',
49
50
  day: 'numeric'
@@ -51,21 +52,23 @@
51
52
  >{/if}
52
53
  </div>
53
54
  <PostContent post={comment} {socialClient} {embedProviders} />
54
- <div class="mt-1 flex items-center gap-5 text-xs text-muted">
55
+ <div class="mt-1 flex items-center gap-5 text-xs text-base-600 dark:text-base-400">
55
56
  {#each [{ icon: MessageCircle, count: comment.replyCount ?? 0, singular: 'reply', plural: 'replies' }, { icon: Repeat2, count: comment.repostCount ?? 0, singular: 'repost', plural: 'reposts' }, { icon: Heart, count: comment.likeCount ?? 0, singular: 'like', plural: 'likes' }] as action (action.singular)}
56
57
  <a
57
58
  {href}
58
59
  target="_blank"
59
60
  rel="noopener noreferrer"
60
61
  aria-label={`${action.count} ${action.count === 1 ? action.singular : action.plural}`}
61
- class={`inline-flex min-h-8 items-center gap-1.5 rounded-ui tabular-nums transition-colors hover:text-action ${focus}`}
62
+ class={`inline-flex min-h-8 items-center gap-1.5 rounded-ui tabular-nums transition-colors hover:text-accent-700 dark:hover:text-accent-300 ${focus}`}
62
63
  ><Icon img={action.icon} size={16} /><span>{action.count}</span></a
63
64
  >
64
65
  {/each}
65
66
  </div>
66
67
  </div>
67
68
  </div>
68
- {#if comment.replies?.length}<div class="mt-4 border-l border-line pl-3 sm:ml-4 sm:pl-6">
69
+ {#if comment.replies?.length}<div
70
+ class="mt-4 border-l border-base-300 pl-3 sm:ml-4 sm:pl-6 dark:border-[color-mix(in_srgb,var(--base-800)_80%,var(--base-950))]"
71
+ >
69
72
  <NestedComments comments={comment.replies} {socialClient} {embedProviders} />
70
73
  </div>{/if}
71
74
  </article>
@@ -21,7 +21,7 @@
21
21
  {#if photo.contentWarning && !revealed.has(i)}
22
22
  <button
23
23
  type="button"
24
- class="mb-2 block w-full break-inside-avoid-column rounded-lg border border-line p-3 text-sm text-muted"
24
+ class="mb-2 block w-full break-inside-avoid-column rounded-lg border border-base-300 p-3 text-sm text-base-600 dark:border-[color-mix(in_srgb,var(--base-800)_80%,var(--base-950))] dark:text-base-400"
25
25
  style:aspect-ratio={photo.width && photo.height ? `${photo.width} / ${photo.height}` : '1'}
26
26
  onclick={() => (revealed = new Set([...revealed, i]))}
27
27
  >Content warning · Show photo {i + 1}</button
@@ -39,7 +39,7 @@
39
39
  {sideOffset}
40
40
  style={[inherited, style].filter(Boolean).join(';')}
41
41
  class={cn(
42
- 'z-50 max-w-[calc(100vw-2rem)] rounded-panel border border-line bg-page p-4 font-sans text-ink outline-none',
42
+ 'z-50 max-w-[calc(100vw-2rem)] rounded-panel border border-base-300 bg-base-50 p-4 font-sans text-base-950 outline-none dark:border-[color-mix(in_srgb,var(--base-800)_80%,var(--base-950))] dark:bg-base-900 dark:text-base-50',
43
43
  className
44
44
  )}
45
45
  >
@@ -33,8 +33,8 @@
33
33
  />
34
34
  <p
35
35
  class={length > maxLength
36
- ? 'red mt-2 text-right text-xs text-action'
37
- : 'mt-2 text-right text-xs text-muted'}
36
+ ? 'red mt-2 text-right text-xs text-accent-700 dark:text-accent-300'
37
+ : 'mt-2 text-right text-xs text-base-600 dark:text-base-400'}
38
38
  aria-live="polite"
39
39
  >
40
40
  {length} / {maxLength}
@@ -5,7 +5,7 @@
5
5
  import { socialPostUrl } from '../social/urls.js';
6
6
  import { focus } from '../styles.js';
7
7
  import Icon from './Icon.svelte';
8
- import { safeWebUrl } from '../social/richtext.js';
8
+ import { safeHttpUrl as safeWebUrl } from '../urls.js';
9
9
  import RichText from './RichText.svelte';
10
10
  import type { ExternalEmbedProvider } from '../social/external-embeds.js';
11
11
  import ExternalEmbed from './ExternalEmbed.svelte';
@@ -34,7 +34,7 @@
34
34
  <div class="mt-3">
35
35
  {#if embed.type === 'external'}<ExternalEmbed {embed} providers={embedProviders} />
36
36
  {:else if embed.type === 'images'}<div
37
- class="grid gap-1 overflow-hidden rounded-ui border border-line"
37
+ class="grid gap-1 overflow-hidden rounded-ui border border-base-300 dark:border-[color-mix(in_srgb,var(--base-800)_80%,var(--base-950))]"
38
38
  class:grid-cols-2={embed.images.length > 1}
39
39
  >
40
40
  {#each embed.images as image, imageIndex (imageIndex)}<a
@@ -64,7 +64,7 @@
64
64
  href={safeWebUrl(embed.href)}
65
65
  target="_blank"
66
66
  rel="noopener noreferrer"
67
- class={`flex items-start gap-3 rounded-ui border border-line p-3 text-ink no-underline hover:border-control-line ${focus}`}
67
+ class={`flex items-start gap-3 rounded-ui border border-base-300 p-3 text-base-950 no-underline hover:border-base-300 dark:border-[color-mix(in_srgb,var(--base-800)_80%,var(--base-950))] dark:text-base-50 dark:hover:border-base-800 ${focus}`}
68
68
  >
69
69
  {#if safeWebUrl(embed.image)}<img
70
70
  src={safeWebUrl(embed.image)}
@@ -72,30 +72,32 @@
72
72
  loading="lazy"
73
73
  class="size-10 shrink-0 rounded-ui object-cover"
74
74
  />{:else}<span
75
- class="flex size-10 shrink-0 items-center justify-center rounded-ui bg-surface text-muted"
75
+ class="flex size-10 shrink-0 items-center justify-center rounded-ui bg-base-50 text-base-600 dark:bg-base-900 dark:text-base-400"
76
76
  ><Icon img={kind.icon} size={22} /></span
77
77
  >{/if}
78
78
  <div class="min-w-0">
79
- <p class="truncate text-xs text-muted">
79
+ <p class="truncate text-xs text-base-600 dark:text-base-400">
80
80
  {kind.label}{embed.handle ? ` · @${embed.handle}` : ''}
81
81
  </p>
82
82
  <p class="mt-0.5 text-sm font-medium break-words">{embed.title}</p>
83
83
  {#if embed.description}<p
84
- class="mt-1 line-clamp-3 text-xs leading-relaxed break-words text-muted"
84
+ class="mt-1 line-clamp-3 text-xs leading-relaxed break-words text-base-600 dark:text-base-400"
85
85
  >
86
86
  {embed.description}
87
87
  </p>{/if}
88
88
  </div>
89
89
  </a>
90
- {:else if embed.type === 'quote'}<div class="rounded-ui border border-line p-3">
90
+ {:else if embed.type === 'quote'}<div
91
+ class="rounded-ui border border-base-300 p-3 dark:border-[color-mix(in_srgb,var(--base-800)_80%,var(--base-950))]"
92
+ >
91
93
  {#if embed.post}<a
92
94
  href={socialPostUrl(embed.post.uri, socialClient) ?? safeWebUrl(embed.post.href)}
93
95
  target="_blank"
94
96
  rel="noopener noreferrer"
95
- class="mb-2 block text-xs font-medium text-muted"
97
+ class="mb-2 block text-xs font-medium text-base-600 dark:text-base-400"
96
98
  >{embed.post.author.name} · @{embed.post.author.handle}</a
97
99
  ><PostContent post={embed.post} {socialClient} {embedProviders} />{:else}<p
98
- class="text-xs text-muted"
100
+ class="text-xs text-base-600 dark:text-base-400"
99
101
  >
100
102
  {embed.reason === 'blocked'
101
103
  ? 'Post blocked'
@@ -105,7 +107,7 @@
105
107
  </p>{/if}
106
108
  </div>
107
109
  {:else if embed.type === 'unsupported'}<p
108
- class="rounded-ui border border-line p-3 text-xs text-muted"
110
+ class="rounded-ui border border-base-300 p-3 text-xs text-base-600 dark:border-[color-mix(in_srgb,var(--base-800)_80%,var(--base-950))] dark:text-base-400"
109
111
  >
110
112
  Embed unavailable
111
113
  </p>{/if}
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { safeWebUrl } from '../social/richtext.js';
2
+ import { safeHttpUrl as safeWebUrl } from '../urls.js';
3
3
  import Icon from './Icon.svelte';
4
4
  import { ArrowUpRight } from '@sketchyicons/svelte';
5
5
  let {
@@ -69,6 +69,6 @@
69
69
  href={safeWebUrl(href)}
70
70
  target="_blank"
71
71
  rel="noopener noreferrer"
72
- class="mt-1 inline-flex items-center gap-1 text-xs text-action hover:underline"
72
+ class="mt-1 inline-flex items-center gap-1 text-xs text-accent-700 hover:underline dark:text-accent-300"
73
73
  >Watch on Bluesky <Icon img={ArrowUpRight} size={14} /></a
74
74
  >
@@ -61,8 +61,9 @@
61
61
  }
62
62
 
63
63
  const linkClass =
64
- 'text-action underline decoration-action/40 underline-offset-2 hover:decoration-action';
65
- const codeClass = 'rounded bg-recess px-1 py-0.5 font-mono text-[0.875em]';
64
+ 'text-accent-700 dark:text-accent-300 underline decoration-accent-700/40 dark:decoration-accent-300/40 underline-offset-2 hover:decoration-accent-700 dark:hover:decoration-accent-300';
65
+ const codeClass =
66
+ 'rounded bg-[color-mix(in_srgb,var(--base-100)_75%,var(--base-200))] dark:bg-base-950 px-1 py-0.5 font-mono text-[0.875em]';
66
67
  </script>
67
68
 
68
69
  {#snippet inlines(children: unknown, inLink: boolean, depth: number)}
@@ -108,7 +109,9 @@
108
109
  : 'text-base'} leading-snug font-semibold"
109
110
  >{@render inlines(node.children, false, depth + 1)}</svelte:element
110
111
  >
111
- {:else if node.type === 'blockquote'}<blockquote class="space-y-3 border-l-2 border-line pl-4">
112
+ {:else if node.type === 'blockquote'}<blockquote
113
+ class="space-y-3 border-l-2 border-base-300 pl-4 dark:border-[color-mix(in_srgb,var(--base-800)_80%,var(--base-950))]"
114
+ >
112
115
  {@render blocks(node.children, depth + 1)}
113
116
  </blockquote>
114
117
  {:else if node.type === 'list'}<svelte:element
@@ -123,20 +126,29 @@
123
126
  {/each}
124
127
  </svelte:element>
125
128
  {:else if node.type === 'codeBlock'}<pre
126
- class="overflow-x-auto rounded-md bg-recess p-3 font-mono text-[0.875em]"><code
129
+ class="overflow-x-auto rounded-md bg-[color-mix(in_srgb,var(--base-100)_75%,var(--base-200))] p-3 font-mono text-[0.875em] dark:bg-base-950"><code
127
130
  >{string(node.value)}</code
128
131
  ></pre>
129
- {:else if node.type === 'thematicBreak'}<hr class="border-line" />
132
+ {:else if node.type === 'thematicBreak'}<hr
133
+ class="border-base-300 dark:border-[color-mix(in_srgb,var(--base-800)_80%,var(--base-950))]"
134
+ />
130
135
  {:else}<p>{inlineText(node, depth)}</p>{/if}
131
136
  {/each}
132
137
  {/snippet}
133
138
 
134
139
  {#if doc.markdown}
135
- <div class={cn('space-y-3 leading-relaxed wrap-anywhere text-ink', className)}>
140
+ <div
141
+ class={cn('space-y-3 leading-relaxed wrap-anywhere text-base-950 dark:text-base-50', className)}
142
+ >
136
143
  {@render blocks(doc.children, 0)}
137
144
  </div>
138
145
  {:else}
139
- <p class={cn('leading-relaxed wrap-anywhere whitespace-pre-wrap text-ink', className)}>
146
+ <p
147
+ class={cn(
148
+ 'leading-relaxed wrap-anywhere whitespace-pre-wrap text-base-950 dark:text-base-50',
149
+ className
150
+ )}
151
+ >
140
152
  {#each nodes(doc.children) as block, index (index)}{@render inlines(
141
153
  block.children,
142
154
  false,