@mintfolio/theme-verdant 0.2.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 (67) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +66 -0
  3. package/THIRD_PARTY_NOTICES.md +11 -0
  4. package/assets/favicon.ico +0 -0
  5. package/assets/favicon.svg +9 -0
  6. package/assets/fonts/Inter-300.woff2 +0 -0
  7. package/assets/fonts/Inter-400.woff2 +0 -0
  8. package/assets/fonts/Inter-500.woff2 +0 -0
  9. package/assets/fonts/Inter-600.woff2 +0 -0
  10. package/assets/fonts/Inter-700.woff2 +0 -0
  11. package/assets/fonts/JetBrainsMono-400.woff2 +0 -0
  12. package/assets/fonts/JetBrainsMono-500.woff2 +0 -0
  13. package/assets/fonts/PlayfairDisplay-400.woff2 +0 -0
  14. package/assets/fonts/PlayfairDisplay-500.woff2 +0 -0
  15. package/assets/fonts/PlayfairDisplay-600.woff2 +0 -0
  16. package/assets/fonts/PlayfairDisplay-700.woff2 +0 -0
  17. package/components/base/Contact.astro +173 -0
  18. package/components/base/Hero.astro +228 -0
  19. package/components/base/Navigation.astro +288 -0
  20. package/components/base/Projects.astro +207 -0
  21. package/components/base/Skills.astro +158 -0
  22. package/components/blog/ArticleProse.astro +217 -0
  23. package/components/blog/CodeBlockEnhancer.astro +310 -0
  24. package/components/blog/PostCard.astro +98 -0
  25. package/components/blog/ProtectedArticle.astro +89 -0
  26. package/config/theme-verdant.config.mjs +74 -0
  27. package/layouts/Base.astro +460 -0
  28. package/lib/palettes.ts +10 -0
  29. package/licenses/Inter-OFL.txt +92 -0
  30. package/licenses/JetBrainsMono-OFL.txt +93 -0
  31. package/licenses/PlayfairDisplay-OFL.txt +191 -0
  32. package/package.json +68 -0
  33. package/pages/archive.astro +384 -0
  34. package/pages/home.astro +2049 -0
  35. package/pages/page.astro +434 -0
  36. package/pages/post.astro +1029 -0
  37. package/scripts/article.ts +42 -0
  38. package/scripts/articleOrigin.ts +31 -0
  39. package/scripts/articleTables.ts +15 -0
  40. package/scripts/blog.ts +26 -0
  41. package/scripts/codeBlocks.ts +172 -0
  42. package/scripts/hero.ts +173 -0
  43. package/scripts/home.ts +12 -0
  44. package/scripts/homePanels.ts +100 -0
  45. package/scripts/hotContent.ts +16 -0
  46. package/scripts/lifecycle.ts +3 -0
  47. package/scripts/lightbox.ts +14 -0
  48. package/scripts/postFilters.ts +101 -0
  49. package/scripts/postList.ts +132 -0
  50. package/scripts/progress.ts +29 -0
  51. package/scripts/protectedArticle.ts +49 -0
  52. package/scripts/protectedToc.ts +29 -0
  53. package/scripts/site.ts +145 -0
  54. package/scripts/tagFold.ts +61 -0
  55. package/scripts/theme.ts +102 -0
  56. package/scripts/toc.ts +149 -0
  57. package/settings.ts +21 -0
  58. package/styles/fonts.css +120 -0
  59. package/styles/global.css +146 -0
  60. package/styles/themes/happyhues/effects.css +62 -0
  61. package/styles/themes/happyhues/index.css +2 -0
  62. package/styles/themes/happyhues/theme.css +225 -0
  63. package/theme.mjs +60 -0
  64. package/utils/blogNavigation.ts +3 -0
  65. package/utils/homeReturnHint.ts +34 -0
  66. package/utils/storage.ts +16 -0
  67. package/utils/types.ts +8 -0
