@troshab/slidev-theme-troshab 0.1.4 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/setup/shiki.ts CHANGED
@@ -15,9 +15,10 @@ const theme = createCssVariablesTheme({
15
15
  })
16
16
 
17
17
  /**
18
- * Transformer that adds `data-lang` attribute to <pre> elements.
19
- * CSS in base.css uses `::before { content: attr(data-lang) }` to render
20
- * a language label bar above every code block.
18
+ * Transformer that adds `data-lang` and `data-filename` attributes to <pre>.
19
+ * CSS in base.css uses:
20
+ * ::before { content: attr(data-lang) } — language label, left
21
+ * ::after { content: attr(data-filename) } — filename label, right
21
22
  */
22
23
  const langLabelTransformer: ShikiTransformer = {
23
24
  name: 'lang-label',
@@ -26,6 +27,14 @@ const langLabelTransformer: ShikiTransformer = {
26
27
  if (lang && lang !== 'text') {
27
28
  node.properties['data-lang'] = lang
28
29
  }
30
+ // Parse {filename="..."} from code block meta
31
+ const meta = this.options.meta?.__raw
32
+ if (meta) {
33
+ const match = meta.match(/filename=["']([^"']+)["']/)
34
+ if (match) {
35
+ node.properties['data-filename'] = match[1]
36
+ }
37
+ }
29
38
  },
30
39
  }
31
40
 
package/styles/base.css CHANGED
@@ -12,56 +12,17 @@
12
12
  @import './colors.css';
13
13
 
14
14
  /* ============================================
15
- 1. @font-face — IBM Plex (local)
15
+ 1. @font-face — IBM Plex via @fontsource
16
+ Provides proper unicode-range subsetting
17
+ (latin, cyrillic, cyrillic-ext, etc.)
16
18
  ============================================ */
17
19
 
18
- @font-face {
19
- font-family: 'IBM Plex Sans';
20
- src: url('../fonts/IBMPlexSans-Regular.woff2') format('woff2');
21
- font-weight: 400;
22
- font-style: normal;
23
- font-display: swap;
24
- }
25
-
26
- @font-face {
27
- font-family: 'IBM Plex Sans';
28
- src: url('../fonts/IBMPlexSans-Medium.woff2') format('woff2');
29
- font-weight: 500;
30
- font-style: normal;
31
- font-display: swap;
32
- }
33
-
34
- @font-face {
35
- font-family: 'IBM Plex Sans';
36
- src: url('../fonts/IBMPlexSans-SemiBold.woff2') format('woff2');
37
- font-weight: 600;
38
- font-style: normal;
39
- font-display: swap;
40
- }
41
-
42
- @font-face {
43
- font-family: 'IBM Plex Sans';
44
- src: url('../fonts/IBMPlexSans-Bold.woff2') format('woff2');
45
- font-weight: 700;
46
- font-style: normal;
47
- font-display: swap;
48
- }
49
-
50
- @font-face {
51
- font-family: 'IBM Plex Mono';
52
- src: url('../fonts/IBMPlexMono-Regular.woff2') format('woff2');
53
- font-weight: 400;
54
- font-style: normal;
55
- font-display: swap;
56
- }
57
-
58
- @font-face {
59
- font-family: 'IBM Plex Mono';
60
- src: url('../fonts/IBMPlexMono-Medium.woff2') format('woff2');
61
- font-weight: 500;
62
- font-style: normal;
63
- font-display: swap;
64
- }
20
+ @import '@fontsource/ibm-plex-sans/400.css';
21
+ @import '@fontsource/ibm-plex-sans/500.css';
22
+ @import '@fontsource/ibm-plex-sans/600.css';
23
+ @import '@fontsource/ibm-plex-sans/700.css';
24
+ @import '@fontsource/ibm-plex-mono/400.css';
25
+ @import '@fontsource/ibm-plex-mono/500.css';
65
26
 
