@ansiversa/components 0.0.106 → 0.0.118

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/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { default as WebLayout } from "./src/layouts/WebLayout.astro";
2
2
  export { default as AuthLayout } from "./src/layouts/AuthLayout.astro";
3
+ export { default as AvMiniAppBar } from "./src/layouts/AvMiniAppBar.astro";
3
4
  export { default as AvBrand } from './src/AvBrand.astro';
4
5
  export { default as AvButton } from './src/AvButton.astro';
5
6
  export { default as AvCard } from './src/AvCard.astro';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ansiversa/components",
3
- "version": "0.0.106",
3
+ "version": "0.0.118",
4
4
  "description": "Shared UI components and layouts for the Ansiversa ecosystem",
5
5
  "type": "module",
6
6
  "exports": {
@@ -127,6 +127,12 @@ export const resumeData: ResumeData = {
127
127
  { name: "Tamil", proficiency: "Native" },
128
128
  ],
129
129
 
130
+ declaration: {
131
+ text: "I hereby declare that the information furnished above is true to the best of my knowledge.",
132
+ place: "Pondicherry – India.",
133
+ name: "Karthikeyan Subbarayan Ramalingam",
134
+ },
135
+
130
136
  settings: {
131
137
  showSkillsAs: "chips",
132
138
  paper: "A4",
@@ -91,6 +91,12 @@ export type ResumeData = {
91
91
  summary?: string;
92
92
  }>;
93
93
 
94
+ declaration?: {
95
+ text?: string;
96
+ place?: string;
97
+ name?: string;
98
+ };
99
+
94
100
  // Template feature flags (optional)
95
101
  settings?: {
96
102
  showSkillsAs?: "chips" | "levels"; // chips for classic/modern, levels for minimal
@@ -0,0 +1,73 @@
1
+ ---
2
+ import type { MiniAppLink } from "./miniAppRegistry";
3
+ import { MINI_APP_REGISTRY } from "./miniAppRegistry";
4
+
5
+ interface Props {
6
+ appKey: string;
7
+ links?: MiniAppLink[];
8
+ }
9
+
10
+ const { appKey, links } = Astro.props as Props;
11
+
12
+ const meta = MINI_APP_REGISTRY[appKey];
13
+ const appName = meta?.name ?? appKey;
14
+
15
+ let menuLinks = links ?? meta?.links ?? [];
16
+ if (!menuLinks.length) {
17
+ menuLinks = [{ label: "Home", href: "/" }];
18
+ }
19
+ ---
20
+
21
+ <div class="av-mini-app-bar">
22
+ <div class="av-mini-app-bar__inner">
23
+ <div class="av-mini-app-bar__title" title={appName}>
24
+ <span class="av-mini-app-bar__name">{appName}</span>
25
+ </div>
26
+
27
+ <div class="av-mini-app-bar__menu" x-data="{ open: false }">
28
+ <button
29
+ type="button"
30
+ class="av-mini-app-bar__menu-button"
31
+ aria-label="Open app menu"
32
+ aria-haspopup="menu"
33
+ x-on:click="open = !open"
34
+ x-bind:aria-expanded="open"
35
+ >
36
+ <svg
37
+ class="av-mini-app-bar__menu-icon"
38
+ width="18"
39
+ height="18"
40
+ viewBox="0 0 24 24"
41
+ fill="none"
42
+ stroke="currentColor"
43
+ stroke-width="2"
44
+ stroke-linecap="round"
45
+ stroke-linejoin="round"
46
+ aria-hidden="true"
47
+ >
48
+ <circle cx="12" cy="5" r="1.5" />
49
+ <circle cx="12" cy="12" r="1.5" />
50
+ <circle cx="12" cy="19" r="1.5" />
51
+ </svg>
52
+ </button>
53
+
54
+ <div
55
+ x-show="open"
56
+ x-cloak
57
+ x-transition.origin.top.right
58
+ x-on:click.outside="open = false"
59
+ class="av-user-menu av-mini-app-menu"
60
+ role="menu"
61
+ aria-label={`${appName} menu`}
62
+ >
63
+ <div class="av-nav-user-menu">
64
+ {menuLinks.map((item) => (
65
+ <a href={item.href} class="av-dropdown-item" role="menuitem">
66
+ <span>{item.label}</span>
67
+ </a>
68
+ ))}
69
+ </div>
70
+ </div>
71
+ </div>
72
+ </div>
73
+ </div>
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  import AvBrand from "../AvBrand.astro";
3
3
  import AvFooter from "../AvFooter.astro";
4
+ import AvMiniAppBar from "./AvMiniAppBar.astro";
4
5
  import AvNavbar from "../AvNavbar.astro";
5
6
  import AvNavbarActions from "../AvNavbarActions.astro";
6
7
 
@@ -8,12 +9,14 @@ interface Props {
8
9
  title?: string;
9
10
  description?: string;
10
11
  notificationUnreadCount?: number;
12
+ miniAppKey?: string;
11
13
  }
12
14
 
13
15
  const {
14
16
  title = "Ansiversa",
15
17
  description = "Ansiversa – A unified ecosystem of premium Apps designed to feel simple, powerful, and delightful.",
16
18
  notificationUnreadCount = 0,
19
+ miniAppKey,
17
20
  } = Astro.props;
18
21
 
19
22
  const user = Astro.locals.user;
@@ -77,6 +80,8 @@ const ROOT_URL = rawDomain.match(/^https?:\/\//i)
77
80
  <AvNavbarActions user={user} notificationUnreadCount={notificationUnreadCount} />
78
81
  </AvNavbar>
79
82
 
83
+ {miniAppKey && <AvMiniAppBar appKey={miniAppKey} />}
84
+
80
85
  <!-- Main content -->
81
86
  <main class="av-main">
82
87
  <slot />
@@ -0,0 +1,25 @@
1
+ export type MiniAppLink = { label: string; href: string };
2
+ export type MiniAppMeta = { name: string; links: MiniAppLink[] };
3
+
4
+ export const MINI_APP_REGISTRY: Record<string, MiniAppMeta> = {
5
+ "quiz": {
6
+ name: "Quiz",
7
+ links: [{ label: "Home", href: "/" }],
8
+ },
9
+ "resume-builder": {
10
+ name: "Resume Builder",
11
+ links: [{ label: "Home", href: "/" }],
12
+ },
13
+ "portfolio-creator": {
14
+ name: "Portfolio Creator",
15
+ links: [{ label: "Home", href: "/" }],
16
+ },
17
+ "app-starter": {
18
+ name: "App Starter",
19
+ links: [{ label: "Home", href: "/" }],
20
+ },
21
+ "flashnote": {
22
+ name: "FlashNote",
23
+ links: [{ label: "Home", href: "/" }],
24
+ },
25
+ };
@@ -29,15 +29,22 @@ if (basics.contact.website) {
29
29
  contactItems.push({ label: formatUrlLabel(basics.contact.website), href: basics.contact.website });
30
30
  }
31
31
  for (const link of basics.links ?? []) {
32
- contactItems.push({ label: link.label ?? formatUrlLabel(link.url), href: link.url });
32
+ const urlLabel = link.url ? formatUrlLabel(link.url) : link.label;
33
+ if (urlLabel) {
34
+ contactItems.push({ label: urlLabel, href: link.url });
35
+ }
33
36
  }
34
37
 
35
38
  const experienceItems = data.experience ?? [];
36
39
  const projectItems = data.projects ?? [];
37
40
  const educationItems = data.education ?? [];
38
41
  const skills = data.skills ?? [];
42
+ const certifications = data.certifications ?? [];
39
43
  const highlights = data.highlights ?? [];
40
44
  const languages = data.languages ?? [];
45
+ const awards = data.awards ?? [];
46
+ const declaration = data.declaration ?? {};
47
+ const hasDeclaration = Boolean(declaration.text || declaration.place || declaration.name);
41
48
  ---
42
49
 
43
50
  <style>
@@ -46,6 +53,19 @@ const languages = data.languages ?? [];
46
53
  text-decoration: none;
47
54
  color: inherit;
48
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
+ }
49
69
  }
50
70
  </style>
51
71
 
@@ -57,13 +77,13 @@ const languages = data.languages ?? [];
57
77
  <h1 class="text-3xl font-bold tracking-tight">{basics.fullName}</h1>
58
78
  <p class="mt-1 text-base font-medium text-slate-700">{basics.headline}</p>
59
79
  {basics.summary && (
60
- <p class="mt-3 max-w-2xl text-sm leading-6 text-slate-600">{basics.summary}</p>
80
+ <p class="mt-3 text-sm leading-6 text-slate-600">{basics.summary}</p>
61
81
  )}
62
82
  </div>
63
83
 
64
84
  {contactItems.length > 0 && (
65
85
  <div class="text-sm text-slate-700 sm:text-right">
66
- <div class="flex flex-col gap-1">
86
+ <div class="flex flex-col gap-1 sm:items-end">
67
87
  {contactItems.map((item) =>
68
88
  item.href ? (
69
89
  <a
@@ -81,9 +101,9 @@ const languages = data.languages ?? [];
81
101
  )}
82
102
  </header>
83
103
 
84
- <div class="my-8 h-px bg-slate-200"></div>
104
+ <div class="classic-divider my-8 h-0 border-t border-slate-200"></div>
85
105
 
86
- <div class="grid gap-10 lg:grid-cols-3">
106
+ <div class="classic-grid grid gap-10 lg:grid-cols-3">
87
107
  <div class="space-y-10 lg:col-span-2">
88
108
  {experienceItems.length > 0 && (
89
109
  <section>
@@ -203,6 +223,29 @@ const languages = data.languages ?? [];
203
223
  </section>
204
224
  )}
205
225
 
226
+ {certifications.length > 0 && (
227
+ <section>
228
+ <h2 class="text-sm font-bold tracking-widest text-slate-900">CERTIFICATIONS</h2>
229
+ <ul class="mt-4 space-y-3 text-sm text-slate-700">
230
+ {certifications.map((cert) => {
231
+ const meta = [cert.issuer, cert.year].filter(Boolean).join(" · ");
232
+ return (
233
+ <li class="space-y-1">
234
+ {cert.link ? (
235
+ <a class="font-semibold text-slate-900 underline underline-offset-2" href={cert.link}>
236
+ {cert.name}
237
+ </a>
238
+ ) : (
239
+ <div class="font-semibold text-slate-900">{cert.name}</div>
240
+ )}
241
+ {meta && <div class="text-xs text-slate-600">{meta}</div>}
242
+ </li>
243
+ );
244
+ })}
245
+ </ul>
246
+ </section>
247
+ )}
248
+
206
249
  {highlights.length > 0 && (
207
250
  <section>
208
251
  <h2 class="text-sm font-bold tracking-widest text-slate-900">HIGHLIGHTS</h2>
@@ -227,8 +270,51 @@ const languages = data.languages ?? [];
227
270
  </ul>
228
271
  </section>
229
272
  )}
273
+
274
+ {awards.length > 0 && (
275
+ <section>
276
+ <h2 class="text-sm font-bold tracking-widest text-slate-900">ACHIEVEMENTS</h2>
277
+ <ul class="mt-4 space-y-3 text-sm text-slate-700">
278
+ {awards.map((award) => {
279
+ const meta = [award.by, award.year].filter(Boolean).join(" · ");
280
+ return (
281
+ <li class="space-y-1">
282
+ <div class="font-semibold text-slate-900">{award.title}</div>
283
+ {meta && <div class="text-xs text-slate-600">{meta}</div>}
284
+ {award.summary && <div class="text-sm text-slate-700">{award.summary}</div>}
285
+ </li>
286
+ );
287
+ })}
288
+ </ul>
289
+ </section>
290
+ )}
230
291
  </aside>
231
292
  </div>
293
+
294
+ {hasDeclaration && (
295
+ <>
296
+ <div class="classic-divider my-8 h-0 border-t border-slate-200"></div>
297
+ <section class="space-y-4">
298
+ <h2 class="text-sm font-bold tracking-widest text-slate-900">DECLARATION</h2>
299
+ {declaration.text && (
300
+ <p class="text-sm leading-6 text-slate-700">{declaration.text}</p>
301
+ )}
302
+ {(declaration.place || declaration.name) && (
303
+ <div class="flex flex-wrap items-center justify-between gap-2 text-sm text-slate-700">
304
+ {declaration.place && (
305
+ <div>
306
+ <span class="font-semibold text-slate-900">Place:</span>{" "}
307
+ {declaration.place}
308
+ </div>
309
+ )}
310
+ {declaration.name && (
311
+ <div class="font-semibold text-slate-900">{declaration.name}</div>
312
+ )}
313
+ </div>
314
+ )}
315
+ </section>
316
+ </>
317
+ )}
232
318
  </section>
233
319
  </main>
234
320
  </div>
@@ -30,6 +30,8 @@ const projects = data.projects ?? [];
30
30
  const skills = data.skills ?? [];
31
31
  const education = data.education ?? [];
32
32
  const highlights = data.highlights ?? [];
33
+ const declaration = data.declaration ?? {};
34
+ const hasDeclaration = Boolean(declaration.text || declaration.place || declaration.name);
33
35
  ---
34
36
 
35
37
  <style>
@@ -183,6 +185,31 @@ const highlights = data.highlights ?? [];
183
185
  )}
184
186
  </aside>
185
187
  </div>
188
+
189
+ {hasDeclaration && (
190
+ <>
191
+ <div class="my-8 h-px bg-slate-200"></div>
192
+ <section class="space-y-4">
193
+ <h2 class="text-sm font-bold tracking-widest text-slate-900">DECLARATION</h2>
194
+ {declaration.text && (
195
+ <p class="text-sm leading-6 text-slate-700">{declaration.text}</p>
196
+ )}
197
+ {(declaration.place || declaration.name) && (
198
+ <div class="flex flex-wrap items-center justify-between gap-2 text-sm text-slate-700">
199
+ {declaration.place && (
200
+ <div>
201
+ <span class="font-semibold text-slate-900">Place:</span>{" "}
202
+ {declaration.place}
203
+ </div>
204
+ )}
205
+ {declaration.name && (
206
+ <div class="font-semibold text-slate-900">{declaration.name}</div>
207
+ )}
208
+ </div>
209
+ )}
210
+ </section>
211
+ </>
212
+ )}
186
213
  </section>
187
214
  </main>
188
215
  </div>
@@ -38,6 +38,8 @@ const skills = data.skills ?? [];
38
38
  const education = data.education ?? [];
39
39
  const certifications = data.certifications ?? [];
40
40
  const showSkillsAs = data.settings?.showSkillsAs ?? "levels";
41
+ const declaration = data.declaration ?? {};
42
+ const hasDeclaration = Boolean(declaration.text || declaration.place || declaration.name);
41
43
  ---
42
44
 
43
45
  <style>
@@ -214,6 +216,31 @@ const showSkillsAs = data.settings?.showSkillsAs ?? "levels";
214
216
  )}
215
217
  </aside>
216
218
  </div>
219
+
220
+ {hasDeclaration && (
221
+ <>
222
+ <div class="my-10 h-px bg-slate-200"></div>
223
+ <section class="space-y-4">
224
+ <h2 class="text-xs font-semibold tracking-[0.25em] text-slate-500">DECLARATION</h2>
225
+ {declaration.text && (
226
+ <p class="text-sm leading-7 text-slate-700">{declaration.text}</p>
227
+ )}
228
+ {(declaration.place || declaration.name) && (
229
+ <div class="flex flex-wrap items-center justify-between gap-2 text-sm text-slate-700">
230
+ {declaration.place && (
231
+ <div>
232
+ <span class="font-semibold text-slate-900">Place:</span>{" "}
233
+ {declaration.place}
234
+ </div>
235
+ )}
236
+ {declaration.name && (
237
+ <div class="font-semibold text-slate-900">{declaration.name}</div>
238
+ )}
239
+ </div>
240
+ )}
241
+ </section>
242
+ </>
243
+ )}
217
244
  </section>
218
245
  </main>
219
246
  </div>
@@ -40,6 +40,8 @@ const experienceItems = data.experience ?? [];
40
40
  const projects = data.projects ?? [];
41
41
  const education = data.education ?? [];
42
42
  const highlights = data.highlights ?? [];
43
+ const declaration = data.declaration ?? {};
44
+ const hasDeclaration = Boolean(declaration.text || declaration.place || declaration.name);
43
45
  ---
44
46
 
45
47
  <style>
@@ -238,6 +240,29 @@ const highlights = data.highlights ?? [];
238
240
  )}
