@kntic/links 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.
@@ -0,0 +1,215 @@
1
+ /* glass — glassmorphism theme for KNTIC Links
2
+ *
3
+ * Frosted-glass cards on a dark gradient. Soft purple/blue accent.
4
+ * Subtle glow on hover.
5
+ */
6
+
7
+ *,
8
+ *::before,
9
+ *::after {
10
+ box-sizing: border-box;
11
+ margin: 0;
12
+ padding: 0;
13
+ }
14
+
15
+ :root {
16
+ /* Backgrounds */
17
+ --bg-color: #0a0a1a;
18
+ --bg-secondary: rgba(255, 255, 255, 0.06);
19
+
20
+ /* Text */
21
+ --text-primary: #f0eef6;
22
+ --text-secondary: #c4bfda;
23
+ --text-muted: #8a839e;
24
+
25
+ /* Accent — soft periwinkle */
26
+ --accent-color: #a78bfa;
27
+ --accent-hover: #c4b5fd;
28
+
29
+ /* Link cards */
30
+ --link-bg: var(--bg-secondary);
31
+ --link-bg-hover: rgba(255, 255, 255, 0.1);
32
+ --link-border: rgba(255, 255, 255, 0.1);
33
+ --link-radius: 14px;
34
+ --link-padding: 0.875rem 1.25rem;
35
+
36
+ /* Typography */
37
+ --font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
38
+ --font-mono: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
39
+
40
+ /* Avatar */
41
+ --avatar-radius: 50%;
42
+
43
+ /* Layout */
44
+ --page-max-width: 480px;
45
+
46
+ /* Footer */
47
+ --footer-opacity: 0.45;
48
+ }
49
+
50
+ /* ── Base ────────────────────────────────────────────────────────── */
51
+
52
+ html {
53
+ font-size: 16px;
54
+ -webkit-text-size-adjust: 100%;
55
+ }
56
+
57
+ body {
58
+ font-family: var(--font-body);
59
+ background: linear-gradient(160deg, #0a0a1a 0%, #1a0a2e 40%, #0d1033 70%, #0a0a1a 100%);
60
+ background-attachment: fixed;
61
+ color: var(--text-primary);
62
+ min-height: 100vh;
63
+ display: flex;
64
+ flex-direction: column;
65
+ align-items: center;
66
+ padding: 3rem 1rem 1rem;
67
+ line-height: 1.5;
68
+ position: relative;
69
+ }
70
+
71
+ /* Ambient glow orbs */
72
+ body::before,
73
+ body::after {
74
+ content: '';
75
+ position: fixed;
76
+ border-radius: 50%;
77
+ pointer-events: none;
78
+ filter: blur(80px);
79
+ opacity: 0.3;
80
+ }
81
+
82
+ body::before {
83
+ width: 400px;
84
+ height: 400px;
85
+ background: #7c3aed;
86
+ top: -100px;
87
+ right: -100px;
88
+ }
89
+
90
+ body::after {
91
+ width: 350px;
92
+ height: 350px;
93
+ background: #2563eb;
94
+ bottom: -80px;
95
+ left: -80px;
96
+ }
97
+
98
+ /* ── Profile ─────────────────────────────────────────────────────── */
99
+
100
+ .profile {
101
+ text-align: center;
102
+ margin-bottom: 2rem;
103
+ max-width: var(--page-max-width);
104
+ width: 100%;
105
+ position: relative;
106
+ z-index: 1;
107
+ }
108
+
109
+ .profile__avatar {
110
+ width: 92px;
111
+ height: 92px;
112
+ border-radius: var(--avatar-radius);
113
+ object-fit: cover;
114
+ margin-bottom: 0.75rem;
115
+ border: 2px solid rgba(255, 255, 255, 0.15);
116
+ box-shadow: 0 4px 20px rgba(124, 58, 237, 0.2);
117
+ }
118
+
119
+ .profile__name {
120
+ font-size: 1.35rem;
121
+ font-weight: 600;
122
+ margin-bottom: 0.3rem;
123
+ color: var(--text-primary);
124
+ letter-spacing: -0.01em;
125
+ }
126
+
127
+ .profile__bio {
128
+ font-size: 0.9rem;
129
+ color: var(--text-muted);
130
+ max-width: 320px;
131
+ margin: 0 auto;
132
+ }
133
+
134
+ /* ── Links ───────────────────────────────────────────────────────── */
135
+
136
+ .links {
137
+ list-style: none;
138
+ width: 100%;
139
+ max-width: var(--page-max-width);
140
+ display: flex;
141
+ flex-direction: column;
142
+ gap: 0.625rem;
143
+ position: relative;
144
+ z-index: 1;
145
+ }
146
+
147
+ .links__item {
148
+ display: block;
149
+ width: 100%;
150
+ }
151
+
152
+ .links__anchor {
153
+ display: block;
154
+ width: 100%;
155
+ padding: var(--link-padding);
156
+ background: var(--link-bg);
157
+ -webkit-backdrop-filter: blur(16px) saturate(140%);
158
+ backdrop-filter: blur(16px) saturate(140%);
159
+ border: 1px solid var(--link-border);
160
+ border-radius: var(--link-radius);
161
+ color: var(--accent-color);
162
+ text-decoration: none;
163
+ text-align: center;
164
+ transition: background 0.25s ease, border-color 0.25s ease,
165
+ box-shadow 0.25s ease, color 0.25s ease;
166
+ }
167
+
168
+ .links__anchor:hover,
169
+ .links__anchor:focus {
170
+ background: var(--link-bg-hover);
171
+ color: var(--accent-hover);
172
+ border-color: rgba(167, 139, 250, 0.3);
173
+ box-shadow: 0 4px 24px rgba(124, 58, 237, 0.15),
174
+ 0 0 0 1px rgba(167, 139, 250, 0.1);
175
+ outline: none;
176
+ }
177
+
178
+ .links__label {
179
+ font-size: 0.95rem;
180
+ font-weight: 500;
181
+ }
182
+
183
+ .links__icon {
184
+ margin-right: 0.4em;
185
+ }
186
+
187
+ .links__description {
188
+ display: block;
189
+ font-size: 0.8rem;
190
+ color: var(--text-secondary);
191
+ margin-top: 0.2rem;
192
+ }
193
+
194
+ /* ── Footer ──────────────────────────────────────────────────────── */
195
+
196
+ .footer {
197
+ margin-top: auto;
198
+ padding-top: 2rem;
199
+ padding-bottom: 1rem;
200
+ text-align: center;
201
+ position: relative;
202
+ z-index: 1;
203
+ }
204
+
205
+ .footer__link {
206
+ font-size: 0.7rem;
207
+ color: var(--text-muted);
208
+ text-decoration: none;
209
+ opacity: var(--footer-opacity);
210
+ transition: opacity 0.2s ease;
211
+ }
212
+
213
+ .footer__link:hover {
214
+ opacity: 1;
215
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Theme loader for KNTIC Links.
3
+ *
4
+ * Resolves theme names to CSS files in src/themes/, reads and returns
5
+ * the CSS string. No build step, no preprocessor — plain CSS only.
6
+ */
7
+
8
+ import { readFileSync, readdirSync } from 'node:fs';
9
+ import { resolve, dirname, basename, extname } from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+
14
+ /**
15
+ * Load a theme's CSS content by name.
16
+ *
17
+ * @param {string} themeName — theme name without the .css extension
18
+ * (e.g. "minimal-dark")
19
+ * @returns {string} Raw CSS content of the theme file
20
+ * @throws {Error} If the theme file does not exist or cannot be read
21
+ */
22
+ export function loadTheme(themeName) {
23
+ if (typeof themeName !== 'string' || themeName.trim().length === 0) {
24
+ throw new Error('Theme name must be a non-empty string.');
25
+ }
26
+
27
+ // Sanitise: strip any path separators and .css suffix the caller may have
28
+ // included accidentally. This prevents directory-traversal attempts.
29
+ const sanitised = basename(themeName, '.css');
30
+
31
+ const themePath = resolve(__dirname, `${sanitised}.css`);
32
+ try {
33
+ return readFileSync(themePath, 'utf8');
34
+ } catch {
35
+ const available = listThemes();
36
+ const hint = available.length
37
+ ? `Available themes: ${available.join(', ')}`
38
+ : 'No themes are currently installed.';
39
+
40
+ throw new Error(
41
+ `Theme "${sanitised}" not found at ${themePath}. ${hint}`,
42
+ );
43
+ }
44
+ }
45
+
46
+ /**
47
+ * List all available theme names (derived from *.css filenames in the
48
+ * themes directory).
49
+ *
50
+ * @returns {string[]} Sorted array of theme names (without .css extension)
51
+ */
52
+ export function listThemes() {
53
+ try {
54
+ return readdirSync(__dirname)
55
+ .filter((f) => extname(f) === '.css')
56
+ .map((f) => basename(f, '.css'))
57
+ .sort();
58
+ } catch {
59
+ return [];
60
+ }
61
+ }
@@ -0,0 +1,172 @@
1
+ /* minimal-dark — default KNTIC Links theme
2
+ *
3
+ * Clean, modern dark. No gradients, no gimmicks.
4
+ * This is what most users will ship.
5
+ */
6
+
7
+ *,
8
+ *::before,
9
+ *::after {
10
+ box-sizing: border-box;
11
+ margin: 0;
12
+ padding: 0;
13
+ }
14
+
15
+ :root {
16
+ /* Backgrounds */
17
+ --bg-color: #0f0f0f;
18
+ --bg-secondary: #1a1a1a;
19
+
20
+ /* Text */
21
+ --text-primary: #f0f0f0;
22
+ --text-secondary: #b0b0b0;
23
+ --text-muted: #787878;
24
+
25
+ /* Accent — muted violet */
26
+ --accent-color: #c4b5fd;
27
+ --accent-hover: #ddd6fe;
28
+
29
+ /* Link cards */
30
+ --link-bg: var(--bg-secondary);
31
+ --link-bg-hover: #242424;
32
+ --link-border: #2a2a2a;
33
+ --link-radius: 10px;
34
+ --link-padding: 0.875rem 1.25rem;
35
+
36
+ /* Typography */
37
+ --font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
38
+ --font-mono: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
39
+
40
+ /* Avatar */
41
+ --avatar-radius: 50%;
42
+
43
+ /* Layout */
44
+ --page-max-width: 480px;
45
+
46
+ /* Footer */
47
+ --footer-opacity: 0.5;
48
+ }
49
+
50
+ html {
51
+ font-size: 16px;
52
+ -webkit-text-size-adjust: 100%;
53
+ }
54
+
55
+ body {
56
+ font-family: var(--font-body);
57
+ background-color: var(--bg-color);
58
+ color: var(--text-primary);
59
+ min-height: 100vh;
60
+ display: flex;
61
+ flex-direction: column;
62
+ align-items: center;
63
+ padding: 3rem 1rem 1rem;
64
+ line-height: 1.5;
65
+ }
66
+
67
+ /* ── Profile ─────────────────────────────────────────────────────── */
68
+
69
+ .profile {
70
+ text-align: center;
71
+ margin-bottom: 2rem;
72
+ max-width: var(--page-max-width);
73
+ width: 100%;
74
+ }
75
+
76
+ .profile__avatar {
77
+ width: 88px;
78
+ height: 88px;
79
+ border-radius: var(--avatar-radius);
80
+ object-fit: cover;
81
+ margin-bottom: 0.75rem;
82
+ border: 2px solid var(--link-border);
83
+ }
84
+
85
+ .profile__name {
86
+ font-size: 1.3rem;
87
+ font-weight: 600;
88
+ margin-bottom: 0.3rem;
89
+ color: var(--text-primary);
90
+ letter-spacing: -0.01em;
91
+ }
92
+
93
+ .profile__bio {
94
+ font-size: 0.9rem;
95
+ color: var(--text-muted);
96
+ max-width: 320px;
97
+ margin: 0 auto;
98
+ }
99
+
100
+ /* ── Links ───────────────────────────────────────────────────────── */
101
+
102
+ .links {
103
+ list-style: none;
104
+ width: 100%;
105
+ max-width: var(--page-max-width);
106
+ display: flex;
107
+ flex-direction: column;
108
+ gap: 0.625rem;
109
+ }
110
+
111
+ .links__item {
112
+ display: block;
113
+ width: 100%;
114
+ }
115
+
116
+ .links__anchor {
117
+ display: block;
118
+ width: 100%;
119
+ padding: var(--link-padding);
120
+ background-color: var(--link-bg);
121
+ border: 1px solid var(--link-border);
122
+ border-radius: var(--link-radius);
123
+ color: var(--accent-color);
124
+ text-decoration: none;
125
+ text-align: center;
126
+ transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
127
+ }
128
+
129
+ .links__anchor:hover,
130
+ .links__anchor:focus {
131
+ background-color: var(--link-bg-hover);
132
+ color: var(--accent-hover);
133
+ border-color: #3a3a3a;
134
+ outline: none;
135
+ }
136
+
137
+ .links__label {
138
+ font-size: 0.95rem;
139
+ font-weight: 500;
140
+ }
141
+
142
+ .links__icon {
143
+ margin-right: 0.4em;
144
+ }
145
+
146
+ .links__description {
147
+ display: block;
148
+ font-size: 0.8rem;
149
+ color: var(--text-secondary);
150
+ margin-top: 0.2rem;
151
+ }
152
+
153
+ /* ── Footer ──────────────────────────────────────────────────────── */
154
+
155
+ .footer {
156
+ margin-top: auto;
157
+ padding-top: 2rem;
158
+ padding-bottom: 1rem;
159
+ text-align: center;
160
+ }
161
+
162
+ .footer__link {
163
+ font-size: 0.7rem;
164
+ color: var(--text-muted);
165
+ text-decoration: none;
166
+ opacity: var(--footer-opacity);
167
+ transition: opacity 0.2s ease;
168
+ }
169
+
170
+ .footer__link:hover {
171
+ opacity: 1;
172
+ }
@@ -0,0 +1,176 @@
1
+ /* minimal-light — clean light theme for KNTIC Links
2
+ *
3
+ * Mirror of minimal-dark in light mode. Off-white background,
4
+ * near-black text. Comfortable for daylight reading.
5
+ */
6
+
7
+ *,
8
+ *::before,
9
+ *::after {
10
+ box-sizing: border-box;
11
+ margin: 0;
12
+ padding: 0;
13
+ }
14
+
15
+ :root {
16
+ /* Backgrounds */
17
+ --bg-color: #f9f9f9;
18
+ --bg-secondary: #ffffff;
19
+
20
+ /* Text */
21
+ --text-primary: #111111;
22
+ --text-secondary: #555555;
23
+ --text-muted: #888888;
24
+
25
+ /* Accent — muted indigo */
26
+ --accent-color: #4f46e5;
27
+ --accent-hover: #3730a3;
28
+
29
+ /* Link cards */
30
+ --link-bg: var(--bg-secondary);
31
+ --link-bg-hover: #f3f3f3;
32
+ --link-border: #e5e5e5;
33
+ --link-radius: 10px;
34
+ --link-padding: 0.875rem 1.25rem;
35
+
36
+ /* Typography */
37
+ --font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
38
+ --font-mono: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
39
+
40
+ /* Avatar */
41
+ --avatar-radius: 50%;
42
+
43
+ /* Layout */
44
+ --page-max-width: 480px;
45
+
46
+ /* Footer */
47
+ --footer-opacity: 0.5;
48
+ }
49
+
50
+ html {
51
+ font-size: 16px;
52
+ -webkit-text-size-adjust: 100%;
53
+ }
54
+
55
+ body {
56
+ font-family: var(--font-body);
57
+ background-color: var(--bg-color);
58
+ color: var(--text-primary);
59
+ min-height: 100vh;
60
+ display: flex;
61
+ flex-direction: column;
62
+ align-items: center;
63
+ padding: 3rem 1rem 1rem;
64
+ line-height: 1.5;
65
+ }
66
+
67
+ /* ── Profile ─────────────────────────────────────────────────────── */
68
+
69
+ .profile {
70
+ text-align: center;
71
+ margin-bottom: 2rem;
72
+ max-width: var(--page-max-width);
73
+ width: 100%;
74
+ }
75
+
76
+ .profile__avatar {
77
+ width: 88px;
78
+ height: 88px;
79
+ border-radius: var(--avatar-radius);
80
+ object-fit: cover;
81
+ margin-bottom: 0.75rem;
82
+ border: 2px solid var(--link-border);
83
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
84
+ }
85
+
86
+ .profile__name {
87
+ font-size: 1.3rem;
88
+ font-weight: 600;
89
+ margin-bottom: 0.3rem;
90
+ color: var(--text-primary);
91
+ letter-spacing: -0.01em;
92
+ }
93
+
94
+ .profile__bio {
95
+ font-size: 0.9rem;
96
+ color: var(--text-muted);
97
+ max-width: 320px;
98
+ margin: 0 auto;
99
+ }
100
+
101
+ /* ── Links ───────────────────────────────────────────────────────── */
102
+
103
+ .links {
104
+ list-style: none;
105
+ width: 100%;
106
+ max-width: var(--page-max-width);
107
+ display: flex;
108
+ flex-direction: column;
109
+ gap: 0.625rem;
110
+ }
111
+
112
+ .links__item {
113
+ display: block;
114
+ width: 100%;
115
+ }
116
+
117
+ .links__anchor {
118
+ display: block;
119
+ width: 100%;
120
+ padding: var(--link-padding);
121
+ background-color: var(--link-bg);
122
+ border: 1px solid var(--link-border);
123
+ border-radius: var(--link-radius);
124
+ color: var(--accent-color);
125
+ text-decoration: none;
126
+ text-align: center;
127
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
128
+ transition: background-color 0.2s ease, color 0.2s ease,
129
+ border-color 0.2s ease, box-shadow 0.2s ease;
130
+ }
131
+
132
+ .links__anchor:hover,
133
+ .links__anchor:focus {
134
+ background-color: var(--link-bg-hover);
135
+ color: var(--accent-hover);
136
+ border-color: #d0d0d0;
137
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
138
+ outline: none;
139
+ }
140
+
141
+ .links__label {
142
+ font-size: 0.95rem;
143
+ font-weight: 500;
144
+ }
145
+
146
+ .links__icon {
147
+ margin-right: 0.4em;
148
+ }
149
+
150
+ .links__description {
151
+ display: block;
152
+ font-size: 0.8rem;
153
+ color: var(--text-secondary);
154
+ margin-top: 0.2rem;
155
+ }
156
+
157
+ /* ── Footer ──────────────────────────────────────────────────────── */
158
+
159
+ .footer {
160
+ margin-top: auto;
161
+ padding-top: 2rem;
162
+ padding-bottom: 1rem;
163
+ text-align: center;
164
+ }
165
+
166
+ .footer__link {
167
+ font-size: 0.7rem;
168
+ color: var(--text-muted);
169
+ text-decoration: none;
170
+ opacity: var(--footer-opacity);
171
+ transition: opacity 0.2s ease;
172
+ }
173
+
174
+ .footer__link:hover {
175
+ opacity: 1;
176
+ }