66
27
  /* ============================================
67
28
  2. CSS Custom Properties (Typography & Layout)
@@ -91,19 +52,24 @@
91
52
  --font-weight-semibold: 600;
92
53
  --font-weight-bold: 700;
93
54
 
94
- /* --- Line Heights (role-based) --- */
95
- --line-height-heading: 1.15; /* tight for 1-2 line headings */
96
- --line-height-body: 1.35; /* slides with short text blocks */
97
- --line-height-reading: 1.5; /* longer paragraphs, BDA recommended */
98
- --line-height-list: 1.4;
99
- --line-height-code: 1.4;
55
+ /* --- Line Heights (role-based) ---
56
+ Chung (2004, Vision Research): optimal 1.25-1.5 for body.
57
+ Bringhurst "Elements of Typographic Style": headings 1.1-1.25, body 1.3-1.5.
58
+ Kolers et al. (1981): reading speed U-curve — extremes reduce speed. */
59
+ --line-height-heading: 1.3; /* IBM Plex Sans glyph box (1.275) + safety margin */
60
+ --line-height-body: 1.4; /* Chung mid — comfortable for slides */
61
+ --line-height-reading: 1.5; /* Chung upper — BDA recommended for paragraphs */
62
+ --line-height-list: 1.4; /* matches body for consistency */
63
+ --line-height-code: 1.5; /* mono needs more room due to fixed glyph width */
100
64
 
101
65
  /* --- Letter & Word Spacing (dyslexia-friendly) ---
102
- Zorzi et al. (2012, PNAS): letter-spacing reduces crowding.
103
- BDA 2018: word/letter ratio >= 3.5x. Ours: 4.0x (above minimum). */
104
- --letter-spacing-body: 0.02em;
105
- --word-spacing-body: 0.08em;
106
- --letter-spacing-heading: 0.01em;
66
+ Zorzi et al. (2012, PNAS 109:11455): letter-spacing +20% reading speed.
67
+ Pelli et al. (2007): crowding as dyslexic bottleneck space symbols.
68
+ BDA 2018: word/letter ratio >= 3.5x. Ours: 4.0x (above minimum).
69
+ Presentation tradeoff: full BDA (0.12em/0.16em) too wide for large display type. */
70
+ --letter-spacing-body: 0.025em; /* slight spacing — Zorzi crowding benefit */
71
+ --word-spacing-body: 0.1em; /* word/letter ratio = 4.0x (BDA >= 3.5x) */
72
+ --letter-spacing-heading: 0.005em; /* near-neutral — large headlines don't need extra spacing */
107
73
 
108
74
  /* --- Spacing System (8-point grid) ---
109
75
  Based on 8px base unit for visual rhythm.
@@ -133,15 +99,43 @@
133
99
  --slide-padding: var(--space-lg);
134
100
  --slide-gap: var(--space-sm);
135
101
 
136
- /* --- Safe-zone for projectors/TV overscan (5% default) --- */
137
- --safe-inset-x: 5%;
138
- --safe-inset-y: 5%;
102
+ /* --- Safe-zone for projectors/TV overscan ---
103
+ SMPTE RP 27.3: 5% action-safe zone. Equal inset on all sides — asymmetric % causes
104
+ aspect-ratio dependent visual shift. Using rem keeps proportional across canvas sizes. */
105
+ --safe-inset-x: 3rem; /* ~94px at 31.347px root */
106
+ --safe-inset-y: 3rem; /* equal to x for symmetric safe zone */
139
107
 
140
108
  /* --- Border Width Tokens (rem-based, scale with canvas) --- */
141
109
  --border-thin: 0.0625rem; /* ≈ 1px at 980 → ~2px at 1920 */
142
110
  --border-medium: 0.125rem; /* ≈ 2px → ~4px */
143
111
  --border-thick: 0.1875rem; /* ≈ 3px → ~6px */
144
112
  --border-accent: 0.25rem; /* ≈ 4px → ~8px */
