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.
@@ -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: []