@kolkrabbi/kol-theme 0.82.0 → 0.84.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.
@@ -0,0 +1,355 @@
1
+ /**
2
+ * kol-animation.css — THE MOTION SHEET (user ruling 2026-08-28: "shouldn't we
3
+ * localise animation to its own css?" — the rule he had given before and that
4
+ * every agent agreed to and none of us carried out).
5
+ *
6
+ * WHY ONE FILE. Motion is read as a SET: you tune the underline, the reveal and
7
+ * the zoom in one sitting. Until this file it was spread across four component
8
+ * sheets — and the five `prefers-reduced-motion` blocks were the tell, each one
9
+ * written by whoever happened to be in that file. Nobody could see the estate's
10
+ * motion whole, so nobody could keep it consistent.
11
+ *
12
+ * WHAT LIVES HERE
13
+ * · every `@keyframes` in the system (the gate below enforces it)
14
+ * · every NAMED motion class — a class whose whole job is to move something
15
+ * · every `prefers-reduced-motion` block
16
+ * · the JS mirror of these numbers is `@kolkrabbi/kol-component/utilities/motion.js`
17
+ * (gsap tweens and framer springs read the same durations and curves)
18
+ *
19
+ * WHAT DOES NOT. A bare `transition:` inside a component's own rest rule —
20
+ * `.kol-btn`'s background-color, `.kol-card`'s border-color. Pulling those out
21
+ * means two rules for one selector, and two rules for one selector drift; the
22
+ * rest rule owns its own easing. Named animations move, per-property easing stays.
23
+ *
24
+ * THE GATE: `scripts/validate-motion.mjs` (the 23rd) fails any `@keyframes`
25
+ * declared outside this file, so the sheet cannot rot back into four.
26
+ *
27
+ * Imported LAST by both entries (kol-theme.css and kol-core.css), so a motion
28
+ * class out-ranks the component chrome it moves.
29
+ */
30
+
31
+ /* ═════════════════════════════════════════════════════════════════════
32
+ * KEYFRAMES — all of them, wherever they are used
33
+ * ═════════════════════════════════════════════════════════════════════ */
34
+
35
+ /* The chess pack's four. RENAMED on the way in (2026-08-28): they were `pulse`,
36
+ * `fadeIn`, `numberTick` and `lineDraw` — unprefixed globals, against the
37
+ * `kol-*` namespace law, and a keyframe name is global no matter which file
38
+ * declares it. In one pack's sheet that was a latent collision; in the shared
39
+ * motion sheet it would be a live one, since every app loads this file. Their
40
+ * users in kol-components-chess.css move with them. */
41
+ @keyframes kol-chess-pulse {
42
+ 0%, 100% {
43
+ opacity: 1;
44
+ }
45
+ 50% {
46
+ opacity: 0.4;
47
+ }
48
+ }
49
+
50
+ @keyframes kol-chess-fade-in {
51
+ from {
52
+ opacity: 0;
53
+ transform: translateY(10px);
54
+ }
55
+ to {
56
+ opacity: 1;
57
+ transform: translateY(0);
58
+ }
59
+ }
60
+
61
+ @keyframes kol-chess-number-tick {
62
+ from {
63
+ transform: translateY(-2px);
64
+ opacity: 0.8;
65
+ }
66
+ to {
67
+ transform: translateY(0);
68
+ opacity: 1;
69
+ }
70
+ }
71
+
72
+ @keyframes kol-chess-line-draw {
73
+ from {
74
+ stroke-dashoffset: 100;
75
+ }
76
+ to {
77
+ stroke-dashoffset: 0;
78
+ }
79
+ }
80
+
81
+ @keyframes kol-combo-fade {
82
+ from { opacity: 0.2; transform: translateY(6px); }
83
+ to { opacity: 1; transform: none; }
84
+ }
85
+
86
+ /* ═════════════════════════════════════════════════════════════════════
87
+ * NAMED MOTION — a class whose whole job is to move something
88
+ * ═════════════════════════════════════════════════════════════════════ */
89
+
90
+ /* ─────────────────────────────────────────────────────────────────────
91
+ * ContentCollection — src/organisms/ContentCollection.jsx (2026-08-15)
92
+ * The enter stagger the collection owns; cards never animate themselves.
93
+ * Delay is set per item inline; the curve is the house token. */
94
+ .kol-collection-item {
95
+ animation: kol-collection-in 300ms var(--kol-ease-house) both;
96
+ }
97
+
98
+ @keyframes kol-collection-in {
99
+ from { opacity: 0; transform: translateY(6px); }
100
+ to { opacity: 1; transform: none; }
101
+ }
102
+
103
+ /* Link underline — the animated hover affordance: draws in left→right on
104
+ * hover/focus, retracts right→left on leave (transform-origin swap, not a
105
+ * fade). Line is currentColor so it rides the text at any scale. Minted from
106
+ * kol-website's animations.css (NavLinkUnderline, 2026-08-12); DS version
107
+ * adds :focus-visible parity and reduced-motion handling.
108
+ * Thickness knob: --kol-link-underline-size (2px default — fine at mono-14,
109
+ * a heading-01 link may want 3px). */
110
+ .kol-link-underline {
111
+ position: relative;
112
+ }
113
+ .kol-link-underline::after {
114
+ content: '';
115
+ position: absolute;
116
+ left: 0;
117
+ bottom: -2px;
118
+ width: 100%;
119
+ height: var(--kol-link-underline-size, 2px);
120
+ background-color: currentColor;
121
+ transform-origin: bottom right;
122
+ transform: scaleX(0);
123
+ transition: transform 300ms cubic-bezier(0.65, 0.05, 0.36, 1);
124
+ }
125
+ .kol-link-underline:hover::after,
126
+ .kol-link-underline:focus-visible::after {
127
+ transform-origin: bottom left;
128
+ transform: scaleX(1);
129
+ }
130
+ @media (prefers-reduced-motion: reduce) {
131
+ .kol-link-underline::after {
132
+ transition: none;
133
+ }
134
+ }
135
+
136
+ /* The ROLL mechanics — .kol-roll (ComponentTailwindSourceTrap, 2026-08-12).
137
+ * These were Tailwind utilities emitted in ThemeToggle's JSX (overflow-hidden,
138
+ * transition-transform, duration-500, ease-in-out) — and Tailwind never scans
139
+ * node_modules, so any consumer without the @source lines got a SILENTLY
140
+ * static glyph: visible, clickable, no travel, no coin-roll, no error.
141
+ * A component's own moving parts live HERE, in the CSS it ships — the
142
+ * consumer's scanner owes it nothing. Widths/transforms stay inline in JSX
143
+ * (they are per-render geometry, not chrome). */
144
+ .kol-roll {
145
+ position: relative;
146
+ display: inline-block;
147
+ overflow: hidden;
148
+ }
149
+ .kol-roll-strip {
150
+ display: flex;
151
+ transition: transform 500ms ease-in-out;
152
+ }
153
+ .kol-roll-slot {
154
+ display: inline-flex;
155
+ line-height: 0;
156
+ transition: transform 500ms ease-in-out;
157
+ }
158
+
159
+ /* ContentCard `reveal` (TypefaceCardAndRow, kol-website 2026-08-27 — the
160
+ * shipped TypefaceLibraryItem's hover, verbatim): the plate and the media
161
+ * fade OUT, the reveal node fades IN, all 300ms on the house curve. Keyed
162
+ * on the card root, only when it carries a reveal (`has-reveal`). */
163
+ .kol-card.has-reveal .kol-card-plate,
164
+ .kol-card.has-reveal .kol-card-canvas-media,
165
+ .kol-card.has-reveal .kol-card-reveal {
166
+ transition: opacity 300ms var(--kol-ease-house);
167
+ }
168
+ .kol-card.has-reveal .kol-card-reveal {
169
+ opacity: 0;
170
+ }
171
+ @media (hover: hover) {
172
+ .kol-card.has-reveal:hover .kol-card-plate,
173
+ .kol-card.has-reveal:hover .kol-card-canvas-media {
174
+ opacity: 0;
175
+ }
176
+ .kol-card.has-reveal:hover .kol-card-reveal {
177
+ opacity: 1;
178
+ }
179
+ }
180
+ @media (prefers-reduced-motion: reduce) {
181
+ .kol-card.has-reveal .kol-card-plate,
182
+ .kol-card.has-reveal .kol-card-canvas-media,
183
+ .kol-card.has-reveal .kol-card-reveal { transition: none; }
184
+ }
185
+
186
+ /* ─────────────────────────────────────────────────────────────────────
187
+ * ContentMedia — the hover zoom
188
+ *
189
+ * A state the house serves on image-led cards: the artwork creeps up
190
+ * inside its own frame while the frame holds still. ContentMedia already
191
+ * clips, so the scale has something to push against — without that
192
+ * overflow the image just grows out of the card.
193
+ *
194
+ * Keyed off the CARD's hover, not the media's, because the whole tile is
195
+ * the affordance; hovering the caption must zoom too. Transform only —
196
+ * it is the one property that scales without relayout, and a wall of
197
+ * these must not thrash the grid.
198
+ *
199
+ * 1.06 and 600ms, deliberately slow and small: a zoom you can SEE
200
+ * happening reads as a photograph breathing; a fast one reads as a
201
+ * glitch.
202
+ * ───────────────────────────────────────────────────────────────────── */
203
+ /* THE TARGET IS THE WRAPPER'S CHILD, WHATEVER IT IS (CatalogCardFrameAndZoom,
204
+ * kol-website 2026-08-28): a catalog card whose media is an SVG glyph on a
205
+ * specimen plate resolves `zoom` true and got the class, the transition and no
206
+ * motion while this named img/video. Everything but the ring hairline. */
207
+ .kol-media-zoom > :not(.kol-media-ring) {
208
+ transition: transform 600ms var(--kol-ease-house);
209
+ /* THE ANCHOR IS THE CONSUMER'S (ContentMediaFocusBinding, kol-monitor
210
+ * 2026-08-27): `--kol-media-focus`, bound once on a repo's :root, is where
211
+ * the image pins for the fit (ContentMedia natural / compact read the same
212
+ * token, default top left) and from where the zoom grows. Unset = center,
213
+ * today's value. Monitor binds `top left`. */
214
+ transform-origin: var(--kol-media-focus, center);
215
+ }
216
+
217
+ @media (hover: hover) {
218
+ .group:hover .kol-media-zoom > :not(.kol-media-ring) {
219
+ transform: scale(1.06);
220
+ }
221
+ /* the HERO rung (StackCardHover, 2026-08-27 — user: "that is TOO MUCH
222
+ * ZOOM"): 1.06 on a ~1500px featured thumb is 90px of travel where the
223
+ * 500px list thumbs move 30. 1.02 on the hero is the same pixel travel.
224
+ * `ListingCard size="hero"` wears it; ContentMedia takes zoom="hero". */
225
+ .group:hover .kol-media-zoom.is-hero > :not(.kol-media-ring) {
226
+ transform: scale(1.02);
227
+ }
228
+ }
229
+
230
+ /* ContentMedia `fade` — the image fades in on load (PrintGridCard's 500ms,
231
+ * carried to ContentCard print 2026-08-27). Opacity only; no relayout. */
232
+ .kol-media-fade {
233
+ opacity: 0;
234
+ transition: opacity 500ms var(--kol-ease-house);
235
+ }
236
+ .kol-media-fade.is-loaded {
237
+ opacity: 1;
238
+ }
239
+
240
+ /* ─────────────────────────────────────────────────────────────────────
241
+ * ContentText — the title dim (StackCardHover, 2026-08-27)
242
+ *
243
+ * The article card's title dims to 70% on card hover, the way ListingCard's
244
+ * always has (`group-hover:opacity-70`, 200ms) — so the featured hero and
245
+ * the filtered cards under it hover the same way (user: "shouldn't it also
246
+ * do it to the cards in the content filter?"). Keyed off the CARD / ROW
247
+ * root, not any `.group` ancestor. ContentText stamps the hook on the
248
+ * article title in both forms.
249
+ * ───────────────────────────────────────────────────────────────────── */
250
+ .kol-content-title-dim {
251
+ transition: opacity 200ms var(--kol-ease-house);
252
+ }
253
+ @media (hover: hover) {
254
+ .kol-card:hover .kol-content-title-dim,
255
+ .kol-row:hover .kol-content-title-dim {
256
+ opacity: 0.7;
257
+ }
258
+ }
259
+ @media (prefers-reduced-motion: reduce) {
260
+ .kol-content-title-dim { transition: none; }
261
+ }
262
+
263
+ @media (prefers-reduced-motion: reduce) {
264
+ .kol-media-zoom > :not(.kol-media-ring) {
265
+ transition: none;
266
+ }
267
+ .group:hover .kol-media-zoom > :not(.kol-media-ring) {
268
+ transform: none;
269
+ }
270
+ }
271
+
272
+ .kol-card-feature-visual {
273
+ transition: transform 300ms ease;
274
+ }
275
+ .kol-card-feature:hover .kol-card-feature-visual {
276
+ transform: scale(1.03);
277
+ }
278
+ @media (prefers-reduced-motion: reduce) {
279
+ .kol-card-feature-visual { transition: none; }
280
+ .kol-card-feature:hover .kol-card-feature-visual { transform: none; }
281
+ }
282
+
283
+ /* Media hover zoom — opt-in via `mediaHover` (SectionSplit ruling, 2026-08-15).
284
+ * The brief named "media hover treatment owned by the component", and named the
285
+ * defect: kol-website's HomeFoundry hover had been silently broken for months
286
+ * because each hand-built section re-decided it. Same numbers and the same
287
+ * reduced-motion guard as the CardFeatureItem zoom (CardFeatureHoverZoom,
288
+ * 2026-08-12) — one motion vocabulary, not two. Motion chrome is DS CSS, never
289
+ * a JSX utility (the ComponentTailwindSourceTrap law). The frame's own
290
+ * overflow-hidden + radius clip the scale. */
291
+ .kol-feature-split-visual.is-hoverable > :first-child,
292
+ .kol-section-split-visual.is-hoverable > :first-child {
293
+ transition: transform 300ms ease;
294
+ }
295
+
296
+ .kol-feature-split-visual.is-hoverable:hover > :first-child,
297
+ .kol-section-split-visual.is-hoverable:hover > :first-child {
298
+ transform: scale(1.03);
299
+ }
300
+
301
+ @media (prefers-reduced-motion: reduce) {
302
+ .kol-feature-split-visual.is-hoverable > :first-child,
303
+ .kol-section-split-visual.is-hoverable > :first-child { transition: none; }
304
+ .kol-feature-split-visual.is-hoverable:hover > :first-child,
305
+ .kol-section-split-visual.is-hoverable:hover > :first-child { transform: none; }
306
+ }
307
+
308
+ /* ═════════════════════════════════════════════════════════════════════
309
+ * THE GRAB PILL — the rail's resize edge (RailFlatGrabOpen, kol-mirror
310
+ * 2026-08-28; kol-r2b2's pill, brought over as it is there — user: "make it
311
+ * like it is in kol-r2b2, it has animation and gsap").
312
+ *
313
+ * The 8px strip straddles the rail's right border so the pill's centre IS the
314
+ * line: in a 48px border-box the 1px border sits at x 47–48, centre 47.5, so
315
+ * the strip is offset `right: -3.5px`. Along the line it follows
316
+ * `--kol-rail-grab-y`, tweened from JS (the marks and the tween live in
317
+ * kol-component's `utilities/motion.js`, GRAB); `clamp()` holds it half its own
318
+ * length from either end so it stops flush, never past.
319
+ *
320
+ * Longer and finer than the ColumnBrowser's 3 × 32 — on a rail it should read
321
+ * as a thickening of the line, not a tab stuck to it. The fade is long and
322
+ * unhurried at BOTH ends on a symmetric in-out: an ease-out popped the first
323
+ * 20 % and crawled the rest, which the user read as a jerk.
324
+ * ═════════════════════════════════════════════════════════════════════ */
325
+ .kol-rail-grab {
326
+ position: absolute;
327
+ top: 0;
328
+ bottom: 0;
329
+ right: -3.5px;
330
+ width: 8px;
331
+ cursor: col-resize;
332
+ touch-action: none;
333
+ }
334
+ .kol-rail-grab::before {
335
+ content: '';
336
+ position: absolute;
337
+ left: 50%;
338
+ top: clamp(2.25rem, var(--kol-rail-grab-y, 50%), calc(100% - 2.25rem));
339
+ translate: -50% -50%;
340
+ width: 0.125rem;
341
+ height: 4.5rem;
342
+ border-radius: var(--kol-radius-full);
343
+ background: var(--kol-fg-64);
344
+ opacity: 0;
345
+ transition: opacity 1800ms cubic-bezier(0.45, 0, 0.55, 1) 400ms, background-color 500ms ease;
346
+ }
347
+ /* `is-near` = the pointer is within 20px of the line (JS, hysteresis at 40) */
348
+ .kol-rail-grab.is-near::before,
349
+ .kol-rail-grab.is-dragging::before { opacity: 1; transition-delay: 40ms; }
350
+ .kol-rail-grab:hover::before,
351
+ .kol-rail-grab.is-dragging::before { background: var(--kol-fg-96); }
352
+
353
+ @media (prefers-reduced-motion: reduce) {
354
+ .kol-rail-grab::before { transition: none; }
355
+ }
@@ -1211,25 +1211,3 @@ a.kol-icon-frame {
1211
1211
  padding: 0;
1212
1212
  }
