@ox-content/theme-fabric 2.84.0 → 2.88.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/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -39,7 +39,7 @@ const fabric = {
|
|
|
39
39
|
)}:root:root .nav-link:not(.active):hover{background:var(
|
|
40
40
|
--octc-skin-nav-hover-bg,color-mix(in srgb,var(--octc-color-bg-alt) 58%,transparent)
|
|
41
41
|
)}.header{border-bottom:0}.header{background-color:transparent;background-image:none;box-shadow:none}.content pre{box-shadow:none}`,
|
|
42
|
-
js: "(()=>{try{\n// Shared WebGL2 runtime for the skins that render a live hero backdrop.\n//\n//
|
|
42
|
+
js: "(()=>{try{\n// Shared WebGL2 runtime for the skins that render a live hero backdrop.\n//\n// The SSG concatenates a theme's `js` into one classic inline <script>, so this\n// cannot import anything — hence a hand-rolled runtime rather than a library.\n// It is strictly progressive enhancement: the CSS backdrop is the real design,\n// and the canvas only ever layers on top of it. Every bail-out path below\n// leaves the page exactly as it renders without JavaScript.\n\nfunction octcGL(fragmentSource, options) {\n const opts = options || {};\n const host = document.querySelector(opts.target || \".hero\");\n if (!host || typeof WebGL2RenderingContext === \"undefined\") return;\n if (matchMedia(\"(prefers-reduced-motion: reduce)\").matches) return;\n\n const canvas = document.createElement(\"canvas\");\n canvas.setAttribute(\"aria-hidden\", \"true\");\n canvas.style.cssText =\n \"position:absolute;inset:0;width:100%;height:100%;z-index:0;pointer-events:none;\" +\n \"opacity:0;transition:opacity 900ms ease\";\n const gl = canvas.getContext(\"webgl2\", {\n alpha: true,\n antialias: false,\n depth: false,\n stencil: false,\n powerPreference: \"low-power\",\n });\n if (!gl) return;\n\n // A fullscreen triangle from gl_VertexID needs no buffers at all.\n const VERT =\n \"#version 300 es\\nvoid main(){vec2 p=vec2(float((gl_VertexID<<1)&2),float(gl_VertexID&2));\" +\n \"gl_Position=vec4(p*2.0-1.0,0.0,1.0);}\";\n\n function compile(type, src) {\n const sh = gl.createShader(type);\n gl.shaderSource(sh, src);\n gl.compileShader(sh);\n if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {\n gl.deleteShader(sh);\n return null;\n }\n return sh;\n }\n\n const vs = compile(gl.VERTEX_SHADER, VERT);\n const fs = compile(gl.FRAGMENT_SHADER, fragmentSource);\n if (!vs || !fs) return;\n\n const program = gl.createProgram();\n gl.attachShader(program, vs);\n gl.attachShader(program, fs);\n gl.linkProgram(program);\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) return;\n gl.useProgram(program);\n\n const uRes = gl.getUniformLocation(program, \"u_res\");\n const uTime = gl.getUniformLocation(program, \"u_time\");\n const uColors = [0, 1, 2, 3].map((i) => gl.getUniformLocation(program, \"u_c\" + i));\n\n // A skin that needs uniforms beyond the shared set (Liquid Glass feeds the\n // rects of the panels it refracts) registers them here; the returned callback\n // runs every frame before the draw, in canvas pixels.\n const onFrame = opts.setup ? opts.setup(gl, program, host, canvas) : null;\n\n // Palette values are read through a probe element rather than parsed from the\n // custom property, because a scheme may define one as color-mix(), which only\n // resolves to concrete channels once it is used.\n const probe = document.createElement(\"span\");\n probe.style.cssText = \"position:absolute;width:0;height:0;opacity:0;pointer-events:none\";\n host.appendChild(probe);\n\n function readColor(name, fallback) {\n probe.style.color = fallback;\n probe.style.color = \"var(\" + name + \",\" + fallback + \")\";\n const value = getComputedStyle(probe).color;\n const parts = value.match(/[\\d.]+/g);\n if (!parts) return [0, 0, 0];\n // `color-mix()` serialises as `color(srgb r g b)` with 0-1 channels.\n const scale = value.indexOf(\"color(\") === 0 ? 1 : 255;\n return [parts[0] / scale, parts[1] / scale, parts[2] / scale];\n }\n\n const SLOTS = opts.colors || [\n \"--octc-accent-a\",\n \"--octc-accent-b\",\n \"--octc-accent-c\",\n \"--octc-color-bg\",\n ];\n\n function pushColors() {\n for (let i = 0; i < uColors.length; i++) {\n if (!uColors[i]) continue;\n const c = readColor(SLOTS[i], \"#888888\");\n gl.uniform3f(uColors[i], c[0], c[1], c[2]);\n }\n }\n\n // Resolution is capped rather than tied to devicePixelRatio: this is a soft,\n // out-of-focus backdrop, so a retina-sharp buffer costs fill rate for nothing.\n let width = 0;\n let height = 0;\n function resize() {\n const dpr = Math.min(devicePixelRatio || 1, 1.5);\n const w = Math.max(1, Math.round(host.clientWidth * dpr));\n const h = Math.max(1, Math.round(host.clientHeight * dpr));\n if (w === width && h === height) return;\n width = canvas.width = w;\n height = canvas.height = h;\n gl.viewport(0, 0, w, h);\n gl.uniform2f(uRes, w, h);\n }\n\n let running = false;\n let visible = false;\n let frame = 0;\n const start = performance.now();\n\n function tick(now) {\n if (!running) return;\n resize();\n gl.uniform1f(uTime, (now - start) / 1000);\n if (onFrame) onFrame(now, width, height);\n gl.drawArrays(gl.TRIANGLES, 0, 3);\n frame = requestAnimationFrame(tick);\n }\n\n function setRunning(next) {\n if (next === running) return;\n running = next;\n if (running) frame = requestAnimationFrame(tick);\n else cancelAnimationFrame(frame);\n }\n\n // Scrolled past or backgrounded means no work: an ambient effect must never\n // keep a tab busy once it is off screen.\n new IntersectionObserver((entries) => {\n visible = entries[0].isIntersecting;\n setRunning(visible && !document.hidden);\n }).observe(host);\n document.addEventListener(\"visibilitychange\", () => {\n setRunning(visible && !document.hidden);\n });\n addEventListener(\"resize\", resize, { passive: true });\n\n // The scheme changes under us whenever the reader flips the theme toggle.\n new MutationObserver(pushColors).observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"data-theme\"],\n });\n\n host.insertBefore(canvas, host.firstChild);\n pushColors();\n resize();\n // Set directly rather than from a frame callback: anything that throws\n // between mount and that callback leaves the canvas permanently invisible,\n // which is indistinguishable from the effect simply not existing.\n canvas.style.opacity = opts.opacity == null ? \"1\" : String(opts.opacity);\n}\n\n// Fabric — actual shading rather than a printed texture. The weave is built as a\n// height field of interleaved warp and weft threads; that height gives a normal,\n// and the normal is lit by a slowly orbiting key light. Threads catch the light\n// along their own direction, which is what makes woven cloth read as cloth.\noctcGL(\n `#version 300 es\nprecision highp float;\nuniform vec2 u_res; uniform float u_time;\nuniform vec3 u_c0, u_c1, u_c2, u_c3;\nout vec4 outColor;\n\nfloat hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }\n\n// One cell holds a warp thread crossing a weft thread. Which one passes over\n// alternates per cell, exactly as in a plain weave.\nfloat weave(vec2 p){\n vec2 cell = floor(p);\n vec2 f = fract(p);\n bool warpOver = mod(cell.x + cell.y, 2.0) < 1.0;\n float warp = sin(f.x * 3.14159);\n float weft = sin(f.y * 3.14159);\n float over = warpOver ? warp : weft;\n float under = warpOver ? weft : warp;\n // Slight per-thread variation stops the weave reading as a printed grid.\n float slub = 0.9 + 0.2 * hash(warpOver ? vec2(cell.x, 0.0) : vec2(0.0, cell.y));\n return (over * 0.75 + under * 0.25) * slub;\n}\n\nvoid main(){\n vec2 uv = gl_FragCoord.xy / u_res;\n vec2 p = uv * vec2(u_res.x / u_res.y, 1.0) * 190.0;\n\n float h = weave(p);\n float e = 0.6;\n vec3 n = normalize(vec3(\n (weave(p + vec2(e, 0.0)) - h) / e,\n (weave(p + vec2(0.0, e)) - h) / e,\n 1.0\n ));\n\n // A key light that drifts slowly, so the cloth catches the light as you read.\n float a = u_time * 0.12;\n vec3 light = normalize(vec3(cos(a) * 0.6, sin(a) * 0.6, 0.75));\n float lambert = max(dot(n, light), 0.0);\n float sheen = pow(max(dot(reflect(-light, n), vec3(0.0, 0.0, 1.0)), 0.0), 18.0);\n\n vec3 base = mix(u_c3, u_c0, 0.10);\n vec3 col = base * (0.82 + lambert * 0.34) + sheen * 0.10;\n\n float edge = smoothstep(0.0, 0.2, uv.y) * smoothstep(1.0, 0.78, uv.y);\n outColor = vec4(col, edge * 0.55);\n}`,\n { opacity: 0.7 },\n);\n\n}catch(e){console.warn(\"[ox-content] theme backdrop disabled:\",e)}})();"
|
|
43
43
|
};
|
|
44
44
|
//#endregion
|
|
45
45
|
exports.default = fabric;
|
package/dist/index.mjs
CHANGED
|
@@ -35,7 +35,7 @@ const fabric = {
|
|
|
35
35
|
)}:root:root .nav-link:not(.active):hover{background:var(
|
|
36
36
|
--octc-skin-nav-hover-bg,color-mix(in srgb,var(--octc-color-bg-alt) 58%,transparent)
|
|
37
37
|
)}.header{border-bottom:0}.header{background-color:transparent;background-image:none;box-shadow:none}.content pre{box-shadow:none}`,
|
|
38
|
-
js: "(()=>{try{\n// Shared WebGL2 runtime for the skins that render a live hero backdrop.\n//\n//
|
|
38
|
+
js: "(()=>{try{\n// Shared WebGL2 runtime for the skins that render a live hero backdrop.\n//\n// The SSG concatenates a theme's `js` into one classic inline <script>, so this\n// cannot import anything — hence a hand-rolled runtime rather than a library.\n// It is strictly progressive enhancement: the CSS backdrop is the real design,\n// and the canvas only ever layers on top of it. Every bail-out path below\n// leaves the page exactly as it renders without JavaScript.\n\nfunction octcGL(fragmentSource, options) {\n const opts = options || {};\n const host = document.querySelector(opts.target || \".hero\");\n if (!host || typeof WebGL2RenderingContext === \"undefined\") return;\n if (matchMedia(\"(prefers-reduced-motion: reduce)\").matches) return;\n\n const canvas = document.createElement(\"canvas\");\n canvas.setAttribute(\"aria-hidden\", \"true\");\n canvas.style.cssText =\n \"position:absolute;inset:0;width:100%;height:100%;z-index:0;pointer-events:none;\" +\n \"opacity:0;transition:opacity 900ms ease\";\n const gl = canvas.getContext(\"webgl2\", {\n alpha: true,\n antialias: false,\n depth: false,\n stencil: false,\n powerPreference: \"low-power\",\n });\n if (!gl) return;\n\n // A fullscreen triangle from gl_VertexID needs no buffers at all.\n const VERT =\n \"#version 300 es\\nvoid main(){vec2 p=vec2(float((gl_VertexID<<1)&2),float(gl_VertexID&2));\" +\n \"gl_Position=vec4(p*2.0-1.0,0.0,1.0);}\";\n\n function compile(type, src) {\n const sh = gl.createShader(type);\n gl.shaderSource(sh, src);\n gl.compileShader(sh);\n if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {\n gl.deleteShader(sh);\n return null;\n }\n return sh;\n }\n\n const vs = compile(gl.VERTEX_SHADER, VERT);\n const fs = compile(gl.FRAGMENT_SHADER, fragmentSource);\n if (!vs || !fs) return;\n\n const program = gl.createProgram();\n gl.attachShader(program, vs);\n gl.attachShader(program, fs);\n gl.linkProgram(program);\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) return;\n gl.useProgram(program);\n\n const uRes = gl.getUniformLocation(program, \"u_res\");\n const uTime = gl.getUniformLocation(program, \"u_time\");\n const uColors = [0, 1, 2, 3].map((i) => gl.getUniformLocation(program, \"u_c\" + i));\n\n // A skin that needs uniforms beyond the shared set (Liquid Glass feeds the\n // rects of the panels it refracts) registers them here; the returned callback\n // runs every frame before the draw, in canvas pixels.\n const onFrame = opts.setup ? opts.setup(gl, program, host, canvas) : null;\n\n // Palette values are read through a probe element rather than parsed from the\n // custom property, because a scheme may define one as color-mix(), which only\n // resolves to concrete channels once it is used.\n const probe = document.createElement(\"span\");\n probe.style.cssText = \"position:absolute;width:0;height:0;opacity:0;pointer-events:none\";\n host.appendChild(probe);\n\n function readColor(name, fallback) {\n probe.style.color = fallback;\n probe.style.color = \"var(\" + name + \",\" + fallback + \")\";\n const value = getComputedStyle(probe).color;\n const parts = value.match(/[\\d.]+/g);\n if (!parts) return [0, 0, 0];\n // `color-mix()` serialises as `color(srgb r g b)` with 0-1 channels.\n const scale = value.indexOf(\"color(\") === 0 ? 1 : 255;\n return [parts[0] / scale, parts[1] / scale, parts[2] / scale];\n }\n\n const SLOTS = opts.colors || [\n \"--octc-accent-a\",\n \"--octc-accent-b\",\n \"--octc-accent-c\",\n \"--octc-color-bg\",\n ];\n\n function pushColors() {\n for (let i = 0; i < uColors.length; i++) {\n if (!uColors[i]) continue;\n const c = readColor(SLOTS[i], \"#888888\");\n gl.uniform3f(uColors[i], c[0], c[1], c[2]);\n }\n }\n\n // Resolution is capped rather than tied to devicePixelRatio: this is a soft,\n // out-of-focus backdrop, so a retina-sharp buffer costs fill rate for nothing.\n let width = 0;\n let height = 0;\n function resize() {\n const dpr = Math.min(devicePixelRatio || 1, 1.5);\n const w = Math.max(1, Math.round(host.clientWidth * dpr));\n const h = Math.max(1, Math.round(host.clientHeight * dpr));\n if (w === width && h === height) return;\n width = canvas.width = w;\n height = canvas.height = h;\n gl.viewport(0, 0, w, h);\n gl.uniform2f(uRes, w, h);\n }\n\n let running = false;\n let visible = false;\n let frame = 0;\n const start = performance.now();\n\n function tick(now) {\n if (!running) return;\n resize();\n gl.uniform1f(uTime, (now - start) / 1000);\n if (onFrame) onFrame(now, width, height);\n gl.drawArrays(gl.TRIANGLES, 0, 3);\n frame = requestAnimationFrame(tick);\n }\n\n function setRunning(next) {\n if (next === running) return;\n running = next;\n if (running) frame = requestAnimationFrame(tick);\n else cancelAnimationFrame(frame);\n }\n\n // Scrolled past or backgrounded means no work: an ambient effect must never\n // keep a tab busy once it is off screen.\n new IntersectionObserver((entries) => {\n visible = entries[0].isIntersecting;\n setRunning(visible && !document.hidden);\n }).observe(host);\n document.addEventListener(\"visibilitychange\", () => {\n setRunning(visible && !document.hidden);\n });\n addEventListener(\"resize\", resize, { passive: true });\n\n // The scheme changes under us whenever the reader flips the theme toggle.\n new MutationObserver(pushColors).observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"data-theme\"],\n });\n\n host.insertBefore(canvas, host.firstChild);\n pushColors();\n resize();\n // Set directly rather than from a frame callback: anything that throws\n // between mount and that callback leaves the canvas permanently invisible,\n // which is indistinguishable from the effect simply not existing.\n canvas.style.opacity = opts.opacity == null ? \"1\" : String(opts.opacity);\n}\n\n// Fabric — actual shading rather than a printed texture. The weave is built as a\n// height field of interleaved warp and weft threads; that height gives a normal,\n// and the normal is lit by a slowly orbiting key light. Threads catch the light\n// along their own direction, which is what makes woven cloth read as cloth.\noctcGL(\n `#version 300 es\nprecision highp float;\nuniform vec2 u_res; uniform float u_time;\nuniform vec3 u_c0, u_c1, u_c2, u_c3;\nout vec4 outColor;\n\nfloat hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }\n\n// One cell holds a warp thread crossing a weft thread. Which one passes over\n// alternates per cell, exactly as in a plain weave.\nfloat weave(vec2 p){\n vec2 cell = floor(p);\n vec2 f = fract(p);\n bool warpOver = mod(cell.x + cell.y, 2.0) < 1.0;\n float warp = sin(f.x * 3.14159);\n float weft = sin(f.y * 3.14159);\n float over = warpOver ? warp : weft;\n float under = warpOver ? weft : warp;\n // Slight per-thread variation stops the weave reading as a printed grid.\n float slub = 0.9 + 0.2 * hash(warpOver ? vec2(cell.x, 0.0) : vec2(0.0, cell.y));\n return (over * 0.75 + under * 0.25) * slub;\n}\n\nvoid main(){\n vec2 uv = gl_FragCoord.xy / u_res;\n vec2 p = uv * vec2(u_res.x / u_res.y, 1.0) * 190.0;\n\n float h = weave(p);\n float e = 0.6;\n vec3 n = normalize(vec3(\n (weave(p + vec2(e, 0.0)) - h) / e,\n (weave(p + vec2(0.0, e)) - h) / e,\n 1.0\n ));\n\n // A key light that drifts slowly, so the cloth catches the light as you read.\n float a = u_time * 0.12;\n vec3 light = normalize(vec3(cos(a) * 0.6, sin(a) * 0.6, 0.75));\n float lambert = max(dot(n, light), 0.0);\n float sheen = pow(max(dot(reflect(-light, n), vec3(0.0, 0.0, 1.0)), 0.0), 18.0);\n\n vec3 base = mix(u_c3, u_c0, 0.10);\n vec3 col = base * (0.82 + lambert * 0.34) + sheen * 0.10;\n\n float edge = smoothstep(0.0, 0.2, uv.y) * smoothstep(1.0, 0.78, uv.y);\n outColor = vec4(col, edge * 0.55);\n}`,\n { opacity: 0.7 },\n);\n\n}catch(e){console.warn(\"[ox-content] theme backdrop disabled:\",e)}})();"
|
|
39
39
|
};
|
|
40
40
|
//#endregion
|
|
41
41
|
export { fabric as default, fabric };
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/skin.ts","../src/gl.ts","../src/index.ts"],"sourcesContent":["/**\n * Fabric skin stylesheet.\n *\n * Woven texture, stitched seams and soft cloth-fold reveals.\n *\n * Written entirely against `--octc-*` custom properties and `color-mix()`, plus\n * neutral black/white alpha for depth — no hue is ever named, which is what lets\n * any `@ox-content/theme-color-*` package drive it.\n *\n * Generated from `scripts/theme-skins/skins/fabric.css`; edit that file and\n * run `node scripts/theme-skins/generate.mjs` rather than editing this by hand.\n */\nexport const css = `@property --octc-sweep{syntax:\"<percentage>\";inherits:false;initial-value:0%}@property --octc-spin{syntax:\"<angle>\";inherits:false;initial-value:0deg}@view-transition{navigation:auto}.header{view-transition-name:octc-header}.sidebar{view-transition-name:octc-sidebar}.main{view-transition-name:octc-main}@media (prefers-reduced-motion:no-preference){::view-transition-old(octc-main){animation:octc-swap-out var(--octc-motion-base) var(--octc-motion-ease) both}::view-transition-new(octc-main){animation:octc-swap-in var(--octc-motion-base) var(--octc-motion-ease) both}}@keyframes octc-swap-out{to{opacity:0;transform:translateY(-0.5rem)}}@keyframes octc-swap-in{from{opacity:0;transform:translateY(0.75rem)}}@supports (animation-timeline:view()){@media (prefers-reduced-motion:no-preference){.content>h2,.feature-card{animation:octc-rise linear both;animation-timeline:view();animation-range:entry 0% entry 55%}.features-grid>:nth-child(2n){animation-range:entry 3% entry 58%}.features-grid>:nth-child(3n){animation-range:entry 6% entry 62%}.hero-name,.hero-text,.hero-tagline,.hero-actions{animation:octc-hero-in var(--octc-motion-slow) var(--octc-motion-spring) both}.hero-text{animation-delay:90ms}.hero-tagline{animation-delay:160ms}.hero-actions{animation-delay:240ms}}}@keyframes octc-rise{from{opacity:0;transform:translateY(var(--octc-motion-rise,0.6rem))}}@keyframes octc-hero-in{from{opacity:0;transform:translateY(var(--octc-motion-rise,0.6rem)) scale(0.994)}}.nav-link,.toc-link,.feature-card,.hero-action,.social-link,.search-button,.theme-toggle,.content a{transition:color var(--octc-motion-fast) var(--octc-motion-ease),background-color var(--octc-motion-fast) var(--octc-motion-ease),border-color var(--octc-motion-fast) var(--octc-motion-ease),box-shadow var(--octc-motion-base) var(--octc-motion-ease),transform var(--octc-motion-base) var(--octc-motion-spring)}.search-modal-overlay{transition:opacity var(--octc-motion-base) var(--octc-motion-ease),display var(--octc-motion-base) allow-discrete}@media (prefers-reduced-motion:no-preference){.search-modal{transition:transform var(--octc-motion-base) var(--octc-motion-spring)}@starting-style{.search-modal{transform:translateY(-0.75rem) scale(0.97)}}}@media (prefers-reduced-motion:reduce){*,*::before,*::after{animation-duration:1ms !important;animation-iteration-count:1 !important;transition-duration:1ms !important;scroll-behavior:auto !important}}.entry-page .main{padding-inline:0}.entry-page .layout{padding-top:0}.entry-content{max-width:none;padding-inline:0}.entry-content>.content,.features{max-width:var(--octc-max-content-width);margin-inline:auto;padding-inline:clamp(1.25rem,4vw,2rem)}.hero{padding-inline:clamp(1.5rem,6vw,4rem);background-color:transparent;border-bottom:0;min-height:min(100vh,56rem);padding-block:calc(var(--octc-header-height) + clamp(3rem,11vh,7rem)) clamp(3.5rem,11vh,7rem)}.hero::after{content:\"\";position:absolute;inset:auto 0 0 0;height:clamp(6rem,22vh,14rem);z-index:0;pointer-events:none;background:linear-gradient(to bottom,transparent,var(--octc-color-bg))}.hero-content,.hero-notice,.hero-image{position:relative;z-index:1}.features{border-top:0}.header{background:transparent;border-bottom:0;box-shadow:none}body:not(.entry-page) .header{backdrop-filter:blur(14px) saturate(150%);-webkit-backdrop-filter:blur(14px) saturate(150%)}@supports (animation-timeline:scroll()){.header{animation:var(--octc-header-scroll,octc-header-settle) linear both;animation-timeline:scroll(root block);animation-range:0 7rem}}@keyframes octc-header-settle{from{backdrop-filter:blur(0px) saturate(100%);-webkit-backdrop-filter:blur(0px) saturate(100%)}to{backdrop-filter:blur(14px) saturate(150%);-webkit-backdrop-filter:blur(14px) saturate(150%)}}.sidebar,.toc{top:0;padding-top:calc(var(--octc-header-height) + 1rem)}.header-actions .social-link,.header-actions .theme-toggle{opacity:0.62;transition:opacity var(--octc-motion-fast) var(--octc-motion-ease)}.header:hover .social-link,.header:hover .theme-toggle,.header-actions :focus-visible{opacity:1}.content h1,.content h2,.content h3,.hero-name,.hero-text,.feature-title{text-wrap:balance}.content p,.content li,.hero-tagline,.feature-details{text-wrap:pretty}.content{hanging-punctuation:first last}.content table{font-variant-numeric:tabular-nums}.content a{text-underline-offset:0.18em;text-decoration-skip-ink:auto}.content h1 a,.content h2 a,.content h3 a{text-decoration:none}::selection{background:color-mix(in srgb,var(--octc-color-primary) 28%,transparent);color:var(--octc-color-text)}.sidebar,.toc,.content pre,.search-results{scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--octc-color-text) 22%,transparent) transparent}.content pre{scroll-padding-inline:1rem}.content img,.content video,.content iframe{max-width:100%;height:auto}[data-theme=\"dark\"] body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}@media (prefers-color-scheme:dark){:root:not([data-theme=\"light\"]) body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}}:root{--octc-fb-radius:10px;--octc-fb-thread:color-mix(in srgb,var(--octc-color-text) 7%,transparent);--octc-fb-weave:repeating-linear-gradient(0deg,var(--octc-fb-thread) 0 1px,transparent 1px 3px),repeating-linear-gradient(90deg,var(--octc-fb-thread) 0 1px,transparent 1px 3px);--octc-fb-stitch:color-mix(in srgb,var(--octc-color-primary) 55%,var(--octc-color-border))}.header,.sidebar,.toc,.search-modal,.content pre,.content blockquote,.feature-card,.hero-action,.hero-notice,.social-link,.search-button,.theme-toggle,.content img{border-radius:var(--octc-fb-radius)}body{background-image:var(--octc-fb-weave)}.header{border-radius:0;background-image:var(--octc-fb-weave);background-color:color-mix(in srgb,var(--octc-color-bg) 94%,var(--octc-color-bg-alt));border-bottom:2px dashed var(--octc-fb-stitch)}.sidebar{border-radius:0;background-image:var(--octc-fb-weave);--octc-skin-sidebar-bg:color-mix(in srgb,var(--octc-color-bg-alt) 24%,var(--octc-color-bg));background:color-mix(in srgb,var(--octc-color-bg-alt) 24%,var(--octc-color-bg));border-right:2px dashed var(--octc-fb-stitch)}.toc{border-left:2px dashed var(--octc-fb-stitch)}.nav-title,.toc-title{letter-spacing:0.12em;text-transform:uppercase;font-size:0.68rem}.nav-link,.toc-link{border-radius:8px}.nav-link:hover,.toc-link:hover{--octc-skin-nav-hover-bg:color-mix(in srgb,var(--octc-color-primary) 10%,transparent);background:color-mix(in srgb,var(--octc-color-primary) 10%,transparent);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--octc-fb-stitch) 45%,transparent);transform:translateX(3px)}.nav-link.active{background:color-mix(in srgb,var(--octc-color-primary) 18%,transparent);box-shadow:inset 0 0 0 1px var(--octc-fb-stitch);font-weight:600}.search-button,.theme-toggle,.social-link,.hero-action,.feature-card{border:1px solid color-mix(in srgb,var(--octc-color-border) 80%,transparent);box-shadow:inset 0 0 0 1px transparent,0 2px 6px -3px rgba(0,0,0,0.28);background-image:var(--octc-fb-weave);background-color:color-mix(in srgb,var(--octc-color-bg) 96%,var(--octc-color-bg-alt));position:relative}.search-button::before,.theme-toggle::before,.social-link::before,.hero-action::before,.feature-card::before{content:\"\";position:absolute;inset:4px;border:1px dashed color-mix(in srgb,var(--octc-fb-stitch) 45%,transparent);border-radius:inherit;pointer-events:none;opacity:0;transition:opacity var(--octc-motion-base) var(--octc-motion-ease)}.search-button:hover::before,.theme-toggle:hover::before,.social-link:hover::before,.hero-action:hover::before,.feature-card:hover::before{opacity:1}.search-button:hover,.theme-toggle:hover,.social-link:hover{transform:translateY(-2px)}.search-modal{border:2px dashed var(--octc-fb-stitch);background-image:var(--octc-fb-weave);background-color:var(--octc-color-bg)}.content h2{border-bottom:2px dashed var(--octc-fb-stitch)}.content a{text-decoration-style:dotted;text-underline-offset:3px}.content a:hover{text-decoration-style:solid}.content pre{border:1px solid var(--octc-color-code-frame-border)}.content :not(pre)>code{border-radius:5px;padding:0.1em 0.36em}.content blockquote{border:2px dashed var(--octc-fb-stitch);border-left-width:5px;border-left-style:solid;background-image:var(--octc-fb-weave);background-color:color-mix(in srgb,var(--octc-color-bg-alt) 88%,var(--octc-color-primary) 12%)}.content table{border:1px dashed color-mix(in srgb,var(--octc-fb-stitch) 62%,transparent)}.content th,.content td{border:1px dashed color-mix(in srgb,var(--octc-fb-stitch) 28%,transparent)}.content hr{border:0;height:0;border-top:2px dashed var(--octc-fb-stitch)}.content img{border:6px solid color-mix(in srgb,var(--octc-color-bg-alt) 70%,var(--octc-color-bg))}.hero{background-image:var(--octc-fb-weave);background-color:color-mix(in srgb,var(--octc-color-bg) 88%,var(--octc-color-bg-alt));border-bottom:2px dashed var(--octc-fb-stitch)}.hero-name{letter-spacing:-0.025em}.hero-action{font-weight:620}.hero-action:hover{transform:translateY(-3px);box-shadow:0 10px 22px -12px rgba(0,0,0,0.4)}.features .feature-card{padding:1.3rem}.features .feature-card:hover{transform:translateY(-4px) rotate(-0.35deg);box-shadow:0 16px 34px -18px rgba(0,0,0,0.35)}:focus-visible{outline:2px dashed var(--octc-color-primary);outline-offset:3px}.hero-action-brand,.hero-action-brand:hover,.hero-action-brand:focus-visible{background:var(--octc-brand-fill,var(--octc-color-primary));border-color:var(--octc-brand-edge,var(--octc-color-primary));color:var(--octc-brand-ink,var(--octc-color-bg))}.hero-action-brand:hover{background:var(--octc-brand-fill-hover,var(--octc-color-primary-hover));border-color:var(--octc-brand-edge-hover,var(--octc-color-primary-hover))}.hero-action-alt,.hero-action-alt:hover{color:var(--octc-color-text)}:root:root .sidebar{background:var(\n--octc-skin-sidebar-bg,color-mix(in srgb,var(--octc-color-bg-alt) 16%,var(--octc-color-bg))\n)}:root:root .nav-link:not(.active):hover{background:var(\n--octc-skin-nav-hover-bg,color-mix(in srgb,var(--octc-color-bg-alt) 58%,transparent)\n)}.header{border-bottom:0}.header{background-color:transparent;background-image:none;box-shadow:none}.content pre{box-shadow:none}`;\n","/**\n * Fabric WebGL2 hero backdrop.\n *\n * Progressive enhancement only: it bails out under prefers-reduced-motion,\n * on a missing WebGL2 context, and whenever the hero scrolls out of view.\n * The CSS backdrop underneath is the design; this layers on top of it.\n *\n * Generated from scripts/theme-skins/js/fabric.js; edit that file and\n * run node scripts/theme-skins/generate.mjs rather than editing this by hand.\n */\nexport const js =\n '(()=>{try{\\n// Shared WebGL2 runtime for the skins that render a live hero backdrop.\\n//\\n// Liquid Glass deliberately has none. A caustic field behind the page makes the\\n// *page* the glass, and the material belongs to the controls sitting on it —\\n// the refraction there is geometric, done with an SVG displacement map on the\\n// backdrop filter rather than painted underneath.\\n//\\n// The SSG concatenates a theme\\'s `js` into one classic inline <script>, so this\\n// cannot import anything — hence a hand-rolled runtime rather than a library.\\n// It is strictly progressive enhancement: the CSS backdrop is the real design,\\n// and the canvas only ever layers on top of it. Every bail-out path below\\n// leaves the page exactly as it renders without JavaScript.\\n\\nfunction octcGL(fragmentSource, options) {\\n const opts = options || {};\\n const host = document.querySelector(opts.target || \".hero\");\\n if (!host || typeof WebGL2RenderingContext === \"undefined\") return;\\n if (matchMedia(\"(prefers-reduced-motion: reduce)\").matches) return;\\n\\n const canvas = document.createElement(\"canvas\");\\n canvas.setAttribute(\"aria-hidden\", \"true\");\\n canvas.style.cssText =\\n \"position:absolute;inset:0;width:100%;height:100%;z-index:0;pointer-events:none;\" +\\n \"opacity:0;transition:opacity 900ms ease\";\\n const gl = canvas.getContext(\"webgl2\", {\\n alpha: true,\\n antialias: false,\\n depth: false,\\n stencil: false,\\n powerPreference: \"low-power\",\\n });\\n if (!gl) return;\\n\\n // A fullscreen triangle from gl_VertexID needs no buffers at all.\\n const VERT =\\n \"#version 300 es\\\\nvoid main(){vec2 p=vec2(float((gl_VertexID<<1)&2),float(gl_VertexID&2));\" +\\n \"gl_Position=vec4(p*2.0-1.0,0.0,1.0);}\";\\n\\n function compile(type, src) {\\n const sh = gl.createShader(type);\\n gl.shaderSource(sh, src);\\n gl.compileShader(sh);\\n if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {\\n gl.deleteShader(sh);\\n return null;\\n }\\n return sh;\\n }\\n\\n const vs = compile(gl.VERTEX_SHADER, VERT);\\n const fs = compile(gl.FRAGMENT_SHADER, fragmentSource);\\n if (!vs || !fs) return;\\n\\n const program = gl.createProgram();\\n gl.attachShader(program, vs);\\n gl.attachShader(program, fs);\\n gl.linkProgram(program);\\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) return;\\n gl.useProgram(program);\\n\\n const uRes = gl.getUniformLocation(program, \"u_res\");\\n const uTime = gl.getUniformLocation(program, \"u_time\");\\n const uColors = [0, 1, 2, 3].map((i) => gl.getUniformLocation(program, \"u_c\" + i));\\n\\n // Palette values are read through a probe element rather than parsed from the\\n // custom property, because a scheme may define one as color-mix(), which only\\n // resolves to concrete channels once it is used.\\n const probe = document.createElement(\"span\");\\n probe.style.cssText = \"position:absolute;width:0;height:0;opacity:0;pointer-events:none\";\\n host.appendChild(probe);\\n\\n function readColor(name, fallback) {\\n probe.style.color = fallback;\\n probe.style.color = \"var(\" + name + \",\" + fallback + \")\";\\n const value = getComputedStyle(probe).color;\\n const parts = value.match(/[\\\\d.]+/g);\\n if (!parts) return [0, 0, 0];\\n // `color-mix()` serialises as `color(srgb r g b)` with 0-1 channels.\\n const scale = value.indexOf(\"color(\") === 0 ? 1 : 255;\\n return [parts[0] / scale, parts[1] / scale, parts[2] / scale];\\n }\\n\\n const SLOTS = opts.colors || [\\n \"--octc-accent-a\",\\n \"--octc-accent-b\",\\n \"--octc-accent-c\",\\n \"--octc-color-bg\",\\n ];\\n\\n function pushColors() {\\n for (let i = 0; i < uColors.length; i++) {\\n if (!uColors[i]) continue;\\n const c = readColor(SLOTS[i], \"#888888\");\\n gl.uniform3f(uColors[i], c[0], c[1], c[2]);\\n }\\n }\\n\\n // Resolution is capped rather than tied to devicePixelRatio: this is a soft,\\n // out-of-focus backdrop, so a retina-sharp buffer costs fill rate for nothing.\\n let width = 0;\\n let height = 0;\\n function resize() {\\n const dpr = Math.min(devicePixelRatio || 1, 1.5);\\n const w = Math.max(1, Math.round(host.clientWidth * dpr));\\n const h = Math.max(1, Math.round(host.clientHeight * dpr));\\n if (w === width && h === height) return;\\n width = canvas.width = w;\\n height = canvas.height = h;\\n gl.viewport(0, 0, w, h);\\n gl.uniform2f(uRes, w, h);\\n }\\n\\n let running = false;\\n let visible = false;\\n let frame = 0;\\n const start = performance.now();\\n\\n function tick(now) {\\n if (!running) return;\\n resize();\\n gl.uniform1f(uTime, (now - start) / 1000);\\n gl.drawArrays(gl.TRIANGLES, 0, 3);\\n frame = requestAnimationFrame(tick);\\n }\\n\\n function setRunning(next) {\\n if (next === running) return;\\n running = next;\\n if (running) frame = requestAnimationFrame(tick);\\n else cancelAnimationFrame(frame);\\n }\\n\\n // Scrolled past or backgrounded means no work: an ambient effect must never\\n // keep a tab busy once it is off screen.\\n new IntersectionObserver((entries) => {\\n visible = entries[0].isIntersecting;\\n setRunning(visible && !document.hidden);\\n }).observe(host);\\n document.addEventListener(\"visibilitychange\", () => {\\n setRunning(visible && !document.hidden);\\n });\\n addEventListener(\"resize\", resize, { passive: true });\\n\\n // The scheme changes under us whenever the reader flips the theme toggle.\\n new MutationObserver(pushColors).observe(document.documentElement, {\\n attributes: true,\\n attributeFilter: [\"data-theme\"],\\n });\\n\\n host.insertBefore(canvas, host.firstChild);\\n pushColors();\\n resize();\\n // Set directly rather than from a frame callback: anything that throws\\n // between mount and that callback leaves the canvas permanently invisible,\\n // which is indistinguishable from the effect simply not existing.\\n canvas.style.opacity = opts.opacity == null ? \"1\" : String(opts.opacity);\\n}\\n\\n// Fabric — actual shading rather than a printed texture. The weave is built as a\\n// height field of interleaved warp and weft threads; that height gives a normal,\\n// and the normal is lit by a slowly orbiting key light. Threads catch the light\\n// along their own direction, which is what makes woven cloth read as cloth.\\noctcGL(\\n `#version 300 es\\nprecision highp float;\\nuniform vec2 u_res; uniform float u_time;\\nuniform vec3 u_c0, u_c1, u_c2, u_c3;\\nout vec4 outColor;\\n\\nfloat hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }\\n\\n// One cell holds a warp thread crossing a weft thread. Which one passes over\\n// alternates per cell, exactly as in a plain weave.\\nfloat weave(vec2 p){\\n vec2 cell = floor(p);\\n vec2 f = fract(p);\\n bool warpOver = mod(cell.x + cell.y, 2.0) < 1.0;\\n float warp = sin(f.x * 3.14159);\\n float weft = sin(f.y * 3.14159);\\n float over = warpOver ? warp : weft;\\n float under = warpOver ? weft : warp;\\n // Slight per-thread variation stops the weave reading as a printed grid.\\n float slub = 0.9 + 0.2 * hash(warpOver ? vec2(cell.x, 0.0) : vec2(0.0, cell.y));\\n return (over * 0.75 + under * 0.25) * slub;\\n}\\n\\nvoid main(){\\n vec2 uv = gl_FragCoord.xy / u_res;\\n vec2 p = uv * vec2(u_res.x / u_res.y, 1.0) * 190.0;\\n\\n float h = weave(p);\\n float e = 0.6;\\n vec3 n = normalize(vec3(\\n (weave(p + vec2(e, 0.0)) - h) / e,\\n (weave(p + vec2(0.0, e)) - h) / e,\\n 1.0\\n ));\\n\\n // A key light that drifts slowly, so the cloth catches the light as you read.\\n float a = u_time * 0.12;\\n vec3 light = normalize(vec3(cos(a) * 0.6, sin(a) * 0.6, 0.75));\\n float lambert = max(dot(n, light), 0.0);\\n float sheen = pow(max(dot(reflect(-light, n), vec3(0.0, 0.0, 1.0)), 0.0), 18.0);\\n\\n vec3 base = mix(u_c3, u_c0, 0.10);\\n vec3 col = base * (0.82 + lambert * 0.34) + sheen * 0.10;\\n\\n float edge = smoothstep(0.0, 0.2, uv.y) * smoothstep(1.0, 0.78, uv.y);\\n outColor = vec4(col, edge * 0.55);\\n}`,\\n { opacity: 0.7 },\\n);\\n\\n}catch(e){console.warn(\"[ox-content] theme backdrop disabled:\",e)}})();';\n","import type { ThemeConfig } from \"@ox-content/vite-plugin\";\n\nimport { css } from \"./skin\";\nimport { js } from \"./gl\";\n\n/**\n * Fabric — Woven texture, stitched seams and soft cloth-fold reveals.\n *\n * Form only: geometry, texture, typography and motion. It names no colors, so\n * it composes with any `@ox-content/theme-color-*` package:\n *\n * ```ts\n * theme: [fabric, tokyoNight]\n * ```\n */\nexport const fabric: ThemeConfig = {\n name: \"fabric\",\n fonts: {\n sans: 'ui-sans-serif, system-ui, -apple-system, \"Segoe UI Variable\", \"Segoe UI\", Roboto, \"Helvetica Neue\", sans-serif',\n mono: 'ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, \"Cascadia Mono\", Consolas, monospace',\n },\n layout: {\n sidebarWidth: \"264px\",\n headerHeight: \"64px\",\n maxContentWidth: \"900px\",\n },\n entryPage: { mode: \"default\" },\n tokens: {\n \"motion-fast\": \"220ms\",\n \"motion-base\": \"420ms\",\n \"motion-slow\": \"1000ms\",\n \"motion-ease\": \"cubic-bezier(0.33, 1, 0.68, 1)\",\n \"motion-spring\": \"cubic-bezier(0.34, 1.4, 0.64, 1)\",\n \"motion-rise\": \"0.62rem\",\n },\n css,\n js,\n};\n\nexport default fabric;\n"],"mappings":";;;;;;;;;;;;AEeA,MAAa,SAAsB;CACjC,MAAM;CACN,OAAO;EACL,MAAM;EACN,MAAM;CACR;CACA,QAAQ;EACN,cAAc;EACd,cAAc;EACd,iBAAiB;CACnB;CACA,WAAW,EAAE,MAAM,UAAU;CAC7B,QAAQ;EACN,eAAe;EACf,eAAe;EACf,eAAe;EACf,eAAe;EACf,iBAAiB;EACjB,eAAe;CACjB;CACA;;;;;CACA;AACF"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/skin.ts","../src/gl.ts","../src/index.ts"],"sourcesContent":["/**\n * Fabric skin stylesheet.\n *\n * Woven texture, stitched seams and soft cloth-fold reveals.\n *\n * Written entirely against `--octc-*` custom properties and `color-mix()`, plus\n * neutral black/white alpha for depth — no hue is ever named, which is what lets\n * any `@ox-content/theme-color-*` package drive it.\n *\n * Generated from `scripts/theme-skins/skins/fabric.css`; edit that file and\n * run `node scripts/theme-skins/generate.mjs` rather than editing this by hand.\n */\nexport const css = `@property --octc-sweep{syntax:\"<percentage>\";inherits:false;initial-value:0%}@property --octc-spin{syntax:\"<angle>\";inherits:false;initial-value:0deg}@view-transition{navigation:auto}.header{view-transition-name:octc-header}.sidebar{view-transition-name:octc-sidebar}.main{view-transition-name:octc-main}@media (prefers-reduced-motion:no-preference){::view-transition-old(octc-main){animation:octc-swap-out var(--octc-motion-base) var(--octc-motion-ease) both}::view-transition-new(octc-main){animation:octc-swap-in var(--octc-motion-base) var(--octc-motion-ease) both}}@keyframes octc-swap-out{to{opacity:0;transform:translateY(-0.5rem)}}@keyframes octc-swap-in{from{opacity:0;transform:translateY(0.75rem)}}@supports (animation-timeline:view()){@media (prefers-reduced-motion:no-preference){.content>h2,.feature-card{animation:octc-rise linear both;animation-timeline:view();animation-range:entry 0% entry 55%}.features-grid>:nth-child(2n){animation-range:entry 3% entry 58%}.features-grid>:nth-child(3n){animation-range:entry 6% entry 62%}.hero-name,.hero-text,.hero-tagline,.hero-actions{animation:octc-hero-in var(--octc-motion-slow) var(--octc-motion-spring) both}.hero-text{animation-delay:90ms}.hero-tagline{animation-delay:160ms}.hero-actions{animation-delay:240ms}}}@keyframes octc-rise{from{opacity:0;transform:translateY(var(--octc-motion-rise,0.6rem))}}@keyframes octc-hero-in{from{opacity:0;transform:translateY(var(--octc-motion-rise,0.6rem)) scale(0.994)}}.nav-link,.toc-link,.feature-card,.hero-action,.social-link,.search-button,.theme-toggle,.content a{transition:color var(--octc-motion-fast) var(--octc-motion-ease),background-color var(--octc-motion-fast) var(--octc-motion-ease),border-color var(--octc-motion-fast) var(--octc-motion-ease),box-shadow var(--octc-motion-base) var(--octc-motion-ease),transform var(--octc-motion-base) var(--octc-motion-spring)}.search-modal-overlay{transition:opacity var(--octc-motion-base) var(--octc-motion-ease),display var(--octc-motion-base) allow-discrete}@media (prefers-reduced-motion:no-preference){.search-modal{transition:transform var(--octc-motion-base) var(--octc-motion-spring)}@starting-style{.search-modal{transform:translateY(-0.75rem) scale(0.97)}}}@media (prefers-reduced-motion:reduce){*,*::before,*::after{animation-duration:1ms !important;animation-iteration-count:1 !important;transition-duration:1ms !important;scroll-behavior:auto !important}}.entry-page .main{padding-inline:0}.entry-page .layout{padding-top:0}.entry-content{max-width:none;padding-inline:0}.entry-content>.content,.features{max-width:var(--octc-max-content-width);margin-inline:auto;padding-inline:clamp(1.25rem,4vw,2rem)}.hero{padding-inline:clamp(1.5rem,6vw,4rem);background-color:transparent;border-bottom:0;min-height:min(100vh,56rem);padding-block:calc(var(--octc-header-height) + clamp(3rem,11vh,7rem)) clamp(3.5rem,11vh,7rem)}.hero::after{content:\"\";position:absolute;inset:auto 0 0 0;height:clamp(6rem,22vh,14rem);z-index:0;pointer-events:none;background:linear-gradient(to bottom,transparent,var(--octc-color-bg))}.hero-content,.hero-notice,.hero-image{position:relative;z-index:1}.features{border-top:0}.header{background:transparent;border-bottom:0;box-shadow:none}body:not(.entry-page) .header{backdrop-filter:blur(14px) saturate(150%);-webkit-backdrop-filter:blur(14px) saturate(150%)}@supports (animation-timeline:scroll()){.header{animation:var(--octc-header-scroll,octc-header-settle) linear both;animation-timeline:scroll(root block);animation-range:0 7rem}}@keyframes octc-header-settle{from{backdrop-filter:blur(0px) saturate(100%);-webkit-backdrop-filter:blur(0px) saturate(100%)}to{backdrop-filter:blur(14px) saturate(150%);-webkit-backdrop-filter:blur(14px) saturate(150%)}}.sidebar,.toc{top:0;padding-top:calc(var(--octc-header-height) + 1rem)}.header-actions .social-link,.header-actions .theme-toggle{opacity:0.62;transition:opacity var(--octc-motion-fast) var(--octc-motion-ease)}.header:hover .social-link,.header:hover .theme-toggle,.header-actions :focus-visible{opacity:1}.content h1,.content h2,.content h3,.hero-name,.hero-text,.feature-title{text-wrap:balance}.content p,.content li,.hero-tagline,.feature-details{text-wrap:pretty}.content{hanging-punctuation:first last}.content table{font-variant-numeric:tabular-nums}.content a{text-underline-offset:0.18em;text-decoration-skip-ink:auto}.content h1 a,.content h2 a,.content h3 a{text-decoration:none}::selection{background:color-mix(in srgb,var(--octc-color-primary) 28%,transparent);color:var(--octc-color-text)}.sidebar,.toc,.content pre,.search-results{scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--octc-color-text) 22%,transparent) transparent}.content pre{scroll-padding-inline:1rem}.content img,.content video,.content iframe{max-width:100%;height:auto}[data-theme=\"dark\"] body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}@media (prefers-color-scheme:dark){:root:not([data-theme=\"light\"]) body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}}:root{--octc-fb-radius:10px;--octc-fb-thread:color-mix(in srgb,var(--octc-color-text) 7%,transparent);--octc-fb-weave:repeating-linear-gradient(0deg,var(--octc-fb-thread) 0 1px,transparent 1px 3px),repeating-linear-gradient(90deg,var(--octc-fb-thread) 0 1px,transparent 1px 3px);--octc-fb-stitch:color-mix(in srgb,var(--octc-color-primary) 55%,var(--octc-color-border))}.header,.sidebar,.toc,.search-modal,.content pre,.content blockquote,.feature-card,.hero-action,.hero-notice,.social-link,.search-button,.theme-toggle,.content img{border-radius:var(--octc-fb-radius)}body{background-image:var(--octc-fb-weave)}.header{border-radius:0;background-image:var(--octc-fb-weave);background-color:color-mix(in srgb,var(--octc-color-bg) 94%,var(--octc-color-bg-alt));border-bottom:2px dashed var(--octc-fb-stitch)}.sidebar{border-radius:0;background-image:var(--octc-fb-weave);--octc-skin-sidebar-bg:color-mix(in srgb,var(--octc-color-bg-alt) 24%,var(--octc-color-bg));background:color-mix(in srgb,var(--octc-color-bg-alt) 24%,var(--octc-color-bg));border-right:2px dashed var(--octc-fb-stitch)}.toc{border-left:2px dashed var(--octc-fb-stitch)}.nav-title,.toc-title{letter-spacing:0.12em;text-transform:uppercase;font-size:0.68rem}.nav-link,.toc-link{border-radius:8px}.nav-link:hover,.toc-link:hover{--octc-skin-nav-hover-bg:color-mix(in srgb,var(--octc-color-primary) 10%,transparent);background:color-mix(in srgb,var(--octc-color-primary) 10%,transparent);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--octc-fb-stitch) 45%,transparent);transform:translateX(3px)}.nav-link.active{background:color-mix(in srgb,var(--octc-color-primary) 18%,transparent);box-shadow:inset 0 0 0 1px var(--octc-fb-stitch);font-weight:600}.search-button,.theme-toggle,.social-link,.hero-action,.feature-card{border:1px solid color-mix(in srgb,var(--octc-color-border) 80%,transparent);box-shadow:inset 0 0 0 1px transparent,0 2px 6px -3px rgba(0,0,0,0.28);background-image:var(--octc-fb-weave);background-color:color-mix(in srgb,var(--octc-color-bg) 96%,var(--octc-color-bg-alt));position:relative}.search-button::before,.theme-toggle::before,.social-link::before,.hero-action::before,.feature-card::before{content:\"\";position:absolute;inset:4px;border:1px dashed color-mix(in srgb,var(--octc-fb-stitch) 45%,transparent);border-radius:inherit;pointer-events:none;opacity:0;transition:opacity var(--octc-motion-base) var(--octc-motion-ease)}.search-button:hover::before,.theme-toggle:hover::before,.social-link:hover::before,.hero-action:hover::before,.feature-card:hover::before{opacity:1}.search-button:hover,.theme-toggle:hover,.social-link:hover{transform:translateY(-2px)}.search-modal{border:2px dashed var(--octc-fb-stitch);background-image:var(--octc-fb-weave);background-color:var(--octc-color-bg)}.content h2{border-bottom:2px dashed var(--octc-fb-stitch)}.content a{text-decoration-style:dotted;text-underline-offset:3px}.content a:hover{text-decoration-style:solid}.content pre{border:1px solid var(--octc-color-code-frame-border)}.content :not(pre)>code{border-radius:5px;padding:0.1em 0.36em}.content blockquote{border:2px dashed var(--octc-fb-stitch);border-left-width:5px;border-left-style:solid;background-image:var(--octc-fb-weave);background-color:color-mix(in srgb,var(--octc-color-bg-alt) 88%,var(--octc-color-primary) 12%)}.content table{border:1px dashed color-mix(in srgb,var(--octc-fb-stitch) 62%,transparent)}.content th,.content td{border:1px dashed color-mix(in srgb,var(--octc-fb-stitch) 28%,transparent)}.content hr{border:0;height:0;border-top:2px dashed var(--octc-fb-stitch)}.content img{border:6px solid color-mix(in srgb,var(--octc-color-bg-alt) 70%,var(--octc-color-bg))}.hero{background-image:var(--octc-fb-weave);background-color:color-mix(in srgb,var(--octc-color-bg) 88%,var(--octc-color-bg-alt));border-bottom:2px dashed var(--octc-fb-stitch)}.hero-name{letter-spacing:-0.025em}.hero-action{font-weight:620}.hero-action:hover{transform:translateY(-3px);box-shadow:0 10px 22px -12px rgba(0,0,0,0.4)}.features .feature-card{padding:1.3rem}.features .feature-card:hover{transform:translateY(-4px) rotate(-0.35deg);box-shadow:0 16px 34px -18px rgba(0,0,0,0.35)}:focus-visible{outline:2px dashed var(--octc-color-primary);outline-offset:3px}.hero-action-brand,.hero-action-brand:hover,.hero-action-brand:focus-visible{background:var(--octc-brand-fill,var(--octc-color-primary));border-color:var(--octc-brand-edge,var(--octc-color-primary));color:var(--octc-brand-ink,var(--octc-color-bg))}.hero-action-brand:hover{background:var(--octc-brand-fill-hover,var(--octc-color-primary-hover));border-color:var(--octc-brand-edge-hover,var(--octc-color-primary-hover))}.hero-action-alt,.hero-action-alt:hover{color:var(--octc-color-text)}:root:root .sidebar{background:var(\n--octc-skin-sidebar-bg,color-mix(in srgb,var(--octc-color-bg-alt) 16%,var(--octc-color-bg))\n)}:root:root .nav-link:not(.active):hover{background:var(\n--octc-skin-nav-hover-bg,color-mix(in srgb,var(--octc-color-bg-alt) 58%,transparent)\n)}.header{border-bottom:0}.header{background-color:transparent;background-image:none;box-shadow:none}.content pre{box-shadow:none}`;\n","/**\n * Fabric WebGL2 hero backdrop.\n *\n * Progressive enhancement only: it bails out under prefers-reduced-motion,\n * on a missing WebGL2 context, and whenever the hero scrolls out of view.\n * The CSS backdrop underneath is the design; this layers on top of it.\n *\n * Generated from scripts/theme-skins/js/fabric.js; edit that file and\n * run node scripts/theme-skins/generate.mjs rather than editing this by hand.\n */\nexport const js =\n '(()=>{try{\\n// Shared WebGL2 runtime for the skins that render a live hero backdrop.\\n//\\n// The SSG concatenates a theme\\'s `js` into one classic inline <script>, so this\\n// cannot import anything — hence a hand-rolled runtime rather than a library.\\n// It is strictly progressive enhancement: the CSS backdrop is the real design,\\n// and the canvas only ever layers on top of it. Every bail-out path below\\n// leaves the page exactly as it renders without JavaScript.\\n\\nfunction octcGL(fragmentSource, options) {\\n const opts = options || {};\\n const host = document.querySelector(opts.target || \".hero\");\\n if (!host || typeof WebGL2RenderingContext === \"undefined\") return;\\n if (matchMedia(\"(prefers-reduced-motion: reduce)\").matches) return;\\n\\n const canvas = document.createElement(\"canvas\");\\n canvas.setAttribute(\"aria-hidden\", \"true\");\\n canvas.style.cssText =\\n \"position:absolute;inset:0;width:100%;height:100%;z-index:0;pointer-events:none;\" +\\n \"opacity:0;transition:opacity 900ms ease\";\\n const gl = canvas.getContext(\"webgl2\", {\\n alpha: true,\\n antialias: false,\\n depth: false,\\n stencil: false,\\n powerPreference: \"low-power\",\\n });\\n if (!gl) return;\\n\\n // A fullscreen triangle from gl_VertexID needs no buffers at all.\\n const VERT =\\n \"#version 300 es\\\\nvoid main(){vec2 p=vec2(float((gl_VertexID<<1)&2),float(gl_VertexID&2));\" +\\n \"gl_Position=vec4(p*2.0-1.0,0.0,1.0);}\";\\n\\n function compile(type, src) {\\n const sh = gl.createShader(type);\\n gl.shaderSource(sh, src);\\n gl.compileShader(sh);\\n if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {\\n gl.deleteShader(sh);\\n return null;\\n }\\n return sh;\\n }\\n\\n const vs = compile(gl.VERTEX_SHADER, VERT);\\n const fs = compile(gl.FRAGMENT_SHADER, fragmentSource);\\n if (!vs || !fs) return;\\n\\n const program = gl.createProgram();\\n gl.attachShader(program, vs);\\n gl.attachShader(program, fs);\\n gl.linkProgram(program);\\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) return;\\n gl.useProgram(program);\\n\\n const uRes = gl.getUniformLocation(program, \"u_res\");\\n const uTime = gl.getUniformLocation(program, \"u_time\");\\n const uColors = [0, 1, 2, 3].map((i) => gl.getUniformLocation(program, \"u_c\" + i));\\n\\n // A skin that needs uniforms beyond the shared set (Liquid Glass feeds the\\n // rects of the panels it refracts) registers them here; the returned callback\\n // runs every frame before the draw, in canvas pixels.\\n const onFrame = opts.setup ? opts.setup(gl, program, host, canvas) : null;\\n\\n // Palette values are read through a probe element rather than parsed from the\\n // custom property, because a scheme may define one as color-mix(), which only\\n // resolves to concrete channels once it is used.\\n const probe = document.createElement(\"span\");\\n probe.style.cssText = \"position:absolute;width:0;height:0;opacity:0;pointer-events:none\";\\n host.appendChild(probe);\\n\\n function readColor(name, fallback) {\\n probe.style.color = fallback;\\n probe.style.color = \"var(\" + name + \",\" + fallback + \")\";\\n const value = getComputedStyle(probe).color;\\n const parts = value.match(/[\\\\d.]+/g);\\n if (!parts) return [0, 0, 0];\\n // `color-mix()` serialises as `color(srgb r g b)` with 0-1 channels.\\n const scale = value.indexOf(\"color(\") === 0 ? 1 : 255;\\n return [parts[0] / scale, parts[1] / scale, parts[2] / scale];\\n }\\n\\n const SLOTS = opts.colors || [\\n \"--octc-accent-a\",\\n \"--octc-accent-b\",\\n \"--octc-accent-c\",\\n \"--octc-color-bg\",\\n ];\\n\\n function pushColors() {\\n for (let i = 0; i < uColors.length; i++) {\\n if (!uColors[i]) continue;\\n const c = readColor(SLOTS[i], \"#888888\");\\n gl.uniform3f(uColors[i], c[0], c[1], c[2]);\\n }\\n }\\n\\n // Resolution is capped rather than tied to devicePixelRatio: this is a soft,\\n // out-of-focus backdrop, so a retina-sharp buffer costs fill rate for nothing.\\n let width = 0;\\n let height = 0;\\n function resize() {\\n const dpr = Math.min(devicePixelRatio || 1, 1.5);\\n const w = Math.max(1, Math.round(host.clientWidth * dpr));\\n const h = Math.max(1, Math.round(host.clientHeight * dpr));\\n if (w === width && h === height) return;\\n width = canvas.width = w;\\n height = canvas.height = h;\\n gl.viewport(0, 0, w, h);\\n gl.uniform2f(uRes, w, h);\\n }\\n\\n let running = false;\\n let visible = false;\\n let frame = 0;\\n const start = performance.now();\\n\\n function tick(now) {\\n if (!running) return;\\n resize();\\n gl.uniform1f(uTime, (now - start) / 1000);\\n if (onFrame) onFrame(now, width, height);\\n gl.drawArrays(gl.TRIANGLES, 0, 3);\\n frame = requestAnimationFrame(tick);\\n }\\n\\n function setRunning(next) {\\n if (next === running) return;\\n running = next;\\n if (running) frame = requestAnimationFrame(tick);\\n else cancelAnimationFrame(frame);\\n }\\n\\n // Scrolled past or backgrounded means no work: an ambient effect must never\\n // keep a tab busy once it is off screen.\\n new IntersectionObserver((entries) => {\\n visible = entries[0].isIntersecting;\\n setRunning(visible && !document.hidden);\\n }).observe(host);\\n document.addEventListener(\"visibilitychange\", () => {\\n setRunning(visible && !document.hidden);\\n });\\n addEventListener(\"resize\", resize, { passive: true });\\n\\n // The scheme changes under us whenever the reader flips the theme toggle.\\n new MutationObserver(pushColors).observe(document.documentElement, {\\n attributes: true,\\n attributeFilter: [\"data-theme\"],\\n });\\n\\n host.insertBefore(canvas, host.firstChild);\\n pushColors();\\n resize();\\n // Set directly rather than from a frame callback: anything that throws\\n // between mount and that callback leaves the canvas permanently invisible,\\n // which is indistinguishable from the effect simply not existing.\\n canvas.style.opacity = opts.opacity == null ? \"1\" : String(opts.opacity);\\n}\\n\\n// Fabric — actual shading rather than a printed texture. The weave is built as a\\n// height field of interleaved warp and weft threads; that height gives a normal,\\n// and the normal is lit by a slowly orbiting key light. Threads catch the light\\n// along their own direction, which is what makes woven cloth read as cloth.\\noctcGL(\\n `#version 300 es\\nprecision highp float;\\nuniform vec2 u_res; uniform float u_time;\\nuniform vec3 u_c0, u_c1, u_c2, u_c3;\\nout vec4 outColor;\\n\\nfloat hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }\\n\\n// One cell holds a warp thread crossing a weft thread. Which one passes over\\n// alternates per cell, exactly as in a plain weave.\\nfloat weave(vec2 p){\\n vec2 cell = floor(p);\\n vec2 f = fract(p);\\n bool warpOver = mod(cell.x + cell.y, 2.0) < 1.0;\\n float warp = sin(f.x * 3.14159);\\n float weft = sin(f.y * 3.14159);\\n float over = warpOver ? warp : weft;\\n float under = warpOver ? weft : warp;\\n // Slight per-thread variation stops the weave reading as a printed grid.\\n float slub = 0.9 + 0.2 * hash(warpOver ? vec2(cell.x, 0.0) : vec2(0.0, cell.y));\\n return (over * 0.75 + under * 0.25) * slub;\\n}\\n\\nvoid main(){\\n vec2 uv = gl_FragCoord.xy / u_res;\\n vec2 p = uv * vec2(u_res.x / u_res.y, 1.0) * 190.0;\\n\\n float h = weave(p);\\n float e = 0.6;\\n vec3 n = normalize(vec3(\\n (weave(p + vec2(e, 0.0)) - h) / e,\\n (weave(p + vec2(0.0, e)) - h) / e,\\n 1.0\\n ));\\n\\n // A key light that drifts slowly, so the cloth catches the light as you read.\\n float a = u_time * 0.12;\\n vec3 light = normalize(vec3(cos(a) * 0.6, sin(a) * 0.6, 0.75));\\n float lambert = max(dot(n, light), 0.0);\\n float sheen = pow(max(dot(reflect(-light, n), vec3(0.0, 0.0, 1.0)), 0.0), 18.0);\\n\\n vec3 base = mix(u_c3, u_c0, 0.10);\\n vec3 col = base * (0.82 + lambert * 0.34) + sheen * 0.10;\\n\\n float edge = smoothstep(0.0, 0.2, uv.y) * smoothstep(1.0, 0.78, uv.y);\\n outColor = vec4(col, edge * 0.55);\\n}`,\\n { opacity: 0.7 },\\n);\\n\\n}catch(e){console.warn(\"[ox-content] theme backdrop disabled:\",e)}})();';\n","import type { ThemeConfig } from \"@ox-content/vite-plugin\";\n\nimport { css } from \"./skin\";\nimport { js } from \"./gl\";\n\n/**\n * Fabric — Woven texture, stitched seams and soft cloth-fold reveals.\n *\n * Form only: geometry, texture, typography and motion. It names no colors, so\n * it composes with any `@ox-content/theme-color-*` package:\n *\n * ```ts\n * theme: [fabric, tokyoNight]\n * ```\n */\nexport const fabric: ThemeConfig = {\n name: \"fabric\",\n fonts: {\n sans: 'ui-sans-serif, system-ui, -apple-system, \"Segoe UI Variable\", \"Segoe UI\", Roboto, \"Helvetica Neue\", sans-serif',\n mono: 'ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, \"Cascadia Mono\", Consolas, monospace',\n },\n layout: {\n sidebarWidth: \"264px\",\n headerHeight: \"64px\",\n maxContentWidth: \"900px\",\n },\n entryPage: { mode: \"default\" },\n tokens: {\n \"motion-fast\": \"220ms\",\n \"motion-base\": \"420ms\",\n \"motion-slow\": \"1000ms\",\n \"motion-ease\": \"cubic-bezier(0.33, 1, 0.68, 1)\",\n \"motion-spring\": \"cubic-bezier(0.34, 1.4, 0.64, 1)\",\n \"motion-rise\": \"0.62rem\",\n },\n css,\n js,\n};\n\nexport default fabric;\n"],"mappings":";;;;;;;;;;;;AEeA,MAAa,SAAsB;CACjC,MAAM;CACN,OAAO;EACL,MAAM;EACN,MAAM;CACR;CACA,QAAQ;EACN,cAAc;EACd,cAAc;EACd,iBAAiB;CACnB;CACA,WAAW,EAAE,MAAM,UAAU;CAC7B,QAAQ;EACN,eAAe;EACf,eAAe;EACf,eAAe;EACf,eAAe;EACf,iBAAiB;EACjB,eAAe;CACjB;CACA;;;;;CACA;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ox-content/theme-fabric",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.88.0",
|
|
4
4
|
"description": "Woven texture, stitched seams and soft cloth-fold reveals — an Ox Content skin that composes with any @ox-content/theme-color-* scheme",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"craft",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"@typescript/native-preview": "^7.0.0-dev.20260707.2",
|
|
46
46
|
"typescript": "^7.0.2",
|
|
47
47
|
"vite-plus": "0.2.8",
|
|
48
|
-
"@ox-content/vite-plugin": "2.
|
|
48
|
+
"@ox-content/vite-plugin": "2.88.0"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"@ox-content/vite-plugin": ">=2.84.0"
|