@vmz/ui 0.2.3 → 0.2.5

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 (63) hide show
  1. package/package.json +1 -1
  2. package/src/components/Accordion.vmz +46 -63
  3. package/src/components/Alert.vmz +35 -38
  4. package/src/components/AppShell.vmz +92 -75
  5. package/src/components/Autocomplete.vmz +91 -119
  6. package/src/components/Avatar.vmz +37 -8
  7. package/src/components/Badge.vmz +29 -23
  8. package/src/components/Breadcrumb.vmz +36 -34
  9. package/src/components/BulkActions.vmz +22 -28
  10. package/src/components/Button.vmz +139 -121
  11. package/src/components/Callout.vmz +33 -36
  12. package/src/components/Card.vmz +68 -51
  13. package/src/components/Checkbox.vmz +28 -47
  14. package/src/components/CodeBlock.vmz +31 -25
  15. package/src/components/Collapse.vmz +34 -16
  16. package/src/components/ConsoleShell.vmz +107 -110
  17. package/src/components/Content.vmz +8 -4
  18. package/src/components/DataTable.vmz +124 -150
  19. package/src/components/DatePicker.vmz +526 -521
  20. package/src/components/Dialog.vmz +408 -327
  21. package/src/components/Divider.vmz +38 -11
  22. package/src/components/Drawer.vmz +325 -318
  23. package/src/components/Dropdown.vmz +53 -19
  24. package/src/components/Empty.vmz +23 -21
  25. package/src/components/Field.vmz +77 -83
  26. package/src/components/FilterBar.vmz +8 -10
  27. package/src/components/Flex.vmz +21 -6
  28. package/src/components/Footer.vmz +9 -4
  29. package/src/components/Form.vmz +35 -46
  30. package/src/components/FormItem.vmz +44 -43
  31. package/src/components/Grid.vmz +22 -6
  32. package/src/components/Header.vmz +11 -4
  33. package/src/components/Icon.vmz +43 -44
  34. package/src/components/Layout.vmz +9 -4
  35. package/src/components/Link.vmz +17 -25
  36. package/src/components/List.vmz +62 -75
  37. package/src/components/Menu.vmz +182 -197
  38. package/src/components/Notification.vmz +35 -38
  39. package/src/components/Pagination.vmz +37 -47
  40. package/src/components/Popover.vmz +157 -171
  41. package/src/components/Progress.vmz +36 -10
  42. package/src/components/Prose.vmz +29 -31
  43. package/src/components/QueryForm.vmz +14 -22
  44. package/src/components/RadioGroup.vmz +70 -84
  45. package/src/components/Result.vmz +36 -38
  46. package/src/components/Segmented.vmz +43 -22
  47. package/src/components/Select.vmz +404 -411
  48. package/src/components/Sider.vmz +21 -4
  49. package/src/components/Skeleton.vmz +34 -42
  50. package/src/components/Space.vmz +24 -9
  51. package/src/components/Spinner.vmz +17 -23
  52. package/src/components/Steps.vmz +48 -51
  53. package/src/components/Switch.vmz +50 -60
  54. package/src/components/Table.vmz +81 -84
  55. package/src/components/Tabs.vmz +66 -88
  56. package/src/components/Tag.vmz +36 -11
  57. package/src/components/TextArea.vmz +73 -77
  58. package/src/components/Timeline.vmz +36 -32
  59. package/src/components/Toc.vmz +31 -37
  60. package/src/components/Tooltip.vmz +25 -35
  61. package/src/components/Tree.vmz +127 -144
  62. package/src/components/Typography.vmz +33 -7
  63. package/src/components/Upload.vmz +1135 -1057
@@ -1,6 +1,23 @@
1
- <template><aside class="vmz-ui-sider" data-vmz-ui="sider"><slot /></aside></template>
1
+ <template>
2
+ <aside data-vmz-ui="sider" class="vmz-ui-sider">
3
+ <slot />
4
+ </aside>
5
+ </template>
2
6
  <style>
