+5
-1
local-infra/pds.env
+5
-1
local-infra/pds.env
···
11
11
PDS_DID_PLC_URL=http://plc:8080
12
12
PDS_HOSTNAME=pds.dev.grain.social
13
13
PDS_EMAIL_SMTP_URL=smtp://maildev:1025
14
-
PDS_EMAIL_FROM_ADDRESS=admin@grain.social
14
+
PDS_EMAIL_FROM_ADDRESS=support@grain.social
15
15
PDS_SERVICE_NAME=Grain Social
16
16
PDS_INVITE_REQUIRED=0
17
17
PDS_OAUTH_PROVIDER_NAME=Grain Social
···
21
21
PDS_PRIMARY_COLOR=#00a6f4
22
22
PDS_PRIMARY_COLOR_CONTRAST=#fff
23
23
PDS_PRIMARY_COLOR_HUE=#fff
24
+
PDS_TERMS_OF_SERVICE_URL = 'http://localhost:8080/support/terms'
25
+
PDS_PRIVACY_POLICY_URL = 'http://localhost:8080/support/privacy'
26
+
PDS_SUPPORT_URL= 'http://localhost:8080/support'
27
+
PDS_CONTACT_EMAIL_ADDRESS = 'support@grain.social'
+1
-1
services/pds/.env.example
+1
-1
services/pds/.env.example
+4
services/pds/fly.toml
+4
services/pds/fly.toml
···
29
29
PDS_PRIMARY_COLOR = '#00a6f4'
30
30
PDS_PRIMARY_COLOR_CONTRAST = '#fff'
31
31
PDS_PRIMARY_COLOR_HUE = '#fff'
32
+
PDS_TERMS_OF_SERVICE_URL = 'https://grain.social/support/terms'
33
+
PDS_PRIVACY_POLICY_URL = 'https://grain.social/support/privacy'
34
+
PDS_SUPPORT_URL= 'https://grain.social/support'
35
+
PDS_CONTACT_EMAIL_ADDRESS = 'support@grain.social'
32
36
33
37
[[mounts]]
34
38
source = 'pdsdata'
+37
-8
src/components/LoginPage.tsx
+37
-8
src/components/LoginPage.tsx
···
17
17
infoText="e.g., user.bsky.social, user.grain.social, example.com, https://pds.example.com"
18
18
infoClass="text-white text-sm! bg-zinc-950/70 p-4 font-mono"
19
19
/>
20
-
<div class="absolute bottom-2 right-2 text-white text-sm">
21
-
Photo by{" "}
22
-
<a
23
-
href={profileLink("chadtmiller.com")}
24
-
class="hover:underline font-semibold"
25
-
>
26
-
@chadtmiller.com
27
-
</a>
20
+
<div class="absolute bottom-2 left-2 right-2 flex flex-col sm:flex-row justify-between items-start sm:items-end text-white text-sm gap-1 sm:gap-0">
21
+
<div class="flex flex-col sm:flex-row">
22
+
<span>
23
+
© 2025 Grain Social. All rights reserved.
24
+
</span>
25
+
<span class="flex flex-row items-center flex-wrap">
26
+
<a
27
+
href="/support/terms"
28
+
class="underline hover:no-underline ml-0 sm:ml-2 mt-1 sm:mt-0"
29
+
>
30
+
Terms
31
+
</a>
32
+
<span class="mx-1">|</span>
33
+
<a
34
+
href="/support/privacy"
35
+
class="underline hover:no-underline ml-0 sm:ml-1 mt-1 sm:mt-0"
36
+
>
37
+
Privacy
38
+
</a>
39
+
<span class="mx-1">|</span>
40
+
<a
41
+
href="/support/copyright"
42
+
class="underline hover:no-underline ml-0 sm:ml-1 mt-1 sm:mt-0"
43
+
>
44
+
Copyright
45
+
</a>
46
+
</span>
47
+
</div>
48
+
<div>
49
+
Photo by{" "}
50
+
<a
51
+
href={profileLink("chadtmiller.com")}
52
+
class="underline hover:no-underline"
53
+
>
54
+
@chadtmiller.com
55
+
</a>
56
+
</div>
28
57
</div>
29
58
</div>
30
59
);
+326
src/legal.tsx
+326
src/legal.tsx
···
1
+
import { ComponentChildren } from "preact";
2
+
3
+
type SectionProps = {
4
+
title: string;
5
+
children: ComponentChildren;
6
+
};
7
+
8
+
const Section = ({ title, children }: SectionProps) => (
9
+
<section className="mb-8">
10
+
<h2 className="text-xl font-bold mb-2 text-zinc-800 dark:text-zinc-100">
11
+
{title}
12
+
</h2>
13
+
<div className="space-y-2 text-zinc-700 dark:text-zinc-300 text-sm">
14
+
{children}
15
+
</div>
16
+
</section>
17
+
);
18
+
19
+
export function Terms() {
20
+
return (
21
+
<div className="px-4 py-4">
22
+
<nav className="mb-4 text-sm text-zinc-500 dark:text-zinc-300">
23
+
<a href="/support" className="text-sky-500 hover:underline">
24
+
support
25
+
</a>{" "}
26
+
<span className="mx-1">></span>{" "}
27
+
<span className="text-zinc-700 dark:text-zinc-100">terms</span>
28
+
</nav>
29
+
<h1 className="text-3xl font-bold mb-6 text-zinc-900 dark:text-white">
30
+
Terms and Conditions
31
+
</h1>
32
+
33
+
<div className="mb-6 text-sm text-zinc-900 dark:text-white">
34
+
Last Updated: June 3, 2025
35
+
</div>
36
+
37
+
<Section title="Overview">
38
+
<p>
39
+
Grain is a photo sharing app built on the{" "}
40
+
<a
41
+
href="https://atproto.com/"
42
+
className="text-sky-500 hover:underline"
43
+
target="_blank"
44
+
rel="noopener noreferrer"
45
+
>
46
+
AT Protocol
47
+
</a>
48
+
. All data, including photos, galleries, favorites, and metadata, is
49
+
public and stored on the AT Protocol network. Users can upload photos,
50
+
create and favorite galleries, and view non-location EXIF metadata.
51
+
</p>
52
+
<p>
53
+
Grain is an open source project. These Terms apply to your use of the
54
+
hosted version at{" "}
55
+
<code>grain.social</code>, not to self-hosted instances or forks of
56
+
the source code.
57
+
</p>
58
+
</Section>
59
+
60
+
<Section title="Account and Data Ownership">
61
+
<p>
62
+
Grain uses the AT Protocol, so users retain full control over their
63
+
data. We are an independent project and not affiliated with Bluesky or
64
+
the AT Protocol.
65
+
</p>
66
+
<p>
67
+
If you use a <code>grain.social</code>{" "}
68
+
handle, your data may be stored on our own self-hosted{" "}
69
+
<a
70
+
href="https://atproto.com/guides/glossary#pds-personal-data-server"
71
+
className="text-sky-500 hover:underline"
72
+
target="_blank"
73
+
rel="noopener noreferrer"
74
+
>
75
+
PDS (Personal Data Server)
76
+
</a>{" "}
77
+
in accordance with protocol standards.
78
+
</p>
79
+
</Section>
80
+
81
+
<Section title="Content">
82
+
<p>
83
+
You are responsible for any content you share. Do not upload content
84
+
you do not have rights to. All uploads are publicly visible and cannot
85
+
currently be set as private.
86
+
</p>
87
+
</Section>
88
+
89
+
<Section title="Analytics">
90
+
<p>
91
+
We use{" "}
92
+
<a
93
+
href="https://www.goatcounter.com/"
94
+
className="text-sky-500 hover:underline"
95
+
>
96
+
Goatcounter
97
+
</a>{" "}
98
+
for basic analytics. No personal data is collected, tracked, or sold.
99
+
</p>
100
+
</Section>
101
+
102
+
<Section title="Prohibited Conduct">
103
+
<p>
104
+
Do not upload illegal content, harass users, impersonate others, or
105
+
attempt to disrupt the network.
106
+
</p>
107
+
</Section>
108
+
109
+
<Section title="Disclaimers">
110
+
<p>
111
+
Grain is provided "as is." We do not guarantee uptime, data retention,
112
+
or uninterrupted access.
113
+
</p>
114
+
</Section>
115
+
116
+
<Section title="Termination">
117
+
<p>
118
+
We reserve the right to suspend or terminate your access to Grain at
119
+
any time, without prior notice, for conduct that we believe violates
120
+
these Terms, our community standards, or is harmful to other users or
121
+
the AT Protocol network. Terminated accounts may lose access to
122
+
uploaded content unless retained through the protocol’s data
123
+
persistence mechanisms.
124
+
</p>
125
+
</Section>
126
+
127
+
<Section title="Changes">
128
+
<p>
129
+
We may update these terms periodically. Continued use means acceptance
130
+
of any changes.
131
+
</p>
132
+
</Section>
133
+
134
+
<Section title="Contact">
135
+
<p>
136
+
For any questions about these Terms, your account, or issues with the
137
+
app, you can contact us at{" "}
138
+
<a
139
+
href="mailto:support@grain.social"
140
+
className="text-sky-500 hover:underline"
141
+
>
142
+
support@grain.social
143
+
</a>
144
+
.
145
+
</p>
146
+
</Section>
147
+
</div>
148
+
);
149
+
}
150
+
151
+
export function PrivacyPolicy() {
152
+
return (
153
+
<div className="px-4 py-4">
154
+
<nav className="mb-4 text-sm text-zinc-500 dark:text-zinc-300">
155
+
<a href="/support" className="text-sky-500 hover:underline">
156
+
support
157
+
</a>{" "}
158
+
<span className="mx-1">></span>{" "}
159
+
<span className="text-zinc-700 dark:text-zinc-100">privacy</span>
160
+
</nav>
161
+
<h1 className="text-3xl font-bold mb-6 text-zinc-900 dark:text-white">
162
+
Privacy Policy
163
+
</h1>
164
+
165
+
<div className="mb-6 text-sm text-zinc-900 dark:text-white">
166
+
Last Updated: June 3, 2025
167
+
</div>
168
+
169
+
<Section title="Data Storage and Access">
170
+
<p>
171
+
Your data is stored on the AT Protocol. If you use a{" "}
172
+
<code>grain.social</code>{" "}
173
+
handle, it may be stored on our PDS. We do not store or access data
174
+
beyond the protocol’s standard behavior.
175
+
</p>
176
+
</Section>
177
+
178
+
<Section title="Public Data">
179
+
<p>
180
+
All content on Grain is public. Private uploads are not currently
181
+
supported.
182
+
</p>
183
+
</Section>
184
+
185
+
{
186
+
/* Coming soon */
187
+
/* <Section title="EXIF Metadata">
188
+
<p>
189
+
We optionally collect and display EXIF metadata (excluding location)
190
+
from your photos. At upload time, you can choose whether to allow this
191
+
metadata to be collected. The metadata is stored according to standard
192
+
AT Protocol storage mechanisms and is not retained outside the
193
+
protocol or used for other purposes.
194
+
</p>
195
+
<p>
196
+
You can learn more about the types of metadata commonly embedded in
197
+
photos at{" "}
198
+
<a
199
+
href="https://exiv2.org/tags.html"
200
+
className="text-sky-500 hover:underline"
201
+
target="_blank"
202
+
rel="noopener noreferrer"
203
+
>
204
+
exiv2.org
205
+
</a>
206
+
.
207
+
</p>
208
+
</Section> */
209
+
}
210
+
211
+
<Section title="Analytics">
212
+
<p>
213
+
We use{" "}
214
+
<a
215
+
href="https://www.goatcounter.com/"
216
+
className="text-sky-500 hover:underline"
217
+
>
218
+
Goatcounter
219
+
</a>{" "}
220
+
for analytics. It is privacy-focused: no IP addresses, cookies, or
221
+
personal data is collected.
222
+
</p>
223
+
</Section>
224
+
225
+
<Section title="No Ads or Tracking">
226
+
<p>We do not serve ads, use third-party tracking, or sell user data.</p>
227
+
</Section>
228
+
229
+
<Section title="Children’s Privacy">
230
+
<p>Grain is not intended for users under 13 years of age.</p>
231
+
</Section>
232
+
233
+
<Section title="Changes to Policy">
234
+
<p>
235
+
This policy may be updated. Material changes will be communicated via
236
+
the app or site.
237
+
</p>
238
+
</Section>
239
+
240
+
<Section title="Contact">
241
+
<p>
242
+
For privacy questions, contact us at{" "}
243
+
<a
244
+
href="mailto:support@grain.social"
245
+
className="text-sky-500 hover:underline"
246
+
>
247
+
support@grain.social
248
+
</a>
249
+
.
250
+
</p>
251
+
</Section>
252
+
</div>
253
+
);
254
+
}
255
+
256
+
export function CopyrightPolicy() {
257
+
return (
258
+
<div className="px-4 py-4">
259
+
<nav className="mb-4 text-sm text-zinc-500 dark:text-zinc-300">
260
+
<a href="/support" className="text-sky-500 hover:underline">
261
+
support
262
+
</a>{" "}
263
+
<span className="mx-1">></span>{" "}
264
+
<span className="text-zinc-700 dark:text-zinc-100">copyright</span>
265
+
</nav>
266
+
<h1 className="text-3xl font-bold mb-6 text-zinc-900 dark:text-white">
267
+
Copyright Policy
268
+
</h1>
269
+
270
+
<div className="mb-6 text-sm text-zinc-900 dark:text-white">
271
+
Last Updated: June 3, 2025
272
+
</div>
273
+
274
+
<Section title="Copyright Infringement">
275
+
<p>
276
+
Grain respects the intellectual property rights of others and expects
277
+
users to do the same. If you believe your copyrighted work has been
278
+
used in a way that constitutes infringement, please notify us
279
+
promptly.
280
+
</p>
281
+
</Section>
282
+
283
+
<Section title="Notice Requirements">
284
+
<p>
285
+
Your infringement notice must include: (1) a description of the
286
+
copyrighted work, (2) the location of the infringing material, (3)
287
+
your contact information, (4) a statement that you believe in good
288
+
faith the use is not authorized, and (5) a statement, under penalty of
289
+
perjury, that the information is accurate.
290
+
</p>
291
+
</Section>
292
+
293
+
<Section title="DMCA Compliance">
294
+
<p>
295
+
Grain complies with the Digital Millennium Copyright Act (DMCA). If
296
+
you are a copyright holder and believe your rights have been violated,
297
+
you may file a DMCA notice with the required information to our
298
+
designated agent. We will promptly respond to all valid DMCA notices
299
+
and take appropriate action, including removal of the infringing
300
+
content and disabling access.
301
+
</p>
302
+
</Section>
303
+
304
+
<Section title="Repeat Infringers">
305
+
<p>
306
+
Accounts that repeatedly infringe copyright may be suspended or
307
+
removed in accordance with AT Protocol and Grain Social’s moderation
308
+
guidelines.
309
+
</p>
310
+
</Section>
311
+
312
+
<Section title="Contact">
313
+
<p>
314
+
To report infringement or submit a DMCA notice, contact us at{" "}
315
+
<a
316
+
href="mailto:support@grain.social"
317
+
className="text-sky-500 hover:underline"
318
+
>
319
+
support@grain.social
320
+
</a>
321
+
.
322
+
</p>
323
+
</Section>
324
+
</div>
325
+
);
326
+
}
+6
src/main.tsx
+6
src/main.tsx
···
8
8
import * as dialogHandlers from "./routes/dialogs.tsx";
9
9
import { handler as exploreHandler } from "./routes/explore.tsx";
10
10
import { handler as galleryHandler } from "./routes/gallery.tsx";
11
+
import * as legalHandlers from "./routes/legal.tsx";
11
12
import { handler as notificationsHandler } from "./routes/notifications.tsx";
12
13
import { handler as onboardHandler } from "./routes/onboard.tsx";
13
14
import { handler as profileHandler } from "./routes/profile.tsx";
14
15
import { handler as recordHandler } from "./routes/record.ts";
16
+
import { handler as supportHandler } from "./routes/support.tsx";
15
17
import { handler as timelineHandler } from "./routes/timeline.tsx";
16
18
import { handler as uploadHandler } from "./routes/upload.tsx";
17
19
import { appStateMiddleware, type State } from "./state.ts";
···
57
59
route("/profile/:handle/gallery/:rkey", galleryHandler),
58
60
route("/upload", uploadHandler),
59
61
route("/onboard", onboardHandler),
62
+
route("/support", supportHandler),
63
+
route("/support/privacy", legalHandlers.privacyHandler),
64
+
route("/support/terms", legalHandlers.termsHandler),
65
+
route("/support/copyright", legalHandlers.copyrightHandler),
60
66
route("/dialogs/create-account", dialogHandlers.createAccount),
61
67
route("/dialogs/gallery/new", dialogHandlers.createGallery),
62
68
route("/dialogs/gallery/:rkey", dialogHandlers.editGallery),
+33
src/routes/legal.tsx
+33
src/routes/legal.tsx
···
1
+
import { BffContext, RouteHandler } from "@bigmoves/bff";
2
+
import { CopyrightPolicy, PrivacyPolicy, Terms } from "../legal.tsx";
3
+
import type { State } from "../state.ts";
4
+
5
+
export const termsHandler: RouteHandler = (
6
+
_req,
7
+
_params,
8
+
ctx: BffContext<State>,
9
+
) => {
10
+
return ctx.render(
11
+
<Terms />,
12
+
);
13
+
};
14
+
15
+
export const privacyHandler: RouteHandler = (
16
+
_req,
17
+
_params,
18
+
ctx: BffContext<State>,
19
+
) => {
20
+
return ctx.render(
21
+
<PrivacyPolicy />,
22
+
);
23
+
};
24
+
25
+
export const copyrightHandler: RouteHandler = (
26
+
_req,
27
+
_params,
28
+
ctx: BffContext<State>,
29
+
) => {
30
+
return ctx.render(
31
+
<CopyrightPolicy />,
32
+
);
33
+
};
+33
src/routes/support.tsx
+33
src/routes/support.tsx
···
1
+
import { RouteHandler } from "@bigmoves/bff";
2
+
3
+
export const handler: RouteHandler = (_req, _params, ctx) => {
4
+
ctx.state.meta = [{ title: "Support — Grain" }];
5
+
return ctx.render(
6
+
<div className="px-4 py-4">
7
+
<h1 className="text-3xl font-bold mb-4 text-zinc-900 dark:text-white">
8
+
Support
9
+
</h1>
10
+
<p className="mb-4 text-zinc-700 dark:text-zinc-300">
11
+
For help, questions, or to report issues, please email us at{" "}
12
+
<a
13
+
href="mailto:support@grain.social"
14
+
className="text-sky-500 hover:underline"
15
+
>
16
+
support@grain.social
17
+
</a>.
18
+
</p>
19
+
<p className="mb-2 text-zinc-700 dark:text-zinc-300">
20
+
You can also review our{" "}
21
+
<a href="/support/terms" className="text-sky-500 hover:underline">
22
+
Terms
23
+
</a>,{" "}
24
+
<a href="/support/privacy" className="text-sky-500 hover:underline">
25
+
Privacy Policy
26
+
</a>, and{" "}
27
+
<a href="/support/copyright" className="text-sky-500 hover:underline">
28
+
Copyright Policy
29
+
</a>.
30
+
</p>
31
+
</div>,
32
+
);
33
+
};
+86
static/styles.css
+86
static/styles.css
···
11
11
--color-zinc-50: oklch(98.5% 0 0);
12
12
--color-zinc-100: oklch(96.7% 0.001 286.375);
13
13
--color-zinc-200: oklch(92% 0.004 286.32);
14
+
--color-zinc-300: oklch(87.1% 0.006 286.286);
14
15
--color-zinc-500: oklch(55.2% 0.016 285.938);
15
16
--color-zinc-600: oklch(44.2% 0.017 285.786);
17
+
--color-zinc-700: oklch(37% 0.013 285.805);
16
18
--color-zinc-800: oklch(27.4% 0.006 286.033);
17
19
--color-zinc-900: oklch(21% 0.006 285.885);
18
20
--color-zinc-950: oklch(14.1% 0.005 285.823);
···
30
32
--text-xl--line-height: calc(1.75 / 1.25);
31
33
--text-2xl: 1.5rem;
32
34
--text-2xl--line-height: calc(2 / 1.5);
35
+
--text-3xl: 1.875rem;
36
+
--text-3xl--line-height: calc(2.25 / 1.875);
33
37
--text-4xl: 2.25rem;
34
38
--text-4xl--line-height: calc(2.5 / 2.25);
35
39
--font-weight-semibold: 600;
···
184
188
}
185
189
}
186
190
@layer utilities {
191
+
.visible {
192
+
visibility: visible;
193
+
}
187
194
.sr-only {
188
195
position: absolute;
189
196
width: 1px;
···
276
283
max-width: 96rem;
277
284
}
278
285
}
286
+
.mx-1 {
287
+
margin-inline: calc(var(--spacing) * 1);
288
+
}
279
289
.mx-auto {
280
290
margin-inline: auto;
281
291
}
···
291
301
.my-30 {
292
302
margin-block: calc(var(--spacing) * 30);
293
303
}
304
+
.mt-1 {
305
+
margin-top: calc(var(--spacing) * 1);
306
+
}
294
307
.mt-2 {
295
308
margin-top: calc(var(--spacing) * 2);
296
309
}
···
308
321
}
309
322
.mb-4 {
310
323
margin-bottom: calc(var(--spacing) * 4);
324
+
}
325
+
.mb-6 {
326
+
margin-bottom: calc(var(--spacing) * 6);
327
+
}
328
+
.mb-8 {
329
+
margin-bottom: calc(var(--spacing) * 8);
330
+
}
331
+
.ml-0 {
332
+
margin-left: calc(var(--spacing) * 0);
311
333
}
312
334
.flex {
313
335
display: flex;
···
454
476
.flex-col {
455
477
flex-direction: column;
456
478
}
479
+
.flex-row {
480
+
flex-direction: row;
481
+
}
457
482
.flex-wrap {
458
483
flex-wrap: wrap;
459
484
}
···
462
487
}
463
488
.items-center {
464
489
align-items: center;
490
+
}
491
+
.items-start {
492
+
align-items: flex-start;
465
493
}
466
494
.justify-between {
467
495
justify-content: space-between;
···
648
676
font-size: var(--text-2xl);
649
677
line-height: var(--tw-leading, var(--text-2xl--line-height));
650
678
}
679
+
.text-3xl {
680
+
font-size: var(--text-3xl);
681
+
line-height: var(--tw-leading, var(--text-3xl--line-height));
682
+
}
651
683
.text-4xl {
652
684
font-size: var(--text-4xl);
653
685
line-height: var(--tw-leading, var(--text-4xl--line-height));
···
694
726
.text-white {
695
727
color: var(--color-white);
696
728
}
729
+
.text-zinc-500 {
730
+
color: var(--color-zinc-500);
731
+
}
697
732
.text-zinc-600 {
698
733
color: var(--color-zinc-600);
699
734
}
735
+
.text-zinc-700 {
736
+
color: var(--color-zinc-700);
737
+
}
738
+
.text-zinc-800 {
739
+
color: var(--color-zinc-800);
740
+
}
700
741
.text-zinc-900 {
701
742
color: var(--color-zinc-900);
702
743
}
···
706
747
.lowercase {
707
748
text-transform: lowercase;
708
749
}
750
+
.underline {
751
+
text-decoration-line: underline;
752
+
}
709
753
.ring-sky-500 {
710
754
--tw-ring-color: var(--color-sky-500);
711
755
}
712
756
.group-data-\[added\=true\]\:block {
713
757
&:is(:where(.group)[data-added="true"] *) {
714
758
display: block;
759
+
}
760
+
}
761
+
.hover\:no-underline {
762
+
&:hover {
763
+
@media (hover: hover) {
764
+
text-decoration-line: none;
765
+
}
715
766
}
716
767
}
717
768
.hover\:underline {
···
777
828
left: calc(var(--spacing) * 0);
778
829
}
779
830
}
831
+
.sm\:mt-0 {
832
+
@media (width >= 40rem) {
833
+
margin-top: calc(var(--spacing) * 0);
834
+
}
835
+
}
836
+
.sm\:ml-1 {
837
+
@media (width >= 40rem) {
838
+
margin-left: calc(var(--spacing) * 1);
839
+
}
840
+
}
841
+
.sm\:ml-2 {
842
+
@media (width >= 40rem) {
843
+
margin-left: calc(var(--spacing) * 2);
844
+
}
845
+
}
780
846
.sm\:h-screen {
781
847
@media (width >= 40rem) {
782
848
height: 100vh;
···
832
898
align-items: center;
833
899
}
834
900
}
901
+
.sm\:items-end {
902
+
@media (width >= 40rem) {
903
+
align-items: flex-end;
904
+
}
905
+
}
835
906
.sm\:justify-between {
836
907
@media (width >= 40rem) {
837
908
justify-content: space-between;
···
840
911
.sm\:justify-end {
841
912
@media (width >= 40rem) {
842
913
justify-content: flex-end;
914
+
}
915
+
}
916
+
.sm\:gap-0 {
917
+
@media (width >= 40rem) {
918
+
gap: calc(var(--spacing) * 0);
843
919
}
844
920
}
845
921
.sm\:space-x-2 {
···
907
983
.dark\:text-zinc-50 {
908
984
@media (prefers-color-scheme: dark) {
909
985
color: var(--color-zinc-50);
986
+
}
987
+
}
988
+
.dark\:text-zinc-100 {
989
+
@media (prefers-color-scheme: dark) {
990
+
color: var(--color-zinc-100);
991
+
}
992
+
}
993
+
.dark\:text-zinc-300 {
994
+
@media (prefers-color-scheme: dark) {
995
+
color: var(--color-zinc-300);
910
996
}
911
997
}
912
998
.dark\:text-zinc-500 {