@bagelink/vue 1.15.59 → 1.15.61

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 (53) hide show
  1. package/dist/components/Avatar.vue.d.ts +5 -1
  2. package/dist/components/Avatar.vue.d.ts.map +1 -1
  3. package/dist/components/Badge.vue.d.ts +6 -1
  4. package/dist/components/Badge.vue.d.ts.map +1 -1
  5. package/dist/components/Dropdown.vue.d.ts.map +1 -1
  6. package/dist/components/ListItem.vue.d.ts +12 -0
  7. package/dist/components/ListItem.vue.d.ts.map +1 -1
  8. package/dist/components/Progress.vue.d.ts +38 -0
  9. package/dist/components/Progress.vue.d.ts.map +1 -0
  10. package/dist/components/Swiper.vue.d.ts.map +1 -1
  11. package/dist/components/index.d.ts +1 -0
  12. package/dist/components/index.d.ts.map +1 -1
  13. package/dist/components/layout/AppSidebar.vue.d.ts +1 -0
  14. package/dist/components/layout/AppSidebar.vue.d.ts.map +1 -1
  15. package/dist/components/layout/Divider.vue.d.ts +31 -0
  16. package/dist/components/layout/Divider.vue.d.ts.map +1 -0
  17. package/dist/components/layout/Layout.vue.d.ts.map +1 -1
  18. package/dist/components/layout/SidebarNavItem.vue.d.ts +18 -0
  19. package/dist/components/layout/SidebarNavItem.vue.d.ts.map +1 -0
  20. package/dist/components/layout/index.d.ts +1 -0
  21. package/dist/components/layout/index.d.ts.map +1 -1
  22. package/dist/directives/index.d.ts +2 -0
  23. package/dist/directives/index.d.ts.map +1 -1
  24. package/dist/directives/reveal.d.ts +29 -0
  25. package/dist/directives/reveal.d.ts.map +1 -0
  26. package/dist/index.cjs +37 -37
  27. package/dist/index.mjs +7948 -7649
  28. package/dist/plugins/bagel.d.ts.map +1 -1
  29. package/dist/style.css +1 -1
  30. package/dist/utils/index.d.ts +1 -1
  31. package/dist/utils/index.d.ts.map +1 -1
  32. package/package.json +1 -1
  33. package/src/components/Avatar.vue +20 -4
  34. package/src/components/Badge.vue +32 -2
  35. package/src/components/Dropdown.vue +7 -1
  36. package/src/components/ListItem.vue +151 -76
  37. package/src/components/Progress.vue +80 -0
  38. package/src/components/Swiper.vue +19 -1
  39. package/src/components/index.ts +1 -0
  40. package/src/components/layout/AppSidebar.vue +11 -15
  41. package/src/components/layout/Divider.vue +64 -0
  42. package/src/components/layout/Layout.vue +17 -4
  43. package/src/components/layout/SidebarNavItem.vue +186 -0
  44. package/src/components/layout/index.ts +1 -0
  45. package/src/directives/index.ts +2 -0
  46. package/src/directives/reveal.ts +78 -0
  47. package/src/plugins/bagel.ts +2 -1
  48. package/src/styles/appearance.css +11 -0
  49. package/src/styles/bagel.css +1 -0
  50. package/src/styles/dark.css +94 -2
  51. package/src/styles/layout.css +65 -13
  52. package/src/styles/motion.css +91 -0
  53. package/src/utils/index.ts +1 -1
