@leavepulse/ui 0.31.0 → 0.32.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 (157) hide show
  1. package/dist/assets/{LpDropzone-DVDC-ABU.css → LpDropzone-BBoWvi-J.css} +12 -12
  2. package/dist/assets/{LpImageEditor-DqP5JKCy.css → LpImageEditor-BsSwej47.css} +6 -6
  3. package/dist/chunks/{LpAddressInput-BDkp3-Bp.js → LpAddressInput-C35TsJXt.js} +19 -14
  4. package/dist/chunks/{LpAppShell-Chz8Vbtu.js → LpAppShell-lVvpENMH.js} +18 -13
  5. package/dist/chunks/{LpBreadcrumbs-5uvViCXi.js → LpBreadcrumbs-p4jHe_V-.js} +9 -7
  6. package/dist/chunks/{LpCalendar-B7ZL0ty3.js → LpCalendar-B7Ko5RdR.js} +12 -7
  7. package/dist/chunks/{LpCard-D0M2ZCnB.js → LpCard-1SeUYPBT.js} +36 -9
  8. package/dist/chunks/{LpCheckbox-BO0zuuPh.js → LpCheckbox-CVccKYaH.js} +9 -4
  9. package/dist/chunks/{LpCodeBlock-BWX1KZwk.js → LpCodeBlock-Bo0B4TFR.js} +37 -32
  10. package/dist/chunks/{LpDatePicker-dY2q4dr4.js → LpDatePicker-6JFXllj5.js} +1 -1
  11. package/dist/chunks/{LpDisclosure-B4kVAWfg.js → LpDisclosure-y7wDBgLa.js} +11 -6
  12. package/dist/chunks/{LpEmptyState-BV3q7Qyi.js → LpEmptyState-Dsdqz1Wj.js} +13 -5
  13. package/dist/chunks/{LpFileTree-DfFqY__1.js → LpFileTree-C-NFRT0H.js} +19 -14
  14. package/dist/chunks/{LpFileTreeNode-COchUMNn.js → LpFileTreeNode-DNJUJiiB.js} +13 -6
  15. package/dist/chunks/{LpFormField-fV_2ZdCk.js → LpFormField-CazBc2-a.js} +14 -9
  16. package/dist/chunks/{LpInput-Bt4J71xn.js → LpInput-9EXWUM9B.js} +1 -1
  17. package/dist/chunks/{LpLightbox-Ci35Y_xP.js → LpLightbox-Cixfxezw.js} +29 -6
  18. package/dist/chunks/{LpLink-BHZ1CLlF.js → LpLink-DCND-zyu.js} +16 -9
  19. package/dist/chunks/{LpLogViewer-TSRyt3t9.js → LpLogViewer-y53ZqSVq.js} +20 -10
  20. package/dist/chunks/{LpMapPicker-CXvoTrAe.js → LpMapPicker-ul4oYno6.js} +14 -9
  21. package/dist/chunks/LpMediaGallery-t1z3bQcY.js +187 -0
  22. package/dist/chunks/{LpNotificationBell-gdvQY89m.js → LpNotificationBell-lHePf89W.js} +1 -1
  23. package/dist/chunks/{LpNumberField-NPE8Bumx.js → LpNumberField-CWuX08iX.js} +12 -5
  24. package/dist/chunks/{LpOtpInput-DqaT0Z75.js → LpOtpInput-y2v-pmiN.js} +11 -6
  25. package/dist/chunks/{LpPagination-CeGuNe_q.js → LpPagination-5FxjmmB5.js} +21 -19
  26. package/dist/chunks/{LpPasswordInput-BqMAc5HA.js → LpPasswordInput--sjYCkLS.js} +1 -1
  27. package/dist/chunks/{LpPhoneInput-NXXjciJJ.js → LpPhoneInput-BDSKVl_z.js} +16 -9
  28. package/dist/chunks/{LpPie-BL1vTDSM.js → LpPie-c2-q4DJu.js} +14 -8
  29. package/dist/chunks/{LpRadio-CL-pI0_O.js → LpRadio-B5AfqTpZ.js} +13 -5
  30. package/dist/chunks/{LpRadioGroup-B4yDTmi6.js → LpRadioGroup-xPIuVDO_.js} +12 -5
  31. package/dist/chunks/{LpSegmented-BmpanyAo.js → LpSegmented-fV0Sk92p.js} +16 -6
  32. package/dist/chunks/{LpSidebar-BiWM_lDB.js → LpSidebar-DcYavim2.js} +2 -2
  33. package/dist/chunks/{LpSidebarNav-CsgeuGAu.js → LpSidebarNav-CqwvT4H8.js} +21 -16
  34. package/dist/chunks/{LpSlider-B1SF3pRV.js → LpSlider-BwNIBkAK.js} +12 -5
  35. package/dist/chunks/{LpStat-CDanpzU0.js → LpStat-CQaW8R--.js} +23 -18
  36. package/dist/chunks/{LpStepper-Wtl-6nUh.js → LpStepper-Bk-XaQYU.js} +15 -10
  37. package/dist/chunks/{LpSwitch-v6fnccSM.js → LpSwitch-xgnRU2k0.js} +12 -5
  38. package/dist/chunks/{LpTextarea-BMIOfw5l.js → LpTextarea-t7F6gv8E.js} +1 -1
  39. package/dist/chunks/{LpUptimeBar-DcQ8HikG.js → LpUptimeBar-DvAMno0S.js} +26 -21
  40. package/dist/component-names.d.ts +1 -1
  41. package/dist/components/LpAddressInput.vue.d.ts +34 -17
  42. package/dist/components/LpAddressInput.vue.js +1 -1
  43. package/dist/components/LpAppShell.vue.d.ts +65 -31
  44. package/dist/components/LpAppShell.vue.js +1 -1
  45. package/dist/components/LpBreadcrumbs.vue.d.ts +12 -6
  46. package/dist/components/LpBreadcrumbs.vue.js +1 -1
  47. package/dist/components/LpCalendar.vue.d.ts +15 -6
  48. package/dist/components/LpCalendar.vue.js +1 -1
  49. package/dist/components/LpCard.vue.d.ts +26 -0
  50. package/dist/components/LpCard.vue.js +1 -1
  51. package/dist/components/LpChart.vue.d.ts +54 -12
  52. package/dist/components/LpChart.vue.js +15 -9
  53. package/dist/components/LpCheckbox.vue.d.ts +16 -12
  54. package/dist/components/LpCheckbox.vue.js +1 -1
  55. package/dist/components/LpCodeBlock.vue.d.ts +25 -6
  56. package/dist/components/LpCodeBlock.vue.js +1 -1
  57. package/dist/components/LpDatePicker.vue.js +1 -1
  58. package/dist/components/LpDisclosure.vue.d.ts +24 -14
  59. package/dist/components/LpDisclosure.vue.js +1 -1
  60. package/dist/components/LpDropzone.vue.d.ts +38 -14
  61. package/dist/components/LpDropzone.vue.js +17 -11
  62. package/dist/components/LpEmptyState.vue.d.ts +15 -10
  63. package/dist/components/LpEmptyState.vue.js +1 -1
  64. package/dist/components/LpFileTree.vue.d.ts +51 -31
  65. package/dist/components/LpFileTree.vue.js +1 -1
  66. package/dist/components/LpFileTreeNode.vue.d.ts +31 -16
  67. package/dist/components/LpFileTreeNode.vue.js +1 -1
  68. package/dist/components/LpFormField.vue.d.ts +14 -13
  69. package/dist/components/LpFormField.vue.js +1 -1
  70. package/dist/components/LpImageEditor.vue.d.ts +33 -10
  71. package/dist/components/LpImageEditor.vue.js +13 -7
  72. package/dist/components/LpInput.vue.js +1 -1
  73. package/dist/components/LpLightbox.vue.js +1 -1
  74. package/dist/components/LpLink.vue.d.ts +46 -11
  75. package/dist/components/LpLink.vue.js +1 -1
  76. package/dist/components/LpLogViewer.vue.d.ts +63 -6
  77. package/dist/components/LpLogViewer.vue.js +1 -1
  78. package/dist/components/LpMapPicker.vue.d.ts +25 -10
  79. package/dist/components/LpMapPicker.vue.js +1 -1
  80. package/dist/components/LpMediaGallery.vue.d.ts +76 -0
  81. package/dist/components/LpMediaGallery.vue.js +4 -0
  82. package/dist/components/LpNotificationBell.vue.js +1 -1
  83. package/dist/components/LpNumberField.vue.d.ts +12 -6
  84. package/dist/components/LpNumberField.vue.js +1 -1
  85. package/dist/components/LpOtpInput.vue.d.ts +18 -6
  86. package/dist/components/LpOtpInput.vue.js +1 -1
  87. package/dist/components/LpPagination.vue.d.ts +16 -6
  88. package/dist/components/LpPagination.vue.js +1 -1
  89. package/dist/components/LpPasswordInput.vue.js +1 -1
  90. package/dist/components/LpPhoneInput.vue.d.ts +18 -6
  91. package/dist/components/LpPhoneInput.vue.js +1 -1
  92. package/dist/components/LpPie.vue.d.ts +96 -34
  93. package/dist/components/LpPie.vue.js +1 -1
  94. package/dist/components/LpRadio.vue.d.ts +10 -10
  95. package/dist/components/LpRadio.vue.js +1 -1
  96. package/dist/components/LpRadioGroup.vue.d.ts +12 -12
  97. package/dist/components/LpRadioGroup.vue.js +1 -1
  98. package/dist/components/LpSegmented.vue.d.ts +12 -6
  99. package/dist/components/LpSegmented.vue.js +1 -1
  100. package/dist/components/LpSidebar.vue.js +1 -1
  101. package/dist/components/LpSidebarNav.vue.d.ts +28 -17
  102. package/dist/components/LpSidebarNav.vue.js +1 -1
  103. package/dist/components/LpSlider.vue.d.ts +13 -6
  104. package/dist/components/LpSlider.vue.js +1 -1
  105. package/dist/components/LpStat.vue.d.ts +35 -11
  106. package/dist/components/LpStat.vue.js +1 -1
  107. package/dist/components/LpStepper.vue.d.ts +17 -5
  108. package/dist/components/LpStepper.vue.js +1 -1
  109. package/dist/components/LpSwitch.vue.d.ts +8 -6
  110. package/dist/components/LpSwitch.vue.js +1 -1
  111. package/dist/components/LpTable.vue.js +1 -1
  112. package/dist/components/LpTextarea.vue.js +1 -1
  113. package/dist/components/LpUptimeBar.vue.d.ts +21 -5
  114. package/dist/components/LpUptimeBar.vue.js +1 -1
  115. package/dist/composables/useMergedClass.d.ts +20 -0
  116. package/dist/composables/useMergedClass.js +11 -0
  117. package/dist/index.d.ts +2 -0
  118. package/dist/index.js +95 -89
  119. package/package.json +1 -3
  120. package/src/component-names.ts +1 -0
  121. package/src/components/LpAddressInput.vue +16 -1
  122. package/src/components/LpAppShell.vue +16 -1
  123. package/src/components/LpBreadcrumbs.vue +16 -1
  124. package/src/components/LpCalendar.vue +15 -1
  125. package/src/components/LpCard.vue +50 -5
  126. package/src/components/LpChart.vue +16 -1
  127. package/src/components/LpCheckbox.vue +16 -1
  128. package/src/components/LpCodeBlock.vue +16 -1
  129. package/src/components/LpDisclosure.vue +15 -1
  130. package/src/components/LpDropzone.vue +21 -6
  131. package/src/components/LpEmptyState.vue +21 -3
  132. package/src/components/LpFileTree.vue +16 -1
  133. package/src/components/LpFileTreeNode.vue +15 -1
  134. package/src/components/LpFormField.vue +16 -1
  135. package/src/components/LpImageEditor.vue +16 -1
  136. package/src/components/LpLightbox.vue +54 -2
  137. package/src/components/LpLink.vue +25 -9
  138. package/src/components/LpLogViewer.vue +20 -2
  139. package/src/components/LpMapPicker.vue +15 -1
  140. package/src/components/LpMediaGallery.vue +280 -0
  141. package/src/components/LpNumberField.vue +15 -1
  142. package/src/components/LpOtpInput.vue +15 -1
  143. package/src/components/LpPagination.vue +16 -1
  144. package/src/components/LpPhoneInput.vue +23 -8
  145. package/src/components/LpPie.vue +16 -1
  146. package/src/components/LpRadio.vue +20 -3
  147. package/src/components/LpRadioGroup.vue +15 -1
  148. package/src/components/LpSegmented.vue +21 -2
  149. package/src/components/LpSidebarNav.vue +16 -1
  150. package/src/components/LpSlider.vue +15 -1
  151. package/src/components/LpStat.vue +16 -1
  152. package/src/components/LpStepper.vue +16 -1
  153. package/src/components/LpSwitch.vue +15 -1
  154. package/src/components/LpUptimeBar.vue +16 -1
  155. package/src/composables/useMergedClass.ts +30 -0
  156. package/src/index.ts +2 -0
  157. package/src/tokens/tokens.css +29 -0