239
241
  </div>
240
242
  </section>
243
+
244
+ {hasDeclaration && (
245
+ <section>
246
+ <div class="my-8 h-px bg-slate-200"></div>
247
+ <h2 class="text-xs font-bold tracking-widest text-slate-500">DECLARATION</h2>
248
+ {declaration.text && (
249
+ <p class="mt-3 text-sm leading-6 text-slate-700">{declaration.text}</p>
250
+ )}
251
+ {(declaration.place || declaration.name) && (
252
+ <div class="mt-3 flex flex-wrap items-center justify-between gap-2 text-sm text-slate-700">
253
+ {declaration.place && (
254
+ <div>
255
+ <span class="font-semibold text-slate-900">Place:</span>{" "}
256
+ {declaration.place}
257
+ </div>
258
+ )}
259
+ {declaration.name && (
260
+ <div class="font-semibold text-slate-900">{declaration.name}</div>
261
+ )}
262
+ </div>
263
+ )}
264
+ </section>
265
+ )}
241
266
  </div>
242
267
  </div>
243
268
  </section>
@@ -1,4 +1,5 @@
1
1
  @import "tailwindcss";
2
+ @source "../**/*.{astro,js,jsx,ts,tsx}";
2
3
 
3
4
  /* =========================================
4
5
  Ansiversa Design Tokens
@@ -1403,6 +1404,39 @@
1403
1404
  @apply text-slate-50;
1404
1405
  }
1405
1406
 
1407
+ /* Mini-app context bar ------------------------------- */
1408
+
1409
+ .av-mini-app-bar {
1410
+ @apply border-b border-slate-800/60 bg-slate-950/70 backdrop-blur-xl;
1411
+ position: relative;
1412
+ z-index: 40;
1413
+ }
1414
+
1415
+ .av-mini-app-bar__inner {
1416
+ @apply w-full max-w-none px-4 py-2 flex items-center justify-between gap-3;
1417
+ }
1418
+
1419
+ .av-mini-app-bar__title {
1420
+ @apply text-sm font-medium text-slate-100;
1421
+ min-width: 0;
1422
+ }
1423
+
1424
+ .av-mini-app-bar__name {
1425
+ @apply block truncate;
1426
+ }
1427
+
1428
+ .av-mini-app-bar__menu {
1429
+ @apply relative;
1430
+ }
1431
+
1432
+ .av-mini-app-bar__menu-button {
1433
+ @apply inline-flex h-8 w-8 items-center justify-center rounded-lg border border-slate-800/70 bg-slate-950/60 text-slate-200 transition hover:border-slate-600 hover:text-slate-50;
1434
+ }
1435
+
1436
+ .av-mini-app-menu {
1437
+ @apply w-56 mt-2;
1438
+ }
1439
+
1406
1440
  /* Badges / Pills --------------------------------------- */
1407
1441
 
1408
1442
  .av-badge {