vending-mocha 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 (42) hide show
  1. package/.github/workflows/npm-publish.yml +45 -0
  2. package/.github/workflows/regenerate-dist.yml +38 -0
  3. package/LICENSE +201 -0
  4. package/README.md +85 -0
  5. package/SKILL.md +82 -0
  6. package/bin/cli.js +441 -0
  7. package/eslint.config.js +23 -0
  8. package/index.html +16 -0
  9. package/package.json +57 -0
  10. package/posts/customization-guide.md +45 -0
  11. package/posts/deploy-to-github-pages.md +109 -0
  12. package/posts/hello-world.md +20 -0
  13. package/posts/markdown-features.md +57 -0
  14. package/prerender.js +221 -0
  15. package/projects/legacy-api.md +7 -0
  16. package/projects/task-master.md +7 -0
  17. package/projects/vending-mocha.md +7 -0
  18. package/scripts/generate-posts-data.js +41 -0
  19. package/scripts/generate-projects-data.js +40 -0
  20. package/scripts/generate-rss.js +75 -0
  21. package/src/App.css +566 -0
  22. package/src/App.tsx +33 -0
  23. package/src/components/Footer.tsx +11 -0
  24. package/src/components/MarkdownImage.tsx +40 -0
  25. package/src/components/Profile.tsx +45 -0
  26. package/src/components/SiteHeader.tsx +44 -0
  27. package/src/context/ThemeContext.tsx +75 -0
  28. package/src/entry-client.tsx +32 -0
  29. package/src/entry-server.tsx +26 -0
  30. package/src/pages/BlogPost.tsx +93 -0
  31. package/src/pages/HomePage.tsx +85 -0
  32. package/src/pages/Projects.tsx +50 -0
  33. package/src/site.config.ts +38 -0
  34. package/src/utils/basePath.ts +21 -0
  35. package/src/utils/date.ts +17 -0
  36. package/src/utils/frontmatter.ts +32 -0
  37. package/static/favicon.ico +0 -0
  38. package/static/images/profile.png +0 -0
  39. package/tsconfig.app.json +36 -0
  40. package/tsconfig.json +7 -0
  41. package/tsconfig.node.json +26 -0
  42. package/vite.config.ts +61 -0