1213
1213
 
1214
- /* The ROLL mechanics — .kol-roll (ComponentTailwindSourceTrap, 2026-08-12).
1215
- * These were Tailwind utilities emitted in ThemeToggle's JSX (overflow-hidden,
1216
- * transition-transform, duration-500, ease-in-out) — and Tailwind never scans
1217
- * node_modules, so any consumer without the @source lines got a SILENTLY
1218
- * static glyph: visible, clickable, no travel, no coin-roll, no error.
1219
- * A component's own moving parts live HERE, in the CSS it ships — the
1220
- * consumer's scanner owes it nothing. Widths/transforms stay inline in JSX
1221
- * (they are per-render geometry, not chrome). */
1222
- .kol-roll {
1223
- position: relative;
1224
- display: inline-block;
1225
- overflow: hidden;
1226
- }
1227
- .kol-roll-strip {
1228
- display: flex;
1229
- transition: transform 500ms ease-in-out;
1230
- }
1231
- .kol-roll-slot {
1232
- display: inline-flex;
1233
- line-height: 0;
1234
- transition: transform 500ms ease-in-out;
1235
- }
@@ -1021,55 +1021,16 @@
1021
1021
  height: 6px;
1022
1022
  border-radius: 50%;
1023
1023
  background: #5dd27f;
