create-foliokit 1.0.0 → 1.0.1
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.
- package/README.md +59 -325
- package/package.json +1 -1
- package/templates/default/data.ts +58 -116
- package/templates/default/public/avatar-placeholder.png +0 -0
- package/templates/default/public/experience-logo-placeholder.png +0 -0
- package/templates/default/public/project-placeholder.png +0 -0
- package/templates/default/src/App.tsx +79 -49
- package/templates/default/src/ProjectMagazineGallery.tsx +13 -5
- package/templates/default/src/main.tsx +18 -0
- package/templates/default/src/styles.css +49 -84
- package/templates/default/tests/portfolio.test.js +2 -2
- package/templates/default/types.ts +73 -0
- package/templates/default/.gitignore +0 -20
- package/templates/default/public/avatar-placeholder.svg +0 -10
- package/templates/default/public/footer-heart.png +0 -0
- package/templates/default/public/love-always-wins.png +0 -0
- package/templates/default/public/project-placeholder.svg +0 -10
- package/templates/default/public/stars-foreground-landscape.svg +0 -202
- package/templates/default/public/stars-foreground-portrait.svg +0 -202
- package/templates/default/public/stars-landscape.svg +0 -322
- package/templates/default/public/stars-portrait.svg +0 -322
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
--elevated: #090909;
|
|
16
16
|
--secondary: #0b0b0b;
|
|
17
17
|
--lime: #e5cf9c;
|
|
18
|
+
--social-hover: #00e676;
|
|
18
19
|
--pink: #ec4899;
|
|
19
20
|
--violet: #a78bfa;
|
|
20
21
|
--warm-white: #ffffffde;
|
|
@@ -26,6 +27,8 @@
|
|
|
26
27
|
--frame-dot-hover: #3a352b;
|
|
27
28
|
--content-width: 880px;
|
|
28
29
|
--page-gutter: 16px;
|
|
30
|
+
--grid-size: 40px;
|
|
31
|
+
--grid-line: rgba(255, 255, 255, .18);
|
|
29
32
|
--section-space: 20px;
|
|
30
33
|
--section-space-end: 22px;
|
|
31
34
|
--panel-inset: 12px;
|
|
@@ -44,41 +47,21 @@
|
|
|
44
47
|
|
|
45
48
|
* { box-sizing: border-box; }
|
|
46
49
|
html, body { overflow-x: clip; }
|
|
47
|
-
body { min-width: 320px; margin: 0; color: var(--warm-white); background: #000000; isolation: isolate; }
|
|
50
|
+
body { position: relative; min-width: 320px; margin: 0; color: var(--warm-white); background: #000000; isolation: isolate; }
|
|
48
51
|
body::before {
|
|
49
|
-
position:
|
|
52
|
+
position: absolute;
|
|
50
53
|
z-index: -1;
|
|
51
54
|
inset: 0;
|
|
52
55
|
pointer-events: none;
|
|
53
|
-
background:
|
|
54
|
-
opacity: .9;
|
|
55
|
-
content: '';
|
|
56
|
-
}
|
|
57
|
-
body::after {
|
|
58
|
-
--near-stars: url('/stars-foreground-landscape.svg');
|
|
59
|
-
position: fixed;
|
|
60
|
-
z-index: -1;
|
|
61
|
-
inset: -2%;
|
|
62
|
-
pointer-events: none;
|
|
56
|
+
background-color: var(--obsidian);
|
|
63
57
|
background-image:
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
background-
|
|
69
|
-
background-size: 100% 100%, 100% 100%, 100% 100%, cover;
|
|
70
|
-
background-repeat: no-repeat;
|
|
71
|
-
opacity: .96;
|
|
72
|
-
mix-blend-mode: screen;
|
|
73
|
-
transform-origin: center;
|
|
74
|
-
animation: galaxy-depth 36s ease-in-out infinite alternate;
|
|
75
|
-
will-change: transform;
|
|
58
|
+
linear-gradient(var(--grid-line) 1px, transparent 1px),
|
|
59
|
+
linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
|
|
60
|
+
background-position: center center, center center;
|
|
61
|
+
background-size: var(--grid-size) var(--grid-size);
|
|
62
|
+
background-repeat: repeat;
|
|
76
63
|
content: '';
|
|
77
64
|
}
|
|
78
|
-
@keyframes galaxy-depth {
|
|
79
|
-
from { transform: translate3d(-.45%, .3%, 0) scale(1); }
|
|
80
|
-
to { transform: translate3d(.35%, -.25%, 0) scale(1.025); }
|
|
81
|
-
}
|
|
82
65
|
a { color: inherit; text-decoration: none; transition: color var(--interaction-duration) var(--interaction-ease); }
|
|
83
66
|
a:hover, a:focus-visible { color: var(--lime); }
|
|
84
67
|
button, a { -webkit-tap-highlight-color: transparent; }
|
|
@@ -86,11 +69,12 @@ button:focus-visible, a:focus-visible { outline: 2px solid var(--lime); outline-
|
|
|
86
69
|
h1, h2, h3, p { margin-top: 0; }
|
|
87
70
|
h1, h2, h3 { font-family: var(--heading-font); }
|
|
88
71
|
.mono { font-family: var(--mono-font); }
|
|
89
|
-
.section-wrap { width: min(var(--content-width), calc(100% - var(--page-gutter) - var(--page-gutter))); margin-inline: auto; }
|
|
72
|
+
.section-wrap { width: min(var(--content-width), calc(100% - var(--page-gutter) - var(--page-gutter))); margin-inline: auto; background: var(--obsidian); }
|
|
90
73
|
.dotted-frame { position: relative; }
|
|
91
74
|
.dotted-frame::before { position: absolute; z-index: 0; inset: 0; pointer-events: none; background-image: repeating-linear-gradient(90deg, var(--frame-dot) 0 1px, transparent 1px 5px), repeating-linear-gradient(90deg, var(--frame-dot) 0 1px, transparent 1px 5px), repeating-linear-gradient(0deg, var(--frame-dot) 0 1px, transparent 1px 5px), repeating-linear-gradient(0deg, var(--frame-dot) 0 1px, transparent 1px 5px); background-repeat: no-repeat; background-position: center top, center bottom, left center, right center; background-size: calc(100% - 16px) 1px, calc(100% - 16px) 1px, 1px calc(100% - 16px), 1px calc(100% - 16px); content: ''; transition: background-image .18s ease; }
|
|
92
75
|
.dotted-frame > * { position: relative; z-index: 1; }
|
|
93
76
|
|
|
77
|
+
|
|
94
78
|
.site-header {
|
|
95
79
|
position: sticky;
|
|
96
80
|
top: 0;
|
|
@@ -124,7 +108,6 @@ h1, h2, h3 { font-family: var(--heading-font); }
|
|
|
124
108
|
a.text-interaction:hover, a.text-interaction:focus-visible,
|
|
125
109
|
a:hover .text-interaction, a:focus-visible .text-interaction,
|
|
126
110
|
.project-card:hover .text-interaction, .project-card:focus-within .text-interaction,
|
|
127
|
-
.portfolio-history-row:hover .text-interaction, .portfolio-history-row:focus-within .text-interaction,
|
|
128
111
|
.contact-submit:hover .text-interaction, .contact-submit:focus-visible .text-interaction { color: var(--lime); }
|
|
129
112
|
.main-nav .nav-link.is-active { color: var(--lime); }
|
|
130
113
|
.main-nav .nav-link { transition: color var(--interaction-duration) var(--interaction-ease); }
|
|
@@ -211,13 +194,9 @@ a:not(.social-link):not(.contact-social-link):hover > img, a:not(.social-link):n
|
|
|
211
194
|
.profile-photo-stage { position: relative; width: 100px; height: 96px; flex: 0 0 100px; }
|
|
212
195
|
.profile-photo { position: absolute; top: 6px; left: 8px; z-index: 2; display: block; width: 84px; height: 84px; border: 1px solid color-mix(in srgb, var(--warm-white) 26%, transparent); border-radius: 50%; object-fit: cover; object-position: center; }
|
|
213
196
|
.hero h1 { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-start; gap: 7px; margin: 0; color: var(--warm-white); font: 600 25px/1.15 var(--heading-font); letter-spacing: -.05em; }
|
|
214
|
-
.
|
|
215
|
-
.
|
|
197
|
+
.verified-mark { display: inline-flex; width: 20px; height: 20px; flex: 0 0 20px; align-self: center; align-items: center; justify-content: center; color: #fff; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .45)); }
|
|
198
|
+
.verified-mark svg { display: block; width: 100%; height: 100%; }
|
|
216
199
|
.skill-name, .site-tooltip { padding: 6px 9px; border: 1px dotted var(--frame-dot-hover); border-radius: 4px; color: var(--warm-white); background: var(--elevated); font: 10px/1.3 var(--body-font); font-weight: 400; letter-spacing: 0; text-align: center; white-space: nowrap; box-shadow: 0 4px 14px rgba(0, 0, 0, .16); }
|
|
217
|
-
.heart-message { position: absolute; top: 50%; left: calc(100% + 10px); z-index: 6; width: max-content; max-width: min(190px, calc(100vw - 24px)); padding: 7px 10px; border: 1px dotted var(--frame-dot-hover); border-radius: 4px; color: var(--warm-white); background: var(--elevated); font: 11px/1.35 var(--body-font); letter-spacing: .01em; text-align: center; white-space: nowrap; box-shadow: 0 4px 14px rgba(0, 0, 0, .16); opacity: 0; visibility: hidden; pointer-events: none; transform: translate(-3px, -50%); transition: opacity 180ms var(--interaction-ease), transform 180ms var(--interaction-ease), visibility 180ms; }
|
|
218
|
-
@media (hover: hover) and (pointer: fine) {
|
|
219
|
-
.name-heart-wrap:hover .heart-message { opacity: 1; visibility: visible; transform: translate(0, -50%); }
|
|
220
|
-
}
|
|
221
200
|
.site-tooltip { position: fixed; z-index: 100; max-width: min(220px, calc(100vw - 24px)); overflow: hidden; text-overflow: ellipsis; opacity: 0; pointer-events: none; transform: translate(-50%, -100%) scale(.98); animation: tooltip-enter .16s ease both; }
|
|
222
201
|
.site-tooltip.is-below { transform: translate(-50%, 0) scale(.98); }
|
|
223
202
|
@keyframes tooltip-enter { from { opacity: 0; transform: translate(-50%, -100%) scale(.97); } to { opacity: 1; transform: translate(-50%, -100%) scale(1); } }
|
|
@@ -268,30 +247,36 @@ a:not(.social-link):not(.contact-social-link):hover > img, a:not(.social-link):n
|
|
|
268
247
|
.testimonial-card .portfolio-content-organization { margin-bottom: 0; }
|
|
269
248
|
[hidden] { display: none !important; }
|
|
270
249
|
.portfolio-content-card a { display: inline-block; margin: 2px 10px 2px 0; color: var(--lime); font-size: var(--text-sm); }
|
|
271
|
-
.portfolio-history-panel { padding: 0; }
|
|
272
|
-
.portfolio-history-panel > header { margin: 0; padding: 16px; border-bottom: 1px
|
|
250
|
+
.portfolio-history-panel { overflow: hidden; margin: 4px 0; padding: 0; border: 1px solid var(--border); border-radius: 8px; background: var(--surface); }
|
|
251
|
+
.portfolio-history-panel > header { margin: 0; padding: 16px; border-bottom: 1px solid var(--border); background: color-mix(in srgb, var(--surface) 70%, var(--graphite)); }
|
|
273
252
|
.portfolio-history-panel > header h2 { margin: 0; }
|
|
274
|
-
.portfolio-history-list { display: grid; }
|
|
275
|
-
.portfolio-history-row { display: grid; grid-template-columns: auto minmax(0, 1fr) minmax(100px, 22%); align-items: center; gap: 16px; min-width: 0; padding:
|
|
253
|
+
.portfolio-history-list { display: grid; gap: 7px; padding: 12px; }
|
|
254
|
+
.portfolio-history-row { position: relative; display: grid; grid-template-columns: auto minmax(0, 1fr) minmax(100px, 22%); align-items: center; gap: 16px; min-width: 0; padding: 16px; border: 1px dashed var(--frame-dot); border-radius: 4px; background: transparent; transition: border-color var(--interaction-duration) var(--interaction-ease); }
|
|
276
255
|
.portfolio-history-row.is-education-row { grid-template-columns: minmax(0, 1fr) minmax(100px, 22%); }
|
|
277
|
-
.portfolio-history-row:last-child { border-bottom: 0; }
|
|
278
256
|
.portfolio-history-row:hover, .portfolio-history-row:focus-within { border-color: color-mix(in srgb, var(--lime) 58%, var(--frame-dot)); }
|
|
279
|
-
.portfolio-history-logo { width: 56px; height: 56px; margin: 0; border: 1px solid var(--border); border-radius:
|
|
280
|
-
.portfolio-history-logo-placeholder { display: grid; place-items: center; color: var(--text-muted); font: 500 10px/1 var(--body-font); }
|
|
257
|
+
.portfolio-history-logo { width: 56px; height: 56px; margin: 0; border: 1px solid var(--border); border-radius: 8px; background: var(--card); object-fit: contain; }
|
|
281
258
|
.portfolio-history-main { min-width: 0; }
|
|
259
|
+
.portfolio-history-main h3, .portfolio-content-organization { overflow-wrap: anywhere; }
|
|
282
260
|
.portfolio-history-main h3 { margin: 0 0 4px; color: var(--warm-white); font: 600 var(--text-md)/1.35 var(--heading-font); }
|
|
283
261
|
.portfolio-history-row .portfolio-content-organization, .portfolio-history-date, .portfolio-history-description { margin: 0; color: var(--text-muted); font-size: var(--text-sm); line-height: 1.5; }
|
|
284
|
-
.portfolio-history-
|
|
262
|
+
.portfolio-history-meta { display: grid; min-width: 0; max-width: 100%; justify-items: end; align-content: center; gap: 6px; }
|
|
263
|
+
.portfolio-history-date { justify-self: end; width: fit-content; min-width: 0; max-width: 100%; padding: 4px 8px; border: 1px solid var(--border); border-radius: 3px; background: var(--card); color: var(--muted); text-align: right; white-space: normal; overflow-wrap: anywhere; }
|
|
264
|
+
.portfolio-history-location { display: inline-flex; align-items: center; gap: 4px; margin: 0; color: var(--text-muted); font-size: 10px; line-height: 1.3; text-align: right; }
|
|
265
|
+
.portfolio-history-location { min-width: 0; max-width: 100%; flex-wrap: wrap; overflow-wrap: anywhere; }
|
|
266
|
+
.portfolio-history-location svg { flex: 0 0 auto; }
|
|
267
|
+
.portfolio-organization-link { display: inline-flex; align-items: center; gap: 4px; color: inherit; text-decoration: underline dotted var(--frame-dot); text-underline-offset: 3px; transition: color var(--interaction-duration) var(--interaction-ease), text-decoration-color var(--interaction-duration) var(--interaction-ease); }
|
|
268
|
+
.portfolio-organization-link:hover, .portfolio-organization-link:focus-visible { color: var(--lime); text-decoration-color: var(--lime); }
|
|
285
269
|
.portfolio-history-link { display: inline-flex; margin-left: 8px; color: var(--text-muted); font-size: 1.15em; text-decoration: none; transition: color .2s ease, transform .2s ease; }
|
|
286
270
|
.portfolio-history-link:hover, .portfolio-history-link:focus-visible { color: var(--lime); transform: translate(2px, -2px); }
|
|
287
271
|
.portfolio-history-description { margin-top: 5px; }
|
|
288
272
|
@media (max-width: 600px) {
|
|
289
273
|
.portfolio-history-panel > header { padding: 14px 12px; }
|
|
290
|
-
.portfolio-history-row { grid-template-columns: auto minmax(0, 1fr); gap: 12px; padding:
|
|
274
|
+
.portfolio-history-row { grid-template-columns: auto minmax(0, 1fr); gap: 12px; padding: 14px 12px; }
|
|
291
275
|
.portfolio-history-row.is-education-row { grid-template-columns: minmax(0, 1fr); }
|
|
292
276
|
.portfolio-history-logo { width: 46px; height: 46px; border-radius: 10px; }
|
|
293
|
-
.portfolio-history-
|
|
294
|
-
.portfolio-history-
|
|
277
|
+
.portfolio-history-meta { grid-column: 2; justify-items: start; }
|
|
278
|
+
.portfolio-history-date, .portfolio-history-location { justify-self: start; text-align: left; }
|
|
279
|
+
.portfolio-history-row.is-education-row .portfolio-history-meta { grid-column: 1; }
|
|
295
280
|
}
|
|
296
281
|
.metallic-shimmer-text { color: #D6B77A; }
|
|
297
282
|
@supports ((background-clip: text) or (-webkit-background-clip: text)) {
|
|
@@ -314,15 +299,13 @@ a:not(.social-link):not(.contact-social-link):hover > img, a:not(.social-link):n
|
|
|
314
299
|
.social-links { display: flex; max-width: 100%; flex-wrap: wrap; justify-content: flex-start; gap: 7px; margin-top: 14px; }
|
|
315
300
|
.social-link { display: inline-flex; width: auto; min-width: 0; height: 35px; flex: 0 0 auto; align-items: center; justify-content: flex-start; gap: 7px; padding: 6px 9px; border: 1px dotted var(--frame-dot); border-radius: 5px; color: var(--muted); background: var(--surface); font: 10px/1.2 var(--body-font); transition: transform var(--interaction-duration) var(--interaction-ease), color var(--interaction-duration) var(--interaction-ease), border-color var(--interaction-duration) var(--interaction-ease), background var(--interaction-duration) var(--interaction-ease), opacity var(--interaction-duration) var(--interaction-ease); }
|
|
316
301
|
.social-link:hover, .social-link:focus-visible,
|
|
317
|
-
.contact-social-link:hover, .contact-social-link:focus-visible { color: var(--
|
|
318
|
-
.social-link:hover .
|
|
319
|
-
.contact-social-link:hover > img, .contact-social-link:focus-visible > img { filter: brightness(0) saturate(100%) invert(
|
|
320
|
-
.
|
|
321
|
-
.social-link:active .social-icon, .contact-social-link:active > svg, .contact-social-link:active > img { transform: translateY(0); opacity: .72; }
|
|
302
|
+
.contact-social-link:hover, .contact-social-link:focus-visible { color: var(--social-hover); border-color: var(--frame-dot); }
|
|
303
|
+
.social-link:hover .social-icon, .social-link:focus-visible .social-icon, .contact-social-link:hover > svg, .contact-social-link:focus-visible > svg, .contact-social-link:hover > img, .contact-social-link:focus-visible > img { opacity: 1; }
|
|
304
|
+
.social-link:hover .social-icon, .social-link:focus-visible .social-icon, .contact-social-link:hover > img, .contact-social-link:focus-visible > img { filter: brightness(0) saturate(100%) invert(72%) sepia(99%) saturate(1037%) hue-rotate(82deg) brightness(102%) contrast(104%); }
|
|
305
|
+
.contact-social-link:hover > svg, .contact-social-link:focus-visible > svg { color: var(--social-hover); }
|
|
322
306
|
.social-link.is-unavailable { cursor: default; }
|
|
323
|
-
.social-icon { display: block; width: 16px; height: 16px; object-fit: contain; opacity: .82; transition: opacity var(--interaction-duration) var(--interaction-ease), transform var(--interaction-duration) var(--interaction-ease); }
|
|
324
|
-
.brand-icon-fallback { display: inline-grid; flex: 0 0 auto; place-items: center; overflow: hidden; border: 1px solid
|
|
325
|
-
.social-link:hover .brand-icon-fallback, .social-link:focus-visible .brand-icon-fallback, .contact-social-link:hover .brand-icon-fallback, .contact-social-link:focus-visible .brand-icon-fallback { border-color: var(--lime); color: var(--lime); }
|
|
307
|
+
.social-icon { display: block; width: 16px; height: 16px; object-fit: contain; opacity: .82; transition: opacity var(--interaction-duration) var(--interaction-ease), transform var(--interaction-duration) var(--interaction-ease), filter var(--interaction-duration) var(--interaction-ease); }
|
|
308
|
+
.brand-icon-fallback { display: inline-grid; flex: 0 0 auto; place-items: center; overflow: hidden; border: 1px solid var(--frame-dot); border-radius: 3px; color: var(--muted); background: color-mix(in srgb, var(--surface) 82%, transparent); font: 700 6px/1 var(--mono-font); letter-spacing: -.04em; }
|
|
326
309
|
.resume-icon { color: var(--muted); }
|
|
327
310
|
|
|
328
311
|
.section-rule { display: flex; align-items: center; gap: 12px; color: var(--muted); font-size: 8px; letter-spacing: .09em; }
|
|
@@ -351,7 +334,7 @@ a:not(.social-link):not(.contact-social-link):hover > img, a:not(.social-link):n
|
|
|
351
334
|
.project-actions .project-action-placeholder { max-width: 190px; color: var(--text-muted); font-family: var(--body-font); cursor: pointer; }
|
|
352
335
|
.project-actions a:hover, .project-actions a:focus-visible, .project-actions .project-action-placeholder:hover { color: var(--lime); }
|
|
353
336
|
.project-actions a > svg, .project-actions a > img, .project-actions .project-action-placeholder > svg { display: block; width: 14px; height: 14px; flex: 0 0 14px; object-fit: contain; transition: transform var(--interaction-duration) var(--interaction-ease), filter var(--interaction-duration) var(--interaction-ease), color var(--interaction-duration) var(--interaction-ease); }
|
|
354
|
-
.project-actions a:focus-visible, .project-actions .project-action-placeholder:focus-visible { outline: 1px solid var(--
|
|
337
|
+
.project-actions a:focus-visible, .project-actions .project-action-placeholder:focus-visible { outline: 1px solid var(--frame-dot-hover); outline-offset: 1px; }
|
|
355
338
|
|
|
356
339
|
.skills, .contact-section { position: relative; padding: var(--section-space) 0 var(--section-space-end); }
|
|
357
340
|
.contact-section::before { position: absolute; top: 0; right: 0; left: 0; height: 1px; background: repeating-linear-gradient(90deg, var(--stitch) 0 4px, transparent 4px 8px); content: ''; }
|
|
@@ -363,7 +346,7 @@ a:not(.social-link):not(.contact-social-link):hover > img, a:not(.social-link):n
|
|
|
363
346
|
.skill-tile { position: relative; display: grid; width: 42px; height: 42px; flex: 0 0 42px; place-items: center; padding: 0; border: 1px solid transparent; border-radius: 4px; color: var(--warm-white); background: transparent; }
|
|
364
347
|
.skill-tile:hover { z-index: 2; }
|
|
365
348
|
.skill-logo { width: 32px; height: 32px; object-fit: contain; }
|
|
366
|
-
.skill-logo-fallback { display: grid; width: 32px; height: 32px; place-items: center; border: 1px solid
|
|
349
|
+
.skill-logo-fallback { display: grid; width: 32px; height: 32px; place-items: center; border: 1px solid var(--frame-dot); border-radius: 4px; color: var(--muted); font: 700 8px/1 var(--mono-font); letter-spacing: -.02em; }
|
|
367
350
|
.skill-logo-fallback[hidden] { display: none; }
|
|
368
351
|
.skill-name { position: absolute; top: calc(100% + 7px); left: 50%; z-index: 8; min-width: max-content; opacity: 0; pointer-events: none; transform: translate(-50%, -3px) scale(.97); transition: opacity var(--interaction-duration) var(--interaction-ease), transform var(--interaction-duration) var(--interaction-ease); }
|
|
369
352
|
.skill-tile:hover .skill-name { opacity: 1; transform: translate(-50%, 0) scale(1); }
|
|
@@ -392,10 +375,11 @@ a:not(.social-link):not(.contact-social-link):hover > img, a:not(.social-link):n
|
|
|
392
375
|
.contact-social-panel { min-width: 0; padding: 10px 1px 0 14px; border-left: 1px dotted var(--frame-dot); }
|
|
393
376
|
.contact-social-links { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 5px; margin-top: 9px; }
|
|
394
377
|
.contact-social-link { display: flex; min-width: 0; min-height: 34px; align-items: center; justify-content: center; gap: 7px; padding: 6px 8px; border: 1px dotted var(--frame-dot); border-radius: 3px; color: var(--muted); background: transparent; font: 10px/1.25 var(--body-font); transition: color var(--interaction-duration) var(--interaction-ease), border-color var(--interaction-duration) var(--interaction-ease); }
|
|
395
|
-
.contact-social-link:hover, .contact-social-link:focus-visible { color: var(--
|
|
378
|
+
.contact-social-link:hover, .contact-social-link:focus-visible { color: var(--social-hover); }
|
|
379
|
+
.social-link:focus-visible, .contact-social-link:focus-visible, .skill-tile:focus-visible { outline-color: var(--frame-dot-hover); }
|
|
396
380
|
.contact-social-link > svg, .contact-social-link > img, .contact-brand-icon { display: block; width: 16px; height: 16px; flex: 0 0 16px; object-fit: contain; }
|
|
397
381
|
.contact-social-link > svg { color: var(--muted); }
|
|
398
|
-
.contact-social-link > svg, .contact-social-link > img { opacity: .82; transition: transform var(--interaction-duration) var(--interaction-ease), opacity var(--interaction-duration) var(--interaction-ease); }
|
|
382
|
+
.contact-social-link > svg, .contact-social-link > img { opacity: .82; transition: transform var(--interaction-duration) var(--interaction-ease), opacity var(--interaction-duration) var(--interaction-ease), filter var(--interaction-duration) var(--interaction-ease), color var(--interaction-duration) var(--interaction-ease); }
|
|
399
383
|
.contact-brand-icon { fill: currentColor; }
|
|
400
384
|
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; clip-path: inset(50%); }
|
|
401
385
|
.site-footer { display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 17px var(--page-gutter) 20px; border-top: 1px dotted var(--frame-dot); color: var(--muted); text-align: center; }
|
|
@@ -403,9 +387,7 @@ a:not(.social-link):not(.contact-social-link):hover > img, a:not(.social-link):n
|
|
|
403
387
|
.footer-copyright, .footer-credit, .footer-quote { margin: 0; font-family: var(--body-font); }
|
|
404
388
|
.footer-copyright { color: var(--text-muted); font-size: 10px; line-height: 1.45; }
|
|
405
389
|
.footer-credit { color: var(--muted); font-size: 10px; line-height: 1.45; }
|
|
406
|
-
.footer-heart { display: inline-block; width: 14px; height: 14px; margin: 0 2px; object-fit: contain; vertical-align: -3px; animation: footer-heart-pulse 1.8s ease-in-out infinite; }
|
|
407
390
|
.footer-quote { width: min(100%, var(--content-width)); margin-top: 5px; padding-top: 9px; border-top: 1px dotted color-mix(in srgb, var(--frame-dot) 72%, transparent); color: var(--text-muted); font-size: 10px; font-style: italic; line-height: 1.45; letter-spacing: .01em; }
|
|
408
|
-
@keyframes footer-heart-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.16); } }
|
|
409
391
|
|
|
410
392
|
@keyframes wave-hand {
|
|
411
393
|
0%, 62%, 100% { transform: rotate(0); }
|
|
@@ -421,35 +403,21 @@ a:not(.social-link):not(.contact-social-link):hover > img, a:not(.social-link):n
|
|
|
421
403
|
from { text-shadow: 0 0 3px rgba(229, 207, 156, .2); }
|
|
422
404
|
to { text-shadow: 0 0 5px rgba(229, 207, 156, .32); }
|
|
423
405
|
}
|
|
424
|
-
@keyframes heart-beat {
|
|
425
|
-
0%, 10%, 25%, 42%, 100% { transform: scale(1); filter: drop-shadow(0 0 0 rgba(229, 207, 156, 0)); }
|
|
426
|
-
14% { transform: scale(1.14); filter: drop-shadow(0 0 3px rgba(229, 207, 156, .48)); }
|
|
427
|
-
19% { transform: scale(1.03); }
|
|
428
|
-
31% { transform: scale(1.2); filter: drop-shadow(0 0 5px rgba(229, 207, 156, .62)); }
|
|
429
|
-
37% { transform: scale(1.05); }
|
|
430
|
-
}
|
|
431
406
|
@keyframes role-metallic-shimmer {
|
|
432
407
|
from { background-position: 0 0, 100% 50%; }
|
|
433
408
|
to { background-position: 0 0, 0% 50%; }
|
|
434
409
|
}
|
|
435
410
|
|
|
436
411
|
@media (prefers-reduced-motion: reduce) {
|
|
437
|
-
.
|
|
412
|
+
.profile-intro .profile-role, .metallic-shimmer-text { animation: none; }
|
|
438
413
|
}
|
|
439
414
|
|
|
440
415
|
@media (max-width: 760px) {
|
|
441
416
|
:root { --page-gutter: 14px; }
|
|
442
|
-
body::before { opacity: .2; }
|
|
443
|
-
body::after { display: none; animation: none; will-change: auto; }
|
|
444
|
-
.heart-message { top: auto; bottom: calc(100% + 5px); left: 50%; transform: translate(-50%, 3px); }
|
|
445
|
-
@media (hover: hover) and (pointer: fine) {
|
|
446
|
-
.name-heart-wrap:hover .heart-message { transform: translate(-50%, 0); }
|
|
447
|
-
}
|
|
448
417
|
.main-nav { gap: 12px; }
|
|
449
418
|
.project-grid { gap: 8px; }
|
|
450
419
|
}
|
|
451
420
|
@media (max-width: 600px) {
|
|
452
|
-
body::before { background-image: url('/stars-portrait.svg'); opacity: .16; }
|
|
453
421
|
:root { --page-gutter: 12px; --section-space: 16px; --section-space-end: 18px; --panel-inset: 10px; }
|
|
454
422
|
.site-header { min-height: 44px; }
|
|
455
423
|
.wordmark { font-size: 13px; }
|
|
@@ -466,10 +434,10 @@ a:not(.social-link):not(.contact-social-link):hover > img, a:not(.social-link):n
|
|
|
466
434
|
.hero h1 { margin-top: 7px; font-size: 22px; }
|
|
467
435
|
.profile-intro { font-size: 13px; }
|
|
468
436
|
.profile-intro .profile-role { font-size: 14px; }
|
|
469
|
-
.profile-metadata { flex: 1 1 100%; flex-direction: row; align-items: center; justify-content: flex-start; gap: 12px; font-size: 10px; }
|
|
437
|
+
.profile-metadata { min-width: 0; flex: 1 1 100%; flex-direction: row; flex-wrap: wrap; align-items: center; justify-content: flex-start; gap: 12px; font-size: 10px; }
|
|
470
438
|
.profile-bio { max-width: 100%; font-size: 12px; line-height: 1.55; }
|
|
471
439
|
.social-links { gap: 6px; margin-top: 12px; }
|
|
472
|
-
.social-link { min-height:
|
|
440
|
+
.social-link { min-height: 44px; padding: 7px 10px; }
|
|
473
441
|
.projects { padding: var(--section-space) 0 var(--section-space-end) 12px; }
|
|
474
442
|
.projects-intro { gap: 4px; padding: 5px 0 9px; }
|
|
475
443
|
.projects-intro h2 { font-size: 22px; }
|
|
@@ -479,7 +447,7 @@ a:not(.social-link):not(.contact-social-link):hover > img, a:not(.social-link):n
|
|
|
479
447
|
.project-info { gap: 7px; }
|
|
480
448
|
.project-info > h3 { font-size: 14px; }
|
|
481
449
|
.project-info > p { font-size: 10px; }
|
|
482
|
-
.project-actions a { min-height:
|
|
450
|
+
.project-actions a { min-height: 44px; padding: 5px 10px; font-size: 11px; }
|
|
483
451
|
.skills-panel { padding: 9px 10px 11px; }
|
|
484
452
|
.skills-panel > h2 { margin-bottom: 7px; padding-bottom: 6px; font-size: 18px; }
|
|
485
453
|
.skills-grid { gap: 4px; }
|
|
@@ -490,12 +458,9 @@ a:not(.social-link):not(.contact-social-link):hover > img, a:not(.social-link):n
|
|
|
490
458
|
.contact-content { grid-template-columns: minmax(0, 1fr); }
|
|
491
459
|
.contact-form { padding: 9px 0 10px; }
|
|
492
460
|
.contact-social-panel { padding: 9px 0 0; border-top: 1px dotted var(--frame-dot); border-left: 0; }
|
|
493
|
-
.contact-social-link { min-height:
|
|
461
|
+
.contact-submit, .contact-social-link { min-height: 44px; }
|
|
494
462
|
.site-footer { padding: 16px var(--page-gutter) 18px; }
|
|
495
463
|
}
|
|
496
|
-
@media (max-width: 600px) and (hover: none) {
|
|
497
|
-
.heart-message { display: none; }
|
|
498
|
-
}
|
|
499
464
|
@media (max-width: 380px) {
|
|
500
465
|
:root { --page-gutter: 10px; }
|
|
501
466
|
.profile-metadata { gap: 4px; font-size: 9px; }
|
|
@@ -513,7 +478,7 @@ main > section.section-wrap::before,
|
|
|
513
478
|
main > section.section-wrap::after { display: none; }
|
|
514
479
|
.dotted-frame::before { display: none; }
|
|
515
480
|
.skills-panel, .contact-panel { border: 0; border-radius: 0; }
|
|
516
|
-
.project-card
|
|
481
|
+
.project-card { border: 1px dashed var(--frame-dot); border-radius: 0; }
|
|
517
482
|
.project-card {
|
|
518
483
|
transition: border-color var(--interaction-duration) var(--interaction-ease);
|
|
519
484
|
}
|
|
@@ -32,8 +32,8 @@ test('portfolio content, optional sections, section ordering and custom sections
|
|
|
32
32
|
assert.ok(aboutSection)
|
|
33
33
|
assert.equal([...aboutSection.matchAll(/<p>/g)].length, 3, 'bio content renders as three paragraphs')
|
|
34
34
|
await access(resolve(templateRoot, 'public', config.profile.profileImage.slice(1)))
|
|
35
|
-
assert.ok(config.projects.every(({ image }) => image === '/project-placeholder.
|
|
36
|
-
await access(resolve(templateRoot, 'public', 'project-placeholder.
|
|
35
|
+
assert.ok(config.projects.every(({ image }) => image === '/project-placeholder.png'))
|
|
36
|
+
await access(resolve(templateRoot, 'public', 'project-placeholder.png'))
|
|
37
37
|
const styles = await readFile(resolve(templateRoot, 'src/styles.css'), 'utf8')
|
|
38
38
|
assert.match(styles, /\.profile-photo\s*\{[^}]*border-radius:\s*50%/)
|
|
39
39
|
assert.deepEqual(config.skills.map(({ name }) => name), [
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export interface Skill {
|
|
2
|
+
name: string
|
|
3
|
+
icon: string
|
|
4
|
+
category?: string
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface Project {
|
|
8
|
+
name: string
|
|
9
|
+
category?: string
|
|
10
|
+
description: string
|
|
11
|
+
image?: string
|
|
12
|
+
imageAlt?: string
|
|
13
|
+
technologies: Skill[]
|
|
14
|
+
liveUrl?: string
|
|
15
|
+
githubUrl?: string
|
|
16
|
+
featured?: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ContentItem {
|
|
20
|
+
title: string
|
|
21
|
+
organization?: string
|
|
22
|
+
organizationUrl?: string
|
|
23
|
+
date?: string
|
|
24
|
+
location?: string
|
|
25
|
+
description?: string
|
|
26
|
+
image?: string
|
|
27
|
+
imageAlt?: string
|
|
28
|
+
imagePlaceholder?: string
|
|
29
|
+
links?: { label: string; url: string }[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface Testimonial {
|
|
33
|
+
name: string
|
|
34
|
+
role: string
|
|
35
|
+
company?: string
|
|
36
|
+
message: string
|
|
37
|
+
image?: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface SocialIcon {
|
|
41
|
+
label: string
|
|
42
|
+
icon: string
|
|
43
|
+
url?: string
|
|
44
|
+
color?: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface PortfolioConfig {
|
|
48
|
+
profile: {
|
|
49
|
+
name: string
|
|
50
|
+
headline: string
|
|
51
|
+
introduction: string
|
|
52
|
+
about: string
|
|
53
|
+
location?: string
|
|
54
|
+
timeZone?: string
|
|
55
|
+
timeZoneLabel?: string
|
|
56
|
+
profileImage?: string
|
|
57
|
+
resumeUrl?: string
|
|
58
|
+
quote?: string
|
|
59
|
+
}
|
|
60
|
+
socialIcons: SocialIcon[]
|
|
61
|
+
experience: ContentItem[]
|
|
62
|
+
education: ContentItem[]
|
|
63
|
+
skills: Skill[]
|
|
64
|
+
projects: Project[]
|
|
65
|
+
testimonials: Testimonial[]
|
|
66
|
+
contact: { email?: string }
|
|
67
|
+
seo: {
|
|
68
|
+
title: string
|
|
69
|
+
description: string
|
|
70
|
+
siteUrl?: string
|
|
71
|
+
image?: string
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" role="img" aria-labelledby="title description">
|
|
2
|
-
<title id="title">Avatar placeholder</title>
|
|
3
|
-
<desc id="description">A neutral circular placeholder for a 400 by 400 profile image.</desc>
|
|
4
|
-
<circle cx="200" cy="200" r="192" fill="#29292c"/>
|
|
5
|
-
<circle cx="200" cy="200" r="176" fill="#e7e7e9" stroke="#45454a" stroke-width="12"/>
|
|
6
|
-
<g fill="#77777d" font-family="Arial, sans-serif" text-anchor="middle">
|
|
7
|
-
<text x="200" y="188" font-size="30" font-weight="700">Avatar</text>
|
|
8
|
-
<text x="200" y="224" font-size="25" font-weight="600">400 x 400</text>
|
|
9
|
-
</g>
|
|
10
|
-
</svg>
|
|
Binary file
|
|
Binary file
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 675" role="img" aria-labelledby="title description">
|
|
2
|
-
<title id="title">Project image placeholder</title>
|
|
3
|
-
<desc id="description">A neutral 1200 by 675 project thumbnail placeholder.</desc>
|
|
4
|
-
<rect width="1200" height="675" fill="#050505"/>
|
|
5
|
-
<rect x="24" y="24" width="1152" height="627" rx="34" fill="#e7e7e9"/>
|
|
6
|
-
<g fill="#77777d" font-family="Arial, sans-serif" text-anchor="middle">
|
|
7
|
-
<text x="600" y="310" font-size="58" font-weight="700">Project image</text>
|
|
8
|
-
<text x="600" y="376" font-size="52" font-weight="600">1200 x 675</text>
|
|
9
|
-
</g>
|
|
10
|
-
</svg>
|