3
- .vmz-ui-sider{width:16rem;padding:1rem;border-right:1px solid var(--vmz-line-default);background:var(--vmz-surface-paper)}
4
- @media(max-width:720px){.vmz-ui-sider{width:100%;border-right:0;border-bottom:1px solid var(--vmz-line-default)}}
7
+ .vmz-ui-sider {
8
+ width: 16rem;
9
+ padding: 1rem;
10
+ border-right: 1px solid var(--vmz-line-default);
11
+ background: var(--vmz-surface-paper);
12
+ }
13
+ @media (max-width: 720px) {
14
+ .vmz-ui-sider {
15
+ width: 100%;
16
+ border-right: 0;
17
+ border-bottom: 1px solid var(--vmz-line-default);
18
+ }
19
+ }
5
20
  </style>
6
- <script client>export default class Sider {}</script>
21
+ <script client>
22
+ export default class Sider {}
23
+ </script>
@@ -1,80 +1,72 @@
1
1
  <template>
2
- <div
3
- class="vmz-ui-skeleton"
4
- data-vmz-ui="skeleton"
5
- :data-loading="loadingAttr"
6
- role="status"
7
- aria-live="polite"
8
- :aria-busy="loadingAttr"
9
- >
10
- <div v-if="loading" class="vmz-ui-skeleton__block" data-vmz-skeleton="block"></div>
11
- <div v-if="loading" class="vmz-ui-skeleton__line" data-vmz-skeleton="line"></div>
12
- <div v-if="loading" class="vmz-ui-skeleton__line" data-vmz-skeleton="line"></div>
13
- <div v-if="!loading" class="vmz-ui-skeleton__content">
14
- <slot />
15
- </div>
2
+ <div data-vmz-ui="skeleton" :data-loading="loadingAttr" role="status" aria-live="polite" :aria-busy="loadingAttr" class="vmz-ui-skeleton">
3
+ <div v-if="loading" data-vmz-skeleton="block" class="vmz-ui-skeleton__block" />
4
+ <div v-if="loading" data-vmz-skeleton="line" class="vmz-ui-skeleton__line" />
5
+ <div v-if="loading" data-vmz-skeleton="line" class="vmz-ui-skeleton__line" />
6
+ <div v-if="!loading" class="vmz-ui-skeleton__content">
7
+ <slot />
8
+ </div>
16
9
  </div>
17
10
  </template>
18
-
19
11
  <style>
20
- .vmz-ui-skeleton {
12
+ .vmz-ui-skeleton {
21
13
  display: flex;
22
14
  flex-direction: column;
23
15
  gap: var(--vmz-density-compact-gap);
24
16
  min-height: 4.5rem;
25
- }
17
+ }
26
18
 
27
- .vmz-ui-skeleton__block,
28
- .vmz-ui-skeleton__line {
19
+ .vmz-ui-skeleton__block,
20
+ .vmz-ui-skeleton__line {
29
21
  border-radius: var(--vmz-radius-md, 6px);
30
22
  background: color-mix(in srgb, var(--vmz-action-primary-background) 18%, transparent);
31
- animation: vmz-ui-skeleton-pulse var(--vmz-motion-overlay-duration) var(--vmz-motion-overlay-easing) infinite alternate;
32
- }
23
+ animation: vmz-ui-skeleton-pulse var(--vmz-motion-overlay-duration)
24
+ var(--vmz-motion-overlay-easing) infinite alternate;
25
+ }
33
26
 
34
- .vmz-ui-skeleton__block {
27
+ .vmz-ui-skeleton__block {
35
28
  width: 100%;
36
29
  height: 2.5rem;
37
- }
30
+ }
38
31
 
39
- .vmz-ui-skeleton__line {
32
+ .vmz-ui-skeleton__line {
40
33
  width: 72%;
41
34
  height: 0.75rem;
42
- }
35
+ }
43
36
 
44
- .vmz-ui-skeleton__line + .vmz-ui-skeleton__line {
37
+ .vmz-ui-skeleton__line + .vmz-ui-skeleton__line {
45
38
  width: 48%;
46
- }
39
+ }
47
40
 
48
- .vmz-ui-skeleton__content {
41
+ .vmz-ui-skeleton__content {
49
42
  display: flex;
50
43
  flex-direction: column;
51
44
  gap: inherit;
52
- }
45
+ }
53
46
 
54
- @keyframes vmz-ui-skeleton-pulse {
47
+ @keyframes vmz-ui-skeleton-pulse {
55
48
  from {
56
- opacity: 0.55;
49
+ opacity: 0.55;
57
50
  }
58
51
  to {
59
- opacity: 1;
52
+ opacity: 1;
60
53
  }
61
- }
54
+ }
62
55
 
