@bagelink/vue 1.15.112 → 1.15.114

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bagelink/vue",
3
3
  "type": "module",
4
- "version": "1.15.112",
4
+ "version": "1.15.114",
5
5
  "description": "Bagel core sdk packages",
6
6
  "author": {
7
7
  "name": "Bagel Studio",
@@ -5,6 +5,7 @@ import { onClickOutside } from '@vueuse/core'
5
5
  import { ref, computed, onMounted, watch } from 'vue'
6
6
  import { WEEK_START_DAY } from '../../../utils/calendar/time'
7
7
  import DatePicker from './DatePicker.vue'
8
+ import type { DateRangeValue } from './DatePicker.vue'
8
9
  import type { BagelInputShellProps } from './bagelInputShell'
9
10
  import { useBagelInputShell } from './bagelInputShell'
10
11
 
@@ -19,7 +20,13 @@ export interface DateInputProps extends BagelInputShellProps {
19
20
  * @default false
20
21
  */
21
22
  enableTime?: boolean
22
- modelValue?: string | Date
23
+ /**
24
+ * Select a start/end date range in a single input.
25
+ * When true, `modelValue` is a `{ start, end }` object.
26
+ * @default false
27
+ */
28
+ range?: boolean
29
+ modelValue?: string | Date | DateRangeValue
23
30
  min?: string | Date
24
31
  max?: string | Date
25
32
  mode?: ModeType
@@ -37,6 +44,7 @@ const props = withDefaults(
37
44
  enableTime: false,
38
45
  editMode: true,
39
46
  small: false,
47
+ range: false,
40
48
  mode: () => ({ mode: 'day' }),
41
49
  firstDayOfWeek: WEEK_START_DAY.SUNDAY,
42
50
  locale: ''
@@ -47,7 +55,9 @@ const { $t } = useI18n()
47
55
 
48
56
  const { shellClass, shellStyle } = useBagelInputShell(props)
49
57
 
50
- const selectedDate = defineModel<string | Date>()
58
+ const selectedDate = defineModel<string | Date | DateRangeValue>()
59
+
60
+ const RANGE_SEPARATOR = ' - '
51
61
  const inputValue = ref('')
52
62
  const isValid = ref(true)
53
63
  const isTyping = ref(false)
@@ -58,11 +68,22 @@ const datePickerRef = ref<HTMLElement | null>(null)
58
68
  const calendarRef = ref<HTMLElement | null>(null)
59
69
 
60
70
  // Formatting utilities
61
- function formatDisplayDate(date: Date | string | undefined): string {
71
+ function formatSingleDate(date: Date | string | undefined): string {
62
72
  if (date === undefined || date === '') return ''
63
73
  return formatDate(date, { format: props.enableTime ? 'DD.MM.YY HH:mm' : 'DD.MM.YY' })
64
74
  }
65
75
 
76
+ function formatDisplayDate(value: Date | string | DateRangeValue | undefined): string {
77
+ if (props.range) {
78
+ const range = (value ?? {}) as DateRangeValue
79
+ const start = formatSingleDate(range.start)
80
+ const end = formatSingleDate(range.end)
81
+ if (!start && !end) return ''
82
+ return `${start}${RANGE_SEPARATOR}${end}`
83
+ }
84
+ return formatSingleDate(value as Date | string | undefined)
85
+ }
86
+
66
87
  function parseUserInput(input: string): Date | null {
67
88
  const date = new Date(input)
68
89
  if (!Number.isNaN(date.getTime())) return date
@@ -88,25 +109,41 @@ function normalizeDate(date: Date): string {
88
109
  return normalized.toISOString().split('T')[0]
89
110
  }
90
111
 
112
+ function hasModelValue(): boolean {
113
+ if (props.range) {
114
+ const range = selectedDate.value as DateRangeValue | undefined
115
+ return !!(range && (range.start || range.end))
116
+ }
117
+ return selectedDate.value !== undefined
118
+ }
119
+
91
120
  function syncInputValue() {
92
121
  if (!isTyping.value) {
93
- inputValue.value = selectedDate.value !== undefined ? formatDisplayDate(selectedDate.value) : ''
122
+ inputValue.value = hasModelValue() ? formatDisplayDate(selectedDate.value) : ''
94
123
  }
95
124
  }
96
125
 
126
+ // Format a single "DD.MM.YY" fragment as the user types (auto-insert dots).
127
+ function autoFormatDatePart(raw: string): string {
128
+ const cleaned = raw.replace(/[^\d.]/g, '')
129
+ const parts = cleaned.split('.')
130
+ let formatted = parts.slice(0, 3).join('.')
131
+ if (formatted.length === 2 && !formatted.includes('.')) formatted += '.'
132
+ else if (formatted.length === 5 && parts.length === 2) formatted += '.'
133
+ return formatted
134
+ }
135
+
97
136
  // Event handlers
98
137
  function handleInput(event: Event) {
99
138
  const input = event.target as HTMLInputElement
100
139
  isTyping.value = true
101
140
 
102
- // Auto-format: clean and add dots after DD and MM
103
- const cleaned = input.value.replace(/[^\d.]/g, '')
104
- const parts = cleaned.split('.')
105
- let formatted = parts.slice(0, 3).join('.')
106
-
107
- if (formatted.length === 2 && !formatted.includes('.')) formatted += '.'
108
- else if (formatted.length === 5 && parts.length === 2) formatted += '.'
141
+ if (props.range) {
142
+ handleRangeInput(input)
143
+ return
144
+ }
109
145
 
146
+ const formatted = autoFormatDatePart(input.value)
110
147
  inputValue.value = formatted
111
148
  if (input.value !== formatted) input.value = formatted
112
149
 
@@ -123,6 +160,49 @@ function handleInput(event: Event) {
123
160
  }
124
161
  }
125
162
 
163
+ function handleRangeInput(input: HTMLInputElement) {
164
+ // Split on the separator (tolerate a bare "-" typed by the user)
165
+ const [rawStart = '', rawEnd = ''] = input.value.split(/\s*-\s*/, 2)
166
+ const startFmt = autoFormatDatePart(rawStart)
167
+ const endFmt = autoFormatDatePart(rawEnd)
168
+
169
+ // Reassemble display; only show separator once the user moved past the start
170
+ const typedSeparator = /-/.test(input.value)
171
+ const formatted = (endFmt || typedSeparator)
172
+ ? `${startFmt}${RANGE_SEPARATOR}${endFmt}`
173
+ : startFmt
174
+
175
+ inputValue.value = formatted
176
+ if (input.value !== formatted) input.value = formatted
177
+
178
+ const startDate = rawStart ? parseUserInput(startFmt) : null
179
+ const endDate = rawEnd ? parseUserInput(endFmt) : null
180
+
181
+ // Invalid only if a non-empty part fails to parse
182
+ if ((rawStart && !startDate) || (rawEnd && !endDate)) {
183
+ isValid.value = false
184
+ return
185
+ }
186
+
187
+ isValid.value = true
188
+ if (!startDate && !endDate) {
189
+ selectedDate.value = undefined
190
+ return
191
+ }
192
+
193
+ // Order endpoints if both present
194
+ let lo = startDate
195
+ let hi = endDate
196
+ if (startDate && endDate && endDate < startDate) {
197
+ lo = endDate
198
+ hi = startDate
199
+ }
200
+ selectedDate.value = {
201
+ start: lo ? normalizeDate(lo) : undefined,
202
+ end: hi ? normalizeDate(hi) : undefined,
203
+ }
204
+ }
205
+
126
206
  function handleFocus() {
127
207
  if (!isClosing) isOpen.value = true
128
208
  }
@@ -132,7 +212,7 @@ function handleBlur() {
132
212
  if (inputValue.value.length === 0) {
133
213
  selectedDate.value = undefined
134
214
  isValid.value = true
135
- } else if (selectedDate.value !== undefined) {
215
+ } else if (hasModelValue()) {
136
216
  inputValue.value = formatDisplayDate(selectedDate.value)
137
217
  isValid.value = true
138
218
  } else {
@@ -153,15 +233,16 @@ function handleKeydown(event: KeyboardEvent) {
153
233
  if (key === 'Escape') {
154
234
  isOpen.value = false
155
235
  isTyping.value = false
156
- inputValue.value = selectedDate.value !== undefined ? formatDisplayDate(selectedDate.value) : ''
236
+ inputValue.value = hasModelValue() ? formatDisplayDate(selectedDate.value) : ''
157
237
  isValid.value = true
158
238
  } else if (key === 'Enter') {
159
239
  isTyping.value = false
160
- if (selectedDate.value !== undefined) inputValue.value = formatDisplayDate(selectedDate.value)
240
+ if (hasModelValue()) inputValue.value = formatDisplayDate(selectedDate.value)
161
241
  isOpen.value = false
162
- } else if (key === 'ArrowUp' || key === 'ArrowDown') {
242
+ } else if (!props.range && (key === 'ArrowUp' || key === 'ArrowDown')) {
243
+ // Arrow-key increment only applies to single-date mode
163
244
  event.preventDefault()
164
- const date = selectedDate.value !== undefined ? new Date(selectedDate.value) : new Date()
245
+ const date = selectedDate.value !== undefined ? new Date(selectedDate.value as string | Date) : new Date()
165
246
  const increment = key === 'ArrowUp' ? 1 : -1
166
247
 
167
248
  if (shiftKey) date.setMonth(date.getMonth() + increment)
@@ -181,6 +262,11 @@ watch(() => selectedDate.value, syncInputValue, { immediate: true })
181
262
 
182
263
  const hasValue = computed(() => inputValue.value.length > 0)
183
264
 
265
+ const defaultPlaceholder = computed(() => {
266
+ const single = props.enableTime ? 'DD.MM.YY HH:mm' : 'DD.MM.YY'
267
+ return props.range ? `${single}${RANGE_SEPARATOR}${single}` : single
268
+ })
269
+
184
270
  onClickOutside(datePickerRef, () => {
185
271
  isClosing = true
186
272
  isOpen.value = false
@@ -188,7 +274,7 @@ onClickOutside(datePickerRef, () => {
188
274
  }, { ignore: [calendarRef] })
189
275
 
190
276
  onMounted(() => {
191
- if (selectedDate.value === undefined && props.defaultValue !== undefined) {
277
+ if (!props.range && selectedDate.value === undefined && props.defaultValue !== undefined) {
192
278
  selectedDate.value = props.defaultValue
193
279
  }
194
280
  })
@@ -208,7 +294,7 @@ onMounted(() => {
208
294
  <div ref="datePickerRef" class="date-picker-container">
209
295
  <TextInput
210
296
  v-model="inputValue" iconStart="calendar"
211
- :placeholder="underlined ? ' ' : (resolveI18n(placeholder) || (enableTime ? 'DD.MM.YY HH:mm' : 'DD.MM.YY'))"
297
+ :placeholder="underlined ? ' ' : (resolveI18n(placeholder) || defaultPlaceholder)"
212
298
  :required="underlined ? false : required" :disabled="!editMode"
213
299
  :error="!isValid ? $t('date.invalidFormat') : error" :class="{ 'txt-center': center }"
214
300
  :underlined="underlined" :frame="frame" :outline="outline" :min-width="minWidth"
@@ -223,6 +309,7 @@ onMounted(() => {
223
309
  <DatePicker
224
310
  v-model="selectedDate" :min="min" :max="max" :mode="mode"
225
311
  :firstDayOfWeek="firstDayOfWeek" :locale="locale" :enableTime="enableTime"
312
+ :range="range"
226
313
  />
227
314
  </div>
228
315
  </Dropdown>
@@ -5,15 +5,26 @@ import { Btn, NumberInput } from '@bagelink/vue'
5
5
  import { computed, ref, toValue } from 'vue'
6
6
  import Time, { WEEK_START_DAY } from '../../../utils/calendar/time'
7
7
 
8
+ export interface DateRangeValue {
9
+ start?: string
10
+ end?: string
11
+ }
12
+
8
13
  const props = withDefaults(
9
14
  defineProps<{
10
- modelValue?: string | Date
15
+ modelValue?: string | Date | DateRangeValue
11
16
  min?: string | Date
12
17
  max?: string | Date
13
18
  mode?: ModeType
14
19
  firstDayOfWeek?: WEEK_START_DAY
15
20
  locale?: string
16
21
  enableTime?: boolean
22
+ /**
23
+ * Whether to select a start/end date range instead of a single date.
24
+ * When true, `modelValue` is a `{ start, end }` object.
25
+ * @default false
26
+ */
27
+ range?: boolean
17
28
  highlightedDates?: MaybeRefOrGetter<(string | Date)[]>
18
29
  disabledDates?: MaybeRefOrGetter<(string | Date)[]>
19
30
  allowedDates?: MaybeRefOrGetter<(string | Date)[]>
@@ -24,11 +35,15 @@ const props = withDefaults(
24
35
  firstDayOfWeek: WEEK_START_DAY.SUNDAY,
25
36
  locale: '',
26
37
  enableTime: false,
38
+ range: false,
27
39
  },
28
40
  )
29
41
 
30
42
  const emit = defineEmits(['update:modelValue'])
31
43
 
44
+ // Tentative end date hovered during range selection
45
+ const hoverDate = ref<Date | null>(null)
46
+
32
47
  const computedHighlightedDates = computed(() => toValue(props.highlightedDates))
33
48
  const computedDisabledDates = computed(() => toValue(props.disabledDates))
34
49
  const computedAllowedDates = computed(() => toValue(props.allowedDates))
@@ -128,17 +143,36 @@ function useDateValidation() {
128
143
  // Date state composable
129
144
  function useDateState() {
130
145
  const selectedDate = computed(() => {
131
- return parseDate(props.modelValue)
146
+ return parseDate(props.range ? undefined : (props.modelValue as string | Date | undefined))
147
+ })
148
+
149
+ const rangeStart = computed(() => {
150
+ if (!props.range) { return null }
151
+ return parseDate((props.modelValue as DateRangeValue | undefined)?.start)
152
+ })
153
+
154
+ const rangeEnd = computed(() => {
155
+ if (!props.range) { return null }
156
+ return parseDate((props.modelValue as DateRangeValue | undefined)?.end)
132
157
  })
133
158
 
134
159
  return {
135
- selectedDate
160
+ selectedDate,
161
+ rangeStart,
162
+ rangeEnd
136
163
  }
137
164
  }
138
165
 
139
166
  // Initialize core composables
140
167
  const { isDateDisabled, isMonthDisabled, isYearDisabled } = useDateValidation()
141
- const { selectedDate } = useDateState()
168
+ const { selectedDate, rangeStart, rangeEnd } = useDateState()
169
+
170
+ function isSameDay(a: Date | null, b: Date | null): boolean {
171
+ if (!a || !b) { return false }
172
+ return a.getFullYear() === b.getFullYear()
173
+ && a.getMonth() === b.getMonth()
174
+ && a.getDate() === b.getDate()
175
+ }
142
176
 
143
177
  // Calendar view composable
144
178
  function useCalendarView() {
@@ -200,10 +234,35 @@ function useCalendarView() {
200
234
  })
201
235
 
202
236
  const isSelected = (date: Date | null) => {
203
- if (!date || !selectedDate.value) { return false }
204
- return date.getFullYear() === selectedDate.value.getFullYear()
205
- && date.getMonth() === selectedDate.value.getMonth()
206
- && date.getDate() === selectedDate.value.getDate()
237
+ if (!date) { return false }
238
+ if (props.range) {
239
+ return isSameDay(date, rangeStart.value) || isSameDay(date, rangeEnd.value)
240
+ }
241
+ if (!selectedDate.value) { return false }
242
+ return isSameDay(date, selectedDate.value)
243
+ }
244
+
245
+ // Effective end of the range for display: committed end, or the hovered date
246
+ // while the user is picking the second endpoint.
247
+ const previewEnd = computed(() => {
248
+ if (rangeEnd.value) { return rangeEnd.value }
249
+ if (rangeStart.value && hoverDate.value) { return hoverDate.value }
250
+ return null
251
+ })
252
+
253
+ const isRangeStart = (date: Date | null) => props.range && isSameDay(date, rangeStart.value)
254
+ const isRangeEnd = (date: Date | null) => props.range && isSameDay(date, rangeEnd.value)
255
+
256
+ const isInRange = (date: Date | null) => {
257
+ if (!props.range || !date || !rangeStart.value) { return false }
258
+ const end = previewEnd.value
259
+ if (!end) { return false }
260
+ const lo = rangeStart.value < end ? rangeStart.value : end
261
+ const hi = rangeStart.value < end ? end : rangeStart.value
262
+ const d = new Date(date.getFullYear(), date.getMonth(), date.getDate())
263
+ const loD = new Date(lo.getFullYear(), lo.getMonth(), lo.getDate())
264
+ const hiD = new Date(hi.getFullYear(), hi.getMonth(), hi.getDate())
265
+ return d > loD && d < hiD
207
266
  }
208
267
 
209
268
  const isToday = (date: Date | null) => {
@@ -243,6 +302,9 @@ function useCalendarView() {
243
302
  years,
244
303
  weekDays,
245
304
  isSelected,
305
+ isRangeStart,
306
+ isRangeEnd,
307
+ isInRange,
246
308
  isToday,
247
309
  isNotInMonth,
248
310
  isHighlighted
@@ -336,6 +398,9 @@ const {
336
398
  years,
337
399
  weekDays,
338
400
  isSelected,
401
+ isRangeStart,
402
+ isRangeEnd,
403
+ isInRange,
339
404
  isToday,
340
405
  isNotInMonth,
341
406
  isHighlighted
@@ -343,10 +408,42 @@ const {
343
408
  const { selectMonth, selectYear, previousMonth, nextMonth, previousYear, nextYear } = useNavigation()
344
409
  const { hours, minutes, handleHourInput, handleMinuteInput } = useTimeHandling()
345
410
 
411
+ function toDayString(date: Date): string {
412
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate()).toISOString()
413
+ }
414
+
415
+ // Range selection handler: first click sets start (clears end), second click
416
+ // sets end (ordering start/end automatically). A third click starts over.
417
+ function selectRange(date: Date) {
418
+ const start = rangeStart.value
419
+ const end = rangeEnd.value
420
+
421
+ if (!start || end) {
422
+ // Start a new range
423
+ emit('update:modelValue', { start: toDayString(date), end: undefined })
424
+ return
425
+ }
426
+
427
+ // Completing the range — order the two endpoints
428
+ const lo = date < start ? date : start
429
+ const hi = date < start ? start : date
430
+ emit('update:modelValue', { start: toDayString(lo), end: toDayString(hi) })
431
+ hoverDate.value = null
432
+ }
433
+
434
+ function onDayHover(date: Date | null) {
435
+ if (props.range && date) { hoverDate.value = date }
436
+ }
437
+
346
438
  // Date selection handler
347
439
  function selectDate(date: Date | null) {
348
440
  if (!date) { return }
349
441
 
442
+ if (props.range) {
443
+ selectRange(date)
444
+ return
445
+ }
446
+
350
447
  if (props.enableTime) {
351
448
  // Create date with current time values
352
449
  const newDate = new Date(date.getFullYear(), date.getMonth(), date.getDate())
@@ -400,11 +497,15 @@ function selectDate(date: Date | null) {
400
497
  class="day aspect-ratio-1 flex align-items-center justify-content-center pointer round txt14 p-0"
401
498
  :class="{
402
499
  'selected': isSelected(date),
500
+ 'range-start': isRangeStart(date),
501
+ 'range-end': isRangeEnd(date),
502
+ 'in-range': isInRange(date),
403
503
  'today': isToday(date),
404
504
  'disabled': isDateDisabled(date),
405
505
  'not-in-month': isNotInMonth(date),
406
506
  'highlighted': isHighlighted(date),
407
507
  }" :disabled="isDateDisabled(date)" @click="selectDate(date)"
508
+ @mouseenter="onDayHover(date)"
408
509
  >
409
510
  {{ date?.getDate() }}
410
511
  </button>
@@ -529,6 +630,21 @@ color: var(--bgl-white);
529
630
  border: 1px solid var(--bgl-primary);
530
631
  }
531
632
 
633
+ .day.in-range:not(.selected) {
634
+ background-color: var(--bgl-secondary-light, rgba(var(--bgl-primary-rgb, 0, 123, 255), 0.15));
635
+ border-radius: 0;
636
+ }
637
+
638
+ .day.range-start:not(.range-end) {
639
+ border-top-right-radius: 0;
640
+ border-bottom-right-radius: 0;
641
+ }
642
+
643
+ .day.range-end:not(.range-start) {
644
+ border-top-left-radius: 0;
645
+ border-bottom-left-radius: 0;
646
+ }
647
+
532
648
  .day.disabled {
533
649
  opacity: 0.6;
534
650
  filter: grayscale(0.3);
@@ -303,7 +303,7 @@ onMounted(() => {
303
303
  <div v-if="clearable && selectedItemCount > 0" class="ms-auto ps-05 me-05">
304
304
  <Btn flat thin icon="clear" class="color-gray" @click="selectedItems = []; emitUpdate()" />
305
305
  </div>
306
- <Icon v-if="!disabled" thin transition="rotate" :icon="open ? (underlined ? 'expand_less' : 'unfold_less') : (underlined ? 'expand_more' : 'unfold_more')" />
306
+ <Icon v-if="!disabled" thin transition="scale" :icon="open ? (underlined ? 'expand_less' : 'unfold_less') : (underlined ? 'expand_more' : 'unfold_more')" />
307
307
  </button>
308
308
  <input
309
309
  v-if="required && !underlined" tabindex="-1"
@@ -244,29 +244,55 @@ gap: 0.5rem;
244
244
  background: var(--input-disabled-bg);
245
245
  }
246
246
 
247
- .textInputIconWrap .bgl_icon-font {
248
- color: var(--bgl-input-color);
249
- position: absolute;
250
- inset-inline-end: calc(var(--bgl-input-height) / 3 - 0.25rem);
251
- /* center on the input row regardless of input height (dense/small/default) */
247
+ /* Icon centering.
248
+ The icon is absolutely positioned inside the <label>, whose only positioned ancestor
249
+ is `.bagel-input`. In the DEFAULT variant the label text is in normal flow and pushes
250
+ the input DOWN, but the old `top: input-height/2` was measured from the component top —
251
+ so with a label the icon rode up onto the label text (the misalignment).
252
+
253
+ Fix: make the <label> the containing block and anchor the icon into a band the height
254
+ of the input row pinned to the BOTTOM of the label (the input is the last row), then
255
+ flex-center it. Bottom-anchored + fixed band height ⇒ centered on the input regardless
256
+ of a label above, and in dense/small modes. Scoped to :not(.underlined) because the
257
+ underlined variant floats its label as an overlay (input isn't pushed down). */
258
+ .bagel-input:not(.underlined).textInputIconWrap > label,
259
+ .bagel-input:not(.underlined).txtInputIconStart > label {
260
+ position: relative;
261
+ display: block;
262
+ }
263
+
264
+ .bagel-input:not(.underlined).textInputIconWrap .bgl_icon-font,
265
+ .bagel-input:not(.underlined).txtInputIconStart .iconStart {
266
+ top: auto;
267
+ bottom: calc(var(--bgl-input-height) / 2);
268
+ transform: translateY(50%);
269
+ }
270
+
271
+ /* underlined: label floats as an overlay, so the original top-center is correct */
272
+ .underlined.textInputIconWrap .bgl_icon-font,
273
+ .underlined.txtInputIconStart .iconStart {
252
274
  top: calc(var(--bgl-input-height) / 2);
253
275
  transform: translateY(-50%);
276
+ }
277
+
278
+ .textInputIconWrap .bgl_icon-font,
279
+ .txtInputIconStart .iconStart {
280
+ color: var(--bgl-input-color);
281
+ position: absolute;
254
282
  line-height: 0;
255
283
  }
256
284
 
285
+ .textInputIconWrap .bgl_icon-font {
286
+ inset-inline-end: calc(var(--bgl-input-height) / 3 - 0.25rem);
287
+ }
288
+
257
289
  .textInputIconWrap input,
258
290
  .textInputIconWrap textarea {
259
291
  padding-inline-end: calc(var(--bgl-input-height) / 3 + 1.5rem);
260
292
  }
261
293
 
262
294
  .txtInputIconStart .iconStart {
263
- color: var(--bgl-input-color);
264
- position: absolute;
265
295
  inset-inline-start: calc(var(--bgl-input-height) / 3 - 0.25rem);
266
- /* center on the input row regardless of input height (dense/small/default) */
267
- top: calc(var(--bgl-input-height) / 2);
268
- transform: translateY(-50%);
269
- line-height: 0;
270
296
  }
271
297
 
272
298
  .txtInputIconStart input,
@@ -25,10 +25,8 @@
25
25
  #00000020 (translucent dark). Blends over any dark surface (bg, card,
26
26
  input) instead of being a fixed opaque gray. */
27
27
 
28
- /* ---- Brand (slightly adjusted for dark backgrounds) ------------------- */
29
- --bgl-primary: #4f7cff !important;
30
- --bgl-primary-tint: #4f7cff33;
31
- --bgl-primary-light: #1a2236;
28
+ /* ---- Brand (derived from the project's --bgl-primary, not hardcoded) --- */
29
+ --bgl-primary-tint: color-mix(in srgb, var(--bgl-primary) 20%, transparent);
32
30
 
33
31
  /* ---- Status / accent colors (tuned for dark backgrounds) ------------- *
34
32
  * The light-mode bases (e.g. blue #2e5bff, green #75c98f, red #ed6c6f,