@troshab/slidev-theme-troshab 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 (168) hide show
  1. package/CLAUDE.md +537 -0
  2. package/LICENSE +134 -0
  3. package/README.md +168 -0
  4. package/SKILL.md +414 -0
  5. package/components/AnimatedCounter.vue +35 -0
  6. package/components/Background.vue +204 -0
  7. package/components/Callout.vue +135 -0
  8. package/components/Card.vue +75 -0
  9. package/components/CardGrid.vue +67 -0
  10. package/components/CaseStudy.vue +66 -0
  11. package/components/CodeDiff.vue +229 -0
  12. package/components/CodeHighlight.vue +337 -0
  13. package/components/ColorSwatch.vue +114 -0
  14. package/components/Confetti.vue +292 -0
  15. package/components/Conversation.vue +405 -0
  16. package/components/Countdown.vue +476 -0
  17. package/components/Definition.vue +59 -0
  18. package/components/DeviceMockup.vue +392 -0
  19. package/components/Funnel.vue +87 -0
  20. package/components/Icon.vue +73 -0
  21. package/components/Iframe.vue +38 -0
  22. package/components/Image.vue +69 -0
  23. package/components/ImageCompare.vue +436 -0
  24. package/components/MatrixGrid.vue +85 -0
  25. package/components/MermaidChart.vue +299 -0
  26. package/components/Metric.vue +161 -0
  27. package/components/PersonCard.vue +165 -0
  28. package/components/PricingTable.vue +144 -0
  29. package/components/Progress.vue +100 -0
  30. package/components/Pyramid.vue +81 -0
  31. package/components/QRCode.vue +137 -0
  32. package/components/QuoteBlock.vue +101 -0
  33. package/components/SpeechBubble.vue +169 -0
  34. package/components/Stepper.vue +542 -0
  35. package/components/StyledList.vue +156 -0
  36. package/components/StyledText.vue +275 -0
  37. package/components/SwotGrid.vue +99 -0
  38. package/components/Tags.vue +20 -0
  39. package/components/Testimonial.vue +243 -0
  40. package/components/Typewriter.vue +181 -0
  41. package/components_base/AnimatedCounter.vue +208 -0
  42. package/components_base/CodeHighlight.vue +364 -0
  43. package/composables/useColors.ts +101 -0
  44. package/composables/useShiki.ts +81 -0
  45. package/example_content.md +371 -0
  46. package/example_dark.md +10 -0
  47. package/example_slides/001-cover.md +15 -0
  48. package/example_slides/002-agenda.md +25 -0
  49. package/example_slides/003-section-layouts.md +14 -0
  50. package/example_slides/004-fullscreen-centered.md +7 -0
  51. package/example_slides/005-fullscreen-align-bottom.md +14 -0
  52. package/example_slides/006-fullscreen-no-padding.md +14 -0
  53. package/example_slides/007-fullscreen-bg-image-dark.md +13 -0
  54. package/example_slides/008-fullscreen-bg-image-light.md +13 -0
  55. package/example_slides/009-fullscreen-bg-gradient.md +15 -0
  56. package/example_slides/010-fullscreen-bg-color.md +13 -0
  57. package/example_slides/011-split-basic.md +17 -0
  58. package/example_slides/012-split-image-text.md +18 -0
  59. package/example_slides/013-split-contrast.md +22 -0
  60. package/example_slides/014-columns-basic.md +13 -0
  61. package/example_slides/015-columns-two.md +26 -0
  62. package/example_slides/016-columns-ratios.md +22 -0
  63. package/example_slides/017-columns-three.md +31 -0
  64. package/example_slides/018-columns-four.md +22 -0
  65. package/example_slides/019-columns-alignment.md +23 -0
  66. package/example_slides/020-columns-styled.md +21 -0
  67. package/example_slides/021-footnote-prop.md +16 -0
  68. package/example_slides/022-iframe-fullscreen.md +8 -0
  69. package/example_slides/023-iframe-split.md +18 -0
  70. package/example_slides/024-section-components.md +14 -0
  71. package/example_slides/025-styled-text.md +9 -0
  72. package/example_slides/026-styled-text.md +15 -0
  73. package/example_slides/027-text-formatting.md +28 -0
  74. package/example_slides/028-text-spoiler.md +15 -0
  75. package/example_slides/029-icon-component.md +47 -0
  76. package/example_slides/030-metric-component.md +29 -0
  77. package/example_slides/031-person-card.md +33 -0
  78. package/example_slides/032-styled-list.md +50 -0
  79. package/example_slides/033-color-swatch.md +35 -0
  80. package/example_slides/034-code-highlight.md +9 -0
  81. package/example_slides/035-iframe-component.md +9 -0
  82. package/example_slides/036-callout.md +15 -0
  83. package/example_slides/037-card-grid.md +27 -0
  84. package/example_slides/038-stepper-variants.md +18 -0
  85. package/example_slides/039-stepper-clicks.md +49 -0
  86. package/example_slides/040-stepper-interactive.md +28 -0
  87. package/example_slides/041-tags-progress.md +21 -0
  88. package/example_slides/042-speech-bubble.md +30 -0
  89. package/example_slides/043-conversation.md +13 -0
  90. package/example_slides/044-device-iphone.md +26 -0
  91. package/example_slides/045-device-browser.md +7 -0
  92. package/example_slides/046-qrcode.md +26 -0
  93. package/example_slides/047-countdown.md +14 -0
  94. package/example_slides/048-typewriter.md +8 -0
  95. package/example_slides/049-confetti.md +16 -0
  96. package/example_slides/050-image-compare.md +13 -0
  97. package/example_slides/051-code-diff.md +24 -0
  98. package/example_slides/052-quote-block.md +8 -0
  99. package/example_slides/053-testimonial.md +26 -0
  100. package/example_slides/054-testimonial-featured.md +16 -0
  101. package/example_slides/055-funnel.md +12 -0
  102. package/example_slides/056-pyramid.md +13 -0
  103. package/example_slides/057-pricing-table.md +9 -0
  104. package/example_slides/058-swot-grid.md +12 -0
  105. package/example_slides/059-matrix-grid.md +12 -0
  106. package/example_slides/060-case-study.md +11 -0
  107. package/example_slides/061-definition.md +15 -0
  108. package/example_slides/062-mermaid-intro.md +34 -0
  109. package/example_slides/063-mermaid-flowchart.md +19 -0
  110. package/example_slides/064-mermaid-sequence.md +17 -0
  111. package/example_slides/065-mermaid-xy-chart.md +16 -0
  112. package/example_slides/066-mermaid-pie.md +17 -0
  113. package/example_slides/067-mermaid-class.md +19 -0
  114. package/example_slides/068-mermaid-state.md +19 -0
  115. package/example_slides/069-mermaid-er.md +22 -0
  116. package/example_slides/070-mermaid-gantt.md +24 -0
  117. package/example_slides/071-mermaid-timeline.md +17 -0
  118. package/example_slides/072-mermaid-mindmap.md +21 -0
  119. package/example_slides/073-mermaid-gitgraph.md +20 -0
  120. package/example_slides/074-mermaid-split.md +30 -0
  121. package/example_slides/075-mermaid-columns.md +32 -0
  122. package/example_slides/076-section-addons.md +14 -0
  123. package/example_slides/077-asciinema.md +27 -0
  124. package/example_slides/078-fancyarrow.md +31 -0
  125. package/example_slides/079-fancyarrow-demo.md +23 -0
  126. package/example_slides/080-section-theme.md +14 -0
  127. package/example_slides/081-color-architecture.md +22 -0
  128. package/example_slides/082-semantic-text-colors.md +25 -0
  129. package/example_slides/083-typography.md +16 -0
  130. package/example_slides/084-typography-rationale.md +22 -0
  131. package/example_slides/085-icons.md +24 -0
  132. package/example_slides/086-tables.md +14 -0
  133. package/example_slides/087-code-blocks.md +18 -0
  134. package/example_slides/088-motion-modes.md +35 -0
  135. package/example_slides/089-slide-transitions.md +31 -0
  136. package/example_slides/090-v-click-reveals.md +40 -0
  137. package/example_slides/091-accessibility.md +27 -0
  138. package/example_slides/092-safe-zone.md +17 -0
  139. package/example_slides/093-questions.md +8 -0
  140. package/example_white.md +10 -0
  141. package/fonts/IBMPlexMono-Medium.woff2 +1449 -0
  142. package/fonts/IBMPlexMono-Regular.woff2 +1449 -0
  143. package/fonts/IBMPlexSans-Bold.woff2 +1449 -0
  144. package/fonts/IBMPlexSans-Medium.woff2 +1449 -0
  145. package/fonts/IBMPlexSans-Regular.woff2 +1449 -0
  146. package/fonts/IBMPlexSans-SemiBold.woff2 +1449 -0
  147. package/fonts/LICENSE.txt +93 -0
  148. package/layouts/slide.vue +251 -0
  149. package/package.json +62 -0
  150. package/public/avatars/alice.png +0 -0
  151. package/public/avatars/bob.png +0 -0
  152. package/public/avatars/carol.png +0 -0
  153. package/scripts/chart-audit.mjs +216 -0
  154. package/scripts/contrast-audit.mjs +299 -0
  155. package/scripts/generate-palette.mjs +395 -0
  156. package/scripts/integrity-audit.mjs +357 -0
  157. package/scripts/shared/css-utils.mjs +216 -0
  158. package/scripts/shiki-audit.mjs +300 -0
  159. package/scripts/typography-audit.mjs +300 -0
  160. package/setup/main.ts +107 -0
  161. package/setup/mermaid.ts +237 -0
  162. package/setup/shiki.ts +40 -0
  163. package/snippets/demo.ts +26 -0
  164. package/styles/base.css +1053 -0
  165. package/styles/colors.css +422 -0
  166. package/styles/index.css +12 -0
  167. package/styles/motion.css +486 -0
  168. package/uno.config.ts +67 -0