1024
- animation: pulse 2s ease-in-out infinite;
1024
+ animation: kol-chess-pulse 2s ease-in-out infinite;
1025
1025
  }
1026
1026
 
1027
- @keyframes pulse {
1028
- 0%, 100% {
1029
- opacity: 1;
1030
- }
1031
- 50% {
1032
- opacity: 0.4;
1033
- }
1034
- }
1035
-
1036
- @keyframes fadeIn {
1037
- from {
1038
- opacity: 0;
1039
- transform: translateY(10px);
1040
- }
1041
- to {
1042
- opacity: 1;
1043
- transform: translateY(0);
1044
- }
1045
- }
1046
-
1047
- @keyframes numberTick {
1048
- from {
1049
- transform: translateY(-2px);
1050
- opacity: 0.8;
1051
- }
1052
- to {
1053
- transform: translateY(0);
1054
- opacity: 1;
1055
- }
1056
- }
1057
-
1058
- @keyframes lineDraw {
1059
- from {
1060
- stroke-dashoffset: 100;
1061
- }
1062
- to {
1063
- stroke-dashoffset: 0;
1064
- }
1065
- }
1066
1027
 
1067
1028
  .animate-fade-in {
1068
- animation: fadeIn 0.6s ease-out forwards;
1029
+ animation: kol-chess-fade-in 0.6s ease-out forwards;
1069
1030
  }
