@ansiversa/components 0.0.120 → 0.0.121

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ansiversa/components",
3
- "version": "0.0.120",
3
+ "version": "0.0.121",
4
4
  "description": "Shared UI components and layouts for the Ansiversa ecosystem",
5
5
  "type": "module",
6
6
  "exports": {
@@ -47,32 +47,10 @@ const declaration = data.declaration ?? {};
47
47
  const hasDeclaration = Boolean(declaration.text || declaration.place || declaration.name);
48
48
  ---
49
49
 
50
- <style>
51
- @media print {
52
- .resume-template a {
53
- text-decoration: none;
54
- color: inherit;
55
- }
56
-
57
- .resume-template .classic-grid {
58
- display: grid;
59
- grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
60
- gap: 2.5rem;
61
- }
62
-
63
- .resume-template .classic-divider {
64
- background: none !important;
65
- border-top: 1px solid #e2e8f0 !important;
66
- -webkit-print-color-adjust: exact;
67
- print-color-adjust: exact;
68
- }
69
- }
70
- </style>
71
-
72
50
  <div class="resume-template bg-slate-100 text-slate-900 print:bg-white">
73
51
  <main class="mx-auto max-w-4xl p-4 sm:p-6">
74
52
  <section class="rounded-2xl bg-white p-6 shadow-sm ring-1 ring-slate-200 sm:p-10 print:shadow-none print:ring-0">
75
- <header class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
53
+ <header class="av-print-avoid-break flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
76
54
  <div>
77
55
  <h1 class="text-3xl font-bold tracking-tight">{basics.fullName}</h1>
78
56
  <p class="mt-1 text-base font-medium text-slate-700">{basics.headline}</p>
@@ -87,7 +65,7 @@ const hasDeclaration = Boolean(declaration.text || declaration.place || declarat
87
65
  {contactItems.map((item) =>
88
66
  item.href ? (
89
67
  <a
90
- class="text-slate-900 underline underline-offset-2 hover:text-slate-700"
68
+ class="text-slate-900 underline underline-offset-2 hover:text-slate-700 print:no-underline"
91
69
  href={item.href}
92
70
  >
93
71
  {item.label}
@@ -101,10 +79,10 @@ const hasDeclaration = Boolean(declaration.text || declaration.place || declarat
101
79
  )}
102
80
  </header>
103
81
 
104
- <div class="classic-divider my-8 h-0 border-t border-slate-200"></div>
82
+ <div class="my-8 h-0 border-t border-slate-200"></div>
105
83
 
106
- <div class="classic-grid grid gap-10 lg:grid-cols-3">
107
- <div class="space-y-10 lg:col-span-2">
84
+ <div class="grid gap-10 lg:grid-cols-3 print:grid-cols-1">
85
+ <div class="space-y-10 lg:col-span-2 print:col-span-1">
108
86
  {experienceItems.length > 0 && (
109
87
  <section>
110
88
  <h2 class="text-sm font-bold tracking-widest text-slate-900">EXPERIENCE</h2>
@@ -113,7 +91,7 @@ const hasDeclaration = Boolean(declaration.text || declaration.place || declarat
113
91
  const range = formatDateRange(item);
114
92
  const meta = item.location ? `${range} · ${item.location}` : range;
115
93
  return (
116
- <article>
94
+ <article class="av-print-avoid-break">
117
95
  <div class="flex flex-col gap-1 sm:flex-row sm:items-baseline sm:justify-between">
118
96
  <div>
119
97
  <h3 class="text-base font-semibold">{item.role}</h3>
@@ -147,7 +125,7 @@ const hasDeclaration = Boolean(declaration.text || declaration.place || declarat
147
125
  ? formatDateRange({ start: project.start, end: project.end, present: project.present })
148
126
  : undefined;
149
127
  return (
150
- <article>
128
+ <article class="av-print-avoid-break">
151
129
  <div class="flex items-baseline justify-between gap-3">
152
130
  {project.link ? (
153
131
  <a class="text-base font-semibold underline underline-offset-4" href={project.link}>
@@ -191,7 +169,7 @@ const hasDeclaration = Boolean(declaration.text || declaration.place || declarat
191
169
  {educationItems.map((edu) => {
192
170
  const range = edu.start ? formatDateRange({ start: edu.start, end: edu.end }) : undefined;
193
171
  return (
194
- <div class="flex flex-col gap-1 sm:flex-row sm:items-baseline sm:justify-between">
172
+ <div class="av-print-avoid-break flex flex-col gap-1 sm:flex-row sm:items-baseline sm:justify-between">
195
173
  <div>
196
174
  <h3 class="text-base font-semibold">{edu.degree}</h3>
197
175
  <p class="text-sm text-slate-700">{edu.school}</p>
@@ -211,7 +189,7 @@ const hasDeclaration = Boolean(declaration.text || declaration.place || declarat
211
189
 
212
190
  <aside class="space-y-10">
213
191
  {skills.length > 0 && (
214
- <section>
192
+ <section class="av-print-avoid-break">
215
193
  <h2 class="text-sm font-bold tracking-widest text-slate-900">SKILLS</h2>
216
194
  <div class="mt-4 flex flex-wrap gap-2">
217
195
  {skills.map((skill) => (
@@ -224,13 +202,13 @@ const hasDeclaration = Boolean(declaration.text || declaration.place || declarat
224
202
  )}
225
203
 
226
204
  {certifications.length > 0 && (
227
- <section>
205
+ <section class="av-print-avoid-break">
228
206
  <h2 class="text-sm font-bold tracking-widest text-slate-900">CERTIFICATIONS</h2>
229
207
  <ul class="mt-4 space-y-3 text-sm text-slate-700">
230
208
  {certifications.map((cert) => {
231
209
  const meta = [cert.issuer, cert.year].filter(Boolean).join(" · ");
232
210
  return (
233
- <li class="space-y-1">
211
+ <li class="av-print-avoid-break space-y-1">
234
212
  {cert.link ? (
235
213
  <a class="font-semibold text-slate-900 underline underline-offset-2" href={cert.link}>
236
214
  {cert.name}
@@ -247,7 +225,7 @@ const hasDeclaration = Boolean(declaration.text || declaration.place || declarat
247
225
  )}
248
226
 
249
227
  {highlights.length > 0 && (
250
- <section>
228
+ <section class="av-print-avoid-break">
251
229
  <h2 class="text-sm font-bold tracking-widest text-slate-900">HIGHLIGHTS</h2>
252
230
  <ul class="mt-4 list-disc space-y-2 pl-5 text-sm leading-6 text-slate-700">
253
231
  {highlights.map((item) => (
@@ -258,7 +236,7 @@ const hasDeclaration = Boolean(declaration.text || declaration.place || declarat
258
236
  )}
259
237
 
260
238
  {languages.length > 0 && (
261
- <section>
239
+ <section class="av-print-avoid-break">
262
240
  <h2 class="text-sm font-bold tracking-widest text-slate-900">LANGUAGES</h2>
263
241
  <ul class="mt-4 space-y-2 text-sm text-slate-700">
264
242
  {languages.map((language) => (
@@ -278,7 +256,7 @@ const hasDeclaration = Boolean(declaration.text || declaration.place || declarat
278
256
  {awards.map((award) => {
279
257
  const meta = [award.by, award.year].filter(Boolean).join(" · ");
280
258
  return (
281
- <li class="space-y-1">
259
+ <li class="av-print-avoid-break space-y-1">
282
260
  <div class="font-semibold text-slate-900">{award.title}</div>
283
261
  {meta && <div class="text-xs text-slate-600">{meta}</div>}
284
262
  {award.summary && <div class="text-sm text-slate-700">{award.summary}</div>}
@@ -293,8 +271,8 @@ const hasDeclaration = Boolean(declaration.text || declaration.place || declarat
293
271
 
294
272
  {hasDeclaration && (
295
273
  <>
296
- <div class="classic-divider my-8 h-0 border-t border-slate-200"></div>
297
- <section class="space-y-4">
274
+ <div class="my-8 h-0 border-t border-slate-200"></div>
275
+ <section class="av-print-avoid-break space-y-4">
298
276
  <h2 class="text-sm font-bold tracking-widest text-slate-900">DECLARATION</h2>
299
277
  {declaration.text && (
300
278
  <p class="text-sm leading-6 text-slate-700">{declaration.text}</p>
@@ -25,6 +25,13 @@ for (const link of basics.links ?? []) {
25
25
  contactChips.push(link.label ?? formatUrlLabel(link.url));
26
26
  }
27
27
 
28
+ const normalizeSkillLabel = (value?: string) =>
29
+ (value ?? "")
30
+ .replace(/\s+/g, " ")
31
+ .replace(/[|•]+/g, " · ")
32
+ .replace(/(\d)([A-Za-z])/g, "$1 $2")
33
+ .trim();
34
+
28
35
  const experienceItems = data.experience ?? [];
29
36
  const projects = data.projects ?? [];
30
37
  const skills = data.skills ?? [];
@@ -34,22 +41,10 @@ const declaration = data.declaration ?? {};
34
41
  const hasDeclaration = Boolean(declaration.text || declaration.place || declaration.name);
35
42
  ---
36
43
 
37
- <style>
38
- @media print {
39
- .resume-template a {
40
- text-decoration: none;
41
- color: inherit;
42
- }
43
- .resume-template {
44
- background: white;
45
- }
46
- }
47
- </style>
48
-
49
44
  <div class="resume-template bg-slate-100 text-slate-900 print:bg-white">
50
45
  <main class="mx-auto max-w-4xl p-4 sm:p-6">
51
46
  <section class="rounded-2xl bg-white p-7 shadow-sm ring-1 ring-slate-200 sm:p-10 print:shadow-none print:ring-0">
52
- <header class="flex flex-col gap-5 sm:flex-row sm:items-end sm:justify-between">
47
+ <header class="av-print-avoid-break flex flex-col gap-5 sm:flex-row sm:items-end sm:justify-between">
53
48
  <div>
54
49
  <h1 class="text-3xl font-bold tracking-tight">{basics.fullName}</h1>
55
50
  <p class="mt-1 text-sm font-medium text-slate-700">{basics.headline}</p>
@@ -63,7 +58,7 @@ const hasDeclaration = Boolean(declaration.text || declaration.place || declarat
63
58
  </div>
64
59
 
65
60
  {basics.summary && (
66
- <div class="rounded-xl border border-slate-200 bg-slate-50 p-4 text-sm text-slate-700 sm:max-w-sm">
61
+ <div class="av-print-avoid-break rounded-xl border border-slate-200 bg-slate-50 p-4 text-sm text-slate-700 sm:max-w-sm">
67
62
  <div class="text-xs font-semibold tracking-widest text-slate-500">SUMMARY</div>
68
63
  <p class="mt-2 leading-6">{basics.summary}</p>
69
64
  </div>
@@ -72,14 +67,14 @@ const hasDeclaration = Boolean(declaration.text || declaration.place || declarat
72
67
 
73
68
  <div class="my-8 h-px bg-slate-200"></div>
74
69
 
75
- <div class="grid gap-10 lg:grid-cols-3">
70
+ <div class="grid gap-10 lg:grid-cols-3 print:grid-cols-1">
76
71
  <div class="lg:col-span-2">
77
72
  {experienceItems.length > 0 && (
78
73
  <>
79
74
  <h2 class="text-sm font-bold tracking-widest text-slate-900">EXPERIENCE</h2>
80
75
  <div class="mt-6 space-y-8">
81
76
  {experienceItems.map((item) => (
82
- <div class="relative pl-6">
77
+ <div class="av-print-avoid-break relative pl-6">
83
78
  <div class="absolute left-0 top-1.5 h-full w-px bg-slate-200"></div>
84
79
  <div class="absolute left-[-5px] top-1.5 h-3 w-3 rounded-full bg-slate-900"></div>
85
80
 
@@ -112,7 +107,7 @@ const hasDeclaration = Boolean(declaration.text || declaration.place || declarat
112
107
  <h2 class="text-sm font-bold tracking-widest text-slate-900">PROJECTS</h2>
113
108
  <div class="mt-5 space-y-4">
114
109
  {projects.map((project) => (
115
- <div class="rounded-xl border border-slate-200 p-4">
110
+ <div class="av-print-avoid-break rounded-xl border border-slate-200 p-4">
116
111
  <div class="flex items-baseline justify-between">
117
112
  {project.link ? (
118
113
  <a class="font-semibold underline underline-offset-4" href={project.link}>
@@ -143,22 +138,22 @@ const hasDeclaration = Boolean(declaration.text || declaration.place || declarat
143
138
 
144
139
  <aside class="space-y-8">
145
140
  {skills.length > 0 && (
146
- <section class="rounded-xl border border-slate-200 p-4">
141
+ <section class="av-print-avoid-break rounded-xl border border-slate-200 p-4">
147
142
  <h2 class="text-xs font-bold tracking-widest text-slate-500">SKILLS</h2>
148
- <div class="mt-3 flex flex-wrap gap-2">
143
+ <ul class="mt-3 list-disc space-y-1 pl-5 text-sm text-slate-700">
149
144
  {skills.map((skill) => (
150
- <span class="rounded-full bg-slate-100 px-3 py-1 text-xs">{skill.name}</span>
145
+ <li>{normalizeSkillLabel(skill.name)}</li>
151
146
  ))}
152
- </div>
147
+ </ul>
153
148
  </section>
154
149
  )}
155
150
 
156
151
  {education.length > 0 && (
157
- <section class="rounded-xl border border-slate-200 p-4">
152
+ <section class="av-print-avoid-break rounded-xl border border-slate-200 p-4">
158
153
  <h2 class="text-xs font-bold tracking-widest text-slate-500">EDUCATION</h2>
159
154
  <div class="mt-3 space-y-4 text-sm text-slate-700">
160
155
  {education.map((edu) => (
161
- <div>
156
+ <div class="av-print-avoid-break rounded-lg border border-slate-100 p-3">
162
157
  <div class="font-semibold">{edu.degree}</div>
163
158
  <div class="text-slate-600">{edu.school}</div>
164
159
  {edu.location && <div class="text-slate-500">{edu.location}</div>}
@@ -174,7 +169,7 @@ const hasDeclaration = Boolean(declaration.text || declaration.place || declarat
174
169
  )}
175
170
 
176
171
  {highlights.length > 0 && (
177
- <section class="rounded-xl border border-slate-200 p-4">
172
+ <section class="av-print-avoid-break rounded-xl border border-slate-200 p-4">
178
173
  <h2 class="text-xs font-bold tracking-widest text-slate-500">HIGHLIGHTS</h2>
179
174
  <ul class="mt-3 space-y-2 text-sm leading-6 text-slate-700">
180
175
  {highlights.map((highlight) => (
@@ -189,7 +184,7 @@ const hasDeclaration = Boolean(declaration.text || declaration.place || declarat
189
184
  {hasDeclaration && (
190
185
  <>
191
186
  <div class="my-8 h-px bg-slate-200"></div>
192
- <section class="space-y-4">
187
+ <section class="av-print-avoid-break space-y-4">
193
188
  <h2 class="text-sm font-bold tracking-widest text-slate-900">DECLARATION</h2>
194
189
  {declaration.text && (
195
190
  <p class="text-sm leading-6 text-slate-700">{declaration.text}</p>
@@ -33,6 +33,8 @@ if (basics.contact.website) {
33
33
  contactItems.push({ label: formatUrlLabel(basics.contact.website), href: basics.contact.website });
34
34
  }
35
35
 
36
+ const directContactLine = [basics.contact.email, basics.contact.phone].filter(Boolean).join(" • ");
37
+
36
38
  const skills = data.skills ?? [];
37
39
  const links = basics.links ?? [];
38
40
  const languages = data.languages ?? [];
@@ -44,39 +46,30 @@ const declaration = data.declaration ?? {};
44
46
  const hasDeclaration = Boolean(declaration.text || declaration.place || declaration.name);
45
47
  ---
46
48
 
47
- <style>
48
- @media print {
49
- .resume-template a {
50
- text-decoration: none;
51
- color: inherit;
52
- }
53
- .resume-template {
54
- background: white;
55
- }
56
- }
57
- </style>
58
-
59
49
  <div class="resume-template bg-slate-100 text-slate-900 print:bg-white">
60
50
  <main class="mx-auto max-w-4xl p-4 sm:p-6">
61
51
  <section class="overflow-hidden rounded-2xl bg-white shadow-sm ring-1 ring-slate-200 print:shadow-none print:ring-0">
62
- <div class="grid lg:grid-cols-12">
63
- <aside class="bg-slate-900 p-7 text-white sm:p-10 lg:col-span-4">
52
+ <div class="grid lg:grid-cols-12 print:grid-cols-1">
53
+ <aside class="bg-slate-900 p-7 text-slate-100 sm:p-10 lg:col-span-4 print:order-2 print:bg-white print:text-slate-900">
64
54
  <div class="space-y-6">
65
55
  <div>
66
56
  <h1 class="text-3xl font-bold tracking-tight">{firstName}</h1>
67
57
  {lastName && <h1 class="-mt-1 text-3xl font-bold tracking-tight">{lastName}</h1>}
68
- <p class="mt-2 text-sm text-white/80">{basics.headline}</p>
58
+ <p class="mt-2 text-sm text-slate-200 print:text-slate-700">{basics.headline}</p>
69
59
  </div>
70
60
 
71
- <div class="h-px bg-white/10"></div>
61
+ <div class="h-px bg-slate-700 print:bg-slate-200"></div>
72
62
 
73
63
  {contactItems.length > 0 && (
74
- <div class="space-y-2 text-sm">
75
- <div class="text-white/70">CONTACT</div>
76
- <div class="space-y-1 text-white/90">
77
- {contactItems.map((item) =>
64
+ <div class="av-print-avoid-break space-y-2 text-sm">
65
+ <div class="font-semibold text-slate-100 print:text-slate-900">CONTACT</div>
66
+ <div class="space-y-1 text-slate-200 print:text-slate-700">
67
+ {directContactLine && <div class="font-medium">{directContactLine}</div>}
68
+ {contactItems
69
+ .filter((item) => item.label !== basics.contact.email && item.label !== basics.contact.phone)
70
+ .map((item) =>
78
71
  item.href ? (
79
- <a class="underline underline-offset-2" href={item.href}>
72
+ <a class="underline underline-offset-2 print:no-underline" href={item.href}>
80
73
  {item.label}
81
74
  </a>
82
75
  ) : (
@@ -88,22 +81,22 @@ const hasDeclaration = Boolean(declaration.text || declaration.place || declarat
88
81
  )}
89
82
 
90
83
  {skills.length > 0 && (
91
- <div class="space-y-3">
92
- <div class="text-sm text-white/70">SKILLS</div>
84
+ <div class="av-print-avoid-break space-y-3">
85
+ <div class="text-sm font-semibold text-slate-100 print:text-slate-900">SKILLS</div>
93
86
  <div class="flex flex-wrap gap-2">
94
87
  {skills.map((skill) => (
95
- <span class="rounded-full bg-white/10 px-3 py-1 text-xs">{skill.name}</span>
88
+ <span class="rounded-full bg-white/10 px-3 py-1 text-xs text-slate-100 print:border print:border-slate-200 print:bg-slate-50 print:text-slate-800">{skill.name}</span>
96
89
  ))}
97
90
  </div>
98
91
  </div>
99
92
  )}
100
93
 
101
94
  {links.length > 0 && (
102
- <div class="space-y-2">
103
- <div class="text-sm text-white/70">LINKS</div>
104
- <div class="space-y-1 text-sm text-white/90">
95
+ <div class="av-print-avoid-break space-y-2">
96
+ <div class="text-sm font-semibold text-slate-100 print:text-slate-900">LINKS</div>
97
+ <div class="space-y-1 text-sm text-slate-200 print:text-slate-700">
105
98
  {links.map((link) => (
106
- <a class="underline underline-offset-2" href={link.url}>
99
+ <a class="underline underline-offset-2 print:no-underline" href={link.url}>
107
100
  {link.label ?? formatUrlLabel(link.url)}
108
101
  </a>
109
102
  ))}
@@ -112,23 +105,24 @@ const hasDeclaration = Boolean(declaration.text || declaration.place || declarat
112
105
  )}
113
106
 
114
107
  {languages.length > 0 && (
115
- <div class="space-y-2">
116
- <div class="text-sm text-white/70">LANGUAGES</div>
117
- <div class="text-sm text-white/90">
118
- {languages
119
- .map((language) =>
120
- language.proficiency ? `${language.name} (${language.proficiency})` : language.name
121
- )
122
- .join(" · ")}
123
- </div>
108
+ <div class="av-print-avoid-break space-y-2">
109
+ <div class="text-sm font-semibold text-slate-100 print:text-slate-900">LANGUAGES</div>
110
+ <ul class="list-disc space-y-1 pl-5 text-sm text-slate-200 print:text-slate-700">
111
+ {languages.map((language) => (
112
+ <li>
113
+ {language.name}
114
+ {language.proficiency ? ` (${language.proficiency})` : ""}
115
+ </li>
116
+ ))}
117
+ </ul>
124
118
  </div>
125
119
  )}
126
120
  </div>
127
121
  </aside>
128
122
 
129
- <div class="p-7 sm:p-10 lg:col-span-8">
123
+ <div class="p-7 sm:p-10 lg:col-span-8 print:order-1">
130
124
  {basics.summary && (
131
- <section>
125
+ <section class="av-print-avoid-break">
132
126
  <h2 class="text-xs font-bold tracking-widest text-slate-500">PROFILE</h2>
133
127
  <p class="mt-3 text-sm leading-6 text-slate-700">{basics.summary}</p>
134
128
  </section>
@@ -141,7 +135,7 @@ const hasDeclaration = Boolean(declaration.text || declaration.place || declarat
141
135
  <h2 class="text-xs font-bold tracking-widest text-slate-500">EXPERIENCE</h2>
142
136
  <div class="mt-5 space-y-6">
143
137
  {experienceItems.map((item) => (
144
- <article>
138
+ <article class="av-print-avoid-break">
145
139
  <div class="flex items-start justify-between gap-4">
146
140
  <div>
147
141
  <h3 class="text-base font-semibold">{item.role}</h3>
@@ -176,7 +170,7 @@ const hasDeclaration = Boolean(declaration.text || declaration.place || declarat
176
170
  <h2 class="text-xs font-bold tracking-widest text-slate-500">PROJECTS</h2>
177
171
  <div class="mt-4 space-y-4">
178
172
  {projects.map((project) => (
179
- <div class="rounded-xl border border-slate-200 p-4">
173
+ <div class="av-print-avoid-break rounded-xl border border-slate-200 p-4">
180
174
  <div class="flex items-baseline justify-between">
181
175
  {project.link ? (
182
176
  <a class="font-semibold underline underline-offset-4" href={project.link}>
@@ -211,7 +205,7 @@ const hasDeclaration = Boolean(declaration.text || declaration.place || declarat
211
205
  <h2 class="text-xs font-bold tracking-widest text-slate-500">EDUCATION</h2>
212
206
  <div class="mt-4 space-y-4">
213
207
  {education.map((edu) => (
214
- <div class="rounded-xl border border-slate-200 p-4">
208
+ <div class="av-print-avoid-break rounded-xl border border-slate-200 p-4">
215
209
  <h3 class="font-semibold">{edu.degree}</h3>
216
210
  <p class="text-sm text-slate-600">{edu.school}</p>
217
211
  {edu.location && (
@@ -242,7 +236,7 @@ const hasDeclaration = Boolean(declaration.text || declaration.place || declarat
242
236
  </section>
243
237
 
244
238
  {hasDeclaration && (
245
- <section>
239
+ <section class="av-print-avoid-break">
246
240
  <div class="my-8 h-px bg-slate-200"></div>
247
241
  <h2 class="text-xs font-bold tracking-widest text-slate-500">DECLARATION</h2>
248
242
  {declaration.text && (
@@ -1975,6 +1975,11 @@
1975
1975
  color: var(--av-text);
1976
1976
  }
1977
1977
 
1978
+ .av-print-avoid-break {
1979
+ break-inside: avoid;
1980
+ page-break-inside: avoid;
1981
+ }
1982
+
1978
1983
  /* Loading bar utility */
1979
1984
  .av-loading-bar {
1980
1985
  height: 4px;