@@ -0,0 +1,64 @@
1
+ <script lang="ts" setup>
2
+ defineOptions({ name: 'BglDivider' })
3
+ import { useSlots } from 'vue'
4
+
5
+ /**
6
+ * Hairline separator in the theme border color.
7
+ * - Horizontal by default; `vertical` for inline groups (toolbars, breadcrumbs).
8
+ * - Default slot (or `label`) renders centered text with a line on each side
9
+ * (horizontal only) — e.g. an "OR" divider.
10
+ * - `size` sets the length of a vertical divider (maps to --divider-size).
11
+ */
12
+ const {
13
+ vertical = false,
14
+ label = '',
15
+ size,
16
+ class: className = '',
17
+ } = defineProps<{
18
+ vertical?: boolean
19
+ label?: string
20
+ size?: string
21
+ class?: string
22
+ }>()
23
+
24
+ const slots = useSlots()
25
+ </script>
26
+
27
+ <template>
28
+ <!-- vertical: short hairline, no label support -->
29
+ <div
30
+ v-if="vertical"
31
+ class="divider-v"
32
+ :class="className"
33
+ :style="size ? { '--divider-size': size } : undefined"
34
+ role="separator"
35
+ aria-orientation="vertical"
36
+ />
37
+
38
+ <!-- horizontal with label/slot: line — content — line -->
39
+ <div v-else-if="label || slots.default" class="bgl-divider-labeled" :class="className" role="separator">
40
+ <span class="divider bgl-divider-line" />
41
+ <span class="bgl-divider-label"><slot>{{ label }}</slot></span>
42
+ <span class="divider bgl-divider-line" />
43
+ </div>
44
+
45
+ <!-- plain horizontal hairline -->
46
+ <div v-else class="divider" :class="className" role="separator" />
47
+ </template>
48
+
49
+ <style scoped>
50
+ .bgl-divider-labeled {
51
+ display: flex;
52
+ align-items: center;
53
+ gap: 0.75rem;
54
+ }
55
+ .bgl-divider-line {
56
+ flex: 1;
57
+ }
58
+ .bgl-divider-label {
59
+ font-size: 0.8125rem;
60
+ color: var(--bgl-text-soft, var(--bgl-gray));
61
+ white-space: nowrap;
62
+ flex-shrink: 0;
63
+ }
64
+ </style>
@@ -22,19 +22,32 @@ const props = withDefaults(defineProps<LayoutProrps>(), {
22
22
 
23
23
  })
24
24
 
25
- const gridTemplateRows = computed(() => (props.rows.length > 0 ? props.rows.join(' ') : 'auto'))
25
+ // Bare `fr` tracks won't shrink below their content's intrinsic size, which
26
+ // causes overflow when a child (table, long text, nested grid) is wider than its
27
+ // share. Wrapping in `minmax(0, …)` makes the track shrinkable — the behaviour
28
+ // you almost always want. Authors can opt out by passing an explicit minmax().
29
+ function normalizeTrack(track: string): string {
30
+ const t = track.trim()
31
+ if (/^\d*\.?\d+fr$/.test(t)) { return `minmax(0, ${t})` }
32
+ return t
33
+ }
34
+ function buildTemplate(tracks: string[]): string {
35
+ return tracks.length > 0 ? tracks.map(normalizeTrack).join(' ') : 'auto'
36
+ }
37
+
38
+ const gridTemplateRows = computed(() => buildTemplate(props.rows))
26
39
  const gapSize = computed(() => `${props.gap}rem`)
27
40
  const mGapSize = computed(() => props.mGap !== undefined ? `${props.mGap}rem` : gapSize.value)
28
41
 
29
42
  const mGridTemplateRows = computed(() => {
30
- if (props.mRows?.length) { return props.mRows.join(' ') }
43
+ if (props.mRows?.length) { return buildTemplate(props.mRows) }
31
44
  return gridTemplateRows.value
32
45
  })
33
46
 
34
- const gridTemplateColumns = computed(() => (props.columns.length > 0 ? props.columns.join(' ') : 'auto'))
47
+ const gridTemplateColumns = computed(() => buildTemplate(props.columns))
35
48
 
36
49
  const mGridTemplateColumns = computed(() => {
37
- if (props.mColumns?.length) { return props.mColumns.join(' ') }
50
+ if (props.mColumns?.length) { return buildTemplate(props.mColumns) }
38
51
  return gridTemplateColumns.value
39
52
  })
40
53
  </script>
