slidev-theme-cyberpunk-ide 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.
package/README.md ADDED
@@ -0,0 +1,216 @@
1
+ # slidev-theme-cyberpunk-ide
2
+
3
+ [![NPM version](https://img.shields.io/npm/v/slidev-theme-cyberpunk-ide?color=3AB9D4&label=)](https://www.npmjs.com/package/slidev-theme-cyberpunk-ide)
4
+
5
+ A cyberpunk IDE-style theme for [Slidev](https://github.com/slidevjs/slidev), designed for teaching computer science. Each slide looks like a file open in a dark IDE, complete with title bar, tab bar, and status bar.
6
+
7
+ ## Install
8
+
9
+ Add the following frontmatter to your `slides.md`:
10
+
11
+ ```yaml
12
+ ---
13
+ theme: cyberpunk-ide
14
+ ---
15
+ ```
16
+
17
+ ## Fonts
18
+
19
+ The theme uses **Monaspace Neon** (body and code) and **Monaspace Radon** (comments in code blocks). Both variable fonts are bundled with the theme — no manual download or configuration required. They are licensed under the [SIL Open Font License 1.1](public/fonts/LICENSE).
20
+
21
+ ## Layouts
22
+
23
+ ### `cover` (default for slide 1)
24
+
25
+ Title slide with a cyberpunk grid background, scan lines, and an animated gradient on the heading.
26
+
27
+ ```yaml
28
+ ---
29
+ layout: cover
30
+ ---
31
+
32
+ # Course Title
33
+
34
+ Subtitle or description
35
+ ```
36
+
37
+ **Optional named slots:**
38
+
39
+ | Slot | Position | Purpose |
40
+ | ---------------- | ------------ | -------------------------- |
41
+ | `::logo::` | Top-left | School or institution logo |
42
+ | `::logo-right::` | Top-right | Department or course logo |
43
+ | `::sponsors::` | Bottom strip | Sponsor / partner logos |
44
+
45
+ The top bar is only rendered if at least one of `logo` or `logo-right` is provided. The sponsors strip is only rendered if `sponsors` is provided.
46
+
47
+ ```markdown
48
+ ---
49
+ layout: cover
50
+ ---
51
+
52
+ # Course Title
53
+
54
+ Subtitle
55
+
56
+ ::logo::
57
+ <img src="/logo-school.png" alt="School" />
58
+
59
+ ::logo-right::
60
+ <img src="/logo-dept.png" alt="Department" />
61
+
62
+ ::sponsors::
63
+ <img src="/sponsor1.png" alt="Sponsor A" />
64
+ <img src="/sponsor2.png" alt="Sponsor B" />
65
+ ```
66
+
67
+ Images are automatically constrained (`max-height: 44px` for logos, `28px` for sponsors). Place image files in your presentation's `public/` folder and reference them with a leading `/`.
68
+
69
+ ### `default`
70
+
71
+ Standard content slide wrapped in the IDE (title bar, tab bar, editor area, status bar).
72
+
73
+ ```yaml
74
+ ---
75
+ filename: algorithms.py # tab name and title bar — default: main.py
76
+ language: Python # status bar right — default: Python
77
+ branch: 03/recursion # status bar left — default: main
78
+ repo: ComputerScience101 # status bar left (repo name) — default: cyberpunk-ide
79
+ ---
80
+
81
+ # Slide title
82
+ ```
83
+
84
+ ### `section`
85
+
86
+ Full-screen section divider with a grid background and a glowing accent line.
87
+
88
+ ```yaml
89
+ ---
90
+ layout: section
91
+ section: Module 2 # label shown above the title — default: Modulo
92
+ ---
93
+
94
+ # *Chapter* Title
95
+ ```
96
+
97
+ Wrap a word in `*...*` (em) to apply the neon purple accent color.
98
+
99
+ ## Tab bar
100
+
101
+ The tab bar shows one chip-style tab per slide, auto-scrolling to keep the active one visible. Clicking any tab navigates to that slide.
102
+
103
+ ### Hiding slides from the tab bar
104
+
105
+ By default, slides with `layout: cover` or `layout: section` are **hidden** from the tab bar (they don't have IDE chrome anyway).
106
+
107
+ To **show all slides** including cover and section slides:
108
+
109
+ ```yaml
110
+ ---
111
+ # in the presentation headmatter (first slide)
112
+ themeConfig:
113
+ tabsShowAll: true
114
+ ---
115
+ ```
116
+
117
+ To **hide a specific slide** from the tab bar regardless of the global setting:
118
+
119
+ ```yaml
120
+ ---
121
+ hideTab: true
122
+ ---
123
+ ```
124
+
125
+ ## Status bar
126
+
127
+ | Position | Content | Frontmatter key | Default |
128
+ | -------- | -------------------------------- | --------------- | --------------------------- |
129
+ | Left | Repo name (GitHub icon) | `repo` | `cyberpunk-ide` |
130
+ | Left | Branch / topic (git branch icon) | `branch` | `main` |
131
+ | Right | Language | `language` | `Python` |
132
+ | Right | Encoding | — | `UTF-8` (fixed) |
133
+ | Right | Slide counter | — | `current / total` (dynamic) |
134
+
135
+ ## Line numbers
136
+
137
+ Line numbers are **enabled by default** by the theme. The scope of control is:
138
+
139
+ | Level | How |
140
+ | -------------------------------- | ---------------------------------- |
141
+ | Whole presentation (disable) | `lineNumbers: false` in headmatter |
142
+ | Single code block (disable) | ` ```python {lineNumbers:false} ` |
143
+ | Single code block (custom start) | ` ```python {startLine:10} ` |
144
+
145
+ ## Syntax highlighting
146
+
147
+ Code blocks use the **Tokyo Night** theme via Shiki. Comment tokens are automatically rendered in **Monaspace Radon** (cursive/calligraphic style) to visually distinguish them from code.
148
+
149
+ ## Callouts
150
+
151
+ Six callout types are available for highlighting definitions, tips, warnings, and learning objectives.
152
+
153
+ ```markdown
154
+ :::definition Definition
155
+ An **algorithm** is a finite sequence of unambiguous instructions.
156
+ :::
157
+
158
+ :::info Useful Information
159
+ You can use `len()` to get the length of any sequence.
160
+ :::
161
+
162
+ :::warning Warning
163
+ Do not modify a list while iterating over it.
164
+ :::
165
+
166
+ :::clean Clean Code
167
+ Use descriptive names: `number_of_students` is better than `n`.
168
+ :::
169
+
170
+ :::code Python Syntax
171
+ `for item in list:` iterates without using indices.
172
+ :::
173
+
174
+ :::learn What You Will Learn
175
+ In this module you will see **recursion** in action.
176
+ :::
177
+ ```
178
+
179
+ | Type | Color | Use for |
180
+ | ------------ | ------ | ---------------------------- |
181
+ | `definition` | brown | Formal definitions |
182
+ | `info` | yellow | Tips and notes |
183
+ | `warning` | red | Pitfalls and gotchas |
184
+ | `clean` | cyan | Best practices / clean code |
185
+ | `code` | grey | Syntax reminders / code tips |
186
+ | `learn` | purple | Learning objectives |
187
+
188
+ **Custom icon and color** — each callout accepts optional `icon` and `color` props to override the preset:
189
+
190
+ ```markdown
191
+ <Callout type="info" title="Nota" icon="i-ph-star" color="#9ece6a">
192
+
193
+ Custom icon (any UnoCSS / Phosphor icon class) and hex color.
194
+
195
+ </Callout>
196
+ ```
197
+
198
+ > Icons used by the presets (`paper.png`, `bulb.png`, etc.) must be present in `public/callouts/` of the presentation folder.
199
+
200
+ ## Glossary tooltips
201
+
202
+ Add a `glossary` map to a slide's frontmatter to automatically wrap matching terms with interactive tooltips. Hovering (or focusing) the term shows its definition inline.
203
+
204
+ ```yaml
205
+ ---
206
+ filename: recursion.py
207
+ glossary:
208
+ algorithm: A finite sequence of **unambiguous** instructions that solves a problem
209
+ recursion: A technique where a function calls `itself` to solve sub-problems
210
+ base case: The condition that stops the recursion, preventing a stack overflow
211
+ ---
212
+ ```
213
+
214
+ Definitions support inline formatting: `**bold**`, `*italic*`, `` `code` ``.
215
+
216
+ Terms inside fenced code blocks and inline code spans are left untouched.
package/UNLICENSE ADDED
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <https://unlicense.org/>
File without changes
@@ -0,0 +1,37 @@
1
+ <script setup lang="ts">
2
+ const props = defineProps<{
3
+ type?: string
4
+ title: string
5
+ icon?: string
6
+ color?: string
7
+ }>()
8
+
9
+ const presets: Record<string, { icon: string; color: string }> = {
10
+ definition: { icon: '/callouts/paper.png', color: '#a9836e' },
11
+ info: { icon: '/callouts/bulb.png', color: '#e0af68' },
12
+ warning: { icon: '/callouts/fire.png', color: '#f7768e' },
13
+ clean: { icon: '/callouts/clean.png', color: '#7dcfff' },
14
+ code: { icon: '/callouts/code.png', color: '#565f89' },
15
+ learn: { icon: '/callouts/brain.png', color: '#bb9af7' },
16
+ }
17
+
18
+ const preset = props.type ? presets[props.type] : undefined
19
+ const resolvedColor = props.color ?? preset?.color ?? '#7dcfff'
20
+ const resolvedIcon = props.icon ?? preset?.icon ?? 'i-ph-info'
21
+
22
+ // true = UnoCSS icon class, false = image path
23
+ const isUnoIcon = resolvedIcon.startsWith('i-')
24
+ </script>
25
+
26
+ <template>
27
+ <div class="callout" :data-type="type" :style="{ '--callout-color': resolvedColor }">
28
+ <div class="callout-header">
29
+ <span v-if="isUnoIcon" class="callout-icon" :class="resolvedIcon" />
30
+ <img v-else class="callout-icon" :src="resolvedIcon" aria-hidden="true" />
31
+ <span class="callout-title">{{ title }}</span>
32
+ </div>
33
+ <div class="callout-body">
34
+ <slot />
35
+ </div>
36
+ </div>
37
+ </template>
@@ -0,0 +1,115 @@
1
+ <script setup lang="ts">
2
+ import { configs } from '@slidev/client'
3
+ import { useNav } from '@slidev/client'
4
+ import { computed, nextTick, ref, watch } from 'vue'
5
+
6
+ withDefaults(defineProps<{
7
+ filename?: string
8
+ language?: string
9
+ branch?: string
10
+ repo?: string
11
+ }>(), {
12
+ filename: 'main.py',
13
+ language: 'Python',
14
+ branch: 'main',
15
+ repo: 'cyberpunk-ide',
16
+ })
17
+
18
+ const { slides, currentPage, total, go } = useNav()
19
+
20
+ // themeConfig.tabsShowAll: true → show every slide in the tab bar
21
+ // default (false/unset) → hide slides with layout cover or section
22
+ const tabsShowAll = computed(() => {
23
+ const v = configs.themeConfig?.tabsShowAll
24
+ return v === true || v === 'true' || v === 1
25
+ })
26
+
27
+ const visibleSlides = computed(() => {
28
+ return slides.value.filter((slide) => {
29
+ const fm = slide?.meta?.slide?.frontmatter ?? {}
30
+ // Per-slide explicit opt-out
31
+ if (fm.hideTab === true || fm.hideTab === 'true') return false
32
+ // Global filter: skip cover/section layouts unless tabsShowAll is set
33
+ if (!tabsShowAll.value) {
34
+ const layout = (slide?.meta as any)?.layout ?? fm.layout
35
+ if (layout === 'cover' || layout === 'section') return false
36
+ }
37
+ return true
38
+ })
39
+ })
40
+
41
+ function slideFilename(no: number): string {
42
+ const slide = slides.value.find(s => s.no === no)
43
+ return slide?.meta?.slide?.frontmatter?.filename ?? `slide-${no}.md`
44
+ }
45
+
46
+ const tabbarEl = ref<HTMLElement>()
47
+
48
+ watch(currentPage, async () => {
49
+ await nextTick()
50
+ const active = tabbarEl.value?.querySelector<HTMLElement>('.ide-tab.active')
51
+ if (active) active.scrollIntoView({ block: 'nearest', inline: 'nearest', behavior: 'smooth' })
52
+ }, { immediate: true })
53
+ </script>
54
+
55
+ <template>
56
+ <div class="ide-frame">
57
+
58
+ <!-- Title Bar -->
59
+ <div class="ide-titlebar">
60
+ <div class="ide-traffic-lights">
61
+ <span class="traffic-close" />
62
+ <span class="traffic-minimize" />
63
+ <span class="traffic-maximize" />
64
+ </div>
65
+ <div class="ide-titlebar-text">{{ filename }} — Cyberpunk IDE</div>
66
+ </div>
67
+
68
+ <!-- Tab Bar -->
69
+ <div ref="tabbarEl" class="ide-tabbar">
70
+ <div
71
+ v-for="slide in visibleSlides"
72
+ :key="slide.no"
73
+ class="ide-tab"
74
+ :class="{ active: slide.no === currentPage }"
75
+ @click="go(slide.no)"
76
+ >
77
+ <span class="ide-tab-icon">⬡</span>
78
+ <span class="ide-tab-name">{{ slideFilename(slide.no) }}</span>
79
+ <span class="ide-tab-close">×</span>
80
+ </div>
81
+ </div>
82
+
83
+ <!-- Editor -->
84
+ <div class="ide-editor">
85
+ <div class="ide-gutter" />
86
+ <div class="ide-content">
87
+ <slot />
88
+ </div>
89
+ </div>
90
+
91
+ <!-- Status Bar -->
92
+ <div class="ide-statusbar">
93
+ <div class="status-left">
94
+ <span class="status-item">
95
+ <span class="i-ph-github-logo status-icon" />
96
+ {{ repo }}
97
+ </span>
98
+ <span class="status-sep">›</span>
99
+ <span class="status-item">
100
+ <span class="i-ph-git-branch status-icon" />
101
+ {{ branch }}
102
+ </span>
103
+ </div>
104
+ <div class="status-right">
105
+ <span class="status-item">{{ language }}</span>
106
+ <span class="status-item">UTF-8</span>
107
+ <span class="status-item">
108
+ <span class="i-ph-hash status-icon" />
109
+ {{ currentPage }} / {{ total }}
110
+ </span>
111
+ </div>
112
+ </div>
113
+
114
+ </div>
115
+ </template>
@@ -0,0 +1,55 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref } from 'vue'
3
+
4
+ const props = defineProps<{ text: string }>()
5
+
6
+ function escapeHtml(s: string): string {
7
+ return s
8
+ .replace(/&/g, '&amp;')
9
+ .replace(/</g, '&lt;')
10
+ .replace(/>/g, '&gt;')
11
+ .replace(/"/g, '&quot;')
12
+ .replace(/'/g, '&#39;')
13
+ }
14
+
15
+ const rendered = computed(() =>
16
+ escapeHtml(props.text)
17
+ .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
18
+ .replace(/&#96;(.*?)&#96;/g, '<code>$1</code>')
19
+ .replace(/`(.*?)`/g, '<code>$1</code>')
20
+ .replace(/\*(.*?)\*/g, '<em>$1</em>'),
21
+ )
22
+
23
+ const boxRef = ref<HTMLElement>()
24
+
25
+ function adjust() {
26
+ const box = boxRef.value
27
+ if (!box) return
28
+
29
+ // Reset so we measure the natural position
30
+ box.style.left = ''
31
+ box.style.right = ''
32
+ box.style.transform = ''
33
+
34
+ const rect = box.getBoundingClientRect()
35
+
36
+ // Clamp horizontally inside the viewport
37
+ if (rect.right > window.innerWidth - 8) {
38
+ box.style.left = 'auto'
39
+ box.style.right = '0'
40
+ box.style.transform = 'none'
41
+ }
42
+ else if (rect.left < 8) {
43
+ box.style.left = '0'
44
+ box.style.right = 'auto'
45
+ box.style.transform = 'none'
46
+ }
47
+ }
48
+ </script>
49
+
50
+ <template>
51
+ <span class="cp-tooltip-trigger" tabindex="0" @mouseenter="adjust" @focus="adjust">
52
+ <slot />
53
+ <span ref="boxRef" class="cp-tooltip-box" v-html="rendered" />
54
+ </span>
55
+ </template>
@@ -0,0 +1,26 @@
1
+ <template>
2
+ <div class="slidev-layout cover">
3
+ <div class="cover-bracket-tl" />
4
+ <div class="cover-bracket-br" />
5
+
6
+ <!-- Top bar: logo slots (only rendered if used) -->
7
+ <div v-if="$slots.logo || $slots['logo-right']" class="cover-topbar">
8
+ <div class="cover-logo-left">
9
+ <slot name="logo" />
10
+ </div>
11
+ <div class="cover-logo-right">
12
+ <slot name="logo-right" />
13
+ </div>
14
+ </div>
15
+
16
+ <!-- Main content -->
17
+ <div class="cover-inner">
18
+ <slot />
19
+ </div>
20
+
21
+ <!-- Bottom sponsors strip (only rendered if used) -->
22
+ <div v-if="$slots.sponsors" class="cover-sponsors">
23
+ <slot name="sponsors" />
24
+ </div>
25
+ </div>
26
+ </template>
@@ -0,0 +1,12 @@
1
+ <template>
2
+ <div class="slidev-layout default">
3
+ <IdeFrame
4
+ :filename="$frontmatter.filename ?? 'main.py'"
5
+ :language="$frontmatter.language ?? 'Python'"
6
+ :branch="$frontmatter.branch ?? 'main'"
7
+ :repo="$frontmatter.repo ?? 'cyberpunk-ide'"
8
+ >
9
+ <slot />
10
+ </IdeFrame>
11
+ </div>
12
+ </template>
@@ -0,0 +1,7 @@
1
+ <template>
2
+ <div class="slidev-layout intro">
3
+ <div class="my-auto">
4
+ <slot />
5
+ </div>
6
+ </div>
7
+ </template>
@@ -0,0 +1,9 @@
1
+ <template>
2
+ <div class="slidev-layout section">
3
+ <div class="section-inner">
4
+ <div class="section-eyebrow">{{ $frontmatter.section ?? 'Modulo' }}</div>
5
+ <slot />
6
+ <div class="section-bar" />
7
+ </div>
8
+ </div>
9
+ </template>
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "slidev-theme-cyberpunk-ide",
3
+ "version": "0.1.0",
4
+ "description": "A cyberpunk IDE-style Slidev theme for teaching computer science",
5
+ "author": "Marco Farina",
6
+ "license": "Unlicense",
7
+ "type": "module",
8
+ "keywords": [
9
+ "slidev-theme",
10
+ "slidev"
11
+ ],
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/marcofarina/slidev-theme-cyberpunk-ide.git"
15
+ },
16
+ "homepage": "https://github.com/marcofarina/slidev-theme-cyberpunk-ide",
17
+ "files": [
18
+ "components/",
19
+ "layouts/",
20
+ "public/",
21
+ "setup/",
22
+ "styles/",
23
+ "vite.config.ts",
24
+ "README.md",
25
+ "UNLICENSE"
26
+ ],
27
+ "engines": {
28
+ "node": ">=18.0.0"
29
+ },
30
+ "scripts": {
31
+ "build": "slidev build example.md",
32
+ "dev": "slidev example.md --open",
33
+ "export": "slidev export example.md",
34
+ "screenshot": "slidev export example.md --format png"
35
+ },
36
+ "dependencies": {
37
+ "@slidev/types": "^52.14.1"
38
+ },
39
+ "devDependencies": {
40
+ "@slidev/cli": "^52.14.1",
41
+ "@types/markdown-it-container": "^4.0.0",
42
+ "markdown-it-container": "^4.0.0"
43
+ },
44
+ "//": "Learn more: https://sli.dev/guide/write-theme.html",
45
+ "slidev": {
46
+ "colorSchema": "dark",
47
+ "defaults": {
48
+ "lineNumbers": true,
49
+ "fonts": {
50
+ "sans": "Monaspace Neon",
51
+ "mono": "Monaspace Neon",
52
+ "local": [
53
+ "Monaspace Neon",
54
+ "Monaspace Radon"
55
+ ],
56
+ "provider": "none"
57
+ }
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,7 @@
1
+ <a href="https://www.flaticon.com/free-icons/brain" title="brain icons">Brain icons created by Freepik - Flaticon</a>
2
+ <a href="https://www.flaticon.com/free-icons/idea" title="idea icons">Idea icons created by Freepik - Flaticon</a>
3
+ <a href="https://www.flaticon.com/free-icons/clean" title="clean icons">Clean icons created by Freepik - Flaticon</a>
4
+ <a href="https://www.flaticon.com/free-icons/keyboard-key" title="keyboard-key icons">Keyboard-key icons created by
5
+ littleicon - Flaticon</a>
6
+ <a href="https://www.flaticon.com/free-icons/fire" title="fire icons">Fire icons created by pocike - Flaticon</a>
7
+ <a href="https://www.flaticon.com/free-icons/scroll" title="scroll icons">Scroll icons created by Freepik - Flaticon</a>
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file