slidev-theme-kotlin 0.13.2 → 0.14.1

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 (40) hide show
  1. package/CHANGELOG.md +38 -1
  2. package/README.md +396 -633
  3. package/assets/gutter/suspendCall@14x14.svg +6 -0
  4. package/assets/gutter/suspendCall@14x14_dark.svg +6 -0
  5. package/bin/present.mjs +296 -0
  6. package/components/DrawnAnnotation.vue +57 -9
  7. package/components/DrawnAnnotationEditorToolbar.vue +1 -1
  8. package/components/InlineCompilerError.vue +46 -15
  9. package/components/InlineValue.vue +3 -3
  10. package/components/MagicMoveBetween.vue +6 -4
  11. package/components/README.md +60 -2
  12. package/components/SmartCast.vue +3 -3
  13. package/components/SuspendGutter.vue +168 -0
  14. package/components/TypeHint.vue +3 -3
  15. package/components/Warning.vue +3 -3
  16. package/components/carried-layer.ts +29 -0
  17. package/components/code-highlight.ts +119 -5
  18. package/components/transition-ink.ts +27 -6
  19. package/docs/annotation-editor.png +0 -0
  20. package/docs/code-marks.md +76 -0
  21. package/docs/phone-remote.png +0 -0
  22. package/docs/presenting.md +122 -0
  23. package/docs/publishing.md +111 -0
  24. package/docs/snippets.md +84 -0
  25. package/global-top.vue +3 -8
  26. package/package.json +11 -4
  27. package/remote/RemoteControl.vue +278 -0
  28. package/remote/connection.ts +19 -0
  29. package/setup/code-imports.ts +3 -2
  30. package/setup/code-marks.ts +562 -0
  31. package/setup/kotlin-snippets.ts +4 -2
  32. package/setup/magic-move-between.ts +10 -2
  33. package/setup/main.ts +5 -1
  34. package/setup/remote-sync.ts +226 -0
  35. package/setup/routes.ts +21 -0
  36. package/setup/static-content.ts +39 -5
  37. package/setup/transformers.ts +16 -1
  38. package/styles/layout.css +17 -0
  39. package/assets/edit-3-svgrepo-com.svg +0 -5
  40. package/components/DrawnAnnotationEditorLauncher.vue +0 -59
package/README.md CHANGED
@@ -1,139 +1,91 @@
1
1
  # Slidev Theme Kotlin
2
2
 
