@ziyarago/ui 0.0.0-stage → 0.1.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 (119) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/LICENSE +25 -0
  3. package/README.md +121 -2
  4. package/THIRD_PARTY_NOTICES.md +12 -0
  5. package/assets/3d/_sheet.png +0 -0
  6. package/assets/3d/archive.png +0 -0
  7. package/assets/3d/bag.png +0 -0
  8. package/assets/3d/cameraoff.png +0 -0
  9. package/assets/3d/comment.png +0 -0
  10. package/assets/3d/crowd.png +0 -0
  11. package/assets/3d/firstaid.png +0 -0
  12. package/assets/3d/flag.png +0 -0
  13. package/assets/3d/grain.png +0 -0
  14. package/assets/3d/guide.png +0 -0
  15. package/assets/3d/hotel.png +0 -0
  16. package/assets/3d/kaaba.png +0 -0
  17. package/assets/3d/logo.png +0 -0
  18. package/assets/3d/luggage.png +0 -0
  19. package/assets/3d/map.png +0 -0
  20. package/assets/3d/mosque.png +0 -0
  21. package/assets/3d/plane.png +0 -0
  22. package/assets/3d/profile.png +0 -0
  23. package/assets/3d/restaurant.png +0 -0
  24. package/assets/3d/ring360.png +0 -0
  25. package/assets/3d/shoes.png +0 -0
  26. package/assets/3d/star.png +0 -0
  27. package/assets/3d/store.png +0 -0
  28. package/assets/3d/taxi.png +0 -0
  29. package/assets/3d/threads.png +0 -0
  30. package/assets/3d/tour360.png +0 -0
  31. package/assets/3d/trash.png +0 -0
  32. package/assets/3d/umbrella.png +0 -0
  33. package/assets/3d/video.png +0 -0
  34. package/assets/3d/water.png +0 -0
  35. package/assets/brand/mark.png +0 -0
  36. package/assets/brand/ziyarago-logo-light.svg +21 -0
  37. package/assets/brand/ziyarago-logo.svg +21 -0
  38. package/assets/brand/ziyarago-mark-light.svg +13 -0
  39. package/assets/brand/ziyarago-mark.svg +13 -0
  40. package/assets/fonts/thmanyah/thmanyahsans-Black.otf +0 -0
  41. package/assets/fonts/thmanyah/thmanyahsans-Bold.otf +0 -0
  42. package/assets/fonts/thmanyah/thmanyahsans-Light.otf +0 -0
  43. package/assets/fonts/thmanyah/thmanyahsans-Medium.otf +0 -0
  44. package/assets/fonts/thmanyah/thmanyahsans-Regular.otf +0 -0
  45. package/assets/fonts/thmanyah/thmanyahserifdisplay-Black.otf +0 -0
  46. package/assets/fonts/thmanyah/thmanyahserifdisplay-Bold.otf +0 -0
  47. package/assets/fonts/thmanyah/thmanyahserifdisplay-Light.otf +0 -0
  48. package/assets/fonts/thmanyah/thmanyahserifdisplay-Medium.otf +0 -0
  49. package/assets/fonts/thmanyah/thmanyahserifdisplay-Regular.otf +0 -0
  50. package/assets/fonts/thmanyah/thmanyahseriftext-Black.otf +0 -0
  51. package/assets/fonts/thmanyah/thmanyahseriftext-Bold.otf +0 -0
  52. package/assets/fonts/thmanyah/thmanyahseriftext-Light.otf +0 -0
  53. package/assets/fonts/thmanyah/thmanyahseriftext-Medium.otf +0 -0
  54. package/assets/fonts/thmanyah/thmanyahseriftext-Regular.otf +0 -0
  55. package/dist/llms-full.txt +2293 -0
  56. package/dist/llms.txt +239 -0
  57. package/dist/manifest.json +10254 -0
  58. package/dist/style.css +1357 -0
  59. package/dist/tokens.css +48 -0
  60. package/package.json +118 -4
  61. package/src/catalog.js +4 -0
  62. package/src/core/components.js +124 -0
  63. package/src/core/components2.js +163 -0
  64. package/src/core/components3.js +35 -0
  65. package/src/core/components4.js +58 -0
  66. package/src/core/components6.js +48 -0
  67. package/src/core/components7.js +42 -0
  68. package/src/core/dialog.js +65 -0
  69. package/src/core/index.js +9 -0
  70. package/src/core/pages.js +51 -0
  71. package/src/meta/README.md +29 -0
  72. package/src/meta/actions.js +161 -0
  73. package/src/meta/data.js +327 -0
  74. package/src/meta/domain.js +212 -0
  75. package/src/meta/feedback.js +153 -0
  76. package/src/meta/forms.js +453 -0
  77. package/src/meta/groups.js +16 -0
  78. package/src/meta/index.js +14 -0
  79. package/src/meta/layout.js +159 -0
  80. package/src/meta/media.js +104 -0
  81. package/src/meta/navigation.js +99 -0
  82. package/src/meta/overlay.js +264 -0
  83. package/src/meta/pages.js +229 -0
  84. package/src/meta/templates.json +2422 -0
  85. package/src/meta/typography.js +58 -0
  86. package/src/nuxt/module.mjs +29 -0
  87. package/src/nuxt/plugin.mjs +9 -0
  88. package/src/react/index.js +31 -0
  89. package/src/styles/components.css +162 -0
  90. package/src/styles/components2.css +68 -0
  91. package/src/styles/components3.css +29 -0
  92. package/src/styles/components4.css +18 -0
  93. package/src/styles/components5.css +5 -0
  94. package/src/styles/components6.css +19 -0
  95. package/src/styles/components7.css +44 -0
  96. package/src/styles/components8.css +47 -0
  97. package/src/styles/effects.css +43 -0
  98. package/src/styles/fonts.css +17 -0
  99. package/src/styles/index.css +21 -0
  100. package/src/styles/sizes/actions.css +126 -0
  101. package/src/styles/sizes/data.css +161 -0
  102. package/src/styles/sizes/domain.css +35 -0
  103. package/src/styles/sizes/feedback.css +69 -0
  104. package/src/styles/sizes/forms.css +122 -0
  105. package/src/styles/sizes/layout.css +15 -0
  106. package/src/styles/sizes/media.css +28 -0
  107. package/src/styles/sizes/navigation.css +146 -0
  108. package/src/styles/sizes/overlay.css +79 -0
  109. package/src/styles/sizes/typography.css +12 -0
  110. package/src/styles/tokens.css +47 -0
  111. package/src/theme.js +52 -0
  112. package/src/vue/index.js +29 -0
  113. package/tokens/tokens.json +9 -0
  114. package/types/core.d.ts +4 -0
  115. package/types/nuxt.d.ts +11 -0
  116. package/types/react.d.ts +189 -0
  117. package/types/shared.d.ts +18 -0
  118. package/types/theme.d.ts +6 -0
  119. package/types/vue.d.ts +192 -0
