@kolkrabbi/kol-framework 0.7.0 → 0.8.1

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/kol-framework.css CHANGED
@@ -167,15 +167,30 @@ body {
167
167
  /* ───── Full-bleed (THE one mechanism, 2026-07-30) ─────
168
168
  * Cancels the page inset with a negative margin — container-relative, so it is
169
169
  * correct inside any layout, including .kol-brand-layout's sidenav grid.
170
- * Promoted from kol-website's local `@utility full-bleed`; the DS owns the
171
- * inset (--kol-pad-section-x), so it owns the cancellation.
170
+ * Promoted from kol-website's local `@utility full-bleed`.
171
+ *
172
+ * THE INSET IS A PARAMETER (2026-07-30, second pass). v1 hardwired
173
+ * --kol-pad-section-x, which silently assumed every consumer pads its pages
174
+ * with the DS ladder. kol-website's apps/web does not — it pads with
175
+ * `.breakpoint-padding` (1 / 1.25 / 1.5rem) and uses zero .kol-page classes,
176
+ * so adopting the hardwired rule over-pulled by 4–24px and every hero
177
+ * overhung the viewport. A cancellation utility must cancel the inset that is
178
+ * ACTUALLY THERE; the DS cannot know that from inside the package.
179
+ *
180
+ * So: --kol-full-bleed-inset defaults to the DS ladder (zero change for
181
+ * consumers on .kol-page) and is overridable by any consumer running its own
182
+ * inset — scope it on the padded wrapper, or on :root for a whole app:
183
+ *
184
+ * :root { --kol-full-bleed-inset: 1rem; }
185
+ * @media (min-width: 768px) { :root { --kol-full-bleed-inset: 1.25rem; } }
186
+ * @media (min-width: 1024px) { :root { --kol-full-bleed-inset: 1.5rem; } }
172
187
  *
173
188
  * NEVER use the viewport trick (`width:100vw; margin-left: calc(50% - 50vw)`):
174
189
  * 50vw measures the VIEWPORT, and inside a sidenav grid the content track is
175
190
  * not viewport-centred — the element overhangs left by ~half the sidenav width
176
191
  * and any `overflow:hidden` ancestor clips it (the /review slice bug). */
177
192
  .kol-full-bleed {
178
- margin-inline: calc(-1 * var(--kol-pad-section-x));
193
+ margin-inline: calc(-1 * var(--kol-full-bleed-inset, var(--kol-pad-section-x)));
179
194
  }
180
195
 
181
196
  /* ───── Standard 4-col content grid ─────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolkrabbi/kol-framework",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "KOL app shell — sidenav, layout, theme toggle, footer, heroes + brand color layer. SideNav takes nav data as props so the tree stays app-local.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -27,8 +27,8 @@
27
27
  "app-shell"
28
28
  ],
29
29
  "dependencies": {
30
- "@kolkrabbi/kol-component": "0.14.0",
31
- "@kolkrabbi/kol-icons": "0.8.10"
30
+ "@kolkrabbi/kol-component": "0.14.1",
31
+ "@kolkrabbi/kol-icons": "0.8.11"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "react": "^18.3.0 || ^19.0.0",
@@ -36,20 +36,26 @@ const MODE_LABEL = { light: 'Light mode', dark: 'Dark mode', system: 'System' }
36
36
  const NEXT_MODE = { light: 'dark', dark: 'system', system: 'light' }
37
37
 
38
38
  export default function ThemeToggle({ variant = 'icon', size = 'md', className = '' }) {
39
- const { isDark, mode, cycle } = useTheme()
39
+ const { mode, cycle } = useTheme()
40
40
 
41
41
  const next = NEXT_MODE[mode]
42
42
  const nextLabel = next === 'system' ? 'system theme' : `${next} mode`
43
43
  const handleToggle = cycle
44
44
 
45
- /* The roll: the strip slides one glyph-width per mode while the split-circle
46
- * glyphs spin 180° in the same clock — dark→light rolls forward (>>),
47
- * light→dark rolls back (<<), system slides on to the desktop glyph. */
48
- const glyphSpin = {
49
- transform: isDark ? 'rotate(0deg)' : 'rotate(180deg)',
45
+ /* The ROLL (redone 2026-07-30 — the 0.5.13 motion was declared FAILED).
46
+ * Wheel mechanics, not slide+flip: rotation ACCUMULATES with the slot
47
+ * (-180° per glyph-width) and runs on the same 500ms/ease clock as the
48
+ * strip's translateX, so the split-circle reads as a coin rolling to its
49
+ * next position — travel left = CCW, travel right = CW, two slots = two
50
+ * full half-turns. The old code rotated on `isDark` alone, desynced from
51
+ * the travel, which is exactly why it read as a slide with a flip bolted
52
+ * on. The desktop glyph doesn't spin — it's not a wheel; it slides in as
53
+ * the no-choice state. */
54
+ const slot = mode === 'dark' ? 0 : mode === 'light' ? 1 : 2
55
+ const glyphRoll = {
56
+ transform: `rotate(${slot * -180}deg)`,
50
57
  lineHeight: 0,
51
58
  }
52
- const slot = mode === 'dark' ? 0 : mode === 'light' ? 1 : 2
53
59
  const iconSwap = (size) => (
54
60
  <span
55
61
  className="relative inline-block overflow-hidden"
@@ -60,10 +66,10 @@ export default function ThemeToggle({ variant = 'icon', size = 'md', className =
60
66
  className="flex transition-transform duration-500 ease-in-out"
61
67
  style={{ width: size * 3, transform: `translateX(-${slot * size}px)` }}
62
68
  >
63
- <span className="inline-flex transition-transform duration-500 ease-in-out" style={glyphSpin}>
69
+ <span className="inline-flex transition-transform duration-500 ease-in-out" style={glyphRoll}>
64
70
  <Icon name="mode-toggle-01" size={size} />
65
71
  </span>
66
- <span className="inline-flex transition-transform duration-500 ease-in-out" style={glyphSpin}>
72
+ <span className="inline-flex transition-transform duration-500 ease-in-out" style={glyphRoll}>
67
73
  <Icon name="mode-toggle-01" size={size} />
68
74
  </span>
69
75
  <span className="inline-flex" style={{ lineHeight: 0 }}>