@kolkrabbi/kol-component 0.146.0 → 0.147.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolkrabbi/kol-component",
3
- "version": "0.146.0",
3
+ "version": "0.147.0",
4
4
  "description": "KOL design-system components — atoms through organisms, emitting canonical kol-* classes. Pairs with @kolkrabbi/kol-theme for styling.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -32,6 +32,10 @@ import { Icon } from '@kolkrabbi/kol-icons'
32
32
  * @param {string} href link target; `http*`/`mailto` → new tab, else plain same-tab anchor
33
33
  * @param {Function} onNavigate (event) => void — click seam on the same-tab anchor (SPA intercept)
34
34
  * @param {'auto'|'9/6'|'10/6'|'16/9'|'1/1'} imageAspectRatio aspect class on the visual middle
35
+ * @param {number} zoom hover zoom scale for THIS card's visual (default 1.03, the shipped value).
36
+ * Per-feature because the right amount belongs to the artwork, not the component: 3% is correct on
37
+ * a dense photographic visual and invisible on sparse line-art, and one set can hold both
38
+ * (CardFeatureZoomScale, kol-website 2026-08-31).
35
39
  * @param {string} imagePosition `<img>` object-position
36
40
  */
37
41
  export default function SectionCardItem({
@@ -43,6 +47,7 @@ export default function SectionCardItem({
43
47
  href,
44
48
  onNavigate,
45
49
  imageAspectRatio = 'auto',
50
+ zoom,
46
51
  imagePosition = 'center',
47
52
  className = '',
48
53
  style,
@@ -89,9 +94,10 @@ export default function SectionCardItem({
89
94
  {icon && <Icon name={icon} size={16} className="shrink-0" />}
90
95
  </div>
91
96
 
92
- {/* kol-card-feature-visual: zooms 1.03 on card hover (chrome in
93
- * kol-theme — CardFeatureHoverZoom 2026-08-12); all three visual
94
- * forms ride the same wrapper, reduced-motion opts out. */}
97
+ {/* kol-card-feature-visual: zooms on card hover (chrome in kol-theme —
98
+ * CardFeatureHoverZoom 2026-08-12); all three visual forms ride the same
99
+ * wrapper, reduced-motion opts out. The AMOUNT is `zoom`, published as
100
+ * `--kol-card-feature-zoom` and defaulting to the shipped 1.03. */}
95
101
  <div className={`kol-card-feature-visual w-full flex-auto flex items-center justify-center overflow-hidden ${aspectClass}`.trim()}>
96
102
  {visual ? (
97
103
  typeof visual === 'string' ? (
@@ -130,6 +136,10 @@ export default function SectionCardItem({
130
136
  </>
131
137
  )
132
138
 
139
+ /* the card publishes the amount; the theme rule reads it with 1.03 as the
140
+ * fallback, so a card that sets nothing renders exactly as it always did */
141
+ const rootStyle = zoom != null ? { ...style, '--kol-card-feature-zoom': zoom } : style
142
+
133
143
  const baseClasses = textOnly
134
144
  ? `kol-card-feature kol-card-feature--text w-full flex-1 min-h-[180px] p-4 md:p-5 lg:p-6 gap-2 ${bg} rounded border border-fg-08 flex flex-col justify-between items-start overflow-hidden ${className}`
135
145
  : `kol-card-feature w-full flex-1 h-[304px] md:h-72 p-4 md:p-6 gap-4 ${bg} rounded border border-fg-08 flex flex-col justify-between items-start overflow-hidden ${className}`.trim()
@@ -142,7 +152,7 @@ export default function SectionCardItem({
142
152
  <a
143
153
  href={href}
144
154
  className={`${baseClasses} hover:border-fg-32 transition-colors duration-300`}
145
- style={style}
155
+ style={rootStyle}
146
156
  target="_blank"
147
157
  rel="noreferrer noopener"
148
158
  >
@@ -156,12 +166,12 @@ export default function SectionCardItem({
156
166
  href={href}
157
167
  onClick={onNavigate}
158
168
  className={`${baseClasses} hover:border-fg-24 transition-colors duration-300`}
159
- style={style}
169
+ style={rootStyle}
160
170
  >
161
171
  {content}
162
172
  </a>
163
173
  )
164
174
  }
165
175
 
166
- return <div className={baseClasses} style={style}>{content}</div>
176
+ return <div className={baseClasses} style={rootStyle}>{content}</div>
167
177
  }
@@ -22,7 +22,10 @@ import { minHeightClass } from './sectionHeights.js'
22
22
  * paints its surface — every token inside resolves to the other theme's.
23
23
  * @param {'full'|'80'|'60'|string} [height='60'] min-height on the family's ladder — full = 100dvh,
24
24
  * 80 = 70svh / 80vh, 60 = 50svh / 60vh (default), 40 = 35svh / 40vh; content stays vertically centred inside it
25
- * @param {{title, icon, visual, description, href, backgroundColor, imageAspectRatio}[]} features
25
+ * @param {{title, icon, visual, description, href, backgroundColor, imageAspectRatio, zoom}[]} features
26
+ * `zoom` is the per-card hover scale (default 1.03) — the right amount belongs to the ARTWORK,
27
+ * not the component: 3% reads correctly on a dense photographic visual and is invisible on sparse
28
+ * line-art, and one set can hold both (CardFeatureZoomScale, kol-website 2026-08-31).
26
29
  * @param {ReactNode} eyebrow (alias label) · headline · body the header (heading-03 + mono lede by default)
27
30
  * @param {ReactNode} actions centred action row under the cards
28
31
  * @param {Function} onNavigate (event, feature) => void
@@ -97,6 +100,7 @@ export default function SectionCards({
97
100
  href={feature.href}
98
101
  backgroundColor={feature.backgroundColor}
99
102
  imageAspectRatio={feature.imageAspectRatio}
103
+ zoom={feature.zoom}
100
104
  onNavigate={onNavigate ? (event) => onNavigate(event, feature) : undefined}
101
105
  className={itemClassName}
102
106
  style={typeof itemStyle === 'function' ? itemStyle(index) : itemStyle}
@@ -51,6 +51,13 @@ import { minHeightClass } from './sectionHeights.js'
51
51
  * Button (SectionNewsletterControlSize, kol-website 2026-08-31). The pair was hardcoded md with no
52
52
  * seam, so a page that sets `size="lg"` on every other call-site button could not match it here and
53
53
  * the newsletter read visibly smaller directly beneath them. Default is today's md — nothing moves.
54
+ * @param {boolean} [fullBleed=false] the FILL breaks the page gutter while the content keeps it
55
+ * (SectionNewsletterFullBleed, kol-website 2026-08-31). This card is a filled surface inside
56
+ * `.kol-page`, so the gutter clipped its background and left strips of page down both sides of the
57
+ * colour. Fill and content padding are the same box, so a consumer could not bleed one without
58
+ * dragging the other out with it. The breakout literal is SectionHero's, character for character —
59
+ * two organisms in one family must not invent two ways to leave a gutter. The section's own
60
+ * `px-5 sm:px-8` then re-insets the content, so only the fill moves.
54
61
  * @param {string} className extra classes on the section
55
62
  * @param {'primary'|'secondary'|'tertiary'|'inverse'|'auto'|'none'|string} background the section's surface
56
63
  * (SectionBackgroundProp, 2026-08-27) — a named surface, `none`, or a raw utility / token string; default = what it painted before
@@ -58,6 +65,7 @@ import { minHeightClass } from './sectionHeights.js'
58
65
  export default function SectionNewsletter({
59
66
  height = '40',
60
67
  controlSize = 'md',
68
+ fullBleed = false,
61
69
  theme,
62
70
  background,
63
71
  eyebrow,
@@ -118,7 +126,7 @@ export default function SectionNewsletter({
118
126
  * boundary and read as breaking out of it. `px-5` is a floor the band owns.
119
127
  * Desktop does not move — the measure caps below the padded width, so the
120
128
  * inner block still centres at 80px from the band edge. */
121
- className={`kol-section-newsletter w-full flex flex-col justify-center px-5 sm:px-8 py-24 ${surfaceClass(background, theme ? 'primary' : 'none')} ${theme ? 'text-auto' : ''} ${minHeightClass(height)} ${className}`.replace(/\s+/g, ' ').trim()}
129
+ className={`kol-section-newsletter ${fullBleed ? 'w-screen ml-[calc(50%-50vw)]' : 'w-full'} flex flex-col justify-center px-5 sm:px-8 py-24 ${surfaceClass(background, theme ? 'primary' : 'none')} ${theme ? 'text-auto' : ''} ${minHeightClass(height)} ${className}`.replace(/\s+/g, ' ').trim()}
122
130
  >
123
131
  {/* the family's ONE cap — the shell's --kol-container-max ladder — and
124
132
  * inside it the lede's MEASURE on a wrapper (SectionNewsletterForm,
@@ -10,7 +10,9 @@
10
10
  *
11
11
  * @param {'left'|'center'|'right'} align horizontal alignment + container layout mode (default 'center')
12
12
  * @param {string} title optional heading rendered above the group, authored at the call site
13
- * @param {string} className extra container classes (appended)
13
+ * @param {string} className extra container classes (appended) — note this lands on the OUTER
14
+ * container, not the flex row that carries the gap; the gap is responsive by default (8 stacked,
15
+ * 16 as a row) rather than reachable, so there is nothing to reach for.
14
16
  * @param {ReactNode} children the Buttons
15
17
  */
16
18
 
@@ -32,7 +34,16 @@ export default function ButtonGroup({ align = 'center', title, className = '', c
32
34
  {title && <h3 className="kol-sans-heading-05 text-emphasis mb-6">{title}</h3>}
33
35
  <div
34
36
  className={[
35
- 'flex flex-col gap-4 sm:flex-row sm:items-center',
37
+ /* THE GAP IS TWO JOBS, NOT ONE (ButtonGroupResponsiveGap, kol-website
38
+ * 2026-08-31). The group changes axis at `sm`, so a single `gap-4` was
39
+ * doing horizontal separation between two side-by-side buttons AND
40
+ * vertical separation between two full-width stacked ones. Those do not
41
+ * want the same number: 16 reads too open stacked (user: "16 is way too
42
+ * big, at least lets see 8 or 12"), and 8 is what looks right on device.
43
+ * A responsive default rather than a prop — the stacked case IS the
44
+ * narrow viewport, so the value is pickable once instead of per
45
+ * consumer. The row keeps today's 16 and nothing moves at `sm` and up. */
46
+ 'flex flex-col gap-2 sm:flex-row sm:gap-4 sm:items-center',
36
47
  JUSTIFY[align] || JUSTIFY.center,
37
48
  ].join(' ')}
38
49
  >