@@ -0,0 +1,186 @@
1
+ <script lang="ts" setup>
2
+ import type { NavLink } from '@bagelink/vue'
3
+ import { Btn, Icon, Dropdown } from '@bagelink/vue'
4
+ import { computed, ref, watch } from 'vue'
5
+ import { useRoute } from 'vue-router'
6
+ import { resolveI18n } from '../../i18n'
7
+
8
+ interface LinkWithAction extends NavLink {
9
+ activeRoutes?: string[]
10
+ children?: LinkWithAction[]
11
+ action?: () => void
12
+ }
13
+
14
+ const props = withDefaults(defineProps<{
15
+ link: LinkWithAction
16
+ /** sidebar is expanded (true) or collapsed to icons (false) */
17
+ open: boolean
18
+ isMobile: boolean
19
+ bgColor: string
20
+ textColor: string
21
+ activeColor: string
22
+ }>(), {})
23
+
24
+ const route = useRoute()
25
+
26
+ const hasChildren = computed(() => !!props.link.children?.length)
27
+
28
+ // active detection (mirrors AppSidebar logic, recursive for parents)
29
+ function linkActive(link: LinkWithAction): boolean {
30
+ const linkPath = link.to
31
+ if (link.activeRoutes?.length) {
32
+ return link.activeRoutes.some(p =>
33
+ p === '/' ? route.path === p : route.path === p || route.path.startsWith(`${p}/`))
34
+ }
35
+ if (!linkPath) return false
36
+ if (linkPath === '/') return route.path === linkPath
37
+ return route.path === linkPath || route.path.startsWith(`${linkPath}/`)
38
+ }
39
+
40
+ const childActive = computed(() => props.link.children?.some(linkActive) ?? false)
41
+ const selfActive = computed(() => linkActive(props.link))
42
+ const isActive = computed(() => selfActive.value || (hasChildren.value && childActive.value))
43
+
44
+ // expanded/collapsed group state — auto-open when a child is active
45
+ const expanded = ref(childActive.value)
46
+ watch(childActive, v => { if (v) expanded.value = true })
47
+
48
+ function onParentClick() {
49
+ if (hasChildren.value) expanded.value = !expanded.value
50
+ else props.link.action?.()
51
+ }
52
+ </script>
53
+
54
+ <template>
55
+ <!-- LEAF link -->
56
+ <Btn
57
+ v-if="!hasChildren"
58
+ :title="!open && !isMobile ? resolveI18n(link.label) : ''"
59
+ fullWidth alignTxt="start" class="flex-shrink-0 px-075"
60
+ :class="{ 'nav-btn-active': selfActive }"
61
+ :style="{
62
+ backgroundColor: selfActive ? activeColor : bgColor,
63
+ color: selfActive ? 'white' : textColor,
64
+ }"
65
+ :to="link.to || '/'" @click="link.action"
66
+ >
67
+ <Icon :name="link.icon" size="1.2" />
68
+ <span class="nav-text">{{ resolveI18n(link.label) }}</span>
69
+ </Btn>
70
+
71
+ <!-- PARENT with children, EXPANDED sidebar -->
72
+ <div v-else-if="open || isMobile" class="sidebar-group w100p">
73
+ <Btn
74
+ fullWidth alignTxt="start" class="flex-shrink-0 px-075 sidebar-group-toggle"
75
+ :class="{ 'nav-btn-active': isActive }"
76
+ :style="{
77
+ backgroundColor: isActive ? activeColor : bgColor,
78
+ color: isActive ? 'white' : textColor,
79
+ }"
80
+ @click="onParentClick"
81
+ >
82
+ <Icon :name="link.icon" size="1.2" />
83
+ <span class="nav-text flex-grow">{{ resolveI18n(link.label) }}</span>
84
+ <Icon name="expand_more" size="1.1" class="nav-text sidebar-chevron" :class="{ 'sidebar-chevron-open': expanded }" />
85
+ </Btn>
86
+ <div class="sidebar-children" :class="{ 'sidebar-children-open': expanded }">
87
+ <Btn
88
+ v-for="child in link.children" :key="child.to || child.label"
89
+ fullWidth alignTxt="start" class="flex-shrink-0 ps-2 pe-075 sidebar-child"
90
+ :class="{ 'nav-btn-active': linkActive(child) }"
91
+ :style="{
92
+ backgroundColor: linkActive(child) ? activeColor : 'transparent',
93
+ color: linkActive(child) ? 'white' : textColor,
94
+ }"
95
+ :to="child.to || '/'" @click="child.action"
96
+ >
97
+ <Icon v-if="child.icon" :name="child.icon" size="1.05" class="opacity-7" />
98
+ <span class="nav-text">{{ resolveI18n(child.label) }}</span>
99
+ </Btn>
100
+ </div>
101
+ </div>
102
+
103
+ <!-- PARENT with children, COLLAPSED sidebar → hover popover flyout -->
104
+ <Dropdown
105
+ v-else
106
+ placement="right-start"
107
+ :triggers="['hover']"
108
+ card
109
+ class="w100p sidebar-flyout-trigger"
110
+ >
111
+ <template #trigger>
112
+ <Btn
113
+ fullWidth alignTxt="start" class="flex-shrink-0 px-075"
114
+ :class="{ 'nav-btn-active': isActive }"
115
+ :style="{
116
+ backgroundColor: isActive ? activeColor : bgColor,
117
+ color: isActive ? 'white' : textColor,
118
+ }"
119
+ >
120
+ <Icon :name="link.icon" size="1.2" />
121
+ <span class="nav-text">{{ resolveI18n(link.label) }}</span>
122
+ </Btn>
123
+ </template>
124
+ <!-- flyout content -->
125
+ <div class="sidebar-flyout p-05">
126
+ <p class="sidebar-flyout-label">{{ resolveI18n(link.label) }}</p>
127
+ <Btn
128
+ v-for="child in link.children" :key="child.to || child.label"
129
+ fullWidth alignTxt="start" thin class="flex-shrink-0 px-075 sidebar-flyout-item"
130
+ :class="{ 'nav-btn-active': linkActive(child) }"
131
+ :style="{
132
+ backgroundColor: linkActive(child) ? activeColor : 'transparent',
133
+ color: linkActive(child) ? 'white' : 'var(--bgl-text-color)',
134
+ }"
135
+ :to="child.to || '/'" @click="child.action"
136
+ >
137
+ <Icon v-if="child.icon" :name="child.icon" size="1.05" class="opacity-7" />
138
+ <span>{{ resolveI18n(child.label) }}</span>
139
+ </Btn>
140
+ </div>
141
+ </Dropdown>
142
+ </template>
143
+
144
+ <style scoped>
145
+ .sidebar-chevron {
146
+ margin-inline-start: auto;
147
+ transition: transform 0.2s ease;
148
+ opacity: 0.6;
149
+ }
150
+ .sidebar-chevron-open {
151
+ transform: rotate(180deg);
152
+ }
153
+ .sidebar-children {
154
+ display: grid;
155
+ grid-template-rows: 0fr;
156
+ transition: grid-template-rows 0.25s ease;
157
+ overflow: hidden;
158
+ }
159
+ .sidebar-children-open {
160
+ grid-template-rows: 1fr;
161
+ }
162
+ .sidebar-children > * {
163
+ min-height: 0;
164
+ }
165
+ .sidebar-child {
166
+ font-size: 0.85rem;
167
+ }
168
+ .sidebar-flyout {
169
+ min-width: 180px;
170
+ display: flex;
171
+ flex-direction: column;
172
+ gap: 2px;
173
+ }
174
+ .sidebar-flyout-label {
175
+ margin: 0;
176
+ padding: 0.25rem 0.5rem 0.375rem;
177
+ font-size: 0.6875rem;
178
+ font-weight: 600;
179
+ text-transform: uppercase;
180
+ letter-spacing: 0.05em;
181
+ opacity: 0.45;
182
+ }
183
+ .sidebar-flyout-item {
184
+ border-radius: var(--bgl-btn-border-radius);
185
+ }
186
+ </style>
@@ -4,6 +4,7 @@ export { default as AppSidebar } from './AppSidebar.vue'
4
4
  export { useAppLayout } from './appLayoutContext'