113
+
114
+ /* --- Fine spacing (< 8px, for tight UI elements) --- */
115
+ --space-3xs: 0.125rem; /* 2px */
116
+ --space-2xs: 0.25rem; /* 4px */
117
+ --space-xs-fine: 0.375rem; /* 6px */
118
+ --space-tight: 0.75rem; /* 12px — between xs and sm */
119
+ --space-code-pt: 2.25rem; /* 36px — code block top padding (for lang label) */
120
+
121
+ /* --- Border Radius Tokens --- */
122
+ --radius-xs: 0.25rem; /* 4px — inline code */
123
+ --radius-sm: 0.375rem; /* 6px — buttons, small cards */
124
+ --radius-md: 0.5rem; /* 8px — cards, panels */
125
+ --radius-pill: 9999px; /* full pill shape */
126
+
127
+ /* --- Transform Compensation ---
128
+ Padding to compensate for transform: scale() visual overflow beyond layout box.
129
+ Calculated for scale(1.15) on medium elements (~63px markers).
130
+ Formula: (scale - 1) * element_size / 2 ≈ 4.7px per side. */
131
+ --transform-scale-comp: 0.1875rem; /* 6px — for scale(1.15) on 63px elements */
132
+
133
+ /* --- Typography ratios (em-based, font-size relative) --- */
134
+ --font-size-smaller: 0.8em; /* for <sup>, <sub>, <small> */
135
+ --letter-spacing-emphasis: 0.05em; /* for small uppercase labels */
136
+ --underline-offset: 0.15em; /* text-underline-offset */
137
+ --em-quarter: 0.25em; /* list item spacing */
138
+ --em-half: 1.5em; /* indent / nested */
145
139
  }
146
140
 
147
141
  /* ============================================
@@ -177,6 +171,17 @@
177
171
  text-align: left;
178
172
  }
179
173
 
174
+ /* Mixed-font line-box fix: <p>/<li> containing inline <code> mix IBM Plex Sans
175
+ * and Plex Mono glyph metrics. Mono's effective descent is slightly larger
176
+ * than Sans at the same font-size, so the line box expands by ~1px — which
177
+ * rounds up to scrollHeight > clientHeight. Bumping line-height from 1.4 to
178
+ * 1.45 on these specific paragraphs absorbs the mixed-font rounding without
179
+ * visible impact on prose-only paragraphs. */
180
+ .slidev-layout p:has(code),
181
+ .slidev-layout li:has(code) {
182
+ line-height: 1.45;
183
+ }
184
+
180
185
  /* --- Headings --- */
181
186
  .slidev-layout h1,
182
187
  .slidev-layout h2,
@@ -286,9 +291,9 @@
286
291
  }
287
292
 
288
293
  .slidev-layout pre {
289
- border-radius: 0.5rem;
290
- margin-top: 1rem;
291
- margin-bottom: 1rem;
294
+ border-radius: var(--radius-md);
295
+ margin-top: var(--space-sm);
296
+ margin-bottom: var(--space-sm);
292
297
  white-space: pre-wrap;
293
298
  overflow-wrap: break-word;
294
299
  }
@@ -311,11 +316,21 @@
311
316
  border: none;
312
317
  }
313
318
 
319
+ /* Slidev wraps code blocks in .slidev-code-wrapper (block, no BFC). Inner
320
+ * <pre> inherits 1rem top/bottom margin from the .slidev-layout pre rule
321
+ * (~31px at canvas font-size). When wrapper is a flex item (slide-col-body),
322
+ * those margins do not collapse with siblings or out of the flex container,
323
+ * inflating body height beyond column clientHeight. Zero margin inside the
324
+ * wrapper; outer spacing is handled by the wrapper itself. */
325
+ .slidev-code-wrapper pre {
326
+ margin: 0;
327
+ }
328
+
314
329
  /* Language label bar above code blocks (via Shiki transformer data-lang attribute) */
315
330
  .slidev-layout pre[data-lang] {
316
331
  position: relative;
317
332
  /* Override Slidev's --slidev-code-padding (applied with !important) to make room for the label */
318
- --slidev-code-padding: 2.25rem 0.5rem 0.5rem 0.5rem;
333
+ --slidev-code-padding: var(--space-code-pt) 0.5rem 0.5rem 0.5rem;
319
334
  }
320
335
 
