@brightsideoy/kama 0.1.0-alpha.113 → 0.1.0-alpha.115

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 (60) hide show
  1. package/bin/cli.js +172 -379
  2. package/content/definitions/blocks/button.yaml +7 -8
  3. package/content/definitions/blocks/header.yaml +7 -9
  4. package/content/definitions/blocks/header_image.yaml +14 -15
  5. package/content/definitions/blocks/hero.yaml +22 -21
  6. package/content/definitions/blocks/image_card.yaml +46 -47
  7. package/content/definitions/blocks/single_image.yaml +19 -20
  8. package/content/definitions/blocks/text.yaml +8 -9
  9. package/content/definitions/channels/ecommerce.yaml +14 -16
  10. package/content/definitions/channels/website.yaml +20 -21
  11. package/content/definitions/field-sets/access.yaml +3 -5
  12. package/content/definitions/field-sets/channel-base.yaml +25 -25
  13. package/content/definitions/field-sets/content-block.yaml +36 -41
  14. package/content/definitions/field-sets/page-base.yaml +40 -45
  15. package/content/definitions/field-sets/page-meta.yaml +1 -4
  16. package/content/definitions/field-sets/page-social.yaml +6 -8
  17. package/content/definitions/field-sets/tree-base.yaml +25 -25
  18. package/content/definitions/nodes/address.yaml +29 -21
  19. package/content/definitions/nodes/article.yaml +24 -19
  20. package/content/definitions/nodes/event.yaml +32 -26
  21. package/content/definitions/nodes/external-link.yaml +20 -17
  22. package/content/definitions/nodes/page.yaml +38 -35
  23. package/content/definitions/nodes/person.yaml +29 -27
  24. package/content/definitions/nodes/product.yaml +52 -49
  25. package/content/definitions/trees/navigation.yaml +26 -21
  26. package/content/definitions/trees/social-links.yaml +5 -2
  27. package/dist/assets/index-8T39DEP3.js +162 -0
  28. package/dist/assets/index-B9oh2_TL.css +1 -0
  29. package/dist/index.html +2 -2
  30. package/dist/index.js +6492 -4249
  31. package/package.json +5 -6
  32. package/schema.sql +5 -13
  33. package/templates/astro-starter/astro.config.mjs +40 -0
  34. package/templates/astro-starter/package.json.template +20 -0
  35. package/templates/astro-starter/src/components/blocks/BlockRenderer.astro +125 -0
  36. package/templates/astro-starter/src/components/blocks/ColumnLayout.astro +66 -0
  37. package/templates/astro-starter/src/components/blocks/HeaderImage.astro +131 -0
  38. package/templates/astro-starter/src/components/blocks/Hero.astro +218 -0
  39. package/templates/astro-starter/src/components/blocks/ImageBlock.astro +160 -0
  40. package/templates/astro-starter/src/components/blocks/TextBlock.astro +13 -0
  41. package/templates/astro-starter/src/components/global/Breadcrumb.astro +62 -0
  42. package/templates/astro-starter/src/components/global/Footer.astro +48 -0
  43. package/templates/astro-starter/src/components/global/Header.astro +92 -0
  44. package/templates/astro-starter/src/components/global/NavigationMenu.astro +450 -0
  45. package/templates/astro-starter/src/components/ui/KamaImage.astro +148 -0
  46. package/templates/astro-starter/src/kama-image-service.ts +41 -0
  47. package/templates/astro-starter/src/layouts/Layout.astro +280 -0
  48. package/templates/astro-starter/src/lib/cms.ts +149 -0
  49. package/templates/astro-starter/src/lib/media-url.ts +34 -0
  50. package/templates/astro-starter/src/middleware.ts +306 -0
  51. package/templates/astro-starter/src/pages/404.astro +46 -0
  52. package/templates/astro-starter/src/pages/[...slug].astro +234 -0
  53. package/templates/astro-starter/src/pages/media/[...path].ts +113 -0
  54. package/templates/astro-starter/tsconfig.json +0 -0
  55. package/test-local.sh +76 -0
  56. package/dist/assets/index-10GEw0KY.css +0 -1
  57. package/dist/assets/index-THJ6BqPP.js +0 -1394
  58. package/kama-worker/bundled-definitions.js +0 -2394
  59. package/kama-worker/bundled-definitions.ts +0 -2395
  60. package/scripts/bundle-definitions.js +0 -224
