@humanforest/slidev-theme 0.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.
Files changed (124) hide show
  1. package/LICENSE +67 -0
  2. package/README.md +577 -0
  3. package/assetUrl.js +38 -0
  4. package/components/Alert.vue +209 -0
  5. package/components/AreaBox.vue +152 -0
  6. package/components/AreaChart.vue +145 -0
  7. package/components/Avatar.vue +267 -0
  8. package/components/AvatarGroup.vue +94 -0
  9. package/components/BadgeStrip.vue +203 -0
  10. package/components/Bars.vue +207 -0
  11. package/components/BoroughShape.vue +127 -0
  12. package/components/BrowserFrame.vue +484 -0
  13. package/components/CalendarGrid.vue +480 -0
  14. package/components/Card.vue +70 -0
  15. package/components/Carousel.vue +266 -0
  16. package/components/ChartFrame.vue +426 -0
  17. package/components/ChordChart.vue +264 -0
  18. package/components/ComposedChart.vue +187 -0
  19. package/components/Contents.vue +86 -0
  20. package/components/DataTable.vue +244 -0
  21. package/components/DeckMap.vue +409 -0
  22. package/components/DeviceFrame.vue +331 -0
  23. package/components/DonutChart.vue +103 -0
  24. package/components/DottedMap.vue +126 -0
  25. package/components/Endorsement.vue +76 -0
  26. package/components/Grid.vue +169 -0
  27. package/components/GroupedBarChart.vue +141 -0
  28. package/components/IconTile.vue +116 -0
  29. package/components/Kpi.vue +188 -0
  30. package/components/LineChart.vue +133 -0
  31. package/components/Logo.vue +203 -0
  32. package/components/LondonMap.vue +348 -0
  33. package/components/MarkPair.vue +109 -0
  34. package/components/Marquee.vue +263 -0
  35. package/components/NestedDonutChart.vue +295 -0
  36. package/components/Placeholder.vue +88 -0
  37. package/components/SankeyChart.vue +223 -0
  38. package/components/ScatterChart.vue +142 -0
  39. package/components/StackedBarChart.vue +143 -0
  40. package/components/StatCard.vue +134 -0
  41. package/components/StatusTrack.vue +334 -0
  42. package/components/Timeline.vue +249 -0
  43. package/components/TimelineChart.vue +329 -0
  44. package/components/TreemapChart.vue +267 -0
  45. package/components/backgrounds.js +135 -0
  46. package/components/boroughGeometry.ts +59 -0
  47. package/components/chartData.ts +277 -0
  48. package/components/chartTip.ts +201 -0
  49. package/components/codeTheme.ts +239 -0
  50. package/components/deckPalette.ts +157 -0
  51. package/components/dottedMapGeometry.js +60 -0
  52. package/components/iconSlugs.js +55 -0
  53. package/components/iconUrl.js +51 -0
  54. package/components/mermaidTheme.ts +972 -0
  55. package/components/motionDuration.ts +41 -0
  56. package/components/tableRules.ts +257 -0
  57. package/confidential-mark.vue +151 -0
  58. package/custom-nav-controls.vue +79 -0
  59. package/endorsements/b-corp-black.svg +30 -0
  60. package/endorsements/b-corp-white.svg +30 -0
  61. package/global-top.vue +1662 -0
  62. package/icons/forest/bike-asterisk.svg +21 -0
  63. package/icons/forest/bike-down.svg +19 -0
  64. package/icons/forest/bike-off.svg +20 -0
  65. package/icons/forest/bike-up.svg +19 -0
  66. package/icons/forest/bike-x.svg +19 -0
  67. package/icons/forest/bike.svg +18 -0
  68. package/icons/forest/mark-fill.svg +6 -0
  69. package/icons/forest/mark.svg +6 -0
  70. package/icons/forest/parking.svg +3 -0
  71. package/icons/forest/star-fill.svg +4 -0
  72. package/icons/forest/star.svg +4 -0
  73. package/icons/forest/traffic-light-caution.svg +8 -0
  74. package/icons/forest/traffic-light-go.svg +8 -0
  75. package/icons/forest/traffic-light-lit.svg +8 -0
  76. package/icons/forest/traffic-light-stop.svg +8 -0
  77. package/icons/forest/traffic-light.svg +8 -0
  78. package/icons/lucide/battery.svg +1 -0
  79. package/icons/lucide/bike.svg +1 -0
  80. package/icons/lucide/chart-line.svg +1 -0
  81. package/icons/lucide/check.svg +1 -0
  82. package/icons/lucide/circle-check.svg +1 -0
  83. package/icons/lucide/clock.svg +1 -0
  84. package/icons/lucide/code.svg +1 -0
  85. package/icons/lucide/component.svg +1 -0
  86. package/icons/lucide/frame.svg +1 -0
  87. package/icons/lucide/layout-grid.svg +1 -0
  88. package/icons/lucide/map-pin.svg +1 -0
  89. package/icons/lucide/map.svg +1 -0
  90. package/icons/lucide/palette.svg +1 -0
  91. package/icons/lucide/table.svg +1 -0
  92. package/icons/lucide/triangle-alert.svg +1 -0
  93. package/icons/lucide/type.svg +1 -0
  94. package/icons/lucide/wrench.svg +1 -0
  95. package/layouts/bleed.vue +317 -0
  96. package/layouts/cover.vue +1514 -0
  97. package/layouts/default.vue +187 -0
  98. package/layouts/divider.vue +286 -0
  99. package/layouts/end.vue +384 -0
  100. package/layouts/quote.vue +218 -0
  101. package/layouts/split.vue +1875 -0
  102. package/layouts/stack.vue +944 -0
  103. package/layouts/statement.vue +67 -0
  104. package/manifest/chrome.ts +36 -0
  105. package/manifest/components.ts +49 -0
  106. package/manifest/index.ts +57 -0
  107. package/manifest/layouts.ts +37 -0
  108. package/manifest/manifest.json +6514 -0
  109. package/manifest/presets.ts +635 -0
  110. package/notch.js +458 -0
  111. package/notchPath.js +188 -0
  112. package/package.json +77 -0
  113. package/section.js +110 -0
  114. package/setup/mermaid-renderer.ts +2120 -0
  115. package/setup/mermaid.ts +30 -0
  116. package/setup/shiki.ts +44 -0
  117. package/setup/shortcuts.ts +28 -0
  118. package/slide-bottom.vue +305 -0
  119. package/slide-top.vue +268 -0
  120. package/styles/base.css +2611 -0
  121. package/styles/index.ts +71 -0
  122. package/styles/tokens.css +144 -0
  123. package/uno.config.ts +231 -0
  124. package/useNotchCuts.js +200 -0
