goofmint 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.
Files changed (60) hide show
  1. package/.claude/settings.local.json +17 -0
  2. package/.github/workflows/deploy.yml +44 -0
  3. package/.nvmrc +1 -0
  4. package/.ruby-version +1 -0
  5. package/DEPLOYMENT.md +111 -0
  6. package/Gemfile +10 -0
  7. package/README.md +118 -0
  8. package/_config.yml +77 -0
  9. package/_data/i18n.yml +86 -0
  10. package/_data/social.yml +19 -0
  11. package/_includes/footer.html +18 -0
  12. package/_includes/header.html +42 -0
  13. package/_includes/i18n.html +2 -0
  14. package/_layouts/default.html +25 -0
  15. package/_layouts/post.html +18 -0
  16. package/_layouts/project.html +37 -0
  17. package/_posts/2025-01-03-welcome.md +62 -0
  18. package/_posts/en/2025-01-03-welcome.md +61 -0
  19. package/_projects/bug-sniper.md +28 -0
  20. package/_projects/caiz.md +29 -0
  21. package/_projects/drowl.md +28 -0
  22. package/_projects/incho-app.md +32 -0
  23. package/_projects/moongift.md +33 -0
  24. package/_projects/review-game.md +28 -0
  25. package/_projects/sheetdb.md +30 -0
  26. package/_projects/text2cal.md +49 -0
  27. package/_projects_en/bug-sniper.md +28 -0
  28. package/_projects_en/caiz.md +29 -0
  29. package/_projects_en/drowl.md +28 -0
  30. package/_projects_en/incho-app.md +32 -0
  31. package/_projects_en/moongift.md +33 -0
  32. package/_projects_en/review-game.md +28 -0
  33. package/_projects_en/sheetdb.md +30 -0
  34. package/_projects_en/text2cal.md +49 -0
  35. package/_sass/_base.scss +58 -0
  36. package/_sass/_components.scss +113 -0
  37. package/_sass/_layout.scss +140 -0
  38. package/_sass/_syntax.scss +137 -0
  39. package/_sass/_theme.scss +261 -0
  40. package/about.md +182 -0
  41. package/assets/css/main.scss +68 -0
  42. package/assets/images/atsushi.jpg +0 -0
  43. package/assets/images/daruma.jpeg +0 -0
  44. package/assets/images/icons/email.svg +1 -0
  45. package/assets/images/icons/github.svg +1 -0
  46. package/assets/images/icons/linkedin.svg +1 -0
  47. package/assets/images/icons/x.svg +1 -0
  48. package/assets/images/moveum.jpeg +0 -0
  49. package/assets/images/notes.jpeg +0 -0
  50. package/assets/js/lang-toggle.js +89 -0
  51. package/assets/js/theme-toggle.js +69 -0
  52. package/blog.html +21 -0
  53. package/en/about.md +180 -0
  54. package/en/blog.html +21 -0
  55. package/en/index.html +45 -0
  56. package/en/projects.html +26 -0
  57. package/index.html +45 -0
  58. package/index.js +321 -0
  59. package/package.json +43 -0
  60. package/projects.html +29 -0
