@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.
- package/LICENSE +21 -0
- package/README.md +149 -0
- package/package.json +46 -0
- package/src/.gitkeep +0 -0
- package/src/cli.js +39 -0
- package/src/commands/add.js +83 -0
- package/src/commands/config-cmd.js +49 -0
- package/src/commands/deploy.js +97 -0
- package/src/commands/init.js +107 -0
- package/src/commands/list.js +108 -0
- package/src/commands/open-cmd.js +64 -0
- package/src/commands/qr.js +99 -0
- package/src/commands/remove.js +55 -0
- package/src/commands/status.js +64 -0
- package/src/commands/theme.js +86 -0
- package/src/config.js +221 -0
- package/src/generator.js +185 -0
- package/src/themes/.gitkeep +0 -0
- package/src/themes/README.md +110 -0
- package/src/themes/developer.css +210 -0
- package/src/themes/glass.css +215 -0
- package/src/themes/loader.js +61 -0
- package/src/themes/minimal-dark.css +172 -0
- package/src/themes/minimal-light.css +176 -0
- package/src/themes/terminal.css +229 -0
- package/src/utils.js +80 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/* terminal — classic green-on-black terminal theme for KNTIC Links
|
|
2
|
+
*
|
|
3
|
+
* Matrix-inspired. Monospace everything. Green glow on hover.
|
|
4
|
+
* Cursor blink on the name.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap');
|
|
8
|
+
|
|
9
|
+
*,
|
|
10
|
+
*::before,
|
|
11
|
+
*::after {
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
margin: 0;
|
|
14
|
+
padding: 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
:root {
|
|
18
|
+
/* Backgrounds */
|
|
19
|
+
--bg-color: #0a0a0a;
|
|
20
|
+
--bg-secondary: #0f0f0f;
|
|
21
|
+
|
|
22
|
+
/* Text */
|
|
23
|
+
--text-primary: #00ff41;
|
|
24
|
+
--text-secondary: #00cc33;
|
|
25
|
+
--text-muted: #008f22;
|
|
26
|
+
|
|
27
|
+
/* Accent */
|
|
28
|
+
--accent-color: #00ff41;
|
|
29
|
+
--accent-hover: #33ff66;
|
|
30
|
+
|
|
31
|
+
/* Link cards */
|
|
32
|
+
--link-bg: var(--bg-secondary);
|
|
33
|
+
--link-bg-hover: #0d1a0f;
|
|
34
|
+
--link-border: #00ff41;
|
|
35
|
+
--link-radius: 0;
|
|
36
|
+
--link-padding: 0.75rem 1rem;
|
|
37
|
+
|
|
38
|
+
/* Typography */
|
|
39
|
+
--font-body: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'Courier New', Courier, monospace;
|
|
40
|
+
--font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'Courier New', Courier, monospace;
|
|
41
|
+
|
|
42
|
+
/* Avatar */
|
|
43
|
+
--avatar-radius: 0;
|
|
44
|
+
|
|
45
|
+
/* Layout */
|
|
46
|
+
--page-max-width: 520px;
|
|
47
|
+
|
|
48
|
+
/* Footer */
|
|
49
|
+
--footer-opacity: 0.5;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* ── Animations ──────────────────────────────────────────────────── */
|
|
53
|
+
|
|
54
|
+
@keyframes blink {
|
|
55
|
+
0%, 100% { opacity: 1; }
|
|
56
|
+
50% { opacity: 0; }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@keyframes scanline {
|
|
60
|
+
0% { transform: translateY(-100%); }
|
|
61
|
+
100% { transform: translateY(100vh); }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* ── Base ────────────────────────────────────────────────────────── */
|
|
65
|
+
|
|
66
|
+
html {
|
|
67
|
+
font-size: 16px;
|
|
68
|
+
-webkit-text-size-adjust: 100%;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
body {
|
|
72
|
+
font-family: var(--font-body);
|
|
73
|
+
background-color: var(--bg-color);
|
|
74
|
+
color: var(--text-primary);
|
|
75
|
+
min-height: 100vh;
|
|
76
|
+
display: flex;
|
|
77
|
+
flex-direction: column;
|
|
78
|
+
align-items: center;
|
|
79
|
+
padding: 3rem 1rem 1rem;
|
|
80
|
+
line-height: 1.6;
|
|
81
|
+
position: relative;
|
|
82
|
+
overflow-x: hidden;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* Subtle scanline overlay */
|
|
86
|
+
body::before {
|
|
87
|
+
content: '';
|
|
88
|
+
position: fixed;
|
|
89
|
+
top: 0;
|
|
90
|
+
left: 0;
|
|
91
|
+
width: 100%;
|
|
92
|
+
height: 4px;
|
|
93
|
+
background: rgba(0, 255, 65, 0.06);
|
|
94
|
+
animation: scanline 8s linear infinite;
|
|
95
|
+
pointer-events: none;
|
|
96
|
+
z-index: 1000;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* ── Profile ─────────────────────────────────────────────────────── */
|
|
100
|
+
|
|
101
|
+
.profile {
|
|
102
|
+
text-align: left;
|
|
103
|
+
margin-bottom: 1.5rem;
|
|
104
|
+
max-width: var(--page-max-width);
|
|
105
|
+
width: 100%;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.profile__avatar {
|
|
109
|
+
width: 72px;
|
|
110
|
+
height: 72px;
|
|
111
|
+
border-radius: var(--avatar-radius);
|
|
112
|
+
object-fit: cover;
|
|
113
|
+
margin-bottom: 0.75rem;
|
|
114
|
+
border: 1px solid var(--link-border);
|
|
115
|
+
filter: grayscale(100%) brightness(1.1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.profile__name {
|
|
119
|
+
font-size: 1.1rem;
|
|
120
|
+
font-weight: 700;
|
|
121
|
+
margin-bottom: 0.3rem;
|
|
122
|
+
color: var(--text-primary);
|
|
123
|
+
text-shadow: 0 0 8px rgba(0, 255, 65, 0.4);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* Blinking cursor after name */
|
|
127
|
+
.profile__name::after {
|
|
128
|
+
content: '█';
|
|
129
|
+
animation: blink 1s step-end infinite;
|
|
130
|
+
margin-left: 2px;
|
|
131
|
+
font-weight: 400;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.profile__bio {
|
|
135
|
+
font-size: 0.8rem;
|
|
136
|
+
color: var(--text-muted);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.profile__bio::before {
|
|
140
|
+
content: '// ';
|
|
141
|
+
color: var(--text-muted);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* ── Links ───────────────────────────────────────────────────────── */
|
|
145
|
+
|
|
146
|
+
.links {
|
|
147
|
+
list-style: none;
|
|
148
|
+
width: 100%;
|
|
149
|
+
max-width: var(--page-max-width);
|
|
150
|
+
display: flex;
|
|
151
|
+
flex-direction: column;
|
|
152
|
+
gap: 0.5rem;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.links__item {
|
|
156
|
+
display: block;
|
|
157
|
+
width: 100%;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.links__anchor {
|
|
161
|
+
display: block;
|
|
162
|
+
width: 100%;
|
|
163
|
+
padding: var(--link-padding);
|
|
164
|
+
background-color: var(--link-bg);
|
|
165
|
+
border: 1px solid var(--link-border);
|
|
166
|
+
border-radius: var(--link-radius);
|
|
167
|
+
color: var(--accent-color);
|
|
168
|
+
text-decoration: none;
|
|
169
|
+
text-align: left;
|
|
170
|
+
font-family: var(--font-mono);
|
|
171
|
+
transition: background-color 0.15s ease, box-shadow 0.15s ease,
|
|
172
|
+
text-shadow 0.15s ease;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.links__anchor::before {
|
|
176
|
+
content: '> ';
|
|
177
|
+
color: var(--text-muted);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.links__anchor:hover,
|
|
181
|
+
.links__anchor:focus {
|
|
182
|
+
background-color: var(--link-bg-hover);
|
|
183
|
+
color: var(--accent-hover);
|
|
184
|
+
box-shadow: 0 0 12px rgba(0, 255, 65, 0.15), inset 0 0 12px rgba(0, 255, 65, 0.03);
|
|
185
|
+
text-shadow: 0 0 6px rgba(0, 255, 65, 0.5);
|
|
186
|
+
outline: none;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.links__label {
|
|
190
|
+
font-size: 0.85rem;
|
|
191
|
+
font-weight: 500;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.links__icon {
|
|
195
|
+
margin-right: 0.4em;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.links__description {
|
|
199
|
+
display: block;
|
|
200
|
+
font-size: 0.75rem;
|
|
201
|
+
color: var(--text-secondary);
|
|
202
|
+
margin-top: 0.15rem;
|
|
203
|
+
padding-left: 1.15em;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* ── Footer ──────────────────────────────────────────────────────── */
|
|
207
|
+
|
|
208
|
+
.footer {
|
|
209
|
+
margin-top: auto;
|
|
210
|
+
padding-top: 2rem;
|
|
211
|
+
padding-bottom: 1rem;
|
|
212
|
+
text-align: center;
|
|
213
|
+
max-width: var(--page-max-width);
|
|
214
|
+
width: 100%;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.footer__link {
|
|
218
|
+
font-size: 0.65rem;
|
|
219
|
+
color: var(--text-muted);
|
|
220
|
+
text-decoration: none;
|
|
221
|
+
opacity: var(--footer-opacity);
|
|
222
|
+
font-family: var(--font-mono);
|
|
223
|
+
transition: opacity 0.2s ease;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.footer__link:hover {
|
|
227
|
+
opacity: 1;
|
|
228
|
+
text-shadow: 0 0 6px rgba(0, 255, 65, 0.4);
|
|
229
|
+
}
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utility helpers for @kntic/links.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFile } from 'node:fs/promises';
|
|
6
|
+
import { resolve } from 'node:path';
|
|
7
|
+
import yaml from 'js-yaml';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Default config filename.
|
|
11
|
+
*/
|
|
12
|
+
export const CONFIG_FILE = 'links.yml';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Load and parse the links YAML config from the current directory.
|
|
16
|
+
* @param {string} [dir=process.cwd()] - Directory to look in.
|
|
17
|
+
* @returns {Promise<object>} Parsed config object.
|
|
18
|
+
*/
|
|
19
|
+
export async function loadConfig(dir = process.cwd()) {
|
|
20
|
+
const filePath = resolve(dir, CONFIG_FILE);
|
|
21
|
+
const raw = await readFile(filePath, 'utf8');
|
|
22
|
+
return yaml.load(raw);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Pretty-print an error and exit.
|
|
27
|
+
* @param {string} message
|
|
28
|
+
* @param {number} [code=1]
|
|
29
|
+
*/
|
|
30
|
+
export function fatal(message, code = 1) {
|
|
31
|
+
console.error(`error: ${message}`);
|
|
32
|
+
process.exit(code);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Link scheduling
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Determine whether a link is currently active based on its scheduling fields.
|
|
41
|
+
*
|
|
42
|
+
* Rules:
|
|
43
|
+
* - No dates → always active
|
|
44
|
+
* - Only scheduled_from → active from that date onwards (inclusive, >=)
|
|
45
|
+
* - Only scheduled_until → active until that date (inclusive, <=)
|
|
46
|
+
* - Both → active within the window [scheduled_from, scheduled_until]
|
|
47
|
+
*
|
|
48
|
+
* All comparisons are performed in UTC using Date.parse().
|
|
49
|
+
*
|
|
50
|
+
* @param {object} link — a link object that may contain scheduled_from / scheduled_until
|
|
51
|
+
* @param {Date} [now=new Date()] — reference time for the comparison
|
|
52
|
+
* @returns {boolean} true if the link should be included in the current build
|
|
53
|
+
*/
|
|
54
|
+
export function isLinkActive(link, now = new Date()) {
|
|
55
|
+
if (!link || typeof link !== 'object') return false;
|
|
56
|
+
|
|
57
|
+
const ts = now.getTime();
|
|
58
|
+
|
|
59
|
+
if (link.scheduled_from) {
|
|
60
|
+
const from = Date.parse(link.scheduled_from);
|
|
61
|
+
if (!Number.isNaN(from) && ts < from) return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (link.scheduled_until) {
|
|
65
|
+
const until = Date.parse(link.scheduled_until);
|
|
66
|
+
// Inclusive: if only a date (YYYY-MM-DD) is given, Date.parse returns
|
|
67
|
+
// midnight UTC — we treat the entire day as included, so we compare
|
|
68
|
+
// against the end of that day (start-of-next-day minus 1 ms) only when
|
|
69
|
+
// the value looks like a bare date (10 chars, no time component).
|
|
70
|
+
if (!Number.isNaN(until)) {
|
|
71
|
+
const untilEnd =
|
|
72
|
+
typeof link.scheduled_until === 'string' && link.scheduled_until.length === 10
|
|
73
|
+
? until + 86_400_000 - 1 // end of day
|
|
74
|
+
: until;
|
|
75
|
+
if (ts > untilEnd) return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return true;
|
|
80
|
+
}
|