1070
1031
 
1071
1032
  .animate-number-tick {
1072
- animation: numberTick 0.3s ease-out;
1033
+ animation: kol-chess-number-tick 0.3s ease-out;
1073
1034
  }
1074
1035
 
1075
1036
  .chess-hero__side-link {
@@ -605,16 +605,6 @@
605
605
  * DS CSS (the ComponentTailwindSourceTrap law, same day). The card's own
606
606
  * overflow-hidden + radius clip the scale.
607
607
  * ──────────────────────────────────────────────────────────────────── */
608
- .kol-card-feature-visual {
609
- transition: transform 300ms ease;
610
- }
611
- .kol-card-feature:hover .kol-card-feature-visual {
612
- transform: scale(1.03);
613
- }
614
- @media (prefers-reduced-motion: reduce) {
615
- .kol-card-feature-visual { transition: none; }
616
- .kol-card-feature:hover .kol-card-feature-visual { transform: none; }
617
- }
618
608
 
619
609
  /* the TEXT-ONLY tile (SectionCardItem with no visual — FoundrySpecimenSections,
620
610
  * 2026-08-27): kol-website's .feature-card frame + hover, verbatim — a 1% wash
@@ -1044,32 +1034,6 @@
1044
1034
  border-color 300ms var(--kol-ease-house);
1045
1035
  }
1046
1036
 
1047
- /* ContentCard `reveal` (TypefaceCardAndRow, kol-website 2026-08-27 — the
1048
- * shipped TypefaceLibraryItem's hover, verbatim): the plate and the media
1049
- * fade OUT, the reveal node fades IN, all 300ms on the house curve. Keyed
1050
- * on the card root, only when it carries a reveal (`has-reveal`). */
1051
- .kol-card.has-reveal .kol-card-plate,
1052
- .kol-card.has-reveal .kol-card-canvas-media,
1053
- .kol-card.has-reveal .kol-card-reveal {
1054
- transition: opacity 300ms var(--kol-ease-house);
1055
- }
1056
- .kol-card.has-reveal .kol-card-reveal {
1057
- opacity: 0;
1058
- }
1059
- @media (hover: hover) {
1060
- .kol-card.has-reveal:hover .kol-card-plate,
1061
- .kol-card.has-reveal:hover .kol-card-canvas-media {
1062
- opacity: 0;
1063
- }
1064
- .kol-card.has-reveal:hover .kol-card-reveal {
1065
- opacity: 1;
1066
- }
1067
- }
1068
- @media (prefers-reduced-motion: reduce) {
1069
- .kol-card.has-reveal .kol-card-plate,
1070
- .kol-card.has-reveal .kol-card-canvas-media,
1071
- .kol-card.has-reveal .kol-card-reveal { transition: none; }
1072
- }
1073
1037
 
1074
1038
  /* ─────────────────────────────────────────────────────────────────────
1075
1039
  * The house expand — molecules/SearchInput.jsx · organisms/ContentFilters.jsx
@@ -1099,92 +1063,6 @@
1099
1063
  transition: opacity 300ms var(--kol-ease-house);
1100
1064
  }
1101
1065
 
1102
- /* ─────────────────────────────────────────────────────────────────────
1103
- * ContentMedia — the hover zoom
1104
- *
1105
- * A state the house serves on image-led cards: the artwork creeps up
1106
- * inside its own frame while the frame holds still. ContentMedia already
1107
- * clips, so the scale has something to push against — without that
1108
- * overflow the image just grows out of the card.
1109
- *
1110
- * Keyed off the CARD's hover, not the media's, because the whole tile is
1111
- * the affordance; hovering the caption must zoom too. Transform only —
1112
- * it is the one property that scales without relayout, and a wall of
1113
- * these must not thrash the grid.
1114
- *
1115
- * 1.06 and 600ms, deliberately slow and small: a zoom you can SEE
1116
- * happening reads as a photograph breathing; a fast one reads as a
1117
- * glitch.
1118
- * ───────────────────────────────────────────────────────────────────── */
1119
- .kol-media-zoom > img,
1120
- .kol-media-zoom > video {
1121
- transition: transform 600ms var(--kol-ease-house);
1122
- /* THE ANCHOR IS THE CONSUMER'S (ContentMediaFocusBinding, kol-monitor
1123
- * 2026-08-27): `--kol-media-focus`, bound once on a repo's :root, is where
1124
- * the image pins for the fit (ContentMedia natural / compact read the same
1125
- * token, default top left) and from where the zoom grows. Unset = center,
1126
- * today's value. Monitor binds `top left`. */
1127
- transform-origin: var(--kol-media-focus, center);
1128
- }
1129
-
1130
- @media (hover: hover) {
1131
- .group:hover .kol-media-zoom > img,
1132
- .group:hover .kol-media-zoom > video {
1133
- transform: scale(1.06);
1134
- }
1135
- /* the HERO rung (StackCardHover, 2026-08-27 — user: "that is TOO MUCH
1136
- * ZOOM"): 1.06 on a ~1500px featured thumb is 90px of travel where the
1137
- * 500px list thumbs move 30. 1.02 on the hero is the same pixel travel.
1138
- * `ListingCard size="hero"` wears it; ContentMedia takes zoom="hero". */
1139
- .group:hover .kol-media-zoom.is-hero > img,
1140
- .group:hover .kol-media-zoom.is-hero > video {
1141
- transform: scale(1.02);
1142
- }
1143
- }
1144
-
1145
- /* ContentMedia `fade` — the image fades in on load (PrintGridCard's 500ms,
1146
- * carried to ContentCard print 2026-08-27). Opacity only; no relayout. */
1147
- .kol-media-fade {
1148
- opacity: 0;
1149
- transition: opacity 500ms var(--kol-ease-house);
1150
- }
1151
- .kol-media-fade.is-loaded {
1152
- opacity: 1;
1153
- }
1154
-
1155
- /* ─────────────────────────────────────────────────────────────────────
1156
- * ContentText — the title dim (StackCardHover, 2026-08-27)
1157
- *
1158
- * The article card's title dims to 70% on card hover, the way ListingCard's
1159
- * always has (`group-hover:opacity-70`, 200ms) — so the featured hero and
1160
- * the filtered cards under it hover the same way (user: "shouldn't it also
1161
- * do it to the cards in the content filter?"). Keyed off the CARD / ROW
1162
- * root, not any `.group` ancestor. ContentText stamps the hook on the
1163
- * article title in both forms.
1164
- * ───────────────────────────────────────────────────────────────────── */
1165
- .kol-content-title-dim {
1166
- transition: opacity 200ms var(--kol-ease-house);
1167
- }
1168
- @media (hover: hover) {
1169
- .kol-card:hover .kol-content-title-dim,
1170
- .kol-row:hover .kol-content-title-dim {
1171
- opacity: 0.7;
1172
- }
1173
- }
1174
- @media (prefers-reduced-motion: reduce) {
1175
- .kol-content-title-dim { transition: none; }
1176
- }
1177
-
1178
- @media (prefers-reduced-motion: reduce) {
1179
- .kol-media-zoom > img,
1180
- .kol-media-zoom > video {
1181
- transition: none;
1182
- }
1183
- .group:hover .kol-media-zoom > img,
1184
- .group:hover .kol-media-zoom > video {
1185
- transform: none;
1186
- }
1187
- }
1188
1066
 
1189
1067
  /* Markdown is BOUNDED where it renders (SettingsPanelApproved, kol-r2b2
1190
1068
  * 2026-08-27): in the 320px column preview a document scaled to the frame; in
@@ -375,30 +375,6 @@
375
375
  display: block;
376
376
  }
377
377
 
378
- /* Media hover zoom — opt-in via `mediaHover` (SectionSplit ruling, 2026-08-15).
379
- * The brief named "media hover treatment owned by the component", and named the
380
- * defect: kol-website's HomeFoundry hover had been silently broken for months
381
- * because each hand-built section re-decided it. Same numbers and the same
382
- * reduced-motion guard as the CardFeatureItem zoom (CardFeatureHoverZoom,
383
- * 2026-08-12) — one motion vocabulary, not two. Motion chrome is DS CSS, never
384
- * a JSX utility (the ComponentTailwindSourceTrap law). The frame's own
385
- * overflow-hidden + radius clip the scale. */
386
- .kol-feature-split-visual.is-hoverable > :first-child,
387
- .kol-section-split-visual.is-hoverable > :first-child {
388
- transition: transform 300ms ease;
389
- }
390
-
391
- .kol-feature-split-visual.is-hoverable:hover > :first-child,
392
- .kol-section-split-visual.is-hoverable:hover > :first-child {
393
- transform: scale(1.03);
394
- }
395
-
396
- @media (prefers-reduced-motion: reduce) {
397
- .kol-feature-split-visual.is-hoverable > :first-child,
398
- .kol-section-split-visual.is-hoverable > :first-child { transition: none; }
399
- .kol-feature-split-visual.is-hoverable:hover > :first-child,
400
- .kol-section-split-visual.is-hoverable:hover > :first-child { transform: none; }
401
- }
402
378
 
403
379
  .kol-feature-split-visual-veil,
404
380
  .kol-section-split-visual-veil {
@@ -699,15 +675,3 @@
699
675
  width: calc((100cqw - 120px) / 6);
700
676
  }
701
677
 
702
- /* ─────────────────────────────────────────────────────────────────────
703
- * ContentCollection — src/organisms/ContentCollection.jsx (2026-08-15)
704
- * The enter stagger the collection owns; cards never animate themselves.
705
- * Delay is set per item inline; the curve is the house token. */
706
- .kol-collection-item {
707
- animation: kol-collection-in 300ms var(--kol-ease-house) both;
708
- }
709
-
710
- @keyframes kol-collection-in {
711
- from { opacity: 0; transform: translateY(6px); }
712
- to { opacity: 1; transform: none; }
713
- }
@@ -281,10 +281,6 @@
281
281
  .kol-page--fullbleed .kol-combo-lab { flex: 1; }
282
282
  .kol-page--fullbleed .kol-combo-stage-wrap { min-height: 520px; }
283
283
 
284
- @keyframes kol-combo-fade {
285
- from { opacity: 0.2; transform: translateY(6px); }
286
- to { opacity: 1; transform: none; }
287
- }
288
284
 
289
285
  .kol-combo-readout {
290
286
  display: grid;
package/kol-core.css CHANGED
@@ -32,3 +32,8 @@
32
32
  @import "./kol-components-organisms.css" layer(components);
33
33
  @import "./kol-components-shell.css" layer(components);
34
34
  @import "./kol-design-tokens.css" layer(components);
35
+
36
+ /* MOTION — last, so a named motion class out-ranks the chrome it moves
37
+ * (kol-animation.css, 2026-08-28: every keyframe, every named motion class,
38
+ * every reduced-motion block, one file). */
39
+ @import "./kol-animation.css" layer(components);
package/kol-theme.css CHANGED
@@ -52,3 +52,8 @@
52
52
  /* Theme-level design tokens — LAST, as they always were (lifted into their own
53
53
  * file 2026-08-27 so kol-core.css shares them; emitted CSS unchanged). */
54
54
  @import "./kol-design-tokens.css" layer(components);
55
+
56
+ /* MOTION — last, so a named motion class out-ranks the chrome it moves
57
+ * (kol-animation.css, 2026-08-28: every keyframe, every named motion class,
58
+ * every reduced-motion block, one file). */
59
+ @import "./kol-animation.css" layer(components);
package/kol-utilities.css CHANGED
@@ -91,38 +91,6 @@
91
91
  -moz-appearance: textfield;
92
92
  }
93
93
 
94
- /* Link underline — the animated hover affordance: draws in left→right on
95
- * hover/focus, retracts right→left on leave (transform-origin swap, not a
96
- * fade). Line is currentColor so it rides the text at any scale. Minted from
97
- * kol-website's animations.css (NavLinkUnderline, 2026-08-12); DS version
98
- * adds :focus-visible parity and reduced-motion handling.
99
- * Thickness knob: --kol-link-underline-size (2px default — fine at mono-14,
100
- * a heading-01 link may want 3px). */
101
- .kol-link-underline {
102
- position: relative;
103
- }
104
- .kol-link-underline::after {
105
- content: '';
106
- position: absolute;
107
- left: 0;
108
- bottom: -2px;
109
- width: 100%;
110
- height: var(--kol-link-underline-size, 2px);
111
- background-color: currentColor;
112
- transform-origin: bottom right;
113
- transform: scaleX(0);
114
- transition: transform 300ms cubic-bezier(0.65, 0.05, 0.36, 1);
115
- }
116
- .kol-link-underline:hover::after,
117
- .kol-link-underline:focus-visible::after {
118
- transform-origin: bottom left;
119
- transform: scaleX(1);
120
- }
121
- @media (prefers-reduced-motion: reduce) {
122
- .kol-link-underline::after {
123
- transition: none;
124
- }
125
- }
126
94
 
127
95
  /* Placeholder prose — the ONE gate (GatedEmptyState, kol-fxr 2026-08-15).
128
96
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolkrabbi/kol-theme",
3
- "version": "0.82.0",
3
+ "version": "0.84.0",
4
4
  "description": "KOL (Kolkrabbi) design-system tokens + base CSS — brand-neutral. The canonical token/cascade layer every other KOL package and consumer builds on.",
5
5
  "license": "MIT",
6
6
  "type": "module",