js-kompo-utils 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 (67) hide show
  1. package/README.md +1 -0
  2. package/app.js +22 -0
  3. package/components/VlApexChart.vue +89 -0
  4. package/components/VlCKEditorTemplate.vue +52 -0
  5. package/components/VlCKEditorWithVars.vue +80 -0
  6. package/components/VlCollapsible.vue +91 -0
  7. package/components/VlCopyBox.vue +61 -0
  8. package/components/VlCreditCardInput.vue +104 -0
  9. package/components/VlDateMonthDay.vue +327 -0
  10. package/components/VlDateTextInput.vue +151 -0
  11. package/components/VlInternationalPhoneInput.vue +902 -0
  12. package/components/VlPartialDate.vue +283 -0
  13. package/components/VlPasswordInput.vue +358 -0
  14. package/components/VlQrCodeScanner.vue +91 -0
  15. package/components/VlSwipeableTab.vue +35 -0
  16. package/components/VlSwipeableTabs.vue +325 -0
  17. package/components/VlText.vue +72 -0
  18. package/components/VlTutorialStepBuilder.vue +671 -0
  19. package/components/VlValidatedInput.vue +262 -0
  20. package/components/data/countries.js +253 -0
  21. package/components/getAllDefaultComponents.js +12 -0
  22. package/docs/architecture.md +71 -0
  23. package/docs/tutorials.md +247 -0
  24. package/docs/usage.md +128 -0
  25. package/functions/index.js +7 -0
  26. package/functions/inputs.js +64 -0
  27. package/functions/loading.js +20 -0
  28. package/kompo-ckeditor-1.0.17/index.js +5 -0
  29. package/kompo-ckeditor-1.0.17/komponents/CKEditor.vue +36 -0
  30. package/kompo-ckeditor-1.0.17/komponents/CKEditorExtended.vue +66 -0
  31. package/kompo-ckeditor-1.0.17/komponents/EditableCKEditor.vue +42 -0
  32. package/kompo-ckeditor-1.0.17/komponents/TranslatableEditor.vue +28 -0
  33. package/kompo-ckeditor-1.0.17/mixins/CKEditor.js +291 -0
  34. package/kompo-ckeditor-1.0.17/mixins/CKEditorUtilities.js +52 -0
  35. package/kompo-ckeditor-1.0.17/mixins/CkUploadAdapter.js +95 -0
  36. package/kompo-ckeditor-1.0.17/mixins/translations/en.js +1 -0
  37. package/kompo-ckeditor-1.0.17/mixins/translations/fr.js +1 -0
  38. package/kompo-ckeditor-1.0.17/plugins/JsonBlockCommand.js +100 -0
  39. package/kompo-ckeditor-1.0.17/plugins/JsonBlockEditing.js +225 -0
  40. package/kompo-ckeditor-1.0.17/plugins/JsonBlockUi.js +21 -0
  41. package/kompo-ckeditor-1.0.17/plugins/MentionPlugin.js +118 -0
  42. package/orchestrator/KompoJsBuilder.js +333 -0
  43. package/package.json +25 -0
  44. package/raw_animations/count-up-animation.js +220 -0
  45. package/raw_animations/magic-poof.js +210 -0
  46. package/tutorials/TutorialConfig.js +34 -0
  47. package/tutorials/TutorialEngine.js +448 -0
  48. package/tutorials/index.js +2 -0
  49. package/tutorials/modules/AutoNext.js +86 -0
  50. package/tutorials/modules/Avatar.js +94 -0
  51. package/tutorials/modules/Bubble.js +617 -0
  52. package/tutorials/modules/Cursor.js +315 -0
  53. package/tutorials/modules/Highlight.js +282 -0
  54. package/tutorials/modules/HoverForcer.js +250 -0
  55. package/tutorials/modules/Mobile.js +148 -0
  56. package/tutorials/modules/Options.js +98 -0
  57. package/tutorials/modules/Overlay.js +142 -0
  58. package/tutorials/modules/Scroll.js +37 -0
  59. package/tutorials/modules/Typewriter.js +132 -0
  60. package/tutorials/step-builder/step-builder-bridge.js +436 -0
  61. package/tutorials/step-builder/utils/bezier-editor.js +379 -0
  62. package/tutorials/step-builder/utils/code-generator.js +127 -0
  63. package/tutorials/step-builder/utils/element-picker.js +125 -0
  64. package/tutorials/tutorial.scss +816 -0
  65. package/tutorials/utils/selectors.js +225 -0
  66. package/tutorials/utils/stability.js +76 -0
  67. package/tutorials/utils/svg.js +46 -0