@@ -0,0 +1,288 @@
1
+ ---
2
+ /**
3
+ * Navigation - Fixed top navigation bar with mobile hamburger menu.
4
+ *
5
+ * Features:
6
+ * - Desktop: Horizontal link list with hover underline animation
7
+ * - Mobile: Hamburger toggle with slide-down menu
8
+ * - Smooth scroll navigation to page sections
9
+ * - Keyboard accessible (Escape closes mobile menu)
10
+ */
11
+ import type { ThemeContext } from '@mintfolio/theme-api';
12
+ import type { VerdantSettings } from '../../settings';
13
+
14
+ /** Public site data supplied by the containing theme page. */
15
+ interface Props { theme: ThemeContext<VerdantSettings>; }
16
+ const { theme } = Astro.props;
17
+
18
+ const navLinks = [
19
+ { href: '#hero', label: '首页' },
20
+ { href: '#skills', label: '技能' },
21
+ { href: '#projects', label: '项目' },
22
+ { href: '#blog', label: '博客' },
23
+ { href: '#contact', label: '联系' },
24
+ ];
25
+ ---
26
+
27
+ <nav class="nav-container glass" role="navigation" aria-label="主导航">
28
+ <div class="nav-inner">
29
+ <!-- Logo -->
30
+ <a href="#hero" class="nav-logo">
31
+ {theme.site.profile.name}
32
+ </a>
33
+
34
+ <!-- Desktop Navigation -->
35
+ <ul class="nav-links">
36
+ {navLinks.map((link) => (
37
+ <li>
38
+ <a href={link.href} class="nav-link">{link.label}</a>
39
+ </li>
40
+ ))}
41
+ </ul>
42
+
43
+ <!-- Mobile Menu Toggle -->
44
+ <button
45
+ class="nav-toggle"
46
+ id="mobile-menu-toggle"
47
+ aria-label="切换菜单"
48
+ aria-expanded="false"
49
+ aria-controls="mobile-menu"
50
+ >
51
+ <span class="hamburger-line" aria-hidden="true"></span>
52
+ <span class="hamburger-line" aria-hidden="true"></span>
53
+ <span class="hamburger-line" aria-hidden="true"></span>
54
+ </button>
55
+ </div>
56
+
57
+ <!-- Mobile Menu -->
58
+ <div id="mobile-menu" class="mobile-menu" aria-hidden="true" aria-labelledby="mobile-menu-toggle">
59
+ <ul class="mobile-nav-links">
60
+ {navLinks.map((link) => (
61
+ <li>
62
+ <a href={link.href} class="mobile-nav-link">{link.label}</a>
63
+ </li>
64
+ ))}
65
+ </ul>
66
+ </div>
67
+ </nav>
68
+
69
+ <style>
70
+ .nav-container {
71
+ position: fixed;
72
+ top: 0;
73
+ left: 0;
74
+ right: 0;
75
+ z-index: 1000;
76
+ border-radius: 0;
77
+ border-top: none;
78
+ border-left: none;
79
+ border-right: none;
80
+ }
81
+
82
+ .nav-inner {
83
+ max-width: var(--max-width);
84
+ margin: 0 auto;
85
+ padding: 1rem 2rem;
86
+ display: flex;
87
+ align-items: center;
88
+ justify-content: space-between;
89
+ }
90
+
91
+ .nav-logo {
92
+ font-size: 1.25rem;
93
+ font-weight: 600;
94
+ color: var(--color-text);
95
+ text-decoration: none;
96
+ transition: color 0.2s ease;
97
+ }
98
+
99
+ .nav-logo:hover {
100
+ color: var(--color-primary);
101
+ }
102
+
103
+ .nav-links {
104
+ display: flex;
105
+ align-items: center;
106
+ gap: 2rem;
107
+ list-style: none;
108
+ margin: 0;
109
+ padding: 0;
110
+ }
111
+
112
+ .nav-link {
113
+ color: var(--color-text-muted);
114
+ text-decoration: none;
115
+ font-size: 0.9375rem;
116
+ font-weight: 500;
117
+ transition: color 0.2s ease;
118
+ position: relative;
119
+ }
120
+
121
+ .nav-link:hover {
122
+ color: var(--color-text);
123
+ }
124
+
125
+ .nav-link::after {
126
+ content: '';
127
+ position: absolute;
128
+ bottom: -4px;
129
+ left: 0;
130
+ width: 0;
131
+ height: 2px;
132
+ background: var(--gradient-primary);
133
+ transition: width 0.2s ease;
134
+ }
135
+
136
+ .nav-link:hover::after {
137
+ width: 100%;
138
+ }
139
+
140
+ .nav-toggle {
141
+ display: none;
142
+ flex-direction: column;
143
+ justify-content: center;
144
+ gap: 5px;
145
+ width: 32px;
146
+ height: 32px;
147
+ background: none;
148
+ border: none;
149
+ cursor: pointer;
150
+ padding: 0;
151
+ }
152
+
153
+ .hamburger-line {
154
+ display: block;
155
+ width: 100%;
156
+ height: 2px;
157
+ background-color: var(--color-text);
158
+ transition: transform 0.3s ease, opacity 0.3s ease;
159
+ }
160
+
161
+ .nav-toggle[aria-expanded="true"] .hamburger-line:nth-child(1) {
162
+ transform: translateY(7px) rotate(45deg);
163
+ }
164
+
165
+ .nav-toggle[aria-expanded="true"] .hamburger-line:nth-child(2) {
166
+ opacity: 0;
167
+ }
168
+
169
+ .nav-toggle[aria-expanded="true"] .hamburger-line:nth-child(3) {
170
+ transform: translateY(-7px) rotate(-45deg);
171
+ }
172
+
173
+ .mobile-menu {
174
+ display: none;
175
+ overflow: hidden;
176
+ max-height: 0;
177
+ transition: max-height 0.3s ease;
178
+ }
179
+
180
+ .mobile-menu.open {
181
+ max-height: 400px;
182
+ }
183
+
184
+ .mobile-nav-links {
185
+ list-style: none;
186
+ margin: 0;
187
+ padding: 0.5rem 2rem 1.5rem;
188
+ display: flex;
189
+ flex-direction: column;
190
+ gap: 0.5rem;
191
+ }
192
+
193
+ .mobile-nav-link {
194
+ display: block;
195
+ color: var(--color-text-muted);
196
+ text-decoration: none;
197
+ font-size: 1rem;
198
+ font-weight: 500;
199
+ padding: 0.75rem 0;
200
+ transition: color 0.2s ease;
201
+ }
202
+
203
+ .mobile-nav-link:hover {
204
+ color: var(--color-text);
205
+ }
206
+
207
+ @media (max-width: 768px) {
208
+ .nav-links {
209
+ display: none;
210
+ }
211
+
212
+ .nav-toggle {
213
+ display: flex;
214
+ }
215
+
216
+ .mobile-menu {
217
+ display: block;
218
+ }
219
+ }
220
+ </style>
221
+
222
+ <script>
223
+ let cleanupNavigation: (() => void) | null = null;
224
+
225
+ function initNavigation() {
226
+ cleanupNavigation?.();
227
+
228
+ const toggle = document.querySelector<HTMLElement>('.nav-toggle');
229
+ const mobileMenu = document.getElementById('mobile-menu');
230
+
231
+ if (!toggle || !mobileMenu) return;
232
+
233
+ const disposers: Array<() => void> = [];
234
+ const addDisposer = (dispose: () => void) => {
235
+ disposers.push(dispose);
236
+ };
237
+
238
+ const closeMenu = () => {
239
+ toggle.setAttribute('aria-expanded', 'false');
240
+ mobileMenu.setAttribute('aria-hidden', 'true');
241
+ mobileMenu.classList.remove('open');
242
+ };
243
+
244
+ const onToggleClick = () => {
245
+ const isExpanded = toggle.getAttribute('aria-expanded') === 'true';
246
+ toggle.setAttribute('aria-expanded', String(!isExpanded));
247
+ mobileMenu.setAttribute('aria-hidden', String(isExpanded));
248
+ mobileMenu.classList.toggle('open');
249
+ };
250
+ toggle.addEventListener('click', onToggleClick);
251
+ addDisposer(() => toggle.removeEventListener('click', onToggleClick));
252
+
253
+ // Close mobile menu when clicking a link
254
+ const mobileLinks = mobileMenu.querySelectorAll('.mobile-nav-link');
255
+ mobileLinks.forEach((link) => {
256
+ link.addEventListener('click', closeMenu);
257
+ addDisposer(() => link.removeEventListener('click', closeMenu));
258
+ });
259
+
260
+ // Close mobile menu on escape key
261
+ const onKeydown = (e: KeyboardEvent) => {
262
+ if (e.key === 'Escape' && mobileMenu.classList.contains('open')) {
263
+ closeMenu();
264
+ }
265
+ };
266
+ document.addEventListener('keydown', onKeydown);
267
+ addDisposer(() => document.removeEventListener('keydown', onKeydown));
268
+
269
+ cleanupNavigation = () => {
270
+ closeMenu();
271
+ disposers.forEach((dispose) => dispose());
272
+ cleanupNavigation = null;
273
+ };
274
+ }
275
+
276
+ const handleNavigationBeforeSwap = () => {
277
+ cleanupNavigation?.();
278
+ };
279
+
280
+ const lifecycleFlag = 'navigationLifecycleBound';
281
+ if (document.documentElement.dataset[lifecycleFlag] !== '1') {
282
+ document.addEventListener('astro:before-swap', handleNavigationBeforeSwap);
283
+ document.addEventListener('astro:page-load', initNavigation);
284
+ document.documentElement.dataset[lifecycleFlag] = '1';
285
+ }
286
+
287
+ initNavigation();
288
+ </script>
@@ -0,0 +1,207 @@
1
+ ---
2
+ /**
3
+ * Projects - Portfolio projects grid section.
4
+ *
5
+ * Displays project cards with:
6
+ * - Title and description
7
+ * - Technology tags
8
+ * - Links to source code (repo) and live demo (link)
9
+ * Responsive grid: 3 columns on large screens, auto-fit otherwise.
10
+ */
11
+ import type { ThemeContext } from '@mintfolio/theme-api';
12
+ import type { VerdantSettings } from '../../settings';
13
+
14
+ /** Public site data supplied by the containing theme page. */
15
+ interface Props { theme: ThemeContext<VerdantSettings>; }
16
+ import type { Project } from '@mintfolio/theme-api';
17
+
18
+ const { theme } = Astro.props;
19
+ const { projects } = theme.site;
20
+ ---
21
+
22
+ <section id="projects" class="projects-section">
23
+ <div class="container">
24
+ <h2 class="section-title gradient-text">项目作品</h2>
25
+
26
+ <div class="projects-grid">
27
+ {projects.map((project: Project) => (
28
+ <article class="project-card glass-card">
29
+ <div class="card-content">
30
+ <h3 class="project-title">{project.title}</h3>
31
+ <p class="project-description">{project.description}</p>
32
+
33
+ <div class="project-tags">
34
+ {project.tags.map((tag: string) => (
35
+ <span class="tag">{tag}</span>
36
+ ))}
37
+ </div>
38
+
39
+ <div class="project-links">
40
+ {project.repo && (
41
+ <a
42
+ href={project.repo}
43
+ class="project-link"
44
+ target="_blank"
45
+ rel="noopener noreferrer"
46
+ aria-label={`查看 ${project.title} 源码`}
47
+ >
48
+ <svg class="link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
49
+ <path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/>
50
+ </svg>
51
+ <span>源码</span>
52
+ </a>
53
+ )}
54
+ {project.link && (
55
+ <a
56
+ href={project.link}
57
+ class="project-link"
58
+ target="_blank"
59
+ rel="noopener noreferrer"
60
+ aria-label={`访问 ${project.title} 在线演示`}
61
+ >
62
+ <svg class="link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
63
+ <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
64
+ <polyline points="15 3 21 3 21 9"/>
65
+ <line x1="10" y1="14" x2="21" y2="3"/>
66
+ </svg>
67
+ <span>演示</span>
68
+ </a>
69
+ )}
70
+ </div>
71
+ </div>
72
+ </article>
73
+ ))}
74
+ </div>
75
+ </div>
76
+ </section>
77
+
78
+ <style>
79
+ .projects-section {
80
+ padding: var(--section-padding, 5rem 0);
81
+ }
82
+
83
+ .container {
84
+ max-width: var(--container-max, 1200px);
85
+ margin: 0 auto;
86
+ padding: 0 var(--container-padding, 1.5rem);
87
+ }
88
+
89
+ .section-title {
90
+ font-size: var(--section-title-size, 2.5rem);
91
+ font-weight: 700;
92
+ text-align: center;
93
+ margin-bottom: var(--section-title-margin, 3rem);
94
+ }
95
+
96
+ .projects-grid {
97
+ display: grid;
98
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 350px), 1fr));
99
+ gap: var(--grid-gap, 1.5rem);
100
+ }
101
+
102
+ .project-card {
103
+ position: relative;
104
+ transition: transform var(--transition-duration, 0.3s) ease, box-shadow var(--transition-duration, 0.3s) ease;
105
+ }
106
+
107
+ .project-card:hover {
108
+ transform: translateY(-4px);
109
+ box-shadow: var(--card-hover-shadow, 0 20px 40px rgba(0, 0, 0, 0.2));
110
+ }
111
+
112
+ .card-content {
113
+ padding: var(--card-padding, 1.5rem);
114
+ display: flex;
115
+ flex-direction: column;
116
+ height: 100%;
117
+ }
118
+
119
+ .project-title {
120
+ font-size: var(--project-title-size, 1.25rem);
121
+ font-weight: 600;
122
+ color: var(--text-primary, #ffffff);
123
+ margin-bottom: 0.75rem;
124
+ }
125
+
126
+ .project-description {
127
+ font-size: var(--project-desc-size, 0.95rem);
128
+ color: var(--text-secondary, rgba(255, 255, 255, 0.75));
129
+ line-height: 1.6;
130
+ margin-bottom: 1rem;
131
+ flex-grow: 1;
132
+ }
133
+
134
+ .project-tags {
135
+ display: flex;
136
+ flex-wrap: wrap;
137
+ gap: 0.5rem;
138
+ margin-bottom: 1.25rem;
139
+ }
140
+
141
+ .tag {
142
+ display: inline-block;
143
+ padding: 0.25rem 0.75rem;
144
+ font-size: var(--tag-size, 0.75rem);
145
+ font-weight: 500;
146
+ color: var(--tag-color, rgba(255, 255, 255, 0.9));
147
+ background: var(--tag-bg, rgba(255, 255, 255, 0.1));
148
+ border: 1px solid var(--tag-border, rgba(255, 255, 255, 0.15));
149
+ border-radius: var(--tag-radius, 9999px);
150
+ transition: background var(--transition-duration, 0.2s) ease;
151
+ }
152
+
153
+ .tag:hover {
154
+ background: var(--tag-bg-hover, rgba(255, 255, 255, 0.2));
155
+ }
156
+
157
+ .project-links {
158
+ display: flex;
159
+ gap: 1rem;
160
+ margin-top: auto;
161
+ padding-top: 1rem;
162
+ border-top: 1px solid var(--border-subtle, rgba(255, 255, 255, 0.1));
163
+ }
164
+
165
+ .project-link {
166
+ display: inline-flex;
167
+ align-items: center;
168
+ gap: 0.5rem;
169
+ font-size: var(--link-size, 0.875rem);
170
+ font-weight: 500;
171
+ color: var(--link-color, rgba(255, 255, 255, 0.8));
172
+ text-decoration: none;
173
+ transition: color var(--transition-duration, 0.2s) ease;
174
+ }
175
+
176
+ .project-link:hover {
177
+ color: var(--link-color-hover, #ffffff);
178
+ }
179
+
180
+ .link-icon {
181
+ width: 1.125rem;
182
+ height: 1.125rem;
183
+ flex-shrink: 0;
184
+ }
185
+
186
+ /* Responsive adjustments */
187
+ @media (max-width: 768px) {
188
+ .projects-section {
189
+ padding: var(--section-padding-mobile, 3rem 0);
190
+ }
191
+
192
+ .section-title {
193
+ font-size: var(--section-title-size-mobile, 2rem);
194
+ margin-bottom: var(--section-title-margin-mobile, 2rem);
195
+ }
196
+
197
+ .projects-grid {
198
+ grid-template-columns: 1fr;
199
+ }
200
+ }
201
+
202
+ @media (min-width: 1024px) {
203
+ .projects-grid {
204
+ grid-template-columns: repeat(3, 1fr);
205
+ }
206
+ }
207
+ </style>
@@ -0,0 +1,158 @@
1
+ ---
2
+ /**
3
+ * Skills - Skills section with progress bar visualization.
4
+ *
5
+ * Displays skills grouped by category (Frontend, Backend, Tools, etc.)
6
+ * with animated progress bars showing proficiency levels (0-100%).
7
+ * Responsive grid: 3 columns on desktop, 2 on tablet, 1 on mobile.
8
+ */
9
+ import type { ThemeContext } from '@mintfolio/theme-api';
10
+ import type { VerdantSettings } from '../../settings';
11
+
12
+ /** Public site data supplied by the containing theme page. */
13
+ interface Props { theme: ThemeContext<VerdantSettings>; }
14
+ import type { SkillCategory } from '@mintfolio/theme-api';
15
+
16
+ const { theme } = Astro.props;
17
+ const skills: SkillCategory[] = theme.site.skills;
18
+ ---
19
+
20
+ <section id="skills" class="skills-section">
21
+ <div class="container">
22
+ <h2 class="section-title gradient-text">技能</h2>
23
+
24
+ <div class="skills-grid">
25
+ {skills.map((category, categoryIndex) => (
26
+ <div class="glass-card skill-category">
27
+ <h3 class="category-name">{category.category}</h3>
28
+
29
+ <div class="skill-list">
30
+ {category.items.map((skill, skillIndex) => (
31
+ <div class="skill-item">
32
+ <div class="skill-header">
33
+ <span class="skill-name">{skill.name}</span>
34
+ <span class="skill-level">{skill.level}%</span>
35
+ </div>
36
+ <div class="progress-track">
37
+ <div
38
+ class="progress-fill"
39
+ style={`--target-width: ${skill.level}%; --delay: ${(categoryIndex * category.items.length + skillIndex) * 0.1}s`}
40
+ ></div>
41
+ </div>
42
+ </div>
43
+ ))}
44
+ </div>
45
+ </div>
46
+ ))}
47
+ </div>
48
+ </div>
49
+ </section>
50
+
51
+ <style>
52
+ .skills-section {
53
+ padding: var(--section-padding, 6rem 2rem);
54
+ position: relative;
55
+ }
56
+
57
+ .container {
58
+ max-width: var(--max-width, 1200px);
59
+ margin: 0 auto;
60
+ }
61
+
62
+ .section-title {
63
+ font-size: 2.5rem;
64
+ font-weight: 700;
65
+ text-align: center;
66
+ margin-bottom: 3rem;
67
+ }
68
+
69
+ .skills-grid {
70
+ display: grid;
71
+ grid-template-columns: repeat(3, 1fr);
72
+ gap: 1.5rem;
73
+ }
74
+
75
+ @media (max-width: 1024px) {
76
+ .skills-grid {
77
+ grid-template-columns: repeat(2, 1fr);
78
+ }
79
+ }
80
+
81
+ @media (max-width: 640px) {
82
+ .skills-grid {
83
+ grid-template-columns: 1fr;
84
+ }
85
+
86
+ .section-title {
87
+ font-size: 2rem;
88
+ }
89
+ }
90
+
91
+ .skill-category {
92
+ padding: 1.5rem;
93
+ }
94
+
95
+ .category-name {
96
+ font-size: 1.25rem;
97
+ font-weight: 600;
98
+ color: var(--color-text, #f8fafc);
99
+ margin-bottom: 1.25rem;
100
+ padding-bottom: 0.75rem;
101
+ border-bottom: 1px solid var(--glass-border, rgba(255, 255, 255, 0.1));
102
+ }
103
+
104
+ .skill-list {
105
+ display: flex;
106
+ flex-direction: column;
107
+ gap: 1rem;
108
+ }
109
+
110
+ .skill-item {
111
+ display: flex;
112
+ flex-direction: column;
113
+ gap: 0.5rem;
114
+ }
115
+
116
+ .skill-header {
117
+ display: flex;
118
+ justify-content: space-between;
119
+ align-items: center;
120
+ }
121
+
122
+ .skill-name {
123
+ font-size: 0.9rem;
124
+ color: var(--color-text, #f8fafc);
125
+ font-weight: 500;
126
+ }
127
+
128
+ .skill-level {
129
+ font-size: 0.8rem;
130
+ color: var(--color-text-muted, #94a3b8);
131
+ font-weight: 500;
132
+ }
133
+
134
+ .progress-track {
135
+ height: 6px;
136
+ background: rgba(255, 255, 255, 0.1);
137
+ border-radius: 3px;
138
+ overflow: hidden;
139
+ }
140
+
141
+ .progress-fill {
142
+ height: 100%;
143
+ width: 0;
144
+ background: linear-gradient(90deg, var(--color-primary, #8b5cf6), var(--color-secondary, #06b6d4));
145
+ border-radius: 3px;
146
+ animation: fillProgress 0.8s ease-out forwards;
147
+ animation-delay: var(--delay, 0s);
148
+ }
149
+
150
+ @keyframes fillProgress {
151
+ from {
152
+ width: 0;
153
+ }
154
+ to {
155
+ width: var(--target-width, 0%);
156
+ }
157
+ }
158
+ </style>