5
5
  export type { AppLayoutContext } from './appLayoutContext'
6
6
  export { default as BottomMenu } from './BottomMenu.vue'
7
+ export { default as Divider } from './Divider.vue'
7
8
  export { default as Layout } from './Layout.vue'
8
9
  export { default as Panel } from './Panel.vue'
9
10
  export { default as Resizable } from './Resizable.vue'
@@ -1,4 +1,6 @@
1
1
  export { default as pattern } from './pattern'
2
+ export { default as reveal } from './reveal'
3
+ export type { RevealOptions } from './reveal'
2
4
  export { default as ripple } from './ripple'
3
5
  export { vResize } from './vResize'
4
6
  export type { ResizeEvent, ResizeOptions } from './vResize'
@@ -0,0 +1,78 @@
1
+ import type { Directive, DirectiveBinding } from 'vue'
2
+
3
+ /**
4
+ * v-reveal — animate an element into view on first scroll-intersection.
5
+ *
6
+ * <div v-reveal>…</div> // default: fade + rise
7
+ * <div v-reveal="'left'">…</div> // slide from a direction
8
+ * <div v-reveal="{ y: 'up', delay: 120 }">…</div>
9
+ * <Card v-for="…" v-reveal="{ delay: i * 80 }" /> // stagger
10
+ *
11
+ * Adds `.bgl-reveal` immediately (hidden, pre-transformed) and toggles
12
+ * `.bgl-reveal-in` once the element enters the viewport. Honors
13
+ * `prefers-reduced-motion` (shows instantly, no transform). All visual tuning
14
+ * lives in CSS (motion.css) via data-attributes, so it's themeable and cheap.
15
+ */
16
+
17
+ type Dir = 'up' | 'down' | 'left' | 'right' | 'none'
18
+
19
+ interface RevealOptions {
20
+ /** Direction the element travels in from. Default 'up'. */
21
+ y?: Dir
22
+ /** Stagger / entrance delay in ms. */
23
+ delay?: number
24
+ /** Animate only once (default) or re-run when scrolled away and back. */
25
+ once?: boolean
26
+ /** 0–1 visibility threshold before triggering. Default 0.12. */
27
+ threshold?: number
28
+ }
29
+
30
+ const prefersReducedMotion = () =>
31
+ typeof window !== 'undefined'
32
+ && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches
33
+
34
+ function parse(value: RevealOptions | Dir | undefined): Required<RevealOptions> {
35
+ const base: Required<RevealOptions> = { y: 'up', delay: 0, once: true, threshold: 0.12 }
36
+ if (!value) { return base }
37
+ if (typeof value === 'string') { return { ...base, y: value } }
38
+ return { ...base, ...value }
39
+ }
40
+
41
+ const observers = new WeakMap<HTMLElement, IntersectionObserver>()
42
+
43
+ const reveal: Directive<HTMLElement, RevealOptions | Dir> = {
44
+ mounted(el, binding: DirectiveBinding<RevealOptions | Dir>) {
45
+ const opts = parse(binding.value)
46
+
47
+ // Reduced motion: reveal instantly, skip all transforms/observers.
48
+ if (prefersReducedMotion()) { return }
49
+
50
+ el.classList.add('bgl-reveal')
51
+ if (opts.y !== 'none') { el.dataset.revealDir = opts.y }
52
+ if (opts.delay) { el.style.setProperty('--bgl-reveal-delay', `${opts.delay}ms`) }
53
+
54
+ const observer = new IntersectionObserver((entries) => {
55
+ for (const entry of entries) {
56
+ if (entry.isIntersecting) {
57
+ el.classList.add('bgl-reveal-in')
58
+ if (opts.once) { observer.unobserve(el) }
59
+ } else if (!opts.once) {
60
+ el.classList.remove('bgl-reveal-in')
61
+ }
62
+ }
63
+ }, { threshold: opts.threshold, rootMargin: '0px 0px -8% 0px' })
64
+
65
+ observer.observe(el)
66
+ observers.set(el, observer)
67
+ },
68
+ unmounted(el) {
69
+ observers.get(el)?.disconnect()
70
+ observers.delete(el)
71
+ },
72
+ getSSRProps() {
73
+ return {}
74
+ },
75
+ }
76
+
77
+ export default reveal
78
+ export type { RevealOptions }
@@ -5,7 +5,7 @@ import type { BagelToastOptions } from './useToast'
5
5
  import FloatingVue from 'floating-vue'