@@ -0,0 +1,156 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * StyledList primitive — themed list markers, columns, and formatting.
4
+ *
5
+ * Wraps markdown list content and applies styled markers.
6
+ *
7
+ * <StyledList marker="check" color="success">
8
+ *
9
+ * - Item one
10
+ * - Item two
11
+ *
12
+ * </StyledList>
13
+ */
14
+
15
+ import type { SemanticColor } from '../composables/useColors'
16
+ import { semanticColorVar } from '../composables/useColors'
17
+
18
+ const props = withDefaults(defineProps<{
19
+ marker?: 'disc' | 'number' | 'check' | 'arrow' | 'star' | 'none'
20
+ color?: SemanticColor
21
+ size?: 'sm' | 'md' | 'lg'
22
+ columns?: 1 | 2
23
+ indent?: 'normal' | 'hanging'
24
+ align?: 'left' | 'center'
25
+ }>(), {
26
+ marker: 'disc',
27
+ color: 'primary',
28
+ size: 'md',
29
+ columns: 1,
30
+ indent: 'normal',
31
+ align: 'left',
32
+ })
33
+ </script>
34
+
35
+ <template>
36
+ <div
37
+ class="styled-list"
38
+ :class="[
39
+ `styled-list-${marker}`,
40
+ `styled-list-${size}`,
41
+ { 'styled-list-cols-2': columns === 2 },
42
+ { 'styled-list-hanging': indent === 'hanging' },
43
+ { 'styled-list-center': align === 'center' },
44
+ ]"
45
+ :style="{ '--marker-color': semanticColorVar[color] }"
46
+ >
47
+ <slot />
48
+ </div>
49
+ </template>
50
+
51
+ <style>
52
+ .styled-list ul,
53
+ .styled-list ol {
54
+ list-style: none;
55
+ margin-inline-start: 0;
56
+ max-width: none;
57
+ }
58
+
59
+ .styled-list li {
60
+ list-style: none;
61
+ padding-left: 2em;
62
+ position: relative;
63
+ margin-block: 0.5em;
64
+ }
65
+
66
+ /* Size variants */
67
+ .styled-list-sm li { font-size: var(--font-size-small); }
68
+ .styled-list-md li { font-size: var(--font-size-base); }
69
+ .styled-list-lg li { font-size: calc(var(--font-size-base) * 1.15); }
70
+
71
+ /* Marker: disc (default) */
72
+ .styled-list-disc ul { margin-inline-start: 1.5em; }
73
+ .styled-list-disc li { list-style: disc; padding-left: 0; }
74
+
75
+ /* Marker: check */
76
+ .styled-list-check li::before {
77
+ content: '\2713';
78
+ position: absolute;
79
+ left: 0;
80
+ color: var(--marker-color);
81
+ font-weight: var(--font-weight-bold);
82
+ }
83
+
84
+ /* Marker: arrow */
85
+ .styled-list-arrow li::before {
86
+ content: '\2192';
87
+ position: absolute;
88
+ left: 0;
89
+ color: var(--marker-color);
90
+ font-weight: var(--font-weight-bold);
91
+ }
92
+
93
+ /* Marker: star */
94
+ .styled-list-star li::before {
95
+ content: '\2605';
96
+ position: absolute;
97
+ left: 0;
98
+ color: var(--marker-color);
99
+ font-weight: var(--font-weight-bold);
100
+ }
101
+
102
+ /* Marker: number */
103
+ .styled-list-number ul,
104
+ .styled-list-number ol {
105
+ counter-reset: styled-list;
106
+ }
107
+
108
+ .styled-list-number li {
109
+ counter-increment: styled-list;
110
+ }
111
+
112
+ .styled-list-number li::before {
113
+ content: counter(styled-list) '.';
114
+ position: absolute;
115
+ left: 0;
116
+ color: var(--marker-color);
117
+ font-weight: var(--font-weight-bold);
118
+ font-size: var(--font-size-base);
119
+ width: 1.5em;
120
+ text-align: center;
121
+ }
122
+
123
+ /* Marker: none */
124
+ .styled-list-none li { padding-left: 0; }
125
+ .styled-list-none li::before { display: none; }
126
+
127
+ /* Columns */
128
+ .styled-list-cols-2 ul,
129
+ .styled-list-cols-2 ol {
130
+ columns: 2;
131
+ column-gap: var(--space-xl);
132
+ }
133
+
134
+ .styled-list-cols-2 li {
135
+ break-inside: avoid;
136
+ }
137
+
138
+ /* Hanging indent (for references) */
139
+ .styled-list-hanging li {
140
+ padding-left: 2em;
141
+ text-indent: -2em;
142
+ line-height: var(--line-height-reading);
143
+ }
144
+
145
+ .styled-list-hanging li::before { display: none; }
146
+
147
+ /* Center alignment */
148
+ .styled-list-center ul,
149
+ .styled-list-center ol {
150
+ text-align: center;
151
+ }
152
+
153
+ .styled-list-center li {
154
+ margin-block: 0.75em;
155
+ }
156
+ </style>
@@ -0,0 +1,275 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * StyledText — universal themed text component.
4
+ *
5
+ * Replaces markdown headings/text when you need color, effects, or formatting.
6
+ * Use plain markdown (#, ##, ###, paragraphs) when no styling is needed.
7
+ *
8
+ * Heading (renders <h1>/<h2>/<h3>):
9
+ * <StyledText tag="h1" color="primary">Colored Title</StyledText>
10
+ * <StyledText tag="h2" align="center">Centered H2</StyledText>
11
+ *
12
+ * Block (default, renders <p>):
13
+ * <StyledText color="secondary">Subtitle text</StyledText>
14
+ * <StyledText size="small" color="tertiary">Caption</StyledText>
15
+ *
16
+ * Inline (renders <span>):
17
+ * <StyledText inline color="primary">colored word</StyledText>
18
+ * <StyledText inline highlight="warning">highlighted</StyledText>
19
+ * <StyledText inline kbd>Ctrl+S</StyledText>
20
+ * <StyledText inline gradient>fancy text</StyledText>
21
+ * <StyledText inline bold color="danger">bold danger</StyledText>
22
+ *
23
+ * Spoiler (auto-integrates with Slidev v-click):
24
+ * <StyledText inline spoiler>hidden answer</StyledText>
25
+ * Automatically registers as next v-click. Reveals on slide advance,
26
+ * re-blurs on backward navigation. Falls back to manual click outside Slidev.
27
+ */
28
+
29
+ import { ref, computed, inject, onMounted, onUnmounted, watchEffect } from 'vue'
30
+ import type { Ref } from 'vue'
31
+ import type { TextColor } from '../composables/useColors'
32
+ import { textColorVar, semanticColorVar, gradientVar } from '../composables/useColors'
33
+
34
+ type HighlightColor = 'primary' | 'success' | 'warning' | 'danger' | 'info'
35
+ type TagName = 'h1' | 'h2' | 'h3' | 'p' | 'span'
36
+
37
+ const props = defineProps<{
38
+ // HTML tag (h1/h2/h3/p/span). Default: p. `inline` is shorthand for span.
39
+ tag?: TagName
40
+ inline?: boolean
41
+ // Block-only props
42
+ align?: 'left' | 'center' | 'right'
43
+ // Shared props
44
+ color?: TextColor
45
+ size?: 'small' | 'base' | 'large'
46
+ // Inline formatting props
47
+ bold?: boolean
48
+ italic?: boolean
49
+ underline?: boolean
50
+ strike?: boolean
51
+ uppercase?: boolean
52
+ mono?: boolean
53
+ gradient?: boolean
54
+ kbd?: boolean
55
+ highlight?: HighlightColor
56
+ spoiler?: boolean
57
+ }>()
58
+
59
+ const resolvedTag = computed<TagName>(() => {
60
+ if (props.tag) return props.tag
61
+ return props.inline ? 'span' : 'p'
62
+ })
63
+
64
+ const isInline = computed(() => resolvedTag.value === 'span')
65
+ const isHeading = computed(() => ['h1', 'h2', 'h3'].includes(resolvedTag.value))
66
+
67
+ // --- Spoiler: auto v-click integration ---
68
+ const rootEl = ref<HTMLElement | null>(null)
69
+ const clicksContext = inject<Ref<any>>('$$slidev-clicks-context', ref(null))
70
+ const revealed = ref(false)
71
+ const hasSlidevClicks = computed(() => !!clicksContext.value?.calculate)
72
+
73
+ if (props.spoiler) {
74
+ onMounted(() => {
75
+ const ctx = clicksContext.value
76
+ if (ctx?.calculate && rootEl.value) {
77
+ const info = ctx.calculate('+1')
78
+ if (info) {
79
+ ctx.register(rootEl.value, info)
80
+ // Track click state reactively — info.isActive is a computed ref
81
+ watchEffect(() => {
82
+ revealed.value = info.isActive.value
83
+ })
84
+ }
85
+ }
86
+ })
87
+
88
+ onUnmounted(() => {
89
+ if (clicksContext.value?.unregister && rootEl.value) {
90
+ clicksContext.value.unregister(rootEl.value)
91
+ }
92
+ })
93
+ }
94
+
95
+ function handleSpoilerClick(e: Event) {
96
+ if (!props.spoiler) return
97
+ if (hasSlidevClicks.value) return
98
+ e.stopPropagation()
99
+ revealed.value = !revealed.value
100
+ }
101
+
102
+ function handleSpoilerKey(e: KeyboardEvent) {
103
+ if (!props.spoiler || hasSlidevClicks.value) return
104
+ if (e.key === ' ') e.preventDefault()
105
+ revealed.value = !revealed.value
106
+ }
107
+
108
+ // --- CSS variables ---
109
+ const cssVars = computed(() => {
110
+ const vars: Record<string, string> = {}
111
+ if (props.color && props.color !== 'inherit' && !props.gradient)
112
+ vars['--text-color'] = textColorVar[props.color]
113
+ if (props.gradient && props.color && props.color !== 'inherit')
114
+ vars['--text-gradient'] = gradientVar[props.color as keyof typeof gradientVar] || gradientVar.primary
115
+ if (props.highlight)
116
+ vars['--text-highlight-bg'] = semanticColorVar[props.highlight]
117
+ return vars
118
+ })
119
+
120
+ const sizeMap: Record<string, string> = {
121
+ base: 'var(--font-size-base)',
122
+ small: 'var(--font-size-small)',
123
+ large: 'var(--font-size-h2)',
124
+ }
125
+ </script>
126
+
127
+ <template>
128
+ <component
129
+ ref="rootEl"
130
+ :is="resolvedTag"
131
+ :class="[
132
+ isInline ? 'styled-text-inline' : 'styled-text-block',
133
+ {
134
+ 'is-bold': bold,
135
+ 'is-italic': italic,
136
+ 'is-underline': underline,
137
+ 'is-strike': strike,
138
+ 'is-spoiler': spoiler && !revealed,
139
+ 'is-spoiler-revealed': spoiler && revealed,
140
+ 'is-mono': mono,
141
+ 'is-uppercase': uppercase,
142
+ 'is-gradient': gradient,
143
+ 'is-highlight': highlight,
144
+ 'is-kbd': kbd,
145
+ 'is-size-small': size === 'small',
146
+ 'is-size-large': size === 'large',
147
+ },
148
+ ]"
149
+ :style="{
150
+ ...cssVars,
151
+ ...(isInline ? {} : {
152
+ textAlign: align || undefined,
153
+ ...(isHeading ? {} : { fontSize: sizeMap[size || 'base'] }),
154
+ }),
155
+ }"
156
+ :role="spoiler && !hasSlidevClicks ? 'button' : undefined"
157
+ :tabindex="spoiler && !hasSlidevClicks ? 0 : undefined"
158
+ :aria-label="spoiler && !revealed ? 'Spoiler — click to reveal' : undefined"
159
+ @click="handleSpoilerClick"
160
+ @keydown.enter="handleSpoilerKey"
161
+ @keydown.space="handleSpoilerKey"
162
+ >
163
+ <slot />
164
+ </component>
165
+ </template>
166
+
167
+ <style scoped>
168
+ /* === Block mode (default) === */
169
+ .styled-text-block {
170
+ color: var(--text-color, inherit);
171
+ }
172
+
173
+ /* === Inline mode === */
174
+ .styled-text-inline {
175
+ color: var(--text-color, inherit);
176
+ }
177
+
178
+ /* --- Formatting --- */
179
+ .is-bold { font-weight: 700; }
180
+ .is-italic { font-style: italic; }
181
+ .is-underline { text-decoration: underline; text-underline-offset: 0.15em; }
182
+ .is-strike { text-decoration: line-through; }
183
+
184
+ .is-underline.is-strike {
185
+ text-decoration: underline line-through;
186
+ text-underline-offset: 0.15em;
187
+ }
188
+
189
+ /* --- Sizes (inline mode) --- */
190
+ .styled-text-inline.is-size-small { font-size: var(--font-size-small); }
191
+ .styled-text-inline.is-size-large { font-size: var(--font-size-h2); }
192
+
193
+ /* --- Spoiler --- */
194
+ .is-spoiler {
195
+ filter: blur(0.45em);
196
+ cursor: pointer;
197
+ transition: filter var(--duration-moderate, 240ms) var(--ease-standard, ease);
198
+ user-select: none;
199
+ border-radius: 0.15em;
200
+ background: var(--color-bg-muted);
201
+ padding: 0.1em 0.25em;
202
+ margin: 0 0.1em;
203
+ }
204
+
205
+ .is-spoiler:hover {
206
+ filter: blur(0.3em);
207
+ }
208
+
209
+ .is-spoiler:focus-visible {
210
+ outline: 3px solid var(--color-primary);
211
+ outline-offset: 2px;
212
+ }
213
+
214
+ .is-spoiler-revealed {
215
+ cursor: default;
216
+ user-select: auto;
217
+ border-radius: 0.15em;
218
+ background: var(--color-bg-muted);
219
+ padding: 0.1em 0.25em;
220
+ margin: 0 0.1em;
221
+ animation: spoiler-reveal var(--duration-moderate, 240ms) var(--ease-standard, ease);
222
+ }
223
+
224
+ @keyframes spoiler-reveal {
225
+ from { filter: blur(0.45em); }
226
+ to { filter: blur(0); }
227
+ }
228
+
229
+ /* --- Monospace --- */
230
+ .is-mono {
231
+ font-family: 'IBM Plex Mono', monospace;
232
+ font-size: 0.9em;
233
+ background: var(--color-bg-muted);
234
+ padding: 0.1em 0.35em;
235
+ border-radius: 0.2em;
236
+ margin: 0 0.1em;
237
+ }
238
+
239
+ /* --- Uppercase --- */
240
+ .is-uppercase {
241
+ text-transform: uppercase;
242
+ letter-spacing: 0.05em;
243
+ }
244
+
245
+ /* --- Gradient text --- */
246
+ .is-gradient {
247
+ background: var(--text-gradient, var(--gradient-primary));
248
+ -webkit-background-clip: text;
249
+ -webkit-text-fill-color: transparent;
250
+ background-clip: text;
251
+ }
252
+
253
+ /* --- Highlight --- */
254
+ .is-highlight {
255
+ background-color: color-mix(in srgb, var(--text-highlight-bg) 20%, transparent);
256
+ padding: 0.1em 0.25em;
257
+ border-radius: 0.2em;
258
+ border-bottom: 2px solid var(--text-highlight-bg);
259
+ margin: 0 0.1em;
260
+ }
261
+
262
+ /* --- Keyboard key --- */
263
+ .is-kbd {
264
+ font-family: 'IBM Plex Mono', monospace;
265
+ font-size: 0.85em;
266
+ background: var(--color-bg-soft);
267
+ border: 1px solid var(--color-border);
268
+ border-bottom-width: 2px;
269
+ padding: 0.1em 0.4em;
270
+ border-radius: 0.25em;
271
+ box-shadow: 0 1px 0 var(--color-border);
272
+ white-space: nowrap;
273
+ margin: 0 0.1em;
274
+ }
275
+ </style>
@@ -0,0 +1,99 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * SwotGrid widget — SWOT analysis built on top of MatrixGrid.
4
+ *
5
+ * <SwotGrid
6
+ * :strengths="['Strong brand', 'Expert team']"
7
+ * :weaknesses="['Limited reach', 'High cost']"
8
+ * :opportunities="['New markets', 'Partnerships']"
9
+ * :threats="['Competition', 'Regulation']"
10
+ * />
11
+ */
12
+ import { computed } from 'vue'
13
+
14
+ const props = withDefaults(defineProps<{
15
+ strengths: string[]
16
+ weaknesses: string[]
17
+ opportunities: string[]
18
+ threats: string[]
19
+ showAxes?: boolean
20
+ }>(), {
21
+ showAxes: true,
22
+ })
23
+
24
+ const cells = computed(() => [
25
+ { title: 'Strengths', items: props.strengths, color: 'success' as const },
26
+ { title: 'Weaknesses', items: props.weaknesses, color: 'danger' as const },
27
+ { title: 'Opportunities', items: props.opportunities, color: 'primary' as const },
28
+ { title: 'Threats', items: props.threats, color: 'warning' as const },
29
+ ])
30
+ </script>
31
+
32
+ <template>
33
+ <div class="swot-wrapper" :class="{ 'swot-with-axes': showAxes }">
34
+ <div v-if="showAxes" class="swot-axis-top">
35
+ <span class="swot-axis-label">Helpful</span>
36
+ <span class="swot-axis-label">Harmful</span>
37
+ </div>
38
+ <div class="swot-body">
39
+ <div v-if="showAxes" class="swot-axis-left">
40
+ <span class="swot-axis-label-vertical">Internal</span>
41
+ <span class="swot-axis-label-vertical">External</span>
42
+ </div>
43
+ <MatrixGrid :cells="cells" />
44
+ </div>
45
+ </div>
46
+ </template>
47
+
48
+ <style scoped>
49
+ .swot-wrapper {
50
+ width: 100%;
51
+ }
52
+
53
+ .swot-axis-top {
54
+ display: grid;
55
+ grid-template-columns: 1fr 1fr;
56
+ text-align: center;
57
+ padding-bottom: var(--space-xs);
58
+ }
59
+
60
+ .swot-with-axes .swot-axis-top {
61
+ margin-left: 2rem;
62
+ }
63
+
64
+ .swot-axis-label {
65
+ font-size: var(--font-size-small);
66
+ font-weight: var(--font-weight-semibold);
67
+ color: var(--color-text-secondary);
68
+ text-transform: uppercase;
69
+ letter-spacing: 0.05em;
70
+ }
71
+
72
+ .swot-body {
73
+ display: flex;
74
+ }
75
+
76
+ .swot-axis-left {
77
+ display: flex;
78
+ flex-direction: column;
79
+ justify-content: space-around;
80
+ width: 2rem;
81
+ flex-shrink: 0;
82
+ }
83
+
84
+ .swot-axis-label-vertical {
85
+ font-size: var(--font-size-small);
86
+ font-weight: var(--font-weight-semibold);
87
+ color: var(--color-text-secondary);
88
+ text-transform: uppercase;
89
+ letter-spacing: 0.05em;
90
+ writing-mode: vertical-lr;
91
+ transform: rotate(180deg);
92
+ text-align: center;
93
+ }
94
+
95
+ .swot-body :deep(.matrix-grid) {
96
+ flex: 1;
97
+ margin-top: 0;
98
+ }
99
+ </style>
@@ -0,0 +1,20 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Tags component - теги/мітки
4
+ *
5
+ * Використання:
6
+ * <Tags :items="['Vue', 'TypeScript', 'Slidev']" />
7
+ */
8
+
9
+ defineProps<{
10
+ items: string[]
11
+ }>()
12
+ </script>
13
+
14
+ <template>
15
+ <div class="tags-container">
16
+ <span v-for="item in items" :key="item" class="tag">
17
+ {{ item }}
18
+ </span>
19
+ </div>
20
+ </template>