321
336
  .slidev-layout pre[data-lang]::before {
@@ -324,7 +339,7 @@
324
339
  top: 0;
325
340
  left: 0;
326
341
  right: 0;
327
- padding: 0.25rem 0.75rem;
342
+ padding: var(--space-2xs) 0.75rem;
328
343
  font-family: var(--font-mono);
329
344
  font-size: 0.8em;
330
345
  font-weight: var(--font-weight-medium);
@@ -333,15 +348,29 @@
333
348
  color: var(--color-text-tertiary);
334
349
  border-bottom: var(--border-thin) solid var(--color-border);
335
350
  background: var(--shiki-color-background, var(--color-bg-muted));
336
- border-radius: 0.5rem 0.5rem 0 0;
351
+ border-radius: var(--radius-md) var(--radius-md) 0 0;
352
+ }
353
+
354
+ /* Filename label (right side of header bar) — via {filename="..."} in code fence meta */
355
+ .slidev-layout pre[data-filename]::after {
356
+ content: attr(data-filename);
357
+ position: absolute;
358
+ top: 0;
359
+ right: 0;
360
+ padding: var(--space-2xs) 0.75rem;
361
+ font-family: var(--font-mono);
362
+ font-size: 0.8em;
363
+ font-weight: var(--font-weight-normal);
364
+ color: var(--color-text-muted);
365
+ opacity: 0.7;
337
366
  }
338
367
 
339
368
  .slidev-layout code {
340
- padding-left: 0.375rem;
341
- padding-right: 0.375rem;
342
- padding-top: 0.125rem;
343
- padding-bottom: 0.125rem;
344
- border-radius: 0.25rem;
369
+ padding-left: var(--space-xs-fine);
370
+ padding-right: var(--space-xs-fine);
371
+ padding-top: var(--space-3xs);
372
+ padding-bottom: var(--space-3xs);
373
+ border-radius: var(--radius-xs);
345
374
  background-color: var(--color-bg-muted);
346
375
  }
347
376
 
@@ -356,18 +385,18 @@
356
385
 
357
386
  .slidev-layout table {
358
387
  width: 100%;
359
- margin-top: 1rem;
360
- margin-bottom: 1rem;
388
+ margin-top: var(--space-sm);
389
+ margin-bottom: var(--space-sm);
361
390
  border-collapse: collapse;
362
391
  font-variant-numeric: tabular-nums;
363
392
  }
364
393
 
365
394
  .slidev-layout th,
366
395
  .slidev-layout td {
367
- padding-left: 1rem;
368
- padding-right: 1rem;
369
- padding-top: 0.5rem;
370
- padding-bottom: 0.5rem;
396
+ padding-left: var(--space-sm);
397
+ padding-right: var(--space-sm);
398
+ padding-top: var(--space-xs);
399
+ padding-bottom: var(--space-xs);
371
400
  border: var(--border-thin) solid var(--color-border);
372
401
  text-align: left;
373
402
  }
@@ -382,9 +411,9 @@
382
411
  ============================================ */
383
412
 
384
413
  .slidev-layout blockquote {
385
- padding-left: 1rem;
386
- margin-top: 1rem;
387
- margin-bottom: 1rem;
414
+ padding-left: var(--space-sm);
415
+ margin-top: var(--space-sm);
416
+ margin-bottom: var(--space-sm);
388
417
  border-left: var(--border-accent) solid var(--color-border-strong);
389
418
  color: var(--color-text-secondary);
390
419
  font-style: normal; /* No italic for readability */
@@ -603,10 +632,17 @@
603
632
  display: flex;
604
633
  flex-direction: column;
605
634
  flex: 1;
606
- overflow: auto;
607
635
  min-height: 0;
608
636
  }
609
637
 
638
+ /* Prevent last-child margin from leaking into scrollHeight (margin-collapse artifact) */
639
+ .slide-single > *:last-child,
640
+ .slide-panel-content > *:last-child,
641
+ .slide-col > *:last-child,
642
+ .slide-col-body > *:last-child {
643
+ margin-bottom: 0;
644
+ }
645
+
610
646
  .slide-grid {
611
647
  display: flex;
612
648
  flex: 1;
@@ -614,7 +650,6 @@
614
650
  }
615
651
 
616
652
  .slide-col {
617
- overflow: hidden;
618
653
  display: flex;
619
654
  flex-direction: column;
620
655
  min-width: 0;
@@ -640,13 +675,13 @@
640
675
  }
641
676
 
642
677
  /* Per-column backgrounds */