63
- @media (prefers-reduced-motion: reduce) {
56
+ @media (prefers-reduced-motion: reduce) {
64
57
  .vmz-ui-skeleton__block,
65
58
  .vmz-ui-skeleton__line {
66
- animation: none;
59
+ animation: none;
67
60
  }
68
- }
61
+ }
69
62
  </style>
70
-
71
63
  <script client>
72
64
  /**
73
- * VMZ UI — Skeleton (UI5 Console Surface).
74
- * Loading placeholder with stable min-height (CLS guard); parent owns loading.
75
- */
65
+ * VMZ UI — Skeleton (UI5 Console Surface).
66
+ * Loading placeholder with stable min-height (CLS guard); parent owns loading.
67
+ */
76
68
  export default class Skeleton {
77
- public loading: boolean = true;
78
- public loadingAttr: string = "true";
69
+ public loading: boolean = true;
70
+ public loadingAttr: string = "true";
79
71
  }
80
72
  </script>
@@ -1,15 +1,30 @@
1
- <template><div class="vmz-ui-space" data-vmz-ui="space" :data-direction="direction" :data-size="size" :data-wrap="wrap?'true':'false'"><slot /></div></template>
1
+ <template>
2
+ <div data-vmz-ui="space" :data-direction="direction" :data-size="size" :data-wrap='wrap ? "true" : "false"' class="vmz-ui-space">
3
+ <slot />
4
+ </div>
5
+ </template>
2
6
  <style>
3
- .vmz-ui-space{display:flex;gap:var(--vmz-density-control-gap,.75rem)}
4
- .vmz-ui-space[data-direction='vertical']{flex-direction:column}
5
- .vmz-ui-space[data-wrap='true']{flex-wrap:wrap}
6
- .vmz-ui-space[data-size='small']{gap:.5rem}
7
- .vmz-ui-space[data-size='large']{gap:1.5rem}
7
+ .vmz-ui-space {
8
+ display: flex;
9
+ gap: var(--vmz-density-control-gap, 0.75rem);
10
+ }
11
+ .vmz-ui-space[data-direction="vertical"] {
12
+ flex-direction: column;
13
+ }
14
+ .vmz-ui-space[data-wrap="true"] {
15
+ flex-wrap: wrap;
16
+ }
17
+ .vmz-ui-space[data-size="small"] {
18
+ gap: 0.5rem;
19
+ }
20
+ .vmz-ui-space[data-size="large"] {
21
+ gap: 1.5rem;
22
+ }
8
23
  </style>
9
24
  <script client>
10
25
  export default class Space {
11
- public direction: string = 'horizontal';
12
- public size: string = 'middle';
13
- public wrap: boolean = false;
26
+ public direction: string = "horizontal";
27
+ public size: string = "middle";
28
+ public wrap: boolean = false;
14
29
  }
15
30
  </script>
@@ -1,14 +1,8 @@
1
1
  <template>
2
- <span
3
- class="vmz-ui-spinner"
4
- data-vmz-ui="spinner"
5
- role="status"
6
- :aria-label="label"
7
- ></span>
2
+ <span data-vmz-ui="spinner" role="status" :aria-label="label" class="vmz-ui-spinner" />
8
3
  </template>
9
-
10
4
  <style>
11
- .vmz-ui-spinner {
5
+ .vmz-ui-spinner {
12
6
  display: inline-block;
13
7
  width: 1rem;
14
8
  height: 1rem;
@@ -16,30 +10,30 @@
16
10
  border-top-color: var(--vmz-action-primary-background);
17
11
  border-radius: 50%;
18
12
  vertical-align: -0.15em;
19
- animation: vmz-ui-spin var(--vmz-motion-overlay-duration) var(--vmz-motion-overlay-easing) infinite;
20
- }
13
+ animation: vmz-ui-spin var(--vmz-motion-overlay-duration) var(--vmz-motion-overlay-easing)
14
+ infinite;
15
+ }
21
16
 
22
- @keyframes vmz-ui-spin {
17
+ @keyframes vmz-ui-spin {
23
18
  to {
24
- transform: rotate(360deg);
19
+ transform: rotate(360deg);
25
20
  }
26
- }
21
+ }
27
22
 
