@fluid-theme/core 0.4.3

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 (48) hide show
  1. package/AGENTS.md +32 -0
  2. package/CHANGELOG.md +159 -0
  3. package/CLAUDE.md +104 -0
  4. package/GEMINI.md +48 -0
  5. package/LICENSE +21 -0
  6. package/README.md +253 -0
  7. package/dist/fluid.min.css +2 -0
  8. package/fluid-agent-kit/README.md +57 -0
  9. package/fluid-agent-kit/skills/accessibility.md +41 -0
  10. package/fluid-agent-kit/skills/css-conventions.md +51 -0
  11. package/fluid-agent-kit/skills/image-optimization.md +115 -0
  12. package/fluid-agent-kit/skills/ux-rules.md +43 -0
  13. package/package.json +68 -0
  14. package/src/animations/scroll-reveal.css +224 -0
  15. package/src/components/accordion.css +250 -0
  16. package/src/components/alert.css +453 -0
  17. package/src/components/avatar.css +192 -0
  18. package/src/components/badge.css +182 -0
  19. package/src/components/button.css +387 -0
  20. package/src/components/card.css +650 -0
  21. package/src/components/chip.css +143 -0
  22. package/src/components/dropdown.css +335 -0
  23. package/src/components/empty-state.css +105 -0
  24. package/src/components/form.css +508 -0
  25. package/src/components/hero.css +293 -0
  26. package/src/components/indicator.css +455 -0
  27. package/src/components/loading.css +274 -0
  28. package/src/components/modal.css +364 -0
  29. package/src/components/nav.css +726 -0
  30. package/src/components/scroll-progress.css +104 -0
  31. package/src/components/search-input.css +125 -0
  32. package/src/components/settings-panel.css +603 -0
  33. package/src/components/step-indicator.css +188 -0
  34. package/src/components/table.css +350 -0
  35. package/src/components/themed-images.css +229 -0
  36. package/src/components/tooltip.css +247 -0
  37. package/src/components/version-badge.css +70 -0
  38. package/src/core/base.css +884 -0
  39. package/src/core/reset.css +159 -0
  40. package/src/core/tokens.css +2416 -0
  41. package/src/fluid.css +242 -0
  42. package/src/layouts/positioning.css +626 -0
  43. package/src/sections/cta.css +210 -0
  44. package/src/sections/features.css +356 -0
  45. package/src/sections/footer.css +283 -0
  46. package/src/sections/stats.css +234 -0
  47. package/src/sections/team.css +181 -0
  48. package/src/sections/testimonials.css +251 -0
