discord-transcript-ui 1.0.1 → 1.1.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.
@@ -0,0 +1,1947 @@
1
+ /*!
2
+ * discord-transcript-ui v1.0.0 — single stylesheet for Discord chat transcripts
3
+ * MIT licensed. Light DOM only: no Shadow DOM, no build step required to use.
4
+ *
5
+ * Recovery information for archived transcripts lives in the HTML file itself
6
+ * (asset URLs + sha256 + mirrors). See docs/CDN-STRATEGY.md in the source repo.
7
+ *
8
+ * Contents
9
+ * 1 tokens 6 inline text 11 embeds
10
+ * 2 base 7 code 12 attachments
11
+ * 3 page shell 8 system/command 13 reactions
12
+ * 4 messages 9 threads 14 action row
13
+ * 5 message 10 reply 15 a11y + print
14
+ *
15
+ * Everything is themed through --dt-* custom properties: override them on
16
+ * :root, on dms, or per element. Markup contract: docs/SPEC.md.
17
+ */
18
+
19
+ /* ==========================================================================
20
+ 1. TOKENS — dark by default, light opt-in
21
+ ========================================================================== */
22
+
23
+ /* Tokens live on :root so every component inherits them, and on
24
+ dms so a transcript can be re-themed without touching the page. */
25
+ :root {
26
+ --dt-font: 'gg sans', 'Noto Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
27
+ --dt-font-mono:
28
+ 'gg mono', 'Source Code Pro', Consolas, 'Andale Mono WT', 'Andale Mono', 'Lucida Console',
29
+ 'DejaVu Sans Mono', 'Liberation Mono', 'Nimbus Mono L', Monaco, 'Courier New', Courier, monospace;
30
+ --dt-font-size: 1rem;
31
+ --dt-line-height: 1.375rem;
32
+ --dt-radius-sm: 4px;
33
+ --dt-radius-md: 8px;
34
+ --dt-radius-lg: 12px;
35
+
36
+ /* Discord's 2024+ "visual refresh" palette, taken from its live token sheet.
37
+ See docs/DESIGN-TOKENS.md for every value, source URL and open question. */
38
+ --dt-bg-primary: #323339;
39
+ --dt-bg-secondary: #393a41;
40
+ --dt-bg-tertiary: #2c2d32;
41
+ --dt-bg-embed: #393a41;
42
+ --dt-bg-code: #323339;
43
+ --dt-bg-inline-code: rgba(88, 101, 242, 0.078);
44
+ --dt-bg-modifier-hover: rgba(151, 151, 159, 0.122);
45
+ --dt-bg-modifier-active: rgba(151, 151, 159, 0.161);
46
+ --dt-bg-message-hover: rgba(151, 151, 159, 0.039);
47
+ --dt-bg-mentioned: rgba(248, 163, 0, 0.078);
48
+ --dt-bg-scrollbar-thumb: #767780;
49
+ --dt-bg-scrollbar-track: transparent;
50
+
51
+ --dt-text-normal: #f3f3f4;
52
+ --dt-text-strong: #ffffff;
53
+ --dt-text-subtle: #c5c6ca;
54
+ --dt-text-muted: #9d9ea5;
55
+ --dt-text-link: #76aff6;
56
+ --dt-text-on-brand: #ffffff;
57
+ --dt-status-online: #3d9e60;
58
+ --dt-status-idle: #ffcb6e;
59
+ --dt-status-dnd: #dc4247;
60
+ --dt-status-offline: #9d9ea5;
61
+
62
+ --dt-border: rgba(151, 151, 159, 0.122);
63
+ --dt-border-strong: rgba(151, 151, 159, 0.2);
64
+ --dt-brand: #5865f2;
65
+ --dt-accent-bar: var(--dt-brand);
66
+ --dt-role-default: #f3f3f4;
67
+ --dt-embed-default-color: #4f545c;
68
+ --dt-divider: rgba(151, 151, 159, 0.122);
69
+ --dt-line-color: #595a63;
70
+
71
+ --dt-mention-bg: rgba(88, 101, 242, 0.239);
72
+ --dt-mention-text: #cdd7ff;
73
+ --dt-mention-role-color: var(--dt-role-default);
74
+ --dt-mention-channel-bg: rgba(151, 151, 159, 0.122);
75
+ --dt-mention-channel-text: var(--dt-text-normal);
76
+
77
+ --dt-spoiler-bg: #7d7e87;
78
+ --dt-spoiler-revealed-bg: rgba(151, 151, 159, 0.078);
79
+
80
+ --dt-gutter: 16px;
81
+ --dt-avatar-size: 40px;
82
+ --dt-avatar-gap: 16px;
83
+ --dt-msg-padding-right: 48px;
84
+ --dt-reply-avatar-size: 16px;
85
+
86
+ --dt-button-secondary: rgba(151, 151, 159, 0.122);
87
+ --dt-button-secondary-hover: rgba(151, 151, 159, 0.2);
88
+ --dt-button-primary: #5865f2;
89
+ --dt-button-primary-hover: #4654c0;
90
+ --dt-button-success: #248046;
91
+ --dt-button-danger: #da3e44;
92
+ --dt-button-link: rgba(151, 151, 159, 0.122);
93
+
94
+ --dt-reaction-bg: rgba(151, 151, 159, 0.122);
95
+ --dt-reaction-border: rgba(151, 151, 159, 0.122);
96
+ --dt-reaction-bg-reacted: rgba(88, 101, 242, 0.239);
97
+ --dt-reaction-border-reacted: #5865f2;
98
+ --dt-reaction-text-reacted: #dfe4ff;
99
+
100
+ --dt-focus-ring: #76aff6;
101
+ --dt-transition: 120ms ease;
102
+ }
103
+
104
+ /* Light theme: set light-theme on dms, data-theme, or the page root. */
105
+ dms[light-theme],
106
+ dms[data-theme='light'],
107
+ :root[data-theme='light'] {
108
+ --dt-bg-primary: #ffffff;
109
+ --dt-bg-secondary: #ffffff;
110
+ --dt-bg-tertiary: #f3f3f4;
111
+ --dt-bg-embed: #ffffff;
112
+ --dt-bg-code: #f3f3f4;
113
+ --dt-bg-inline-code: rgba(88, 101, 242, 0.078);
114
+ --dt-bg-modifier-hover: rgba(151, 151, 159, 0.122);
115
+ --dt-bg-modifier-active: rgba(151, 151, 159, 0.161);
116
+ --dt-bg-message-hover: rgba(151, 151, 159, 0.078);
117
+ --dt-bg-mentioned: rgba(248, 163, 0, 0.1);
118
+ --dt-bg-scrollbar-thumb: #8b8c94;
119
+ --dt-bg-scrollbar-track: transparent;
120
+
121
+ --dt-text-normal: #2e2e34;
122
+ --dt-text-strong: #28282d;
123
+ --dt-text-subtle: #595a63;
124
+ --dt-text-muted: #6c6d76;
125
+ --dt-text-link: #006dd4;
126
+ --dt-role-default: #2e2e34;
127
+
128
+ --dt-border: rgba(151, 151, 159, 0.278);
129
+ --dt-border-strong: rgba(151, 151, 159, 0.4);
130
+ --dt-divider: rgba(151, 151, 159, 0.278);
131
+ --dt-line-color: #c5c6ca;
132
+ --dt-embed-default-color: #d4d7dc;
133
+
134
+ --dt-mention-bg: rgba(88, 101, 242, 0.15);
135
+ --dt-mention-text: #2e3c88;
136
+ --dt-mention-channel-bg: rgba(151, 151, 159, 0.122);
137
+ --dt-mention-channel-text: #2e2e34;
138
+
139
+ --dt-spoiler-bg: #8f9097;
140
+ --dt-spoiler-revealed-bg: #ebeced;
141
+
142
+ --dt-reaction-border: rgba(151, 151, 159, 0.278);
143
+ --dt-reaction-text-reacted: #273478;
144
+ --dt-status-online: #01783e;
145
+ --dt-status-idle: #faaa00;
146
+ --dt-status-dnd: #d22d39;
147
+ --dt-status-offline: #5f606a;
148
+ --dt-focus-ring: #006dd4;
149
+ }
150
+
151
+ /* Explicit dark still wins inside a light scope. */
152
+ dms[data-theme='dark'] {
153
+ --dt-bg-primary: #323339;
154
+ --dt-bg-secondary: #393a41;
155
+ --dt-bg-tertiary: #2c2d32;
156
+ --dt-bg-embed: #393a41;
157
+ --dt-bg-code: #323339;
158
+ --dt-bg-inline-code: rgba(88, 101, 242, 0.078);
159
+ --dt-bg-modifier-hover: rgba(151, 151, 159, 0.122);
160
+ --dt-bg-modifier-active: rgba(151, 151, 159, 0.161);
161
+ --dt-bg-message-hover: rgba(151, 151, 159, 0.039);
162
+ --dt-bg-mentioned: rgba(248, 163, 0, 0.078);
163
+ --dt-bg-scrollbar-thumb: #767780;
164
+ --dt-bg-scrollbar-track: transparent;
165
+ --dt-text-normal: #f3f3f4;
166
+ --dt-text-strong: #ffffff;
167
+ --dt-text-subtle: #c5c6ca;
168
+ --dt-text-muted: #9d9ea5;
169
+ --dt-text-link: #76aff6;
170
+ --dt-role-default: #f3f3f4;
171
+ --dt-border: rgba(151, 151, 159, 0.122);
172
+ --dt-border-strong: rgba(151, 151, 159, 0.2);
173
+ --dt-divider: rgba(151, 151, 159, 0.122);
174
+ --dt-line-color: #595a63;
175
+ --dt-embed-default-color: #4f545c;
176
+ --dt-mention-bg: rgba(88, 101, 242, 0.239);
177
+ --dt-mention-text: #cdd7ff;
178
+ --dt-mention-channel-bg: rgba(151, 151, 159, 0.122);
179
+ --dt-mention-channel-text: #f3f3f4;
180
+ --dt-spoiler-bg: #7d7e87;
181
+ --dt-spoiler-revealed-bg: rgba(151, 151, 159, 0.078);
182
+ --dt-reaction-border: rgba(151, 151, 159, 0.122);
183
+ --dt-reaction-text-reacted: #dfe4ff;
184
+ --dt-status-online: #3d9e60;
185
+ --dt-status-idle: #ffcb6e;
186
+ --dt-status-dnd: #dc4247;
187
+ --dt-status-offline: #9d9ea5;
188
+ --dt-focus-ring: #76aff6;
189
+ }
190
+
191
+ /* ==========================================================================
192
+ 2. BASE
193
+ ========================================================================== */
194
+
195
+ :where(
196
+ dms,
197
+ dm,
198
+ dai,
199
+ drp,
200
+ de,
201
+ ded,
202
+ defs,
203
+ def,
204
+ defo,
205
+ dats,
206
+ dimg,
207
+ dfil,
208
+ daud,
209
+ dvid,
210
+ drs,
211
+ dr,
212
+ dsp,
213
+ dti,
214
+ dme,
215
+ demo,
216
+ dc,
217
+ dp,
218
+ dq,
219
+ dli,
220
+ dul,
221
+ dol,
222
+ dh,
223
+ dcmd,
224
+ dsy,
225
+ dth,
226
+ dthm,
227
+ dar,
228
+ dbtn,
229
+ dvat
230
+ ) {
231
+ box-sizing: border-box;
232
+ }
233
+
234
+ :where(
235
+ dms,
236
+ dm,
237
+ de,
238
+ dats,
239
+ drs,
240
+ drp,
241
+ dsy,
242
+ dcmd,
243
+ dth,
244
+ dthm,
245
+ dar,
246
+ dp,
247
+ dq,
248
+ dul,
249
+ dol,
250
+ dh
251
+ ) {
252
+ display: block;
253
+ font-family: var(--dt-font);
254
+ font-size: var(--dt-font-size);
255
+ line-height: var(--dt-line-height);
256
+ color: var(--dt-text-normal);
257
+ text-align: left;
258
+ }
259
+
260
+ :where(dm, drp, de, dats, drs) {
261
+ -webkit-text-size-adjust: 100%;
262
+ word-wrap: break-word;
263
+ overflow-wrap: anywhere;
264
+ }
265
+
266
+ /* Inline formatting */
267
+ :where(db) {
268
+ font-weight: 700;
269
+ }
270
+
271
+ :where(di) {
272
+ font-style: italic;
273
+ }
274
+
275
+ :where(dun) {
276
+ text-decoration: underline;
277
+ }
278
+
279
+ :where(dst) {
280
+ text-decoration: line-through;
281
+ }
282
+
283
+ /* Discord's "-# small print" (subtext). Discord renders it as <small> at
284
+ .875rem in the subtle text colour, on its own line; it is not a subscript.
285
+ Value from Discord's live CSS: docs/DESIGN-TOKENS.md, "Small / <small>". */
286
+ :where(dsub) {
287
+ display: block;
288
+ color: var(--dt-text-subtle);
289
+ font-size: 0.875rem;
290
+ line-height: 1.20313rem;
291
+ }
292
+
293
+ /* Markdown headings */
294
+ :where(dh) {
295
+ display: block;
296
+ margin: 8px 0 0;
297
+ font-weight: 600;
298
+ line-height: 1.375em;
299
+ }
300
+
301
+ :where(dh[level='1']) {
302
+ font-size: 1.5rem;
303
+ }
304
+
305
+ :where(dh[level='2']) {
306
+ font-size: 1.25rem;
307
+ }
308
+
309
+ :where(dh[level='3']) {
310
+ font-size: 1rem;
311
+ }
312
+
313
+ /* Blockquote */
314
+ :where(dq) {
315
+ display: block;
316
+ margin: 4px 0;
317
+ padding: 0 8px 0 12px;
318
+ color: var(--dt-text-subtle);
319
+ border-left: 4px solid var(--dt-line-color);
320
+ border-radius: var(--dt-radius-sm);
321
+ }
322
+
323
+ /* Lists */
324
+ :where(dul, dol) {
325
+ margin: 0;
326
+ padding-left: 1.375em;
327
+ }
328
+
329
+ :where(dli) {
330
+ display: list-item;
331
+ margin: 0 0 4px;
332
+ padding: 0;
333
+ }
334
+
335
+ :where(dul) > dli {
336
+ list-style: disc outside;
337
+ }
338
+
339
+ :where(dol) > dli {
340
+ list-style: decimal outside;
341
+ }
342
+
343
+ /* Links */
344
+ :where(dl) {
345
+ color: var(--dt-text-link);
346
+ text-decoration: none;
347
+ }
348
+
349
+ :where(dl):hover {
350
+ text-decoration: underline;
351
+ }
352
+
353
+ :where(dm, de, ded, def, drp, dq)
354
+ a:not([class]) {
355
+ color: var(--dt-text-link);
356
+ text-decoration: none;
357
+ }
358
+
359
+ :where(dm, de, ded, def, drp, dq)
360
+ a:not([class]):hover {
361
+ text-decoration: underline;
362
+ }
363
+
364
+ /* ==========================================================================
365
+ 3. PAGE SHELL (optional)
366
+ ========================================================================== */
367
+
368
+ .dt-page {
369
+ min-height: 100vh;
370
+ margin: 0;
371
+ background: var(--dt-bg-primary);
372
+ color: var(--dt-text-normal);
373
+ font-family: var(--dt-font);
374
+ font-size: var(--dt-font-size);
375
+ line-height: var(--dt-line-height);
376
+ -webkit-font-smoothing: antialiased;
377
+ }
378
+
379
+ .dt-page__inner {
380
+ max-width: 100%;
381
+ margin: 0 auto;
382
+ padding: 1rem 0 4rem;
383
+ }
384
+
385
+ .dt-page__title {
386
+ margin: 0 0 1rem;
387
+ padding: 0 16px;
388
+ font-size: 1.25rem;
389
+ font-weight: 700;
390
+ color: var(--dt-text-strong);
391
+ }
392
+
393
+ .dt-page__meta {
394
+ margin: 0 0 1.5rem;
395
+ padding: 0 16px;
396
+ font-size: 0.875rem;
397
+ color: var(--dt-text-muted);
398
+ }
399
+
400
+ /* ==========================================================================
401
+ 4. MESSAGE LIST
402
+ ========================================================================== */
403
+
404
+ :where(dms) {
405
+ display: block;
406
+ position: relative;
407
+ margin: 0 auto;
408
+ padding: 8px 0 16px;
409
+ background: var(--dt-bg-primary);
410
+ color: var(--dt-text-normal);
411
+ font-family: var(--dt-font);
412
+ line-height: var(--dt-line-height);
413
+ overflow-x: hidden;
414
+ overflow-y: auto;
415
+ scrollbar-color: var(--dt-bg-scrollbar-thumb) var(--dt-bg-scrollbar-track);
416
+ }
417
+
418
+ :where(dms[no-background]) {
419
+ background: transparent;
420
+ padding: 0;
421
+ }
422
+
423
+ :where(dms[channel-name])::before {
424
+ content: attr(channel-name);
425
+ display: block;
426
+ margin: 0 0 8px;
427
+ padding: 4px 16px 12px;
428
+ border-bottom: 1px solid var(--dt-divider);
429
+ font-size: 1.25rem;
430
+ font-weight: 700;
431
+ color: var(--dt-text-strong);
432
+ }
433
+
434
+ dms[channel-type='text'][channel-name]::before,
435
+ dms[channel-type='voice'][channel-name]::before,
436
+ dms[channel-type='thread'][channel-name]::before,
437
+ dms[channel-type='forum'][channel-name]::before,
438
+ dms[channel-type='locked'][channel-name]::before {
439
+ content: '#\00a0' attr(channel-name);
440
+ }
441
+
442
+ dms ::-webkit-scrollbar {
443
+ width: 16px;
444
+ height: 16px;
445
+ }
446
+
447
+ dms ::-webkit-scrollbar-thumb {
448
+ background: var(--dt-bg-scrollbar-thumb);
449
+ border: 4px solid transparent;
450
+ background-clip: padding-box;
451
+ border-radius: 8px;
452
+ }
453
+
454
+ dms ::-webkit-scrollbar-track {
455
+ background: var(--dt-bg-scrollbar-track);
456
+ }
457
+
458
+ /* ==========================================================================
459
+ 5. MESSAGE
460
+ ========================================================================== */
461
+
462
+ :where(dm) {
463
+ position: relative;
464
+ display: block;
465
+ margin: 0;
466
+ padding: 2px var(--dt-msg-padding-right) 2px var(--dt-gutter);
467
+ color: var(--dt-text-normal);
468
+ font-family: var(--dt-font);
469
+ font-size: var(--dt-font-size);
470
+ line-height: var(--dt-line-height);
471
+ -webkit-user-select: text;
472
+ user-select: text;
473
+ }
474
+
475
+ :where(dm:hover) {
476
+ background: var(--dt-bg-message-hover);
477
+ }
478
+
479
+ /* Pre-upgrade fallback: no avatar or header yet, but the author name and the
480
+ message text are both visible, so a JS-less or CSS-only view stays readable. */
481
+ dm:not([data-dt-ready])::before {
482
+ content: attr(author) '\00a0';
483
+ font-weight: 600;
484
+ color: var(--dt-role-default);
485
+ }
486
+
487
+ dm:not([data-dt-ready]):not([author])::before,
488
+ dm:not([data-dt-ready])[author='']::before {
489
+ content: '';
490
+ }
491
+
492
+ /* Structure built by discord-transcript.js */
493
+ .dt-msg {
494
+ display: grid;
495
+ grid-template-columns: var(--dt-avatar-size) minmax(0, 1fr);
496
+ column-gap: var(--dt-avatar-gap);
497
+ align-items: start;
498
+ }
499
+
500
+ .dt-avatar {
501
+ grid-column: 1;
502
+ width: var(--dt-avatar-size);
503
+ height: var(--dt-avatar-size);
504
+ margin-top: 2px;
505
+ border-radius: 50%;
506
+ overflow: hidden;
507
+ background: var(--dt-bg-tertiary);
508
+ }
509
+
510
+ .dt-avatar img {
511
+ display: block;
512
+ width: 100%;
513
+ height: 100%;
514
+ object-fit: cover;
515
+ }
516
+
517
+ .dt-content {
518
+ grid-column: 2;
519
+ min-width: 0;
520
+ }
521
+
522
+ .dt-header {
523
+ display: flex;
524
+ flex-wrap: wrap;
525
+ align-items: center;
526
+ min-height: 1.375rem;
527
+ }
528
+
529
+ .dt-author {
530
+ margin-right: 8px;
531
+ font-weight: 500;
532
+ color: var(--dt-role-default);
533
+ text-decoration: none;
534
+ }
535
+
536
+ .dt-author--interactive:hover {
537
+ text-decoration: underline;
538
+ cursor: pointer;
539
+ }
540
+
541
+ .dt-badges {
542
+ display: inline-flex;
543
+ align-items: center;
544
+ gap: 4px;
545
+ margin-right: 8px;
546
+ }
547
+
548
+ .dt-badge {
549
+ display: inline-flex;
550
+ align-items: center;
551
+ justify-content: center;
552
+ gap: 3px;
553
+ height: 16px;
554
+ padding: 0 5px;
555
+ border-radius: 4px;
556
+ background: var(--dt-brand);
557
+ color: var(--dt-text-on-brand);
558
+ font-size: 0.625rem;
559
+ font-weight: 600;
560
+ line-height: 1;
561
+ letter-spacing: 0.02em;
562
+ text-transform: uppercase;
563
+ }
564
+
565
+ /* Verified bot / application tag: a slightly rounded blurple rectangle reading
566
+ "✓ APP" (the tick is drawn here, the text comes from the script so that it is
567
+ real, readable text rather than generated content). */
568
+ .dt-badge--verified {
569
+ padding: 0 5px 0 4px;
570
+ }
571
+
572
+ .dt-badge--verified::before {
573
+ content: '\2713';
574
+ font-size: 0.75rem;
575
+ line-height: 1;
576
+ }
577
+
578
+ .dt-badge--server {
579
+ background: #3ba55c;
580
+ }
581
+
582
+ .dt-badge--official {
583
+ background: #1abc84;
584
+ }
585
+
586
+ .dt-timestamp {
587
+ font-size: 0.75rem;
588
+ line-height: 1.375rem;
589
+ color: var(--dt-text-muted);
590
+ white-space: nowrap;
591
+ }
592
+
593
+ .dt-timestamp:hover {
594
+ text-decoration: underline;
595
+ }
596
+
597
+ .dt-edited {
598
+ margin-left: 4px;
599
+ font-size: 0.625rem;
600
+ color: var(--dt-text-muted);
601
+ }
602
+
603
+ .dt-body {
604
+ display: block;
605
+ min-width: 0;
606
+ }
607
+
608
+ .dt-body > :first-child {
609
+ margin-top: 0;
610
+ }
611
+
612
+ /* Emoji-only messages render larger. */
613
+ .dt-jumbo {
614
+ display: flex;
615
+ flex-wrap: wrap;
616
+ align-items: center;
617
+ gap: 2px;
618
+ }
619
+
620
+ .dt-jumbo demo img,
621
+ .dt-jumbo > img.emoji {
622
+ width: 48px;
623
+ height: 48px;
624
+ }
625
+
626
+ /* States */
627
+ dm[highlight],
628
+ dm[ephemeral],
629
+ dm[mentioned] {
630
+ background: var(--dt-bg-mentioned);
631
+ }
632
+
633
+ dm[highlight]::after,
634
+ dm[ephemeral]::after,
635
+ dm[mentioned]::after {
636
+ content: '';
637
+ position: absolute;
638
+ top: 0;
639
+ bottom: 0;
640
+ left: 0;
641
+ width: 2px;
642
+ background: var(--dt-accent-bar);
643
+ pointer-events: none;
644
+ }
645
+
646
+ dm[ephemeral] {
647
+ background: rgba(88, 101, 242, 0.08);
648
+ }
649
+
650
+ dm[highlight]:hover,
651
+ dm[mentioned]:hover {
652
+ background: var(--dt-bg-mentioned);
653
+ }
654
+
655
+ /* Continuation rows (same author as the previous message), detected by JS or
656
+ forced by the parser with message-body-only. */
657
+ dm[data-dt-continuation] .dt-avatar,
658
+ dm[message-body-only] .dt-avatar {
659
+ visibility: hidden;
660
+ }
661
+
662
+ dm[data-dt-continuation] .dt-header,
663
+ dm[message-body-only] .dt-header {
664
+ display: none;
665
+ }
666
+
667
+ dm[data-dt-continuation]:hover::before,
668
+ dm[data-dt-continuation]:focus-within::before {
669
+ content: attr(data-dt-short-time);
670
+ position: absolute;
671
+ top: 3px;
672
+ left: var(--dt-gutter);
673
+ width: calc(var(--dt-avatar-size) - 4px);
674
+ font-size: 0.6875rem;
675
+ line-height: 1.375rem;
676
+ color: var(--dt-text-muted);
677
+ white-space: nowrap;
678
+ overflow: hidden;
679
+ pointer-events: none;
680
+ }
681
+
682
+ dm[data-dt-group-start] {
683
+ margin-top: 1.0625rem;
684
+ }
685
+
686
+ dm:first-child {
687
+ margin-top: 0;
688
+ }
689
+
690
+ /* Compact mode */
691
+ dms[compact-mode],
692
+ dm[compact-mode] {
693
+ --dt-avatar-size: 16px;
694
+ --dt-avatar-gap: 8px;
695
+ --dt-gutter: 16px;
696
+ --dt-msg-padding-right: 16px;
697
+ }
698
+
699
+ dms[compact-mode] dm {
700
+ padding-top: 1px;
701
+ padding-bottom: 1px;
702
+ }
703
+
704
+ dms[compact-mode] .dt-avatar {
705
+ margin-top: 0.25rem;
706
+ }
707
+
708
+ dms[compact-mode] .dt-header {
709
+ display: inline;
710
+ margin-right: 8px;
711
+ }
712
+
713
+ dms[compact-mode] .dt-author {
714
+ margin-right: 6px;
715
+ }
716
+
717
+ dms[compact-mode] .dt-body {
718
+ display: inline;
719
+ }
720
+
721
+ dms[compact-mode] .dt-timestamp {
722
+ margin-right: 6px;
723
+ font-size: 0.6875rem;
724
+ }
725
+
726
+ /* ==========================================================================
727
+ 6. AUTHOR INFO, MENTION, EMOJI, TIME, SPOILER
728
+ ========================================================================== */
729
+
730
+ :where(dai) {
731
+ display: inline-flex;
732
+ align-items: center;
733
+ gap: 4px;
734
+ font-weight: 500;
735
+ color: var(--dt-role-default);
736
+ }
737
+
738
+ dai:not([data-dt-ready])::before {
739
+ content: attr(author);
740
+ }
741
+
742
+ :where(
743
+ dai[bot],
744
+ dai[server],
745
+ dai[official-app],
746
+ dai[op]
747
+ )
748
+ ::after {
749
+ margin-left: 4px;
750
+ padding: 0 4px;
751
+ border-radius: 3px;
752
+ background: var(--dt-brand);
753
+ color: var(--dt-text-on-brand);
754
+ font-size: 0.625rem;
755
+ font-weight: 600;
756
+ line-height: 15px;
757
+ }
758
+
759
+ /* Bots are tagged "APP". Verified bots add the checkmark, so they read "✓ APP"
760
+ (equal :where() specificity, so the later rule wins for verified bots). */
761
+ :where(dai[bot])::after {
762
+ content: 'APP';
763
+ }
764
+
765
+ :where(dai[bot][verified])::after {
766
+ content: '\2713\00a0APP';
767
+ }
768
+
769
+ :where(dai[server])::after {
770
+ content: 'SERVER';
771
+ background: #3ba55c;
772
+ }
773
+
774
+ :where(dai[official-app])::after {
775
+ content: 'OFFICIAL';
776
+ background: #1abc84;
777
+ }
778
+
779
+ :where(dai[op])::after {
780
+ content: 'OP';
781
+ }
782
+
783
+ /* Mentions */
784
+ :where(dme) {
785
+ display: inline;
786
+ padding: 0 2px;
787
+ border-radius: 3px;
788
+ background: var(--dt-mention-bg);
789
+ color: var(--dt-mention-text);
790
+ font-weight: 500;
791
+ cursor: pointer;
792
+ }
793
+
794
+ :where(dme):hover {
795
+ background: var(--dt-bg-modifier-active);
796
+ }
797
+
798
+ :where(dme[type='channel']),
799
+ :where(dme[type='voice']),
800
+ :where(dme[type='thread']),
801
+ :where(dme[type='forum']),
802
+ :where(dme[type='locked']) {
803
+ background: var(--dt-mention-channel-bg);
804
+ color: var(--dt-mention-channel-text);
805
+ }
806
+
807
+ :where(dme[type='role']) {
808
+ background: var(--dt-mention-bg);
809
+ color: var(--dt-mention-role-color);
810
+ }
811
+
812
+ /* Prefixes, so the parser does not have to repeat "@" and "#" per mention. */
813
+ dme:not([no-prefix])::before {
814
+ content: '@';
815
+ }
816
+
817
+ dme:not([no-prefix])[type='channel']::before,
818
+ dme:not([no-prefix])[type='voice']::before,
819
+ dme:not([no-prefix])[type='thread']::before,
820
+ dme:not([no-prefix])[type='forum']::before,
821
+ dme:not([no-prefix])[type='locked']::before {
822
+ content: '#';
823
+ }
824
+
825
+ /* Custom emoji */
826
+ :where(demo) {
827
+ display: inline-block;
828
+ vertical-align: -0.25em;
829
+ }
830
+
831
+ :where(demo) img {
832
+ display: inline-block;
833
+ width: 1.375em;
834
+ height: 1.375em;
835
+ object-fit: contain;
836
+ vertical-align: middle;
837
+ }
838
+
839
+ demo:not([data-dt-ready]):not(:has(img))::before {
840
+ content: attr(name);
841
+ font-size: 0.75rem;
842
+ color: var(--dt-text-muted);
843
+ }
844
+
845
+ /* Inline timestamp chip */
846
+ :where(dti) {
847
+ display: inline;
848
+ padding: 0 2px;
849
+ border-radius: 3px;
850
+ background: var(--dt-bg-modifier-hover);
851
+ font-size: 0.875rem;
852
+ white-space: nowrap;
853
+ }
854
+
855
+ :where(dti):hover {
856
+ background: var(--dt-bg-modifier-active);
857
+ }
858
+
859
+ /* Spoiler */
860
+ :where(dsp) {
861
+ display: inline;
862
+ border-radius: var(--dt-radius-sm);
863
+ background: var(--dt-spoiler-bg);
864
+ color: transparent;
865
+ cursor: pointer;
866
+ transition:
867
+ background var(--dt-transition),
868
+ color var(--dt-transition);
869
+ }
870
+
871
+ :where(dsp)::selection {
872
+ background: transparent;
873
+ color: transparent;
874
+ }
875
+
876
+ :where(dsp[activated]),
877
+ :where(dsp[revealed]),
878
+ :where(dsp[data-dt-revealed]) {
879
+ background: var(--dt-spoiler-revealed-bg);
880
+ color: inherit;
881
+ cursor: auto;
882
+ }
883
+
884
+ /* No-JS safety valve: hovering peeks instead of hiding content forever. */
885
+ dsp:not([data-dt-ready]):hover {
886
+ background: var(--dt-spoiler-revealed-bg);
887
+ color: inherit;
888
+ }
889
+
890
+ dsp[data-dt-ready] {
891
+ outline: none;
892
+ }
893
+
894
+ dsp[data-dt-ready]:focus-visible {
895
+ box-shadow: 0 0 0 2px var(--dt-focus-ring);
896
+ }
897
+
898
+ /* ==========================================================================
899
+ 7. CODE
900
+ ========================================================================== */
901
+
902
+ :where(dc) {
903
+ display: inline;
904
+ padding: 0 2px;
905
+ border-radius: var(--dt-radius-sm);
906
+ background: var(--dt-bg-inline-code);
907
+ border: 1px solid var(--dt-border-strong);
908
+ font-family: var(--dt-font-mono);
909
+ font-size: 0.85em;
910
+ white-space: pre-wrap;
911
+ word-break: break-word;
912
+ }
913
+
914
+ :where(dc[multiline]) {
915
+ display: block;
916
+ margin: 6px 0 0;
917
+ padding: 8px 12px;
918
+ border: 1px solid var(--dt-border);
919
+ border-radius: var(--dt-radius-sm);
920
+ background: var(--dt-bg-code);
921
+ font-size: 0.75rem;
922
+ line-height: 1rem;
923
+ white-space: pre;
924
+ overflow-x: auto;
925
+ tab-size: 4;
926
+ }
927
+
928
+ :where(dp) {
929
+ position: relative;
930
+ display: block;
931
+ margin: 6px 0 0;
932
+ padding: 0;
933
+ border: 1px solid var(--dt-border);
934
+ border-radius: var(--dt-radius-sm);
935
+ background: var(--dt-bg-code);
936
+ overflow: hidden;
937
+ }
938
+
939
+ :where(dp) > :where(dc),
940
+ :where(dp) pre {
941
+ display: block;
942
+ margin: 0;
943
+ padding: 8px 12px;
944
+ border: 0;
945
+ border-radius: 0;
946
+ background: none;
947
+ font-family: var(--dt-font-mono);
948
+ font-size: 0.75rem;
949
+ line-height: 1rem;
950
+ white-space: pre;
951
+ overflow-x: auto;
952
+ tab-size: 4;
953
+ }
954
+
955
+ :where(dp) code {
956
+ font-family: inherit;
957
+ }
958
+
959
+ .dt-code-lang {
960
+ display: block;
961
+ padding: 6px 12px 0;
962
+ font-family: var(--dt-font);
963
+ font-size: 0.75rem;
964
+ color: var(--dt-text-muted);
965
+ }
966
+
967
+ .dt-copy {
968
+ position: absolute;
969
+ top: 6px;
970
+ right: 8px;
971
+ display: none;
972
+ padding: 2px 8px;
973
+ border: 1px solid var(--dt-border-strong);
974
+ border-radius: var(--dt-radius-sm);
975
+ background: var(--dt-bg-primary);
976
+ color: var(--dt-text-normal);
977
+ font-family: var(--dt-font);
978
+ font-size: 0.75rem;
979
+ cursor: pointer;
980
+ }
981
+
982
+ dp:hover .dt-copy,
983
+ .dt-copy:focus-visible {
984
+ display: block;
985
+ }
986
+
987
+ .dt-copy:hover {
988
+ background: var(--dt-bg-modifier-hover);
989
+ }
990
+
991
+ /* ==========================================================================
992
+ 8. SYSTEM MESSAGE + SLASH COMMAND
993
+ ========================================================================== */
994
+
995
+ /* System messages render entirely from markup and attributes: no JS needed. */
996
+ :where(dsy) {
997
+ position: relative;
998
+ display: block;
999
+ margin: 0;
1000
+ padding: 2px var(--dt-msg-padding-right) 2px
1001
+ calc(var(--dt-gutter) + var(--dt-avatar-size) + var(--dt-avatar-gap));
1002
+ background: transparent;
1003
+ color: var(--dt-text-muted);
1004
+ font-size: 0.9375rem;
1005
+ }
1006
+
1007
+ :where(dsy)::before {
1008
+ content: '';
1009
+ position: absolute;
1010
+ top: 7px;
1011
+ left: calc(var(--dt-gutter) + (var(--dt-avatar-size) - 16px) / 2);
1012
+ width: 16px;
1013
+ height: 16px;
1014
+ border-radius: 50%;
1015
+ background: var(--dt-bg-modifier-hover);
1016
+ }
1017
+
1018
+ dsy[type='join']::before {
1019
+ background: var(--dt-status-online);
1020
+ }
1021
+
1022
+ dsy[type='leave']::before,
1023
+ dsy[type='missed-call']::before,
1024
+ dsy[type='alert']::before,
1025
+ dsy[type='error']::before {
1026
+ background: var(--dt-status-dnd);
1027
+ }
1028
+
1029
+ dsy[type='boost']::before {
1030
+ background: #ff73fa;
1031
+ }
1032
+
1033
+ dsy[type='pin']::before {
1034
+ background: var(--dt-status-idle);
1035
+ }
1036
+
1037
+ dsy[type='upgrade']::before,
1038
+ dsy[type='thread']::before,
1039
+ dsy[type='edit']::before,
1040
+ dsy[type='call']::before {
1041
+ background: var(--dt-brand);
1042
+ }
1043
+
1044
+ dsy a,
1045
+ dsy :where(dl) {
1046
+ color: var(--dt-text-muted);
1047
+ font-weight: 500;
1048
+ }
1049
+
1050
+ dsy .dt-system-time {
1051
+ margin-left: 4px;
1052
+ font-size: 0.75rem;
1053
+ color: var(--dt-text-muted);
1054
+ }
1055
+
1056
+ /* Slash / context command rows */
1057
+ :where(dcmd) {
1058
+ position: relative;
1059
+ display: block;
1060
+ margin: 0 0 2px;
1061
+ padding: 0;
1062
+ font-size: 0.9375rem;
1063
+ }
1064
+
1065
+ dcmd:not([data-dt-ready])::before {
1066
+ content: attr(author) ' used /' attr(command) '\00a0';
1067
+ font-weight: 500;
1068
+ color: var(--dt-text-normal);
1069
+ }
1070
+
1071
+ /* Avatar placeholder used when no avatar can be resolved, so an archived
1072
+ transcript never depends on a remote image to render its gutter. */
1073
+ .dt-avatar--initials {
1074
+ display: flex;
1075
+ align-items: center;
1076
+ justify-content: center;
1077
+ color: #ffffff;
1078
+ font-size: 0.875rem;
1079
+ font-weight: 600;
1080
+ text-transform: uppercase;
1081
+ }
1082
+
1083
+ /* ==========================================================================
1084
+ 9. THREADS
1085
+ ========================================================================== */
1086
+
1087
+ :where(dth) {
1088
+ position: relative;
1089
+ display: block;
1090
+ margin: 4px 0 0;
1091
+ padding: 4px 120px 4px 12px;
1092
+ border-left: 2px solid var(--dt-line-color);
1093
+ font-size: 0.875rem;
1094
+ color: var(--dt-text-muted);
1095
+ }
1096
+
1097
+ dth:not([data-dt-ready])::before {
1098
+ content: attr(name);
1099
+ font-weight: 500;
1100
+ color: var(--dt-text-normal);
1101
+ }
1102
+
1103
+ dth[cta]::after {
1104
+ content: attr(cta) ' ' '\203a';
1105
+ position: absolute;
1106
+ top: 4px;
1107
+ right: 8px;
1108
+ font-size: 0.875rem;
1109
+ font-weight: 500;
1110
+ color: var(--dt-text-muted);
1111
+ pointer-events: none;
1112
+ }
1113
+
1114
+ dthm {
1115
+ display: block;
1116
+ margin: 2px 0;
1117
+ font-size: 0.875rem;
1118
+ }
1119
+
1120
+ dthm:not([data-dt-ready])::before {
1121
+ content: attr(author) '\00a0';
1122
+ font-weight: 500;
1123
+ color: var(--dt-role-default);
1124
+ }
1125
+
1126
+ /* ==========================================================================
1127
+ 10. REPLY
1128
+ ========================================================================== */
1129
+
1130
+ :where(drp) {
1131
+ position: relative;
1132
+ display: block;
1133
+ margin: 0 0 2px;
1134
+ color: var(--dt-text-normal);
1135
+ font-size: 0.875rem;
1136
+ line-height: 1.125rem;
1137
+ min-height: 1.125rem;
1138
+ }
1139
+
1140
+ drp:not([data-dt-ready])::before {
1141
+ content: attr(author) '\00a0';
1142
+ font-weight: 500;
1143
+ color: var(--dt-role-default);
1144
+ }
1145
+
1146
+ drp[mentions]:not([data-dt-ready])::before {
1147
+ content: '@' attr(author) '\00a0';
1148
+ }
1149
+
1150
+ drp[deleted] > * {
1151
+ display: none;
1152
+ }
1153
+
1154
+ drp[deleted]::before {
1155
+ content: 'Original message was deleted';
1156
+ font-style: italic;
1157
+ font-weight: 400;
1158
+ color: var(--dt-text-muted);
1159
+ }
1160
+
1161
+ /* A reply preview sits above the author row (Discord's layout): the reply takes
1162
+ the first grid row, so the avatar and the author name share the second. */
1163
+ .dt-msg--has-reply > :where(drp),
1164
+ .dt-msg--has-reply > :where(dcmd) {
1165
+ grid-column: 2;
1166
+ grid-row: 1;
1167
+ }
1168
+
1169
+ .dt-msg--has-reply > .dt-avatar,
1170
+ .dt-msg--has-reply > .dt-content {
1171
+ grid-row: 2;
1172
+ }
1173
+
1174
+ /* Curved connector, drawn only once the reply sits in the content column. */
1175
+ dm[data-dt-ready] :where(drp)::after {
1176
+ content: '';
1177
+ position: absolute;
1178
+ left: -36px;
1179
+ bottom: -2px;
1180
+ width: 36px;
1181
+ height: 12px;
1182
+ border-left: 2px solid var(--dt-line-color);
1183
+ border-top: 2px solid var(--dt-line-color);
1184
+ border-top-left-radius: 6px;
1185
+ pointer-events: none;
1186
+ }
1187
+
1188
+ drp[data-dt-ready] .dt-reply-avatar {
1189
+ display: inline-block;
1190
+ width: var(--dt-reply-avatar-size);
1191
+ height: var(--dt-reply-avatar-size);
1192
+ margin-right: 4px;
1193
+ border-radius: 50%;
1194
+ overflow: hidden;
1195
+ vertical-align: -3px;
1196
+ background: var(--dt-bg-tertiary);
1197
+ }
1198
+
1199
+ drp[data-dt-ready] .dt-reply-avatar img {
1200
+ display: block;
1201
+ width: 100%;
1202
+ height: 100%;
1203
+ object-fit: cover;
1204
+ }
1205
+
1206
+ drp[data-dt-ready] .dt-reply-author {
1207
+ margin-right: 4px;
1208
+ font-weight: 500;
1209
+ color: var(--dt-role-default);
1210
+ }
1211
+
1212
+ drp[data-dt-ready] .dt-reply-author:hover {
1213
+ text-decoration: underline;
1214
+ cursor: pointer;
1215
+ }
1216
+
1217
+ drp[data-dt-ready] .dt-reply-avatar,
1218
+ drp[data-dt-ready] .dt-reply-author {
1219
+ -webkit-user-select: none;
1220
+ user-select: none;
1221
+ }
1222
+
1223
+ dms[compact-mode] :where(drp)::after {
1224
+ display: none;
1225
+ }
1226
+
1227
+ /* ==========================================================================
1228
+ 11. EMBEDS
1229
+ ========================================================================== */
1230
+
1231
+ :where(de) {
1232
+ position: relative;
1233
+ display: block;
1234
+ margin: 8px 0 0;
1235
+ padding: 2px 16px 16px 12px;
1236
+ border: 1px solid var(--dt-border);
1237
+ border-left: 4px solid var(--dt-embed-color, var(--dt-embed-default-color));
1238
+ border-radius: var(--dt-radius-sm);
1239
+ background: var(--dt-bg-embed);
1240
+ max-width: 516px;
1241
+ color: var(--dt-text-normal);
1242
+ font-size: 0.875rem;
1243
+ line-height: 1.125rem;
1244
+ }
1245
+
1246
+ .dt-embed-content {
1247
+ min-width: 0;
1248
+ }
1249
+
1250
+ de[thumbnail] .dt-embed-content,
1251
+ de[data-dt-thumbnail] .dt-embed-content,
1252
+ de:has(.dt-embed-thumbnail) .dt-embed-content {
1253
+ padding-right: 96px;
1254
+ }
1255
+
1256
+ /* Without the JS-built wrapper, inset the whole embed body instead. */
1257
+ de[thumbnail]:not(:has(.dt-embed-content)),
1258
+ de[data-dt-thumbnail]:not(:has(.dt-embed-content)),
1259
+ de:has(.dt-embed-thumbnail):not(:has(.dt-embed-content)) {
1260
+ padding-right: 104px;
1261
+ }
1262
+
1263
+ /* Attribute-only embed header, so a title survives without JavaScript.
1264
+ Preferred durable form: emit <span class="dt-embed-author"> and
1265
+ <span class="dt-embed-title"> markup instead (see docs/SPEC.md). */
1266
+ de:not([data-dt-ready])[embed-title]::before {
1267
+ content: attr(embed-title);
1268
+ display: block;
1269
+ margin: 0 0 8px;
1270
+ font-size: 1rem;
1271
+ font-weight: 600;
1272
+ line-height: 1.375rem;
1273
+ color: var(--dt-text-strong);
1274
+ }
1275
+
1276
+ de:not([data-dt-ready]):not([embed-title])[author-name]::before {
1277
+ content: attr(author-name);
1278
+ display: block;
1279
+ margin: 0 0 8px;
1280
+ font-weight: 600;
1281
+ line-height: 1.125rem;
1282
+ }
1283
+
1284
+ .dt-embed-provider {
1285
+ display: block;
1286
+ margin-bottom: 8px;
1287
+ color: var(--dt-text-normal);
1288
+ }
1289
+
1290
+ .dt-embed-author {
1291
+ display: flex;
1292
+ align-items: center;
1293
+ gap: 8px;
1294
+ margin-bottom: 8px;
1295
+ font-size: 0.875rem;
1296
+ font-weight: 600;
1297
+ line-height: 1.125rem;
1298
+ }
1299
+
1300
+ .dt-embed-author img {
1301
+ width: 24px;
1302
+ height: 24px;
1303
+ border-radius: 50%;
1304
+ object-fit: cover;
1305
+ }
1306
+
1307
+ .dt-embed-author a {
1308
+ color: var(--dt-text-normal);
1309
+ text-decoration: none;
1310
+ }
1311
+
1312
+ .dt-embed-author a:hover {
1313
+ text-decoration: underline;
1314
+ }
1315
+
1316
+ .dt-embed-title {
1317
+ display: block;
1318
+ margin: 0 0 8px;
1319
+ font-size: 1rem;
1320
+ font-weight: 600;
1321
+ line-height: 1.375rem;
1322
+ color: var(--dt-text-strong);
1323
+ text-decoration: none;
1324
+ }
1325
+
1326
+ .dt-embed-title--link,
1327
+ a.dt-embed-title:hover,
1328
+ .dt-embed-title a:hover {
1329
+ text-decoration: underline;
1330
+ }
1331
+
1332
+ a.dt-embed-title {
1333
+ color: var(--dt-text-link);
1334
+ }
1335
+
1336
+ .dt-embed-thumbnail {
1337
+ position: absolute;
1338
+ top: 8px;
1339
+ right: 16px;
1340
+ width: 80px;
1341
+ height: 80px;
1342
+ border-radius: var(--dt-radius-sm);
1343
+ overflow: hidden;
1344
+ background: var(--dt-bg-tertiary);
1345
+ }
1346
+
1347
+ .dt-embed-thumbnail img {
1348
+ display: block;
1349
+ width: 100%;
1350
+ height: 100%;
1351
+ object-fit: cover;
1352
+ }
1353
+
1354
+ :where(ded) {
1355
+ display: block;
1356
+ margin: 0;
1357
+ font-size: 0.875rem;
1358
+ line-height: 1.125rem;
1359
+ white-space: pre-line;
1360
+ }
1361
+
1362
+ :where(defs) {
1363
+ display: grid;
1364
+ grid-template-columns: repeat(12, minmax(0, 1fr));
1365
+ gap: 8px;
1366
+ margin-top: 8px;
1367
+ }
1368
+
1369
+ :where(def) {
1370
+ grid-column: 1 / -1;
1371
+ min-width: 0;
1372
+ font-size: 0.875rem;
1373
+ line-height: 1.125rem;
1374
+ }
1375
+
1376
+ :where(def[inline]) {
1377
+ grid-column: span 4;
1378
+ }
1379
+
1380
+ de[thumbnail] :where(def[inline]),
1381
+ de[data-dt-thumbnail] :where(def[inline]) {
1382
+ grid-column: span 6;
1383
+ }
1384
+
1385
+ .dt-embed-field-title {
1386
+ display: block;
1387
+ margin: 0 0 2px;
1388
+ font-weight: 600;
1389
+ color: var(--dt-text-strong);
1390
+ }
1391
+
1392
+ def:not([data-dt-ready]):not(:has(.dt-embed-field-title))::before {
1393
+ content: attr(field-title);
1394
+ display: block;
1395
+ margin-bottom: 2px;
1396
+ font-weight: 600;
1397
+ color: var(--dt-text-strong);
1398
+ }
1399
+
1400
+ .dt-embed-image {
1401
+ display: block;
1402
+ margin-top: 16px;
1403
+ }
1404
+
1405
+ .dt-embed-image img,
1406
+ .dt-embed-image video {
1407
+ display: block;
1408
+ max-width: 100%;
1409
+ max-height: 300px;
1410
+ border-radius: var(--dt-radius-sm);
1411
+ object-fit: contain;
1412
+ }
1413
+
1414
+ :where(defo) {
1415
+ display: flex;
1416
+ align-items: center;
1417
+ gap: 8px;
1418
+ margin-top: 8px;
1419
+ font-size: 0.75rem;
1420
+ line-height: 1rem;
1421
+ color: var(--dt-text-muted);
1422
+ }
1423
+
1424
+ defo:empty::before {
1425
+ content: attr(footer);
1426
+ }
1427
+
1428
+ .dt-embed-footer-icon {
1429
+ width: 20px;
1430
+ height: 20px;
1431
+ border-radius: 50%;
1432
+ overflow: hidden;
1433
+ background: var(--dt-bg-tertiary);
1434
+ }
1435
+
1436
+ .dt-embed-footer-icon img {
1437
+ display: block;
1438
+ width: 100%;
1439
+ height: 100%;
1440
+ object-fit: cover;
1441
+ }
1442
+
1443
+ .dt-embed-footer-sep {
1444
+ display: inline-block;
1445
+ width: 4px;
1446
+ height: 4px;
1447
+ border-radius: 50%;
1448
+ background: var(--dt-text-muted);
1449
+ opacity: 0.6;
1450
+ }
1451
+
1452
+ /* ==========================================================================
1453
+ 12. ATTACHMENTS
1454
+ ========================================================================== */
1455
+
1456
+ :where(dats) {
1457
+ display: flex;
1458
+ flex-direction: column;
1459
+ align-items: flex-start;
1460
+ gap: 8px;
1461
+ margin-top: 8px;
1462
+ max-width: 550px;
1463
+ }
1464
+
1465
+ :where(dats:empty) {
1466
+ display: none;
1467
+ }
1468
+
1469
+ /* --- images ------------------------------------------------------------- */
1470
+
1471
+ :where(dimg) {
1472
+ position: relative;
1473
+ display: block;
1474
+ max-width: 100%;
1475
+ border-radius: var(--dt-radius-md);
1476
+ overflow: hidden;
1477
+ line-height: 0;
1478
+ }
1479
+
1480
+ :where(dimg) img {
1481
+ display: block;
1482
+ max-width: 100%;
1483
+ max-height: 350px;
1484
+ border-radius: var(--dt-radius-md);
1485
+ object-fit: contain;
1486
+ cursor: pointer;
1487
+ }
1488
+
1489
+ :where(dimg) a {
1490
+ display: block;
1491
+ }
1492
+
1493
+ /* Attribute-only form, filled in by JS; visible placeholder if JS never runs. */
1494
+ dimg[url]:not([data-dt-ready]):not(:has(img)) {
1495
+ min-width: 220px;
1496
+ min-height: 120px;
1497
+ border: 1px dashed var(--dt-border-strong);
1498
+ }
1499
+
1500
+ /* --- spoiler-wrapped media --------------------------------------------- */
1501
+
1502
+ dimg[spoiler] img,
1503
+ dvid[spoiler] video {
1504
+ filter: blur(44px);
1505
+ transition: filter var(--dt-transition);
1506
+ }
1507
+
1508
+ dimg[spoiler][data-dt-revealed] img,
1509
+ dvid[spoiler][data-dt-revealed] video {
1510
+ filter: none;
1511
+ }
1512
+
1513
+ .dt-spoiler-overlay {
1514
+ position: absolute;
1515
+ inset: 0;
1516
+ z-index: 1;
1517
+ display: flex;
1518
+ align-items: center;
1519
+ justify-content: center;
1520
+ border: 0;
1521
+ border-radius: var(--dt-radius-md);
1522
+ background: rgba(0, 0, 0, 0.35);
1523
+ color: #ffffff;
1524
+ font-family: var(--dt-font);
1525
+ font-size: 0.75rem;
1526
+ font-weight: 700;
1527
+ letter-spacing: 0.08em;
1528
+ text-transform: uppercase;
1529
+ cursor: pointer;
1530
+ }
1531
+
1532
+ .dt-spoiler-overlay:hover {
1533
+ background: rgba(0, 0, 0, 0.45);
1534
+ }
1535
+
1536
+ dimg[data-dt-revealed] > .dt-spoiler-overlay,
1537
+ dvid[data-dt-revealed] > .dt-spoiler-overlay {
1538
+ display: none;
1539
+ }
1540
+
1541
+ /* --- files -------------------------------------------------------------- */
1542
+
1543
+ :where(dfil) {
1544
+ display: flex;
1545
+ align-items: center;
1546
+ gap: 12px;
1547
+ width: 100%;
1548
+ max-width: 420px;
1549
+ padding: 12px;
1550
+ border: 1px solid var(--dt-border);
1551
+ border-radius: var(--dt-radius-md);
1552
+ background: var(--dt-bg-secondary);
1553
+ }
1554
+
1555
+ :where(dfil) a {
1556
+ display: flex;
1557
+ align-items: center;
1558
+ gap: 12px;
1559
+ width: 100%;
1560
+ color: inherit;
1561
+ text-decoration: none;
1562
+ }
1563
+
1564
+ /* Attribute-only form: renders name and size with zero JavaScript. */
1565
+ dfil:empty {
1566
+ display: grid;
1567
+ grid-template-columns: 30px minmax(0, 1fr);
1568
+ align-content: center;
1569
+ column-gap: 12px;
1570
+ }
1571
+
1572
+ dfil:empty::before {
1573
+ grid-column: 1;
1574
+ grid-row: 1 / span 2;
1575
+ content: 'FILE';
1576
+ display: flex;
1577
+ align-items: center;
1578
+ justify-content: center;
1579
+ width: 30px;
1580
+ height: 30px;
1581
+ border-radius: var(--dt-radius-sm);
1582
+ background: var(--dt-bg-modifier-hover);
1583
+ color: var(--dt-text-muted);
1584
+ font-size: 0.5625rem;
1585
+ font-weight: 700;
1586
+ }
1587
+
1588
+ dfil[type]:empty::before {
1589
+ content: attr(type);
1590
+ }
1591
+
1592
+ dfil:empty::after {
1593
+ grid-column: 2;
1594
+ content: attr(name) '\A' attr(bytes) ' ' attr(bytes-unit);
1595
+ white-space: pre-line;
1596
+ line-height: 1.25rem;
1597
+ }
1598
+
1599
+ dfil[bytes='']:empty::after,
1600
+ dfil:not([bytes]):empty::after {
1601
+ content: attr(name);
1602
+ }
1603
+
1604
+ .dt-file-icon {
1605
+ display: flex;
1606
+ align-items: center;
1607
+ justify-content: center;
1608
+ width: 30px;
1609
+ height: 30px;
1610
+ border-radius: var(--dt-radius-sm);
1611
+ background: var(--dt-bg-modifier-hover);
1612
+ color: var(--dt-text-muted);
1613
+ font-size: 0.5625rem;
1614
+ font-weight: 700;
1615
+ text-transform: uppercase;
1616
+ flex: none;
1617
+ }
1618
+
1619
+ .dt-file-meta {
1620
+ display: flex;
1621
+ flex-direction: column;
1622
+ min-width: 0;
1623
+ }
1624
+
1625
+ .dt-file-name {
1626
+ font-size: 0.875rem;
1627
+ font-weight: 500;
1628
+ color: var(--dt-text-strong);
1629
+ overflow: hidden;
1630
+ text-overflow: ellipsis;
1631
+ white-space: nowrap;
1632
+ }
1633
+
1634
+ .dt-file-size {
1635
+ font-size: 0.75rem;
1636
+ color: var(--dt-text-muted);
1637
+ }
1638
+
1639
+ /* --- audio / video ------------------------------------------------------ */
1640
+
1641
+ :where(daud) {
1642
+ display: block;
1643
+ width: 100%;
1644
+ max-width: 420px;
1645
+ }
1646
+
1647
+ :where(daud) audio {
1648
+ display: block;
1649
+ width: 100%;
1650
+ max-width: 420px;
1651
+ border-radius: var(--dt-radius-md);
1652
+ outline: none;
1653
+ }
1654
+
1655
+ :where(dvid) {
1656
+ position: relative;
1657
+ display: block;
1658
+ max-width: 100%;
1659
+ border-radius: var(--dt-radius-md);
1660
+ overflow: hidden;
1661
+ background: #000000;
1662
+ line-height: 0;
1663
+ }
1664
+
1665
+ :where(dvid) video {
1666
+ display: block;
1667
+ max-width: 100%;
1668
+ max-height: 400px;
1669
+ border-radius: var(--dt-radius-md);
1670
+ }
1671
+
1672
+ :where(dvid) video:focus-visible,
1673
+ :where(daud) audio:focus-visible {
1674
+ box-shadow: 0 0 0 2px var(--dt-focus-ring);
1675
+ }
1676
+
1677
+ /* ==========================================================================
1678
+ 13. REACTIONS
1679
+ ========================================================================== */
1680
+
1681
+ :where(drs) {
1682
+ display: flex;
1683
+ flex-wrap: wrap;
1684
+ align-items: center;
1685
+ gap: 4px;
1686
+ margin-top: 8px;
1687
+ }
1688
+
1689
+ :where(drs:empty) {
1690
+ display: none;
1691
+ }
1692
+
1693
+ :where(dr) {
1694
+ display: inline-flex;
1695
+ align-items: center;
1696
+ gap: 6px;
1697
+ min-width: 36px;
1698
+ height: 24px;
1699
+ padding: 2px 6px;
1700
+ border: 1px solid var(--dt-reaction-border);
1701
+ border-radius: var(--dt-radius-md);
1702
+ background: var(--dt-reaction-bg);
1703
+ color: var(--dt-text-normal);
1704
+ font-family: var(--dt-font);
1705
+ font-size: 0.875rem;
1706
+ line-height: 1;
1707
+ cursor: pointer;
1708
+ -webkit-user-select: none;
1709
+ user-select: none;
1710
+ transition:
1711
+ background var(--dt-transition),
1712
+ border-color var(--dt-transition);
1713
+ }
1714
+
1715
+ :where(dr:hover) {
1716
+ border-color: var(--dt-border-strong);
1717
+ }
1718
+
1719
+ :where(dr[reacted]) {
1720
+ background: var(--dt-reaction-bg-reacted);
1721
+ border-color: var(--dt-reaction-border-reacted);
1722
+ color: var(--dt-reaction-text-reacted);
1723
+ }
1724
+
1725
+ /* Attribute-only form: emoji and count straight from attributes. */
1726
+ dr:not([data-dt-ready])::before {
1727
+ content: attr(emoji);
1728
+ font-size: 1rem;
1729
+ line-height: 1;
1730
+ }
1731
+
1732
+ dr[emoji^='http']:not([data-dt-ready])::before,
1733
+ dr[emoji^='data:']:not([data-dt-ready])::before,
1734
+ dr[emoji^='blob:']:not([data-dt-ready])::before,
1735
+ dr[emoji^='/']:not([data-dt-ready])::before,
1736
+ dr[emoji^='./']:not([data-dt-ready])::before,
1737
+ dr:not([emoji]):not([data-dt-ready])::before {
1738
+ content: none;
1739
+ }
1740
+
1741
+ dr:not([data-dt-ready])::after {
1742
+ content: attr(count);
1743
+ font-size: 0.875rem;
1744
+ font-weight: 500;
1745
+ line-height: 1;
1746
+ }
1747
+
1748
+ dr:not([count]):not([data-dt-ready])::after,
1749
+ dr[count='1']:not([data-dt-ready])::after {
1750
+ content: none;
1751
+ }
1752
+
1753
+ :where(dr) img,
1754
+ .dt-reaction-emoji {
1755
+ display: inline-block;
1756
+ flex: none;
1757
+ width: 1rem;
1758
+ height: 1rem;
1759
+ overflow: hidden;
1760
+ object-fit: contain;
1761
+ vertical-align: middle;
1762
+ }
1763
+
1764
+ .dt-reaction-count {
1765
+ font-size: 0.75rem;
1766
+ font-weight: 600;
1767
+ line-height: 1;
1768
+ color: inherit;
1769
+ }
1770
+
1771
+ /* ==========================================================================
1772
+ 14. ACTION ROW + BUTTONS
1773
+ ========================================================================== */
1774
+
1775
+ :where(dar) {
1776
+ display: flex;
1777
+ flex-wrap: wrap;
1778
+ gap: 8px;
1779
+ margin-top: 8px;
1780
+ max-width: 600px;
1781
+ }
1782
+
1783
+ :where(dbtn) {
1784
+ display: inline-flex;
1785
+ align-items: center;
1786
+ justify-content: center;
1787
+ gap: 6px;
1788
+ min-height: 32px;
1789
+ padding: 2px 16px;
1790
+ border: 0;
1791
+ border-radius: var(--dt-radius-sm);
1792
+ background: var(--dt-button-secondary);
1793
+ color: #ffffff;
1794
+ font-family: var(--dt-font);
1795
+ font-size: 0.875rem;
1796
+ font-weight: 500;
1797
+ line-height: 1rem;
1798
+ white-space: nowrap;
1799
+ cursor: pointer;
1800
+ transition: background var(--dt-transition);
1801
+ }
1802
+
1803
+ :where(dbtn):hover {
1804
+ background: var(--dt-button-secondary-hover);
1805
+ }
1806
+
1807
+ :where(dbtn[type='primary']) {
1808
+ background: var(--dt-button-primary);
1809
+ }
1810
+
1811
+ :where(dbtn[type='primary']):hover {
1812
+ background: var(--dt-button-primary-hover);
1813
+ }
1814
+
1815
+ :where(dbtn[type='success']) {
1816
+ background: var(--dt-button-success);
1817
+ }
1818
+
1819
+ :where(dbtn[type='destructive'], dbtn[type='danger']) {
1820
+ background: var(--dt-button-danger);
1821
+ }
1822
+
1823
+ :where(dbtn[type='link']) {
1824
+ background: var(--dt-button-link);
1825
+ }
1826
+
1827
+ :where(dbtn[disabled]) {
1828
+ opacity: 0.5;
1829
+ cursor: not-allowed;
1830
+ }
1831
+
1832
+ .dt-button-emoji {
1833
+ width: 1rem;
1834
+ height: 1rem;
1835
+ object-fit: contain;
1836
+ }
1837
+
1838
+ .dt-button-link-icon {
1839
+ width: 12px;
1840
+ height: 12px;
1841
+ opacity: 0.9;
1842
+ }
1843
+
1844
+ /* Verified author tag (used inside author info) */
1845
+ :where(dvat) {
1846
+ display: inline-flex;
1847
+ align-items: center;
1848
+ justify-content: center;
1849
+ gap: 3px;
1850
+ height: 16px;
1851
+ padding: 0 5px 0 4px;
1852
+ border-radius: 4px;
1853
+ background: var(--dt-brand);
1854
+ color: var(--dt-text-on-brand);
1855
+ font-size: 0.625rem;
1856
+ font-weight: 600;
1857
+ line-height: 1;
1858
+ letter-spacing: 0.02em;
1859
+ text-transform: uppercase;
1860
+ vertical-align: -2px;
1861
+ }
1862
+
1863
+ :where(dvat)::before {
1864
+ content: '\2713';
1865
+ font-size: 0.75rem;
1866
+ line-height: 1;
1867
+ }
1868
+
1869
+ :where(dvat:not([verified]))::before {
1870
+ content: none;
1871
+ }
1872
+
1873
+ :where(dvat)::after {
1874
+ content: 'APP';
1875
+ }
1876
+
1877
+ /* ==========================================================================
1878
+ 15. A11Y + PRINT
1879
+ ========================================================================== */
1880
+
1881
+ .dt-divider {
1882
+ height: 1px;
1883
+ margin: 8px 0;
1884
+ border: 0;
1885
+ background: var(--dt-divider);
1886
+ }
1887
+
1888
+ .dt-sr-only {
1889
+ position: absolute;
1890
+ width: 1px;
1891
+ height: 1px;
1892
+ margin: -1px;
1893
+ padding: 0;
1894
+ overflow: hidden;
1895
+ clip: rect(0, 0, 0, 0);
1896
+ white-space: nowrap;
1897
+ border: 0;
1898
+ }
1899
+
1900
+ :where(dm, de, dats, drs) :focus-visible {
1901
+ outline: none;
1902
+ box-shadow: 0 0 0 2px var(--dt-focus-ring);
1903
+ }
1904
+
1905
+ @media (prefers-reduced-motion: reduce) {
1906
+ :where(dsp),
1907
+ :where(dr),
1908
+ :where(dbtn),
1909
+ dimg img {
1910
+ transition: none;
1911
+ }
1912
+ }
1913
+
1914
+ @media (max-width: 480px) {
1915
+ :where(dms) {
1916
+ --dt-msg-padding-right: 16px;
1917
+ }
1918
+
1919
+ :where(de),
1920
+ :where(dats) {
1921
+ max-width: 100%;
1922
+ }
1923
+ }
1924
+
1925
+ @media print {
1926
+ :root,
1927
+ dms,
1928
+ dm {
1929
+ --dt-bg-primary: #ffffff;
1930
+ --dt-bg-secondary: #f2f3f5;
1931
+ --dt-bg-embed: #f2f3f5;
1932
+ --dt-bg-code: #f2f3f5;
1933
+ --dt-text-normal: #000000;
1934
+ --dt-text-strong: #000000;
1935
+ --dt-text-muted: #444444;
1936
+ }
1937
+
1938
+ :where(dsp) {
1939
+ background: none;
1940
+ color: inherit;
1941
+ }
1942
+
1943
+ .dt-copy,
1944
+ .dt-spoiler-overlay {
1945
+ display: none !important;
1946
+ }
1947
+ }