28
- @media (prefers-reduced-motion: reduce) {
23
+ @media (prefers-reduced-motion: reduce) {
29
24
  .vmz-ui-spinner {
30
- animation: none;
31
- border-color: var(--vmz-action-primary-background);
32
- opacity: 0.55;
25
+ animation: none;
26
+ border-color: var(--vmz-action-primary-background);
27
+ opacity: 0.55;
33
28
  }
34
- }
29
+ }
35
30
  </style>
36
-
37
31
  <script client>
38
32
  /**
39
- * VMZ UI — Spinner (Foundation).
40
- * Loading indicator; reduced-motion keeps static feedback.
41
- */
33
+ * VMZ UI — Spinner (Foundation).
34
+ * Loading indicator; reduced-motion keeps static feedback.
35
+ */
42
36
  export default class Spinner {
43
- public label: string = "Loading";
37
+ public label: string = "Loading";
44
38
  }
45
39
  </script>
@@ -1,24 +1,22 @@
1
1
  <template>
2
- <ol class="vmz-ui-steps" data-vmz-ui="steps" :aria-label="label">
3
- <li
4
- v-for="item in items"
5
- :key="item.id"
6
- class="vmz-ui-steps__item"
7
- :data-vmz-step="item.id"
8
- :data-status="item.status"
9
- :aria-current="current === item.id ? 'step' : 'false'"
10
- >
11
- <span class="vmz-ui-steps__index" :data-vmz-step-index="item.id">{{ item.index }}</span>
12
- <span class="vmz-ui-steps__body">
13
- <span class="vmz-ui-steps__title">{{ item.title }}</span>
14
- <span v-if="item.description" class="vmz-ui-steps__description">{{ item.description }}</span>
15
- </span>
16
- </li>
2
+ <ol data-vmz-ui="steps" :aria-label="label" class="vmz-ui-steps">
3
+ <li v-for="item in items" :key="item.id" :data-vmz-step="item.id" :data-status="item.status" :aria-current='current === item.id ? "step" : "false"' class="vmz-ui-steps__item">
4
+ <span :data-vmz-step-index="item.id" class="vmz-ui-steps__index">
5
+ {{ item.index }}
6
+ </span>
7
+ <span class="vmz-ui-steps__body">
8
+ <span class="vmz-ui-steps__title">
9
+ {{ item.title }}
10
+ </span>
11
+ <span v-if="item.description" class="vmz-ui-steps__description">
12
+ {{ item.description }}
13
+ </span>
14
+ </span>
15
+ </li>
17
16
  </ol>
18
17
  </template>
19
-
20
18
  <style>
21
- .vmz-ui-steps {
19
+ .vmz-ui-steps {
22
20
  margin: 0;
23
21
  padding: 0;
24
22
  list-style: none;
@@ -26,24 +24,24 @@
26
24
  flex-direction: column;
27
25
  gap: var(--vmz-density-control-gap);
28
26
  max-width: 36rem;
29
- }
27
+ }
30
28
 
31
- .vmz-ui-steps__item {
29
+ .vmz-ui-steps__item {
32
30
  display: flex;
33
31
  align-items: flex-start;
34
32
  gap: 0.75rem;
35
33
  color: var(--vmz-text-steel);
36
- }
34
+ }
37
35
 
38
- .vmz-ui-steps__item[data-status='done'] {
36
+ .vmz-ui-steps__item[data-status="done"] {
39
37
  color: var(--vmz-status-success-foreground);
40
- }
38
+ }
41
39
 
42
- .vmz-ui-steps__item[data-status='current'] {
40
+ .vmz-ui-steps__item[data-status="current"] {
43
41
  color: var(--vmz-action-primary-active);
44
- }
42
+ }
45
43
 
46
- .vmz-ui-steps__index {
44
+ .vmz-ui-steps__index {
47
45
  flex: 0 0 auto;
48
46
  min-width: 1.75rem;
49
47
  height: 1.75rem;
@@ -55,52 +53,51 @@
55
53
  font-weight: 700;
56
54
  font-size: 0.85rem;
57
55
  color: inherit;
58
- }
56
+ }
59
57
 