3
- A [Slidev](https://sli.dev) theme designed for Kotlin presentations, featuring the Kodee mascot with smooth animations and Kotlin-optimized syntax highlighting.
3
+ A [Slidev](https://sli.dev) theme for Kotlin talks: code windows that look
4
+ like IntelliJ, the decorations your IDE shows, hand-drawn annotations, and
5
+ Kodee.
4
6
 
5
- ## Overview
7
+ **[See the demo deck](https://nomisrev.github.io/slidev-theme-kotlin/)**. Its
8
+ source is [`slides.md`](slides.md), so every slide doubles as an example.
6
9
 
7
- This theme provides a professional presentation template tailored for Kotlin developers. It includes:
10
+ ## Quick start
8
11
 
9
- - **Kodee Mascot Integration**: Animated Kodee character with multiple variants (greeting, wink, wave, jumping, sitting, drinking, heart, in-love, welcome, winter, tiny)
10
- - **Magic Move Animations**: Smooth transitions for Kodee between slides with configurable size and position
11
- - **Magic Move between slides**: Link slides with a `magic-move` separator and their code windows morph across the slide boundary, including several parallel snippets per slide
12
- - **Kotlin Syntax Highlighting**: Custom Shiki configuration optimized for Kotlin code
13
- - **Multiple Layouts**: Pre-built layouts including default, cover, and intro
14
- - **JetBrains Fonts**: Prefers locally installed JetBrains Sans and JetBrains Mono, with Inter and Google-hosted JetBrains Mono fallbacks
15
- - **Drawn annotations**: Click-aware, hand-drawn marks, labels, and connectors for elements and code
16
- - **Inline compiler diagnostics**: IntelliJ-style squiggles and messages for static and Magic Move code blocks
17
-
18
- ## Requirements
19
-
20
- - **Node.js**: >= 20.12.0
21
- - **Package Manager**: npm (or pnpm with shamefully-hoist=true)
22
-
23
- ## Installation
24
-
25
- Install the theme in your Slidev project:
12
+ You need Node.js 20.12 or newer. Install the theme in your Slidev project:
26
13
 
27
14
  ```bash
28
15
  npm install slidev-theme-kotlin
29
16
  ```
30
17
 
31
- ## Usage
32
-
33
- ### Visual annotation editor (development)
34
-
35
- Enable the optional, development-only source writer in the consuming deck's
36
- `vite.config.ts`:
37
-
38
- ```ts
39
- import { defineConfig } from 'vite'
40
- import { drawnAnnotationEditor } from 'slidev-theme-kotlin/annotation-editor'
41
-
42
- export default defineConfig({
43
- plugins: [drawnAnnotationEditor()],
44
- })
45
- ```
18
+ With pnpm, set `shamefully-hoist=true` in `.npmrc`.
46
19
 
47
- Click the theme's **Edit annotations** launcher (or press **Alt+Shift+A**) to start a frozen
48
- edit session on the current slide and click state: Slidev navigation — arrow
49
- keys, space, page keys, click-to-navigate, swipes — is paused until you leave
50
- the editor. Drag a visible label, its width handle, or a connector control, or
51
- nudge the selected control with the arrow keys (**Shift** for larger steps);
52
- every change stays a local draft, so nothing is written mid-gesture. **Done
53
- editing annotations** commits all changed drafts as normalized `:geometry`
54
- bindings on exactly the edited `DrawnAnnotation` tags, then exits. **Esc**
55
- cancels in stages — an active drag, then the selection, then (after a
56
- confirmation) the whole session, discarding its drafts. One toolbar control
57
- hides or shows the connector (writing `:connect` into the tag where the
58
- deck's default differs, see `themeConfig.drawnAnnotation.connect`), one
59
- switches it between manual and automatic routing, another curves or
60
- straightens it with a draggable quadratic control point, and **Reset
61
- annotation** or **Reset slide** removes the saved geometry of one annotation
62
- or of every annotation on the slide. Source revisions are checked, so a
63
- conflicting commit fails without overwriting anyone: the drafts stay visible,
64
- and you either fix and press Done again or choose **Reload saved geometry** to
65
- discard them. The locator, toolbar, and write endpoint exist only while Vite
66
- serves the deck; builds and exports render saved geometry without editor code.
67
-
68
- While serving, an annotation that cannot find what it should mark (a `text`
69
- that no longer matches the fence, an unmatched `selector` or `target`, an
70
- `occurrence` past the last match, or an `InlineCompilerError`, `SmartCast`, `Warning`,
71
- `TypeHint`, or `InlineValue` whose target is not found) also puts a red chip in the top-left corner of its slide, next to
72
- the warning it prints on the console. Hover the chip for the full message. The
73
- chip appears only once the annotation's click has been reached and the slide
74
- has settled, so a Magic Move step that does not yet contain the text never
75
- flags one, and it disappears again when the target turns up. Builds, exports,
76
- the presenter view, and print mode never render it.
77
-
78
- ### Basic Setup
79
-
80
- Add the theme to your slides' frontmatter:
20
+ Then name the theme at the top of `slides.md`:
81
21
 
82
22
  ```yaml
83
23
  ---
84
24
  theme: kotlin
85
25
  transition: fade
26
+ themeConfig:
27
+ kodee: greeting
86
28
  ---
87
29
 
88
30
  # Your Presentation Title
89
31
  ```
90
32
 
91
- ### Code icons
33
+ ## Contents
34
+
35
+ - [Code windows](#code-windows)
36
+ - [Folded imports](#folded-imports)
37
+ - [Magic Move between slides](#magic-move-between-slides)
38
+ - [IDE decorations](#ide-decorations)
39
+ - [Drawn annotations](#drawn-annotations)
40
+ - [Kodee](#kodee)
41
+ - [Layouts](#layouts)
42
+ - [Customising](#customising)
43
+ - [Publishing a deck](#publishing-a-deck)
44
+ - [Presenting from your phone](#presenting-from-your-phone)
45
+ - [Compiling the snippets](#compiling-the-snippets)
46
+ - [Tips](#tips)
47
+ - [Developing the theme](#developing-the-theme)
92
48
 
93
- Code fences are rendered as one IntelliJ Light/Darcula editor surface without a
94
- title bar. A compact identity icon is placed in its top-right corner and follows
95
- the presentation color scheme:
49
+ ## Code windows
50
+
51
+ Every code fence is drawn as an IntelliJ editor, Light or Darcula with the
52
+ deck's colour scheme. An icon in the top-right corner and the border colour
53
+ say what kind of code it is:
96
54
 
97
55
  ````md
98
56
  ```kotlin
99
57
  fun main() = println("Hello")
100
58
  ```
101
59
 
102
- ```kts gradle
60
+ ```kotlin gradle
103
61
  plugins { kotlin("jvm") }
104
62
  ```
63
+ ````
105
64
 
106
- ```yaml toolchain
107
- product: jvm/app
108
- ```
65
+ | Fence | Icon |
66
+ | --- | --- |
67
+ | `kotlin`, `kt`, `kts` | Kotlin |
68
+ | `kotlin gradle` | Gradle |
69
+ | `kotlin ktor` | Ktor |
70
+ | `kotlin arrow` | [Arrow](https://arrow-kt.io) |
71
+ | `kotlin jdbc`, `kotlin r2dbc` | JDBC, R2DBC |
72
+ | `java` | Java |
73
+ | `bash` | Terminal |
74
+ | `sql` | PostgreSQL |
75
+ | `yaml toolchain` | Amper |
76
+ | `xml maven` | Maven |
109
77
 
110
- ```xml maven
111
- <artifactId>demo</artifactId>
112
- ```
78
+ The modifiers work with `kt` and `kts` too, and inside Magic Move. Any other
79
+ fence is a plain code window.
113
80
 
114
- ```sql
115
- SELECT * FROM presentations;
116
- ```
117
- ````
81
+ ### Program output
118
82
 
119
- `kotlin`, `kt`, and `kts` use the Kotlin icon; each with a `gradle`, `jdbc`,
120
- or `r2dbc` modifier uses the Gradle, JDBC, or R2DBC identity respectively, and
121
- with an `arrow` modifier the [Arrow](https://arrow-kt.io) icon, for Kotlin
122
- code that is really about `Raise`, `Resource`, or another Arrow library.
123
- Use `kotlin ktor` (also `kt ktor` or `kts ktor`) for the Ktor icon and purple
124
- border on Ktor code. These modifiers also work with Magic Move.
125
- `yaml toolchain`, `java`, `bash`, `xml maven`, and `sql` use the Amper, Java,
126
- Terminal, Maven, and PostgreSQL icons respectively. All other fences remain plain
127
- code surfaces.
128
-
129
- A `console` fence is program output, not code: it renders as a flat panel in
130
- the colours of IntelliJ's Run tool window, without the editor's outline and
131
- shadow, marked with the green run icon. Put it under the Kotlin fence whose
132
- output it shows:
83
+ A `console` fence is output, not code. It looks like IntelliJ's Run tool
84
+ window. Put it under the fence whose output it shows:
133
85
 
134
86
  ````md
135
87
  ```kotlin
136
- println([1, 2, 3].map { it + 1 })
88
+ println(listOf(1, 2, 3).map { it + 1 })
137
89
  ```
138
90
 
139
91
  ```console
@@ -141,51 +93,11 @@ println([1, 2, 3].map { it + 1 })
141
93
  ```
142
94
  ````
143
95
 
144
- To show a value next to the line that produces it instead, as the debugger
145
- does, wrap the fence in [`InlineValue`](components/README.md#inlinevalue).
146
- The other IntelliJ decorations have a component each: [`SmartCast`](components/README.md#smartcast)
147
- paints the green smart-cast background behind exact code, [`Warning`](components/README.md#warning)
148
- the yellow warning background with an optional message, and [`TypeHint`](components/README.md#typehint)
149
- the grey `this: HTML` inlay hint after a lambda's brace.
96
+ ## Folded imports
150
97
 
151
- The identity border colours are exposed as theme variables and can be
152
- customized from a deck stylesheet. Each variable has a light and dark-mode
153
- value:
154
-
155
- ```css
156
- :root {
157
- --code-window-kotlin-color: #834df0;
158
- --code-window-gradle-color: #6c707e;
159
- --code-window-amper-color: #087cfa;
160
- --code-window-arrow-color: #2455cf;
161
- --code-window-ktor-color: #7f52ff;
162
- --code-window-java-color: #e66d17;
163
- --code-window-terminal-color: #6c707e;
164
- --code-window-maven-color: #3574f0;
165
- --code-window-postgresql-color: #336791;
166
- --code-window-jdbc-color: #f59e0b;
167
- --code-window-r2dbc-color: #06b6d4;
168
-
169
- /* The `console` output panel. */
170
- --code-window-output-background: #f7f8fa;
171
- --code-window-output-color: #2b2d30;
172
- }
173
-
174
- html.dark {
175
- --code-window-kotlin-color: #a571e6;
176
- --code-window-output-background: #1e1f22;
177
- --code-window-output-color: #bcbec4;
178
- /* Override any of the other dark-mode values as needed. */
179
- }
180
- ```
181
-
182
- ### Folded imports
183
-
184
- A snippet on a slide reads best without its imports, but a snippet that
185
- carries them is complete: it compiles as written, a snippet checker such as
186
- Knit can verify it, and the deck's handout (see below) shows a reader which
187
- package every symbol comes from. So write the imports, and the theme folds
188
- them away:
98
+ A snippet reads best without its imports, but a snippet that has them is
99
+ complete: it compiles, and a reader of the handout can see where every symbol
100
+ comes from. So write the imports, and the theme hides them on the slide:
189
101
 
190
102
  ````md
191
103
  ```kotlin
@@ -196,12 +108,14 @@ fun numbers(): Flow<Int> = flowOf(1, 2, 3)
196
108
  ```
197
109
  ````
198
110
 
199
- On the slide, the code window shows only `fun numbers()…`; the handout and
200
- `llms-full.txt` print the fence in full.
111
+ The slide shows only `fun numbers()…`. The handout and `llms-full.txt` print
112
+ the fence in full.
113
+
114
+ ### Hide more than imports
201
115
 
202
- When an example needs more than imports — a table definition, a helper the
203
- example calls — put a `// Example` comment where the example starts.
204
- Everything above it leaves the slide, and so does the marker line itself:
116
+ When an example needs a table definition or a helper, put a `// Example`
117
+ comment where the example starts. Everything above it stays off the slide,
118
+ and so does the comment:
205
119
 
206
120
  ````md
207
121
  ```kotlin
@@ -216,16 +130,14 @@ fun doubled(): Flow<Int> = numbers().map { it * 2 }
216
130
  ```
217
131
  ````
218
132
 
219
- The window shows `fun doubled()…`. The comment is ordinary Kotlin, so the
220
- fence still compiles as written (see the snippet checker below), and the
221
- handout keeps it. A description after the colon is optional.
133
+ The text after the colon is optional.
222
134
 
223
- An exercise slide shows the assertions and hides the answer. Put the answer
224
- above a `// Solution` comment instead: it folds exactly like `// Example`,
225
- the snippet checker compiles the assertions against it, but the handout and
226
- `llms-full.txt` print the fence without it (only its `import` lines survive,
227
- so a reader can still resolve the symbols), and nothing on the slide can
228
- reveal it:
135
+ ### Hide the answer of an exercise
136
+
137
+ Put the answer above a `// Solution` comment. It folds like `// Example`, and
138
+ the snippet checker compiles the assertions against it. The handout and
139
+ `llms-full.txt` leave the answer out (they keep its `import` lines), and
140
+ nothing on the slide can reveal it:
229
141
 
230
142
  ````md
231
143
  ```kotlin
@@ -237,51 +149,26 @@ check(!7.isEven())
237
149
  ```
238
150
  ````
239
151
 
240
- Details:
241
-
242
- - Kotlin, Java, and Scala fences (`kotlin`, `kt`, `kts`, `java`, `scala`)
243
- fold. Without a marker, only a block of `import` lines at the top of the
244
- fence counts, and a `package` line or `@file:` annotation before it stays
245
- visible. With a marker, everything above it folds. Only the first
246
- `// Example` or `// Solution` line in a fence counts, and only when there
247
- is code above it.
248
- - Line numbers, `{1|2-3}` highlight ranges, and annotation targets all
249
- count from the first line of real code, so folding never shifts them.
250
- - Cross-slide Magic Move chains and classic ````md magic-move```` blocks fold
251
- too, step by step.
252
- - Add `show-imports` to a fence (`` ```kotlin show-imports ``) when the slide
253
- is about the imports themselves, or set `themeConfig: { foldImports: false }`
254
- in the headmatter to keep every import visible.
255
-
256
- ### Code sizing
257
-
258
- Every code window uses one font size calculated from the slide canvas width. By
259
- default, a full-width window fits 80 monospace characters. Override the column
260
- count once in the deck's stylesheet, or narrower on a single slide class:
261
-
262
- ```css
263
- :root {
264
- --code-window-columns: 72;
265
- }
266
- ```
152
+ ### Details
267
153
 
268
- The calculation accounts for the standard layout and code padding. A custom
269
- layout with different horizontal spacing can override
270
- `--code-window-inline-space`. To bypass column-based sizing entirely, set an
271
- explicit `--code-window-font-size`, for example `1.25rem`. The font's advance
272
- width and the line height the calculation assumes are
273
- `--code-window-character-width` (`0.61`, JetBrains Mono's) and
274
- `--code-window-line-height` (`1.42`); a deck on another monospace font sets the
275
- first to that font's ratio. The editor surface itself reads `--editor-bg`,
276
- `--editor-fg`, `--editor-border`, and `--editor-shadow`, and one window's
277
- border colour can be overridden with `--code-window-border`.
154
+ - Kotlin, Java, and Scala fences fold (`kotlin`, `kt`, `kts`, `java`,
155
+ `scala`).
156
+ - Without a marker, only the block of `import` lines at the top folds. A
157
+ `package` line or `@file:` annotation before it stays visible.
158
+ - Only the first `// Example` or `// Solution` line in a fence counts, and
159
+ only when there is code above it.
160
+ - Line numbers, `{1|2-3}` highlight ranges, and annotation targets count from
161
+ the first visible line, so folding never shifts them.
162
+ - Magic Move folds too, step by step.
163
+ - To show the imports, add `show-imports` to one fence
164
+ (`` ```kotlin show-imports ``) or set `themeConfig: { foldImports: false }`
165
+ for the whole deck.
278
166
 
279
- ### Magic Move between slides
167
+ ## Magic Move between slides
280
168
 
281
- Slidev's classic Magic Move animates code inside one slide, on clicks. This
282
- theme additionally supports morphing code **across slides**: put `magic-move`
283
- in the separator between two slides and their code windows animate from one to
284
- the next when you navigate.
169
+ Slidev's Magic Move animates code inside one slide. This theme can also morph
170
+ code **from one slide to the next**: put `magic-move` in the separator
171
+ between them.
285
172
 
286
173
  ````md
287
174
  # My Slide
@@ -301,125 +188,85 @@ class Person(val name: String)
301
188
  ```
302
189
  ````
303
190
 
304
- Details:
305
-
306
- - The separator links the slide it precedes to the one before it, so the last
307
- slide of a chain needs no marker. Any number of slides can be chained by
308
- repeating the `magic-move` separator.
309
- - Code windows pair up **by position**: the first fence of a slide morphs into
310
- the first fence of the next, the second into the second, and so on. Several
311
- parallel snippets (say Kotlin next to SQL) each animate independently.
312
- - Everything outside the code fences is a normal slide — headings, prose, and
313
- layout can change freely and follow the deck's regular slide transition.
314
- - With a slide transition such as `fade`, the departing slide's copy of the
315
- window is hidden for the length of the transition, so only the arriving
316
- window is visible while its tokens morph and its box resizes to the new
317
- code. Prefer `fade` over `view-transition`: with `transition:
318
- view-transition`, Slidev 52 switches its slideshow container between a
319
- `div` and a `TransitionGroup` whenever a click is followed by a slide
320
- change, which remounts every preloaded slide of the deck and stalls the
321
- frame for 150 ms or more on each such navigation. If you do use
322
- `view-transition`, the code windows of both slides are paired through the
323
- View Transitions API instead.
324
- - Navigating backward plays the animation in reverse. Deep links, the
325
- overview, and PDF/PNG exports render each slide's own step statically.
326
- - Fence metadata still works: the code-window identity icons, `[Title]`
327
- labels, and `{lines:true}` line numbers apply per chain. Per-click line
328
- highlighting works too — `` ```kotlin {1|2-3} `` registers clicks on that
329
- slide and steps through the ranges before navigation moves on, exactly like
330
- one step of a classic Magic Move block. Snippet imports (`<<< @/...`) do
331
- not participate in chains.
332
- - `magicMove: true` in the slide's YAML frontmatter is equivalent to the bare
333
- `magic-move` separator and can be combined with other frontmatter keys.
334
-
335
- ### Using Kodee Mascot
336
-
337
- Enable Kodee once for the whole deck with `themeConfig`:
338
-
339
- ```yaml
340
- ---
341
- theme: kotlin
342
- themeConfig:
343
- kodee: greeting
344
- ---
345
- ```
346
-
347
- Kodee then uses layout-aware defaults: `small` in the bottom-right corner on regular slides, and `large`/`featured` on `cover` and `intro` slides. Override only the variant when a slide needs a different expression:
348
-
349
- ```yaml
350
- ---
351
- kodee: wink
352
- ---
353
- ```
354
-
355
- Use `kodee: false` to hide the mascot on one slide. The original object form remains available when you need complete control:
191
+ - Repeat the separator to chain any number of slides. The last slide of a
192
+ chain needs no marker.
193
+ - Code windows pair up by position: the first fence morphs into the first
194
+ fence of the next slide, the second into the second. Kotlin next to SQL
195
+ animates side by side.
196
+ - Everything outside the fences is a normal slide. Headings and text can
197
+ change freely.
198
+ - Navigating backward plays the animation in reverse.
199
+ - Icons, `[Title]` labels, `{lines:true}`, and click highlighting such as
200
+ `` ```kotlin {1|2-3} `` all work.
201
+ - `magicMove: true` in a slide's frontmatter does the same as the bare
202
+ separator, and combines with other frontmatter keys.
203
+ - Snippet imports (`<<< @/...`) do not take part in a chain.
204
+ - Use `transition: fade`. With `transition: view-transition`, Slidev 52
205
+ stalls for a moment on many navigations.
206
+
207
+ ## IDE decorations
208
+
209
+ Show what the IDE shows: smart casts, warnings, errors, receiver hints,
210
+ debugger values, and suspension points. Ask for one with a trailing
211
+ `// [!kind …]` comment on the line it belongs to:
356
212
 
357
- ```yaml
358
- ---
359
- kodee:
360
- variant: greeting
361
- size: large
362
- position: featured
363
- ---
364
- ```
365
-
366
- On `cover` and `intro` slides the text sits in a column beside the featured
367
- mascot and wraps inside it instead of running under Kodee. The column is
368
- `--cover-text-width`, 48% of the content box by default, which clears the
369
- mascot's arm. Widen it in the deck's stylesheet when the titles are short, or
370
- give a slide that hides the mascot the full width:
371
-
372
- ```css
373
- .slidev-layout.cover.no-mascot {
374
- --cover-text-width: 100%;
213
+ ````md
214
+ ```kotlin
215
+ fun Application.users() = routing { // [!this Routing]
216
+ route(Api.Users.create) { // [!this TypedRespondScope<CreateUser, User>]
217
+ when (val result = insertUser(body)) {
218
+ is Success -> respond(result.user) // [!smartcast result]
219
+ AlreadyExists -> fail(UserAlreadyExists)
220
+ }
221
+ }
375
222
  }
376
223
  ```
224
+ ````
377
225
 
378
- #### Kodee Configuration Options
379
-
380
- - **variant**: Choose from available Kodee images
381
- - `greeting` - Kodee waving hello
382
- - `wink` - Kodee winking
383
- - `wave` - Kodee waving
384
- - `jumping` - Kodee jumping with joy
385
- - `sitting` - Kodee sitting down
386
- - `drinking` - Kodee with a drink
387
- - `heart` - Kodee with a heart
388
- - `in-love` - Kodee in love
389
- - `welcome` - Kodee welcoming
390
- - `winter` - Kodee in winter attire
391
- - `tiny` - Tiny Kodee
226
+ The comment never reaches the slide, the handout, or the compiled snippets.
392
227
 
393
- - **size**: Control Kodee's size
394
- - `small` - 200x200px (regular-layout default)
395
- - `medium` - 320x320px
396
- - `large` - 600x600px (500x500px for wave variant; cover/intro default)
228
+ | Mark | What appears |
229
+ | --- | --- |
230
+ | `[!smartcast TEXT]` | Green smart-cast background behind `TEXT` |
231
+ | `[!warning TEXT MESSAGE]` | Yellow warning background, with the message after the line |
232
+ | `[!error TEXT MESSAGE]` | Red squiggle under `TEXT`, with the message after the line |
233
+ | `[!this TYPE]` / `[!context TYPE]` | Grey `this: TYPE` hint after the line's `{` |
234
+ | `[!type NAME TYPE]` | Inferred-type hint after `NAME` |
235
+ | `[!value TEXT VALUE]` | Debugger value after the line |
236
+ | `[!suspend]` | Suspend-call icon in the gutter |
397
237
 
398
- - **position**: Control Kodee's placement
399
- - `corner` - Bottom right corner (regular-layout default)
400
- - `featured` - Prominently displayed (cover/intro default)
401
- - `custom` - Use with `x` and `y` coordinates
238
+ Quoting, options such as `on=1`, and several marks on one line are covered in
239
+ [`docs/code-marks.md`](docs/code-marks.md).
402
240
 
403
- - **scale**: Multiply the size, `1.2` for a fifth larger. Defaults to `1`.
241
+ ### As components
404
242
 
405
- #### Custom Positioning
243
+ Each decoration is also a component that wraps one fence. Use it when the
244
+ decoration should appear on a click, or around a classic ````md magic-move````
245
+ block:
406
246
 
407
- For precise control, use custom positioning:
247
+ ````md
248
+ <InlineCompilerError text="userName" message="Unresolved reference: userName" :on="1">
408
249
 
409
- ```yaml
410
- ---
411
- kodee:
412
- variant: greeting
413
- position: custom
414
- x: 100
415
- y: 200
416
- scale: 1.2
417
- ---
250
+ ```kotlin
251
+ println(userName)
418
252
  ```
419
253
 
420
- ### Using DrawnAnnotation
254
+ </InlineCompilerError>
255
+ ````
256
+
257
+ The props of
258
+ [`InlineCompilerError`](components/README.md#inlinecompilererror),
259
+ [`SmartCast`](components/README.md#smartcast),
260
+ [`Warning`](components/README.md#warning),
261
+ [`TypeHint`](components/README.md#typehint),
262
+ [`InlineValue`](components/README.md#inlinevalue), and
263
+ [`SuspendGutter`](components/README.md#suspendgutter) are in
264
+ [`components/README.md`](components/README.md).
265
+
266
+ ## Drawn annotations
421
267
 
422
- `<DrawnAnnotation>` is auto-registered for every deck using the theme. It can mark an element with a selector or exact text inside a Shiki or Magic Move code block, then connect that mark to a label or another element. Labels accept a small, safe Markdown subset (backticked inline code, `>` quotes, `**strong**` or `__strong__`, `*emphasis*` or `_emphasis_`, and line breaks; HTML is escaped): inline code and quotes use the local-first JetBrains Mono stack.
268
+ `<DrawnAnnotation>` draws a hand-drawn mark on an element or on exact text
269
+ inside a code block, and can connect it to a label or to another element:
423
270
 
424
271
  ````md
425
272
  <DrawnAnnotation type="circle" text="fun main" label="program entry point" :on="1">
@@ -431,433 +278,349 @@ fun main() = println("Hello, Kotlin!")
431
278
  </DrawnAnnotation>
432
279
  ````
433
280
 
434
- The full prop reference, timing behavior, and troubleshooting guide are in [`components/README.md`](components/README.md). The bundled [`slides.md`](slides.md) includes working examples of code annotations, connectors, labels, sequential reveals, and Magic Move integration.
281
+ - Marks: `underline`, `circle`, `box`, `strike-through`.
282
+ - Labels take a little Markdown: `` `code` ``, `**strong**`, `*emphasis*`,
283
+ `>` quotes, and line breaks.
284
+ - It follows Magic Move steps and Slidev's clicks.
435
285
 
436
- ### Using InlineCompilerError
286
+ Every prop, the click timing, and troubleshooting are in
287
+ [`components/README.md`](components/README.md#drawnannotation).
437
288
 
438
- `<InlineCompilerError>` is auto-registered for every deck using the theme. Wrap one code block to underline a source line or exact text with an IntelliJ-style diagnostic; it follows both static Shiki blocks and Magic Move steps.
289
+ ### Visual editor
439
290
 
440
- ````md
441
- <InlineCompilerError text="userName" message="Unresolved reference: userName" :on="1">
291
+ Drag labels and connectors into place while the deck runs, instead of typing
292
+ coordinates. Turn it on in the deck's `vite.config.ts`:
442
293
 
443
- ```kotlin
444
- println(userName)
294
+ ```ts
295
+ import { defineConfig } from 'vite'
296
+ import { drawnAnnotationEditor } from 'slidev-theme-kotlin/annotation-editor'
297
+
298
+ export default defineConfig({
299
+ plugins: [drawnAnnotationEditor()],
300
+ })
445
301
  ```
446
302
 
447
- </InlineCompilerError>
448
- ````
303
+ ![The annotation editor: a dragged label, its handles, and the toolbar](docs/annotation-editor.png)
449
304
 
450
- See [`components/README.md`](components/README.md#inlinecompilererror) for the target and click-timing props.
305
+ Then, in `slidev dev`:
451
306
 
452
- #### IntelliJ formatting
307
+ 1. Press **Alt+Shift+A** (**⌥⇧A** on a Mac) on the slide you want to edit. Navigation pauses
308
+ until you leave the editor.
309
+ 2. Drag a label, its width handle, or a connector. The arrow keys nudge the
310
+ selected control (**Shift** for larger steps).
311
+ 3. Press **Done editing annotations**. The positions are saved as a
312
+ `:geometry` attribute on the tags you edited.
453
313
 
454
- IntelliJ recognizes only the first YAML block in a Markdown file as frontmatter. To keep later Slidev frontmatter intact when using **Reformat Code**, enable formatter tags and stop formatting after the deck headmatter:
314
+ **Esc** cancels: first a drag, then the selection, then the whole session.
315
+ The toolbar can also hide, curve, or reset a connector. Nothing of the editor
316
+ is part of a built or exported deck. The details are in
317
+ [Visual-editor geometry](components/README.md#visual-editor-geometry).
455
318
 
456
- ```ini
457
- # .editorconfig
458
- [*.md]
459
- ij_formatter_tags_enabled = true
460
- ```
319
+ ### When an annotation finds nothing
461
320
 
462
- ```md
463
- ---
464
- theme: kotlin
465
- ---
321
+ In `slidev dev`, a red chip in the top-left corner of the slide tells you
322
+ that an annotation or decoration could not find its text or element. Hover it
323
+ for the message; the console prints the same. See
324
+ [When nothing appears](components/README.md#when-nothing-appears).
466
325
 
467
- <!-- @formatter:off -->
468
- ```
326
+ ## Kodee
469
327
 
470
- The theme's example deck includes this guard.
471
-
472
- ## Static handout, `llms.txt`, and hash-mode links
473
-
474
- A Slidev build is a single-page app: without JavaScript its `index.html`
475
- contains nothing but the title. Search engines render JavaScript for the root
476
- URL at best, and AI crawlers (GPTBot, ClaudeBot, CCBot, …) render none, so the
477
- code on your slides never reaches them. Every `slidev build` on this theme
478
- therefore also writes, next to the app:
479
-
480
- - `handout/index.html` and `handout/<section>.html` — one page per source
481
- file (`src:` lesson), with every slide's title, prose, and highlighted code,
482
- each linking back to the live slide. A single-file deck gets one page.
483
- - `llms.txt` and `llms-full.txt` — the [llmstxt.org](https://llmstxt.org)
484
- convention that coding agents and AI crawlers look for: an index, and the
485
- full Markdown of the deck with complete code snippets (folded imports
486
- expanded).
487
- - `sitemap.xml` — only when the deck's public URL is known.
488
- - `robots.txt` — only when that URL is the root of its host and `public/`
489
- ships none. Crawlers read `robots.txt` at the host root alone, so a deck
490
- served from a sub-path (a GitHub project page, a folder of your blog) gets
491
- no file; add `Sitemap: <siteUrl>sitemap.xml` to the host's own
492
- `robots.txt` instead, as the build reminds you.
493
-
494
- The handout and `llms-full.txt` form one outline: the deck, then each
495
- section, then each slide, with a slide's own headings placed below its
496
- title. The handout pages carry a description, canonical URL, Open Graph
497
- tags, and the deck's `lang` from the headmatter (`en` when unset).
498
-
499
- The built `index.html` (and the `404.html` copied from it) is made
500
- self-describing too: `rel="alternate"` links to the handout and
501
- `llms-full.txt`, a plain-text `<meta name="description">` from the headmatter
502
- `info` (replacing the one Slidev renders from the raw Markdown), and a short
503
- paragraph linking to the same files as visible text, hidden as soon as the
504
- app has mounted. That paragraph is ordinary markup rather than `<noscript>`,
505
- which many text extractors drop, so a crawler that renders no JavaScript
506
- still finds the content from the root URL. With `siteUrl` set the page also
507
- carries the canonical URL and a `rel="sitemap"` link.
508
-
509
- Theme markup is translated, not dropped: a `DrawnAnnotation` with a `label`
510
- becomes a quoted note about the code it points at, an `InlineCompilerError`
511
- becomes its diagnostic, Magic Move blocks become their steps, and
512
- `<<< @/file#region` snippet imports are inlined.
513
-
514
- Give the deck its public URL so links, the sitemap, and `llms.txt` are
515
- absolute, and opt out of notes or the whole feature if you need to:
328
+ Turn the mascot on once for the whole deck:
516
329
 
517
330
  ```yaml
331
+ ---
332
+ theme: kotlin
518
333
  themeConfig:
519
- siteUrl: https://example.github.io/my-deck/
520
- handout:
521
- notes: false # leave presenter notes out (default: included)
522
- # handout: false # disable the static content entirely
334
+ kodee: greeting
335
+ ---
523
336
  ```
524
337
 
525
- ## Analytics
338
+ Kodee sits small in the bottom-right corner of regular slides, and large
339
+ beside the title on `cover` and `intro` slides. Between slides it glides to
340
+ its new place.
526
341
 
527
- Decks can count pageviews with [GoatCounter](https://www.goatcounter.com),
528
- which sets no cookies and stores no personal data, so it needs no consent
529
- banner. Give the headmatter the endpoint of your GoatCounter site, or just
530
- its code:
342
+ Change the expression for one slide, or hide the mascot:
531
343
 
532
344
  ```yaml
533
- themeConfig:
534
- analytics:
535
- goatcounter: https://mysite.goatcounter.com/count # or: mysite
345
+ ---
346
+ kodee: wink # or: kodee: false
347
+ ---
536
348
  ```
537
349
 
538
- Every `slidev build` then adds `count.js` to the deck, and the theme sends
539
- one pageview per slide, recorded as `<base>/<slide number>` with the slide's
540
- title, so the dashboard's path list doubles as a per-slide drop-off curve.
541
- Clicks within a slide, the presenter and overview views, notes, print, and
542
- export are never counted. Handout pages carry the tag too and count under
543
- their own URL. `slidev dev`, exports, and screenshots send nothing:
544
- `count.js` ignores localhost and headless browsers by itself.
545
-
546
- Every deck on one host can share one GoatCounter site, since paths carry the
547
- deck's base. Add `#toggle-goatcounter` to a deck's URL to stop counting your
548
- own visits in that browser.
549
-
550
- ## Compiling the snippets
350
+ The variants are `greeting`, `wink`, `wave`, `jumping`, `sitting`,
351
+ `drinking`, `heart`, `in-love`, `welcome`, `winter`, and `tiny`.
551
352
 
552
- A Kotlin fence on a slide is a fragment: it carries its imports (folded away,
553
- see above) but no package, and it may mix declarations with bare statements.
554
- `slidev-kotlin-snippets` turns Kotlin and Java fences into source files, in the
555
- spirit of [KotlinX Knit](https://github.com/Kotlin/kotlinx-knit), so a Gradle
556
- module can compile the deck's code against the real libraries and a stale slide
557
- fails the build instead of the talk.
353
+ ### Size and position
558
354
 
559
- Turn it on in the headmatter and run the CLI from the deck:
355
+ Use the object form for full control:
560
356
 
561
357
  ```yaml
562
- themeConfig:
563
- snippets:
564
- dir: src/main/kotlin/presentation/snippets # default: src/main/kotlin/snippets
565
- javaDir: src/main/java/presentation/snippets # default: src/main/java/snippets
566
- package: presentation.snippets # default: snippets
567
- imports: # added to every file
568
- - presentation.support.*
569
- ```
570
-
571
- ```bash
572
- npx slidev-kotlin-snippets # writes the files, removes stale ones
573
- npx slidev-kotlin-snippets --check # exits 1 when they are out of date (CI)
574
- npx slidev-kotlin-snippets talk.md # another entry than slides.md
358
+ ---
359
+ kodee:
360
+ variant: greeting
361
+ size: large
362
+ position: featured
363
+ ---
575
364
  ```
576
365
 
577
- Each Kotlin fence becomes `<dir>/<section>/<slide>-<title>.kt`; each Java
578
- fence becomes `<javaDir>/<section>/<slide>-<title>.java`. Files have a package,
579
- `<package>.<section>.slide<n>`, so every step of a Magic Move chain can declare
580
- its own `object Talks`. A Java and Kotlin fence on the same slide deliberately
581
- share that package, allowing Java ↔ Kotlin interop examples to compile. Inside
582
- the file:
583
-
584
- - the fence's imports come first, after the `imports` from the config. Keep
585
- the tables, types, and values the slides use without defining them in a
586
- hand-written package and star-import it; a fence that defines its own
587
- `Talks` shadows the shared one, because a declaration in the file's own
588
- package wins over a star import.
589
- - Kotlin top-level declarations (`fun`, `val`, `object`, `class`, ... with
590
- their annotations, modifiers, and `context(...)` lines) stay top-level, and
591
- bare statements such as `Talks.selectAll()` are wrapped in `suspend fun main()`.
592
- A Kotlin `package` line is dropped; `@file:` annotations move above the package.
593
- - Java fences are emitted as Java compilation units with the generated package
594
- and imports; they must therefore contain declarations rather than bare
595
- statements. Their own `package` line is replaced. A fence declaring a public
596
- top-level Java type is named after that type (as Java requires); otherwise it
597
- uses the slide-based filename. When a later slide of the same section declares
598
- a public type an earlier slide already declared, its file goes in a directory
599
- named after the slide, so both compile.
600
- - a slide with several fences in the same language tells them apart by their
601
- first meta word (`jdbc`, `r2dbc`) or a letter.
602
-
603
- Not every fence is meant to compile. Add `no-compile` to a fence that sketches
604
- library internals or an intermediate step (`` ```kotlin no-compile ``); the flag
605
- is stripped before Slidev sees it. Fences inside an `InlineCompilerError` are
606
- skipped automatically, since the slide shows the diagnostic on purpose, and so
607
- are `gradle` build-script fences.
608
-
609
- Wire it into Gradle so `./gradlew check` also fails on stale sources:
366
+ | Key | Values | Default |
367
+ | --- | --- | --- |
368
+ | `variant` | one of the variants above | the deck's `themeConfig.kodee` |
369
+ | `size` | `small` (200px), `medium` (320px), `large` (600px; 500px for `wave`) | `small`; `large` on `cover` and `intro` |
370
+ | `position` | `corner`, `featured`, `custom` | `corner`; `featured` on `cover` and `intro` |
371
+ | `x`, `y` | pixels, with `position: custom` | – |
372
+ | `scale` | a multiplier, `1.2` for a fifth larger | `1` |
610
373
 
611
- ```kotlin
612
- val snippetsCheck by tasks.registering(Exec::class) {
613
- commandLine("npx", "slidev-kotlin-snippets", "--check")
614
- }
615
- tasks.check { dependsOn(snippetsCheck) }
616
- ```
374
+ ## Layouts
617
375
 
618
- ### Deep links on GitHub Pages
376
+ | Layout | Use |
377
+ | --- | --- |
378
+ | `default` | A regular slide |
379
+ | `cover` | The title slide, with the text beside a large Kodee |
380
+ | `intro` | A section or speaker introduction, same arrangement as `cover` |
619
381
 
620
- GitHub Pages has no single-page-app rewrite, so a history-mode deep link such
621
- as `/my-deck/12` is answered by the copied `404.html` with a 404 status. Build
622
- with Slidev's hash router instead:
382
+ ```yaml
383
+ ---
384
+ layout: cover
385
+ ---
623
386
 
624
- ```bash
625
- slidev build --base /my-deck/ --router-mode hash
387
+ # Presentation Title
626
388
  ```
627
389
 
628
- Links then look like `/my-deck/#/12` and always resolve. The theme adds a small
629
- script to hash-mode builds that forwards old `/my-deck/12` links (and
630
- `presenter/…`, `overview`, `notes`, `print`, `export`) to their hash
631
- equivalent, so links shared before the switch keep working.
632
-
633
- ## Development
390
+ Slidev's built-in layouts, such as `two-cols-header`, keep working.
634
391
 
635
- ### Available Scripts
392
+ ## Customising
636
393
 
637
- ```bash
638
- # Start development server with example presentation
639
- npm run dev
394
+ Colours, sizes, and fonts are CSS variables. Override them in the deck's own
395
+ stylesheet (`style.css` or `styles/index.css`).
640
396
 
641
- # Build the example presentation for production
642
- npm run build
397
+ ### Code-window colours
643
398
 
644
- # Export presentation to PDF
645
- npm run export
399
+ Each icon has a border colour, with a light and a dark value:
646
400
 
647
- # Export every slide and click state as PNG screenshots
648
- npm run screenshot
401
+ ```css
402
+ :root {
403
+ --code-window-kotlin-color: #834df0;
404
+ --code-window-gradle-color: #6c707e;
405
+ --code-window-amper-color: #087cfa;
406
+ --code-window-arrow-color: #2455cf;
407
+ --code-window-ktor-color: #7f52ff;
408
+ --code-window-java-color: #e66d17;
409
+ --code-window-terminal-color: #6c707e;
410
+ --code-window-maven-color: #3574f0;
411
+ --code-window-postgresql-color: #336791;
412
+ --code-window-jdbc-color: #f59e0b;
413
+ --code-window-r2dbc-color: #06b6d4;
649
414
 
650
- # Export one slide; --click is zero-based (0 is the initial state)
651
- npm run export:slide -- --slide 12 --click 3
415
+ /* The `console` output panel. */
416
+ --code-window-output-background: #f7f8fa;
417
+ --code-window-output-color: #2b2d30;
418
+ }
652
419
 
653
- # Render this working tree against HEAD and open the generated visual review
654
- npm run visual:review
420
+ html.dark {
421
+ --code-window-kotlin-color: #a571e6;
422
+ --code-window-output-background: #1e1f22;
423
+ --code-window-output-color: #bcbec4;
424
+ /* Override any of the other dark-mode values as needed. */
425
+ }
655
426
  ```
656
427
 
657
- ### Exporting One Slide
658
-
659
- `npm run export:slide` is an AI-agent-friendly wrapper around `slidev export`. It writes one PNG rather than a directory of all exported pages:
428
+ The editor surface reads `--editor-bg`, `--editor-fg`, `--editor-border`, and
429
+ `--editor-shadow`. `--code-window-border` overrides the border of one window.
660
430
 
661
- ```bash
662
- # Initial state of slide 12
663
- npm run export:slide -- --slide 12
431
+ ### Code size
664
432
 
665
- # State after the third click of slide 12
666
- npm run export:slide -- --slide 12 --click 3 --output artifacts/slide-12-click-3.png
433
+ Every code window uses one font size, chosen so that a full-width window fits
434
+ 80 characters. Change the number of columns for the deck, or for one slide
435
+ class:
667
436
 
668
- # Use another deck entry
669
- npm run export:slide -- --entry path/to/talk.md --slide 4 --click 0
437
+ ```css
438
+ :root {
439
+ --code-window-columns: 72;
440
+ }
670
441
  ```
671
442
 
672
- `--slide` is one-based. `--click` is optional and zero-based, so `--click 0` is the initial slide state, `--click 1` is the state after one click, and so on. Without `--output`, screenshots are written under `.slidev/exports/`, which is ignored by Git. Run `npm run export:slide -- --help` for timeout, wait, and dark-mode options.
443
+ | Variable | Default | Use |
444
+ | --- | --- | --- |
445
+ | `--code-window-columns` | `80` | Characters that fit a full-width window |
446
+ | `--code-window-font-size` | from the columns | Set a size such as `1.25rem` and skip the calculation |
447
+ | `--code-window-inline-space` | the standard layout's | Horizontal padding, for a custom layout |
448
+ | `--code-window-character-width` | `0.61` | Width of one character relative to the font size; change it for another monospace font |
449
+ | `--code-window-line-height` | `1.42` | Line height |
673
450
 
674
- ### Running the Slides
451
+ ### Cover text width
675
452
 
676
- The repository includes `slides.md`, a compact presentation demonstrating the theme's layouts, Kotlin highlighting, Kodee animations, magic moves, and click animations:
453
+ On `cover` and `intro` slides the text sits in a column beside Kodee. The
454
+ column is `--cover-text-width`, 48% by default. Widen it when the titles are
455
+ short, or give a slide without the mascot the full width:
677
456
 
678
- ```bash
679
- npm run dev
457
+ ```css
458
+ .slidev-layout.cover.no-mascot {
459
+ --cover-text-width: 100%;
460
+ }
680
461
  ```
681
462
 
682
- This will open the example presentation in your browser with hot-reload enabled.
463
+ ### Fonts
683
464
 
684
- ## Project Structure
465
+ The theme uses JetBrains Sans and JetBrains Mono when they are installed on
466
+ your machine. Otherwise it falls back to Inter and to JetBrains Mono from
467
+ Google Fonts.
685
468
 
686
- ```
687
- .
688
- ├── components/ # Vue components
689
- │ ├── DrawnAnnotation.vue # Hand-drawn marks, labels, and connectors
690
- │ ├── DrawnAnnotationEditor*.vue # Development-only editor launcher and toolbar
691
- │ ├── DrawnAnnotationProblems.vue # Development-only problem chips
692
- │ ├── drawn-annotation/ # Editor store, geometry, and browser writer
693
- │ ├── InlineCompilerError.vue # Inline code diagnostics
694
- │ ├── SmartCast.vue # Smart-cast background behind exact code
695
- │ ├── Warning.vue # Warning background with an optional message
696
- │ ├── TypeHint.vue # Inlay hint after a lambda's brace
697
- │ ├── InlineValue.vue # Debugger inline value after a line
698
- │ ├── code-highlight.ts # The mechanism the four above share
699
- │ ├── code-text-match.ts # Text search shared with DrawnAnnotation
700
- │ ├── MagicMoveBetween.vue # Cross-slide Magic Move code window
701
- │ ├── Kodee.vue # Main Kodee mascot component with animations
702
- │ └── KodeeWrapper.vue # Wrapper component for Kodee integration
703
- ├── global-top.vue # Development-only editor toolbar and problem chips
704
- ├── global-bottom.vue # Deck-wide Kodee layer that follows the current slide
705
- ├── slide-bottom.vue # Per-slide Kodee for the overview and presenter preview
706
- ├── plugins/ # Development-only source writer (`slidev-theme-kotlin/annotation-editor`)
707
- ├── bin/ # `slidev-kotlin-snippets` CLI
708
- ├── layouts/ # Slidev layout templates
709
- │ ├── cover.vue # Cover slide layout
710
- │ ├── default.vue # Default slide layout
711
- │ └── intro.vue # Introduction slide layout
712
- ├── assets/ # Bundled theme assets
713
- │ ├── kodee-*.svg # Kodee mascot variants (11 variants)
714
- │ └── code-window/ # Code-window identity icons, referenced from layout.css
715
- ├── index.html # Head tags merged into every deck (font preconnect)
716
- ├── setup/ # Slidev setup files
717
- │ ├── main.ts # App setup: per-slide analytics pageviews
718
- │ ├── preparser.ts # The bare `magic-move` slide separator
719
- │ ├── shiki.ts # Syntax highlighting configuration for Kotlin
720
- │ ├── transformers.ts # Cross-slide Magic Move and import folding passes
721
- │ ├── magic-move-between.ts # Precompiled Magic Move steps
722
- │ ├── analytics.ts # GoatCounter tag and per-slide pageviews
723
- │ ├── code-imports.ts # Folded imports: fence scanning and source edits
724
- │ ├── kotlin-snippets.ts # Compilable snippet files per fence
725
- │ ├── static-content.ts # Handout, llms.txt, sitemap generation
726
- │ ├── hash-route-redirect.ts # Old history-mode links → hash routes
727
- │ └── vite-plugins.ts # Registers the three build plugins above
728
- ├── styles/ # Theme styles
729
- │ ├── index.ts # Style entry point
730
- │ └── layout.css # Layout styles
731
- ├── .pi/extensions/inspect-shiki-token-scopes.ts # Local Pi tool for inspecting Shiki token scopes
732
- ├── slides.md # Example presentation used for local development and GitHub Pages
733
- ├── package.json # Package configuration and dependencies
734
- └── LICENSE # Apache License 2.0
735
- ```
469
+ ### `themeConfig` reference
736
470
 
737
- ## Environment Variables
471
+ | Key | Default | Use |
472
+ | --- | --- | --- |
473
+ | `kodee` | off | The deck's [Kodee](#kodee) variant, or the object form |
474
+ | `foldImports` | `true` | `false` keeps every import visible |
475
+ | `drawnAnnotation.connect` | `true` | `false` draws labels without a line to their mark |
476
+ | `siteUrl` | – | The deck's public URL, for [publishing](#publishing-a-deck) |
477
+ | `handout` | on | `false` turns the static handout off; `handout.notes: false` leaves presenter notes out |
478
+ | `analytics.goatcounter` | – | A GoatCounter site, to count pageviews |
479
+ | `snippets` | – | Where [compiled snippets](#compiling-the-snippets) go |
738
480
 
739
- <!-- TODO: Document any environment variables if needed -->
481
+ ## Publishing a deck
740
482
 
741
- No environment variables are currently required for this theme.
483
+ A built Slidev deck is a JavaScript app, so search engines and AI crawlers
484
+ see none of your code. Every `slidev build` on this theme therefore also
485
+ writes a static **handout** (`handout/index.html`), **`llms.txt`** and
486
+ **`llms-full.txt`**, and a sitemap. Give the deck its public URL so the links
487
+ are absolute:
742
488
 
743
- ## Visual regression review
489
+ ```yaml
490
+ themeConfig:
491
+ siteUrl: https://example.github.io/my-deck/
492
+ ```
744
493
 
745
- Visual review starts a temporary Slidev server and uses Playwright Chromium to
746
- capture every settled click state from Slidev's normal presenter/player route
747
- (`/<slide>?clicks=<click>`), rather than the print-export `?print=clicks` route.
748
- It renders the current working tree and a Git revision with the same browser,
749
- compares every PNG with `pixelmatch`, and writes a three-column
750
- base/current/diff page. When the local
751
- `../kotlin-fundamentals` consumer deck is available, its baseline is
752
- rendered from its clean `HEAD` and its current capture includes its working-tree
753
- changes, with each capture linked to the corresponding theme revision.
754
- Screenshots are not committed: the chosen Git revision is the baseline, so
755
- accepting an intentional visual change is simply reviewing it and committing
756
- that change.
494
+ On GitHub Pages, build with the hash router so deep links resolve:
757
495
 
758
496
  ```bash
759
- # Compare the working tree with the last commit.
760
- npm run visual:review
497
+ slidev build --base /my-deck/ --router-mode hash
498
+ ```
761
499
 
762
- # Compare a branch with its integration base.
763
- npm run visual:review -- --base origin/main
500
+ To count pageviews per slide without cookies, add a
501
+ [GoatCounter](https://www.goatcounter.com) site:
764
502
 
765
- # Use in automation when any visual difference should fail the command.
766
- npm run visual:check -- --base origin/main
503
+ ```yaml
504
+ themeConfig:
505
+ analytics:
506
+ goatcounter: mysite
767
507
  ```
768
508
 
769
- Open the `file://.../.visual/review/index.html` path printed by the command.
770
- The `.visual/` directory contains disposable base/current captures and diffs
771
- and is ignored by Git. `npm run screenshot` is the non-comparison escape hatch
772
- when raw PNGs are all that is needed.
509
+ What each file contains, `robots.txt`, and what is counted are in
510
+ [`docs/publishing.md`](docs/publishing.md).
511
+
512
+ ## Presenting from your phone
773
513
 
774
- The visual exporter requires the Chromium binary supplied by
775
- `playwright-chromium`. If dependency installation is configured not to run
776
- package install scripts, download it once with:
514
+ `slidev-kotlin-present` serves a built deck on your local network and turns
515
+ your phone into the clicker. No internet connection is needed.
777
516
 
778
517
  ```bash
779
- npx playwright install chromium
518
+ slidev build
519
+ npx slidev-kotlin-present
780
520
  ```
781
521
 
782
- `npm run lint` validates the deck's frontmatter and Kodee configuration; `npm
783
- run build` runs that lint before building the Slidev deck.
522
+ It prints a **Display** link and a QR code. Open the link on the laptop, scan
523
+ the code with your phone, and tap: the right half of the screen advances, the
524
+ left half goes back. The phone shows what the next tap reveals, your speaker
525
+ notes, and whether the display follows.
526
+
527
+ Only a page opened from the QR code can move the slides. The laptop and the
528
+ phone must be on the same network; when the venue's Wi-Fi keeps them apart,
529
+ use your phone's hotspot.
784
530
 
785
- ## Configuration
531
+ On a laptop, `/presenter/1` is still Slidev's presenter console. The status
532
+ line, the options, and who can read what are in
533
+ [`docs/presenting.md`](docs/presenting.md).
786
534
 
787
- ### Theme Defaults
535
+ ## Compiling the snippets
788
536
 
789
- The theme is configured with the following defaults in `package.json`:
537
+ `slidev-kotlin-snippets` turns the Kotlin and Java fences of a deck into
538
+ source files, so a Gradle module can compile them against the real libraries.
539
+ A stale slide then fails the build instead of the talk.
790
540
 
791
- ```json
792
- {
793
- "slidev": {
794
- "colorSchema": "both",
795
- "defaults": {
796
- "fonts": {
797
- "sans": "JetBrains Sans,Inter",
798
- "mono": "JetBrains Mono Local,JetBrains Mono",
799
- "local": "JetBrains Sans,JetBrains Mono Local",
800
- "weights": "400,700,800",
801
- "italic": true
802
- }
803
- }
804
- }
805
- }
541
+ ```yaml
542
+ themeConfig:
543
+ snippets:
544
+ dir: src/main/kotlin/snippets
545
+ package: snippets
806
546
  ```
807
547
 
808
- ## Layouts
548
+ ```bash
549
+ npx slidev-kotlin-snippets # writes the files, removes stale ones
550
+ npx slidev-kotlin-snippets --check # exits 1 when they are out of date (CI)
551
+ ```
809
552
 
810
- The theme provides three layouts:
553
+ Add `no-compile` to a fence that is not meant to compile
554
+ (`` ```kotlin no-compile ``). The Gradle wiring, the remaining options, and
555
+ how fences map to files are in [`docs/snippets.md`](docs/snippets.md).
811
556
 
812
- 1. **default** - Standard slide layout with optional Kodee
813
- 2. **cover** - Cover slide for presentation title
814
- 3. **intro** - Introduction slide layout
557
+ ## Tips
815
558
 
816
- Use layouts by specifying them in slide frontmatter:
559
+ ### IntelliJ's Reformat Code
817
560
 
818
- ```yaml
561
+ IntelliJ treats only the first YAML block of a Markdown file as frontmatter,
562
+ so **Reformat Code** breaks the frontmatter of later slides. Enable formatter
563
+ tags and switch formatting off after the headmatter:
564
+
565
+ ```ini
566
+ # .editorconfig
567
+ [*.md]
568
+ ij_formatter_tags_enabled = true
569
+ ```
570
+
571
+ ```md
819
572
  ---
820
- layout: cover
573
+ theme: kotlin
821
574
  ---
822
575
 
823
- # Presentation Title
576
+ <!-- @formatter:off -->
824
577
  ```
825
578
 
826
- ## Dependencies
579
+ ## Developing the theme
580
+
581
+ `slides.md` is the demo deck. `slides.fixtures.md` holds edge cases for the
582
+ visual review and is not published.
827
583
 
828
- ### Runtime Dependencies
829
- - `@shikijs/magic-move` (4.4.3) - Code-token morphing for Magic Move
830
- - `@slidev/parser` (^52.19.1) - Deck parsing for the snippets CLI and lint script
831
- - `@slidev/types` (^52.19.1) - Slidev type definitions
832
- - `jiti` (2.7.0) - Runs the theme's TypeScript setup modules from the CLI
833
- - `markdown-it` (14.3.0) - Renders the static handout
834
- - `roughjs` (4.6.6) - Hand-drawn SVG paths for `DrawnAnnotation`
584
+ | Command | What it does |
585
+ | --- | --- |
586
+ | `npm run dev` | Serve the demo deck with hot reload |
587
+ | `npm run build` | Lint and build the demo deck |
588
+ | `npm test` | Unit tests |
589
+ | `npm run test:annotation-editor` | Browser test of the visual editor |
590
+ | `npm run test:remote` | Browser test of the phone remote, after `npm run build` |
591
+ | `npm run present` | Present the built demo deck with the phone remote |
592
+ | `npm run visual:review` | Compare screenshots of the working tree with the last commit |
593
+ | `npm run export` | Export the demo deck to PDF |
594
+ | `npm run screenshot` | Export every slide and click state as PNG |
595
+ | `npm run export:slide -- --slide 12 --click 3` | Export one slide as PNG; `--click` starts at 0 |
835
596
 
836
- ### Development Dependencies
837
- - `@slidev/cli` (52.19.1) - Slidev command-line interface
597
+ ### Visual review
838
598
 
839
- ## Contributing
599
+ `npm run visual:review` renders every click state of both decks twice, for
600
+ the working tree and for a Git revision, and writes a page with base,
601
+ current, and diff side by side. Open the `file://` path it prints.
840
602
 
841
- <!-- TODO: Add contribution guidelines -->
603
+ ```bash
604
+ npm run visual:review # against the last commit
605
+ npm run visual:review -- --base origin/main # against a branch
606
+ npm run visual:check -- --base origin/main # fail on any difference
607
+ ```
842
608
 
843
- Contributions are welcome! Please feel free to submit issues or pull requests.
609
+ No screenshots are committed: the Git revision is the baseline, so you accept
610
+ a visual change by committing it. If Chromium is missing, install it once
611
+ with `npx playwright install chromium`.
844
612
 
845
613
  ## License
846
614
 
847
- This project is licensed under the Apache License 2.0. See the [LICENSE](LICENSE) file for details.
615
+ Apache License 2.0. See [LICENSE](LICENSE).
848
616
 
849
617
  ## Credits
850
618
 
851
- - **Kodee Mascot**: Official Kotlin mascot by JetBrains
852
- - **Kotlin Logos**: Official Kotlin branding by JetBrains
853
- - **Slidev**: Presentation framework by [Anthony Fu](https://github.com/antfu)
619
+ - **Kodee** and the Kotlin logo: JetBrains
620
+ - **Slidev**: [Anthony Fu](https://github.com/antfu)
854
621
 
855
622
  ## Links
856
623
 
857
- - [Slidev Documentation](https://sli.dev)
858
- - [Kotlin Official Website](https://kotlinlang.org)
624
+ - [Slidev documentation](https://sli.dev)
625
+ - [Kotlin](https://kotlinlang.org)
859
626
  - [Package on npm](https://www.npmjs.com/package/slidev-theme-kotlin)
860
-
861
- ---
862
-
863
- Made with ❤️ for the Kotlin community