prompt_engine 1.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 (97) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +67 -0
  4. data/Rakefile +22 -0
  5. data/app/assets/stylesheets/prompt_engine/application.css +22 -0
  6. data/app/assets/stylesheets/prompt_engine/buttons.css +124 -0
  7. data/app/assets/stylesheets/prompt_engine/cards.css +63 -0
  8. data/app/assets/stylesheets/prompt_engine/comparison.css +244 -0
  9. data/app/assets/stylesheets/prompt_engine/components/_test_runs.css +144 -0
  10. data/app/assets/stylesheets/prompt_engine/dashboard.css +343 -0
  11. data/app/assets/stylesheets/prompt_engine/evaluations.css +124 -0
  12. data/app/assets/stylesheets/prompt_engine/forms.css +198 -0
  13. data/app/assets/stylesheets/prompt_engine/foundation.css +182 -0
  14. data/app/assets/stylesheets/prompt_engine/layout.css +75 -0
  15. data/app/assets/stylesheets/prompt_engine/loading.css +229 -0
  16. data/app/assets/stylesheets/prompt_engine/notifications.css +78 -0
  17. data/app/assets/stylesheets/prompt_engine/overrides.css +42 -0
  18. data/app/assets/stylesheets/prompt_engine/prompts.css +237 -0
  19. data/app/assets/stylesheets/prompt_engine/sidebar.css +90 -0
  20. data/app/assets/stylesheets/prompt_engine/tables.css +250 -0
  21. data/app/assets/stylesheets/prompt_engine/utilities.css +52 -0
  22. data/app/assets/stylesheets/prompt_engine/versions.css +370 -0
  23. data/app/clients/prompt_engine/open_ai_evals_client.rb +135 -0
  24. data/app/controllers/prompt_engine/admin/base_controller.rb +7 -0
  25. data/app/controllers/prompt_engine/application_controller.rb +4 -0
  26. data/app/controllers/prompt_engine/dashboard_controller.rb +24 -0
  27. data/app/controllers/prompt_engine/eval_runs_controller.rb +23 -0
  28. data/app/controllers/prompt_engine/eval_sets_controller.rb +200 -0
  29. data/app/controllers/prompt_engine/evaluations_controller.rb +32 -0
  30. data/app/controllers/prompt_engine/playground_controller.rb +57 -0
  31. data/app/controllers/prompt_engine/playground_run_results_controller.rb +41 -0
  32. data/app/controllers/prompt_engine/prompts_controller.rb +70 -0
  33. data/app/controllers/prompt_engine/settings_controller.rb +28 -0
  34. data/app/controllers/prompt_engine/test_cases_controller.rb +231 -0
  35. data/app/controllers/prompt_engine/versions_controller.rb +90 -0
  36. data/app/helpers/prompt_engine/application_helper.rb +4 -0
  37. data/app/jobs/prompt_engine/application_job.rb +4 -0
  38. data/app/mailers/prompt_engine/application_mailer.rb +6 -0
  39. data/app/models/prompt_engine/application_record.rb +5 -0
  40. data/app/models/prompt_engine/eval_result.rb +19 -0
  41. data/app/models/prompt_engine/eval_run.rb +40 -0
  42. data/app/models/prompt_engine/eval_set.rb +97 -0
  43. data/app/models/prompt_engine/parameter.rb +126 -0
  44. data/app/models/prompt_engine/parameter_parser.rb +39 -0
  45. data/app/models/prompt_engine/playground_run_result.rb +20 -0
  46. data/app/models/prompt_engine/prompt.rb +192 -0
  47. data/app/models/prompt_engine/prompt_version.rb +72 -0
  48. data/app/models/prompt_engine/setting.rb +45 -0
  49. data/app/models/prompt_engine/test_case.rb +29 -0
  50. data/app/services/prompt_engine/evaluation_runner.rb +258 -0
  51. data/app/services/prompt_engine/playground_executor.rb +124 -0
  52. data/app/services/prompt_engine/variable_detector.rb +97 -0
  53. data/app/views/layouts/prompt_engine/admin.html.erb +65 -0
  54. data/app/views/layouts/prompt_engine/application.html.erb +17 -0
  55. data/app/views/prompt_engine/dashboard/index.html.erb +230 -0
  56. data/app/views/prompt_engine/eval_runs/show.html.erb +204 -0
  57. data/app/views/prompt_engine/eval_sets/compare.html.erb +229 -0
  58. data/app/views/prompt_engine/eval_sets/edit.html.erb +111 -0
  59. data/app/views/prompt_engine/eval_sets/index.html.erb +63 -0
  60. data/app/views/prompt_engine/eval_sets/metrics.html.erb +371 -0
  61. data/app/views/prompt_engine/eval_sets/new.html.erb +113 -0
  62. data/app/views/prompt_engine/eval_sets/show.html.erb +235 -0
  63. data/app/views/prompt_engine/evaluations/index.html.erb +194 -0
  64. data/app/views/prompt_engine/playground/result.html.erb +58 -0
  65. data/app/views/prompt_engine/playground/show.html.erb +129 -0
  66. data/app/views/prompt_engine/playground_run_results/index.html.erb +99 -0
  67. data/app/views/prompt_engine/playground_run_results/show.html.erb +123 -0
  68. data/app/views/prompt_engine/prompts/_form.html.erb +224 -0
  69. data/app/views/prompt_engine/prompts/edit.html.erb +9 -0
  70. data/app/views/prompt_engine/prompts/index.html.erb +80 -0
  71. data/app/views/prompt_engine/prompts/new.html.erb +9 -0
  72. data/app/views/prompt_engine/prompts/show.html.erb +297 -0
  73. data/app/views/prompt_engine/settings/edit.html.erb +93 -0
  74. data/app/views/prompt_engine/shared/_form_errors.html.erb +16 -0
  75. data/app/views/prompt_engine/test_cases/edit.html.erb +72 -0
  76. data/app/views/prompt_engine/test_cases/import.html.erb +92 -0
  77. data/app/views/prompt_engine/test_cases/import_preview.html.erb +103 -0
  78. data/app/views/prompt_engine/test_cases/new.html.erb +79 -0
  79. data/app/views/prompt_engine/versions/_version_card.html.erb +56 -0
  80. data/app/views/prompt_engine/versions/compare.html.erb +82 -0
  81. data/app/views/prompt_engine/versions/index.html.erb +96 -0
  82. data/app/views/prompt_engine/versions/show.html.erb +98 -0
  83. data/config/routes.rb +61 -0
  84. data/db/migrate/20250124000001_create_eval_tables.rb +43 -0
  85. data/db/migrate/20250124000002_add_open_ai_fields_to_evals.rb +11 -0
  86. data/db/migrate/20250125000001_add_grader_fields_to_eval_sets.rb +8 -0
  87. data/db/migrate/20250723161909_create_prompts.rb +17 -0
  88. data/db/migrate/20250723184757_create_prompt_engine_versions.rb +24 -0
  89. data/db/migrate/20250723203838_create_prompt_engine_parameters.rb +20 -0
  90. data/db/migrate/20250724160623_create_prompt_engine_playground_run_results.rb +30 -0
  91. data/db/migrate/20250724165118_create_prompt_engine_settings.rb +14 -0
  92. data/lib/prompt_engine/engine.rb +25 -0
  93. data/lib/prompt_engine/version.rb +3 -0
  94. data/lib/prompt_engine.rb +33 -0
  95. data/lib/tasks/active_prompt_tasks.rake +32 -0
  96. data/lib/tasks/eval_demo.rake +149 -0
  97. metadata +293 -0