@@ -0,0 +1,48 @@
1
+ /* generated */
2
+ /* @ziyarago/ui · tokens (OKLCH). Theme: [data-theme=dark|light] · Palette: [data-palette=gold|tamam] · Direction: dir=rtl */
3
+ :root,[data-palette="gold"]{--zg-brand:oklch(0.676 0.084 67.4);--zg-brand-hover:oklch(0.73 0.09 67.4);--zg-sky:oklch(0.703 0.09 229.6)}
4
+ [data-palette="tamam"]{--zg-brand:oklch(0.77 0.23 142);--zg-brand-hover:oklch(0.82 0.23 142);--zg-sky:oklch(0.72 0.1 200)}
5
+ :root,[data-theme="dark"]{--zg-pop:oklch(0.262 0.004 17.4);
6
+ --zg-on-brand:oklch(0.2 0.02 60);
7
+ --zg-brand-soft:color-mix(in oklch,var(--zg-brand) 22%,transparent);
8
+ --zg-brand-glow:color-mix(in oklch,var(--zg-brand) 40%,transparent);
9
+ --zg-brand-text:color-mix(in oklch,var(--zg-brand) 70%,white);
10
+ --zg-sky-text:oklch(0.8 0.08 226);
11
+ --zg-bg:oklch(0.216 0.003 17.4);
12
+ --zg-s1:oklch(1 0 0 / .055);--zg-s2:oklch(1 0 0 / .085);--zg-s3:oklch(1 0 0 / .13);
13
+ --zg-glass:oklch(0.235 0.006 40 / .92);
14
+ --zg-stroke:oklch(1 0 0 / .08);
15
+ --zg-text-1:oklch(0.97 0.004 70);--zg-text-2:oklch(0.82 0.008 70);--zg-text-3:oklch(0.66 0.01 70);
16
+ --zg-inv-bg:oklch(0.96 0.004 70);--zg-inv-fg:oklch(0.2 0.01 60);
17
+ --zg-green:oklch(0.76 0.15 150);--zg-amber:oklch(0.8 0.14 80);--zg-red:oklch(0.7 0.18 25);
18
+ --zg-rim:inset 0 1px 0 oklch(1 0 0 / .16),inset 0 0 0 1px oklch(1 0 0 / .07),inset 0 -1px 0 oklch(0 0 0 / .25);
19
+ --zg-shadow-1:0 8px 24px oklch(0 0 0 / .28);--zg-shadow-2:0 24px 64px oklch(0 0 0 / .5);
20
+ }
21
+ [data-theme="light"]{--zg-pop:oklch(0.985 0.003 70);
22
+ --zg-on-brand:oklch(0.2 0.02 60);
23
+ --zg-brand-text:oklch(0.45 0.07 65);
24
+ --zg-sky-text:oklch(0.48 0.08 230);
25
+ --zg-bg:oklch(0.919 0.007 67.6);
26
+ --zg-s1:oklch(1 0 0 / .62);--zg-s2:oklch(1 0 0 / .74);--zg-s3:oklch(1 0 0 / .88);
27
+ --zg-glass:oklch(0.975 0.004 70 / .94);
28
+ --zg-stroke:oklch(0.3 0.02 60 / .15);
29
+ --zg-text-1:oklch(0.2 0.01 60);--zg-text-2:oklch(0.36 0.012 60);--zg-text-3:oklch(0.47 0.012 60);
30
+ --zg-inv-bg:oklch(0.22 0.01 60);--zg-inv-fg:oklch(0.97 0.004 70);
31
+ --zg-green:oklch(0.5 0.14 150);--zg-amber:oklch(0.55 0.13 70);--zg-red:oklch(0.52 0.19 25);
32
+ --zg-rim:inset 0 1px 0 oklch(1 0 0 / .95),inset 0 0 0 1px oklch(1 0 0 / .45),0 0 0 1px oklch(0.3 0.02 60 / .13);
33
+ --zg-shadow-1:0 8px 24px oklch(0.4 0.03 60 / .17);--zg-shadow-2:0 24px 64px oklch(0.4 0.03 60 / .22);
34
+ }
35
+ :root{--zg-z-bar:50;--zg-z-pop:60;--zg-z-modal:200;
36
+ --zg-font:'Manrope','Thmanyah Sans',system-ui,sans-serif;--zg-font-ar:'Thmanyah Sans',system-ui,sans-serif;--zg-font-ar-display:'Thmanyah Serif Display','Thmanyah Sans',serif;--zg-font-ar-text:'Thmanyah Serif Text','Thmanyah Sans',serif;--zg-font-mono:'JetBrains Mono',ui-monospace,monospace;
37
+ --zg-h-xs:28px;--zg-h-sm:32px;--zg-h-md:40px;--zg-h-lg:48px;--zg-h-xl:56px;
38
+ --zg-r-card:32px;--zg-r-tile:20px;--zg-ease:cubic-bezier(.2,.8,.2,1);
39
+ }
40
+ [dir="rtl"]{--zg-font:var(--zg-font-ar)}
41
+
42
+ /* Size scale. Adapters put data-size="xs|sm|md|lg|xl" on a component root when it has a size prop.
43
+ Component CSS must read these only inside its own [data-size] selector, never in default rules, so sizes do not leak into children. */
44
+ [data-size="xs"]{--zg-sz-h:28px;--zg-sz-fs:12px;--zg-sz-px:10px;--zg-sz-gap:6px;--zg-sz-icon:14px;--zg-sz-pad:10px}
45
+ [data-size="sm"]{--zg-sz-h:32px;--zg-sz-fs:13px;--zg-sz-px:12px;--zg-sz-gap:8px;--zg-sz-icon:16px;--zg-sz-pad:12px}
46
+ [data-size="md"]{--zg-sz-h:40px;--zg-sz-fs:14px;--zg-sz-px:16px;--zg-sz-gap:10px;--zg-sz-icon:18px;--zg-sz-pad:16px}
47
+ [data-size="lg"]{--zg-sz-h:48px;--zg-sz-fs:15px;--zg-sz-px:20px;--zg-sz-gap:12px;--zg-sz-icon:20px;--zg-sz-pad:20px}
48
+ [data-size="xl"]{--zg-sz-h:56px;--zg-sz-fs:16px;--zg-sz-px:24px;--zg-sz-gap:14px;--zg-sz-icon:22px;--zg-sz-pad:24px}
package/package.json CHANGED
@@ -1,6 +1,120 @@
1
1
  {
2
2
  "name": "@ziyarago/ui",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "ZiyaraGo UI — glass design system for Umrah and Hajj products. OKLCH tokens, two palettes, dark and light, RTL, Thmanyah. One framework-neutral core with Vue 3, Nuxt and React adapters, 160+ components and 16 page templates.",
5
+ "keywords": [
6
+ "ui",
7
+ "design-system",
8
+ "components",
9
+ "vue",
10
+ "react",
11
+ "glassmorphism",
12
+ "rtl",
13
+ "arabic",
14
+ "oklch",
15
+ "umrah",
16
+ "hajj",
17
+ "ziyarago",
18
+ "nuxt"
19
+ ],
20
+ "type": "module",
21
+ "license": "MIT",
22
+ "author": "ZiyaraGo <dev@ziyarago.com>",
23
+ "homepage": "https://ziyara.vip",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/ziyarago/ui.git"
27
+ },
28
+ "bugs": {
29
+ "url": "https://github.com/ziyarago/ui/issues"
30
+ },
31
+ "sideEffects": [
32
+ "**/*.css"
33
+ ],
34
+ "main": "./src/core/index.js",
35
+ "module": "./src/core/index.js",
36
+ "types": "./types/core.d.ts",
37
+ "exports": {
38
+ ".": {
39
+ "types": "./types/core.d.ts",
40
+ "default": "./src/core/index.js"
41
+ },
42
+ "./vue": {
43
+ "types": "./types/vue.d.ts",
44
+ "default": "./src/vue/index.js"
45
+ },
46
+ "./react": {
47
+ "types": "./types/react.d.ts",
48
+ "default": "./src/react/index.js"
49
+ },
50
+ "./theme": {
51
+ "types": "./types/theme.d.ts",
52
+ "default": "./src/theme.js"
53
+ },
54
+ "./style.css": "./dist/style.css",
55
+ "./tokens.css": "./dist/tokens.css",
56
+ "./tokens.json": "./tokens/tokens.json",
57
+ "./assets/*": "./assets/*",
58
+ "./package.json": "./package.json",
59
+ "./nuxt": {
60
+ "types": "./types/nuxt.d.ts",
61
+ "default": "./src/nuxt/module.mjs"
62
+ },
63
+ "./manifest.json": "./dist/manifest.json",
64
+ "./llms.txt": "./dist/llms.txt",
65
+ "./llms-full.txt": "./dist/llms-full.txt",
66
+ "./meta": {
67
+ "default": "./src/meta/index.js"
68
+ }
69
+ },
70
+ "files": [
71
+ "dist",
72
+ "types",
73
+ "assets",
74
+ "tokens",
75
+ "README.md",
76
+ "LICENSE",
77
+ "CHANGELOG.md",
78
+ "THIRD_PARTY_NOTICES.md",
79
+ "src"
80
+ ],
81
+ "scripts": {
82
+ "types": "node scripts/types.mjs",
83
+ "build": "node scripts/build.mjs && node scripts/types.mjs && node scripts/manifest.mjs",
84
+ "check": "node scripts/check.mjs && node scripts/check-meta.mjs",
85
+ "prepack": "npm run build && npm run check",
86
+ "pack:dry": "npm pack --dry-run"
87
+ },
88
+ "engines": {
89
+ "node": ">=18"
90
+ },
91
+ "peerDependencies": {
92
+ "@nuxt/kit": ">=3.0.0",
93
+ "react": ">=18",
94
+ "vue": ">=3.5"
95
+ },
96
+ "peerDependenciesMeta": {
97
+ "vue": {
98
+ "optional": true
99
+ },
100
+ "react": {
101
+ "optional": true
102
+ },
103
+ "@nuxt/kit": {
104
+ "optional": true
105
+ }
106
+ },
107
+ "publishConfig": {
108
+ "access": "public",
109
+ "provenance": true
110
+ },
111
+ "devDependencies": {
112
+ "@nuxt/kit": "^3.21.11",
113
+ "@semantic-release/changelog": "^6.0.3",
114
+ "@semantic-release/exec": "^7.1.0",
115
+ "@semantic-release/git": "^10.0.1",
116
+ "conventional-changelog-conventionalcommits": "^9.3.1",
117
+ "semantic-release": "^24.2.9",
118
+ "vue": "^3.5.44"
119
+ }
120
+ }
package/src/catalog.js ADDED
@@ -0,0 +1,4 @@
1
+ // Source: ziyarago/glass-ui-kit src/data/componentCatalog.ts (ids + groups). Excluded by scope: AI, Restaurants, Logistics.
2
+ export const CATALOG = [["Accordion","Surfaces"],["AppShell","Layout"],["AuthLayout","Layout"],["Alert","Surfaces"],["Avatar","Data"],["Badge","Data"],["Breadcrumbs","Navigation"],["Button","Actions"],["Card","Surfaces"],["Checkbox","Forms"],["ChipInput","Forms"],["ConfirmDialog","Surfaces"],["ColorPicker","Forms"],["Combobox","Forms"],["CommandPalette","Actions"],["DatePicker","Forms"],["DashboardLayout","Layout"],["DataState","Surfaces"],["Drawer","Surfaces"],["Dropdown","Actions"],["EmptyState","Surfaces"],["FileUpload","Forms"],["Form","Forms"],["Kbd","Data"],["Input","Forms"],["NotificationBell","Actions"],["Menu","Actions"],["Modal","Surfaces"],["Navbar","Navigation"],["Pagination","Navigation"],["Page","Layout"],["MiniChart","Data"],["Progress","Surfaces"],["Radio","Forms"],["SegmentedControl","Forms"],["OtpInput","Forms"],["SearchInput","Forms"],["Select","Forms"],["Slider","Forms"],["Stepper","Navigation"],["Sidebar","Navigation"],["Skeleton","Surfaces"],["StatsCard","Data"],["KpiCard","Data"],["SubNav","Navigation"],["Switch","Forms"],["Table","Data"],["Tabs","Navigation"],["Textarea","Forms"],["Toast","Surfaces"],["Tooltip","Actions"],["CopyButton","Actions"],["Divider","Surfaces"],["Rating","Forms"],["Timeline","Data"],["GroupedSelect","Forms"],["HoverCard","Actions"],["LazyImage","Data"],["LoadingOverlay","Surfaces"],["LoginForm","Forms"],["Carousel","Surfaces"],["CommandBar","Actions"],["PhoneInput","Forms"],["TreeView","Data"],["TreeSelect","Forms"],["Banner","Surfaces"],["Code","Surfaces"],["ImageUpload","Forms"],["Spinner","Surfaces"],["MultiSelect","Forms"],["PasswordInput","Forms"],["ScrollArea","Layout"],["ToggleGroup","Actions"],["ContextMenu","Actions"],["InlineEdit","Forms"],["NumberInput","Forms"],["Popover","Actions"],["Tag","Data"],["TimePicker","Forms"],["Sortable","Data"],["CalendarView","Data"],["Fab","Actions"],["RichTextEditor","Forms"],["PricingCard","Surfaces"],["Kanban","Data"],["SettingsSection","Layout"],["SplitLayout","Layout"],["IntegrationCard","Surfaces"],["ErrorPage","Surfaces"],["IncomingCall","Surfaces"],["Screensaver","Surfaces"],["CallCard","Surfaces"],["AuthPages","Templates"],["SystemStatus","Data"],["CrmDashboard","Templates"],["TourBiDashboard","Templates"],["AdminBiDashboard","Templates"],["StepForm","Forms"],["Icons","Templates"],["Chart","Data"],["Gallery","Surfaces"],["Map","Data"],["Descriptions","Data"],["Popconfirm","Surfaces"],["ChatBubble","Surfaces"],["ConversationList","Surfaces"],["ChunkCard","Data"],["PillTab","Navigation"],["KpiInline","Data"],["ActionRail","Navigation"],["DealCard","CRM"],["PipelineBoard","CRM"],["LeadCard","CRM"],["ContactPanel","CRM"],["ActivityTimeline","CRM"],["SalesFunnel","CRM"],["BookingCard","Hotels"],["GuestCard","Hotels"],["RoomCalendar","Hotels"],["RoomGrid","Hotels"],["OccupancyChart","Hotels"]].map(([id, group]) => ({ id, group }));
3
+ // glass-ui-kit name → @ziyarago/ui name (when different)
4
+ export const ALIAS = { CrmDashboard: 'PageCrm', AuthPages: 'PageAuth', TourBiDashboard: 'PageDashboard', AdminBiDashboard: 'PageDashboard', Icons: 'IconTile', RichTextEditor: 'RichEditor', Input: 'TextField', KpiCard: 'KpiTile', Table: 'DataTable', Sidebar: 'SideNav', Navbar: 'TopNav', Page: 'PageHeader', DashboardLayout: 'AppShell', PillTab: 'SegmentedControl', ActivityTimeline: 'Timeline', Kanban: 'KanbanColumn', Radio: 'RadioGroup', OtpInput: 'CodeInput', Form: 'FormField', ImageUpload: 'FileUpload', Chart: 'MiniChart', Progress: 'Progress' };
@@ -0,0 +1,124 @@
1
+ // @ziyarago/ui · framework-neutral component definitions.
2
+ // Each component: { state?, render({ h, props, children, slots, state, set }) }.
3
+ // h(tag, props, ...children) is supplied by the Vue / React adapter. No hooks inside.
4
+ import { trap } from './dialog.js';
5
+ export const cx = (...a) => a.filter(Boolean).join(' ');
6
+ export const DZ = { xs: .82, sm: .91, md: 1, lg: 1.12, xl: 1.25 };
7
+ // Shared outside-click / Escape close for popups. Put `ref: ddRef(close)` on the popup element; its parent is the component root
8
+ // (trigger + popup). Listeners attach only in the browser, only while a popup is mounted, and detach when the last one unmounts.
9
+ const ddReg = [];
10
+ const ddPrune = () => { for (let i = ddReg.length - 1; i >= 0; i--) if (!ddReg[i].el.isConnected) ddReg.splice(i, 1); ddSync(); };
11
+ const ddDown = e => { ddPrune(); for (const r of ddReg.slice()) { const root = r.self ? r.el : (r.el.parentElement || r.el); if (!root.contains(e.target)) r.close(); } };
12
+ const ddKey = e => {
13
+ if (e.key !== 'Escape') return; ddPrune(); const r = ddReg[ddReg.length - 1]; if (!r) return;
14
+ const root = r.self ? r.el : (r.el.parentElement || r.el);
15
+ const t = [...root.querySelectorAll('button,input')].find(x => !r.el.contains(x));
16
+ const ae = document.activeElement; r.close(); if (t && !t.disabled && !(ae && root.contains(ae) && !r.el.contains(ae))) t.focus();
17
+ };
18
+ let ddOn = false;
19
+ const ddSync = () => {
20
+ if (typeof document === 'undefined') return;
21
+ if (ddReg.length && !ddOn) { document.addEventListener('pointerdown', ddDown, true); document.addEventListener('keydown', ddKey); ddOn = true; }
22
+ else if (!ddReg.length && ddOn) { document.removeEventListener('pointerdown', ddDown, true); document.removeEventListener('keydown', ddKey); ddOn = false; }
23
+ };
24
+ export const ddRef = (close, o = {}) => el => {
25
+ if (typeof document === 'undefined') return;
26
+ if (el) { const r = ddReg.find(x => x.el === el); if (r) { r.close = close; r.self = !!o.self; } else ddReg.push({ el, close, self: !!o.self }); ddSync(); }
27
+ else Promise.resolve().then(ddPrune);
28
+ };
29
+ export const dzp = (p, b) => Math.round(b * (DZ[p.size] || 1));
30
+ export const dzf = n => `calc(${n}px*var(--dz,1))`;
31
+ export const dzb = p => ({ xs: 'xs', sm: 'xs', md: 'sm', lg: 'md', xl: 'lg' }[p.size] || 'sm');
32
+ // Icons: Lucide SVGs fetched once, inlined as data-URI masks (works offline after first load; override the base to self-host).
33
+ export let ZG_ICON_BASE = 'https://unpkg.com/lucide-static@0.460.0/icons/';
34
+ export const setIconBase = b => { ZG_ICON_BASE = b.endsWith('/') ? b : b + '/'; };
35
+ const iconCache = {}, iconPending = {};
36
+ const iconUri = svg => 'url("data:image/svg+xml,' + encodeURIComponent(svg.replace(/stroke="currentColor"/g, 'stroke="black"').replace(/currentColor/g, 'black')) + '")';
37
+ const loadIcon = name => {
38
+ if (iconCache[name] || iconPending[name] || typeof document === 'undefined') return;
39
+ iconPending[name] = fetch(ZG_ICON_BASE + name + '.svg').then(r => r.text()).then(svg => {
40
+ iconCache[name] = iconUri(svg);
41
+ document.querySelectorAll('[data-zg-icon="' + name + '"]').forEach(el => el.style.setProperty('--zg-icon', iconCache[name]));
42
+ }).catch(() => {});
43
+ };
44
+ export const icon = (h, name, size) => { loadIcon(name); return h('span', { class: 'zg-icon', 'data-zg-icon': name, style: { ...(iconCache[name] ? { '--zg-icon': iconCache[name] } : {}), ...(size ? { width: size + 'px', height: size + 'px' } : {}) }, 'aria-hidden': 'true' }); };
45
+ const fmt = {
46
+ num: n => (n == null || Number.isNaN(n)) ? '—' : n === -1 ? '∞' : String(Math.round(n)).replace(/\B(?=(\d{3})+(?!\d))/g, '\u2009'),
47
+ sar: n => (n == null || Number.isNaN(n)) ? '—' : 'SAR\u00a0' + String(Math.round(n)).replace(/\B(?=(\d{3})+(?!\d))/g, '\u2009'),
48
+ dur: (s, u) => { if (s == null || Number.isNaN(+s)) return '—'; s = Math.max(0, Math.round(+s)); const m = Math.floor(s / 60), r = s % 60; return `${m} ${(u && u.m) || 'm'} ${String(r).padStart(2, '0')} ${(u && u.s) || 's'}`; },
49
+ };
50
+ const nav = (h, it, cur, onChange, collapsed) => h('button', { key: it.value, class: 'zg-navitem', 'aria-current': it.value === cur ? 'page' : undefined, title: collapsed ? it.label : undefined, onClick: () => onChange && onChange(it.value) },
51
+ it.icon ? icon(h, it.icon, 18) : null, h('span', null, it.label), it.count ? h('span', { class: 'zg-navitem__count' }, it.count) : null);
52
+
53
+ export const components = {
54
+ Button: { render: ({ h, props: p, children }) => h('button', { type: 'button', class: cx('zg-btn', 'zg-btn--' + (p.variant || 'secondary'), p.size && 'zg-btn--' + p.size, p.block && 'zg-btn--block', p.tone && 'zg-t-' + p.tone), disabled: p.disabled || p.loading, onClick: p.onClick },
55
+ p.loading ? h('span', { class: 'zg-spin' }) : p.icon ? icon(h, p.icon, 16) : null, ...children, p.iconRight ? icon(h, p.iconRight, 16) : null) },
56
+ IconButton: { render: ({ h, props: p }) => h('button', { type: 'button', 'aria-label': p.label, class: cx('zg-ibtn', p.size && 'zg-ibtn--' + p.size, p.ghost && 'zg-ibtn--ghost'), onClick: p.onClick }, icon(h, p.icon)) },
57
+ Badge: { render: ({ h, props: p, children }) => h('span', { class: cx('zg-badge', p.tone && 'zg-badge--' + p.tone) }, p.dot ? h('span', { class: 'zg-badge__dot' }) : null, ...children) },
58
+ Avatar: { render: ({ h, props: p }) => h('span', { class: 'zg-avatar', style: { '--s': ({ xs: 24, sm: 32, md: 40, lg: 56, xl: 72 }[p.size] || p.size || 40) + 'px' }, title: p.name }, p.src ? h('img', { src: p.src, alt: p.name || '' }) : (p.name || '?').split(/\s+/).slice(0, 2).map(w => w[0]).join('').toUpperCase()) },
59
+ Switch: { render: ({ h, props: p }) => h('button', { type: 'button', role: 'switch', class: 'zg-switch', 'aria-checked': String(!!p.modelValue), disabled: p.disabled, 'aria-disabled': p.disabled ? 'true' : undefined, onClick: () => !p.disabled && p.onChange && p.onChange(!p.modelValue) }, h('span', { class: 'zg-switch__track' }, h('span', { class: 'zg-switch__thumb' })), p.label || null) },
60
+ Checkbox: { render: ({ h, props: p }) => h('button', { type: 'button', role: 'checkbox', class: 'zg-check', 'aria-checked': String(!!p.modelValue), disabled: p.disabled, 'aria-disabled': p.disabled ? 'true' : undefined, onClick: () => !p.disabled && p.onChange && p.onChange(!p.modelValue) }, h('span', { class: 'zg-check__box' }, p.modelValue ? icon(h, 'check', 14) : null), p.label || null) },
61
+ SegmentedControl: { render: ({ h, props: p }) => h('div', { class: cx('zg-seg', p.size === 'sm' && 'zg-seg--sm'), role: 'group' }, ...(p.items || []).map(it => h('button', { key: it.value, type: 'button', 'aria-pressed': String(it.value === p.modelValue), onClick: () => p.onChange && p.onChange(it.value) }, it.label))) },
62
+ Tabs: { render: ({ h, props: p }) => h('div', { class: 'zg-tabs', role: 'tablist' }, ...(p.items || []).map(it => h('button', { key: it.value, role: 'tab', 'aria-selected': String(it.value === p.modelValue), onClick: () => p.onChange && p.onChange(it.value) }, it.label))) },
63
+ FilterChips: { render: ({ h, props: p }) => { const sel = p.multi ? (p.modelValue || []) : [p.modelValue]; return h('div', { class: 'zg-chips' }, ...(p.items || []).map(it => h('button', { key: it.value, type: 'button', class: 'zg-chip', 'aria-pressed': String(sel.includes(it.value)), onClick: () => { if (!p.onChange) return; p.onChange(p.multi ? (sel.includes(it.value) ? sel.filter(v => v !== it.value) : [...sel, it.value]) : it.value); } }, it.label))); } },
64
+ TextField: { render: ({ h, props: p }) => h('label', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null,
65
+ h('span', { class: cx('zg-input', p.error && 'zg-input--error', p.variant && 'zg-input--' + p.variant, p.density === 'compact' && 'zg-input--compact', p.disabled && 'zg-input--disabled') }, p.icon ? icon(h, p.icon, 18) : null, h('input', { type: p.type || 'text', value: p.modelValue ?? '', placeholder: p.placeholder, disabled: p.disabled, readOnly: p.readonly, onInput: e => p.onChange && p.onChange(e.target.value) }), p.suffix || null),
66
+ p.error ? h('span', { class: 'zg-hint zg-hint--error', role: 'alert' }, p.error) : p.hint ? h('span', { class: 'zg-hint' }, p.hint) : null) },
67
+ Select: { state: { open: false }, render: ({ h, props: p, state, set }) => { const cur = (p.options || []).find(o => o.value === p.modelValue), open = state.open && !p.disabled;
68
+ return h('div', { class: 'zg-field' }, p.label ? h('span', { class: 'zg-label' }, p.label) : null,
69
+ h('div', { class: 'zg-select' }, h('button', { type: 'button', class: 'zg-btn zg-select__trigger', 'aria-haspopup': 'listbox', 'aria-expanded': String(open), disabled: p.disabled, style: { height: 'var(--zg-h-lg)' }, onClick: () => set({ open: !state.open }) }, h('span', null, cur ? cur.label : p.placeholder || '—'), icon(h, open ? 'chevron-up' : 'chevron-down', 16)),
70
+ open ? h('div', { class: 'zg-select__list', role: 'listbox', 'data-zg-dd': '', ref: ddRef(() => set({ open: false })) }, ...(p.options || []).map(o => h('button', { key: o.value, type: 'button', role: 'option', class: 'zg-select__opt', disabled: o.disabled, 'aria-disabled': o.disabled ? 'true' : undefined, 'aria-selected': String(o.value === p.modelValue), onClick: () => { set({ open: false }); p.onChange && p.onChange(o.value); } }, o.label, o.value === p.modelValue ? icon(h, 'check', 16) : null))) : null)); } },
71
+ CodeInput: { render: ({ h, props: p }) => { const n = p.length || 6, v = p.modelValue || '';
72
+ return h('div', { class: 'zg-code', style: { '--n': n } }, ...Array.from({ length: n }, (_, i) => h('span', { key: i, class: cx('zg-code__cell', i === v.length && 'zg-code__cell--on') }, v[i] || '')),
73
+ h('input', { value: v, inputMode: 'numeric', autoComplete: 'one-time-code', maxLength: n, 'aria-label': p.label || 'Code', onInput: e => { const x = e.target.value.replace(/\D/g, '').slice(0, n); p.onChange && p.onChange(x); if (x.length === n && v.length !== n) p.onComplete && p.onComplete(x); } })); } },
74
+ Stepper: { render: ({ h, props: p }) => h('div', { class: 'zg-stepper' }, ...(p.steps || []).flatMap((s, i) => [i ? h('span', { key: 'b' + i, class: 'zg-step__bar' }) : null, h('span', { key: i, class: cx('zg-step', i < p.current && 'zg-step--done', i === p.current && 'zg-step--cur') }, h('span', { class: 'zg-step__n' }, i < p.current ? icon(h, 'check', 14) : i + 1), s)])) },
75
+ Card: { render: ({ h, props: p, children, slots }) => h('section', { class: cx('zg-card', 'zg-glass', p.variant === 'inverse' && 'zg-card--inverse', p.variant === 'gradient' && 'zg-card--grad', p.variant === 'gradient' && 'zg-card--t-' + (p.tone || 'brand'), p.variant === 'gradient' && p.intensity === 'vivid' && 'zg-card--vivid'), 'data-theme': p.variant === 'inverse' ? 'dark' : (p.variant === 'gradient' && p.intensity === 'vivid' ? (['brand', 'warning', undefined].includes(p.tone) ? 'light' : 'dark') : undefined) },
76
+ (p.title || slots.actions) ? h('div', { class: 'zg-card__head' }, h('div', { style: { flex: 1, minWidth: 0 } }, h('h3', { class: 'zg-card__title' }, p.title), p.subtitle ? h('div', { class: 'zg-card__sub' }, p.subtitle) : null), slots.actions || null) : null, ...children) },
77
+ Modal: { render: ({ h, props: p, children, slots }) => { const foot = slots.footer || p.footer; return !p.open ? null : h('div', { class: 'zg-modal-scrim', onClick: p.onClose }, h('div', { class: 'zg-modal', role: 'dialog', 'aria-modal': 'true', 'aria-label': p.title, ref: trap({ onClose: p.onClose }), onClick: e => e.stopPropagation() },
78
+ h('div', { class: 'zg-modal__head' }, h('h3', null, p.title), h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': 'Close', onClick: p.onClose }, icon(h, 'x'))), h('div', { class: 'zg-modal__body' }, ...children), foot ? h('div', { class: 'zg-modal__foot' }, foot) : null)); } },
79
+ Drawer: { render: ({ h, props: p, children, slots }) => { const foot = slots.footer || p.footer; return !p.open ? null : h('div', { class: cx('zg-modal-scrim', 'zg-drawer-scrim', 'zg-drawer-scrim--' + (p.side || 'end')), onClick: p.onClose }, h('div', { class: 'zg-modal zg-drawer', role: 'dialog', 'aria-modal': 'true', 'aria-label': p.title, ref: trap({ onClose: p.onClose }), onClick: e => e.stopPropagation() },
80
+ h('div', { class: 'zg-modal__head' }, h('h3', null, p.title), h('button', { type: 'button', class: 'zg-ibtn zg-ibtn--sm zg-ibtn--ghost', 'aria-label': 'Close', onClick: p.onClose }, icon(h, 'x'))), h('div', { class: 'zg-modal__body' }, ...children), foot ? h('div', { class: 'zg-modal__foot' }, foot) : null)); } },
81
+ Toast: { render: ({ h, props: p, children }) => h('div', { class: cx('zg-toast', 'zg-toast--' + (p.tone || 'info')), role: 'status' }, h('span', { class: 'zg-toast__icon' }, icon(h, { success: 'check', danger: 'circle-alert', warning: 'triangle-alert' }[p.tone] || 'info', 18)), h('div', null, p.title ? h('b', null, p.title) : null, h('span', null, ...children))) },
82
+ Breadcrumbs: { render: ({ h, props: p }) => h('nav', { class: 'zg-crumbs', 'aria-label': 'Breadcrumb' }, ...(p.items || []).flatMap((it, i, a) => [i ? icon(h, 'chevron-right', 14) : null, i === a.length - 1 ? h('b', { key: i, 'aria-current': 'page' }, it.label) : h('a', { key: i, href: it.href || '#' }, it.label)])) },
83
+ Pagination: { render: ({ h, props: p }) => { const total = Math.max(1, Math.floor(+p.pages) || 1), cur = Math.min(total, Math.max(1, +p.page || 1)), s = Math.max(0, p.siblings == null ? 1 : Math.floor(+p.siblings) || 0), go = n => p.onChange && n >= 1 && n <= total && n !== p.page && p.onChange(n);
84
+ const set = new Set([1, total]); for (let i = cur - s; i <= cur + s; i++) if (i >= 1 && i <= total) set.add(i);
85
+ const nums = [...set].sort((a, b) => a - b), items = []; nums.forEach((n, i) => { const prev = nums[i - 1]; if (prev && n - prev === 2) items.push(prev + 1); else if (prev && n - prev > 2) items.push('…'); items.push(n); });
86
+ return h('nav', { class: 'zg-pager', 'aria-label': p.label || 'Pagination' }, h('button', { key: 'prev', type: 'button', class: 'zg-pager__nav', 'aria-label': 'Previous page', disabled: cur <= 1, onClick: () => go(cur - 1) }, icon(h, 'chevron-left', 16)),
87
+ ...items.map((n, i) => n === '…' ? h('span', { key: 'e' + i, class: 'zg-pager__gap', 'aria-hidden': 'true' }, '…') : h('button', { key: n, type: 'button', 'aria-label': 'Page ' + n, 'aria-current': n === p.page ? 'page' : undefined, onClick: () => go(n) }, n)),
88
+ h('button', { key: 'next', type: 'button', class: 'zg-pager__nav', 'aria-label': 'Next page', disabled: cur >= total, onClick: () => go(cur + 1) }, icon(h, 'chevron-right', 16))); } },
89
+ Progress: { render: ({ h, props: p }) => h('div', { class: cx('zg-progress', p.tone && 'zg-progress--' + p.tone) }, (p.label || p.meta) ? h('div', { class: 'zg-progress__row' }, h('span', null, p.label), h('span', { class: 'zg-mono' }, p.meta)) : null, h('div', { class: 'zg-progress__track', role: 'progressbar', 'aria-valuenow': p.value, 'aria-valuemin': 0, 'aria-valuemax': 100 }, h('div', { class: 'zg-progress__bar', style: { width: Math.max(0, Math.min(100, p.value || 0)) + '%' } }))) },
90
+ Ring: { render: ({ h, props: p }) => { const s = { xs: 72, sm: 120, md: 180, lg: 240, xl: 300 }[p.size] || (Number(p.size) > 0 ? Number(p.size) : 180), th = p.thickness || Math.max(8, Math.round(s * .078)), ticks = p.ticks !== false, v = Math.max(0, Math.min(100, p.value || 0)) / 100, col = p.color || 'var(--zg-brand)';
91
+ const r = (s - th) / 2 - (ticks ? 12 : 0), cir = 2 * Math.PI * r, tk = [];
92
+ if (ticks) for (let i = 0; i < 60; i++) { const an = i / 60 * 2 * Math.PI, R = s / 2 - 2, L = i % 5 ? 4 : 8; tk.push(h('line', { key: i, x1: +(s / 2 + Math.cos(an) * R).toFixed(2), y1: +(s / 2 + Math.sin(an) * R).toFixed(2), x2: +(s / 2 + Math.cos(an) * (R - L)).toFixed(2), y2: +(s / 2 + Math.sin(an) * (R - L)).toFixed(2), stroke: i / 60 <= v ? col : 'var(--zg-s3)', 'stroke-width': i % 5 ? 1 : 1.6, 'stroke-linecap': 'round' })); }
93
+ return h('div', { class: 'zg-ring', style: { width: s + 'px', height: s + 'px' }, role: 'img', 'aria-label': Math.round(v * 100) + '%' }, h('svg', { width: s, height: s, viewBox: `0 0 ${s} ${s}` }, ...tk,
94
+ h('circle', { cx: s / 2, cy: s / 2, r, fill: 'none', stroke: 'var(--zg-stroke)', 'stroke-width': th }),
95
+ h('circle', { cx: s / 2, cy: s / 2, r, fill: 'none', stroke: col, 'stroke-width': th, 'stroke-linecap': 'round', 'stroke-dasharray': cir, 'stroke-dashoffset': cir * (1 - v), style: { filter: 'drop-shadow(0 0 10px color-mix(in oklch, ' + col + ' 45%, transparent))', transition: 'stroke-dashoffset 1.4s cubic-bezier(.2,.8,.2,1)' } })),
96
+ h('div', { class: 'zg-ring__c' }, h('span', { class: 'zg-ring__v', style: { fontSize: (p.valueSize || s * .22) + 'px' } }, Math.round(v * 100), h('span', { style: { fontSize: '.45em', color: 'var(--zg-text-3)' } }, '%')), p.label ? h('span', { class: 'zg-ring__l' }, p.label) : null)); } },
97
+ KpiTile: { render: ({ h, props: p }) => h('div', { class: cx('zg-kpi', p.variant && 'zg-kpi--' + p.variant) }, h('span', { class: 'zg-kpi__label' }, p.label), h('span', { class: 'zg-kpi__value zg-num' }, p.value), (p.sub || p.delta) ? h('span', { class: 'zg-kpi__sub' }, p.delta ? h('span', { class: cx('zg-delta', String(p.delta).startsWith('-') ? 'zg-delta--down' : 'zg-delta--up') }, p.delta + ' ') : null, p.sub) : null) },
98
+ DataTable: { render: ({ h, props: p }) => { const cols = p.columns || [], rows = p.rows || [], sel = p.selected || [], all = rows.length && rows.every(r => sel.includes(r.id));
99
+ const th = c => h('th', { key: c.key, 'aria-sort': p.sortKey === c.key ? (p.sortDir === 'desc' ? 'descending' : 'ascending') : undefined, class: c.num ? 'num' : undefined }, c.sortable ? h('button', { type: 'button', onClick: () => p.onSort && p.onSort(c.key, p.sortKey === c.key && p.sortDir !== 'desc' ? 'desc' : 'asc') }, c.label, p.sortKey === c.key ? icon(h, p.sortDir === 'desc' ? 'arrow-down' : 'arrow-up', 12) : null) : c.label);
100
+ return h('div', { class: 'zg-table-wrap' }, h('table', { class: 'zg-table' }, h('thead', null, h('tr', null, p.onSelect ? h('th', { style: { width: '44px' } }, h('button', { type: 'button', role: 'checkbox', class: 'zg-check', 'aria-checked': String(!!all), 'aria-label': (p.labels && p.labels.selectAll) || 'Select all', onClick: () => p.onSelect(all ? [] : rows.map(r => r.id)) }, h('span', { class: 'zg-check__box' }, all ? icon(h, 'check', 14) : null))) : null, ...cols.map(th))),
101
+ h('tbody', null, ...(rows.length ? rows.map(r => h('tr', { key: r.id, 'aria-selected': sel.includes(r.id) ? 'true' : undefined }, p.onSelect ? h('td', null, h('button', { type: 'button', role: 'checkbox', class: 'zg-check', 'aria-checked': String(sel.includes(r.id)), 'aria-label': (p.labels && p.labels.selectRow) || 'Select row', onClick: () => p.onSelect(sel.includes(r.id) ? sel.filter(i => i !== r.id) : [...sel, r.id]) }, h('span', { class: 'zg-check__box' }, sel.includes(r.id) ? icon(h, 'check', 14) : null))) : null, ...cols.map(c => h('td', { key: c.key, class: c.num ? 'num' : undefined }, c.render ? c.render(r[c.key], r, h) : (r[c.key] ?? '—'))))) : [h('tr', { key: 'e' }, h('td', { colSpan: cols.length + 1 }, h('div', { class: 'zg-empty' }, p.empty || '—')))])))); } },
102
+ KanbanColumn: { render: ({ h, props: p, children }) => h('div', { class: 'zg-kcol zg-dm' }, h('div', { class: 'zg-kcol__head' }, p.title, h('span', { class: 'zg-badge' }, p.count ?? children.length)), ...children) },
103
+ KanbanCard: { render: ({ h, props: p, children }) => h('div', { class: 'zg-kcard zg-dm' }, h('b', { style: { fontSize: dzf(14) } }, p.title), p.sub ? h('span', { class: 'zg-hint' }, p.sub) : null, ...children, p.tag ? h('span', { class: cx('zg-badge', p.tone && 'zg-badge--' + p.tone), style: { alignSelf: 'flex-start' } }, p.tag) : null) },
104
+ Timeline: { render: ({ h, props: p }) => h('div', { class: 'zg-timeline' }, ...(p.items || []).map((it, i, a) => h('div', { key: i, class: 'zg-tl' }, h('div', { class: 'zg-tl__rail' }, h('span', { class: 'zg-tl__dot' }), i < a.length - 1 ? h('span', { class: 'zg-tl__line' }) : null), h('div', { class: 'zg-tl__body' }, h('b', null, it.title), it.text ? h('span', null, it.text) : null, h('span', { class: 'zg-tl__time' }, it.time))))) },
105
+ TopNav: { render: ({ h, props: p, slots }) => h('header', { class: 'zg-topnav' }, h('div', { class: 'zg-topnav__brand' }, p.brand), h('nav', { class: 'zg-topnav__items', 'aria-label': 'Main' }, ...(p.items || []).map(it => nav(h, it, p.value, p.onChange))), h('div', { class: 'zg-topnav__actions' }, slots.actions || p.actions || null)) },
106
+ SideNav: { render: ({ h, props: p, slots }) => h('aside', { class: cx('zg-sidenav', p.collapsed && 'zg-sidenav--collapsed') }, h('div', { class: 'zg-sidenav__brand' }, p.brand), ...(p.items || []).map(it => it.group ? h('div', { key: it.group, class: 'zg-sidenav__group', style: p.collapsed ? { display: 'none' } : undefined }, it.group) : nav(h, it, p.value, p.onChange, p.collapsed)), h('div', { class: 'zg-sidenav__sp' }), slots.footer || p.footer || null,
107
+ p.onToggle ? h('button', { type: 'button', class: 'zg-navitem', onClick: p.onToggle, 'aria-label': 'Toggle sidebar' }, icon(h, p.collapsed ? 'panel-left-open' : 'panel-left-close', 18), h('span', null, p.collapseLabel || 'Collapse')) : null) },
108
+ AppShell: { state: { collapsed: false }, render: ({ C, h, props: p, children, slots, state, set }) => {
109
+ const brand = p.brand, top = (p.nav || 'top') === 'top', col = p.collapsed ?? state.collapsed, toggle = () => p.onCollapse ? p.onCollapse(!col) : set({ collapsed: !state.collapsed });
110
+ return h('div', { class: cx('zg-root', 'zg-shell', top && 'zg-shell--top', p.frame && 'zg-shell--frame'), style: { background: 'var(--zg-bg)' } },
111
+ top ? h(C.TopNav, { brand, items: p.items, value: p.value, onChange: p.onChange, actions: p.actions }, null) : h(C.SideNav, { brand, items: p.items, value: p.value, onChange: p.onChange, collapsed: col, onToggle: p.collapsible !== false ? toggle : undefined, footer: slots.footer, collapseLabel: p.collapseLabel }),
112
+ h('main', { class: 'zg-shell__main' }, ...children)); } },
113
+ PageHeader: { render: ({ C, h, props: p, slots }) => h('div', { class: 'zg-page-head' }, h('div', { style: { flex: 1, minWidth: 0 } }, p.crumbs ? h(C.Breadcrumbs, { items: p.crumbs }) : null, h('h1', null, p.title), p.subtitle ? h('p', null, p.subtitle) : null), slots.actions || null) },
114
+ AuthLayout: { render: ({ h, props: p, children }) => h('div', { class: 'zg-root zg-auth', style: p.style }, h('div', { class: 'zg-auth__card' }, p.logo || null, h('div', null, h('h1', { style: { margin: 0, font: '300 30px var(--zg-font)', letterSpacing: '-.03em' } }, p.title), p.subtitle ? h('p', { style: { margin: '6px 0 0', color: 'var(--zg-text-2)', lineHeight: 1.5 } }, p.subtitle) : null), ...children)) },
115
+ LeadCard: { render: ({ C, h, props: p }) => h('div', { class: 'zg-lead zg-dm' }, h('div', { class: 'zg-lead__row' }, h(C.Avatar, { name: p.name, size: dzp(p, 32) }), h('div', { style: { flex: 1, minWidth: 0 } }, h('b', { style: { fontSize: dzf(14) } }, p.name), h('div', { class: 'zg-hint zg-mono' }, p.code)), p.tag ? h('span', { class: cx('zg-badge', p.tone && 'zg-badge--' + p.tone) }, p.tag) : null), h('div', { class: 'zg-lead__row', style: { fontSize: dzf(13), color: 'var(--zg-text-2)' } }, icon(h, 'phone', dzp(p, 14)), p.phone, h('span', { class: 'zg-num', style: { marginInlineStart: 'auto', fontSize: dzf(15), fontWeight: 600, color: 'var(--zg-text-1)' } }, p.amount))) },
116
+ HotelCard: { render: ({ h, props: p }) => h('article', { class: 'zg-hotel zg-dm' }, h('div', { class: 'zg-hotel__img', style: { backgroundImage: p.image ? `url(${p.image})` : undefined } }, h('div', { class: 'zg-hotel__tags' }, ...(p.tags || []).map(t => h('span', { key: t, class: 'zg-badge zg-badge--brand' }, t)))), h('div', { class: 'zg-hotel__body' },
117
+ h('div', { class: 'zg-hotel__row' }, h('div', { style: { minWidth: 0 } }, h('b', { style: { fontSize: dzf(17) } }, p.name), h('div', { class: 'zg-hint' }, '★'.repeat(p.stars || 0), ' · ', p.distance)), h('span', { class: 'zg-badge zg-badge--success', style: { marginInlineStart: 'auto' } }, p.rating)),
118
+ h('div', { class: 'zg-hotel__row', style: { alignItems: 'center' } }, h('span', { class: 'zg-hint' }, 'SAR / ', p.night || 'night'), h('div', { class: 'zg-hotel__price' }, h('div', { class: 'zg-num', style: { fontSize: dzf(26), fontWeight: 300 } }, fmt.num(p.price))))) ) },
119
+ TourCard: { render: ({ C, h, props: p, slots }) => { const seats = Number(p.seats) || 0, booked = Number(p.booked) || 0; return h('article', { class: 'zg-card zg-glass zg-dm' }, h('div', { class: 'zg-card__head' }, h('div', { style: { flex: 1 } }, h('h3', { class: 'zg-card__title' }, p.name), h('div', { class: 'zg-card__sub' }, [p.city, p.sessions != null && p.sessions !== '' ? p.sessions + ' sessions' : null].filter(Boolean).join(' · '))), h('span', { class: 'zg-badge zg-badge--info' }, '360°')), h(C.Progress, { value: seats > 0 ? Math.min(100, booked / seats * 100) : 0, label: 'Seats', meta: `${booked}/${seats}` }), slots.actions || null); } },
120
+ PrayerTimes: { render: ({ h, props: p }) => h('div', { class: 'zg-pt zg-dm' }, ...(p.items || []).map((it, i) => { it = it || {}; return h('div', { key: i, class: 'zg-pt__i', 'aria-current': i === p.next ? 'true' : undefined }, it.name, h('b', null, it.time)); })) },
121
+ RamadanTracker: { render: ({ h, props: p }) => { const day = Number(p.day) || 0; return h('div', { class: 'zg-rt zg-dm', role: 'img', 'aria-label': day ? `Day ${day} of 30` : 'Ramadan tracker' }, ...Array.from({ length: 30 }, (_, i) => { const n = i + 1; return h('i', { key: n, 'data-s': n === day ? 'cur' : n < day ? 'past' : n > 20 ? 'last' : undefined, 'data-odd': n > 20 && n % 2 ? '' : undefined, title: String(n) }); })); } },
122
+ CallRow: { render: ({ C, h, props: p }) => h('div', { class: 'zg-call zg-dm' }, h(C.Avatar, { name: p.name, size: dzp(p, 36) }), h('div', { style: { minWidth: 0 } }, h('b', { style: { fontSize: dzf(14) } }, p.name), h('div', { class: 'zg-hint' }, [p.status, fmt.dur(p.duration, p.durUnits)].filter(Boolean).join(' · '))), h(C.IconButton, { icon: 'play', label: (p.labels && p.labels.play) || 'Play', size: dzb(p), onClick: p.onPlay })) },
123
+ };
124
+ export { fmt };