60
- .vmz-ui-steps__item[data-status='current'] .vmz-ui-steps__index {
58
+ .vmz-ui-steps__item[data-status="current"] .vmz-ui-steps__index {
61
59
  border-color: var(--vmz-action-primary-background);
62
60
  background: var(--vmz-action-primary-background);
63
61
  color: var(--vmz-action-primary-foreground);
64
- }
62
+ }
65
63
 
66
- .vmz-ui-steps__item[data-status='done'] .vmz-ui-steps__index {
64
+ .vmz-ui-steps__item[data-status="done"] .vmz-ui-steps__index {
67
65
  border-color: var(--vmz-status-success-accent);
68
66
  background: color-mix(in srgb, var(--vmz-status-success-accent) 18%, transparent);
69
- }
67
+ }
70
68
 
71
- .vmz-ui-steps__body {
69
+ .vmz-ui-steps__body {
72
70
  display: flex;
73
71
  flex-direction: column;
74
72
  gap: 0.2rem;
75
73
  padding-top: 0.15rem;
76
- }
74
+ }
77
75
 
78
- .vmz-ui-steps__title {
76
+ .vmz-ui-steps__title {
79
77
  font-weight: 700;
80
- }
78
+ }
81
79
 
82
- .vmz-ui-steps__description {
80
+ .vmz-ui-steps__description {
83
81
  font-size: 0.9em;
84
82
  opacity: 0.9;
85
- }
83
+ }
86
84
  </style>
87
-
88
85
  <script client>
89
86
  /**
90
- * VMZ UI — Steps.
91
- * Parent owns `current` id and item `status` (upcoming|current|done).
92
- * Navigation actions stay on the page (no private wizard store).
93
- */
87
+ * VMZ UI — Steps.
88
+ * Parent owns `current` id and item `status` (upcoming|current|done).
89
+ * Navigation actions stay on the page (no private wizard store).
90
+ */
94
91
  export default class Steps {
95
- public label: string = "Steps";
96
- public current: string = "";
97
- /** @type {{ id: string, index: string, title: string, description?: string, status: string }[]} */
98
- public items: {
99
- id: string;
100
- index: string;
101
- title: string;
102
- description?: string;
103
- status: string;
104
- }[] = [];
92
+ public label: string = "Steps";
93
+ public current: string = "";
94
+ /** @type {{ id: string, index: string, title: string, description?: string, status: string }[]} */
95
+ public items: {
96
+ id: string;
97
+ index: string;
98
+ title: string;
99
+ description?: string;
100
+ status: string;
101
+ }[] = [];
105
102
  }
106
103
  </script>
@@ -1,32 +1,23 @@
1
1
  <template>
2
- <label class="vmz-ui-switch" data-vmz-ui="switch" :for="controlId">
3
- <button
4
- :id="controlId"
5
- type="button"
6
- class="vmz-ui-switch__control"
7
- role="switch"
8
- :aria-checked="checked"
9
- :aria-describedby="describedBy"
10
- :disabled="disabled"
11
- @click="toggle"
12
- :onKeyDown="onKeyDown"
13
- >
14
- <span class="vmz-ui-switch__thumb" aria-hidden="true"></span>
15
- </button>
16
- <span class="vmz-ui-switch__label">{{ label }}</span>
2
+ <label data-vmz-ui="switch" :for="controlId" class="vmz-ui-switch">
3
+ <button :id="controlId" type="button" role="switch" :aria-checked="checked" :aria-describedby="describedBy" :disabled="disabled" @click="toggle" :onKeyDown="onKeyDown" class="vmz-ui-switch__control">
4
+ <span aria-hidden="true" class="vmz-ui-switch__thumb" />
5
+ </button>
6
+ <span class="vmz-ui-switch__label">
7
+ {{ label }}
8
+ </span>
17
9
  </label>
18
10
  </template>
19
-
20
11
  <style>
21
- .vmz-ui-switch {
12
+ .vmz-ui-switch {
22
13
  display: inline-flex;
23
14
  align-items: center;
24
15
  gap: 0.55rem;
25
16
  font: inherit;
26
17
  cursor: pointer;
27
- }
18
+ }
28
19
 