package/slide-top.vue ADDED
@@ -0,0 +1,268 @@
1
+ <script setup>
2
+ /*
3
+ Running chrome, mounted PER SLIDE — the top half. slide-bottom.vue documents why this is a
4
+ per-slide hook rather than a global overlay switching on layout name; the same reasoning applies
5
+ here, so it is not restated.
6
+
7
+ Two pills, top-left: the page number (always, once chrome is showing at all) and the section —
8
+ which slide-bottom.vue in Task 4's shape had no way to know, because a slide's section is not
9
+ something the SLIDE carries. It is the nearest PRECEDING divider's title: derived from the deck's
10
+ own slide list, not read off this slide's own frontmatter. See ./section.js for the derivation
11
+ and the two indexing/precedence traps it guards.
12
+
13
+ One tile, top-right: the Forest mark. `<Logo variant="mark">` is already self-contained — its own
14
+ doc comment calls it "the rounded green tile with the white tree" — so no wrapper is needed to
15
+ make it read as a tile; it already is one.
16
+
17
+ Both pills route through BadgeStrip rather than a hand-rolled `<span>` + scoped CSS: BadgeStrip's
18
+ `.badge-strip-item` already IS "--deck-radius-control, the overline type role, the chip token
19
+ pair" — the exact three things this step is required to use — because it is the same pill this
20
+ deck already ships for tags. Reaching for a second, near-identical implementation here would be
21
+ exactly the drift `backgrounds.js`'s own doc comment warns about: two copies of one shape that can
22
+ now disagree. Passing BOTH labels as one `items` array (rather than two separate <BadgeStrip>
23
+ calls) also gets "no pill when section is empty" for free — BadgeStrip already renders one <span>
24
+ per array entry, so filtering the empty string out of the array IS the "no pill" behaviour,
25
+ not a v-if bolted on beside it.
26
+
27
+ The pill does not take BadgeStrip's own default (`accent-dark`, --forest-950), and that is a
28
+ MEASURED choice rather than a preference: --deck-bg-accent-dark resolves to var(--forest-950), and
29
+ .deck-dark's own --deck-bg is ALSO var(--forest-950) — PINNED halves are declared once at :root and
30
+ are never re-pointed under .dark/.deck-dark, so an accent-dark pill would sit at exactly the ground
31
+ colour on any dark-register slide that doesn't opt out of chrome: an invisible-edged pill the
32
+ geometry audit cannot see either way. The chrome takes --deck-accent instead, which moves with
33
+ the register and reads against both the light ground (--warm-50) and the dark one (--forest-950).
34
+
35
+ ★ FIX ROUND 1, CRITICAL 1 — `currentPage` must come from `injectionCurrentPage`, NOT
36
+ `useNav().currentPage`. `useNav()` is `createSharedComposable` (@slidev/client/composables/
37
+ useNav.ts:365) — ONE instance shared by the whole app — and its `currentPage` is
38
+ `currentSlideNo = hasPrimarySlide ? getSlide(params.no)?.no ?? 1 : 1`, a ROUTER value keyed off
39
+ `route.params.no` (useNav.ts:290-291). That param exists on the normal per-slide route, but NOT
40
+ on every route Slidev renders a slide through: `@slidev/client/setup/routes.ts` declares the
41
+ print route as `{ name: 'print', path: '/print' }` — no `:no` — and `apps/deck-template/
42
+ package.json`'s `export` script runs bare `slidev export`, whose one-piece PDF generator visits
43
+ exactly that route. On `/print`, `hasPrimarySlide` is false, so `useNav().currentPage` reads `1`
44
+ for EVERY slide simultaneously — `show` evaluated `1 > 1` as false on every page, and the
45
+ exported PDF carried no chrome at all. `/overview` is the same shape, param-less.
46
+
47
+ `injectionCurrentPage` is what SlideWrapper.vue actually provides PER SLIDE regardless of which
48
+ route rendered it — `provideLocal(injectionCurrentPage, ref(props.route.no))`
49
+ (@slidev/client/internals/SlideWrapper.vue:30) — exported from the same `constants.ts` module
50
+ `injectionFrontmatter` already comes from. `total` and `slides` stay on `useNav()`: those really
51
+ are deck-wide, not per-route.
52
+ */
53
+ import { computed, inject } from 'vue';
54
+ import { useNav } from '@slidev/client';
55
+ import { injectionCurrentPage, injectionFrontmatter } from '@slidev/client/constants.ts';
56
+ import deckTokens from '@humanforest/tokens/deck/deck.json';
57
+ import { deriveSection } from './section.js';
58
+ import { chromeShows, coBrandFitted, darkChromeAt, darkChromeParts, darkRegister } from './notch.js';
59
+ import { backgroundStyle } from './components/backgrounds.js';
60
+
61
+ const { total, slides } = useNav();
62
+ const frontmatter = inject(injectionFrontmatter, {});
63
+ // Falls back to a ref(1), not a bare 1 — this is read as `currentPage.value` everywhere below,
64
+ // and SlideWrapper.vue always provides a real ref in practice; the fallback only matters for a
65
+ // component rendered completely outside a SlideWrapper (never happens in this theme, but inject's
66
+ // own default must still be the same shape as the real value or `.value` throws).
67
+ const currentPage = inject(injectionCurrentPage, computed(() => 1));
68
+
69
+ // Same opt-out as slide-bottom.vue, and the same slide-1 hide — a cover slide carries no page
70
+ // number worth reading and can never have a divider behind it. The predicate itself lives in
71
+ // ./notch.js now, not because this file needed it moved but because split.vue needs the SAME
72
+ // answer: a panel only cuts a notch when there is actually a mark to seat in it (task-15-brief.md's
73
+ // own named failure case — a notch on a `chrome: false` slide is a hole with nothing in it).
74
+ const show = computed(() => chromeShows(frontmatter, currentPage.value));
75
+
76
+ /*
77
+ ★★ THE CHROME OPTS ITSELF INTO THE DARK REGISTER, because nothing else can put it there.
78
+
79
+ `deck-dark` lands on `.slidev-layout` — that is where both `class: deck-dark` and the layouts'
80
+ `dark` prop write it — and SlideWrapper renders this chrome as a SIBLING of the layout, not a
81
+ descendant. So every --deck-* role inside the chrome kept resolving on the LIGHT register while
82
+ the slide beside it was dark. Measured on a `dark: true` slide before this: the page pill painted
83
+ forest-700 with white ink, identical to its light-register self.
84
+
85
+ Reading the same frontmatter this component already injects and stamping the class on its own root
86
+ is the only place the two can be reconciled without moving `deck-dark` onto the page element,
87
+ which no layout can reach.
88
+ */
89
+ // Delegated to notch.js: a cover can resolve a dark ground from its VARIANT without saying so in
90
+ // frontmatter, and this row is a sibling of the layout so it cannot read the classes that resolution
91
+ // lands as. See darkRegister() — the same module, and the same reason, as darkChromeParts below.
92
+ const isDarkRegister = computed(() => darkRegister(frontmatter));
93
+ // …and the HALVES. A bleeding `panelDark` panel is dark under one end of this row and light under
94
+ // the other, so the register lands per end rather than on the row. See darkChromeParts().
95
+ const darkParts = computed(() => darkChromeParts(frontmatter).top);
96
+
97
+ /*
98
+ ★ THE MIRROR — chrome sits on the TEXT side, the mark sits on the PANEL side.
99
+
100
+ Jose: "if text is left or right, the notch position should adapt (and move the page to the other
101
+ side, along with the confidenciality chip)". `flip` is frontmatter, so this component can read it
102
+ exactly the way it already reads `chrome` — no new plumbing and no coupling to split.vue, which
103
+ reads the same key as its own prop and cuts its notch on the opposite side of the slide.
104
+
105
+ ★ THAT MIRROR IS GONE. It flipped the row on a `flip: true` split so the mark could sit in the
106
+ notch cut from the figure's top-left corner. The mark is fixed top-right now and the notch is only
107
+ cut where it can reach — see the note above hasNotch in ./notch.js for the reasoning and what it
108
+ cost. What is left here is one arrangement, so this file no longer computes a side at all.
109
+ */
110
+ // The arrangement is FIXED: pills left, mark right, on every slide. It used to flip on a
111
+ // `flip: true` split so the mark could sit in the notch cut from the figure's top-left corner —
112
+ // see the note above hasNotch in ./notch.js for why that went and what it cost.
113
+
114
+
115
+ const section = computed(() => deriveSection(slides.value, currentPage.value));
116
+ // Only the section rides in the BadgeStrip now. The page number is a CIRCLE, which is a different
117
+ // geometry from a text pill (equal on both axes, sized by its own line box rather than by its
118
+ // content's width), so it cannot be one of BadgeStrip's items without either stretching for a
119
+ // 3-digit number or clipping one. It keeps BadgeStrip's tone pair and type role so the two still
120
+ // read as one family — see .deck-page-dot below.
121
+ const sectionLabels = computed(() => [section.value].filter(Boolean));
122
+
123
+ // ★ THE MARK'S SIZE IS A TOKEN NOW, read rather than restated (task-15-brief.md). It used to be a
124
+ // literal here, on the reasoning that Logo's `size` prop takes a plain number (cover.vue :size="72",
125
+ // end.vue :size="88") and sets an SVG geometry scale rather than a CSS box, so no --deck-* property
126
+ // had the right shape. That reasoning held while this component was the only thing that cared. It
127
+ // no longer is: split.vue cuts a notch sized from this exact number, and a value only one of the two
128
+ // can see is precisely how a cut-out and its occupant drift apart. deck.json is the same generated
129
+ // artefact --deck-chrome-mark is emitted from, so the mark the chrome DRAWS and the mark the notch
130
+ // is CUT FOR cannot disagree — change MARK_PX in scripts/deck.ts and both move.
131
+ const MARK_SIZE = deckTokens.chrome.markPx;
132
+
133
+ /*
134
+ ★ CO-BRANDING — `companion:` in a slide's own frontmatter.
135
+
136
+ THE OCCUPANT IS COMPUTED, NOT DRAWN AND THEN MEASURED. ./notch.js's coBrand() is a pure function
137
+ of the frontmatter plus deck.json's own chrome numbers, and split.vue/stack.vue call the SAME
138
+ function for the same slide — so the box this component draws and the cut those layouts make are
139
+ the same width by construction, not by two files agreeing to use the same literal. Nothing here
140
+ reads a rendered size back, which is what lets the mask be correct on the first frame.
141
+
142
+ --deck-notch-occupant-w is set INLINE, and ONLY when there is a companion. The escape hatch for
143
+ the width is a `class:` on the slide overriding the property, and an inline style on this element
144
+ outranks one. No companion, no declaration, so that override still reaches.
145
+
146
+ The HEIGHT never moves — a co-brand lockup shares ONE height
147
+ (packages/ui/src/logo/FSubLogo.vue's own rule, which sets both halves at the wordmark's height
148
+ and measures only the gap between them), so a companion's mark is as tall as the Forest mark and
149
+ only the width grows.
150
+
151
+ ⚠ IT IS `object-fit: cover`, NOT `contain`. The square and its corner are the whole point of the
152
+ pairing, and `contain` letterboxes a non-square source inside that square, which puts the ragged
153
+ silhouette back. Whatever the file is, it is covered into a mark-sized tile so the two read as one
154
+ lockup.
155
+ */
156
+ const companion = computed(() => coBrandFitted(frontmatter, currentPage.value, deckTokens));
157
+ /* The alt text is the author's `companionName`. The fallback is deliberately vague because the
158
+ theme cannot know what the file depicts — a partner's logo, an app icon, a customer's face — and
159
+ a specific wrong alt is worse than a generic right one. Name it. */
160
+ const companionLabel = computed(() => frontmatter?.companionName || 'Companion mark');
161
+ // Undefined — not an empty object and not a `null` value — when there is no companion: Vue drops an
162
+ // undefined :style binding entirely, where `{ '--x': null }` still writes an empty declaration.
163
+ const occupantStyle = computed(() =>
164
+ companion.value ? { '--deck-notch-occupant-w': `${companion.value.occupantPx}px` } : undefined,
165
+ );
166
+ </script>
167
+
168
+ <template>
169
+ <div v-if="show" :class="['deck-chrome-top', isDarkRegister ? 'deck-dark' : '']">
170
+ <!-- The page number LEFT this row (task: "the page number i think it can go at the bottom, other
171
+ extreme of the confidential chip"). It lives in slide-bottom.vue now, paired against the
172
+ confidentiality pill across the foot of the slide. What stays here is the section, which is
173
+ the one label a reader looks UP for — where they are, not how far in. -->
174
+ <div class="deck-chrome-pills" :class="darkChromeAt(darkParts, 'left') ? 'deck-dark' : ''">
175
+ <BadgeStrip v-if="sectionLabels.length" :items="sectionLabels" chrome />
176
+ </div>
177
+ <!-- The occupant. A BOX, not a bare mark — see .deck-chrome-mark below. Forest always leads,
178
+ and now so does the position: the mark holds the right-hand end of this row on every slide.
179
+ The attribute stays rather than being folded into the class, because useNotchCuts.js reads
180
+ the rendered corner rather than being told one, and a literal is the honest way to say a
181
+ value no longer varies. -->
182
+ <MarkPair
183
+ class="deck-chrome-mark"
184
+ :class="darkChromeAt(darkParts, 'right') ? 'deck-dark' : ''"
185
+ data-mark-side="right"
186
+ :style="occupantStyle"
187
+ :src="companion?.src ?? ''"
188
+ :label="companionLabel"
189
+ :size="MARK_SIZE"
190
+ />
191
+ </div>
192
+ </template>
193
+
194
+ <style scoped>
195
+ .deck-chrome-top {
196
+ position: absolute;
197
+ /* task-14-brief.md — chrome is inset --deck-inset-chrome (36px, task-14b-brief.md's revision of
198
+ the original 40px/one-grid-unit figure) on every edge it touches, uniform with
199
+ slide-bottom.vue's pill and with the mark's own corner. Was --deck-space-3 (24px) top /
200
+ grid*2.5 (100px) sides — two different numbers for one kind of content (furniture); now one
201
+ token, one meaning. See base.css's `.slidev-layout` comment and scripts/deck.ts's INSET
202
+ comment for the full two-track reasoning this is half of. */
203
+ top: var(--deck-inset-chrome);
204
+ left: var(--deck-inset-chrome);
205
+ right: var(--deck-inset-chrome);
206
+ display: flex;
207
+ /* flex-START, not centre. The row is the MARK's height (72), the section pill is 49, so centring
208
+ put the pill's top at 47.5 — 11.5px below the mark's own 36. Invisible on a bare slide; obvious
209
+ the moment the pill sits on a stack band, whose top edge is also 36 and gives the eye a hard
210
+ line to judge against. Reported by eye: "why this sits not at the same level? i think it should
211
+ be 36 px". Top-aligning makes one shared edge at --deck-inset-chrome for the mark, the pill and
212
+ any panel or band below them, which is the thing the inset system is for. */
213
+ align-items: flex-start;
214
+ justify-content: space-between;
215
+ /* The band is the MARK's height, floor — so the mark's own top stays at --deck-inset-chrome (the
216
+ point split.vue and stack.vue cut their notch against) whatever else is in the row. A `min-`,
217
+ not a fixed height, because the documented escape hatch lets a slide override
218
+ --deck-notch-occupant-h for a taller occupant, and that one is meant to grow the band. */
219
+ min-height: var(--deck-chrome-mark);
220
+ z-index: 20;
221
+ /* Click-through, like slide-bottom.vue's chrome — this is a running label, never a control. */
222
+ pointer-events: none;
223
+ }
224
+ /*
225
+ THE OCCUPANT BOX — the mark's own size, expressed the way the notch reads it.
226
+
227
+ This is a wrapper rather than the class on <Logo> itself because the notch and the mark have to
228
+ agree on ONE box: split.vue cuts `occupant + gap`, reading the same two custom properties this
229
+ reads, so overriding --deck-notch-occupant-w/-h on a slide resizes the cut and its occupant
230
+ together — one mark, a forest+companion pair, an image, a content block. The `size` prop still
231
+ passes the token's own number so the SVG's intrinsic geometry matches the default box; the box is
232
+ what the notch is measured against.
233
+
234
+ `height: 100%; width: auto` on the mark, and the box aligned to its OUTER edge: a wider occupant
235
+ is wider because it holds MORE, not because the mark stretches. The default is a square box
236
+ holding a square mark, where none of this is visible — it only matters once the box is not.
237
+ */
238
+ .deck-chrome-mark {
239
+ flex: none;
240
+ /* The row itself — display, alignment and the gap between the two halves — is MarkPair's, which
241
+ derives that gap from the `size` this component passes. --deck-chrome-pair-gap is still the
242
+ number both readers of the CUT use: ./notch.js's coBrand() adds it to reach the occupant width
243
+ and scripts/deck.ts adds it again to prove a forest+companion pair fits the narrowest ratio.
244
+ MarkPair reads the same two chrome numbers out of deck.json, so the lockup and the cut made for
245
+ it cannot drift. */
246
+ width: var(--deck-notch-occupant-w, var(--deck-chrome-mark));
247
+ height: var(--deck-notch-occupant-h, var(--deck-chrome-mark));
248
+ }
249
+ /* Only 'right' is emitted now. The 'left' rule went with the mirror rather than being left behind
250
+ as a selector nothing can match. */
251
+ .deck-chrome-mark[data-mark-side='right'] { justify-content: flex-end; }
252
+ /*
253
+ ★★ THE PARTNER'S TILE IS NOT DRAWN HERE ANY MORE — components/MarkPair.vue draws it, and this
254
+ component passes the size. The rounded square, the ring in `currentColor`, the `cover` fit and the
255
+ gap between the two halves were all stated in this file with the chrome's own fixed px tokens;
256
+ cover.vue then drew the same pair twice more, and its poster — at 120px — inherited a corner and a
257
+ ring computed for a 72px tile. The geometry is one component's now, derived from `size`, and this
258
+ file keeps only what is about the CHROME: where the box sits, how wide the notch thinks it is, and
259
+ which register it is in.
260
+ */
261
+
262
+ .deck-chrome-top.deck-dark,
263
+ .deck-chrome-pills.deck-dark,
264
+ .deck-chrome-mark.deck-dark,
265
+ .deck-chrome-confidential.deck-dark {
266
+ background: transparent;
267
+ }
268
+ </style>