ariadna 1.3.1 → 2.0.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 (148) hide show
  1. checksums.yaml +4 -4
  2. data/ariadna.gemspec +0 -1
  3. data/data/agents/ariadna-codebase-mapper.md +34 -722
  4. data/data/agents/ariadna-debugger.md +44 -1139
  5. data/data/agents/ariadna-executor.md +75 -396
  6. data/data/agents/ariadna-planner.md +78 -1215
  7. data/data/agents/ariadna-roadmapper.md +55 -582
  8. data/data/agents/ariadna-verifier.md +60 -702
  9. data/data/ariadna/templates/config.json +8 -33
  10. data/data/ariadna/workflows/debug.md +28 -0
  11. data/data/ariadna/workflows/execute-phase.md +31 -513
  12. data/data/ariadna/workflows/map-codebase.md +20 -319
  13. data/data/ariadna/workflows/new-milestone.md +20 -365
  14. data/data/ariadna/workflows/new-project.md +19 -880
  15. data/data/ariadna/workflows/plan-phase.md +24 -443
  16. data/data/ariadna/workflows/progress.md +20 -376
  17. data/data/ariadna/workflows/quick.md +19 -221
  18. data/data/ariadna/workflows/roadmap-ops.md +28 -0
  19. data/data/ariadna/workflows/verify-work.md +23 -560
  20. data/data/commands/ariadna/add-phase.md +11 -22
  21. data/data/commands/ariadna/debug.md +11 -143
  22. data/data/commands/ariadna/execute-phase.md +12 -30
  23. data/data/commands/ariadna/insert-phase.md +7 -14
  24. data/data/commands/ariadna/map-codebase.md +16 -49
  25. data/data/commands/ariadna/new-milestone.md +12 -25
  26. data/data/commands/ariadna/new-project.md +22 -26
  27. data/data/commands/ariadna/plan-phase.md +13 -22
  28. data/data/commands/ariadna/progress.md +16 -6
  29. data/data/commands/ariadna/quick.md +9 -11
  30. data/data/commands/ariadna/remove-phase.md +9 -12
  31. data/data/commands/ariadna/verify-work.md +14 -19
  32. data/data/skills/rails-backend/API.md +138 -0
  33. data/data/skills/rails-backend/CONTROLLERS.md +154 -0
  34. data/data/skills/rails-backend/JOBS.md +132 -0
  35. data/data/skills/rails-backend/MODELS.md +213 -0
  36. data/data/skills/rails-backend/SKILL.md +169 -0
  37. data/data/skills/rails-frontend/ASSETS.md +154 -0
  38. data/data/skills/rails-frontend/COMPONENTS.md +253 -0
  39. data/data/skills/rails-frontend/SKILL.md +187 -0
  40. data/data/skills/rails-frontend/VIEWS.md +168 -0
  41. data/data/skills/rails-performance/PROFILING.md +106 -0
  42. data/data/skills/rails-performance/SKILL.md +217 -0
  43. data/data/skills/rails-security/AUDIT.md +118 -0
  44. data/data/skills/rails-security/SKILL.md +422 -0
  45. data/data/skills/rails-testing/FIXTURES.md +78 -0
  46. data/data/skills/rails-testing/SKILL.md +160 -0
  47. data/data/skills/rails-testing/SYSTEM-TESTS.md +73 -0
  48. data/lib/ariadna/installer.rb +11 -15
  49. data/lib/ariadna/tools/cli.rb +0 -12
  50. data/lib/ariadna/tools/config_manager.rb +10 -72
  51. data/lib/ariadna/tools/frontmatter.rb +23 -1
  52. data/lib/ariadna/tools/init.rb +201 -401
  53. data/lib/ariadna/tools/model_profiles.rb +6 -14
  54. data/lib/ariadna/tools/phase_manager.rb +1 -10
  55. data/lib/ariadna/tools/state_manager.rb +170 -451
  56. data/lib/ariadna/tools/template_filler.rb +4 -12
  57. data/lib/ariadna/tools/verification.rb +21 -399
  58. data/lib/ariadna/uninstaller.rb +9 -0
  59. data/lib/ariadna/version.rb +1 -1
  60. metadata +20 -91
  61. data/data/agents/ariadna-backend-executor.md +0 -261
  62. data/data/agents/ariadna-frontend-executor.md +0 -259
  63. data/data/agents/ariadna-integration-checker.md +0 -418
  64. data/data/agents/ariadna-phase-researcher.md +0 -469
  65. data/data/agents/ariadna-plan-checker.md +0 -622
  66. data/data/agents/ariadna-project-researcher.md +0 -618
  67. data/data/agents/ariadna-research-synthesizer.md +0 -236
  68. data/data/agents/ariadna-test-executor.md +0 -266
  69. data/data/ariadna/references/checkpoints.md +0 -772
  70. data/data/ariadna/references/continuation-format.md +0 -249
  71. data/data/ariadna/references/decimal-phase-calculation.md +0 -65
  72. data/data/ariadna/references/git-integration.md +0 -248
  73. data/data/ariadna/references/git-planning-commit.md +0 -38
  74. data/data/ariadna/references/model-profile-resolution.md +0 -32
  75. data/data/ariadna/references/model-profiles.md +0 -73
  76. data/data/ariadna/references/phase-argument-parsing.md +0 -61
  77. data/data/ariadna/references/planning-config.md +0 -194
  78. data/data/ariadna/references/questioning.md +0 -153
  79. data/data/ariadna/references/rails-conventions.md +0 -416
  80. data/data/ariadna/references/tdd.md +0 -267
  81. data/data/ariadna/references/ui-brand.md +0 -160
  82. data/data/ariadna/references/verification-patterns.md +0 -853
  83. data/data/ariadna/templates/codebase/architecture.md +0 -481
  84. data/data/ariadna/templates/codebase/concerns.md +0 -380
  85. data/data/ariadna/templates/codebase/conventions.md +0 -434
  86. data/data/ariadna/templates/codebase/integrations.md +0 -328
  87. data/data/ariadna/templates/codebase/stack.md +0 -189
  88. data/data/ariadna/templates/codebase/structure.md +0 -418
  89. data/data/ariadna/templates/codebase/testing.md +0 -606
  90. data/data/ariadna/templates/context.md +0 -283
  91. data/data/ariadna/templates/continue-here.md +0 -78
  92. data/data/ariadna/templates/debug-subagent-prompt.md +0 -91
  93. data/data/ariadna/templates/phase-prompt.md +0 -609
  94. data/data/ariadna/templates/planner-subagent-prompt.md +0 -117
  95. data/data/ariadna/templates/research-project/ARCHITECTURE.md +0 -439
  96. data/data/ariadna/templates/research-project/FEATURES.md +0 -168
  97. data/data/ariadna/templates/research-project/PITFALLS.md +0 -406
  98. data/data/ariadna/templates/research-project/STACK.md +0 -251
  99. data/data/ariadna/templates/research-project/SUMMARY.md +0 -247
  100. data/data/ariadna/templates/state.md +0 -176
  101. data/data/ariadna/templates/summary-complex.md +0 -59
  102. data/data/ariadna/templates/summary-minimal.md +0 -41
  103. data/data/ariadna/templates/summary-standard.md +0 -48
  104. data/data/ariadna/templates/user-setup.md +0 -310
  105. data/data/ariadna/workflows/add-phase.md +0 -111
  106. data/data/ariadna/workflows/add-todo.md +0 -157
  107. data/data/ariadna/workflows/audit-milestone.md +0 -241
  108. data/data/ariadna/workflows/check-todos.md +0 -176
  109. data/data/ariadna/workflows/complete-milestone.md +0 -644
  110. data/data/ariadna/workflows/diagnose-issues.md +0 -219
  111. data/data/ariadna/workflows/discovery-phase.md +0 -289
  112. data/data/ariadna/workflows/discuss-phase.md +0 -408
  113. data/data/ariadna/workflows/execute-plan.md +0 -448
  114. data/data/ariadna/workflows/help.md +0 -470
  115. data/data/ariadna/workflows/insert-phase.md +0 -129
  116. data/data/ariadna/workflows/list-phase-assumptions.md +0 -178
  117. data/data/ariadna/workflows/pause-work.md +0 -122
  118. data/data/ariadna/workflows/plan-milestone-gaps.md +0 -256
  119. data/data/ariadna/workflows/remove-phase.md +0 -154
  120. data/data/ariadna/workflows/research-phase.md +0 -74
  121. data/data/ariadna/workflows/resume-project.md +0 -306
  122. data/data/ariadna/workflows/set-profile.md +0 -80
  123. data/data/ariadna/workflows/settings.md +0 -145
  124. data/data/ariadna/workflows/transition.md +0 -493
  125. data/data/ariadna/workflows/update.md +0 -212
  126. data/data/ariadna/workflows/verify-phase.md +0 -226
  127. data/data/commands/ariadna/add-todo.md +0 -42
  128. data/data/commands/ariadna/audit-milestone.md +0 -42
  129. data/data/commands/ariadna/check-todos.md +0 -41
  130. data/data/commands/ariadna/complete-milestone.md +0 -136
  131. data/data/commands/ariadna/discuss-phase.md +0 -86
  132. data/data/commands/ariadna/help.md +0 -22
  133. data/data/commands/ariadna/list-phase-assumptions.md +0 -50
  134. data/data/commands/ariadna/pause-work.md +0 -35
  135. data/data/commands/ariadna/plan-milestone-gaps.md +0 -40
  136. data/data/commands/ariadna/reapply-patches.md +0 -110
  137. data/data/commands/ariadna/research-phase.md +0 -187
  138. data/data/commands/ariadna/resume-work.md +0 -40
  139. data/data/commands/ariadna/set-profile.md +0 -34
  140. data/data/commands/ariadna/settings.md +0 -36
  141. data/data/commands/ariadna/update.md +0 -37
  142. data/data/guides/backend.md +0 -3069
  143. data/data/guides/frontend.md +0 -1479
  144. data/data/guides/performance.md +0 -1193
  145. data/data/guides/security.md +0 -1522
  146. data/data/guides/style-guide.md +0 -1091
  147. data/data/guides/testing.md +0 -504
  148. data/data/templates.md +0 -94