package/en/index.html ADDED
@@ -0,0 +1,45 @@
1
+ ---
2
+ layout: default
3
+ title: Home
4
+ lang: en
5
+ ---
6
+
7
+ {% include i18n.html %}
8
+
9
+ <section class="hero">
10
+ <h1>{{ t.home.hero_title }}</h1>
11
+ <p>{{ t.home.hero_description }}</p>
12
+ </section>
13
+
14
+ <section class="intro">
15
+ <h2>{{ t.home.about_title }}</h2>
16
+ <p>{{ t.home.about_description }}</p>
17
+ </section>
18
+
19
+ <section class="projects-preview">
20
+ <h2>{{ t.home.featured_projects }}</h2>
21
+ <div class="project-grid">
22
+ {% for project in site.projects_en limit:3 %}
23
+ <div class="project-card">
24
+ <h3><a href="{{ project.url | relative_url }}">{{ project.title }}</a></h3>
25
+ <p>{{ project.excerpt }}</p>
26
+ </div>
27
+ {% endfor %}
28
+ </div>
29
+ <a href="{{ "/en/projects/" | relative_url }}" class="btn">{{ t.home.view_all_projects }}</a>
30
+ </section>
31
+
32
+ <section class="blog-preview">
33
+ <h2>{{ t.home.latest_posts }}</h2>
34
+ <ul class="post-list">
35
+ {% assign en_posts = site.posts | where: "lang", "en" %}
36
+ {% for post in en_posts limit:3 %}
37
+ <li>
38
+ <h3><a href="{{ post.url | relative_url }}">{{ post.title }}</a></h3>
39
+ <p class="post-meta">{{ post.date | date: "%B %d, %Y" }}</p>
40
+ <p>{{ post.excerpt }}</p>
41
+ </li>
42
+ {% endfor %}
43
+ </ul>
44
+ <a href="{{ "/en/blog/" | relative_url }}" class="btn">{{ t.home.view_all_posts }}</a>
45
+ </section>
@@ -0,0 +1,26 @@
1
+ ---
2
+ layout: default
3
+ title: Projects
4
+ permalink: /en/projects/
5
+ lang: en
6
+ ---
7
+
8
+ {% include i18n.html %}
9
+
10
+ <h1>{{ t.projects.title }}</h1>
11
+
12
+ <div class="project-grid">
13
+ {% for project in site.projects_en %}
14
+ <div class="project-card">
15
+ <h2><a href="{{ project.url | relative_url }}">{{ project.title }}</a></h2>
16
+ {% if project.tech %}
17
+ <div class="project-tech">
18
+ {% for tech in project.tech %}
19
+ <span class="tech-tag">{{ tech }}</span>
20
+ {% endfor %}
21
+ </div>
22
+ {% endif %}
23
+ <p>{{ project.excerpt }}</p>
24
+ </div>
25
+ {% endfor %}
26
+ </div>
package/index.html ADDED
@@ -0,0 +1,45 @@
1
+ ---
2
+ layout: default
3
+ title: Home
4
+ lang: ja
5
+ ---
6
+
7
+ {% include i18n.html %}
8
+
9
+ <section class="hero">
10
+ <h1>{{ t.home.hero_title }}</h1>
11
+ <p>{{ t.home.hero_description }}</p>
12
+ </section>
13
+
14
+ <section class="intro">
15
+ <h2>{{ t.home.about_title }}</h2>
16
+ <p>{{ t.home.about_description }}</p>
17
+ </section>
18
+
19
+ <section class="projects-preview">
20
+ <h2>{{ t.home.featured_projects }}</h2>
21
+ <div class="project-grid">
22
+ {% for project in site.projects limit:3 %}
23
+ <div class="project-card">
24
+ <h3><a href="{{ project.url | relative_url }}">{{ project.title }}</a></h3>
25
+ <p>{{ project.excerpt }}</p>
26
+ </div>
27
+ {% endfor %}
28
+ </div>
29
+ <a href="{{ "/projects/" | relative_url }}" class="btn">{{ t.home.view_all_projects }}</a>
30
+ </section>
31
+
32
+ <section class="blog-preview">
33
+ <h2>{{ t.home.latest_posts }}</h2>
34
+ <ul class="post-list">
35
+ {% assign ja_posts = site.posts | where: "lang", "ja" %}
36
+ {% for post in ja_posts limit:3 %}
37
+ <li>
38
+ <h3><a href="{{ post.url | relative_url }}">{{ post.title }}</a></h3>
39
+ <p class="post-meta">{{ post.date | date: "%Y年%m月%d日" }}</p>
40
+ <p>{{ post.excerpt }}</p>
41
+ </li>
42
+ {% endfor %}
43
+ </ul>
44
+ <a href="{{ "/blog/" | relative_url }}" class="btn">{{ t.home.view_all_posts }}</a>
45
+ </section>
package/index.js ADDED
@@ -0,0 +1,321 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { Command } = require('commander');
4
+ const chalk = require('chalk');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const matter = require('gray-matter');
8
+ const { unified } = require('unified');
9
+ const remarkParse = require('remark-parse');
10
+ const remarkGfm = require('remark-gfm');
11
+ const remarkStringify = require('remark-stringify');
12
+
13
+ const program = new Command();
14
+
15
+ // Process markdown for terminal display
16
+ function processMarkdown(markdown) {
17
+ // Remove Liquid template tags
18
+ let processed = markdown.replace(/\{%.*?%\}/g, '');
19
+ processed = processed.replace(/\{\{.*?\}\}/g, '');
20
+
21
+ // Split into lines for processing
22
+ const lines = processed.split('\n');
23
+ const output = [];
24
+
25
+ let inCodeBlock = false;
26
+ let codeBlockLang = '';
27
+
28
+ for (let line of lines) {
29
+ // Code blocks
30
+ if (line.startsWith('```')) {
31
+ inCodeBlock = !inCodeBlock;
32
+ if (inCodeBlock) {
33
+ codeBlockLang = line.substring(3);
34
+ output.push(chalk.gray('┌─ ' + (codeBlockLang || 'code')));
35
+ } else {
36
+ output.push(chalk.gray('└─'));
37
+ }
38
+ continue;
39
+ }
40
+
41
+ if (inCodeBlock) {
42
+ output.push(chalk.cyan('│ ' + line));
43
+ continue;
44
+ }
45
+
46
+ // Headers
47
+ if (line.startsWith('#### ')) {
48
+ output.push('\n' + chalk.yellow.bold(line.substring(5)));
49
+ continue;
50
+ }
51
+ if (line.startsWith('### ')) {
52
+ output.push('\n' + chalk.green.bold(line.substring(4)));
53
+ continue;
54
+ }
55
+ if (line.startsWith('## ')) {
56
+ output.push('\n' + chalk.cyan.bold(line.substring(3)));
57
+ continue;
58
+ }
59
+ if (line.startsWith('# ')) {
60
+ output.push('\n' + chalk.magenta.bold(line.substring(2)));
61
+ continue;
62
+ }
63
+
64
+ // Lists
65
+ if (line.match(/^[\s]*[-*+]\s/)) {
66
+ const indent = line.match(/^[\s]*/)[0].length;
67
+ const content = line.replace(/^[\s]*[-*+]\s/, '');
68
+ output.push(' '.repeat(Math.floor(indent / 2)) + chalk.green('• ') + content);
69
+ continue;
70
+ }
71
+
72
+ // Ordered lists
73
+ if (line.match(/^[\s]*\d+\.\s/)) {
74
+ const match = line.match(/^([\s]*)(\d+)\.\s(.*)$/);
75
+ if (match) {
76
+ const [, indent, num, content] = match;
77
+ output.push(' '.repeat(Math.floor(indent.length / 2)) + chalk.yellow(num + '. ') + content);
78
+ continue;
79
+ }
80
+ }
81
+
82
+ // Blockquotes
83
+ if (line.startsWith('> ')) {
84
+ output.push(chalk.gray('┃ ') + chalk.italic(line.substring(2)));
85
+ continue;
86
+ }
87
+
88
+ // Horizontal rules
89
+ if (line.match(/^[-*_]{3,}$/)) {
90
+ output.push(chalk.gray('─'.repeat(50)));
91
+ continue;
92
+ }
93
+
94
+ // Links - simple format [text](url)
95
+ line = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, url) => {
96
+ return chalk.blue.underline(text) + chalk.gray(' (' + url + ')');
97
+ });
98
+
99
+ // Bold
100
+ line = line.replace(/\*\*([^*]+)\*\*/g, (match, text) => chalk.bold(text));
101
+ line = line.replace(/__([^_]+)__/g, (match, text) => chalk.bold(text));
102
+
103
+ // Italic
104
+ line = line.replace(/\*([^*]+)\*/g, (match, text) => chalk.italic(text));
105
+ line = line.replace(/_([^_]+)_/g, (match, text) => chalk.italic(text));
106
+
107
+ // Inline code
108
+ line = line.replace(/`([^`]+)`/g, (match, code) => chalk.cyan.bgBlack(' ' + code + ' '));
109
+
110
+ // Images - just show alt text
111
+ line = line.replace(/!\[([^\]]*)\]\([^)]+\)/g, (match, alt) => {
112
+ return chalk.gray('🖼️ ' + (alt || 'Image'));
113
+ });
114
+
115
+ // Skip empty HTML iframes
116
+ if (line.match(/^<iframe/)) {
117
+ continue;
118
+ }
119
+
120
+ output.push(line);
121
+ }
122
+
123
+ return output.join('\n');
124
+ }
125
+
126
+ // Determine language based on options
127
+ function getLang(options) {
128
+ return options.lang || 'en';
129
+ }
130
+
131
+ // Read and display about.md
132
+ function showAbout(options) {
133
+ const lang = getLang(options);
134
+ const aboutPath = lang === 'ja'
135
+ ? path.join(__dirname, 'about.md')
136
+ : path.join(__dirname, 'en', 'about.md');
137
+
138
+ if (!fs.existsSync(aboutPath)) {
139
+ console.error(chalk.red(`About file not found: ${aboutPath}`));
140
+ process.exit(1);
141
+ }
142
+
143
+ const content = fs.readFileSync(aboutPath, 'utf8');
144
+ const { data, content: markdown } = matter(content);
145
+
146
+ console.log(chalk.cyan.bold('\n' + data.title + '\n'));
147
+ console.log(processMarkdown(markdown));
148
+ }
149
+
150
+ // List projects
151
+ function listProjects(options) {
152
+ const lang = getLang(options);
153
+ const projectsDir = lang === 'ja'
154
+ ? path.join(__dirname, '_projects')
155
+ : path.join(__dirname, '_projects_en');
156
+
157
+ if (!fs.existsSync(projectsDir)) {
158
+ console.error(chalk.red(`Projects directory not found: ${projectsDir}`));
159
+ process.exit(1);
160
+ }
161
+
162
+ const files = fs.readdirSync(projectsDir).filter(f => f.endsWith('.md'));
163
+
164
+ console.log(chalk.cyan.bold('\n📁 Projects\n'));
165
+
166
+ files.forEach(file => {
167
+ const filePath = path.join(projectsDir, file);
168
+ const content = fs.readFileSync(filePath, 'utf8');
169
+ const { data } = matter(content);
170
+ const slug = file.replace('.md', '');
171
+
172
+ console.log(chalk.green(' • ') + chalk.bold(data.title) + chalk.gray(` (${slug})`));
173
+ if (data.description) {
174
+ console.log(chalk.gray(' ' + data.description));
175
+ }
176
+ });
177
+
178
+ console.log();
179
+ }
180
+
181
+ // Show project detail
182
+ function showProject(slug, options) {
183
+ const lang = getLang(options);
184
+ const projectsDir = lang === 'ja'
185
+ ? path.join(__dirname, '_projects')
186
+ : path.join(__dirname, '_projects_en');
187
+
188
+ const projectPath = path.join(projectsDir, `${slug}.md`);
189
+
190
+ if (!fs.existsSync(projectPath)) {
191
+ console.error(chalk.red(`Project not found: ${slug}`));
192
+ console.log(chalk.yellow('\nAvailable projects:'));
193
+ listProjects(options);
194
+ process.exit(1);
195
+ }
196
+
197
+ const content = fs.readFileSync(projectPath, 'utf8');
198
+ const { data, content: markdown } = matter(content);
199
+
200
+ console.log(chalk.cyan.bold('\n' + data.title + '\n'));
201
+
202
+ if (data.description) {
203
+ console.log(chalk.gray(data.description) + '\n');
204
+ }
205
+
206
+ if (data.tech) {
207
+ console.log(chalk.yellow('Tech: ') + data.tech.join(', ') + '\n');
208
+ }
209
+
210
+ if (data.github) {
211
+ console.log(chalk.blue('GitHub: ') + data.github + '\n');
212
+ }
213
+
214
+ console.log(processMarkdown(markdown));
215
+ }
216
+
217
+ // List blog posts
218
+ function listBlogPosts(options) {
219
+ const lang = getLang(options);
220
+ const postsDir = lang === 'ja'
221
+ ? path.join(__dirname, '_posts')
222
+ : path.join(__dirname, '_posts', 'en');
223
+
224
+ if (!fs.existsSync(postsDir)) {
225
+ console.error(chalk.red(`Posts directory not found: ${postsDir}`));
226
+ process.exit(1);
227
+ }
228
+
229
+ const files = fs.readdirSync(postsDir)
230
+ .filter(f => f.endsWith('.md'))
231
+ .sort()
232
+ .reverse();
233
+
234
+ console.log(chalk.cyan.bold('\n📝 Blog Posts\n'));
235
+
236
+ files.forEach(file => {
237
+ const filePath = path.join(postsDir, file);
238
+ const content = fs.readFileSync(filePath, 'utf8');
239
+ const { data } = matter(content);
240
+
241
+ console.log(chalk.green(' • ') + chalk.bold(data.title) + chalk.gray(` (${file.replace('.md', '')})`));
242
+ if (data.date) {
243
+ console.log(chalk.gray(' ' + new Date(data.date).toLocaleDateString()));
244
+ }
245
+ });
246
+
247
+ console.log();
248
+ }
249
+
250
+ // Show blog post detail
251
+ function showBlogPost(id, options) {
252
+ const lang = getLang(options);
253
+ const postsDir = lang === 'ja'
254
+ ? path.join(__dirname, '_posts')
255
+ : path.join(__dirname, '_posts', 'en');
256
+
257
+ const postPath = path.join(postsDir, `${id}.md`);
258
+
259
+ if (!fs.existsSync(postPath)) {
260
+ console.error(chalk.red(`Blog post not found: ${id}`));
261
+ console.log(chalk.yellow('\nAvailable posts:'));
262
+ listBlogPosts(options);
263
+ process.exit(1);
264
+ }
265
+
266
+ const content = fs.readFileSync(postPath, 'utf8');
267
+ const { data, content: markdown } = matter(content);
268
+
269
+ console.log(chalk.cyan.bold('\n' + data.title + '\n'));
270
+
271
+ if (data.date) {
272
+ console.log(chalk.gray('Published: ' + new Date(data.date).toLocaleDateString()) + '\n');
273
+ }
274
+
275
+ console.log(processMarkdown(markdown));
276
+ }
277
+
278
+ // Setup CLI
279
+ program
280
+ .name('goofmint')
281
+ .description('CLI tool for goofmint.dev portfolio')
282
+ .version('1.0.0')
283
+ .option('-l, --lang <language>', 'Language (ja or en)', 'en');
284
+
285
+ // Default command (about)
286
+ program
287
+ .action((options) => {
288
+ showAbout(options);
289
+ });
290
+
291
+ // Projects command
292
+ program
293
+ .command('projects [slug]')
294
+ .description('List projects or show project detail')
295
+ .action((slug, options) => {
296
+ const parentOptions = program.opts();
297
+ const combinedOptions = { ...parentOptions, ...options };
298
+
299
+ if (slug) {
300
+ showProject(slug, combinedOptions);
301
+ } else {
302
+ listProjects(combinedOptions);
303
+ }
304
+ });
305
+
306
+ // Blog command
307
+ program
308
+ .command('blog [id]')
309
+ .description('List blog posts or show post detail')
310
+ .action((id, options) => {
311
+ const parentOptions = program.opts();
312
+ const combinedOptions = { ...parentOptions, ...options };
313
+
314
+ if (id) {
315
+ showBlogPost(id, combinedOptions);
316
+ } else {
317
+ listBlogPosts(combinedOptions);
318
+ }
319
+ });
320
+
321
+ program.parse(process.argv);
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "goofmint",
3
+ "version": "1.0.0",
4
+ "description": "個人ポートフォリオサイト",
5
+ "homepage": "https://github.com/goofmint/goofmint.dev#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/goofmint/goofmint.dev/issues"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/goofmint/goofmint.dev.git"
12
+ },
13
+ "bin": {
14
+ "goofmint": "index.js"
15
+ },
16
+ "license": "MIT",
17
+ "author": "Atsushi Nakatsugawa",
18
+ "type": "commonjs",
19
+ "main": "index.js",
20
+ "scripts": {
21
+ "test": "echo \"Error: no test specified\" && exit 1",
22
+ "start": "bundle exec jekyll serve",
23
+ "build": "bundle exec jekyll build",
24
+ "deploy": "npm run build & wrangler pages publish ./_site --project-name=goofmint-dev --branch=main"
25
+ },
26
+ "assets": {
27
+ "directory": "./_site"
28
+ },
29
+ "dependencies": {
30
+ "chalk": "^4.1.2",
31
+ "commander": "^12.1.0",
32
+ "gray-matter": "^4.0.3",
33
+ "remark": "^15.0.1",
34
+ "remark-gfm": "^4.0.0",
35
+ "strip-markdown": "^6.0.0",
36
+ "remark-parse": "^11.0.0",
37
+ "remark-stringify": "^11.0.0",
38
+ "unified": "^11.0.4"
39
+ },
40
+ "devDependencies": {
41
+ "wrangler": "^4.54.0"
42
+ }
43
+ }
package/projects.html ADDED
@@ -0,0 +1,29 @@
1
+ ---
2
+ layout: default
3
+ title: Projects
4
+ permalink: /projects/
5
+ lang: ja
6
+ ---
7
+
8
+ {% include i18n.html %}
9
+
10
+ <h1>{{ t.projects.title }}</h1>
11
+
12
+ <div class="project-grid">
13
+ {% for project in site.projects %}
14
+ <div class="project-card">
15
+ <h2><a href="{{ project.url | relative_url }}">{{ project.title }}</a></h2>
16
+ {% if project.tech %}
17
+ <div class="project-tech">
18
+ {% if project.status %}
19
+ <span class="tech-tag status-{{ project.status | downcase }}">{{ project.status }}</span>
20
+ {% endif %}
21
+ {% for tech in project.tech %}
22
+ <span class="tech-tag">{{ tech }}</span>
23
+ {% endfor %}
24
+ </div>
25
+ {% endif %}
26
+ <p>{{ project.excerpt }}</p>
27
+ </div>
28
+ {% endfor %}
29
+ </div>