@@ -0,0 +1,283 @@
1
+ <template>
2
+ <vl-form-field v-bind="$_wrapperAttributes" @labelclick="focus">
3
+ <div class="vlInputGroup">
4
+ <div class="vlInputPrepend" @click.stop="openCalendar" v-html="$_icon" />
5
+ <flat-pickr
6
+ v-model="internalValue"
7
+ class="vlFormControl"
8
+ ref="flatpickr"
9
+ v-bind="$_attributes"
10
+ v-on="$_events"
11
+ autocomplete="off" />
12
+ <div class="vlInputAppend" v-if="!$_readOnly">
13
+ <i v-if="component.value" class="icon-times cursor-pointer hover:text-red-500 transition-colors" @click.stop="clear" />
14
+ </div>
15
+ </div>
16
+ </vl-form-field>
17
+ </template>
18
+
19
+ <script>
20
+ import Field from 'vue-kompo/js/form/mixins/Field'
21
+
22
+ import flatPickr from 'vue-flatpickr-component';
23
+ import 'flatpickr/dist/flatpickr.css';
24
+ import 'flatpickr/dist/themes/airbnb.css';
25
+
26
+ import { French } from "flatpickr/dist/l10n/fr.js"
27
+
28
+ export default {
29
+ mixins: [Field],
30
+ components: { flatPickr },
31
+ data() {
32
+ return {
33
+ internalValue: null
34
+ }
35
+ },
36
+ mounted() {
37
+ this.parseInitialValue();
38
+
39
+ // For day-month mode, hide year elements after flatpickr is ready
40
+ if (this.$_mode === 'day-month') {
41
+ this.$nextTick(() => {
42
+ this.hideYearElements();
43
+ });
44
+ }
45
+ },
46
+ computed: {
47
+ $_mode() { return this.$_config('mode') || 'day-month' },
48
+ $_separator() { return this.$_config('separator') || '-' },
49
+ $_locale() { return this.$_config('kompo_locale') },
50
+ $_placeholder() { return this.$_config('placeholder') || this.defaultPlaceholder },
51
+ $_yearStart() { return this.$_config('yearStart') || new Date().getFullYear() - 10 },
52
+ $_yearEnd() { return this.$_config('yearEnd') || new Date().getFullYear() + 10 },
53
+
54
+ defaultPlaceholder() {
55
+ const placeholders = {
56
+ 'day-month': 'DD-MM',
57
+ 'month-year': 'MM-YYYY'
58
+ };
59
+ return placeholders[this.$_mode] || 'DD-MM';
60
+ },
61
+
62
+ // Flatpickr date format (internal)
63
+ internalDateFormat() {
64
+ if (this.$_mode === 'month-year') {
65
+ return 'Y-m';
66
+ }
67
+ return 'Y-m-d';
68
+ },
69
+
70
+ // Display format
71
+ altFormat() {
72
+ if (this.$_mode === 'month-year') {
73
+ return 'F Y'; // "January 2026"
74
+ }
75
+ return 'd F'; // "25 January" - no year shown
76
+ },
77
+
78
+ $_attributes() {
79
+ const isMonthYear = this.$_mode === 'month-year';
80
+ const isDayMonth = this.$_mode === 'day-month';
81
+
82
+ return {
83
+ ...this.$_defaultFieldAttributes,
84
+ placeholder: this.$_placeholder,
85
+ config: Object.assign({
86
+ wrap: true,
87
+ clickOpens: this.$_config('attrs')?.['disabled'] ? false : true,
88
+ dateFormat: this.internalDateFormat,
89
+ allowInput: false,
90
+ altInput: true,
91
+ altInputClass: 'vlFormControl',
92
+ altFormat: this.altFormat,
93
+ // Disable year navigation for day-month mode
94
+ ...(isDayMonth ? {
95
+ onReady: (selectedDates, dateStr, instance) => {
96
+ this.hideYearElements(instance);
97
+ },
98
+ onOpen: (selectedDates, dateStr, instance) => {
99
+ this.hideYearElements(instance);
100
+ },
101
+ onMonthChange: (selectedDates, dateStr, instance) => {
102
+ this.hideYearElements(instance);
103
+ }
104
+ } : {}),
105
+ // For month-year mode
106
+ ...(isMonthYear ? {
107
+ plugins: [this.monthSelectPlugin()],
108
+ } : {}),
109
+ minDate: isMonthYear ? `${this.$_yearStart}-01` : undefined,
110
+ maxDate: isMonthYear ? `${this.$_yearEnd}-12` : undefined,
111
+ },
112
+ (this.$_locale == 'fr') ? { locale: French } : {},
113
+ ),
114
+ disabled: this.$_readOnly ? true : false
115
+ }
116
+ },
117
+
118
+ $_events() {
119
+ return {
120
+ ...this.$_defaultFieldEvents,
121
+ blur: () => {},
122
+ change: () => {},
123
+ 'on-change': this.onChange,
124
+ 'on-close': this.onClose,
125
+ 'on-open': this.onOpen
126
+ }
127
+ }
128
+ },
129
+ watch: {
130
+ 'component.value': {
131
+ handler(value) {
132
+ if (!value) {
133
+ this.internalValue = null;
134
+ }
135
+ }
136
+ }
137
+ },
138
+ methods: {
139
+ hideYearElements(instance) {
140
+ const fp = instance || this.$refs.flatpickr?.fp;
141
+ if (!fp || !fp.calendarContainer) return;
142
+
143
+ const container = fp.calendarContainer;
144
+
145
+ // Hide year input/display
146
+ const yearInput = container.querySelector('.numInput.cur-year');
147
+ if (yearInput) {
148
+ yearInput.style.display = 'none';
149
+ }
150
+
151
+ // Hide year arrows
152
+ const yearArrowUp = container.querySelector('.arrowUp');
153
+ const yearArrowDown = container.querySelector('.arrowDown');
154
+ if (yearArrowUp) yearArrowUp.style.display = 'none';
155
+ if (yearArrowDown) yearArrowDown.style.display = 'none';
156
+
157
+ // Add class to container for additional CSS targeting
158
+ container.classList.add('flatpickr-no-year');
159
+ },
160
+
161
+ monthSelectPlugin() {
162
+ return (fp) => {
163
+ fp.config.dateFormat = 'Y-m';
164
+
165
+ return {
166
+ onReady: function() {
167
+ fp.calendarContainer.classList.add('flatpickr-monthSelect-months');
168
+ const days = fp.calendarContainer.querySelector('.flatpickr-days');
169
+ if (days) days.style.display = 'none';
170
+ }
171
+ };
172
+ };
173
+ },
174
+
175
+ parseInitialValue() {
176
+ const value = this.component.value;
177
+ if (!value) {
178
+ this.internalValue = null;
179
+ return;
180
+ }
181
+
182
+ const separator = this.$_separator;
183
+ const parts = value.split(separator);
184
+
185
+ if (this.$_mode === 'month-year' && parts.length === 2) {
186
+ const month = parts[0].padStart(2, '0');
187
+ const year = parts[1];
188
+ this.internalValue = `${year}-${month}`;
189
+ } else if (this.$_mode === 'day-month' && parts.length === 2) {
190
+ const day = parts[0].padStart(2, '0');
191
+ const month = parts[1].padStart(2, '0');
192
+ const year = new Date().getFullYear();
193
+ this.internalValue = `${year}-${month}-${day}`;
194
+ }
195
+ },
196
+
197
+ formatOutput(dateStr) {
198
+ if (!dateStr) return '';
199
+
200
+ const separator = this.$_separator;
201
+
202
+ if (this.$_mode === 'month-year') {
203
+ const parts = dateStr.split('-');
204
+ if (parts.length >= 2) {
205
+ return `${parts[1]}${separator}${parts[0]}`;
206
+ }
207
+ } else {
208
+ const parts = dateStr.split('-');
209
+ if (parts.length >= 3) {
210
+ return `${parts[2]}${separator}${parts[1]}`;
211
+ }
212
+ }
213
+ return '';
214
+ },
215
+
216
+ openCalendar() {
217
+ this.$refs.flatpickr.fp.open();
218
+ },
219
+
220
+ focus() {
221
+ this.$refs.flatpickr.fp.open();
222
+ },
223
+
224
+ onOpen(selectedDates, dateStr, instance) {
225
+ if (this.$_mode === 'day-month') {
226
+ this.$nextTick(() => {
227
+ this.hideYearElements(instance);
228
+ });
229
+ }
230
+ },
231
+
232
+ onChange(selectedDates, dateStr, instance) {
233
+ const outputValue = this.formatOutput(dateStr);
234
+ this.component.value = outputValue;
235
+
236
+ this.$_clearErrors();
237
+ this.$_changeAction();
238
+ },
239
+
240
+ onClose(obj, value) {
241
+ this.$_blurAction();
242
+ },
243
+
244
+ clear() {
245
+ this.internalValue = null;
246
+ this.component.value = '';
247
+ this.$_blurAction();
248
+ this.$_changeAction();
249
+ }
250
+ }
251
+ }
252
+ </script>
253
+
254
+ <style>
255
+ /* Hide year elements for day-month mode */
256
+ .flatpickr-no-year .numInput.cur-year,
257
+ .flatpickr-no-year .arrowUp,
258
+ .flatpickr-no-year .arrowDown {
259
+ display: none !important;
260
+ }
261
+
262
+ .flatpickr-no-year .flatpickr-current-month {
263
+ padding-top: 0;
264
+ }
265
+
266
+ .flatpickr-no-year .flatpickr-current-month .flatpickr-monthDropdown-months {
267
+ margin-left: 0;
268
+ }
269
+
270
+ /* Month picker specific styles */
271
+ .flatpickr-monthSelect-months .flatpickr-days {
272
+ display: none !important;
273
+ }
274
+
275
+ .flatpickr-monthSelect-months .flatpickr-current-month {
276
+ font-size: 1.1rem;
277
+ }
278
+
279
+ .flatpickr-monthSelect-months .flatpickr-current-month select,
280
+ .flatpickr-monthSelect-months .flatpickr-current-month input {
281
+ font-size: 1rem;
282
+ }
283
+ </style>
@@ -0,0 +1,358 @@
1
+ <template>
2
+ <vl-form-field v-bind="$_wrapperAttributes">
3
+ <div :class="inputGroupClass">
4
+ <!-- Toggle button on the left -->
5
+ <button
6
+ v-if="showToggle && togglePosition === 'left'"
7
+ type="button"
8
+ class="toggle-button toggle-left"
9
+ :class="{ 'disabled': !allowToggle }"
10
+ @click="togglePasswordVisibility"
11
+ :disabled="!allowToggle"
12
+ :title="isVisible ? 'Hide password' : 'Show password'"
13
+ >
14
+ <span class="toggle-icon" v-html="currentToggleIcon"></span>
15
+ </button>
16
+
17
+ <!-- Password Input -->
18
+ <input
19
+ ref="passwordInput"
20
+ class="vlFormControl password-input"
21
+ :class="{ 'has-left-toggle': showToggle && togglePosition === 'left', 'has-right-toggle': showToggle && togglePosition === 'right' }"
22
+ :type="isVisible ? 'text' : 'password'"
23
+ :value="component?.value || ''"
24
+ v-bind="$_attributes"
25
+ v-on="$_events"
26
+ @input="onPasswordInput"
27
+ @focus="onPasswordFocus"
28
+ @blur="onPasswordBlur"
29
+ />
30
+
31
+ <!-- Toggle button on the right -->
32
+ <button
33
+ v-if="showToggle && togglePosition === 'right'"
34
+ type="button"
35
+ class="toggle-button toggle-right"
36
+ :class="{ 'disabled': !allowToggle }"
37
+ @click="togglePasswordVisibility"
38
+ :disabled="!allowToggle"
39
+ :title="isVisible ? 'Hide password' : 'Show password'"
40
+ >
41
+ <span class="toggle-icon" v-html="currentToggleIcon"></span>
42
+ </button>
43
+ </div>
44
+
45
+ <!-- Optional Strength Indicator -->
46
+ <div v-if="strengthIndicator && component?.value" class="strength-indicator">
47
+ <div class="strength-bar">
48
+ <div
49
+ class="strength-fill"
50
+ :class="strengthClass"
51
+ :style="{ width: strengthPercentage + '%' }"
52
+ ></div>
53
+ </div>
54
+ <div class="strength-text" :class="strengthClass">
55
+ {{ strengthLabel }}
56
+ </div>
57
+ </div>
58
+ </vl-form-field>
59
+ </template>
60
+
61
+ <script>
62
+ import Field from 'vue-kompo/js/form/mixins/Field'
63
+ import HasInputAttributes from 'vue-kompo/js/form/mixins/HasInputAttributes'
64
+
65
+ export default {
66
+ name: 'VlPasswordInput',
67
+ mixins: [Field, HasInputAttributes],
68
+
69
+ data() {
70
+ return {
71
+ isVisible: false,
72
+ isFocused: false,
73
+ }
74
+ },
75
+
76
+ computed: {
77
+ $_attributes() {
78
+ return {
79
+ ...this.$_defaultFieldAttributes,
80
+ ...this.$_defaultInputAttributes,
81
+ autocomplete: 'current-password',
82
+ }
83
+ },
84
+
85
+ showToggle() {
86
+ return this.$_config('showToggle') !== false
87
+ },
88
+
89
+ togglePosition() {
90
+ return this.$_config('togglePosition') || 'right'
91
+ },
92
+
93
+ allowToggle() {
94
+ return this.$_config('allowToggle') !== false
95
+ },
96
+
97
+ toggleIconShow() {
98
+ return this.$_config('toggleIconShow') || this.defaultShowIcon
99
+ },
100
+
101
+ toggleIconHide() {
102
+ return this.$_config('toggleIconHide') || this.defaultHideIcon
103
+ },
104
+
105
+ defaultShowIcon() {
106
+ return `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#6b7280" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
107
+ <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
108
+ <circle cx="12" cy="12" r="3"></circle>
109
+ </svg>`
110
+ },
111
+
112
+ defaultHideIcon() {
113
+ return `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#6b7280" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
114
+ <path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path>
115
+ <line x1="1" y1="1" x2="23" y2="23"></line>
116
+ </svg>`
117
+ },
118
+
119
+ strengthIndicator() {
120
+ return this.$_config('strengthIndicator') === true
121
+ },
122
+
123
+ currentToggleIcon() {
124
+ return this.isVisible ? this.toggleIconHide : this.toggleIconShow
125
+ },
126
+
127
+ inputGroupClass() {
128
+ const baseClass = 'vlInputGroup rounded-lg flex items-stretch'
129
+ const errorClass = this.showError ? (this.$_config('invalidClass') || '!border !border-red-600') : ''
130
+ return `${baseClass} ${errorClass}`
131
+ },
132
+
133
+ showError() {
134
+ const validateFront = this.$_config('validateFront')
135
+ if (!validateFront) return false
136
+
137
+ return this.component?.value && !this.isValidPassword && !this.isFocused
138
+ },
139
+
140
+ isValidPassword() {
141
+ // Basic validation - can be extended
142
+ const value = this.component?.value || ''
143
+ return value.length >= 1 // At least one character
144
+ },
145
+
146
+ // Password strength calculation
147
+ passwordStrength() {
148
+ const password = this.component?.value || ''
149
+ if (!password) return 0
150
+
151
+ let score = 0
152
+
153
+ // Length
154
+ if (password.length >= 8) score += 25
155
+ if (password.length >= 12) score += 25
156
+
157
+ // Character variety
158
+ if (/[a-z]/.test(password)) score += 12.5
159
+ if (/[A-Z]/.test(password)) score += 12.5
160
+ if (/[0-9]/.test(password)) score += 12.5
161
+ if (/[^A-Za-z0-9]/.test(password)) score += 12.5
162
+
163
+ return Math.min(score, 100)
164
+ },
165
+
166
+ strengthPercentage() {
167
+ return this.passwordStrength
168
+ },
169
+
170
+ strengthClass() {
171
+ const strength = this.passwordStrength
172
+ if (strength < 25) return 'strength-weak'
173
+ if (strength < 50) return 'strength-fair'
174
+ if (strength < 75) return 'strength-good'
175
+ return 'strength-strong'
176
+ },
177
+
178
+ strengthLabel() {
179
+ const strength = this.passwordStrength
180
+ if (strength < 25) return 'Weak'
181
+ if (strength < 50) return 'Fair'
182
+ if (strength < 75) return 'Good'
183
+ return 'Strong'
184
+ }
185
+ },
186
+
187
+ methods: {
188
+ togglePasswordVisibility() {
189
+ if (!this.allowToggle) return
190
+
191
+ this.isVisible = !this.isVisible
192
+
193
+ // Keep focus on input after toggle
194
+ this.$nextTick(() => {
195
+ this.focusPasswordInput()
196
+ })
197
+ },
198
+
199
+ onPasswordInput(event) {
200
+ const value = event.target.value
201
+ this.component.value = value
202
+ },
203
+
204
+ onPasswordFocus() {
205
+ this.isFocused = true
206
+ },
207
+
208
+ onPasswordBlur() {
209
+ this.isFocused = false
210
+ },
211
+
212
+ focusPasswordInput() {
213
+ if (this.$refs.passwordInput) {
214
+ this.$refs.passwordInput.focus()
215
+ }
216
+ }
217
+ }
218
+ }
219
+ </script>
220
+
221
+ <style scoped>
222
+ .vlInputGroup {
223
+ display: flex;
224
+ align-items: stretch;
225
+ border-radius: 0.5rem;
226
+ position: relative;
227
+ }
228
+
229
+ .toggle-button {
230
+ display: flex;
231
+ align-items: center;
232
+ justify-content: center;
233
+ padding: 0.5rem;
234
+ border: 1px solid #d1d5db;
235
+ background: white;
236
+ cursor: pointer;
237
+ user-select: none;
238
+ width: 40px;
239
+ min-width: 40px;
240
+ }
241
+
242
+ .toggle-button:hover:not(.disabled) {
243
+ background-color: white;
244
+ border-color: #d1d5db;
245
+ }
246
+
247
+ .toggle-button:hover:not(.disabled) .toggle-icon svg {
248
+ stroke-width: 3;
249
+ stroke: #374151;
250
+ }
251
+
252
+ .toggle-button:focus {
253
+ outline: none;
254
+ border-color: #d1d5db;
255
+ box-shadow: none;
256
+ }
257
+
258
+ .toggle-button:active {
259
+ background-color: white;
260
+ border-color: #d1d5db;
261
+ box-shadow: none;
262
+ transform: none;
263
+ }
264
+
265
+ .toggle-button.disabled {
266
+ background-color: #f3f4f6;
267
+ color: #9ca3af;
268
+ cursor: not-allowed;
269
+ }
270
+
271
+ .toggle-left {
272
+ border-right: none;
273
+ border-radius: 0.5rem 0 0 0.5rem;
274
+ padding-left: 1.25rem;
275
+ }
276
+
277
+ .toggle-right {
278
+ border-left: none;
279
+ border-radius: 0 0.5rem 0.5rem 0;
280
+ padding-right: 1.25rem;
281
+ }
282
+
283
+ .toggle-icon {
284
+ font-size: 1rem;
285
+ line-height: 1;
286
+ }
287
+
288
+ .password-input.has-left-toggle {
289
+ border-radius: 0 0.5rem 0.5rem 0;
290
+ border-left: none;
291
+ }
292
+
293
+ .password-input.has-right-toggle {
294
+ border-radius: 0.5rem 0 0 0.5rem;
295
+ border-right: none;
296
+ }
297
+
298
+ .password-input:not(.has-left-toggle):not(.has-right-toggle) {
299
+ border-radius: 0.5rem;
300
+ }
301
+
302
+ /* Strength Indicator */
303
+ .strength-indicator {
304
+ margin-top: 1rem;
305
+ }
306
+
307
+ .strength-bar {
308
+ height: 4px;
309
+ background-color: #e5e7eb;
310
+ border-radius: 2px;
311
+ overflow: hidden;
312
+ margin-bottom: 0.25rem;
313
+ }
314
+
315
+ .strength-fill {
316
+ height: 100%;
317
+ transition: width 0.3s ease-in-out, background-color 0.3s ease-in-out;
318
+ border-radius: 2px;
319
+ }
320
+
321
+ .strength-text {
322
+ font-size: 0.75rem;
323
+ font-weight: 500;
324
+ }
325
+
326
+ /* Strength Colors */
327
+ .strength-weak {
328
+ color: #dc2626;
329
+ }
330
+ .strength-weak .strength-fill,
331
+ .strength-fill.strength-weak {
332
+ background-color: #dc2626;
333
+ }
334
+
335
+ .strength-fair {
336
+ color: #ea580c;
337
+ }
338
+ .strength-fair .strength-fill,
339
+ .strength-fill.strength-fair {
340
+ background-color: #ea580c;
341
+ }
342
+
343
+ .strength-good {
344
+ color: #ca8a04;
345
+ }
346
+ .strength-good .strength-fill,
347
+ .strength-fill.strength-good {
348
+ background-color: #ca8a04;
349
+ }
350
+
351
+ .strength-strong {
352
+ color: #16a34a;
353
+ }
354
+ .strength-strong .strength-fill,
355
+ .strength-fill.strength-strong {
356
+ background-color: #16a34a;
357
+ }
358
+ </style>