643
- .slide-col-bg-soft { background: var(--color-bg-soft); border-radius: 0.5rem; padding: 1rem; }
644
- .slide-col-bg-muted { background: var(--color-bg-muted); border-radius: 0.5rem; padding: 1rem; }
645
- .slide-col-bg-primary { background: var(--color-primary); color: var(--color-primary-foreground); border-radius: 0.5rem; padding: 1rem; }
646
- .slide-col-bg-success { background: var(--color-success); color: var(--color-success-foreground); border-radius: 0.5rem; padding: 1rem; }
647
- .slide-col-bg-warning { background: var(--color-warning); color: var(--color-warning-foreground); border-radius: 0.5rem; padding: 1rem; }
648
- .slide-col-bg-danger { background: var(--color-danger); color: var(--color-danger-foreground); border-radius: 0.5rem; padding: 1rem; }
649
- .slide-col-bg-info { background: var(--color-info); color: var(--color-info-foreground); border-radius: 0.5rem; padding: 1rem; }
678
+ .slide-col-bg-soft { background: var(--color-bg-soft); border-radius: var(--radius-md); padding: var(--space-sm); }
679
+ .slide-col-bg-muted { background: var(--color-bg-muted); border-radius: var(--radius-md); padding: var(--space-sm); }
680
+ .slide-col-bg-primary { background: var(--color-primary); color: var(--color-primary-foreground); border-radius: var(--radius-md); padding: var(--space-sm); }
681
+ .slide-col-bg-success { background: var(--color-success); color: var(--color-success-foreground); border-radius: var(--radius-md); padding: var(--space-sm); }
682
+ .slide-col-bg-warning { background: var(--color-warning); color: var(--color-warning-foreground); border-radius: var(--radius-md); padding: var(--space-sm); }
683
+ .slide-col-bg-danger { background: var(--color-danger); color: var(--color-danger-foreground); border-radius: var(--radius-md); padding: var(--space-sm); }
684
+ .slide-col-bg-info { background: var(--color-info); color: var(--color-info-foreground); border-radius: var(--radius-md); padding: var(--space-sm); }
650
685
 
651
686
  .slide-col-bg-primary h1, .slide-col-bg-primary h2, .slide-col-bg-primary h3,
652
687
  .slide-col-bg-success h1, .slide-col-bg-success h2, .slide-col-bg-success h3,
@@ -655,7 +690,7 @@
655
690
  .slide-col-bg-info h1, .slide-col-bg-info h2, .slide-col-bg-info h3 { color: inherit; }
656
691
 
657
692
  /* Per-column border */
658
- .slide-col-border { border: var(--border-thin) solid var(--color-border); border-radius: 0.5rem; padding: 1rem; }
693
+ .slide-col-border { border: var(--border-thin) solid var(--color-border); border-radius: var(--radius-md); padding: var(--space-sm); }
659
694
 
660
695
  /* --- 7c. Split Mode --- */