@@ -0,0 +1,343 @@
1
+ /* Dashboard Layout */
2
+ .dashboard {
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: var(--spacing-xl);
6
+ }
7
+
8
+ .dashboard__stats {
9
+ display: grid;
10
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
11
+ gap: var(--spacing-lg);
12
+ }
13
+
14
+ .dashboard__content {
15
+ display: grid;
16
+ grid-template-columns: 1fr 1fr;
17
+ gap: var(--spacing-xl);
18
+ }
19
+
20
+ @media (max-width: 1024px) {
21
+ .dashboard__content {
22
+ grid-template-columns: 1fr;
23
+ }
24
+ }
25
+
26
+ .dashboard__section {
27
+ min-width: 0;
28
+ }
29
+
30
+ /* Stat Cards */
31
+ .stat-card {
32
+ background: var(--color-white);
33
+ border: 1px solid var(--color-gray-200);
34
+ border-radius: var(--radius-lg);
35
+ padding: var(--spacing-lg);
36
+ display: flex;
37
+ align-items: center;
38
+ gap: var(--spacing-md);
39
+ transition: var(--transition-all);
40
+ }
41
+
42
+ .stat-card:hover {
43
+ border-color: var(--color-gray-300);
44
+ box-shadow: var(--shadow-sm);
45
+ }
46
+
47
+ .stat-card__icon {
48
+ width: 48px;
49
+ height: 48px;
50
+ border-radius: var(--radius-md);
51
+ display: flex;
52
+ align-items: center;
53
+ justify-content: center;
54
+ flex-shrink: 0;
55
+ }
56
+
57
+ .stat-card__icon--primary {
58
+ background: var(--color-primary-50);
59
+ color: var(--color-primary-600);
60
+ }
61
+
62
+ .stat-card__icon--success {
63
+ background: var(--color-success-50);
64
+ color: var(--color-success-600);
65
+ }
66
+
67
+ .stat-card__icon--info {
68
+ background: var(--color-info-50);
69
+ color: var(--color-info-600);
70
+ }
71
+
72
+ .stat-card__icon--warning {
73
+ background: var(--color-warning-50);
74
+ color: var(--color-warning-600);
75
+ }
76
+
77
+ .stat-card__content {
78
+ flex: 1;
79
+ min-width: 0;
80
+ }
81
+
82
+ .stat-card__title {
83
+ font-size: var(--text-sm);
84
+ font-weight: 500;
85
+ color: var(--color-gray-600);
86
+ margin: 0 0 var(--spacing-xs) 0;
87
+ }
88
+
89
+ .stat-card__value {
90
+ font-size: var(--text-2xl);
91
+ font-weight: 600;
92
+ color: var(--color-gray-900);
93
+ margin: 0;
94
+ line-height: 1.2;
95
+ }
96
+
97
+ /* Test Runs List */
98
+ .test-runs-list {
99
+ display: flex;
100
+ flex-direction: column;
101
+ gap: var(--spacing-sm);
102
+ }
103
+
104
+ .test-run-item {
105
+ padding: var(--spacing-md);
106
+ border: 1px solid var(--color-gray-200);
107
+ border-radius: var(--radius-md);
108
+ transition: var(--transition-all);
109
+ }
110
+
111
+ .test-run-item:hover {
112
+ border-color: var(--color-gray-300);
113
+ background: var(--color-gray-50);
114
+ }
115
+
116
+ .test-run-item__main {
117
+ display: flex;
118
+ flex-direction: column;
119
+ gap: var(--spacing-sm);
120
+ }
121
+
122
+ .test-run-item__header {
123
+ display: flex;
124
+ justify-content: space-between;
125
+ align-items: start;
126
+ gap: var(--spacing-md);
127
+ }
128
+
129
+ .test-run-item__title {
130
+ font-size: var(--text-base);
131
+ font-weight: 500;
132
+ margin: 0;
133
+ }
134
+
135
+ .test-run-item__date {
136
+ font-size: var(--text-sm);
137
+ white-space: nowrap;
138
+ }
139
+
140
+ .test-run-item__content {
141
+ display: flex;
142
+ flex-direction: column;
143
+ gap: var(--spacing-xs);
144
+ }
145
+
146
+ .test-run-item__model {
147
+ font-size: var(--text-sm);
148
+ margin: 0;
149
+ }
150
+
151
+ .test-run-item__error {
152
+ font-size: var(--text-sm);
153
+ color: var(--color-danger-600);
154
+ margin: 0;
155
+ display: flex;
156
+ align-items: center;
157
+ gap: var(--spacing-xs);
158
+ }
159
+
160
+ /* Empty State */
161
+ .empty-state {
162
+ text-align: center;
163
+ padding: var(--spacing-2xl) var(--spacing-lg);
164
+ }
165
+
166
+ .empty-state p {
167
+ margin-bottom: var(--spacing-md);
168
+ }
169
+
170
+ /* Dashboard Table Enhancements */
171
+ .dashboard__section .table-container {
172
+ border: none;
173
+ box-shadow: none;
174
+ background: transparent;
175
+ }
176
+
177
+ .dashboard__section .table {
178
+ background: transparent;
179
+ }
180
+
181
+ .dashboard__section .table__row {
182
+ border-bottom: 1px solid var(--color-gray-100);
183
+ }
184
+
185
+ .dashboard__section .table__row:hover {
186
+ background-color: var(--color-gray-50);
187
+ }
188
+
189
+ .dashboard__section .table__cell {
190
+ padding: var(--spacing-md) var(--spacing-sm);
191
+ }
192
+
193
+ .dashboard__section .table__cell:first-child {
194
+ padding-left: 0;
195
+ }
196
+
197
+ .dashboard__section .table__cell:last-child {
198
+ padding-right: 0;
199
+ }
200
+
201
+ .dashboard__section .table__cell--header {
202
+ padding-top: 0;
203
+ padding-bottom: var(--spacing-sm);
204
+ font-size: var(--text-xs);
205
+ text-transform: uppercase;
206
+ letter-spacing: 0.05em;
207
+ color: var(--color-gray-500);
208
+ }
209
+
210
+ .dashboard__section .table__action {
211
+ font-weight: 500;
212
+ color: var(--color-gray-900);
213
+ }
214
+
215
+ .dashboard__section .table__action:hover {
216
+ color: var(--color-primary-600);
217
+ }
218
+
219
+ /* Metrics Dashboard */
220
+ .metrics-summary {
221
+ display: grid;
222
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
223
+ gap: var(--spacing-lg);
224
+ margin-bottom: var(--spacing-xl);
225
+ }
226
+
227
+ .metrics-card {
228
+ background: var(--color-white);
229
+ border: 1px solid var(--color-gray-200);
230
+ border-radius: var(--radius-lg);
231
+ padding: var(--spacing-lg);
232
+ display: flex;
233
+ align-items: center;
234
+ gap: var(--spacing-lg);
235
+ transition: var(--transition-all);
236
+ }
237
+
238
+ .metrics-card:hover {
239
+ border-color: var(--color-gray-300);
240
+ box-shadow: var(--shadow-sm);
241
+ transform: translateY(-2px);
242
+ }
243
+
244
+ .metrics-card__icon {
245
+ width: 56px;
246
+ height: 56px;
247
+ border-radius: var(--radius-full);
248
+ background: var(--color-primary-50);
249
+ color: var(--color-primary-600);
250
+ display: flex;
251
+ align-items: center;
252
+ justify-content: center;
253
+ flex-shrink: 0;
254
+ }
255
+
256
+ .metrics-card__icon svg {
257
+ width: 28px;
258
+ height: 28px;
259
+ }
260
+
261
+ .metrics-card__content {
262
+ flex: 1;
263
+ min-width: 0;
264
+ }
265
+
266
+ .metrics-card__label {
267
+ font-size: var(--font-size-sm);
268
+ font-weight: 500;
269
+ color: var(--color-gray-600);
270
+ margin: 0 0 var(--spacing-xs) 0;
271
+ }
272
+
273
+ .metrics-card__value {
274
+ font-size: var(--font-size-2xl);
275
+ font-weight: 700;
276
+ color: var(--color-gray-900);
277
+ margin: 0;
278
+ line-height: 1.2;
279
+ }
280
+
281
+ /* Chart Grid */
282
+ .chart-grid {
283
+ display: grid;
284
+ grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
285
+ gap: var(--spacing-lg);
286
+ margin-bottom: var(--spacing-lg);
287
+ }
288
+
289
+ @media (max-width: 900px) {
290
+ .chart-grid {
291
+ grid-template-columns: 1fr;
292
+ }
293
+ }
294
+
295
+ /* Chart Container */
296
+ .chart-container {
297
+ position: relative;
298
+ height: 300px;
299
+ width: 100%;
300
+ padding: var(--spacing-sm);
301
+ }
302
+
303
+ .chart-container--small {
304
+ height: 250px;
305
+ }
306
+
307
+ .chart-container canvas {
308
+ max-width: 100%;
309
+ }
310
+
311
+ /* Metrics-specific text colors */
312
+ .text-warning {
313
+ color: var(--color-warning);
314
+ }
315
+
316
+ /* Responsive adjustments */
317
+ @media (max-width: 768px) {
318
+ .metrics-summary {
319
+ grid-template-columns: 1fr;
320
+ }
321
+
322
+ .metrics-card {
323
+ padding: var(--spacing-md);
324
+ }
325
+
326
+ .metrics-card__icon {
327
+ width: 48px;
328
+ height: 48px;
329
+ }
330
+
331
+ .metrics-card__icon svg {
332
+ width: 24px;
333
+ height: 24px;
334
+ }
335
+
336
+ .metrics-card__value {
337
+ font-size: var(--font-size-xl);
338
+ }
339
+
340
+ .chart-container {
341
+ height: 250px;
342
+ }
343
+ }
@@ -0,0 +1,124 @@
1
+ /* Evaluations Index Page */
2
+ .eval-prompts-list {
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: var(--spacing-xl);
6
+ }
7
+
8
+ .eval-prompt-item {
9
+ border-bottom: 1px solid var(--color-gray-200);
10
+ padding-bottom: var(--spacing-xl);
11
+ }
12
+
13
+ .eval-prompt-item:last-child {
14
+ border-bottom: none;
15
+ padding-bottom: 0;
16
+ }
17
+
18
+ .eval-prompt-header {
19
+ display: flex;
20
+ align-items: center;
21
+ gap: var(--spacing-md);
22
+ margin-bottom: var(--spacing-lg);
23
+ }
24
+
25
+ .eval-prompt-title {
26
+ font-size: var(--font-size-lg);
27
+ font-weight: 600;
28
+ margin: 0;
29
+ }
30
+
31
+ .eval-sets-grid {
32
+ display: grid;
33
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
34
+ gap: var(--spacing-md);
35
+ }
36
+
37
+ .eval-set-card {
38
+ background: var(--color-gray-50);
39
+ border: 1px solid var(--color-gray-200);
40
+ border-radius: var(--radius-md);
41
+ padding: var(--spacing-md);
42
+ display: flex;
43
+ flex-direction: column;
44
+ gap: var(--spacing-sm);
45
+ }
46
+
47
+ .eval-set-card__header {
48
+ display: flex;
49
+ justify-content: space-between;
50
+ align-items: start;
51
+ gap: var(--spacing-md);
52
+ }
53
+
54
+ .eval-set-card__title {
55
+ font-size: var(--font-size-base);
56
+ font-weight: 600;
57
+ margin: 0;
58
+ }
59
+
60
+ .eval-set-card__stats {
61
+ display: flex;
62
+ gap: var(--spacing-sm);
63
+ font-size: var(--font-size-sm);
64
+ color: var(--color-gray-600);
65
+ }
66
+
67
+ .eval-set-card__stat {
68
+ white-space: nowrap;
69
+ }
70
+
71
+ .eval-set-card__description {
72
+ font-size: var(--font-size-sm);
73
+ color: var(--color-gray-600);
74
+ margin: 0;
75
+ }
76
+
77
+ .eval-set-card__actions {
78
+ display: flex;
79
+ gap: var(--spacing-sm);
80
+ margin-top: auto;
81
+ }
82
+
83
+ /* Eval Set Summary on Prompt Show Page */
84
+ .eval-sets-summary {
85
+ display: flex;
86
+ flex-direction: column;
87
+ gap: var(--spacing-md);
88
+ }
89
+
90
+ .eval-set-summary-item {
91
+ background: var(--color-gray-50);
92
+ border: 1px solid var(--color-gray-200);
93
+ border-radius: var(--radius-md);
94
+ padding: var(--spacing-md);
95
+ }
96
+
97
+ .eval-set-summary__header {
98
+ display: flex;
99
+ justify-content: space-between;
100
+ align-items: center;
101
+ margin-bottom: var(--spacing-sm);
102
+ }
103
+
104
+ .eval-set-summary__title {
105
+ font-size: var(--font-size-base);
106
+ font-weight: 600;
107
+ margin: 0;
108
+ }
109
+
110
+ .eval-set-summary__stats {
111
+ display: flex;
112
+ gap: var(--spacing-sm);
113
+ }
114
+
115
+ .eval-set-summary__description {
116
+ font-size: var(--font-size-sm);
117
+ color: var(--color-gray-600);
118
+ margin: 0 0 var(--spacing-sm) 0;
119
+ }
120
+
121
+ .eval-set-summary__result {
122
+ font-size: var(--font-size-sm);
123
+ color: var(--color-gray-700);
124
+ }
@@ -0,0 +1,198 @@
1
+ /* Form Components */
2
+ .form {
3
+ max-width: 100%;
4
+ }
5
+
6
+ /* Form Group */
7
+ .form__group {
8
+ margin-bottom: var(--spacing-lg);
9
+ }
10
+
11
+ .form__group:last-child {
12
+ margin-bottom: 0;
13
+ }
14
+
15
+ /* Form Label */
16
+ .form__label {
17
+ display: block;
18
+ font-size: var(--font-size-sm);
19
+ font-weight: 500;
20
+ color: var(--color-gray-700);
21
+ margin-bottom: var(--spacing-xs);
22
+ }
23
+
24
+ .form__label--required::after {
25
+ content: " *";
26
+ color: var(--color-danger);
27
+ }
28
+
29
+ .form__required {
30
+ color: var(--color-danger);
31
+ margin-left: var(--spacing-xs);
32
+ }
33
+
34
+ /* Form Input Base */
35
+ .form__input,
36
+ .form__textarea,
37
+ .form__select {
38
+ display: block;
39
+ width: 100%;
40
+ padding: var(--spacing-sm) var(--spacing-md);
41
+ font-family: inherit;
42
+ font-size: var(--font-size-sm);
43
+ line-height: var(--line-height-normal);
44
+ color: var(--color-gray-900);
45
+ background-color: white;
46
+ border: 1px solid var(--color-gray-300);
47
+ border-radius: var(--radius-md);
48
+ transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
49
+ -webkit-appearance: none;
50
+ -moz-appearance: none;
51
+ appearance: none;
52
+ }
53
+
54
+ .form__input:focus,
55
+ .form__textarea:focus,
56
+ .form__select:focus {
57
+ outline: none;
58
+ border-color: var(--color-primary-500);
59
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
60
+ }
61
+
62
+ .form__input::placeholder,
63
+ .form__textarea::placeholder {
64
+ color: var(--color-gray-400);
65
+ }
66
+
67
+ /* Textarea Specific */
68
+ .form__textarea {
69
+ min-height: 120px;
70
+ resize: vertical;
71
+ }
72
+
73
+ /* Select Specific */
74
+ .form__select {
75
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
76
+ background-position: right var(--spacing-sm) center;
77
+ background-repeat: no-repeat;
78
+ background-size: 20px;
79
+ padding-right: var(--spacing-xl);
80
+ }
81
+
82
+ /* Form Help Text */
83
+ .form__help {
84
+ font-size: var(--font-size-sm);
85
+ color: var(--color-gray-600);
86
+ margin-top: var(--spacing-xs);
87
+ }
88
+
89
+ /* Form Error States */
90
+ .form__group--error .form__input,
91
+ .form__group--error .form__textarea,
92
+ .form__group--error .form__select {
93
+ border-color: var(--color-danger);
94
+ }
95
+
96
+ .form__group--error .form__input:focus,
97
+ .form__group--error .form__textarea:focus,
98
+ .form__group--error .form__select:focus {
99
+ border-color: var(--color-danger);
100
+ box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
101
+ }
102
+
103
+ /* HTML5 Validation States */
104
+ .form__input:invalid,
105
+ .form__textarea:invalid,
106
+ .form__select:invalid {
107
+ border-color: var(--color-danger);
108
+ }
109
+
110
+ .form__input:invalid:focus,
111
+ .form__textarea:invalid:focus,
112
+ .form__select:invalid:focus {
113
+ border-color: var(--color-danger);
114
+ box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
115
+ }
116
+
117
+ .form__input:valid:focus,
118
+ .form__textarea:valid:focus,
119
+ .form__select:valid:focus {
120
+ border-color: var(--color-success);
121
+ box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.1);
122
+ }
123
+
124
+ /* Disable validation styles until user interacts */
125
+ .form__input:not(:focus):invalid,
126
+ .form__textarea:not(:focus):invalid,
127
+ .form__select:not(:focus):invalid {
128
+ border-color: var(--color-gray-300);
129
+ }
130
+
131
+ /* Form Actions */
132
+ .form__actions {
133
+ display: flex;
134
+ gap: var(--spacing-md);
135
+ margin-top: var(--spacing-xl);
136
+ padding-top: var(--spacing-lg);
137
+ border-top: 1px solid var(--color-gray-200);
138
+ }
139
+
140
+ .form__actions--right {
141
+ justify-content: flex-end;
142
+ }
143
+
144
+ /* Inline Form Elements */
145
+ .form__inline {
146
+ display: flex;
147
+ gap: var(--spacing-md);
148
+ align-items: flex-start;
149
+ }
150
+
151
+ .form__inline .form__group {
152
+ flex: 1;
153
+ margin-bottom: 0;
154
+ }
155
+
156
+
157
+ /* Disabled State */
158
+ .form__input:disabled,
159
+ .form__textarea:disabled,
160
+ .form__select:disabled {
161
+ background-color: var(--color-gray-100);
162
+ color: var(--color-gray-500);
163
+ cursor: not-allowed;
164
+ }
165
+
166
+ /* Small Variants */
167
+ .form__input--small,
168
+ .form__select--small {
169
+ padding: var(--spacing-xs) var(--spacing-sm);
170
+ font-size: var(--font-size-xs);
171
+ }
172
+
173
+ /* Form Errors Display */
174
+ .form__errors {
175
+ background-color: #fef2f2;
176
+ border: 1px solid #fecaca;
177
+ border-radius: var(--radius-md);
178
+ padding: var(--spacing-md);
179
+ margin-bottom: var(--spacing-lg);
180
+ }
181
+
182
+ .form__errors h3 {
183
+ font-size: var(--font-size-base);
184
+ margin-bottom: var(--spacing-sm);
185
+ }
186
+
187
+ .form__errors ul {
188
+ margin: 0;
189
+ padding-left: var(--spacing-lg);
190
+ }
191
+
192
+ /* Form Section Title */
193
+ .form__section-title {
194
+ font-size: var(--font-size-lg);
195
+ font-weight: 600;
196
+ margin-bottom: var(--spacing-sm);
197
+ color: var(--color-gray-900);
198
+ }