6
6
  import lightboxPlugin from '../components/lightbox/index'
7
7
  import { DialogPlugin } from '../dialog/useDialog'
8
- import { ripple, pattern } from '../directives'
8
+ import { ripple, pattern, reveal } from '../directives'
9
9
  import { createI18n, getI18n } from '../i18n'
10
10
  import clickOutside from '../utils/clickOutside'
11
11
  import { ToastPlugin } from './useToast'
@@ -23,6 +23,7 @@ export const BagelVue: Plugin<BagelOptions> = {
23
23
  app.directive('click-outside', clickOutside)
24
24
  app.directive('ripple', ripple)
25
25
  app.directive('pattern', pattern)
26
+ app.directive('reveal', reveal)
26
27
 
27
28
  // Install UI plugins
28
29
  app.use(lightboxPlugin)
@@ -767,6 +767,17 @@
767
767
  filter: saturate(200%) !important;
768
768
  }
769
769
 
770
+ /* Glass — frosted translucent surface for use over photos / gradients / dark
771
+ * heroes (nav pills, eyebrow chips, overlays). Light-on-dark by default; flip
772
+ * with --bgl-glass-bg / --bgl-glass-color / --bgl-glass-border. */
773
+ .glass {
774
+ background-color: var(--bgl-glass-bg, rgba(255, 255, 255, 0.18)) !important;
775
+ color: var(--bgl-glass-color, #fff) !important;
776
+ border: 1px solid var(--bgl-glass-border, rgba(255, 255, 255, 0.28)) !important;
777
+ backdrop-filter: blur(8px);
778
+ -webkit-backdrop-filter: blur(8px);
779
+ }
780
+
770
781
  /* Backdrop Filter */
771
782
  .backdrop-blur-none {
772
783
  backdrop-filter: blur(0) !important;
@@ -21,6 +21,7 @@
21
21
  @import "buttons.css";
22
22
  @import "scrollbar.css";
23
23
  @import "transitions.css";
24
+ @import "motion.css";
24
25
  @import "loginCard.css";
25
26
  @import "app-layout.css";
26
27
 
@@ -26,6 +26,11 @@
26
26
  filter: invert(1);
27
27
  } */
28
28
 
29
+ /* `:root.bgl-dark-mode` (the class lives on <html>) raises specificity to
30
+ (0,2,0) so these tokens win over any project that redefines colors in its
31
+ own `:root {}` (e.g. udi sets --bgl-blue: #7ad3f7). The bare .bgl-dark-mode
32
+ is kept as a fallback in case the class is applied to a non-root element. */
33
+ :root.bgl-dark-mode,
29
34
  .bgl-dark-mode {
30
35
  /* ---- Dark palette (raw values) ---------------------------------------- */
31
36
  --bgl-dm-bg: #121317;
@@ -41,14 +46,31 @@
41
46
  /* primary text */
42
47
  --bgl-dm-text-muted: #9a9b9d;
43
48
  /* secondary text */
44
- --bgl-dm-border: #383a3f;
45
- /* borders / dividers */
49
+ --bgl-dm-border: rgba(255, 255, 255, 0.12);
50
+ /* borders / dividers — translucent light, mirroring the light-mode
51
+ #00000020 (translucent dark). Blends over any dark surface (bg, card,
52
+ input) instead of being a fixed opaque gray. */
46
53
 
47
54
  /* ---- Brand (slightly adjusted for dark backgrounds) ------------------- */
48
55
  --bgl-primary: #4f7cff !important;
49
56
  --bgl-primary-tint: #4f7cff33;
50
57
  --bgl-primary-light: #1a2236;
51
58
 
59
+ /* ---- Status / accent colors (tuned for dark backgrounds) ------------- *
60
+ * The light-mode bases (e.g. blue #2e5bff, green #75c98f, red #ed6c6f,
61
+ * yellow #ffbb00) are either too dark/saturated or too pastel to read well
62
+ * on a dark surface. Shift them lighter and slightly desaturated so they
63
+ * pop against the dark UI without glowing. Tints follow the same hue at low
64
+ * opacity for soft fills/badges. */
65
+ --bgl-blue: #5b8cff;
66
+ --bgl-blue-tint: rgba(91, 140, 255, 0.18);
67
+ --bgl-green: #5dd49b;
68
+ --bgl-green-tint: rgba(93, 212, 155, 0.18);
69
+ --bgl-red: #f47174;
70
+ --bgl-red-tint: rgba(244, 113, 116, 0.18);
71
+ --bgl-yellow: #ffc94d;
72
+ --bgl-yellow-tint: rgba(255, 201, 77, 0.18);
73
+
52
74
  /* ---- Surfaces / backgrounds ------------------------------------------ */
53
75
  --bgl-bg: var(--bgl-dm-bg);
54
76
  --bgl-box-bg: var(--bgl-dm-surface);
@@ -121,6 +143,57 @@
121
143
  --bgl-gray-10: color-mix(in oklab, white 4%, var(--bgl-dm-surface));
122
144
  --bgl-gray-20: color-mix(in oklab, white 8%, var(--bgl-dm-surface));
123
145
  --bgl-gray-30: color-mix(in oklab, white 13%, var(--bgl-dm-surface));
146
+ /* Mid/high end of the scale: in light mode these go DARKER as the number
147
+ rises (gray-80 ≈ a strong gray used for lines/dividers). On dark they must
148
+ instead get LIGHTER so "higher number = more contrast vs the surface"
149
+ still holds — otherwise gray-80 lines vanish on the dark background.
150
+ Fixes calendar hour-lines and any other gray-40..90 separators at once. */
151
+ --bgl-gray-40: color-mix(in oklab, white 18%, var(--bgl-dm-surface));
152
+ --bgl-gray-50: color-mix(in oklab, white 24%, var(--bgl-dm-surface));
153
+ --bgl-gray-60: color-mix(in oklab, white 32%, var(--bgl-dm-surface));
154
+ --bgl-gray-70: color-mix(in oklab, white 42%, var(--bgl-dm-surface));
155
+ --bgl-gray-80: color-mix(in oklab, white 54%, var(--bgl-dm-surface));
156
+ --bgl-gray-90: color-mix(in oklab, white 70%, var(--bgl-dm-surface));
157
+
158
+ /* ---- Colored tint scale (10/20/30) ----------------------------------- *
159
+ * In light mode --bgl-{color}-10/20/30 are near-white pastel washes (e.g.
160
+ * green-30 = 70% white + green) used as soft fills — chat bubbles, badges,
161
+ * highlighted rows. On a dark UI those pale pastels glow and force dark text
162
+ * to be unreadable. Remap each to a low-opacity wash of its OWN base color
163
+ * OVER the dark surface, so the fill stays subtly colored but dark, and the
164
+ * light text token reads on top. Higher number = a touch more color.
165
+ * Because these sit in the high-specificity :root.bgl-dark-mode block, they
166
+ * also override any project that hard-sets e.g. --bgl-blue-30 in :root. */
167
+ --bgl-blue-10: color-mix(in oklab, var(--bgl-blue) 10%, var(--bgl-dm-surface));
168
+ --bgl-blue-20: color-mix(in oklab, var(--bgl-blue) 16%, var(--bgl-dm-surface));
169
+ --bgl-blue-30: color-mix(in oklab, var(--bgl-blue) 24%, var(--bgl-dm-surface));
170
+ --bgl-green-10: color-mix(in oklab, var(--bgl-green) 10%, var(--bgl-dm-surface));
171
+ --bgl-green-20: color-mix(in oklab, var(--bgl-green) 16%, var(--bgl-dm-surface));
172
+ --bgl-green-30: color-mix(in oklab, var(--bgl-green) 24%, var(--bgl-dm-surface));
173
+ --bgl-red-10: color-mix(in oklab, var(--bgl-red) 10%, var(--bgl-dm-surface));
174
+ --bgl-red-20: color-mix(in oklab, var(--bgl-red) 16%, var(--bgl-dm-surface));
175
+ --bgl-red-30: color-mix(in oklab, var(--bgl-red) 24%, var(--bgl-dm-surface));
176
+ --bgl-yellow-10: color-mix(in oklab, var(--bgl-yellow) 10%, var(--bgl-dm-surface));
177
+ --bgl-yellow-20: color-mix(in oklab, var(--bgl-yellow) 16%, var(--bgl-dm-surface));
178
+ --bgl-yellow-30: color-mix(in oklab, var(--bgl-yellow) 24%, var(--bgl-dm-surface));
179
+ --bgl-purple-10: color-mix(in oklab, var(--bgl-purple) 10%, var(--bgl-dm-surface));
180
+ --bgl-purple-20: color-mix(in oklab, var(--bgl-purple) 16%, var(--bgl-dm-surface));
181
+ --bgl-purple-30: color-mix(in oklab, var(--bgl-purple) 24%, var(--bgl-dm-surface));
182
+ --bgl-brown-10: color-mix(in oklab, var(--bgl-brown) 10%, var(--bgl-dm-surface));
183
+ --bgl-brown-20: color-mix(in oklab, var(--bgl-brown) 16%, var(--bgl-dm-surface));
184
+ --bgl-brown-30: color-mix(in oklab, var(--bgl-brown) 24%, var(--bgl-dm-surface));
185
+ --bgl-orange-10: color-mix(in oklab, var(--bgl-orange) 10%, var(--bgl-dm-surface));
186
+ --bgl-orange-20: color-mix(in oklab, var(--bgl-orange) 16%, var(--bgl-dm-surface));
187
+ --bgl-orange-30: color-mix(in oklab, var(--bgl-orange) 24%, var(--bgl-dm-surface));
188
+ --bgl-turquoise-10: color-mix(in oklab, var(--bgl-turquoise) 10%, var(--bgl-dm-surface));
189
+ --bgl-turquoise-20: color-mix(in oklab, var(--bgl-turquoise) 16%, var(--bgl-dm-surface));
190
+ --bgl-turquoise-30: color-mix(in oklab, var(--bgl-turquoise) 24%, var(--bgl-dm-surface));
191
+ --bgl-pink-10: color-mix(in oklab, var(--bgl-pink) 10%, var(--bgl-dm-surface));
192
+ --bgl-pink-20: color-mix(in oklab, var(--bgl-pink) 16%, var(--bgl-dm-surface));
193
+ --bgl-pink-30: color-mix(in oklab, var(--bgl-pink) 24%, var(--bgl-dm-surface));
194
+ --bgl-primary-10: color-mix(in oklab, var(--bgl-primary) 10%, var(--bgl-dm-surface));
195
+ --bgl-primary-20: color-mix(in oklab, var(--bgl-primary) 16%, var(--bgl-dm-surface));
196
+ --bgl-primary-30: color-mix(in oklab, var(--bgl-primary) 24%, var(--bgl-dm-surface));
124
197
  }
125
198
 
126
199
  /* Native form controls (date/time pickers) render dark glyphs that are
@@ -156,6 +229,25 @@
156
229
  border-color: var(--bgl-dm-border);
157
230
  }
158
231
 
232
+ /* .pair-white (e.g. <Btn color="white">) and .pair-gray are neutral/secondary
233
+ fills on light UIs (literal white / a light #b7b7b7 gray). On dark they stay
234
+ glaring light blocks, so for the FILLED variant turn them into a raised dark
235
+ surface with light text and a hairline border (a proper secondary button).
236
+ Flat pills and border pills keep their own treatment (they only tint text /
237
+ outline, not the fill), so we exclude them. */
238
+ .bgl-dark-mode .pair-white:not(.bgl_flatPill):not(.bgl_pill-border),
239
+ .bgl-dark-mode .pair-gray:not(.bgl_flatPill):not(.bgl_pill-border) {
240
+ background-color: var(--bgl-dm-surface-2);
241
+ color: var(--bgl-dm-text);
242
+ border-color: var(--bgl-dm-border);
243
+ }
244
+ /* Hover/active on the dark secondary button: lift one step instead of the
245
+ default lighten-filter which would wash the light original out. */
246
+ .bgl-dark-mode .pair-white:not(.bgl_flatPill):not(.bgl_pill-border):hover,
247
+ .bgl-dark-mode .pair-gray:not(.bgl_flatPill):not(.bgl_pill-border):hover {
248
+ background-color: color-mix(in oklab, white 8%, var(--bgl-dm-surface-2));
249
+ }
250
+
159
251
  /* ----------------------------------------------------------------------------
160
252
  * Absolute color utilities (bg-white / bg-black …)
161
253
  * ----------------------------------------------------------------------------