@@ -1,7 +1,15 @@
1
+ <script lang="ts">
2
+ // The consumer's `class` is merged into the root's own (below) rather than
3
+ // appended to it, so opt out of the automatic pass-through that would add it
4
+ // a second time, unmerged.
5
+ export default { inheritAttrs: false }
6
+ </script>
7
+
1
8
  <script setup lang="ts">
2
9
  // Single-value slider (reka Slider). reka models value as an array; we expose
3
10
  // a plain number for the common single-thumb case. Themed via tokens.
4
11
  import { SliderRange, SliderRoot, SliderThumb, SliderTrack } from "reka-ui"
12
+ import { useMergedAttrs } from "../composables/useMergedClass"
5
13
 
6
14
  const props = withDefaults(
7
15
  defineProps<{
@@ -29,6 +37,11 @@ function onUpdate(v: number[] | undefined) {
29
37
  function onCommit(v: number[] | undefined) {
30
38
  if (v && v.length) emit("change", v[0])
31
39
  }
40
+
41
+ // A slider is full-width by default; putting one in a toolbar means constraining it, and a fixed h-5 has to yield to a denser row.
42
+ const { class: rootClass, attrs: rest } = useMergedAttrs(
43
+ "relative flex h-5 w-full touch-none select-none items-center data-[disabled]:opacity-55",
44
+ )
32
45
  </script>
33
46
 
34
47
  <template>
@@ -38,7 +51,8 @@ function onCommit(v: number[] | undefined) {
38
51
  :max="max"
39
52
  :step="step"
40
53
  :disabled="disabled"
41
- class="relative flex h-5 w-full touch-none select-none items-center data-[disabled]:opacity-55"
54
+ :class="rootClass"
55
+ v-bind="rest"
42
56
  @update:model-value="onUpdate"
43
57
  @value-commit="onCommit"
44
58
  >
@@ -1,9 +1,17 @@
1
+ <script lang="ts">
2
+ // The consumer's `class` is merged into the root's own (below) rather than
3
+ // appended to it, so opt out of the automatic pass-through that would add it
4
+ // a second time, unmerged.
5
+ export default { inheritAttrs: false }
6
+ </script>
7
+
1
8
  <script setup lang="ts">
2
9
  // Stat tile: dot + caps label, big mono value, optional delta/trend badge and a
3
10
  // subtitle. A product pattern, fully token-driven — re-skins with the theme.
4
11
  import { computed } from "vue"
5
12
  import LpIcon from "./LpIcon.vue"
6
13
  import LpNumberFlow from "./LpNumberFlow.vue"
14
+ import { useMergedAttrs } from "../composables/useMergedClass"
7
15
 
8
16
  const props = withDefaults(
9
17
  defineProps<{
@@ -86,10 +94,17 @@ const deltaDecimals = computed(() => {
86
94
  const [, fraction] = String(props.delta).split(".")
87
95
  return Math.min(fraction?.length ?? 0, 4)
88
96
  })
97
+
98
+ // A stat tile spaces its label and value on a fixed gap; a dense KPI row needs that tighter.
99
+ const { class: rootClass, attrs: rest } = useMergedAttrs(
100
+ "flex flex-col gap-2",
101
+ )
89
102
  </script>
90
103
 
91
104
  <template>
92
- <div class="flex flex-col gap-2">
105
+ <div
106
+ :class="rootClass"
107
+ v-bind="rest">
93
108
  <div class="flex items-center gap-2">
94
109
  <span v-if="online" class="size-1.5 rounded-full bg-action" />
95
110
  <LpIcon v-if="icon" :name="icon" :size="14" class="text-muted" />
@@ -1,6 +1,14 @@
1
+ <script lang="ts">
2
+ // The consumer's `class` is merged into the root's own (below) rather than
3
+ // appended to it, so opt out of the automatic pass-through that would add it
4
+ // a second time, unmerged.
5
+ export default { inheritAttrs: false }
6
+ </script>
7
+
1
8
  <script setup lang="ts">
2
9
  import { computed } from "vue"
3
10
  import LpIcon from "./LpIcon.vue"
11
+ import { useMergedAttrs } from "../composables/useMergedClass"
4
12
 
5
13
  export interface Step {
6
14
  label: string
@@ -38,10 +46,17 @@ function isCompleted(step: Step, i: number): boolean {
38
46
  function isCurrent(i: number): boolean {
39
47
  return i === currentIndex.value
40
48
  }
49
+
50
+ // The stepper spaces its steps evenly; a compact wizard header needs that spacing under the consumer's control.
51
+ const { class: rootClass, attrs: rest } = useMergedAttrs(
52
+ "flex items-center gap-2",
53
+ )
41
54
  </script>
42
55
 
43
56
  <template>
44
- <ol class="flex items-center gap-2">
57
+ <ol
58
+ :class="rootClass"
59
+ v-bind="rest">
45
60
  <li
46
61
  v-for="(step, i) in steps"
47
62
  :key="step.key ?? i"
@@ -1,15 +1,29 @@
1
+ <script lang="ts">
2
+ // The consumer's `class` is merged into the root's own (below) rather than
3
+ // appended to it, so opt out of the automatic pass-through that would add it
4
+ // a second time, unmerged.
5
+ export default { inheritAttrs: false }
6
+ </script>
7
+
1
8
  <script setup lang="ts">
2
9
  import { SwitchRoot, SwitchThumb } from "reka-ui"
10
+ import { useMergedAttrs } from "../composables/useMergedClass"
3
11
 
4
12
  defineProps<{ modelValue?: boolean; disabled?: boolean }>()
5
13
  defineEmits<{ (e: "update:modelValue", value: boolean): void }>()
14
+
15
+ // A switch has a fixed track size, so a consumer asking for a bigger one must not have to depend on stylesheet order to get it.
16
+ const { class: rootClass, attrs: rest } = useMergedAttrs(
17
+ "group inline-flex h-5 w-9 shrink-0 items-center rounded-pill border border-line bg-surface-soft px-0.5 outline-none transition-colors duration-[var(--duration-fast)] focus-visible:ring-2 focus-visible:ring-ring data-[state=checked]:border-transparent data-[state=checked]:bg-brand disabled:cursor-not-allowed disabled:opacity-55",
18
+ )
6
19
  </script>
7
20
 
8
21
  <template>
9
22
  <SwitchRoot
10
23
  :model-value="modelValue"
11
24
  :disabled="disabled"
12
- class="group inline-flex h-5 w-9 shrink-0 items-center rounded-pill border border-line bg-surface-soft px-0.5 outline-none transition-colors duration-[var(--duration-fast)] focus-visible:ring-2 focus-visible:ring-ring data-[state=checked]:border-transparent data-[state=checked]:bg-brand disabled:cursor-not-allowed disabled:opacity-55"
25
+ :class="rootClass"
26
+ v-bind="rest"
13
27
  @update:model-value="(v) => $emit('update:modelValue', v)"
14
28
  >
15
29
  <SwitchThumb
@@ -1,8 +1,16 @@
1
+ <script lang="ts">
2
+ // The consumer's `class` is merged into the root's own (below) rather than
3
+ // appended to it, so opt out of the automatic pass-through that would add it
4
+ // a second time, unmerged.
5
+ export default { inheritAttrs: false }
6
+ </script>
7
+
1
8
  <script setup lang="ts">
2
9
  import { computed, ref } from "vue"
3
10
  import LpNumberFlow from "./LpNumberFlow.vue"
4
11
  import LpRollingText from "./LpRollingText.vue"
5
12
  import LpTooltip from "./LpTooltip.vue"
13
+ import { useMergedAttrs } from "../composables/useMergedClass"
6
14
 
7
15
  export type UptimeStatus = "operational" | "degraded" | "down" | "maintenance" | "empty"
8
16
 
@@ -115,10 +123,17 @@ const uptimeDecimals = computed(() => {
115
123
  const trimmed = Number(v.toFixed(2))
116
124
  return Number.isInteger(trimmed) ? 0 : String(trimmed).split(".")[1].length
117
125
  })
126
+
127
+ // The bar sets its own gap between the strip and its caption, which a compact status row has to close.
128
+ const { class: rootClass, attrs: rest } = useMergedAttrs(
129
+ "flex flex-col gap-1.5",
130
+ )
118
131
  </script>
119
132
 
120
133
  <template>
121
- <div class="flex flex-col gap-1.5">
134
+ <div
135
+ :class="rootClass"
136
+ v-bind="rest">
122
137
  <div v-if="title" class="text-xs">
123
138
  <span class="font-medium text-ink">{{ title }}</span>
124
139
  </div>
@@ -46,6 +46,36 @@ export function useMergedClass(
46
46
  return computed(() => cn(toValue(own), attrs.class as string | undefined) ?? "")
47
47
  }
48
48
 
49
+ /**
50
+ * `useMergedClass` plus the rest of `$attrs`, for the common `inheritAttrs:
51
+ * false` shape: bind `class` and `attrs` and the consumer's `class` lands once,
52
+ * merged, while ids, handlers and aria-* still reach the element.
53
+ *
54
+ * ```ts
55
+ * const { class: rootClass, attrs: rest } = useMergedAttrs(() => card({ padded }))
56
+ * ```
57
+ * ```vue
58
+ * <div :class="rootClass" v-bind="rest">
59
+ * ```
60
+ *
61
+ * Binding the raw `$attrs` alongside a merged class is the bug this avoids:
62
+ * Vue appends that second `class` to the merged one, and stylesheet order —
63
+ * not the caller — decides which wins.
64
+ */
65
+ export function useMergedAttrs(own: MaybeRefOrGetter<string | undefined>): {
66
+ class: ComputedRef<string>
67
+ attrs: ComputedRef<Record<string, unknown>>
68
+ } {
69
+ const attrs = useAttrs()
70
+ return {
71
+ class: useMergedClass(own),
72
+ attrs: computed(() => {
73
+ const { class: _omitted, ...rest } = attrs
74
+ return rest
75
+ }),
76
+ }
77
+ }
78
+
49
79
  /**
50
80
  * The same merge as a plain function, for the places that already have both
51
81
  * strings in hand and do not need the attrs lookup — a render helper, or a
package/src/index.ts CHANGED
@@ -103,6 +103,7 @@ export { fileNameOf } from "./components/lightbox"
103
103
  export { default as LpLink } from "./components/LpLink.vue"
104
104
  export { default as LpLogViewer } from "./components/LpLogViewer.vue"
105
105
  export type { LogLevel, LogLine } from "./components/LpLogViewer.vue"
106
+ export { default as LpMediaGallery } from "./components/LpMediaGallery.vue"
106
107
  export { default as LpModal } from "./components/LpModal.vue"
107
108
  export { default as LpNotificationBell } from "./components/LpNotificationBell.vue"
108
109
  export type {
@@ -227,6 +228,7 @@ export { useClipboard } from "./composables/useClipboard"
227
228
  export type { CopyOptions, UseClipboard } from "./composables/useClipboard"
228
229
  export { useHotkeys, isAppleKeyboard, appleModifierLabel } from "./composables/useHotkeys"
229
230
  export type { HotkeyBinding } from "./composables/useHotkeys"
231
+ export { useMergedClass, useMergedAttrs, mergeClass } from "./composables/useMergedClass"
230
232
  export { useInputFilter } from "./composables/useInputFilter"
231
233
  export type { UseInputFilterOptions } from "./composables/useInputFilter"
232
234
  export { easeOut, easeTravel, easingToken, prefersReducedMotion } from "./composables/easing"
@@ -555,6 +555,35 @@
555
555
  var(--lp-glow-spread-bottom);
556
556
  }
557
557
 
558
+ /*
559
+ * Accent stripe down a card's leading edge (LpCard's `accent`).
560
+ *
561
+ * Drawn as an inset box-shadow rather than a border or a positioned child,
562
+ * because those two cannot do the job here:
563
+ *
564
+ * - A `border-left` occupies layout. It pushes the content 3px inward, so an
565
+ * accented card and a plain one in the same list no longer agree on where
566
+ * their text starts — measured 23px against 21px. Compensating in the
567
+ * padding leaves the card fighting `--surface-border-width`, which the skin
568
+ * axis changes per theme.
569
+ * - An absolutely positioned child collapses: `.lp-skin-panel > *` gives every
570
+ * direct child `position: relative`, which takes `inset-y-0` away from the
571
+ * stripe and paints it at zero width. `::before` is not free either — the
572
+ * skin painter already uses it for the overlay gradient.
573
+ *
574
+ * An inset shadow takes no space, follows `border-radius` round the corners on
575
+ * its own, and needs nothing of the content. It composes rather than replaces:
576
+ * the skin painter's own inset + drop shadows follow the stripe in the same
577
+ * list, so an accented `raised` card keeps its elevation, and `interactive`
578
+ * still swaps `--surface-panel-shadow` underneath on hover.
579
+ */
580
+ .lp-card-accent {
581
+ box-shadow:
582
+ inset var(--lp-card-accent-width, 3px) 0 0 0 var(--lp-card-accent, transparent),
583
+ var(--surface-panel-inset),
584
+ var(--surface-panel-shadow);
585
+ }
586
+
558
587
  /* ── circular theme reveal (View Transitions) ────
559
588
  applyThemeWithTransition() snapshots the page, swaps the theme tokens, then
560
589
  shrinks a clip-path circle in the OLD snapshot from the toggle's position,