29
- .vmz-ui-switch__control {
20
+ .vmz-ui-switch__control {
30
21
  position: relative;
31
22
  width: 2.4rem;
32
23
  height: 1.35rem;
@@ -36,23 +27,23 @@
36
27
  background: transparent;
37
28
  cursor: pointer;
38
29
  outline: none;
39
- }
30
+ }
40
31
 
41
- .vmz-ui-switch__control[aria-checked='true'],
42
- .vmz-ui-switch__control[aria-checked=''] {
32
+ .vmz-ui-switch__control[aria-checked="true"],
33
+ .vmz-ui-switch__control[aria-checked=""] {
43
34
  background: var(--vmz-action-primary-background);
44
- }
35
+ }
45
36
 
46
- .vmz-ui-switch__control:focus-visible {
37
+ .vmz-ui-switch__control:focus-visible {
47
38
  box-shadow: 0 0 0 2px var(--vmz-focus-ring);
48
- }
39
+ }
49
40
 
50
- .vmz-ui-switch__control:disabled {
41
+ .vmz-ui-switch__control:disabled {
51
42
  opacity: 0.55;
52
43
  cursor: not-allowed;
53
- }
44
+ }
54
45
 
55
- .vmz-ui-switch__thumb {
46
+ .vmz-ui-switch__thumb {
56
47
  position: absolute;
57
48
  top: 0.12rem;
58
49
  left: 0.12rem;
@@ -61,48 +52,47 @@
61
52
  border-radius: 999px;
62
53
  background: var(--vmz-action-primary-foreground);
63
54
  transition: transform var(--vmz-motion-control-duration) var(--vmz-motion-control-easing);
64
- }
55
+ }
65
56
 
66
- .vmz-ui-switch__control[aria-checked='true'] .vmz-ui-switch__thumb,
67
- .vmz-ui-switch__control[aria-checked=''] .vmz-ui-switch__thumb {
57
+ .vmz-ui-switch__control[aria-checked="true"] .vmz-ui-switch__thumb,
58
+ .vmz-ui-switch__control[aria-checked=""] .vmz-ui-switch__thumb {
68
59
  transform: translateX(1.05rem);
69
- }
60
+ }
70
61
 
71
- .vmz-ui-switch__label {
62
+ .vmz-ui-switch__label {
72
63
  font-weight: 600;
73
- }
64
+ }
74
65
 
75
- @media (prefers-reduced-motion: reduce) {
66
+ @media (prefers-reduced-motion: reduce) {
76
67
  .vmz-ui-switch__thumb {
77
- transition: none;
68
+ transition: none;
78
69
  }
79
- }
70
+ }
80
71
  </style>
81
-
82
72
  <script client>
83
73
  /**
84
- * VMZ UI — Switch (UI2).
85
- * role=switch + aria-checked; parent owns checked (no parallel form store).
86
- */
74
+ * VMZ UI — Switch (UI2).
75
+ * role=switch + aria-checked; parent owns checked (no parallel form store).
76
+ */
87
77
  export default class Switch {
88
- public label: string = "";
89
- public checked: boolean = false;
90
- public disabled: boolean = false;
91
- public controlId: string = "vmz-switch";
92
- public describedBy: string = "";
93
- public onChange: ((checked: boolean) => void) | null = null;
94
- toggle() {
95
- if (this.disabled) return;
96
- const next = !this.checked;
97
- this.emit("change", next);
98
- if (typeof this.onChange === "function") this.onChange(next);
99
- }
100
- onKeyDown(ev) {
101
- if (!ev) return;
102
- if (ev.key === " " || ev.key === "Enter") {
103
- ev.preventDefault();
104
- this.toggle();
105
- }
106
- }
78
+ public label: string = "";
79
+ public checked: boolean = false;
80
+ public disabled: boolean = false;
81
+ public controlId: string = "vmz-switch";
82
+ public describedBy: string = "";
83
+ public onChange: ((checked: boolean) => void) | null = null;
84
+ toggle() {
85
+ if (this.disabled) return;
86
+ const next = !this.checked;
87
+ this.emit("change", next);
88
+ if (typeof this.onChange === "function") this.onChange(next);
89
+ }
90
+ onKeyDown(ev) {
91
+ if (!ev) return;
92
+ if (ev.key === " " || ev.key === "Enter") {
93
+ ev.preventDefault();
94
+ this.toggle();
95
+ }
96
+ }
107
97
  }
108
98
  </script>