661
696
  .slide-split {
@@ -672,7 +707,6 @@
672
707
 
673
708
  .slide-panel {
674
709
  position: relative;
675
- overflow: hidden;
676
710
  }
677
711
 
678
712
  .slide-panel-bg-only {
@@ -687,7 +721,6 @@
687
721
 
688
722
  .slide-panel-content {
689
723
  padding: var(--safe-inset-y) var(--safe-inset-x);
690
- overflow: auto;
691
724
  display: flex;
692
725
  flex-direction: column;
693
726
  justify-content: center;
@@ -721,6 +754,15 @@
721
754
  margin-right: calc(var(--safe-inset-x) * -1);
722
755
  }
723
756
 
757
+ /* When footer is present, reduce bottom padding to reclaim space */
758
+ .slidev-layout:has(.slide-footer) {
759
+ padding-bottom: 0;
760
+ }
761
+ /* Split panels: reduce bottom padding when footer takes over that space */
762
+ .slide-split:has(.slide-footer) .slide-panel-content {
763
+ padding-bottom: 1%;
764
+ }
765
+
724
766
  /* ============================================
725
767
  8. Component Styles
726
768
  ============================================ */
@@ -729,17 +771,17 @@
729
771
  .tags-container {
730
772
  display: flex;
731
773
  flex-wrap: wrap;
732
- gap: 0.5rem;
733
- margin-top: 1rem;
734
- margin-bottom: 1rem;
774
+ gap: var(--space-xs);
775
+ margin-top: var(--space-sm);
776
+ margin-bottom: var(--space-sm);
735
777
  }
736
778
 
737
779
  .tag {
738
- padding-left: 0.75rem;
739
- padding-right: 0.75rem;
740
- padding-top: 0.25rem;
741
- padding-bottom: 0.25rem;
742
- border-radius: 9999px;
780
+ padding-left: var(--space-tight);
781
+ padding-right: var(--space-tight);
782
+ padding-top: var(--space-2xs);
783
+ padding-bottom: var(--space-2xs);
784
+ border-radius: var(--radius-pill);
743
785
  font-size: var(--font-size-small);
744
786
  background-color: var(--color-primary-soft);
745
787
  color: var(--color-primary);
@@ -748,8 +790,8 @@
748
790
  /* Button components */
749
791
  .btn {
750
792
  display: inline-block;
751
- padding: 0.5rem 1rem;
752
- border-radius: 0.375rem;
793
+ padding: var(--space-xs) 1rem;
794
+ border-radius: var(--radius-sm);
753
795
  font-weight: var(--font-weight-medium);
754
796
  text-decoration: none;
755
797
  cursor: pointer;
@@ -838,7 +880,7 @@ a.btn-secondary:hover {
838
880
  position: absolute;
839
881
  inset: 5% 5%;
840
882
  border: var(--border-thick) dashed var(--color-danger);
841
- border-radius: 0.5rem;
883
+ border-radius: var(--radius-md);
842
884
  pointer-events: none;
843
885
  }
844
886
 
@@ -864,9 +906,9 @@ a.btn-secondary:hover {
864
906
  /* Enhanced blockquote callout base style */
865
907
  .slidev-layout blockquote:has(p:first-child strong:first-child) {
866
908
  border-left-width: var(--border-accent);
867
- padding: 1rem 1rem 1rem 1.25rem;
909
+ padding: var(--space-sm) 1rem 1rem 1.25rem;
868
910
  background-color: var(--color-bg-soft);
869
- border-radius: 0 0.375rem 0.375rem 0;
911
+ border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
870
912
  }
871
913
 
872
914
  /* Semantic callout classes — apply to blockquote */
@@ -901,7 +943,7 @@ a.btn-secondary:hover {
901
943
 
902
944
  /* Ensure focus is not obscured */
903
945
  .slidev-layout :focus-visible {
904
- scroll-padding: 1rem;
946
+ scroll-padding: var(--space-sm);
905
947
  }
906
948
 
907
949
  /* (Old layout sections 12-27 removed - replaced by section 7 above) */
@@ -954,19 +996,19 @@ a.btn-secondary:hover {
954
996
 
955
997
  /* Ensure thought bubble circles are not clipped in overflow: auto containers */
956
998
  .bubble-thought.bubble-bottom {
957
- margin-bottom: 1.5rem;
999
+ margin-bottom: var(--space-md);
958
1000
  }
959
1001
 
960
1002
  .bubble-thought.bubble-top {
961
- margin-top: 1.5rem;
1003
+ margin-top: var(--space-md);
962
1004
  }
963
1005
 
964
1006
  .bubble-thought.bubble-left {
965
- margin-left: 2rem;
1007
+ margin-left: var(--space-lg);
966
1008
  }
967
1009
 
968
1010
  .bubble-thought.bubble-right {
969
- margin-right: 2rem;
1011
+ margin-right: var(--space-lg);
970
1012
  }
971
1013
 
972
1014
  /* ============================================
@@ -974,7 +1016,7 @@ a.btn-secondary:hover {
974
1016
  ============================================ */
975
1017
 
976
1018
  .device-mockup-placeholder {
977
- padding: 2rem;
1019
+ padding: var(--space-lg);
978
1020
  text-align: center;
979
1021
  }
980
1022
 
package/styles/motion.css CHANGED
@@ -313,7 +313,7 @@
313
313
  animation: entrance-slide-right var(--dur-moderate-02) var(--ease-enter) calc(var(--stagger-base) * 4) both;
314
314
  }
315
315
 
316
- .slide-grid > .slide-col:last-child:nth-last-child(2) {
316
+ .slide-grid > .slide-col:nth-child(2):last-child {
317
317
  animation: entrance-slide-left var(--dur-moderate-02) var(--ease-enter) calc(var(--stagger-base) * 4) both;
318
318
  }
319
319