@kolkrabbi/kol-framework 0.48.0 → 0.50.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/kol-framework.css CHANGED
@@ -101,9 +101,12 @@ body {
101
101
  deliberately tighter: chrome is not page content. The shell header and the
102
102
  shell content frame both read this, so they cannot disagree; before it,
103
103
  the header carried bare Tailwind steps and the frame carried the page ramp.
104
- FLAT, not stepped — one value at every width, on the user's word.
104
+ ONE STEP (user ruling 2026-10-02, the phone pass): on a phone it is the
105
+ page ladder's 20, so the header, the frame and a page all stand on one
106
+ edge — it was 24 at every width, which put a phone page 4px off the
107
+ ladder. 24 returns at 768, where the 2026-07-31 ruling was made.
105
108
  Do NOT reach for this on page containers — that is --kol-pad-section-x. */
106
- --kol-pad-chrome-x: var(--kol-spacing-6);
109
+ --kol-pad-chrome-x: var(--kol-spacing-5);
107
110
 
108
111
  /* Stepped page / section / band padding tokens.
109
112
  Used by chrome (topnav, sidenav) and landing bands. Scaled at 768 / 1024
@@ -117,6 +120,7 @@ body {
117
120
 
118
121
  @media (min-width: 768px) {
119
122
  :root {
123
+ --kol-pad-chrome-x: var(--kol-spacing-6);
120
124
  --kol-pad-page-x: 32px;
121
125
  --kol-pad-page-y: 96px;
122
126
  --kol-pad-section-x: 32px;
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@kolkrabbi/kol-framework",
3
- "version": "0.48.0",
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.",
3
+ "version": "0.50.0",
4
+ "description": "Site shell and page layout",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "./src/index.js",
8
8
  "module": "./src/index.js",
9
9
  "exports": {
10
10
  ".": "./src/index.js",
11
+ "./vite": "./src/vite.js",
11
12
  "./src/*": "./src/*",
12
13
  "./kol-framework.css": "./kol-framework.css",
13
14
  "./kol-brand-color.css": "./kol-brand-color.css"
@@ -21,8 +22,8 @@
21
22
  "react-router-dom": "^6.0.0 || ^7.0.0"
22
23
  },
23
24
  "devDependencies": {
24
- "@kolkrabbi/kol-component": "^0.230.0",
25
- "@kolkrabbi/kol-icons": "^0.30.0"
25
+ "@kolkrabbi/kol-component": "^0.266.0",
26
+ "@kolkrabbi/kol-icons": "^0.39.0"
26
27
  },
27
28
  "files": [
28
29
  "src",
package/src/PageHero.jsx CHANGED
@@ -2,7 +2,7 @@ import { Link } from 'react-router-dom'
2
2
  import { SectionText } from '@kolkrabbi/kol-component'
3
3
 
4
4
  /**
5
- * PageHero — the page's opening band: label · title · lede, with an optional
5
+ * PageHero — A page's opening band. the page's opening band: label · title · lede, with an optional
6
6
  * mark beside the text and an optional back link above it.
7
7
  *
8
8
  * ONE HERO (page-family-is-not-a-set, kol-client-olina 2026-09-03; user, on
@@ -24,7 +24,7 @@ export const ShellTocCollapsedContext = createContext(null)
24
24
  * (sidenav-rail-hairline, 2026-08-26): `background`, `hairline`, `isActive`,
25
25
  * `onNavigate` … the next rail option needs no AppShell release. */
26
26
  /**
27
- * PageLayout — the brand-book page layout: the `.kol-brand-layout` grid with
27
+ * PageLayout — The brand-book page layout. the brand-book page layout: the `.kol-brand-layout` grid with
28
28
  * the sidenav in its first track, the page plane in the second, the mobile
29
29
  * drawer + hamburger, the modal provider and the optional TOC rail.
30
30
  *
@@ -3,7 +3,7 @@ import { Tooltip } from '@kolkrabbi/kol-component'
3
3
  import { useTheme } from './theme.js'
4
4
 
5
5
  /**
6
- * Theme toggle — two-position glyph-roll button (the approved 2-variant spec).
6
+ * Theme toggle — Switch between light and dark. Two-position glyph-roll button (the approved 2-variant spec).
7
7
  *
8
8
  * Clicking cycles **light ↔ dark**. `system` is not a position — see the
9
9
  * ruling above MODE_LABEL. Alt- or shift-click clears the choice and hands the
package/src/index.js CHANGED
@@ -17,6 +17,8 @@ export { useDragResize } from '@kolkrabbi/kol-component'
17
17
  export { default as ShellHeader, HEADER_ICON } from './ShellHeader.jsx'
18
18
  export { default as ThemeToggle } from './ThemeToggle.jsx'
19
19
  export { useTheme, applyTheme, getInitialTheme, THEME_STORAGE_KEY, THEME_BOOT_SCRIPT } from './theme.js'
20
+ /* the app loader's runtime half — the build half is `loaderApp()` in `@kolkrabbi/kol-framework/vite` */
21
+ export { hideLoaderApp } from './loaderApp.js'
20
22
  export { default as Layout } from './Layout.jsx'
21
23
  export { default as PageSection } from './PageSection.jsx'
22
24
  export { default as PortalFooter } from './PortalFooter.jsx'
@@ -0,0 +1,14 @@
1
+ /**
2
+ * hideLoaderApp — the app loader's other half (`loaderApp()` in `@kolkrabbi/kol-framework/vite` writes it into
3
+ * index.html). Call it once React has committed: two frames later the loader fades (240 ms, none under reduced
4
+ * motion — the CSS drops the transition) and leaves the DOM. Safe to call when there is no loader.
5
+ */
6
+ export function hideLoaderApp() {
7
+ if (typeof document === 'undefined') return
8
+ const el = document.getElementById('kol-loader-app')
9
+ if (!el) return
10
+ requestAnimationFrame(() => requestAnimationFrame(() => {
11
+ el.classList.add('is-done')
12
+ setTimeout(() => { el.remove(); document.getElementById('kol-loader-app-css')?.remove() }, 300)
13
+ }))
14
+ }
package/src/vite.js ADDED
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Vite plugins for KOL apps — Node-side, no React (import from `@kolkrabbi/kol-framework/vite` in vite.config).
3
+ *
4
+ * devFavicon — THE DEV TAB IS TELLABLE AT A GLANCE (2026-10-17 — user: "I need to be able to tell which tabs
5
+ * are localhost dev and which are production"). On the dev server the favicon becomes a solid tile with the
6
+ * app's letters; a build never sees it (`apply: 'serve'`). It REPLACES the page's icon link: fxr appended a
7
+ * second one and browsers pick their own (Firefox kept prod's), mirror swapped it at runtime after load.
8
+ * Each app picks its letters and ground once — the table lives in 16-app-anatomy.md § Dev favicon.
9
+ *
10
+ * @param {Object} [opts]
11
+ * @param {string} [opts.label='DEV'] 1–3 letters, fitted to the tile
12
+ * @param {string} [opts.ground='#7C5CFF'] the tile's colour — unique per app
13
+ * @param {string} [opts.ink='#FFFFFF']
14
+ */
15
+ export function devFavicon({ label = 'DEV', ground = '#7C5CFF', ink = '#FFFFFF' } = {}) {
16
+ const text = String(label).slice(0, 3).toUpperCase()
17
+ const size = text.length === 1 ? 28 : text.length === 2 ? 20 : 15
18
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="6" fill="${ground}"/>`
19
+ + `<text x="16" y="${(16 + size * 0.36).toFixed(1)}" text-anchor="middle" font-family="Arial Black, Arial, sans-serif" font-weight="900" font-size="${size}"`
20
+ + `${text.length > 1 ? ' textLength="28" lengthAdjust="spacingAndGlyphs"' : ''} fill="${ink}">${text}</text></svg>`
21
+ const link = `<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,${encodeURIComponent(svg)}">`
22
+ return {
23
+ name: 'kol-dev-favicon',
24
+ apply: 'serve',
25
+ transformIndexHtml(html) {
26
+ return /<link rel="icon"[^>]*>/.test(html) ? html.replace(/<link rel="icon"[^>]*>/, link) : html.replace('</head>', `${link}</head>`)
27
+ },
28
+ }
29
+ }
30
+
31
+ /**
32
+ * loaderApp — THE APP LOADER, OWNED ONCE (AppLoader, kol-monitor 2026-10-17 — user: *"shouldnt that loader just be
33
+ * owned in DS and consumed by the fxr monitor and mirror? with custom graphic input as prop?"*). The app's mark
34
+ * centred on the theme's ground, pulsing while the bundle downloads. It has to be static HTML in `index.html` —
35
+ * a React component would only appear after the wait it covers — so this plugin writes it there: fxr's loader,
36
+ * once, with the graphic as an option. Call `hideLoaderApp()` (kol-framework) after the first commit.
37
+ *
38
+ * Ground and ink follow the stored `data-theme`, else the system (run `THEME_BOOT_SCRIPT` before it, as every
39
+ * KOL app does). Reduced motion: no pulse, no fade.
40
+ *
41
+ * @param {Object} opts
42
+ * @param {string} opts.graphic an SVG — the markup itself, or a path (relative to the app root) to an .svg file;
43
+ * draw it in `currentColor` so it takes the ground's ink
44
+ * @param {number} [opts.size=64] the mark's edge in px
45
+ */
46
+ export function loaderApp({ graphic, size = 64 } = {}) {
47
+ let root = process.cwd()
48
+ const css = `#kol-loader-app{position:fixed;inset:0;z-index:2147483647;display:grid;place-items:center;background:#FAFAFA;color:#0E0E11;transition:opacity 240ms ease}`
49
+ + `@media (prefers-color-scheme: dark){:root:not([data-theme="light"]) #kol-loader-app{background:#121215;color:#FFFFFF}}`
50
+ + `:root[data-theme="dark"] #kol-loader-app{background:#121215;color:#FFFFFF}`
51
+ + `#kol-loader-app svg{width:${size}px;height:${size}px;animation:kol-loader-app-pulse 1.6s ease-in-out infinite}`
52
+ + `#kol-loader-app.is-done{opacity:0;pointer-events:none}`
53
+ + `@keyframes kol-loader-app-pulse{50%{opacity:.35}}`
54
+ + `@media (prefers-reduced-motion: reduce){#kol-loader-app svg{animation:none}#kol-loader-app{transition:none}}`
55
+ return {
56
+ name: 'kol-loader-app',
57
+ configResolved(config) { root = config.root },
58
+ async transformIndexHtml(html) {
59
+ let svg = String(graphic ?? '').trim()
60
+ if (svg && !svg.startsWith('<')) {
61
+ const { readFile } = await import('node:fs/promises')
62
+ const { resolve } = await import('node:path')
63
+ svg = (await readFile(resolve(root, svg), 'utf8')).trim()
64
+ }
65
+ svg = svg.replace(/<\?xml[^>]*>/, '').replace(/<!--[\s\S]*?-->/g, '')
66
+ return html
67
+ .replace('</head>', `<style id="kol-loader-app-css">${css}</style></head>`)
68
+ .replace(/<body([^>]*)>/, `<body$1><div id="kol-loader-app" aria-hidden="true">${svg}</div>`)
69
+ },
70
+ }
71
+ }