package/src/App.css ADDED
@@ -0,0 +1,566 @@
1
+ :root {
2
+ font-family: var(--font-family, Avenir, Open Sans, sans-serif);
3
+ line-height: 1.5;
4
+ font-weight: 400;
5
+
6
+ color-scheme: light dark;
7
+ color: var(--color-text);
8
+ background-color: var(--color-background);
9
+
10
+ font-synthesis: none;
11
+ text-rendering: optimizeLegibility;
12
+ -webkit-font-smoothing: antialiased;
13
+ -moz-osx-font-smoothing: grayscale;
14
+ }
15
+
16
+ body {
17
+ margin: 0;
18
+ min-width: 320px;
19
+ min-height: 100vh;
20
+
21
+ a {
22
+ color: var(--color-link);
23
+ text-decoration: none;
24
+ }
25
+
26
+ a:hover {
27
+ color: var(--color-link-hover);
28
+ }
29
+ }
30
+
31
+ .container {
32
+ max-width: 1280px;
33
+ margin: 0 auto;
34
+ padding: 2rem;
35
+ text-align: center;
36
+ }
37
+
38
+
39
+ h1 {
40
+ font-size: 3.2em;
41
+ line-height: 1.1;
42
+ }
43
+
44
+ .project-grid {
45
+ display: grid;
46
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
47
+ gap: 2rem;
48
+ justify-content: center;
49
+ }
50
+
51
+ .project-card {
52
+ background: var(--color-card-bg);
53
+ padding: 2rem;
54
+ border-radius: 12px;
55
+ transition: transform 0.2s, box-shadow 0.2s;
56
+ border: 1px solid var(--color-border);
57
+
58
+ a {
59
+ color: var(--color-link);
60
+ text-decoration: none;
61
+ }
62
+
63
+ a:hover {
64
+ color: var(--color-link-hover);
65
+ }
66
+
67
+ a.btn {
68
+ background-color: var(--color-text);
69
+ color: var(--color-background);
70
+ }
71
+
72
+ }
73
+
74
+ .project-card:hover {
75
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
76
+ border-color: var(--color-primary);
77
+ }
78
+
79
+ .btn {
80
+ display: inline-block;
81
+ margin-top: 1rem;
82
+ padding: 0.6em 1.2em;
83
+ font-size: 1em;
84
+ font-weight: 500;
85
+ font-family: inherit;
86
+ background-color: var(--color-accent);
87
+ cursor: pointer;
88
+ transition: border-color 0.25s;
89
+ border: 1px solid transparent;
90
+ border-radius: 8px;
91
+ text-decoration: none;
92
+ color: white;
93
+ background-color: var(--color-primary);
94
+ }
95
+
96
+ .btn:hover {
97
+ background-color: var(--color-primary);
98
+ }
99
+
100
+ @media (prefers-color-scheme: light) {
101
+ :root {
102
+ color: var(--color-text);
103
+ background-color: var(--color-background);
104
+ }
105
+
106
+ .project-card {
107
+ background: var(--color-card-bg);
108
+ border-color: var(--color-border);
109
+ }
110
+
111
+ .btn {
112
+ color: white;
113
+ }
114
+ }
115
+
116
+ /* New Blog Design */
117
+
118
+ .blog-container {
119
+ max-width: 900px;
120
+ margin: 0 auto;
121
+ padding: 0;
122
+ text-align: left;
123
+ }
124
+
125
+ .main-header {
126
+ display: flex;
127
+ justify-content: space-between;
128
+ align-items: center;
129
+ flex-direction: row;
130
+ padding: 0 0 10px 0;
131
+ margin-bottom: 3rem;
132
+ position: relative;
133
+ z-index: 1000;
134
+
135
+ &.with-bottom-border {
136
+ border-bottom: 2px solid var(--color-border);
137
+ }
138
+ }
139
+
140
+ .header-actions {
141
+ display: flex;
142
+ align-items: center;
143
+ gap: 1rem;
144
+ }
145
+
146
+ .theme-toggle,
147
+ .hamburger-menu {
148
+ background: none;
149
+ border: none;
150
+ cursor: pointer;
151
+ color: var(--color-text);
152
+ display: flex;
153
+ align-items: center;
154
+ padding: 0.5rem;
155
+ border-radius: 8px;
156
+ transition: background-color 0.2s;
157
+ }
158
+
159
+ .theme-toggle:hover,
160
+ .hamburger-menu:hover {
161
+ background-color: var(--color-card-bg);
162
+ }
163
+
164
+ .hamburger-menu {
165
+ display: none;
166
+ }
167
+
168
+ .nav-container {
169
+ display: flex;
170
+ align-items: center;
171
+ padding: 4px 0;
172
+ gap: 2rem;
173
+ justify-content: right;
174
+ }
175
+
176
+ .site-title {
177
+ font-size: 2.5rem;
178
+ margin: 0;
179
+ font-weight: 700;
180
+ letter-spacing: -0.05em;
181
+ background: none;
182
+ -webkit-text-fill-color: inherit;
183
+
184
+ a {
185
+ color: var(--color-primary);
186
+ text-decoration: none;
187
+ }
188
+ }
189
+
190
+ .top-nav {
191
+ display: flex;
192
+ gap: 2rem;
193
+ }
194
+
195
+ .top-nav a {
196
+ color: var(--color-link);
197
+ text-decoration: none;
198
+ font-weight: 500;
199
+ transition: color 0.2s;
200
+ display: flex;
201
+ align-items: center;
202
+ gap: 0.5rem;
203
+ }
204
+
205
+ .top-nav a:hover {
206
+ color: var(--color-link-hover);
207
+ }
208
+
209
+ /* Profile Section */
210
+ .profile-section {
211
+ display: grid;
212
+ grid-template-columns: 220px 1fr;
213
+ gap: 2rem;
214
+ align-items: start;
215
+ /* margin-bottom: 4rem; */
216
+ }
217
+
218
+ .profile-sidebar {
219
+ display: flex;
220
+ flex-direction: column;
221
+ align-items: center;
222
+ gap: 1rem;
223
+ }
224
+
225
+ .profile-image-container {
226
+ width: 200px;
227
+ height: 200px;
228
+ border-radius: 50%;
229
+ overflow: hidden;
230
+ border: 4px solid var(--color-border);
231
+ margin: auto;
232
+ }
233
+
234
+ .profile-image {
235
+ width: 100%;
236
+ height: 100%;
237
+ object-fit: cover;
238
+ }
239
+
240
+ .social-links {
241
+ display: flex;
242
+ justify-content: left;
243
+ gap: 1rem;
244
+ margin-top: 1rem;
245
+ }
246
+
247
+ .social-links a {
248
+ color: var(--color-text);
249
+ transition: color 0.2s;
250
+ display: flex;
251
+ align-items: center;
252
+ justify-content: center;
253
+ }
254
+
255
+ .social-links a:hover {
256
+ color: var(--color-primary);
257
+ }
258
+
259
+ .profile-info h2 {
260
+ font-size: 1.8rem;
261
+ margin-top: 0;
262
+ margin-bottom: 1rem;
263
+ color: var(--color-primary);
264
+ }
265
+
266
+ .profile-info p {
267
+ line-height: 1.7;
268
+ font-size: 1.1rem;
269
+ }
270
+
271
+ /* Posts Section */
272
+ .posts-section h2 {
273
+ font-size: 1.5rem;
274
+ margin-bottom: 2rem;
275
+ border-bottom: 1px solid var(--color-border);
276
+ padding-bottom: 1rem;
277
+ color: var(--color-primary);
278
+ }
279
+
280
+ .posts-list {
281
+ display: flex;
282
+ flex-direction: column;
283
+ gap: 3rem;
284
+ }
285
+
286
+ .post-item {
287
+ display: flex;
288
+ flex-direction: column;
289
+ gap: 0.5rem;
290
+ }
291
+
292
+ .post-header {
293
+ display: flex;
294
+ justify-content: space-between;
295
+ align-items: baseline;
296
+ flex-wrap: wrap;
297
+ gap: 1rem;
298
+ }
299
+
300
+ .post-header h3 {
301
+ margin: 0;
302
+ font-size: 1.4rem;
303
+ }
304
+
305
+ .post-header h3 a {
306
+ color: var(--color-link);
307
+ text-decoration: none;
308
+ }
309
+
310
+ .post-header h3 a:hover {
311
+ color: var(--color-link-hover);
312
+ }
313
+
314
+ .post-date {
315
+ color: var(--color-secondary);
316
+ font-size: 0.9rem;
317
+ font-family: monospace;
318
+ }
319
+
320
+ .post-summary {
321
+ margin: 0;
322
+ font-size: 1.1rem;
323
+ line-height: 1.6;
324
+ }
325
+
326
+ /* @media (max-width: 700px) {
327
+
328
+
329
+
330
+
331
+
332
+ } */
333
+
334
+ @media (max-width: 700px) {
335
+
336
+ .main-header {
337
+ flex-direction: row;
338
+ justify-content: space-between;
339
+ align-items: center;
340
+ padding-bottom: 0.5rem;
341
+ }
342
+
343
+ .top-nav {
344
+ display: none;
345
+ flex-direction: column;
346
+ position: absolute;
347
+ top: 100%;
348
+ left: 0;
349
+ right: 0;
350
+ background-color: var(--color-background);
351
+ padding: 2rem;
352
+ gap: 1.5rem;
353
+ border-bottom: 1px solid var(--color-border);
354
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
355
+ }
356
+
357
+ .profile-section {
358
+ grid-template-columns: 1fr;
359
+ text-align: center;
360
+ justify-items: center;
361
+ gap: 1rem;
362
+ }
363
+
364
+ .profile-info {
365
+ text-align: left;
366
+ }
367
+
368
+ .profile-sidebar {
369
+ order: -1;
370
+ margin-bottom: 1.5rem;
371
+ }
372
+
373
+ .post-header {
374
+ flex-direction: column;
375
+ gap: 0.2rem;
376
+ }
377
+
378
+ .top-nav.active {
379
+ display: flex;
380
+ }
381
+
382
+ .hamburger-menu {
383
+ display: flex;
384
+ }
385
+
386
+ .top-nav a {
387
+ font-size: 1.2rem;
388
+ width: 100%;
389
+ justify-content: flex-start;
390
+ }
391
+
392
+ .container {
393
+ padding: 1rem;
394
+ }
395
+ }
396
+
397
+
398
+
399
+ /* Blog Styles */
400
+
401
+ .blog-list {
402
+ text-align: left;
403
+ max-width: 800px;
404
+ margin: 0 auto;
405
+ }
406
+
407
+ .posts-grid {
408
+ display: flex;
409
+ flex-direction: column;
410
+ gap: 2rem;
411
+ margin-top: 2rem;
412
+ }
413
+
414
+ .post-card {
415
+ background: var(--color-accent);
416
+ padding: 2rem;
417
+ border-radius: 12px;
418
+ border: 1px solid #333;
419
+ transition: border-color 0.25s;
420
+ }
421
+
422
+ .post-card:hover {
423
+ border-color: var(--color-primary);
424
+ }
425
+
426
+ .post-card h2 {
427
+ margin-top: 0;
428
+ }
429
+
430
+ .post-card h2 a {
431
+ color: inherit;
432
+ text-decoration: none;
433
+ }
434
+
435
+ .post-card h2 a:hover {
436
+ color: var(--color-link-hover);
437
+ }
438
+
439
+ .projects-section {
440
+ margin-top: 1rem;
441
+ }
442
+
443
+ .project-links {
444
+ display: flex;
445
+ gap: 1rem;
446
+ }
447
+
448
+ .project-link {
449
+ color: #fff;
450
+ background-color: var(--color-accent);
451
+ padding: 0.5rem 1rem;
452
+ border-radius: 6px;
453
+ text-decoration: none;
454
+ }
455
+
456
+ .project-link:hover {
457
+ background-color: #444;
458
+ }
459
+
460
+ /* Blog Post Styles */
461
+ .blog-post {
462
+ text-align: left;
463
+ max-width: 800px;
464
+ margin: 0 auto;
465
+ }
466
+
467
+ .meta {
468
+ color: var(--color-secondary);
469
+ }
470
+
471
+ .markdown-content {
472
+ font-size: 1.1rem;
473
+ line-height: 1.8;
474
+ }
475
+
476
+ .markdown-content h1,
477
+ .markdown-content h2,
478
+ .markdown-content h3 {
479
+ margin-top: 2rem;
480
+ color: var(--color-text);
481
+ }
482
+
483
+ .markdown-content p {
484
+ margin-bottom: 1rem;
485
+ color: var(--color-text);
486
+ }
487
+
488
+ code {
489
+ background: var(--color-card-bg);
490
+ color: var(--color-text);
491
+ padding: 0.2em 0.4em;
492
+ border-radius: 4px;
493
+ }
494
+
495
+ .markdown-content pre {
496
+ border-radius: 8px;
497
+ overflow-x: auto;
498
+ }
499
+
500
+ .markdown-content pre code {
501
+ background: none;
502
+ padding: 0;
503
+ }
504
+
505
+ @media (prefers-color-scheme: light) {
506
+ .post-card {
507
+ background: var(--color-card-bg);
508
+ border-color: var(--color-border);
509
+ }
510
+
511
+ .markdown-content p {
512
+ color: #444;
513
+ }
514
+
515
+ .markdown-content h1,
516
+ .markdown-content h2,
517
+ .markdown-content h3 {
518
+ color: var(--color-primary);
519
+ }
520
+
521
+ .meta {
522
+ color: var(--color-secondary);
523
+ }
524
+ }
525
+
526
+ /* App Wrapper */
527
+ .app-wrapper {
528
+ min-height: 100vh;
529
+ }
530
+
531
+
532
+ .footer-container {
533
+ border-top: 1px solid var(--color-border);
534
+ display: flex;
535
+ justify-content: center;
536
+ align-items: center;
537
+ margin: 0 auto;
538
+ padding: 10px 2rem
539
+ }
540
+
541
+ .footer-text {
542
+ margin: 0;
543
+ color: var(--color-secondary);
544
+ }
545
+
546
+ /* Pagination */
547
+ .pagination {
548
+ display: flex;
549
+ justify-content: center;
550
+ gap: 2rem;
551
+ margin-top: 3rem;
552
+ padding: 2rem 0;
553
+ border-top: 1px solid var(--color-border);
554
+ }
555
+
556
+ .prev-link,
557
+ .next-link {
558
+ font-weight: 500;
559
+ color: var(--color-text);
560
+ text-decoration: none;
561
+ }
562
+
563
+ .prev-link:hover,
564
+ .next-link:hover {
565
+ text-decoration: underline;
566
+ }
package/src/App.tsx ADDED
@@ -0,0 +1,33 @@
1
+ import { Routes, Route } from 'react-router-dom';
2
+ import HomePage from './pages/HomePage';
3
+ import BlogPost from './pages/BlogPost';
4
+ import Projects from './pages/Projects';
5
+ import Footer from './components/Footer';
6
+ import './App.css';
7
+ import { ThemeProvider } from './context/ThemeContext';
8
+
9
+ function AppContent() {
10
+ return (
11
+ <>
12
+ <div className="app-wrapper container">
13
+ <Routes>
14
+ <Route path="/" element={<HomePage />} />
15
+ <Route path="/page/:pageNumber" element={<HomePage />} />
16
+ <Route path="/projects" element={<Projects />} />
17
+ <Route path="/post/:slug" element={<BlogPost />} />
18
+ </Routes>
19
+ </div>
20
+ <Footer />
21
+ </>
22
+ );
23
+ }
24
+
25
+ function App() {
26
+ return (
27
+ <ThemeProvider>
28
+ <AppContent />
29
+ </ThemeProvider>
30
+ );
31
+ }
32
+
33
+ export default App;
@@ -0,0 +1,11 @@
1
+ import { siteConfig } from '../site.config';
2
+
3
+ export default function Footer() {
4
+ return (
5
+ <footer className="container footer-container">
6
+ <p className="footer-text">
7
+ {`© ${new Date().getFullYear()} ${siteConfig.title}. ${siteConfig.footerText}`}
8
+ </p>
9
+ </footer>
10
+ );
11
+ }
@@ -0,0 +1,40 @@
1
+
2
+
3
+ interface MarkdownImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
4
+ src?: string;
5
+ alt?: string;
6
+ }
7
+
8
+ export function MarkdownImage({ src, alt, ...props }: MarkdownImageProps) {
9
+ if (!src) {
10
+ return <img alt={alt} {...props} />;
11
+ }
12
+
13
+ try {
14
+ const url = new URL(src, 'http://dummy-base');
15
+ const width = url.searchParams.get('vmw');
16
+ const height = url.searchParams.get('vmh');
17
+
18
+ //remove the vmw and vmh params from the src
19
+ src = src.replace(/\?vmw=\d+&vmh=\d+/, '');
20
+
21
+ // Construct styles or attributes
22
+ const style: React.CSSProperties = {};
23
+ if (width) style.width = `${width}px`;
24
+ if (height) style.height = `${height}px`;
25
+
26
+ return (
27
+ <img
28
+ src={src}
29
+ alt={alt}
30
+ width={width || undefined}
31
+ height={height || undefined}
32
+ style={{ ...props.style, ...style }}
33
+ {...props}
34
+ />
35
+ );
36
+ } catch (e) {
37
+ // Fallback for invalid URLs
38
+ return <img src={src} alt={alt} {...props} />;
39
+ }
40
+ }
@@ -0,0 +1,45 @@
1
+ import Markdown from 'react-markdown';
2
+ import { getBasePath } from '../utils/basePath';
3
+ import remarkBreaks from 'remark-breaks';
4
+ import { siteConfig } from '../site.config';
5
+ import remarkGfm from 'remark-gfm';
6
+ import { Github, Mail, Rss } from 'lucide-react';
7
+
8
+ export default function Profile() {
9
+ return (
10
+ <section className="profile-section">
11
+ {siteConfig.image && (
12
+ <div className="profile-sidebar">
13
+ <div className="profile-image-container">
14
+ <img
15
+ src={siteConfig.image.startsWith('http') ? siteConfig.image : `${getBasePath()}${siteConfig.image.startsWith('/') ? siteConfig.image.slice(1) : siteConfig.image}`}
16
+ alt="Profile"
17
+ className="profile-image"
18
+ />
19
+ </div>
20
+ </div>
21
+ )}
22
+ <div className="profile-info">
23
+ <h1 className="site-title">{siteConfig.title}</h1>
24
+ <div className="social-links">
25
+ {siteConfig.contact?.email && (
26
+ <a href={`mailto:${siteConfig.contact.email}`} aria-label="Email">
27
+ <Mail size={20} />
28
+ </a>
29
+ )}
30
+ {siteConfig.contact?.github && (
31
+ <a href={siteConfig.contact.github} target="_blank" rel="noopener noreferrer" aria-label="Github">
32
+ <Github size={20} />
33
+ </a>
34
+ )}
35
+
36
+ <a href={`${getBasePath()}rss.xml`} target="_blank" rel="noopener noreferrer" aria-label="RSS Feed">
37
+ <Rss size={18} className="inline-block align-middle text-gray-500 hover:text-black transition-colors" />
38
+ </a>
39
+ </div>
40
+ <Markdown remarkPlugins={[remarkBreaks, remarkGfm]}>{siteConfig.description}</Markdown>
41
+
42
+ </div>
43
+ </section>
44
+ );
45
+ }