@@ -0,0 +1,450 @@
1
+ ---
2
+ // src/components/global/NavigationMenu.astro
3
+ import type { TreeNode } from "@/lib/cms";
4
+
5
+ interface Props {
6
+ tree: TreeNode[];
7
+ }
8
+
9
+ const { tree = [] } = Astro.props;
10
+
11
+ // 1. Get current path from Astro context
12
+ const currentPath = Astro.url.pathname.replace(/\/$/, "") || "/";
13
+
14
+ const homeNode = tree.find(
15
+ (item: TreeNode) => item.slug === "home" || item.path === "/",
16
+ );
17
+
18
+ const topLevelNavItems =
19
+ homeNode?.children && homeNode.children.length > 0
20
+ ? homeNode.children
21
+ : tree.filter((item: TreeNode) => item.slug !== "home");
22
+
23
+ // Normalize path comparison helper
24
+ const normalizePath = (path?: string) => {
25
+ if (!path) return "";
26
+ return path.replace(/\/$/, "") || "/";
27
+ };
28
+
29
+ // Active trail check (parent active when on a child page)
30
+ const isPathActive = (itemPath?: string) => {
31
+ const normItem = normalizePath(itemPath);
32
+ if (normItem === "/") return currentPath === "/";
33
+ return currentPath === normItem || currentPath.startsWith(`${normItem}/`);
34
+ };
35
+
36
+ // Exact path check
37
+ const isExactActive = (itemPath?: string) => {
38
+ return currentPath === normalizePath(itemPath);
39
+ };
40
+ ---
41
+
42
+ <!-- 2-LINE HAMBURGER TOGGLE BUTTON -->
43
+ <button
44
+ type="button"
45
+ class="mobile-toggle"
46
+ id="menu-toggle"
47
+ aria-label="Toggle Navigation Menu"
48
+ aria-expanded="false"
49
+ >
50
+ <span class="line line-1"></span>
51
+ <span class="line line-2"></span>
52
+ </button>
53
+
54
+ <!-- NAVIGATION BLOCK & FULLSCREEN CONTAINER -->
55
+ <nav class="nav-block" id="nav-overlay">
56
+ <ul class="nav-list">
57
+ {
58
+ topLevelNavItems.map((item: TreeNode) => {
59
+ const hasChildren = item.children && item.children.length > 0;
60
+ const isParentActive = isPathActive(item.path);
61
+
62
+ if (hasChildren) {
63
+ return (
64
+ <li class="nav-item">
65
+ <details class="nav-details">
66
+ <summary
67
+ class:list={[
68
+ "nav-button",
69
+ "dropdown-summary",
70
+ { active: isParentActive },
71
+ ]}
72
+ >
73
+ <span>{item.title}</span>
74
+ <span class="indicator">▼</span>
75
+ </summary>
76
+ <div class="submenu-container">
77
+ <ul class="dropdown-panel">
78
+ <li>
79
+ <a
80
+ href={item.path}
81
+ class:list={[
82
+ "dropdown-item",
83
+ "overview-item",
84
+ { active: isExactActive(item.path) },
85
+ ]}
86
+ >
87
+ {item.title} Overview
88
+ </a>
89
+ </li>
90
+ {item.children?.map((child: TreeNode) => (
91
+ <li>
92
+ <a
93
+ href={child.path}
94
+ class:list={[
95
+ "dropdown-item",
96
+ { active: isExactActive(child.path) },
97
+ ]}
98
+ >
99
+ {child.title}
100
+ </a>
101
+ </li>
102
+ ))}
103
+ </ul>
104
+ </div>
105
+ </details>
106
+ </li>
107
+ );
108
+ }
109
+
110
+ return (
111
+ <li class="nav-item">
112
+ <a
113
+ href={item.path}
114
+ class:list={["nav-button", { active: isExactActive(item.path) }]}
115
+ >
116
+ {item.title}
117
+ </a>
118
+ </li>
119
+ );
120
+ })
121
+ }
122
+ </ul>
123
+ </nav>
124
+ <script>
125
+ const toggleBtn = document.getElementById("menu-toggle");
126
+ const navOverlay = document.getElementById("nav-overlay");
127
+
128
+ const closeMenu = () => {
129
+ if (toggleBtn && navOverlay) {
130
+ toggleBtn.setAttribute("aria-expanded", "false");
131
+ toggleBtn.classList.remove("is-open");
132
+ navOverlay.classList.remove("is-open");
133
+ document.body.classList.remove("menu-lock-scroll");
134
+ }
135
+ };
136
+
137
+ if (toggleBtn && navOverlay) {
138
+ // Toggle Menu
139
+ toggleBtn.addEventListener("click", () => {
140
+ const isOpen = toggleBtn.getAttribute("aria-expanded") === "true";
141
+ toggleBtn.setAttribute("aria-expanded", (!isOpen).toString());
142
+ toggleBtn.classList.toggle("is-open");
143
+ navOverlay.classList.toggle("is-open");
144
+ document.body.classList.toggle("menu-lock-scroll");
145
+ });
146
+
147
+ // 🎯 UPDATED LINK CLICK LOGIC
148
+ const navLinks = navOverlay.querySelectorAll("a");
149
+ navLinks.forEach(link => {
150
+ link.addEventListener("click", e => {
151
+ // Check where the link is going
152
+ const targetUrl = new URL(link.href);
153
+ const currentUrl = new window.URL(window.location.href);
154
+
155
+ // If it's a link to the exact page we are already on (like a #hash anchor),
156
+ // close the menu so the user can see the page.
157
+ if (targetUrl.pathname === currentUrl.pathname) {
158
+ closeMenu();
159
+ }
160
+ // If it's a new page, DO NOTHING. The menu stays visually open,
161
+ // acting as a loading screen until the browser paints the new page!
162
+ });
163
+ });
164
+ }
165
+
166
+ // Close menu on back/forward browser navigation (bfcache)
167
+ window.addEventListener("pageshow", () => {
168
+ closeMenu();
169
+ });
170
+
171
+ // Close desktop dropdowns when clicking outside
172
+ document.addEventListener("click", event => {
173
+ const allDetails = document.querySelectorAll("details.nav-details");
174
+ allDetails.forEach(details => {
175
+ if (
176
+ details.hasAttribute("open") &&
177
+ !details.contains(event.target as Node)
178
+ ) {
179
+ details.removeAttribute("open");
180
+ }
181
+ });
182
+ });
183
+ </script>
184
+
185
+ <style>
186
+ /* -------------------------------------------------------------
187
+ MOBILE HAMBURGER BUTTON (2-LINE TRANSFORMING TO X)
188
+ ------------------------------------------------------------- */
189
+ .mobile-toggle {
190
+ display: flex;
191
+ flex-direction: column;
192
+ justify-content: center;
193
+ align-items: center;
194
+ gap: 6px;
195
+ width: 44px;
196
+ height: 44px;
197
+ background: transparent;
198
+ border: none;
199
+ cursor: pointer;
200
+ padding: 0;
201
+ z-index: 1001;
202
+ }
203
+
204
+ .mobile-toggle .line {
205
+ width: 22px;
206
+ height: 2px;
207
+ background-color: #0f172a;
208
+ transition:
209
+ transform 0.25s cubic-bezier(0.4, 0, 0.2, 1),
210
+ opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1);
211
+ transform-origin: center;
212
+ }
213
+
214
+ .mobile-toggle.is-open .line-1 {
215
+ transform: translateY(4px) rotate(45deg);
216
+ }
217
+
218
+ .mobile-toggle.is-open .line-2 {
219
+ transform: translateY(-4px) rotate(-45deg);
220
+ }
221
+
222
+ /* -------------------------------------------------------------
223
+ MOBILE FULLSCREEN OVERLAY (DEFAULT MOBILE VIEW < 768px)
224
+ ------------------------------------------------------------- */
225
+ .nav-block {
226
+ position: fixed;
227
+ top: 0;
228
+ left: 0;
229
+ width: 100vw;
230
+ height: 100vh;
231
+ background-color: #ffffff;
232
+ padding: 80px 24px 40px 24px;
233
+ box-sizing: border-box;
234
+ display: flex;
235
+ flex-direction: column;
236
+ overflow-y: auto;
237
+ opacity: 0;
238
+ visibility: hidden;
239
+ pointer-events: none;
240
+ transition:
241
+ opacity 0.3s ease,
242
+ visibility 0.3s ease;
243
+ z-index: 1000;
244
+ }
245
+
246
+ .nav-block.is-open {
247
+ opacity: 1;
248
+ visibility: visible;
249
+ pointer-events: auto;
250
+ }
251
+
252
+ .nav-list {
253
+ display: flex;
254
+ flex-direction: column;
255
+ gap: 8px;
256
+ list-style: none;
257
+ margin: 0;
258
+ padding: 0;
259
+ width: 100%;
260
+ }
261
+
262
+ .nav-item {
263
+ position: relative;
264
+ width: 100%;
265
+ }
266
+
267
+ .nav-button {
268
+ display: flex;
269
+ align-items: center;
270
+ justify-content: space-between;
271
+ width: 100%;
272
+ height: 52px;
273
+ padding: 0 16px;
274
+ font-size: 18px;
275
+ font-weight: 500;
276
+ color: #334155;
277
+ text-decoration: none;
278
+ background: transparent;
279
+ border: none;
280
+ border-radius: 4px;
281
+ cursor: pointer;
282
+ box-sizing: border-box;
283
+ }
284
+
285
+ .nav-button:hover {
286
+ color: #0f172a;
287
+ background: #f8fafc;
288
+ }
289
+
290
+ .nav-button.active {
291
+ color: #0c4a6e;
292
+ font-weight: 600;
293
+ }
294
+
295
+ .dropdown-summary {
296
+ list-style: none;
297
+ user-select: none;
298
+ }
299
+
300
+ .dropdown-summary::-webkit-details-marker {
301
+ display: none;
302
+ }
303
+
304
+ .indicator {
305
+ font-size: 11px;
306
+ color: #64748b;
307
+ transition: transform 0.2s ease;
308
+ }
309
+
310
+ details[open] .indicator {
311
+ transform: rotate(180deg);
312
+ }
313
+
314
+ /* OPEN DROPDOWN SINGLE BLOCK WITH CONNECTING LINE & INDENTATION */
315
+ details.nav-details {
316
+ border-radius: 6px;
317
+ transition: background-color 0.2s ease;
318
+ }
319
+
320
+ details[open].nav-details {
321
+ background: #f8fafc;
322
+ padding-bottom: 8px;
323
+ }
324
+
325
+ /* Left vertical accent line connecting parent item and sub-levels */
326
+ .submenu-container {
327
+ position: relative;
328
+ margin-left: 24px;
329
+ padding-left: 16px;
330
+ border-left: 2px solid #cbd5e1;
331
+ }
332
+
333
+ .dropdown-panel {
334
+ list-style: none;
335
+ margin: 4px 0 0 0;
336
+ padding: 0;
337
+ display: flex;
338
+ flex-direction: column;
339
+ gap: 4px;
340
+ }
341
+
342
+ .dropdown-item {
343
+ display: block;
344
+ padding: 10px 12px;
345
+ font-size: 16px;
346
+ font-weight: 400;
347
+ color: #475569;
348
+ text-decoration: none;
349
+ border-radius: 4px;
350
+ transition:
351
+ background 0.15s ease,
352
+ color 0.15s ease;
353
+ }
354
+
355
+ .overview-item {
356
+ font-weight: 500;
357
+ color: #334155;
358
+ }
359
+
360
+ .dropdown-item:hover {
361
+ background: #e2e8f0;
362
+ color: #0f172a;
363
+ }
364
+
365
+ .dropdown-item.active {
366
+ font-weight: 600;
367
+ color: #0c4a6e;
368
+ background: #e0f2fe;
369
+ }
370
+
371
+ /* Prevent body scrolling when menu open */
372
+ :global(body.menu-lock-scroll) {
373
+ overflow: hidden !important;
374
+ }
375
+
376
+ /* -------------------------------------------------------------
377
+ DESKTOP MEDIA QUERY (>= 768px)
378
+ ------------------------------------------------------------- */
379
+ @media (min-width: 768px) {
380
+ .mobile-toggle {
381
+ display: none;
382
+ }
383
+
384
+ .nav-block {
385
+ position: static;
386
+ width: 100%;
387
+ height: auto;
388
+ background: transparent;
389
+ padding: 0;
390
+ opacity: 1;
391
+ visibility: visible;
392
+ pointer-events: auto;
393
+ overflow-y: visible;
394
+ display: block;
395
+ }
396
+
397
+ .nav-list {
398
+ flex-direction: row;
399
+ gap: 4px;
400
+ justify-content: flex-end;
401
+ }
402
+
403
+ .nav-item {
404
+ width: auto;
405
+ }
406
+
407
+ .nav-button {
408
+ display: inline-flex;
409
+ justify-content: flex-start;
410
+ gap: 6px;
411
+ height: 40px;
412
+ padding: 0 16px;
413
+ font-size: 14px;
414
+ border-radius: 4px;
415
+ margin-top: 4px;
416
+ }
417
+
418
+ details[open].nav-details {
419
+ background: transparent;
420
+ padding-bottom: 0;
421
+ }
422
+
423
+ .submenu-container {
424
+ margin-left: 0;
425
+ padding-left: 0;
426
+ border-left: none;
427
+ }
428
+
429
+ .dropdown-panel {
430
+ position: absolute;
431
+ top: 100%;
432
+ left: 0;
433
+ min-width: 220px;
434
+ background: #ffffff;
435
+ border: 1px solid #e2e8f0;
436
+ margin: 0;
437
+ gap: 0;
438
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
439
+ border-radius: 4px;
440
+ padding: 4px;
441
+ }
442
+
443
+ .dropdown-item {
444
+ font-size: 14px;
445
+ padding: 8px 16px;
446
+ border-radius: 4px;
447
+ border-bottom: none;
448
+ }
449
+ }
450
+ </style>
@@ -0,0 +1,148 @@
1
+ ---
2
+ // src/components/ui/KamaImage.astro
3
+
4
+ import { getMediaUrl } from "@/lib/cms";
5
+ import { buildAgnosticImageUrl } from "@/lib/media-url";
6
+
7
+ interface Props {
8
+ image: any;
9
+ sizes?: string;
10
+ desktopWidths?: number[];
11
+ idealWidth?: number;
12
+ class?: string;
13
+ loading?: "lazy" | "eager";
14
+ fetchpriority?: "auto" | "high" | "low";
15
+ }
16
+
17
+ const {
18
+ image = {},
19
+ sizes = "100vw",
20
+ desktopWidths = [768, 1352],
21
+ idealWidth = 1352,
22
+ class: className = "",
23
+ loading = "lazy",
24
+ fetchpriority = "auto",
25
+ } = Astro.props;
26
+
27
+ if (!image || (!image.url && !image.path && !image.variants)) {
28
+ return null;
29
+ }
30
+
31
+ const variants = image.variants || {};
32
+
33
+ /**
34
+ * Resolve CMS media URLs.
35
+ */
36
+ const rawMobileUrl = getMediaUrl(variants.mobile?.url);
37
+ const rawTabletUrl = getMediaUrl(variants.tablet?.url);
38
+ const rawDesktopUrl = getMediaUrl(
39
+ variants.standard?.url || image.url || image.path,
40
+ );
41
+
42
+ /**
43
+ * Resolve dimensions.
44
+ * Crop dimensions take precedence over original image dimensions.
45
+ */
46
+ const mobileW = Number(variants.mobile?.cropCoordinates?.width || 768);
47
+ const mobileH = Number(variants.mobile?.cropCoordinates?.height || 576);
48
+
49
+ const tabletW = Number(variants.tablet?.cropCoordinates?.width || 1024);
50
+ const tabletH = Number(variants.tablet?.cropCoordinates?.height || 512);
51
+
52
+ const baseWidth = Number(
53
+ variants.standard?.cropCoordinates?.width || image.width || idealWidth,
54
+ );
55
+ const baseHeight = Number(
56
+ variants.standard?.cropCoordinates?.height ||
57
+ image.height ||
58
+ Math.round(idealWidth * 0.5),
59
+ );
60
+
61
+ /**
62
+ * Build responsive srcset using query-based media proxy URLs (/media/foo.jpg?w=768&q=75&f=auto)
63
+ */
64
+ function buildAgnosticSrcset(
65
+ url: string,
66
+ targetWidths: number[],
67
+ quality = 75,
68
+ ): string {
69
+ if (!url) return "";
70
+
71
+ return targetWidths
72
+ .map(width => `${buildAgnosticImageUrl(url, width, quality)} ${width}w`)
73
+ .join(", ");
74
+ }
75
+
76
+ /**
77
+ * Build srcsets.
78
+ */
79
+ const mobileSrcset = rawMobileUrl
80
+ ? buildAgnosticSrcset(rawMobileUrl, [400, 768])
81
+ : null;
82
+
83
+ const tabletSrcset = rawTabletUrl
84
+ ? buildAgnosticSrcset(rawTabletUrl, [768, 1024])
85
+ : null;
86
+
87
+ const desktopSrcset = rawDesktopUrl
88
+ ? buildAgnosticSrcset(rawDesktopUrl, desktopWidths)
89
+ : null;
90
+
91
+ /**
92
+ * Fallback image URL: /media/foo.jpg?w=1352&q=75&f=auto
93
+ */
94
+ const absoluteFallback = rawDesktopUrl
95
+ ? buildAgnosticImageUrl(rawDesktopUrl, idealWidth, 75)
96
+ : "";
97
+ ---
98
+
99
+ <picture>
100
+ {/* Mobile Variant (< 768px) */}
101
+ {
102
+ mobileSrcset && (
103
+ <source
104
+ media="(max-width: 767px)"
105
+ srcset={mobileSrcset}
106
+ sizes="(max-width: 767px) calc(100vw - 48px), 768px"
107
+ width={mobileW}
108
+ height={mobileH}
109
+ />
110
+ )
111
+ }
112
+
113
+ {/* Tablet Variant (768px - 1024px) */}
114
+ {
115
+ tabletSrcset && (
116
+ <source
117
+ media="(min-width: 768px) and (max-width: 1024px)"
118
+ srcset={tabletSrcset}
119
+ sizes="(max-width: 1024px) calc(100vw - 48px), 1024px"
120
+ width={tabletW}
121
+ height={tabletH}
122
+ />
123
+ )
124
+ }
125
+
126
+ {/* Desktop Variant (> 1024px) */}
127
+ {
128
+ desktopSrcset && (
129
+ <source
130
+ srcset={desktopSrcset}
131
+ sizes={sizes}
132
+ width={baseWidth}
133
+ height={baseHeight}
134
+ />
135
+ )
136
+ }
137
+
138
+ <img
139
+ src={absoluteFallback}
140
+ alt={image.alt || image.filename || ""}
141
+ width={baseWidth}
142
+ height={baseHeight}
143
+ loading={loading}
144
+ fetchpriority={fetchpriority}
145
+ decoding={loading === "eager" ? "sync" : "async"}
146
+ class={className}
147
+ />
148
+ </picture>
@@ -0,0 +1,41 @@
1
+ // src/kama-image-service.ts
2
+ import type { LocalImageService } from "astro";
3
+
4
+ const kamaImageService: LocalImageService = {
5
+ getURL(options) {
6
+ let src = options.src;
7
+
8
+ if (typeof src === 'object' && 'src' in src) {
9
+ src = src.src;
10
+ }
11
+
12
+ if (typeof src === 'string') {
13
+ try {
14
+ const url = new URL(src, 'http://localhost');
15
+
16
+ if (options.width) url.searchParams.set('w', Math.round(options.width).toString());
17
+ if (options.height) url.searchParams.set('h', Math.round(options.height).toString());
18
+ if (options.quality) url.searchParams.set('q', options.quality.toString());
19
+ if (options.format) url.searchParams.set('f', options.format.toString());
20
+
21
+ return src.startsWith('http') ? url.toString() : (url.pathname + url.search);
22
+ } catch (e) {
23
+ return src;
24
+ }
25
+ }
26
+ return String(src);
27
+ },
28
+ parseURL(url) {
29
+ return { src: url.pathname + url.search };
30
+ },
31
+ getHTMLAttributes(options) {
32
+ return {
33
+ width: options.width,
34
+ height: options.height,
35
+ loading: options.loading || 'lazy',
36
+ decoding: options.decoding || 'async',
37
+ };
38
+ }
39
+ };
40
+
41
+ export default kamaImageService;