@sp-days-framework/slidev-theme-sykehuspartner 1.0.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.
@@ -0,0 +1,161 @@
1
+ /* Light theme colors */
2
+ :root {
3
+ --sp-primary: #023f84;
4
+ --sp-success: #00a400;
5
+ --sp-info: #54c7ec;
6
+ --sp-warning: #ffba00;
7
+ --sp-danger: #fa383e;
8
+ --sp-background: #f7faff;
9
+ --sp-shadow: #00000028;
10
+ --sp-code-background: #53535309;
11
+
12
+ /* Primary color variations (light) */
13
+ --sp-primary-dark: #023977;
14
+ --sp-primary-darker: #023670;
15
+ --sp-primary-darkest: #012c5c;
16
+ --sp-primary-light: #024591;
17
+ --sp-primary-lighter: #024898;
18
+ --sp-primary-lightest: #0352ac;
19
+
20
+ --sp-title-line: var(--sp-primary);
21
+ }
22
+
23
+ /* Dark theme colors */
24
+ .dark {
25
+ --sp-primary: #6cace4;
26
+ --sp-success: #88e36b;
27
+ --sp-info: #5dccf0;
28
+ --sp-warning: #ffc93d;
29
+ --sp-danger: #ff6369;
30
+ --sp-background: #0d1117;
31
+ --sp-shadow: #ffffff28;
32
+ --sp-code-background: #01010213;
33
+
34
+ /* Primary color variations inverted (dark) */
35
+ --sp-primary-light: #5ea4e1;
36
+ --sp-primary-lighter: #509cdf;
37
+ --sp-primary-lightest: #338cda;
38
+ --sp-primary-dark: #88bce9;
39
+ --sp-primary-darker: #97c4ec;
40
+ --sp-primary-darkest: #c1dcf4;
41
+ }
42
+
43
+ /* Default styling for all layouts */
44
+ .slidev-layout {
45
+ h1 {
46
+ color: var(--sp-primary-dark);
47
+ font-weight: bolder;
48
+ font-size: 2.75rem;
49
+ }
50
+
51
+ h2 {
52
+ /* color: var(--sp-primary-darker); */
53
+ font-weight: bold;
54
+ font-size: 2rem;
55
+ }
56
+
57
+ h3 {
58
+ /* color: var(--sp-primary-darkest); */
59
+ font-weight: bolder;
60
+ font-size: 1.55rem;
61
+ }
62
+
63
+ background: var(--sp-background);
64
+ padding: 1.5rem 2rem;
65
+ position: relative;
66
+
67
+ :not(pre) > code {
68
+ background: var(--sp-code-background);
69
+ }
70
+
71
+ /* blockquote {
72
+ display: flex;
73
+ align-items: center;
74
+ background: var(--sp-code-background);
75
+ color: var(--slidev-theme-color);
76
+ border-color: #f141a8;
77
+ border-left-width: 3px;
78
+ font-size: var(--slidev-theme-font-size, 1.1em);
79
+ } */
80
+ }
81
+
82
+ .banner-image {
83
+ background-image: url("../public/sp-banner-light.svg");
84
+ background-size: contain;
85
+ background-repeat: no-repeat;
86
+ }
87
+
88
+ .dark .banner-image {
89
+ background-image: url("../public/sp-banner-dark.svg");
90
+ }
91
+
92
+ .logo-image {
93
+ background-image: url("../public/sp-logo-light.svg");
94
+ background-size: contain;
95
+ background-repeat: no-repeat;
96
+ background-position: center;
97
+ }
98
+
99
+ .dark .logo-image {
100
+ background-image: url("../public/sp-logo-dark.svg");
101
+ }
102
+
103
+ /* Global logo container and styling */
104
+ .logo-container {
105
+ position: absolute;
106
+ z-index: 100; /* Using highest z-index from all layouts */
107
+ width: 2.5rem;
108
+ height: 2.5rem;
109
+ }
110
+
111
+ /* Default positioning for logo (will be overridden by specific layouts if needed) */
112
+ .slidev-layout .logo-container:not([class*="custom-position"]) {
113
+ top: 1rem;
114
+ right: 1rem;
115
+ }
116
+
117
+ /* Global logo styling */
118
+ .logo {
119
+ height: 100%;
120
+ width: 100%;
121
+ }
122
+
123
+ /* Multi-column layouts */
124
+ .slidev-layout.two-cols,
125
+ .slidev-layout.two-cols-header,
126
+ .slidev-layout.three-cols,
127
+ .slidev-layout.three-cols-header {
128
+ column-gap: 2rem;
129
+ }
130
+
131
+ .slidev-layout.three-cols .col-left,
132
+ .slidev-layout.three-cols .col-middle,
133
+ .slidev-layout.three-cols .col-right,
134
+ .slidev-layout.three-cols-header .col-left,
135
+ .slidev-layout.three-cols-header .col-middle,
136
+ .slidev-layout.three-cols-header .col-right {
137
+ flex: 1;
138
+ }
139
+
140
+ /* Shared styles for cover and intro layouts */
141
+ /* .slidev-layout.cover,
142
+ .slidev-layout.intro {
143
+ height: 100%;
144
+
145
+ h1 {
146
+ font-size: 3.75rem;
147
+ line-height: 5rem;
148
+ }
149
+
150
+ h1 + p {
151
+ margin-top: -0.5rem;
152
+ opacity: 0.5;
153
+ margin-bottom: 1rem;
154
+ }
155
+
156
+ p + h2,
157
+ ul + h2,
158
+ table + h2 {
159
+ margin-top: 2.5rem;
160
+ }
161
+ } */
package/uno.config.ts ADDED
@@ -0,0 +1,47 @@
1
+ import { defineConfig } from 'unocss'
2
+ import fs from 'fs'
3
+ import { globSync } from 'glob'
4
+
5
+ // Function to scan files for icon patterns
6
+ function scanFilesForIcons() {
7
+ try {
8
+ // Find all markdown files in the project directory and all subdirectories
9
+ const slideFiles = globSync('**/*.md', { absolute: true })
10
+
11
+ // Pattern to match icon references like ~i-vscode-icons:file-type-ansible~
12
+ const iconPattern = /~(i-[a-zA-Z0-9-]+:[a-zA-Z0-9-]+)~/g
13
+
14
+ // Set to store unique icons
15
+ const icons = new Set(['i-vscode-icons:file-type-docker']) // Include default icon
16
+
17
+ // Scan each file for icon patterns
18
+ slideFiles.forEach((file: string) => {
19
+ try {
20
+ const content = fs.readFileSync(file, 'utf-8')
21
+ let match
22
+ while ((match = iconPattern.exec(content)) !== null) {
23
+ if (match[1]) {
24
+ icons.add(match[1])
25
+ }
26
+ }
27
+ } catch (err) {
28
+ console.warn(`Error reading file ${file}:`, err)
29
+ }
30
+ })
31
+
32
+ return Array.from(icons)
33
+ } catch (err) {
34
+ console.warn('Error scanning files for icons:', err)
35
+ return ['i-vscode-icons:file-type-docker'] // Fallback to default icon
36
+ }
37
+ }
38
+
39
+ export default defineConfig({
40
+ safelist: [
41
+ // Static icons that should always be included
42
+ 'i-vscode-icons:file-type-docker',
43
+
44
+ // Dynamically scan for icons in slide files
45
+ () => scanFilesForIcons()
46
+ ],
47
+ })
@@ -0,0 +1,48 @@
1
+ import { ref, Ref, onMounted } from 'vue'
2
+
3
+ /**
4
+ * Utility to split slide content into header and main content sections
5
+ * It extracts the first H1 from content and moves it to a separate header element
6
+ *
7
+ * @param headerRef Ref to the header element where H1 will be moved
8
+ * @param contentRef Ref to the content element containing the original content
9
+ * @returns An object with setup function
10
+ */
11
+ export function useHeaderContentSplit(headerRef: Ref<HTMLElement | null>, contentRef: Ref<HTMLElement | null>) {
12
+ // Function to move H1 to header section
13
+ const moveH1ToHeader = () => {
14
+ if (!headerRef.value || !contentRef.value) return
15
+
16
+ // Find the first H1 in content
17
+ const firstH1 = contentRef.value.querySelector('h1')
18
+ if (firstH1 && headerRef.value.children.length === 0) {
19
+ // Move it to the header
20
+ headerRef.value.appendChild(firstH1)
21
+ }
22
+ }
23
+
24
+ const setupHeaderSplit = () => {
25
+ // Initial move when component is mounted
26
+ moveH1ToHeader()
27
+
28
+ // Also set up a MutationObserver to handle dynamically loaded content
29
+ const observer = new MutationObserver(() => {
30
+ // If header is empty and there's an H1 in content, move it
31
+ if (headerRef.value && headerRef.value.children.length === 0) {
32
+ moveH1ToHeader()
33
+ }
34
+ })
35
+
36
+ if (contentRef.value) {
37
+ observer.observe(contentRef.value, { childList: true, subtree: true })
38
+ }
39
+
40
+ return {
41
+ destroy: () => observer.disconnect()
42
+ }
43
+ }
44
+
45
+ return {
46
+ setupHeaderSplit
47
+ }
48
+ }
@@ -0,0 +1,172 @@
1
+ import type { CSSProperties } from "vue";
2
+
3
+ /**
4
+ * Simple function to resolve image paths
5
+ * Works for URLs, absolute paths, and relative paths
6
+ */
7
+ export function getImageUrl(path?: string): string {
8
+ if (!path) return "";
9
+
10
+ // If it's a color, don't process it
11
+ if (path.startsWith("#") || path.startsWith("rgb")) return path;
12
+
13
+ // If it's a full URL, use it as is
14
+ if (path.match(/^https?:\/\//)) return path;
15
+
16
+ // If it's a root-relative path (starts with /), use it as is
17
+ if (path.startsWith("/")) return path;
18
+
19
+ // Otherwise, treat as relative path and add leading slash
20
+ return `/${path}`;
21
+ }
22
+
23
+ /**
24
+ * Generate background style object for image or color
25
+ * @param imagePath - Path to the image or color string
26
+ * @param scale - Optional scale factor for the image (e.g., '50%', '0.5')
27
+ * @param align - Optional alignment for the image (e.g., 'top', 'bottom', 'left', 'right')
28
+ */
29
+ export function getImageStyle(
30
+ imagePath?: string,
31
+ scale?: string,
32
+ align?: string
33
+ ): CSSProperties {
34
+ if (!imagePath) return {};
35
+
36
+ // If it's a color, set as background color
37
+ if (imagePath.startsWith("#") || imagePath.startsWith("rgb")) {
38
+ return {
39
+ backgroundColor: imagePath,
40
+ };
41
+ }
42
+
43
+ // Parse alignment
44
+ let backgroundPosition = "center";
45
+ if (align) {
46
+ // Handle single-direction alignments
47
+ if (["top", "bottom", "left", "right"].includes(align)) {
48
+ backgroundPosition = align;
49
+ }
50
+ // Handle combined alignments (e.g., 'top left', 'bottom right')
51
+ else if (align.includes(" ")) {
52
+ backgroundPosition = align;
53
+ }
54
+ }
55
+
56
+ // Parse scale and convert to background-size
57
+ let backgroundSize = "contain"; // Default to 'contain' to prevent cropping
58
+ if (scale) {
59
+ // If scale is a percentage string
60
+ if (scale.endsWith("%")) {
61
+ // For 100%, use 'contain' to show the full image without cropping
62
+ if (scale === "100%") {
63
+ backgroundSize = "contain";
64
+ } else {
65
+ backgroundSize = scale;
66
+ }
67
+ }
68
+ // If scale is a decimal number
69
+ else if (!isNaN(Number(scale))) {
70
+ const scaleNum = Number(scale);
71
+ // For scale = 1.0 (100%), use 'contain' to show the full image
72
+ if (scaleNum === 1.0) {
73
+ backgroundSize = "contain";
74
+ } else {
75
+ backgroundSize = `${scaleNum * 100}%`;
76
+ }
77
+ }
78
+ // If scale is 'contain' or 'cover'
79
+ else if (["contain", "cover", "auto"].includes(scale)) {
80
+ backgroundSize = scale;
81
+ }
82
+ }
83
+
84
+ // Return combined style
85
+ return {
86
+ backgroundImage: `url("${getImageUrl(imagePath)}")`,
87
+ backgroundPosition,
88
+ backgroundSize,
89
+ backgroundRepeat: "no-repeat",
90
+ };
91
+ }
92
+
93
+ /**
94
+ * Generate style for a foreground image element (not background)
95
+ * @param scale - Optional scale factor for the image (e.g., '50%', '0.5')
96
+ * @param align - Optional alignment for the image container (e.g., 'center', 'flex-start', 'flex-end')
97
+ */
98
+ export function getForegroundImageStyle(
99
+ scale?: string,
100
+ align?: string
101
+ ): CSSProperties {
102
+ const style: CSSProperties = {
103
+ maxWidth: "100%",
104
+ maxHeight: "100%",
105
+ objectFit: "contain",
106
+ };
107
+
108
+ // Apply scaling if specified
109
+ if (scale) {
110
+ // If scale is a percentage string
111
+ if (scale.endsWith("%")) {
112
+ style.width = scale;
113
+ }
114
+ // If scale is a decimal number
115
+ else if (!isNaN(Number(scale))) {
116
+ const scaleNum = Number(scale);
117
+ style.width = `${scaleNum * 100}%`;
118
+ }
119
+ }
120
+
121
+ return style;
122
+ }
123
+
124
+ /**
125
+ * Generate container style for image alignment
126
+ * @param align - Alignment direction ('center', 'top', 'bottom', 'left', 'right', etc)
127
+ */
128
+ export function getImageContainerStyle(align?: string): CSSProperties {
129
+ const style: CSSProperties = {
130
+ display: "flex",
131
+ justifyContent: "center",
132
+ alignItems: "center",
133
+ height: "100%",
134
+ width: "100%",
135
+ overflow: "hidden",
136
+ };
137
+
138
+ if (align) {
139
+ switch (align) {
140
+ case "top":
141
+ style.alignItems = "flex-start";
142
+ break;
143
+ case "bottom":
144
+ style.alignItems = "flex-end";
145
+ break;
146
+ case "left":
147
+ style.justifyContent = "flex-start";
148
+ break;
149
+ case "right":
150
+ style.justifyContent = "flex-end";
151
+ break;
152
+ case "top-left":
153
+ style.alignItems = "flex-start";
154
+ style.justifyContent = "flex-start";
155
+ break;
156
+ case "top-right":
157
+ style.alignItems = "flex-start";
158
+ style.justifyContent = "flex-end";
159
+ break;
160
+ case "bottom-left":
161
+ style.alignItems = "flex-end";
162
+ style.justifyContent = "flex-start";
163
+ break;
164
+ case "bottom-right":
165
+ style.alignItems = "flex-end";
166
+ style.justifyContent = "flex-end";
167
+ break;
168
+ }
169
+ }
170
+
171
+ return style;
172
+ }