@@ -0,0 +1,51 @@
1
+ # Fluid Theme — CSS Conventions
2
+
3
+ When writing or modifying CSS for a site built with The Fluid Theme, follow these rules.
4
+
5
+ ## Layer Order (never violate)
6
+
7
+ ```css
8
+ @layer reset, tokens, base, layouts, components, themes, utilities;
9
+ ```
10
+
11
+ ## Required Patterns
12
+
13
+ - **All values from tokens**: `var(--space-4)` not `16px`, `var(--color-text)` not `#333`
14
+ - **Logical properties only**: `margin-block-start` not `margin-top`, `inline-size` not `width`
15
+ - **Data attributes for variants**: `data-variant="danger"` not `.btn--danger`
16
+ - **Component-scoped private properties**: prefix with underscore `--_card-padding`
17
+ - **All components** wrapped in `@layer components { }`
18
+
19
+ ## Token Quick Reference
20
+
21
+ | Category | Examples |
22
+ |----------|---------|
23
+ | Spacing | `--space-0` through `--space-24` |
24
+ | Typography | `--font-size-xs` through `--font-size-5xl` |
25
+ | Colors | `--color-text`, `--color-surface`, `--accent`, `--color-success/warning/error/info` |
26
+ | Borders | `--radius-sm` through `--radius-full`, `--border-width` |
27
+ | Shadows | `--shadow-xs` through `--shadow-xl` |
28
+ | Motion | `--duration-instant` through `--duration-slowest` |
29
+
30
+ ## Component File Pattern
31
+
32
+ Every component CSS file should include:
33
+ 1. Header comment with usage examples
34
+ 2. Base styles
35
+ 3. Variants via `data-*` attributes
36
+ 4. Responsive behavior (container queries preferred over media queries)
37
+ 5. Reduced motion: `@media (prefers-reduced-motion: reduce) { }`
38
+
39
+ ## File Locations
40
+
41
+ - Components: `src/components/[name].css`
42
+ - Sections: `src/sections/[name].css`
43
+ - After creating a new component, add `@import` to `src/fluid.css`
44
+
45
+ ## Never Do
46
+
47
+ - `padding: 16px` — use a token
48
+ - `margin-top: 1rem` — use logical property (`margin-block-start`)
49
+ - `.card--elevated` — use `data-variant="elevated"`
50
+ - `:focus { outline: none }` — never remove focus outlines
51
+ - Vendor prefixes — modern browsers only
@@ -0,0 +1,115 @@
1
+ # Image Optimization — Fluid Theme
2
+
3
+ This skill covers responsive images, performance optimization, and image utilities available in The Fluid Theme.
4
+
5
+ For the full guide with code examples, see `_docs/images-guide.md`.
6
+
7
+ ## Quick Reference
8
+
9
+ ### Format Priority
10
+
11
+ AVIF > WebP > JPEG > PNG. Use `<picture>` for format fallbacks:
12
+
13
+ ```html
14
+ <div class="hero__background" data-focus="top">
15
+ <picture>
16
+ <source srcset="hero.avif" type="image/avif">
17
+ <source srcset="hero.webp" type="image/webp">
18
+ <img src="hero.jpg" alt="Description" width="1920" height="1080">
19
+ </picture>
20
+ </div>
21
+ ```
22
+
23
+ ### Lazy Loading
24
+
25
+ Add `loading="lazy"` to ALL images except the first/hero image on each page:
26
+
27
+ ```html
28
+ <!-- First image: above the fold, load eagerly -->
29
+ <img src="hero.jpg" alt="Hero" width="1920" height="1080">
30
+
31
+ <!-- All other images: lazy load -->
32
+ <img src="card.jpg" alt="Card" loading="lazy" width="1920" height="1080">
33
+ ```
34
+
35
+ ### Always Include Dimensions
36
+
37
+ Every `<img>` must have `width` and `height` attributes to prevent layout shift (CLS):
38
+
39
+ | Aspect Ratio | Dimensions |
40
+ |-------------|------------|
41
+ | 16:9 (video) | `width="1920" height="1080"` |
42
+ | 1:1 (square) | `width="800" height="800"` |
43
+ | 3:4 (portrait) | `width="800" height="1067"` |
44
+ | 21:9 (wide) | `width="1920" height="823"` |
45
+ | Avatar | `width="96" height="96"` |
46
+
47
+ ### Focal Point Control
48
+
49
+ Use `data-focus` on `.hero__background` or directly on `.card__media`:
50
+
51
+ ```html
52
+ <!-- Preset positions -->
53
+ <div class="hero__background" data-focus="top">
54
+ <img class="card__media" data-focus="left">
55
+
56
+ <!-- Custom position -->
57
+ <div class="hero__background" style="--focus-x: 30%; --focus-y: 20%;">
58
+ ```
59
+
60
+ Available presets: `top`, `bottom`, `left`, `right`, `center`
61
+
62
+ ### Aspect Ratios
63
+
64
+ Use `data-aspect` on `.card__media`:
65
+
66
+ ```html
67
+ <img class="card__media" data-aspect="video"> <!-- 16:9 -->
68
+ <img class="card__media" data-aspect="square"> <!-- 1:1 -->
69
+ <img class="card__media" data-aspect="portrait"> <!-- 3:4 -->
70
+ <img class="card__media" data-aspect="wide"> <!-- 21:9 -->
71
+ ```
72
+
73
+ ### Image Protection
74
+
75
+ Add `data-protected` to discourage casual right-click saving (not DRM):
76
+
77
+ ```html
78
+ <div data-protected>
79
+ <img src="portfolio.jpg" alt="My work">
80
+ </div>
81
+ ```
82
+
83
+ Limitations: Does NOT prevent screenshots, dev tools, or determined users.
84
+
85
+ ### Orientation-Aware Images
86
+
87
+ Show different images based on viewport orientation:
88
+
89
+ ```html
90
+ <img data-orientation="landscape" src="wide-shot.jpg" alt="Scene">
91
+ <img data-orientation="portrait" src="tall-crop.jpg" alt="Scene">
92
+ ```
93
+
94
+ ### Responsive Images Pattern
95
+
96
+ ```html
97
+ <img
98
+ srcset="photo-640.webp 640w, photo-1024.webp 1024w, photo-1920.webp 1920w"
99
+ sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
100
+ src="photo-1024.webp"
101
+ alt="Description"
102
+ loading="lazy"
103
+ width="1920" height="1080"
104
+ >
105
+ ```
106
+
107
+ ## Checklist for New Pages
108
+
109
+ - [ ] First/hero image: no `loading="lazy"`, consider `fetchpriority="high"`
110
+ - [ ] All other images: `loading="lazy"`
111
+ - [ ] All images: `width` and `height` attributes set
112
+ - [ ] All images: meaningful `alt` text
113
+ - [ ] Hero backgrounds: consider `data-focus` for focal point control
114
+ - [ ] Card media: `data-aspect` set appropriately
115
+ - [ ] Images served in WebP or AVIF with fallbacks
@@ -0,0 +1,43 @@
1
+ # UX Behavior Rules — Quick Reference
2
+
3
+ Full specification: `_docs/UX-BEHAVIOR-SPEC.md` (18 sections, 1031 lines).
4
+
5
+ Read the full spec before building interactive components. This is the condensed version.
6
+
7
+ ## The 12 Rules
8
+
9
+ 1. **Destructive Actions**: Confirmation modal required. Title names the specific item. Body explains consequences. Buttons: [Cancel] [Delete Specific Thing]. Add `autofocus` to Cancel. Use `[data-destructive]` on modal.
10
+
11
+ 2. **Action Consistency**: Search entire project for similar patterns before implementing. Same display mode, same order, same styling everywhere.
12
+
13
+ 3. **Modal Strategy**: Toast > inline alert > modal. Use minimum interruption. No nested modals. Use native `<dialog>` with `.showModal()`. Focus trapping is automatic — do NOT add manual focus trap JS.
14
+
15
+ 4. **Feedback**: Every action gets feedback. Save -> toast. Delete -> toast with undo. Error -> inline alert. Silence after action = bug.
16
+
17
+ 5. **Forms**: Validate on blur first, then keystroke after error shown. Specific error messages ("Email must include @" not "Invalid input"). Never reload page on error.
18
+
19
+ 6. **Empty States**: Every list/table needs an empty state with explanation + CTA. Never just "No items found."
20
+
21
+ 7. **Buttons**: Specific labels only. Never "Submit" / "OK" / "Yes" / "Click Here". One primary CTA per viewport. Destructive = `data-variant="danger"`, always last.
22
+
23
+ 8. **Loading**: Feedback after 300ms. Skeleton for page loads, spinner for actions. Never blank screen.
24
+
25
+ 9. **Responsive**: Modal -> bottom sheet on mobile. Table -> stacked rows. 44px minimum touch targets.
26
+
27
+ 10. **Keyboard**: Every mouse interaction works via keyboard. Never remove focus outlines. Escape closes overlays.
28
+
29
+ 11. **Tables**: Row actions consistent across all tables. `.table-wrap` wrapper required. `data-responsive` + `td[data-label]` for mobile.
30
+
31
+ 12. **Consistency**: After ANY pattern implementation, search the entire project for other instances and update them to match.
32
+
33
+ ## When to Read the Full Spec
34
+
35
+ | Task | Read Sections |
36
+ |------|--------------|
37
+ | Building modal/dialog | Sections 1, 3, 12 |
38
+ | Building form | Sections 5, 9, 12 |
39
+ | Adding action buttons | Sections 1, 2, 9 |
40
+ | Building table with actions | Sections 2, 13, 11 |
41
+ | Search or filters | Sections 14, 7 |
42
+ | Starting new project | Section 17 first |
43
+ | Full UX audit | Entire spec |
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@fluid-theme/core",
3
+ "version": "0.4.3",
4
+ "description": "A modern, semantic CSS design system built on physics principles — not utility classes",
5
+ "main": "dist/fluid.min.css",
6
+ "style": "dist/fluid.min.css",
7
+ "exports": {
8
+ ".": "./dist/fluid.min.css",
9
+ "./src": "./src/fluid.css"
10
+ },
11
+ "files": [
12
+ "dist/",
13
+ "src/",
14
+ "fluid-agent-kit/",
15
+ "CLAUDE.md",
16
+ "AGENTS.md",
17
+ "GEMINI.md",
18
+ "LICENSE",
19
+ "README.md",
20
+ "CHANGELOG.md"
21
+ ],
22
+ "scripts": {
23
+ "serve": "npx http-server . -o /index.html",
24
+ "build": "node scripts/build-css.mjs",
25
+ "prepublishOnly": "npm run build",
26
+ "optimize-images": "node scripts/optimize-images.js",
27
+ "test": "echo \"Visual testing only - open examples/foundation.html in browser\""
28
+ },
29
+ "keywords": [
30
+ "css",
31
+ "design-system",
32
+ "theme",
33
+ "framework",
34
+ "semantic",
35
+ "css-layers",
36
+ "custom-properties",
37
+ "logical-properties",
38
+ "oklch",
39
+ "no-build"
40
+ ],
41
+ "author": "Neil White <dev+packages@agilemedia.agency>",
42
+ "license": "MIT",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/Agile-Media-Agency/the-fluid-theme.git"
46
+ },
47
+ "bugs": {
48
+ "url": "https://github.com/Agile-Media-Agency/the-fluid-theme/issues"
49
+ },
50
+ "homepage": "https://thefluidtheme.com",
51
+ "engines": {
52
+ "browsers": [
53
+ "last 2 Chrome versions",
54
+ "last 2 Firefox versions",
55
+ "last 2 Safari versions",
56
+ "last 2 Edge versions"
57
+ ]
58
+ },
59
+ "browserslist": [
60
+ "supports css-cascade-layers",
61
+ "supports css-container-queries",
62
+ "supports css-logical-properties"
63
+ ],
64
+ "devDependencies": {
65
+ "lightningcss": "^1.32.0",
66
+ "sharp": "^0.34.5"
67
+ }
68
+ }
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Scroll Reveal Animations
3
+ *
4
+ * Add [data-reveal] attribute to elements you want to animate in on scroll.
5
+ * Works with the scroll-reveal.js script or CSS-only with animation-timeline.
6
+ *
7
+ * Variants:
8
+ * data-reveal="fade" - Fade in
9
+ * data-reveal="fade-up" - Fade in from below
10
+ * data-reveal="fade-down" - Fade in from above
11
+ * data-reveal="fade-left" - Fade in from right
12
+ * data-reveal="fade-right"- Fade in from left
13
+ * data-reveal="scale" - Scale up
14
+ * data-reveal="blur" - Blur to focus
15
+ *
16
+ * Timing:
17
+ * data-reveal-delay="100" - Delay in ms (100, 200, 300, 400, 500)
18
+ * data-reveal-duration="slow" - Duration (fast, normal, slow)
19
+ */
20
+
21
+ @layer components {
22
+ /* Base reveal state - hidden by default */
23
+ [data-reveal] {
24
+ opacity: 0;
25
+ transition:
26
+ opacity var(--reveal-duration, var(--duration-slow)) var(--reveal-easing, cubic-bezier(0.16, 1, 0.3, 1)),
27
+ transform var(--reveal-duration, var(--duration-slow)) var(--reveal-easing, cubic-bezier(0.16, 1, 0.3, 1)),
28
+ filter var(--reveal-duration, var(--duration-slow)) var(--reveal-easing, cubic-bezier(0.16, 1, 0.3, 1));
29
+ will-change: opacity, transform;
30
+ }
31
+
32
+ /* Revealed state */
33
+ [data-reveal].revealed,
34
+ [data-reveal][data-revealed] {
35
+ opacity: 1;
36
+ transform: none;
37
+ filter: none;
38
+ }
39
+
40
+ /* Fade variants - starting positions */
41
+ [data-reveal="fade-up"] {
42
+ transform: translateY(2rem);
43
+ }
44
+
45
+ [data-reveal="fade-down"] {
46
+ transform: translateY(-2rem);
47
+ }
48
+
49
+ [data-reveal="fade-left"] {
50
+ transform: translateX(2rem);
51
+ }
52
+
53
+ [data-reveal="fade-right"] {
54
+ transform: translateX(-2rem);
55
+ }
56
+
57
+ [data-reveal="scale"] {
58
+ transform: scale(0.95);
59
+ }
60
+
61
+ [data-reveal="blur"] {
62
+ filter: blur(8px);
63
+ }
64
+
65
+ /* Delays */
66
+ [data-reveal-delay="100"] { transition-delay: 100ms; }
67
+ [data-reveal-delay="200"] { transition-delay: 200ms; }
68
+ [data-reveal-delay="300"] { transition-delay: 300ms; }
69
+ [data-reveal-delay="400"] { transition-delay: 400ms; }
70
+ [data-reveal-delay="500"] { transition-delay: 500ms; }
71
+
72
+ /* Durations */
73
+ [data-reveal-duration="fast"] { --reveal-duration: var(--duration-fast); }
74
+ [data-reveal-duration="normal"] { --reveal-duration: var(--duration-slow); }
75
+ [data-reveal-duration="slow"] { --reveal-duration: var(--duration-slower); }
76
+
77
+ /* Stagger children - useful for grids and lists */
78
+ [data-reveal-stagger] > [data-reveal]:nth-child(1) { transition-delay: calc(var(--reveal-stagger, 100ms) * 0); }
79
+ [data-reveal-stagger] > [data-reveal]:nth-child(2) { transition-delay: calc(var(--reveal-stagger, 100ms) * 1); }
80
+ [data-reveal-stagger] > [data-reveal]:nth-child(3) { transition-delay: calc(var(--reveal-stagger, 100ms) * 2); }
81
+ [data-reveal-stagger] > [data-reveal]:nth-child(4) { transition-delay: calc(var(--reveal-stagger, 100ms) * 3); }
82
+ [data-reveal-stagger] > [data-reveal]:nth-child(5) { transition-delay: calc(var(--reveal-stagger, 100ms) * 4); }
83
+ [data-reveal-stagger] > [data-reveal]:nth-child(6) { transition-delay: calc(var(--reveal-stagger, 100ms) * 5); }
84
+ [data-reveal-stagger] > [data-reveal]:nth-child(7) { transition-delay: calc(var(--reveal-stagger, 100ms) * 6); }
85
+ [data-reveal-stagger] > [data-reveal]:nth-child(8) { transition-delay: calc(var(--reveal-stagger, 100ms) * 7); }
86
+
87
+ /* ========================================
88
+ AUTO-REVEAL BY MOTION STYLE
89
+ Sections auto-animate based on theme motion setting
90
+ ======================================== */
91
+
92
+ /* Still: No animations */
93
+ [data-motion="still"] [data-reveal] {
94
+ opacity: 1;
95
+ transform: none;
96
+ filter: none;
97
+ transition: none;
98
+ }
99
+
100
+ /* Serene: Very slow, gentle fade */
101
+ [data-motion="serene"] [data-reveal] {
102
+ --reveal-duration: var(--duration-slower);
103
+ --reveal-easing: cubic-bezier(0.25, 0.1, 0.25, 1);
104
+ }
105
+
106
+ [data-motion="serene"] .section:not([data-reveal]) {
107
+ opacity: 0;
108
+ transform: scale(1.02);
109
+ transition:
110
+ opacity var(--duration-slowest) cubic-bezier(0.25, 0.1, 0.25, 1),
111
+ transform var(--duration-slowest) cubic-bezier(0.25, 0.1, 0.25, 1);
112
+ }
113
+
114
+ [data-motion="serene"] .section:not([data-reveal]).revealed {
115
+ opacity: 1;
116
+ transform: none;
117
+ }
118
+
119
+ /* Stream: Gentle horizontal flow */
120
+ [data-motion="stream"] [data-reveal] {
121
+ --reveal-duration: var(--duration-slow);
122
+ --reveal-easing: cubic-bezier(0.22, 1, 0.36, 1);
123
+ }
124
+
125
+ [data-motion="stream"] .section:not([data-reveal]) {
126
+ opacity: 0;
127
+ transform: translateX(-1rem);
128
+ transition:
129
+ opacity var(--duration-slow) cubic-bezier(0.22, 1, 0.36, 1),
130
+ transform var(--duration-slow) cubic-bezier(0.22, 1, 0.36, 1);
131
+ }
132
+
133
+ [data-motion="stream"] .section:not([data-reveal]).revealed {
134
+ opacity: 1;
135
+ transform: none;
136
+ }
137
+
138
+ /* Flowing: Smooth ease-in-out, organic */
139
+ [data-motion="flowing"] [data-reveal] {
140
+ --reveal-duration: var(--duration-slow);
141
+ --reveal-easing: cubic-bezier(0.16, 1, 0.3, 1);
142
+ }
143
+
144
+ [data-motion="flowing"] .section:not([data-reveal]) {
145
+ opacity: 0;
146
+ transform: translateY(1.5rem);
147
+ transition:
148
+ opacity var(--duration-slow) cubic-bezier(0.16, 1, 0.3, 1),
149
+ transform var(--duration-slow) cubic-bezier(0.16, 1, 0.3, 1);
150
+ }
151
+
152
+ [data-motion="flowing"] .section:not([data-reveal]).revealed {
153
+ opacity: 1;
154
+ transform: none;
155
+ }
156
+
157
+ /* Cascade: Medium energy, waterfall effect */
158
+ [data-motion="cascade"] [data-reveal] {
159
+ --reveal-duration: var(--duration-normal);
160
+ --reveal-easing: cubic-bezier(0.34, 1.56, 0.64, 1);
161
+ }
162
+
163
+ [data-motion="cascade"] .section:not([data-reveal]) {
164
+ opacity: 0;
165
+ transform: translateY(2rem);
166
+ transition:
167
+ opacity var(--duration-normal) cubic-bezier(0.34, 1.56, 0.64, 1),
168
+ transform var(--duration-normal) cubic-bezier(0.34, 1.56, 0.64, 1);
169
+ }
170
+
171
+ [data-motion="cascade"] .section:not([data-reveal]).revealed {
172
+ opacity: 1;
173
+ transform: none;
174
+ }
175
+
176
+ /* Rapids: Fast, direct */
177
+ [data-motion="rapids"] [data-reveal] {
178
+ --reveal-duration: var(--duration-fast);
179
+ --reveal-easing: cubic-bezier(0.25, 0.46, 0.45, 0.94);
180
+ }
181
+
182
+ [data-motion="rapids"] .section:not([data-reveal]) {
183
+ opacity: 0;
184
+ transform: scale(0.98);
185
+ transition:
186
+ opacity var(--duration-fast) cubic-bezier(0.25, 0.46, 0.45, 0.94),
187
+ transform var(--duration-fast) cubic-bezier(0.25, 0.46, 0.45, 0.94);
188
+ }
189
+
190
+ [data-motion="rapids"] .section:not([data-reveal]).revealed {
191
+ opacity: 1;
192
+ transform: none;
193
+ }
194
+
195
+ /* Tsunami: Dramatic, bold sweep */
196
+ [data-motion="tsunami"] [data-reveal] {
197
+ --reveal-duration: var(--duration-normal);
198
+ --reveal-easing: cubic-bezier(0.68, -0.2, 0.32, 1.2);
199
+ }
200
+
201
+ [data-motion="tsunami"] .section:not([data-reveal]) {
202
+ opacity: 0;
203
+ transform: translateY(3rem) scale(0.97);
204
+ transition:
205
+ opacity var(--duration-normal) cubic-bezier(0.68, -0.2, 0.32, 1.2),
206
+ transform var(--duration-normal) cubic-bezier(0.68, -0.2, 0.32, 1.2);
207
+ }
208
+
209
+ [data-motion="tsunami"] .section:not([data-reveal]).revealed {
210
+ opacity: 1;
211
+ transform: none;
212
+ }
213
+
214
+ /* Reduced motion preference */
215
+ @media (prefers-reduced-motion: reduce) {
216
+ [data-reveal],
217
+ .section {
218
+ opacity: 1 !important;
219
+ transform: none !important;
220
+ filter: none !important;
221
+ transition: none !important;
222
+ }
223
+ }
224
+ }