@@ -1,1091 +0,0 @@
1
- # Style Guide
2
-
3
- This document defines the CSS architecture, design tokens, and styling conventions.
4
-
5
- ## Overview
6
-
7
- Use **pure, custom CSS** with no external frameworks (no Tailwind, Bootstrap, etc.). The design system is built on modern CSS features including CSS custom properties, OKLCH colors, CSS layers, and logical properties.
8
-
9
- ## CSS Architecture
10
-
11
- ### Layer Organization
12
-
13
- Styles are organized using CSS `@layer` rules for predictable specificity:
14
-
15
- ```css
16
- @layer reset; /* Browser normalization */
17
- @layer base; /* Base element styles */
18
- @layer components; /* Component-specific styles */
19
- @layer modules; /* Feature modules */
20
- @layer utilities; /* Utility classes */
21
- @layer native; /* Native app overrides */
22
- @layer platform; /* Platform-specific styles */
23
- ```
24
-
25
- ### File Organization
26
-
27
- ```
28
- app/assets/stylesheets/
29
- ├── _global.css # Root variables and @layer definitions
30
- ├── base.css # Base element styling
31
- ├── utilities.css # Utility classes
32
- ├── buttons.css # Button components
33
- ├── inputs.css # Form inputs
34
- ├── cards.css # Card components
35
- ├── layout.css # Main layout
36
- ├── header.css # Header component
37
- ├── dialog.css # Dialogs/modals
38
- ├── animation.css # Keyframes
39
- └── [feature].css # Feature-specific styles
40
- ```
41
-
42
- ---
43
-
44
- ## Color System
45
-
46
- ### OKLCH Color Model
47
-
48
- Use the OKLCH color space for better perceptual uniformity across light and dark modes. Colors are defined as lightness, chroma, and hue values:
49
-
50
- ```css
51
- --lch-black: 0% 0 0
52
- --lch-white: 100% 0 0
53
- --color-ink: oklch(var(--lch-black))
54
- ```
55
-
56
- ### Semantic Colors
57
-
58
- Use semantic color variables rather than raw values:
59
-
60
- | Variable | Purpose |
61
- |----------|---------|
62
- | `--color-ink` | Primary text color |
63
- | `--color-ink-light` | Secondary text |
64
- | `--color-ink-lighter` | Tertiary/muted text |
65
- | `--color-canvas` | Background color |
66
- | `--color-link` | Interactive elements (blue) |
67
- | `--color-positive` | Success states (green) |
68
- | `--color-negative` | Error states (red) |
69
- | `--color-highlight` | Emphasis/markers (yellow) |
70
-
71
- ### Color Palette
72
-
73
- The palette includes 10 color families, each with 7 intensity levels:
74
-
75
- - **Ink** (neutrals): `--lch-ink-1` through `--lch-ink-8`
76
- - **Red, Yellow, Lime, Green, Aqua, Blue, Violet, Purple, Pink**: `--lch-[color]-1` through `--lch-[color]-7`
77
-
78
- ### Card Colors
79
-
80
- Cards use 8 distinct colors for visual categorization:
81
-
82
- ```css
83
- --color-card-1 through --color-card-8
84
- --color-card-default
85
- --color-card-complete
86
- ```
87
-
88
- Card backgrounds use color mixing for subtlety:
89
-
90
- ```css
91
- background: color-mix(in srgb, var(--card-color) 4%, var(--color-canvas));
92
- ```
93
-
94
- ---
95
-
96
- ## Typography
97
-
98
- ### Font Stack
99
-
100
- ```css
101
- --font-sans: "Adwaita Sans", -apple-system, BlinkMacSystemFont, "Segoe UI",
102
- "Noto Sans", Helvetica, Arial, sans-serif,
103
- "Apple Color Emoji", "Segoe UI Emoji";
104
- --font-serif: ui-serif, serif;
105
- --font-mono: ui-monospace, monospace;
106
- ```
107
-
108
- ### Text Size Scale
109
-
110
- | Variable | Desktop | Mobile |
111
- |----------|---------|--------|
112
- | `--text-xx-small` | 0.55rem | 0.65rem |
113
- | `--text-x-small` | 0.75rem | 0.85rem |
114
- | `--text-small` | 0.85rem | 0.95rem |
115
- | `--text-normal` | 1rem | 1.1rem |
116
- | `--text-medium` | 1.1rem | 1.2rem |
117
- | `--text-large` | 1.5rem | 1.5rem |
118
- | `--text-x-large` | 1.8rem | 1.8rem |
119
- | `--text-xx-large` | 2.5rem | 2.5rem |
120
-
121
- ### Typography Utilities
122
-
123
- ```css
124
- .txt-xx-small, .txt-x-small, .txt-small, .txt-normal, .txt-medium, .txt-large
125
- .txt-ink, .txt-subtle, .txt-negative, .txt-positive, .txt-alert
126
- .txt-tight-lines /* Reduced line-height */
127
- .font-weight-black /* 900 */
128
- .font-weight-normal /* 400 */
129
- ```
130
-
131
- ### Font Rendering
132
-
133
- Global settings for consistent rendering:
134
-
135
- ```css
136
- -webkit-font-smoothing: antialiased;
137
- -moz-osx-font-smoothing: grayscale;
138
- text-rendering: optimizeLegibility;
139
- line-height: 1.375;
140
- ```
141
-
142
- ---
143
-
144
- ## Spacing System
145
-
146
- ### Logical Properties
147
-
148
- Use CSS logical properties for RTL support. Use `block` (vertical) and `inline` (horizontal) instead of top/bottom/left/right.
149
-
150
- ### Base Spacing Variables
151
-
152
- ```css
153
- --inline-space: 1ch; /* Character width */
154
- --inline-space-half: 0.5ch;
155
- --inline-space-double: 2ch;
156
-
157
- --block-space: 1rem; /* Vertical rhythm */
158
- --block-space-half: 0.5rem;
159
- --block-space-double: 2rem;
160
- ```
161
-
162
- ### Padding Utilities
163
-
164
- ```css
165
- .pad /* Full padding */
166
- .pad-double /* 2x padding */
167
- .pad-block /* Vertical only */
168
- .pad-block-start /* Top only */
169
- .pad-block-end /* Bottom only */
170
- .pad-block-half /* Half vertical */
171
- .pad-inline /* Horizontal only */
172
- .pad-inline-start /* Left only (LTR) */
173
- .pad-inline-end /* Right only (LTR) */
174
- .pad-inline-half /* Half horizontal */
175
- .unpad, .unpad-block-end, .unpad-inline
176
- ```
177
-
178
- ### Margin Utilities
179
-
180
- ```css
181
- .margin, .margin-block, .margin-inline
182
- .margin-block-start, .margin-block-end
183
- .margin-block-half, .margin-block-double
184
- .margin-inline-start, .margin-inline-end
185
- .center /* margin-inline: auto */
186
- .margin-none, .margin-block-none, .margin-inline-none
187
- ```
188
-
189
- ### Layout Spacing
190
-
191
- ```css
192
- --main-padding: clamp(1ch, 3vw, 3ch);
193
- --main-width: 1400px;
194
- ```
195
-
196
- ---
197
-
198
- ## Components
199
-
200
- ### Buttons
201
-
202
- Buttons use CSS custom properties for variants:
203
-
204
- ```css
205
- .btn {
206
- --btn-background: var(--color-canvas);
207
- --btn-border-color: var(--color-ink-lighter);
208
- --btn-color: inherit;
209
- --btn-padding: 0.5em 1.25em;
210
- --btn-font-weight: 500;
211
- --btn-border-radius: 99rem;
212
- }
213
- ```
214
-
215
- **Variants:**
216
-
217
- | Class | Purpose |
218
- |-------|---------|
219
- | `.btn--link` | Text-only, no background |
220
- | `.btn--plain` | Minimal styling |
221
- | `.btn--circle` | Circular icon button |
222
- | `.btn--negative` | Destructive action (red) |
223
- | `.btn--positive` | Affirmative action (green) |
224
- | `.btn--reversed` | Inverted colors |
225
- | `.btn--circle-mobile` | Circle on small screens |
226
-
227
- **States:**
228
- - Disabled: `opacity: 0.3; pointer-events: none`
229
- - Loading: Animated spinner overlay on form submit
230
-
231
- ### Cards
232
-
233
- ```css
234
- .card {
235
- --card-color: var(--color-card-default);
236
- --card-bg-color: color-mix(in srgb, var(--card-color) 4%, var(--color-canvas));
237
- }
238
-
239
- .card__header
240
- .card__board
241
- .card__id
242
- ```
243
-
244
- ### Dialogs
245
-
246
- Dialogs use CSS transitions with `allow-discrete`:
247
-
248
- ```css
249
- .dialog {
250
- --dialog-duration: 150ms;
251
- /* Scale from 0.2 to 1 with opacity */
252
- }
253
- ```
254
-
255
- ---
256
-
257
- ## Icons & SVG
258
-
259
- Use a **CSS mask-based icon system** with individual SVG files. Icons inherit color from their parent via `currentColor`, making them easy to style contextually.
260
-
261
- ### Icon Storage
262
-
263
- All icons are stored as individual SVG files in `app/assets/images/`:
264
-
265
- ```
266
- app/assets/images/
267
- ├── add.svg
268
- ├── check.svg
269
- ├── bell.svg
270
- ├── bell-alert.svg
271
- ├── bell-off.svg
272
- ├── bookmark.svg
273
- ├── bookmark-outline.svg
274
- ├── boost-color.svg # Special colored variant
275
- └── ... (84 total icons)
276
- ```
277
-
278
- ### Naming Conventions
279
-
280
- | Pattern | Example | Purpose |
281
- |---------|---------|---------|
282
- | `name.svg` | `check.svg` | Standard icon |
283
- | `name-variant.svg` | `bell-alert.svg` | State variant |
284
- | `name-outline.svg` | `bookmark-outline.svg` | Outline style |
285
- | `name--meta.svg` | `add--meta.svg` | Metadata context |
286
- | `name-color.svg` | `boost-color.svg` | Multi-color icon |
287
-
288
- ### Icon Helper
289
-
290
- Use the `icon_tag` helper to render icons:
291
-
292
- ```erb
293
- <%# Basic usage %>
294
- <%= icon_tag "check" %>
295
-
296
- <%# With custom classes %>
297
- <%= icon_tag "pencil", class: "txt-subtle" %>
298
-
299
- <%# In a button with screen reader text %>
300
- <%= button_to path, class: "btn" do %>
301
- <%= icon_tag "trash" %>
302
- <span class="for-screen-reader">Delete item</span>
303
- <% end %>
304
- ```
305
-
306
- The helper generates:
307
-
308
- ```html
309
- <span class="icon icon--check" aria-hidden="true"></span>
310
- ```
311
-
312
- ### How It Works
313
-
314
- Icons use CSS `mask-image` with `currentColor` for flexible styling:
315
-
316
- ```css
317
- .icon {
318
- background-color: currentColor;
319
- block-size: var(--icon-size, 1em);
320
- inline-size: var(--icon-size, 1em);
321
- mask-image: var(--svg);
322
- mask-position: center;
323
- mask-repeat: no-repeat;
324
- mask-size: var(--icon-size, 1em);
325
- }
326
-
327
- .icon--check {
328
- --svg: url("check.svg");
329
- }
330
- ```
331
-
332
- ### Icon Sizing
333
-
334
- Control size via the `--icon-size` custom property:
335
-
336
- | Context | Size | Variable |
337
- |---------|------|----------|
338
- | Default | `1em` | `--icon-size: 1em` |
339
- | Buttons | `1.3em` | `--btn-icon-size: 1.3em` |
340
- | Header buttons | `1rem` | `--btn-icon-size: 1rem` |
341
- | Popups | `24px` | `--popup-icon-size: 24px` |
342
- | Navigation | `2em` | `--icon-size: 2em` |
343
- | Card metadata | `0.9em` | `--icon-size: 0.9em` |
344
- | Reactions | `1.3em` | `--btn-icon-size: 1.3em` |
345
-
346
- ### Icon Colors
347
-
348
- Icons inherit `currentColor` by default. Override contextually:
349
-
350
- ```css
351
- /* Inherits parent text color */
352
- .icon {
353
- background-color: currentColor;
354
- }
355
-
356
- /* Context-specific coloring */
357
- .event-icon .icon {
358
- background-color: var(--card-color);
359
- }
360
-
361
- /* Attachment type colors */
362
- .attachment--red { --attachment-icon-color: oklch(var(--lch-red-medium)); }
363
- .attachment--blue { --attachment-icon-color: oklch(var(--lch-blue-medium)); }
364
- ```
365
-
366
- ### Icon Buttons
367
-
368
- Icon-only buttons automatically adjust sizing:
369
-
370
- ```css
371
- /* Buttons with only an icon (detected via aria-label or .for-screen-reader) */
372
- .btn[aria-label]:where(:has(.icon)),
373
- .btn:where(:has(.for-screen-reader):has(.icon)) {
374
- --btn-padding: 0;
375
- --icon-size: 75%;
376
- aspect-ratio: 1;
377
- display: grid;
378
- place-items: center;
379
- }
380
- ```
381
-
382
- Usage pattern:
383
-
384
- ```erb
385
- <%# Icon button with accessible label %>
386
- <%= button_to path, class: "btn", aria: { label: "Edit" } do %>
387
- <%= icon_tag "pencil" %>
388
- <% end %>
389
-
390
- <%# Or with screen reader text %>
391
- <%= button_to path, class: "btn" do %>
392
- <%= icon_tag "pencil" %>
393
- <span class="for-screen-reader">Edit</span>
394
- <% end %>
395
- ```
396
-
397
- ### Icon Animations
398
-
399
- Icons support animations, particularly in success states:
400
-
401
- ```css
402
- @keyframes zoom-fade {
403
- 100% {
404
- transform: translateY(-1.5em);
405
- scale: 2;
406
- opacity: 0;
407
- }
408
- }
409
-
410
- .btn--success .icon {
411
- animation: zoom-fade 500ms cubic-bezier(0.25, 1.25, 0.5, 1);
412
- }
413
- ```
414
-
415
- ### Multi-Color Icons
416
-
417
- For icons requiring multiple colors (not mask-compatible), use `image_tag`:
418
-
419
- ```erb
420
- <%# Colored icon (not masked) %>
421
- <%= image_tag "boost-color.svg", aria: { hidden: true }, class: "icon" %>
422
- ```
423
-
424
- ### SVG File Requirements
425
-
426
- When creating new icons:
427
-
428
- 1. **ViewBox**: Use `viewBox="0 0 24 24"` or `viewBox="0 0 32 32"`
429
- 2. **No fixed dimensions**: Omit `width` and `height` attributes
430
- 3. **Fill**: Use `fill="currentColor"` or no fill (for mask compatibility)
431
- 4. **Single color**: Icons should be monochrome for mask-image to work
432
- 5. **Optimized**: Remove unnecessary metadata and groups
433
-
434
- Example SVG structure:
435
-
436
- ```xml
437
- <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
438
- <path d="M12 2L2 7l10 5 10-5-10-5z"/>
439
- </svg>
440
- ```
441
-
442
- ### Available Icons
443
-
444
- Common icons organized by category:
445
-
446
- **Navigation:**
447
- `arrow-left`, `arrow-right`, `arrow-up`, `chevron`, `caret-down`, `expand`, `collapse`
448
-
449
- **Actions:**
450
- `add`, `remove`, `close`, `check`, `pencil`, `copy-paste`, `trash`, `bookmark`, `pin`
451
-
452
- **Status:**
453
- `bell`, `bell-alert`, `bell-off`, `check-circle`, `close-circle`, `assigned`
454
-
455
- **UI Elements:**
456
- `menu-dots-horizontal`, `menu-dots-vertical`, `search`, `filter`, `sliders`
457
-
458
- **Entities:**
459
- `person`, `person-add`, `board`, `column-left`, `column-right`
460
-
461
- **Application:**
462
- `fizzy`, `boost`, `boost-color`, `golden-ticket`
463
-
464
- ---
465
-
466
- ## Forms & Inputs
467
-
468
- Use CSS custom properties for flexible, themeable form controls. All inputs prevent iOS auto-zoom with `font-size: max(16px, 1em)`.
469
-
470
- ### Base Input Styling
471
-
472
- ```css
473
- .input {
474
- --input-accent-color: var(--color-ink);
475
- --input-background: transparent;
476
- --input-border-radius: 0.5em;
477
- --input-border-color: var(--color-ink-medium);
478
- --input-border-size: 1px;
479
- --input-color: var(--color-ink);
480
- --input-padding: 0.5em 0.8em;
481
-
482
- font-size: max(16px, 1em); /* Prevents iOS zoom */
483
- inline-size: 100%;
484
- resize: none;
485
- }
486
- ```
487
-
488
- ### Input Variants
489
-
490
- #### Text Input
491
-
492
- Basic text input with full width:
493
-
494
- ```erb
495
- <%= form.text_field :name, class: "input", placeholder: "Enter name..." %>
496
- ```
497
-
498
- #### Actor Input Pattern
499
-
500
- Wraps inputs in a label that acts as the visual input container, providing larger touch targets:
501
-
502
- ```erb
503
- <label class="flex align-center gap input input--actor">
504
- <%= icon_tag "search" %>
505
- <%= form.text_field :query, class: "input full-width", placeholder: "Search..." %>
506
- </label>
507
- ```
508
-
509
- ```css
510
- .input--actor {
511
- &:focus-within {
512
- --input-border-color: var(--color-selected-dark);
513
- outline: var(--focus-ring-size) solid var(--focus-ring-color);
514
- }
515
-
516
- .input {
517
- --input-padding: 0;
518
- --input-border-size: 0;
519
- --input-background: transparent;
520
- }
521
- }
522
- ```
523
-
524
- #### Select Dropdown
525
-
526
- Custom styled select with SVG caret:
527
-
528
- ```erb
529
- <%= form.select :status, options, {}, class: "input input--select" %>
530
- ```
531
-
532
- ```css
533
- .input--select {
534
- --input-border-radius: 2em;
535
- --input-padding: 0.5em 1.8em 0.5em 1.2em;
536
- appearance: none;
537
- background-image: url("caret-down.svg");
538
- background-position: right 0.5em center;
539
- background-repeat: no-repeat;
540
- background-size: 1em;
541
- }
542
- ```
543
-
544
- #### Textarea
545
-
546
- Auto-resizing textarea using `field-sizing: content`:
547
-
548
- ```erb
549
- <%= form.text_area :description, class: "input input--textarea", rows: 1 %>
550
- ```
551
-
552
- ```css
553
- .input--textarea {
554
- min-block-size: calc(3lh + (2 * var(--input-padding)));
555
-
556
- @supports (field-sizing: content) {
557
- field-sizing: content;
558
- max-block-size: calc(3lh + (2 * var(--input-padding)));
559
- min-block-size: calc(1lh + (2 * var(--input-padding)));
560
- }
561
- }
562
- ```
563
-
564
- #### File Input
565
-
566
- Hidden file input with custom button overlay:
567
-
568
- ```erb
569
- <label class="btn input--file">
570
- <%= icon_tag "upload" %>
571
- <span>Choose file</span>
572
- <%= form.file_field :attachment, class: "input", accept: "image/*" %>
573
- </label>
574
- ```
575
-
576
- ```css
577
- .input--file {
578
- cursor: pointer;
579
- display: grid;
580
- place-items: center;
581
-
582
- input[type="file"] {
583
- cursor: pointer;
584
- font-size: 0;
585
- inset: 0;
586
- opacity: 0;
587
- position: absolute;
588
-
589
- &::file-selector-button {
590
- appearance: none;
591
- cursor: pointer;
592
- opacity: 0;
593
- }
594
- }
595
- }
596
- ```
597
-
598
- #### Number Input (Inline)
599
-
600
- Minimal inline number input that sizes to content:
601
-
602
- ```erb
603
- <%= form.number_field :quantity, class: "input boost__input", min: 1 %>
604
- ```
605
-
606
- ```css
607
- .input.boost__input {
608
- --input-border-size: 0;
609
- --input-padding: 0;
610
- inline-size: min-content;
611
-
612
- @supports (field-sizing: content) {
613
- field-sizing: content;
614
- }
615
-
616
- &:focus {
617
- background-color: var(--color-highlight);
618
- }
619
- }
620
- ```
621
-
622
- #### One-Time Code Input
623
-
624
- Styled for OTP/verification codes:
625
-
626
- ```erb
627
- <%= form.text_field :code, class: "input", autocomplete: "one-time-code" %>
628
- ```
629
-
630
- ```css
631
- .input[autocomplete='one-time-code'] {
632
- font-family: var(--font-mono);
633
- font-size: var(--text-large);
634
- font-weight: 900;
635
- inline-size: 18ch;
636
- letter-spacing: 1ch;
637
- text-align: center;
638
- }
639
- ```
640
-
641
- ### Switch/Toggle
642
-
643
- Binary toggle switch component:
644
-
645
- ```erb
646
- <label class="switch">
647
- <%= form.check_box :enabled, class: "switch__input" %>
648
- <span class="switch__btn"></span>
649
- <span class="for-screen-reader">Enable feature</span>
650
- </label>
651
- ```
652
-
653
- ```css
654
- .switch {
655
- --switch-color: var(--color-ink-medium);
656
- block-size: 1.75em;
657
- inline-size: 3em;
658
- border-radius: 2em;
659
- }
660
-
661
- .switch__btn {
662
- background-color: var(--switch-color);
663
- transition: 150ms ease;
664
-
665
- &::before { /* The toggle dot */
666
- background-color: var(--color-ink-inverted);
667
- block-size: 1.35em;
668
- border-radius: 50%;
669
- transition: 150ms ease;
670
- }
671
- }
672
-
673
- .switch__input:checked + .switch__btn {
674
- --switch-color: var(--color-link);
675
-
676
- &::before {
677
- transform: translateX(1.2em);
678
- }
679
- }
680
-
681
- .switch__input:disabled + .switch__btn {
682
- cursor: not-allowed;
683
- opacity: 0.5;
684
- }
685
- ```
686
-
687
- ### Checkbox & Radio as Buttons
688
-
689
- Style checkboxes/radios as toggle buttons:
690
-
691
- ```erb
692
- <label class="btn">
693
- <%= form.radio_button :color, "red" %>
694
- Red
695
- </label>
696
- ```
697
-
698
- ```css
699
- .btn:has(input[type=radio], input[type=checkbox]) {
700
- input {
701
- appearance: none;
702
- cursor: pointer;
703
- inset: 0;
704
- position: absolute;
705
- }
706
-
707
- &:has(input:checked) {
708
- --btn-background: var(--color-ink);
709
- --btn-color: var(--color-ink-inverted);
710
- }
711
- }
712
- ```
713
-
714
- ### Input States
715
-
716
- #### Focus State
717
-
718
- Inputs use an internal focus ring (offset inside the border):
719
-
720
- ```css
721
- .input {
722
- --focus-ring-offset: -1px;
723
-
724
- &:focus {
725
- outline: var(--focus-ring-size) solid var(--focus-ring-color);
726
- outline-offset: var(--focus-ring-offset);
727
- }
728
- }
729
- ```
730
-
731
- #### Disabled State
732
-
733
- ```css
734
- .input:disabled {
735
- cursor: not-allowed;
736
- opacity: 0.5;
737
- pointer-events: none;
738
- }
739
- ```
740
-
741
- #### Readonly State
742
-
743
- ```css
744
- .input[readonly] {
745
- --focus-ring-size: 0;
746
- }
747
- ```
748
-
749
- #### Autofill Styling
750
-
751
- Override browser autofill colors:
752
-
753
- ```css
754
- .input:autofill,
755
- .input:-webkit-autofill {
756
- -webkit-text-fill-color: var(--color-ink);
757
- -webkit-box-shadow: 0 0 0px 1000px var(--color-selected) inset;
758
- }
759
- ```
760
-
761
- ### Form Layout
762
-
763
- Use flexbox utilities for form structure:
764
-
765
- ```erb
766
- <%= form_with model: @user, class: "flex flex-column gap" do |form| %>
767
- <div class="flex flex-column gap-half">
768
- <label>Email</label>
769
- <%= form.email_field :email, class: "input" %>
770
- </div>
771
-
772
- <div class="flex flex-column gap-half">
773
- <label>Password</label>
774
- <%= form.password_field :password, class: "input" %>
775
- </div>
776
-
777
- <button type="submit" class="btn btn--positive">Save</button>
778
- <% end %>
779
- ```
780
-
781
- ### Error Display
782
-
783
- Display validation errors with semantic styling:
784
-
785
- ```erb
786
- <% if @user.errors.any? %>
787
- <div class="txt-negative txt-small margin-block-half">
788
- <p class="font-weight-bold margin-block-none">Your changes couldn't be saved:</p>
789
- <ul class="margin-block-none">
790
- <% @user.errors.full_messages.each do |message| %>
791
- <li><%= message %></li>
792
- <% end %>
793
- </ul>
794
- </div>
795
- <% end %>
796
- ```
797
-
798
- ### Form Submission
799
-
800
- #### Submit Button with Loading State
801
-
802
- Forms with `aria-busy` show a spinner on disabled submit buttons:
803
-
804
- ```css
805
- form[aria-busy] button:disabled {
806
- > * {
807
- visibility: hidden;
808
- }
809
-
810
- &::after {
811
- animation: submitting 1s infinite linear;
812
- /* Spinner overlay */
813
- }
814
- }
815
- ```
816
-
817
- #### Auto-Submit Forms
818
-
819
- Use the `auto_submit_form_with` helper for forms that submit on change:
820
-
821
- ```erb
822
- <%= auto_submit_form_with model: @settings do |form| %>
823
- <%= form.select :theme, options, {},
824
- class: "input input--select",
825
- data: { action: "change->form#submit" } %>
826
- <% end %>
827
- ```
828
-
829
- ### Form Custom Properties Reference
830
-
831
- | Property | Default | Purpose |
832
- |----------|---------|---------|
833
- | `--input-accent-color` | `var(--color-ink)` | Checkbox/radio accent |
834
- | `--input-background` | `transparent` | Input background |
835
- | `--input-border-radius` | `0.5em` | Corner radius |
836
- | `--input-border-color` | `var(--color-ink-medium)` | Border color |
837
- | `--input-border-size` | `1px` | Border width |
838
- | `--input-color` | `var(--color-ink)` | Text color |
839
- | `--input-padding` | `0.5em 0.8em` | Internal padding |
840
- | `--switch-color` | `var(--color-ink-medium)` | Switch track color |
841
-
842
- ---
843
-
844
- ## Layout
845
-
846
- ### Grid-Based Header
847
-
848
- ```css
849
- .header {
850
- display: grid;
851
- grid-template-columns: var(--actions-start-size) 1fr var(--actions-end-size);
852
- }
853
-
854
- .header--mobile-actions-stack /* Stacked on mobile */
855
- ```
856
-
857
- ### Main Content
858
-
859
- ```css
860
- body {
861
- display: grid;
862
- grid-template-rows: auto 1fr auto 9em;
863
- }
864
-
865
- #main {
866
- inline-size: 100dvw;
867
- max-inline-size: var(--main-width);
868
- }
869
- ```
870
-
871
- ### Flexbox Utilities
872
-
873
- ```css
874
- .flex, .flex-inline, .flex-column, .flex-wrap
875
- .flex-1, .flex-item-grow, .flex-item-shrink, .flex-item-no-shrink
876
- .gap, .gap-half, .gap-none
877
- .justify-start, .justify-end, .justify-center, .justify-space-between
878
- .align-start, .align-end, .align-center
879
- ```
880
-
881
- ### Sizing Utilities
882
-
883
- ```css
884
- .full-width, .max-width, .half-width
885
- .min-width, .min-content, .fit-content
886
- .overflow-x, .overflow-y
887
- .overflow-ellipsis
888
- .overflow-line-clamp /* Use with --lines custom property */
889
- ```
890
-
891
- ---
892
-
893
- ## Dark Mode
894
-
895
- Dark mode is supported via theme attribute and system preference:
896
-
897
- ```css
898
- /* User-selected theme */
899
- html[data-theme="dark"] { ... }
900
-
901
- /* System preference fallback */
902
- @media (prefers-color-scheme: dark) {
903
- html:not([data-theme]) { ... }
904
- }
905
- ```
906
-
907
- Dark mode adjustments include:
908
- - Complete OKLCH palette redefinition
909
- - Enhanced layered shadows (6 layers vs 4)
910
- - Adjusted contrast ratios
911
-
912
- ---
913
-
914
- ## Borders & Shadows
915
-
916
- ### Borders
917
-
918
- ```css
919
- --border-color: var(--color-ink-lighter);
920
-
921
- .border /* 1px solid */
922
- .border-block /* Top and bottom */
923
- .border-top, .border-bottom
924
- .borderless
925
- .border-radius /* Uses --border-radius, default 0.5em */
926
- ```
927
-
928
- ### Shadows
929
-
930
- ```css
931
- --shadow:
932
- 0 0 0 1px oklch(var(--lch-black) / 5%),
933
- 0 0.2em 0.2em oklch(var(--lch-black) / 5%),
934
- 0 0.4em 0.4em oklch(var(--lch-black) / 5%),
935
- 0 0.8em 0.8em oklch(var(--lch-black) / 5%);
936
-
937
- .shadow /* Applies the layered shadow */
938
- ```
939
-
940
- ---
941
-
942
- ## Animations
943
-
944
- ### Easing Functions
945
-
946
- ```css
947
- --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
948
- --ease-out-overshoot: cubic-bezier(0.25, 1.75, 0.5, 1);
949
- --ease-out-overshoot-subtle: cubic-bezier(0.25, 1.25, 0.5, 1);
950
- ```
951
-
952
- ### Default Transitions
953
-
954
- ```css
955
- transition: 100ms ease-out;
956
- transition-property: background-color, border-color, box-shadow, filter, outline;
957
- ```
958
-
959
- ### Available Keyframes
960
-
961
- | Animation | Purpose |
962
- |-----------|---------|
963
- | `shake` | Horizontal error shake |
964
- | `react` | Scale up reaction |
965
- | `scale-fade-out` | Shrink and fade |
966
- | `slide-up`, `slide-down` | Vertical movement |
967
- | `slide-up-fade-in` | Combined slide + fade |
968
- | `pulse` | Opacity pulse |
969
- | `submitting` | Spinner animation |
970
- | `success` | Scale on success |
971
- | `wiggle` | Rotation wiggle |
972
-
973
- ### Reduced Motion
974
-
975
- ```css
976
- @media (prefers-reduced-motion: reduce) {
977
- animation-duration: 0.01ms !important;
978
- transition-duration: 0.01ms !important;
979
- }
980
- ```
981
-
982
- ---
983
-
984
- ## Focus States
985
-
986
- ### Focus Ring
987
-
988
- ```css
989
- --focus-ring-color: var(--color-link);
990
- --focus-ring-offset: 1px;
991
- --focus-ring-size: 2px;
992
- --focus-ring: 2px solid var(--color-link);
993
- ```
994
-
995
- Applied via `:focus-visible` for keyboard-only styling on all interactive elements.
996
-
997
- ---
998
-
999
- ## Z-Index Stack
1000
-
1001
- | Variable | Value | Purpose |
1002
- |----------|-------|---------|
1003
- | `--z-events-column-header` | 1 | Column headers |
1004
- | `--z-events-day-header` | 3 | Day headers |
1005
- | `--z-popup` | 10 | Popups/dropdowns |
1006
- | `--z-nav` | 20 | Navigation |
1007
- | `--z-flash` | 30 | Flash messages |
1008
- | `--z-tooltip` | 40 | Tooltips |
1009
- | `--z-bar` | 50 | Action bars |
1010
- | `--z-tray` | 51 | Side trays |
1011
- | `--z-welcome` | 52 | Welcome overlay |
1012
- | `--z-nav-open` | 100 | Open navigation |
1013
-
1014
- ---
1015
-
1016
- ## Accessibility
1017
-
1018
- ### Visibility Utilities
1019
-
1020
- ```css
1021
- .visually-hidden, .for-screen-reader /* Screen reader only */
1022
- [hidden] /* Display none */
1023
- .display-contents /* Remove wrapper */
1024
- ```
1025
-
1026
- ### Responsive Visibility
1027
-
1028
- ```css
1029
- .hide-on-touch /* Hidden on touch devices */
1030
- .show-on-touch /* Visible only on touch */
1031
- .show-on-native /* Native app only */
1032
- .hide-in-pwa /* Hidden in PWA mode */
1033
- .hide-in-browser /* Hidden in browser */
1034
- ```
1035
-
1036
- ---
1037
-
1038
- ## Responsive Design
1039
-
1040
- ### Breakpoints
1041
-
1042
- - **Small**: < 640px (mobile)
1043
- - **Medium**: 640px - 800px (tablet)
1044
- - **Large**: > 800px (desktop)
1045
-
1046
- ### Approach
1047
-
1048
- Mobile-first with `max-width` queries for larger screens. Use `clamp()` for fluid sizing:
1049
-
1050
- ```css
1051
- --main-padding: clamp(1ch, 3vw, 3ch);
1052
- font-size: clamp(0.85rem, 2vw, 1rem);
1053
- ```
1054
-
1055
- ### Safe Area Insets
1056
-
1057
- Support for notched devices:
1058
-
1059
- ```css
1060
- padding-inline: calc(var(--main-padding) + env(safe-area-inset-left));
1061
- padding-block-start: calc(var(--block-space-half) + env(safe-area-inset-top));
1062
- ```
1063
-
1064
- ---
1065
-
1066
- ## Best Practices
1067
-
1068
- ### Do
1069
-
1070
- - Use semantic color variables (`--color-ink`, `--color-link`)
1071
- - Use logical properties (`block-start` instead of `top`)
1072
- - Use CSS custom properties for component variants
1073
- - Use `clamp()` for fluid responsive values
1074
- - Test in both light and dark modes
1075
- - Respect `prefers-reduced-motion`
1076
-
1077
- ### Don't
1078
-
1079
- - Hard-code color values (use variables)
1080
- - Use physical properties when logical work
1081
- - Create new z-index values without adding to the stack
1082
- - Skip focus states on interactive elements
1083
- - Use `px` for font sizes (use `rem`)
1084
-
1085
- ### Adding New Styles
1086
-
1087
- 1. Check if an existing utility class serves the purpose
1088
- 2. Use CSS custom properties for configurable values
1089
- 3. Place styles in the appropriate layer
1090
- 4. Ensure dark mode compatibility
1091
- 5. Test with reduced motion preferences