spectrum-er 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.
- checksums.yaml +7 -0
- data/.htaccess +34 -0
- data/LICENSE.txt +21 -0
- data/README.md +330 -0
- data/_config.yml +340 -0
- data/_data/navigation.yml +38 -0
- data/_includes/content-card.html +30 -0
- data/_includes/gallery-track.html +97 -0
- data/_includes/photo-card.html +23 -0
- data/_includes/section-header.html +12 -0
- data/_includes/site-navigation.html +33 -0
- data/_includes/structured-data.html +22 -0
- data/_includes/tech-bite-card.html +52 -0
- data/_layouts/about.html +63 -0
- data/_layouts/default.html +210 -0
- data/_layouts/films-list.html +27 -0
- data/_layouts/tech-bite-list.html +105 -0
- data/_layouts/tech-bite.html +60 -0
- data/_sass/_base.scss +298 -0
- data/_sass/_color-variables.scss +262 -0
- data/_sass/_components.scss +519 -0
- data/_sass/_layouts.scss +820 -0
- data/_sass/_sections.scss +163 -0
- data/_sass/_utilities.scss +301 -0
- data/assets/css/main.scss +21 -0
- data/assets/images/films/IMG_2112.JPG +0 -0
- data/assets/images/films/IMG_8987.jpg +0 -0
- data/assets/images/films/IMG_8998.jpg +0 -0
- data/assets/images/films/IMG_9270.jpg +0 -0
- data/assets/images/films/IMG_9504.jpg +0 -0
- data/assets/images/films/IMG_9570.jpg +0 -0
- data/assets/js/theme-manager.js +271 -0
- metadata +150 -0
@@ -0,0 +1,271 @@
|
|
1
|
+
/**
|
2
|
+
* Theme Manager
|
3
|
+
* Handles theme switching, color customization, and user preferences
|
4
|
+
*/
|
5
|
+
|
6
|
+
class ThemeManager {
|
7
|
+
constructor() {
|
8
|
+
this.storageKey = 'jekyll-terminal-theme';
|
9
|
+
this.colorPickerKey = 'jekyll-terminal-colors';
|
10
|
+
this.defaultTheme = {
|
11
|
+
primary: '#3498db',
|
12
|
+
secondary: '#2c3e50',
|
13
|
+
accent: '#e74c3c',
|
14
|
+
background: '#ffffff',
|
15
|
+
text: '#2c3e50',
|
16
|
+
lightText: '#7f8c8d',
|
17
|
+
darkMode: false
|
18
|
+
};
|
19
|
+
|
20
|
+
this.init();
|
21
|
+
}
|
22
|
+
|
23
|
+
init() {
|
24
|
+
this.loadTheme();
|
25
|
+
this.setupEventListeners();
|
26
|
+
this.applyTheme();
|
27
|
+
}
|
28
|
+
|
29
|
+
setupEventListeners() {
|
30
|
+
// Dark mode toggle
|
31
|
+
const darkModeToggle = document.querySelector('.dark-mode-toggle');
|
32
|
+
if (darkModeToggle) {
|
33
|
+
darkModeToggle.addEventListener('click', () => {
|
34
|
+
this.toggleDarkMode();
|
35
|
+
});
|
36
|
+
}
|
37
|
+
|
38
|
+
|
39
|
+
// Listen for system theme changes
|
40
|
+
if (window.matchMedia) {
|
41
|
+
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
42
|
+
mediaQuery.addEventListener('change', (e) => {
|
43
|
+
if (!this.hasCustomTheme()) {
|
44
|
+
this.setSystemTheme(e.matches);
|
45
|
+
}
|
46
|
+
});
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
loadTheme() {
|
51
|
+
const savedTheme = localStorage.getItem(this.storageKey);
|
52
|
+
const savedColors = localStorage.getItem(this.colorPickerKey);
|
53
|
+
|
54
|
+
if (savedTheme) {
|
55
|
+
this.currentTheme = { ...this.defaultTheme, ...JSON.parse(savedTheme) };
|
56
|
+
} else {
|
57
|
+
this.currentTheme = { ...this.defaultTheme };
|
58
|
+
}
|
59
|
+
|
60
|
+
if (savedColors) {
|
61
|
+
const colors = JSON.parse(savedColors);
|
62
|
+
this.currentTheme = { ...this.currentTheme, ...colors };
|
63
|
+
}
|
64
|
+
|
65
|
+
// Check for system preference if no custom theme
|
66
|
+
if (!this.hasCustomTheme() && window.matchMedia) {
|
67
|
+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
68
|
+
this.currentTheme.darkMode = prefersDark;
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
72
|
+
saveTheme() {
|
73
|
+
localStorage.setItem(this.storageKey, JSON.stringify(this.currentTheme));
|
74
|
+
}
|
75
|
+
|
76
|
+
saveColors(colors) {
|
77
|
+
localStorage.setItem(this.colorPickerKey, JSON.stringify(colors));
|
78
|
+
}
|
79
|
+
|
80
|
+
hasCustomTheme() {
|
81
|
+
return localStorage.getItem(this.storageKey) !== null;
|
82
|
+
}
|
83
|
+
|
84
|
+
applyTheme() {
|
85
|
+
const root = document.documentElement;
|
86
|
+
|
87
|
+
// Apply color variables
|
88
|
+
root.style.setProperty('--primary-color', this.currentTheme.primary);
|
89
|
+
root.style.setProperty('--secondary-color', this.currentTheme.secondary);
|
90
|
+
root.style.setProperty('--accent-color', this.currentTheme.accent);
|
91
|
+
root.style.setProperty('--bg-color', this.currentTheme.background);
|
92
|
+
root.style.setProperty('--text-color', this.currentTheme.text);
|
93
|
+
root.style.setProperty('--light-text-color', this.currentTheme.lightText);
|
94
|
+
|
95
|
+
// Apply dark mode
|
96
|
+
if (this.currentTheme.darkMode) {
|
97
|
+
document.body.classList.add('dark-mode');
|
98
|
+
} else {
|
99
|
+
document.body.classList.remove('dark-mode');
|
100
|
+
}
|
101
|
+
|
102
|
+
// Update theme color meta tag
|
103
|
+
const themeColorMeta = document.querySelector('meta[name="theme-color"]');
|
104
|
+
if (themeColorMeta) {
|
105
|
+
themeColorMeta.setAttribute('content', this.currentTheme.primary);
|
106
|
+
}
|
107
|
+
|
108
|
+
// Update dark mode toggle state
|
109
|
+
this.updateDarkModeToggle();
|
110
|
+
}
|
111
|
+
|
112
|
+
updateTheme(newColors) {
|
113
|
+
this.currentTheme = { ...this.currentTheme, ...newColors };
|
114
|
+
this.saveTheme();
|
115
|
+
this.saveColors(newColors);
|
116
|
+
this.applyTheme();
|
117
|
+
}
|
118
|
+
|
119
|
+
toggleDarkMode() {
|
120
|
+
this.currentTheme.darkMode = !this.currentTheme.darkMode;
|
121
|
+
this.saveTheme();
|
122
|
+
this.applyTheme();
|
123
|
+
}
|
124
|
+
|
125
|
+
setSystemTheme(isDark) {
|
126
|
+
this.currentTheme.darkMode = isDark;
|
127
|
+
this.saveTheme();
|
128
|
+
this.applyTheme();
|
129
|
+
}
|
130
|
+
|
131
|
+
updateDarkModeToggle() {
|
132
|
+
const darkModeToggle = document.querySelector('.dark-mode-toggle');
|
133
|
+
if (darkModeToggle) {
|
134
|
+
const sunIcon = darkModeToggle.querySelector('.icon-sun');
|
135
|
+
const moonIcon = darkModeToggle.querySelector('.icon-moon');
|
136
|
+
|
137
|
+
if (this.currentTheme.darkMode) {
|
138
|
+
sunIcon.style.display = 'none';
|
139
|
+
moonIcon.style.display = 'inline';
|
140
|
+
} else {
|
141
|
+
sunIcon.style.display = 'inline';
|
142
|
+
moonIcon.style.display = 'none';
|
143
|
+
}
|
144
|
+
}
|
145
|
+
}
|
146
|
+
|
147
|
+
|
148
|
+
resetTheme() {
|
149
|
+
this.currentTheme = { ...this.defaultTheme };
|
150
|
+
this.saveTheme();
|
151
|
+
localStorage.removeItem(this.colorPickerKey);
|
152
|
+
this.applyTheme();
|
153
|
+
}
|
154
|
+
|
155
|
+
// Public API methods
|
156
|
+
getCurrentTheme() {
|
157
|
+
return { ...this.currentTheme };
|
158
|
+
}
|
159
|
+
|
160
|
+
setPrimaryColor(color) {
|
161
|
+
this.updateTheme({ primary: color });
|
162
|
+
}
|
163
|
+
|
164
|
+
setDarkMode(enabled) {
|
165
|
+
this.updateTheme({ darkMode: enabled });
|
166
|
+
}
|
167
|
+
|
168
|
+
// Animation utilities
|
169
|
+
animateColorChange(element, property, newValue, duration = 300) {
|
170
|
+
const startValue = getComputedStyle(element).getPropertyValue(property);
|
171
|
+
const startTime = performance.now();
|
172
|
+
|
173
|
+
const animate = (currentTime) => {
|
174
|
+
const elapsed = currentTime - startTime;
|
175
|
+
const progress = Math.min(elapsed / duration, 1);
|
176
|
+
|
177
|
+
// Simple linear interpolation (could be improved with easing)
|
178
|
+
const currentValue = this.interpolateColor(startValue, newValue, progress);
|
179
|
+
element.style.setProperty(property, currentValue);
|
180
|
+
|
181
|
+
if (progress < 1) {
|
182
|
+
requestAnimationFrame(animate);
|
183
|
+
}
|
184
|
+
};
|
185
|
+
|
186
|
+
requestAnimationFrame(animate);
|
187
|
+
}
|
188
|
+
|
189
|
+
interpolateColor(color1, color2, factor) {
|
190
|
+
// Simple color interpolation (works for hex colors)
|
191
|
+
// This is a basic implementation - for production, consider using a proper color library
|
192
|
+
const hex1 = color1.replace('#', '');
|
193
|
+
const hex2 = color2.replace('#', '');
|
194
|
+
|
195
|
+
const r1 = parseInt(hex1.substr(0, 2), 16);
|
196
|
+
const g1 = parseInt(hex1.substr(2, 2), 16);
|
197
|
+
const b1 = parseInt(hex1.substr(4, 2), 16);
|
198
|
+
|
199
|
+
const r2 = parseInt(hex2.substr(0, 2), 16);
|
200
|
+
const g2 = parseInt(hex2.substr(2, 2), 16);
|
201
|
+
const b2 = parseInt(hex2.substr(4, 2), 16);
|
202
|
+
|
203
|
+
const r = Math.round(r1 + (r2 - r1) * factor);
|
204
|
+
const g = Math.round(g1 + (g2 - g1) * factor);
|
205
|
+
const b = Math.round(b1 + (b2 - b1) * factor);
|
206
|
+
|
207
|
+
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
|
208
|
+
}
|
209
|
+
}
|
210
|
+
|
211
|
+
// Utility functions
|
212
|
+
const ColorUtils = {
|
213
|
+
hexToRgb(hex) {
|
214
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
215
|
+
return result ? {
|
216
|
+
r: parseInt(result[1], 16),
|
217
|
+
g: parseInt(result[2], 16),
|
218
|
+
b: parseInt(result[3], 16)
|
219
|
+
} : null;
|
220
|
+
},
|
221
|
+
|
222
|
+
rgbToHex(r, g, b) {
|
223
|
+
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
|
224
|
+
},
|
225
|
+
|
226
|
+
isValidHex(hex) {
|
227
|
+
return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(hex);
|
228
|
+
},
|
229
|
+
|
230
|
+
getContrastColor(hex) {
|
231
|
+
const rgb = this.hexToRgb(hex);
|
232
|
+
if (!rgb) return '#000000';
|
233
|
+
|
234
|
+
const brightness = (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
|
235
|
+
return brightness > 128 ? '#000000' : '#ffffff';
|
236
|
+
},
|
237
|
+
|
238
|
+
lightenColor(hex, percent) {
|
239
|
+
const rgb = this.hexToRgb(hex);
|
240
|
+
if (!rgb) return hex;
|
241
|
+
|
242
|
+
const factor = percent / 100;
|
243
|
+
const r = Math.min(255, Math.round(rgb.r + (255 - rgb.r) * factor));
|
244
|
+
const g = Math.min(255, Math.round(rgb.g + (255 - rgb.g) * factor));
|
245
|
+
const b = Math.min(255, Math.round(rgb.b + (255 - rgb.b) * factor));
|
246
|
+
|
247
|
+
return this.rgbToHex(r, g, b);
|
248
|
+
},
|
249
|
+
|
250
|
+
darkenColor(hex, percent) {
|
251
|
+
const rgb = this.hexToRgb(hex);
|
252
|
+
if (!rgb) return hex;
|
253
|
+
|
254
|
+
const factor = percent / 100;
|
255
|
+
const r = Math.max(0, Math.round(rgb.r * (1 - factor)));
|
256
|
+
const g = Math.max(0, Math.round(rgb.g * (1 - factor)));
|
257
|
+
const b = Math.max(0, Math.round(rgb.b * (1 - factor)));
|
258
|
+
|
259
|
+
return this.rgbToHex(r, g, b);
|
260
|
+
}
|
261
|
+
};
|
262
|
+
|
263
|
+
// Initialize theme manager when DOM is loaded
|
264
|
+
document.addEventListener('DOMContentLoaded', () => {
|
265
|
+
window.themeManager = new ThemeManager();
|
266
|
+
});
|
267
|
+
|
268
|
+
// Export for module systems
|
269
|
+
if (typeof module !== 'undefined' && module.exports) {
|
270
|
+
module.exports = { ThemeManager, ColorUtils };
|
271
|
+
}
|
metadata
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: spectrum-er
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Duhyeon Kim
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-09-16 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: jekyll
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '4.3'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '4.3'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: jekyll-feed
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0.12'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0.12'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: jekyll-sitemap
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '1.4'
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.4'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: jekyll-seo-tag
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '2.8'
|
61
|
+
type: :runtime
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '2.8'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: bundler
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '2.0'
|
75
|
+
type: :development
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '2.0'
|
82
|
+
description: Spectrum is a modern Jekyll theme designed for developers and tech enthusiasts.
|
83
|
+
Features include tech bite sections, responsive design, dark mode support, and clean
|
84
|
+
typography optimized for readability and performance.
|
85
|
+
email:
|
86
|
+
- kdhluck@naver.com
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".htaccess"
|
92
|
+
- LICENSE.txt
|
93
|
+
- README.md
|
94
|
+
- _config.yml
|
95
|
+
- _data/navigation.yml
|
96
|
+
- _includes/content-card.html
|
97
|
+
- _includes/gallery-track.html
|
98
|
+
- _includes/photo-card.html
|
99
|
+
- _includes/section-header.html
|
100
|
+
- _includes/site-navigation.html
|
101
|
+
- _includes/structured-data.html
|
102
|
+
- _includes/tech-bite-card.html
|
103
|
+
- _layouts/about.html
|
104
|
+
- _layouts/default.html
|
105
|
+
- _layouts/films-list.html
|
106
|
+
- _layouts/tech-bite-list.html
|
107
|
+
- _layouts/tech-bite.html
|
108
|
+
- _sass/_base.scss
|
109
|
+
- _sass/_color-variables.scss
|
110
|
+
- _sass/_components.scss
|
111
|
+
- _sass/_layouts.scss
|
112
|
+
- _sass/_sections.scss
|
113
|
+
- _sass/_utilities.scss
|
114
|
+
- assets/css/main.scss
|
115
|
+
- assets/images/films/IMG_2112.JPG
|
116
|
+
- assets/images/films/IMG_8987.jpg
|
117
|
+
- assets/images/films/IMG_8998.jpg
|
118
|
+
- assets/images/films/IMG_9270.jpg
|
119
|
+
- assets/images/films/IMG_9504.jpg
|
120
|
+
- assets/images/films/IMG_9570.jpg
|
121
|
+
- assets/js/theme-manager.js
|
122
|
+
homepage: https://github.com/dudududukim/spectrum
|
123
|
+
licenses:
|
124
|
+
- MIT
|
125
|
+
metadata:
|
126
|
+
bug_tracker_uri: https://github.com/dudududukim/spectrum/issues
|
127
|
+
changelog_uri: https://github.com/dudududukim/spectrum/blob/main/CHANGELOG.md
|
128
|
+
documentation_uri: https://github.com/dudududukim/spectrum/blob/main/README.md
|
129
|
+
homepage_uri: https://github.com/dudududukim/spectrum
|
130
|
+
source_code_uri: https://github.com/dudududukim/spectrum/tree/main
|
131
|
+
funding_uri: https://github.com/sponsors/dudududukim
|
132
|
+
rubygems_mfa_required: 'false'
|
133
|
+
rdoc_options: []
|
134
|
+
require_paths:
|
135
|
+
- lib
|
136
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: 2.7.0
|
141
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
requirements: []
|
147
|
+
rubygems_version: 3.6.3
|
148
|
+
specification_version: 4
|
149
|
+
summary: A clean, minimal Jekyll theme for personal websites with tech content focus
|
150
|
+
test_files: []
|