@xenosystem/elements-react 0.0.1
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/LICENSE +18 -0
- package/README.md +41 -0
- package/dist/XenoElement.d.ts +39 -0
- package/dist/XenoElement.js +25 -0
- package/dist/containers/Avatar.d.ts +42 -0
- package/dist/containers/Avatar.js +29 -0
- package/dist/containers/Badges.d.ts +61 -0
- package/dist/containers/Badges.js +43 -0
- package/dist/containers/Card.d.ts +24 -0
- package/dist/containers/Card.js +5 -0
- package/dist/containers/Chip.d.ts +34 -0
- package/dist/containers/Chip.js +17 -0
- package/dist/containers/DataTable.d.ts +28 -0
- package/dist/containers/DataTable.js +13 -0
- package/dist/containers/ListRow.d.ts +37 -0
- package/dist/containers/ListRow.js +40 -0
- package/dist/containers/MessageBubble.d.ts +33 -0
- package/dist/containers/MessageBubble.js +9 -0
- package/dist/containers/Panel.d.ts +24 -0
- package/dist/containers/Panel.js +6 -0
- package/dist/containers/Table.d.ts +39 -0
- package/dist/containers/Table.js +20 -0
- package/dist/containers/Tile.d.ts +19 -0
- package/dist/containers/Tile.js +7 -0
- package/dist/content/Callout.d.ts +32 -0
- package/dist/content/Callout.js +10 -0
- package/dist/content/Caret.d.ts +9 -0
- package/dist/content/Caret.js +5 -0
- package/dist/content/CodeBlock.d.ts +36 -0
- package/dist/content/CodeBlock.js +50 -0
- package/dist/content/Collapsible.d.ts +24 -0
- package/dist/content/Collapsible.js +19 -0
- package/dist/content/InlineCode.d.ts +12 -0
- package/dist/content/InlineCode.js +5 -0
- package/dist/content/ModelPicker.d.ts +45 -0
- package/dist/content/ModelPicker.js +124 -0
- package/dist/content/SourceCard.d.ts +23 -0
- package/dist/content/SourceCard.js +18 -0
- package/dist/content/SourcesDisclosure.d.ts +26 -0
- package/dist/content/SourcesDisclosure.js +20 -0
- package/dist/controls/Button.d.ts +39 -0
- package/dist/controls/Button.js +16 -0
- package/dist/controls/ConfirmButton.d.ts +22 -0
- package/dist/controls/ConfirmButton.js +22 -0
- package/dist/controls/IconButton.d.ts +45 -0
- package/dist/controls/IconButton.js +15 -0
- package/dist/controls/Switch.d.ts +11 -0
- package/dist/controls/Switch.js +23 -0
- package/dist/controls/TextInput.d.ts +13 -0
- package/dist/controls/TextInput.js +6 -0
- package/dist/controls/Textarea.d.ts +8 -0
- package/dist/controls/Textarea.js +5 -0
- package/dist/controls/ToggleButton.d.ts +23 -0
- package/dist/controls/ToggleButton.js +23 -0
- package/dist/controls/util.d.ts +48 -0
- package/dist/controls/util.js +23 -0
- package/dist/forms/Checkbox.d.ts +18 -0
- package/dist/forms/Checkbox.js +28 -0
- package/dist/forms/RadioGroup.d.ts +51 -0
- package/dist/forms/RadioGroup.js +83 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.js +81 -0
- package/dist/layout/ResizablePanel.d.ts +40 -0
- package/dist/layout/ResizablePanel.js +108 -0
- package/dist/nav/Sidebar.d.ts +60 -0
- package/dist/nav/Sidebar.js +84 -0
- package/dist/overlays/DateTimePicker.d.ts +71 -0
- package/dist/overlays/DateTimePicker.js +191 -0
- package/dist/overlays/Menu.d.ts +28 -0
- package/dist/overlays/Menu.js +32 -0
- package/dist/overlays/MenuItem.d.ts +71 -0
- package/dist/overlays/MenuItem.js +15 -0
- package/dist/overlays/Modal.d.ts +36 -0
- package/dist/overlays/Modal.js +27 -0
- package/dist/overlays/PickerField.d.ts +32 -0
- package/dist/overlays/PickerField.js +40 -0
- package/dist/overlays/PillFilter.d.ts +25 -0
- package/dist/overlays/PillFilter.js +41 -0
- package/dist/overlays/Popover.d.ts +32 -0
- package/dist/overlays/Popover.js +38 -0
- package/dist/overlays/Reveal.d.ts +20 -0
- package/dist/overlays/Reveal.js +45 -0
- package/dist/overlays/SegmentedControl.d.ts +23 -0
- package/dist/overlays/SegmentedControl.js +43 -0
- package/dist/overlays/Tabs.d.ts +32 -0
- package/dist/overlays/Tabs.js +142 -0
- package/dist/overlays/Tooltip.d.ts +23 -0
- package/dist/overlays/Tooltip.js +61 -0
- package/dist/status/ProgressBar.d.ts +20 -0
- package/dist/status/ProgressBar.js +11 -0
- package/dist/status/Spinner.d.ts +16 -0
- package/dist/status/Spinner.js +12 -0
- package/dist/status/StatusIndicator.d.ts +19 -0
- package/dist/status/StatusIndicator.js +11 -0
- package/dist/status/StepTimeline.d.ts +26 -0
- package/dist/status/StepTimeline.js +16 -0
- package/dist/status/ThinkingCube.d.ts +24 -0
- package/dist/status/ThinkingCube.js +11 -0
- package/dist/useDialog.d.ts +45 -0
- package/dist/useDialog.js +122 -0
- package/dist/useGooPill.d.ts +57 -0
- package/dist/useGooPill.js +76 -0
- package/dist/useMenu.d.ts +50 -0
- package/dist/useMenu.js +119 -0
- package/dist/useTabs.d.ts +88 -0
- package/dist/useTabs.js +93 -0
- package/package.json +58 -0
- package/src/chrome-separated.css +132 -0
- package/src/chrome-unified.css +72 -0
- package/src/containers/Badges.css +116 -0
- package/src/containers/avatar.css +70 -0
- package/src/containers/card.css +75 -0
- package/src/containers/chip.css +103 -0
- package/src/containers/list-row.css +132 -0
- package/src/containers/message-bubble.css +112 -0
- package/src/containers/panel.css +120 -0
- package/src/containers/table.css +102 -0
- package/src/containers/tile.css +17 -0
- package/src/content/callout.css +77 -0
- package/src/content/caret.css +30 -0
- package/src/content/code-block.css +159 -0
- package/src/content/collapsible.css +80 -0
- package/src/content/inline-code.css +14 -0
- package/src/content/model-picker.css +219 -0
- package/src/content/source-card.css +99 -0
- package/src/content/sources-disclosure.css +118 -0
- package/src/controls.css +453 -0
- package/src/fonts.css +34 -0
- package/src/forms/Checkbox.css +114 -0
- package/src/forms/RadioGroup.css +115 -0
- package/src/goo.css +97 -0
- package/src/icon-motion.css +1909 -0
- package/src/layout/ResizablePanel.css +79 -0
- package/src/nav/sidebar.css +296 -0
- package/src/overlays/date-time-picker.css +238 -0
- package/src/overlays/menu.css +195 -0
- package/src/overlays/modal.css +167 -0
- package/src/overlays/picker-field.css +81 -0
- package/src/overlays/pill-filter.css +129 -0
- package/src/overlays/reveal.css +56 -0
- package/src/overlays/segmented-control.css +185 -0
- package/src/overlays/tabs.css +143 -0
- package/src/overlays/tooltip.css +126 -0
- package/src/scrollbar.css +184 -0
- package/src/size.css +179 -0
- package/src/status/progress-bar.css +88 -0
- package/src/status/spinner.css +33 -0
- package/src/status/statusindicator.css +65 -0
- package/src/status/steptimeline.css +137 -0
- package/src/status/thinkingcube.css +75 -0
- package/src/xeno-element.css +60 -0
- package/src/xeno-elements.css +71 -0
- package/src/xeno-theme.css +99 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* `<RadioGroup>` / `<RadioRow>` — SQUARE markers (radius-xs) with a smaller inner square, never circles.
|
|
3
|
+
* CSS-first: paint is keyed off `data-selection` (off|on) and `data-availability`; layout off
|
|
4
|
+
* `data-orientation`. Monochrome shell — only theme tokens, no literal colours.
|
|
5
|
+
*/
|
|
6
|
+
.xeno-radiogroup {
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
gap: 2px;
|
|
10
|
+
}
|
|
11
|
+
.xeno-radiogroup[data-orientation='horizontal'] {
|
|
12
|
+
flex-direction: row;
|
|
13
|
+
gap: 12px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.xeno-radio {
|
|
17
|
+
display: inline-flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
gap: 9px;
|
|
20
|
+
margin: 0;
|
|
21
|
+
padding: 6px 8px;
|
|
22
|
+
background: transparent;
|
|
23
|
+
border: 1px solid transparent;
|
|
24
|
+
border-radius: var(--xeno-radius-md);
|
|
25
|
+
color: var(--xeno-text);
|
|
26
|
+
font-family: inherit;
|
|
27
|
+
font-size: 14px;
|
|
28
|
+
line-height: 1;
|
|
29
|
+
text-align: left;
|
|
30
|
+
cursor: pointer;
|
|
31
|
+
user-select: none;
|
|
32
|
+
transition: background var(--xeno-dur) var(--xeno-ease);
|
|
33
|
+
}
|
|
34
|
+
.xeno-radio:hover:not(:disabled) {
|
|
35
|
+
background: var(--xeno-hover);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.xeno-radio-marker {
|
|
39
|
+
display: inline-flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
justify-content: center;
|
|
42
|
+
flex: none;
|
|
43
|
+
width: 16px;
|
|
44
|
+
height: 16px;
|
|
45
|
+
background: var(--xeno-canvas);
|
|
46
|
+
border: 1px solid var(--xeno-border);
|
|
47
|
+
border-radius: var(--xeno-radius-xs);
|
|
48
|
+
transition: border-color var(--xeno-dur) var(--xeno-ease);
|
|
49
|
+
}
|
|
50
|
+
.xeno-radio:hover:not(:disabled) .xeno-radio-marker {
|
|
51
|
+
border-color: var(--xeno-muted);
|
|
52
|
+
}
|
|
53
|
+
.xeno-radio[data-selection='on'] .xeno-radio-marker {
|
|
54
|
+
border-color: var(--xeno-text);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* The mark is a SQUARE, and at 8px it has to be drawn like one. It carried `--xeno-radius-xs` (3px),
|
|
58
|
+
which is 37% of its own width — enough rounding that at this size it reads as a dot rather than as a
|
|
59
|
+
square. `--xeno-radius-hair` puts it at 19%, in the same family as the switch knob's 21%. */
|
|
60
|
+
.xeno-radio-dot {
|
|
61
|
+
width: 8px;
|
|
62
|
+
height: 8px;
|
|
63
|
+
background: var(--xeno-text);
|
|
64
|
+
border-radius: var(--xeno-radius-hair, 1.5px);
|
|
65
|
+
scale: 0;
|
|
66
|
+
opacity: 0;
|
|
67
|
+
transition:
|
|
68
|
+
scale var(--xeno-dur) var(--xeno-ease),
|
|
69
|
+
opacity var(--xeno-dur) var(--xeno-ease);
|
|
70
|
+
}
|
|
71
|
+
.xeno-radio[data-selection='on'] .xeno-radio-dot {
|
|
72
|
+
scale: 1;
|
|
73
|
+
opacity: 1;
|
|
74
|
+
}
|
|
75
|
+
/* Choosing STAMPS: the square overshoots and settles, so the choice lands rather than fades in. Only on
|
|
76
|
+
the way in — letting go is quiet, and the transition above still handles it.
|
|
77
|
+
Armed at the group, which is safe HERE and would not be on a multi-select: exactly one radio can be
|
|
78
|
+
on, so the only one whose rule starts matching when the group arms is the one that just changed. */
|
|
79
|
+
.xeno-radiogroup[data-motion='ready'] .xeno-radio[data-selection='on'] .xeno-radio-dot {
|
|
80
|
+
animation: xeno-radio-stamp 260ms var(--xeno-ease-overshoot, cubic-bezier(0.34, 1.4, 0.5, 1));
|
|
81
|
+
}
|
|
82
|
+
@keyframes xeno-radio-stamp {
|
|
83
|
+
0% { scale: 0; opacity: 0; }
|
|
84
|
+
45% { scale: 1.22; opacity: 1; }
|
|
85
|
+
100% { scale: 1; opacity: 1; }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.xeno-radio-label {
|
|
89
|
+
color: var(--xeno-text);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.xeno-radio:focus-visible {
|
|
93
|
+
outline: none;
|
|
94
|
+
box-shadow:
|
|
95
|
+
0 0 0 2px var(--xeno-canvas),
|
|
96
|
+
0 0 0 4px var(--xeno-border);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.xeno-radio:disabled,
|
|
100
|
+
.xeno-radio[data-availability='disabled'] {
|
|
101
|
+
opacity: 0.4;
|
|
102
|
+
cursor: not-allowed;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@media (prefers-reduced-motion: reduce) {
|
|
106
|
+
.xeno-radio,
|
|
107
|
+
.xeno-radio-marker,
|
|
108
|
+
.xeno-radio-dot {
|
|
109
|
+
transition: none;
|
|
110
|
+
}
|
|
111
|
+
/* The mark still appears — it just does not stamp. */
|
|
112
|
+
.xeno-radio-dot {
|
|
113
|
+
animation: none !important;
|
|
114
|
+
}
|
|
115
|
+
}
|
package/src/goo.css
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* `goo.css` — the travelling hover pill, as a primitive rather than a menu feature.
|
|
3
|
+
*
|
|
4
|
+
* ONE filled row that travels to whatever you are pointing at, instead of each row painting its own
|
|
5
|
+
* background. A fill that appears on the new row and vanishes from the old one is two events with no
|
|
6
|
+
* journey between them, and it never says where it came from.
|
|
7
|
+
*
|
|
8
|
+
* This lives outside `menu.css` because the behaviour is not about menus. Any vertical list of rows can
|
|
9
|
+
* host it — a menu, a command palette, a product's own hand-rolled dropdown — and the alternative was
|
|
10
|
+
* every one of those copying the same twenty lines. Pair it with `useGooPill()`, which supplies the two
|
|
11
|
+
* numbers a stylesheet cannot work out on its own: where the row starts and how tall it is.
|
|
12
|
+
*
|
|
13
|
+
* The sidebar keeps its own version, and that is not duplication: a sidebar OWNS its rows (it maps an
|
|
14
|
+
* `items` array, so the offset is `index x stride` and pure CSS can do it). A host with arbitrary
|
|
15
|
+
* children cannot assume a stride, so it measures. Same effect, two honest implementations.
|
|
16
|
+
*/
|
|
17
|
+
.xeno-goo-host {
|
|
18
|
+
/* How far the pill misbehaves on the way. Restrained on purpose — enough deformation to read as
|
|
19
|
+
weight, not enough to look like jelly on something you use constantly. */
|
|
20
|
+
--xeno-goo-stretch: 0.14;
|
|
21
|
+
--xeno-goo-squeeze: 0.06;
|
|
22
|
+
--xeno-goo-dur: 220ms;
|
|
23
|
+
--xeno-goo-pill-ease: cubic-bezier(0.34, 1.12, 0.5, 1);
|
|
24
|
+
isolation: isolate;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* The pill is positioned against the host, so the host has to be a containing block — but claiming
|
|
28
|
+
`position: relative` outright would be this stylesheet overruling the host's own layout. A menu panel
|
|
29
|
+
is `absolute` (it is anchored to a trigger) and a product's panel is Tailwind's `.absolute`; both are
|
|
30
|
+
one class, so whoever loads last would win, and a panel silently dropped back into flow is a much
|
|
31
|
+
worse bug than a pill in the wrong place.
|
|
32
|
+
`:where()` is specificity ZERO: it supplies `relative` only where nothing else has an opinion, and
|
|
33
|
+
loses to every real position the host declares. `absolute`, `fixed` and `sticky` are all containing
|
|
34
|
+
blocks too, so losing is the correct outcome, not a fallback. */
|
|
35
|
+
:where(.xeno-goo-host) {
|
|
36
|
+
position: relative;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* `--xeno-goo-inset` defaults to the 4px a padded list gives itself, so the pill lines up with the rows
|
|
40
|
+
rather than with the panel edge. Fill and radius are tokens so a product can hand it its own ink. */
|
|
41
|
+
.xeno-goo-pill {
|
|
42
|
+
position: absolute;
|
|
43
|
+
z-index: -1;
|
|
44
|
+
left: var(--xeno-goo-inset, 4px);
|
|
45
|
+
right: var(--xeno-goo-inset, 4px);
|
|
46
|
+
top: var(--xeno-goo-y, 0px);
|
|
47
|
+
height: var(--xeno-goo-h, 0px);
|
|
48
|
+
background: var(--xeno-goo-fill, var(--xeno-control));
|
|
49
|
+
border-radius: var(--xeno-goo-radius, var(--xeno-radius-sm));
|
|
50
|
+
opacity: 0;
|
|
51
|
+
pointer-events: none;
|
|
52
|
+
transition:
|
|
53
|
+
top var(--xeno-goo-dur) var(--xeno-goo-pill-ease),
|
|
54
|
+
height var(--xeno-goo-dur) var(--xeno-goo-pill-ease),
|
|
55
|
+
opacity var(--xeno-dur, 0.14s) ease;
|
|
56
|
+
}
|
|
57
|
+
.xeno-goo-host[data-goo='on'] > .xeno-goo-pill {
|
|
58
|
+
opacity: 1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* The stretch, keyed off the row index so re-pointing restarts it — a browser will not replay an
|
|
62
|
+
animation whose name did not change. Ten rows; past that the pill still travels, it just stops
|
|
63
|
+
stretching, which is a fair trade for a list nobody scans by deformation anyway. */
|
|
64
|
+
.xeno-goo-host[data-goo='on'][data-goo-index='0'] > .xeno-goo-pill { animation: xeno-goo-pill-0 var(--xeno-goo-dur) 1; }
|
|
65
|
+
.xeno-goo-host[data-goo='on'][data-goo-index='1'] > .xeno-goo-pill { animation: xeno-goo-pill-1 var(--xeno-goo-dur) 1; }
|
|
66
|
+
.xeno-goo-host[data-goo='on'][data-goo-index='2'] > .xeno-goo-pill { animation: xeno-goo-pill-2 var(--xeno-goo-dur) 1; }
|
|
67
|
+
.xeno-goo-host[data-goo='on'][data-goo-index='3'] > .xeno-goo-pill { animation: xeno-goo-pill-3 var(--xeno-goo-dur) 1; }
|
|
68
|
+
.xeno-goo-host[data-goo='on'][data-goo-index='4'] > .xeno-goo-pill { animation: xeno-goo-pill-4 var(--xeno-goo-dur) 1; }
|
|
69
|
+
.xeno-goo-host[data-goo='on'][data-goo-index='5'] > .xeno-goo-pill { animation: xeno-goo-pill-5 var(--xeno-goo-dur) 1; }
|
|
70
|
+
.xeno-goo-host[data-goo='on'][data-goo-index='6'] > .xeno-goo-pill { animation: xeno-goo-pill-6 var(--xeno-goo-dur) 1; }
|
|
71
|
+
.xeno-goo-host[data-goo='on'][data-goo-index='7'] > .xeno-goo-pill { animation: xeno-goo-pill-7 var(--xeno-goo-dur) 1; }
|
|
72
|
+
.xeno-goo-host[data-goo='on'][data-goo-index='8'] > .xeno-goo-pill { animation: xeno-goo-pill-8 var(--xeno-goo-dur) 1; }
|
|
73
|
+
.xeno-goo-host[data-goo='on'][data-goo-index='9'] > .xeno-goo-pill { animation: xeno-goo-pill-9 var(--xeno-goo-dur) 1; }
|
|
74
|
+
@keyframes xeno-goo-pill-0 { 0% { scale: 1 1; } 50% { scale: calc(1 - var(--xeno-goo-squeeze)) calc(1 + var(--xeno-goo-stretch)); } 100% { scale: 1 1; } }
|
|
75
|
+
@keyframes xeno-goo-pill-1 { 0% { scale: 1 1; } 50% { scale: calc(1 - var(--xeno-goo-squeeze)) calc(1 + var(--xeno-goo-stretch)); } 100% { scale: 1 1; } }
|
|
76
|
+
@keyframes xeno-goo-pill-2 { 0% { scale: 1 1; } 50% { scale: calc(1 - var(--xeno-goo-squeeze)) calc(1 + var(--xeno-goo-stretch)); } 100% { scale: 1 1; } }
|
|
77
|
+
@keyframes xeno-goo-pill-3 { 0% { scale: 1 1; } 50% { scale: calc(1 - var(--xeno-goo-squeeze)) calc(1 + var(--xeno-goo-stretch)); } 100% { scale: 1 1; } }
|
|
78
|
+
@keyframes xeno-goo-pill-4 { 0% { scale: 1 1; } 50% { scale: calc(1 - var(--xeno-goo-squeeze)) calc(1 + var(--xeno-goo-stretch)); } 100% { scale: 1 1; } }
|
|
79
|
+
@keyframes xeno-goo-pill-5 { 0% { scale: 1 1; } 50% { scale: calc(1 - var(--xeno-goo-squeeze)) calc(1 + var(--xeno-goo-stretch)); } 100% { scale: 1 1; } }
|
|
80
|
+
@keyframes xeno-goo-pill-6 { 0% { scale: 1 1; } 50% { scale: calc(1 - var(--xeno-goo-squeeze)) calc(1 + var(--xeno-goo-stretch)); } 100% { scale: 1 1; } }
|
|
81
|
+
@keyframes xeno-goo-pill-7 { 0% { scale: 1 1; } 50% { scale: calc(1 - var(--xeno-goo-squeeze)) calc(1 + var(--xeno-goo-stretch)); } 100% { scale: 1 1; } }
|
|
82
|
+
@keyframes xeno-goo-pill-8 { 0% { scale: 1 1; } 50% { scale: calc(1 - var(--xeno-goo-squeeze)) calc(1 + var(--xeno-goo-stretch)); } 100% { scale: 1 1; } }
|
|
83
|
+
@keyframes xeno-goo-pill-9 { 0% { scale: 1 1; } 50% { scale: calc(1 - var(--xeno-goo-squeeze)) calc(1 + var(--xeno-goo-stretch)); } 100% { scale: 1 1; } }
|
|
84
|
+
|
|
85
|
+
/* Nothing here raises the rows, on purpose. A negative `z-index` child paints below its parent's in-flow
|
|
86
|
+
content, so the rows are already over the pill without a single declaration — and a blanket
|
|
87
|
+
`position: relative` on the host's children would quietly re-anchor any absolutely-positioned
|
|
88
|
+
descendant (a submenu, a tooltip) to the wrong box. The one thing a host DOES have to give up is rows
|
|
89
|
+
that paint their own hover background: two filled surfaces read as two highlights. */
|
|
90
|
+
|
|
91
|
+
/* Motion off: the pill still marks the row, it just stops travelling and deforming to get there. */
|
|
92
|
+
@media (prefers-reduced-motion: reduce) {
|
|
93
|
+
.xeno-goo-pill {
|
|
94
|
+
transition: opacity var(--xeno-dur, 0.14s) ease;
|
|
95
|
+
animation: none !important;
|
|
96
|
+
}
|
|
97
|
+
}
|