@ox-content/theme-fluid 2.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.
- package/LICENSE +21 -0
- package/README.md +51 -0
- package/dist/index.cjs +54 -0
- package/dist/index.d.cts +16 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +16 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +51 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 ubugeeei
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# @ox-content/theme-fluid
|
|
2
|
+
|
|
3
|
+
Fluid — Organic blob gradients that drift and morph behind the content — for [Ox Content](https://github.com/ubugeeei-prod/ox-content).
|
|
4
|
+
|
|
5
|
+
**Form only.** Geometry, texture, typography and motion, written entirely
|
|
6
|
+
against `--octc-*` custom properties. It names no colors, so it pairs with any
|
|
7
|
+
`@ox-content/theme-color-*` scheme. About 9.7 kB of CSS, zero JavaScript, zero
|
|
8
|
+
runtime dependencies.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @ox-content/theme-fluid @ox-content/theme-color-tokyo-night
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
// vite.config.ts
|
|
16
|
+
import { defineConfig } from "vite";
|
|
17
|
+
import { oxContent } from "@ox-content/vite-plugin";
|
|
18
|
+
import fluid from "@ox-content/theme-fluid";
|
|
19
|
+
import tokyoNight from "@ox-content/theme-color-tokyo-night";
|
|
20
|
+
|
|
21
|
+
export default defineConfig({
|
|
22
|
+
plugins: [
|
|
23
|
+
oxContent({
|
|
24
|
+
srcDir: "docs",
|
|
25
|
+
ssg: { siteName: "My Docs", theme: [fluid, tokyoNight] },
|
|
26
|
+
}),
|
|
27
|
+
],
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Layers compose left to right, so anything you append wins:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
theme: [fluid, tokyoNight, { colors: { primary: "#ff5f56" } }];
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Motion
|
|
38
|
+
|
|
39
|
+
Transitions, scroll-driven reveals and cross-document page transitions are all
|
|
40
|
+
declarative CSS — no router, no observer, no client bundle. Everything sits
|
|
41
|
+
behind `@supports` and is switched off under `prefers-reduced-motion: reduce`.
|
|
42
|
+
|
|
43
|
+
Retune the choreography without touching the stylesheet:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
theme: [fluid, tokyoNight, { tokens: { "motion-base": "200ms", "motion-ease": "linear" } }];
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## License
|
|
50
|
+
|
|
51
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Object.defineProperties(exports, {
|
|
2
|
+
__esModule: { value: true },
|
|
3
|
+
[Symbol.toStringTag]: { value: "Module" }
|
|
4
|
+
});
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region src/index.ts
|
|
7
|
+
/**
|
|
8
|
+
* Fluid — Organic blob gradients that drift and morph behind the content.
|
|
9
|
+
*
|
|
10
|
+
* Form only: geometry, texture, typography and motion. It names no colors, so
|
|
11
|
+
* it composes with any `@ox-content/theme-color-*` package:
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* theme: [fluid, tokyoNight]
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
const fluid = {
|
|
18
|
+
name: "fluid",
|
|
19
|
+
fonts: {
|
|
20
|
+
sans: "\"SF Pro Rounded\", ui-rounded, \"Hiragino Maru Gothic ProN\", \"Varela Round\", system-ui, sans-serif",
|
|
21
|
+
mono: "ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, \"Cascadia Mono\", Consolas, monospace"
|
|
22
|
+
},
|
|
23
|
+
layout: {
|
|
24
|
+
sidebarWidth: "270px",
|
|
25
|
+
headerHeight: "68px",
|
|
26
|
+
maxContentWidth: "920px"
|
|
27
|
+
},
|
|
28
|
+
entryPage: { mode: "default" },
|
|
29
|
+
tokens: {
|
|
30
|
+
"motion-fast": "220ms",
|
|
31
|
+
"motion-base": "420ms",
|
|
32
|
+
"motion-slow": "1000ms",
|
|
33
|
+
"motion-ease": "cubic-bezier(0.65, 0, 0.35, 1)",
|
|
34
|
+
"motion-spring": "cubic-bezier(0.34, 1.56, 0.64, 1)",
|
|
35
|
+
"motion-rise": "0.88rem"
|
|
36
|
+
},
|
|
37
|
+
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-fl-blob:42% 58% 62% 38% / 44% 40% 60% 56%;--octc-fl-blob-alt:58% 42% 38% 62% / 56% 60% 40% 44%;--octc-fl-radius:26px}.header,.sidebar,.toc,.search-modal,.content pre,.content blockquote,.feature-card,.hero-notice,.social-link,.search-button,.theme-toggle,.content img,.content table{border-radius:var(--octc-fl-radius)}.header{border-radius:0 0 var(--octc-fl-radius) var(--octc-fl-radius);background:color-mix(in srgb,var(--octc-color-bg) 90%,transparent);backdrop-filter:blur(16px);border-bottom:1px solid color-mix(in srgb,var(--octc-color-border) 40%,transparent)}.sidebar{border-radius:0 var(--octc-fl-radius) var(--octc-fl-radius) 0;border-right:1px solid color-mix(in srgb,var(--octc-color-border) 35%,transparent);--octc-skin-sidebar-bg:color-mix(in srgb,var(--octc-color-bg-alt) 30%,var(--octc-color-bg));background:color-mix(in srgb,var(--octc-color-bg-alt) 30%,var(--octc-color-bg))}.toc{border-radius:var(--octc-fl-radius) 0 0 var(--octc-fl-radius);border-left:0}.nav-link,.toc-link{border-radius:999px;padding-left:0.85rem}.nav-link:hover,.toc-link:hover{--octc-skin-nav-hover-bg:color-mix(in srgb,var(--octc-color-primary) 12%,transparent);background:color-mix(in srgb,var(--octc-color-primary) 12%,transparent);transform:translateX(5px)}.nav-link.active{background:color-mix(in srgb,var(--octc-color-primary) 20%,transparent);font-weight:600}.search-button,.theme-toggle,.social-link,.mobile-footer-btn,.hero-action{border-radius:999px;border:1px solid color-mix(in srgb,var(--octc-color-border) 45%,transparent)}.search-button:hover,.theme-toggle:hover,.social-link:hover{transform:translateY(-2px) scale(1.04);background:color-mix(in srgb,var(--octc-color-primary) 12%,transparent)}.search-modal{border-radius:28px;box-shadow:0 34px 90px -34px rgba(0,0,0,0.45)}.search-input{border-radius:999px}.content h2{border-bottom:0;padding-bottom:0;position:relative}.content h2::after{content:"";position:absolute;left:0;bottom:-0.35rem;width:3.5rem;height:5px;border-radius:999px;background:linear-gradient(
|
|
38
|
+
90deg,var(--octc-accent-a,var(--octc-color-primary)),var(--octc-accent-b,var(--octc-color-primary))
|
|
39
|
+
)}.content :not(pre)>code{border-radius:999px;padding:0.14em 0.55em}.content blockquote{border:0;border-radius:var(--octc-fl-radius);background:color-mix(in srgb,var(--octc-color-primary) 10%,var(--octc-color-bg-alt));box-shadow:inset 4px 0 0 0 var(--octc-color-primary)}.content table{overflow:hidden}.content hr{border:0;height:5px;border-radius:999px;background:linear-gradient(
|
|
40
|
+
90deg,transparent,color-mix(in srgb,var(--octc-color-primary) 55%,transparent),transparent
|
|
41
|
+
)}.hero{position:relative;overflow:hidden}.hero::before{content:"";position:absolute;inset:-20% -15%;filter:blur(52px);opacity:0.34;pointer-events:none;background:radial-gradient(
|
|
42
|
+
38% 42% at 18% 22%,var(--octc-accent-a,var(--octc-color-primary)),transparent 70%
|
|
43
|
+
),radial-gradient(
|
|
44
|
+
34% 38% at 82% 78%,var(--octc-accent-b,var(--octc-color-primary)),transparent 70%
|
|
45
|
+
)}@media (prefers-reduced-motion:no-preference){.hero::before{animation:octc-fl-drift 38s ease-in-out infinite alternate}}@keyframes octc-fl-drift{0%{transform:translate3d(0,0,0) scale(1) rotate(0deg)}100%{transform:translate3d(4%,3%,0) scale(1.14) rotate(8deg)}}.hero-content{position:relative;z-index:1}.hero-name{letter-spacing:-0.035em}.hero-action{font-weight:650;padding-inline:2rem}.hero-action:hover{transform:translateY(-3px) scale(1.03)}.hero-notice{border-radius:var(--octc-fl-radius)}.features .feature-card{border:1px solid color-mix(in srgb,var(--octc-color-border) 40%,transparent);padding:1.4rem;background:color-mix(in srgb,var(--octc-color-bg-alt) 34%,var(--octc-color-bg))}.features .feature-card:hover{transform:translateY(-5px) scale(1.015);border-radius:34px;border-color:color-mix(in srgb,var(--octc-color-primary) 40%,transparent);box-shadow:0 26px 56px -28px rgba(0,0,0,0.4)}.feature-icon{border-radius:var(--octc-fl-blob)}:focus-visible{outline:2px solid var(--octc-color-primary);outline-offset:4px;border-radius:999px}.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(
|
|
46
|
+
--octc-skin-sidebar-bg,color-mix(in srgb,var(--octc-color-bg-alt) 16%,var(--octc-color-bg))
|
|
47
|
+
)}:root:root .nav-link:not(.active):hover{background:var(
|
|
48
|
+
--octc-skin-nav-hover-bg,color-mix(in srgb,var(--octc-color-bg-alt) 58%,transparent)
|
|
49
|
+
)}.header{border-bottom:0}.header{background-color:transparent;background-image:none;box-shadow:none}.content pre{box-shadow:none}`,
|
|
50
|
+
js: "(()=>{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// Fluid — dye carried by a curl-noise velocity field. Tracing the flow backwards\n// a few steps and sampling there is semi-Lagrangian advection, the same idea a\n// full fluid solver uses, minus the pressure solve it would need to be physical.\n// The result reads as liquid at a fraction of the cost.\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\nfloat noise(vec2 p){\n vec2 i = floor(p), f = fract(p);\n vec2 u = f * f * (3.0 - 2.0 * f);\n return mix(mix(hash(i), hash(i + vec2(1, 0)), u.x),\n mix(hash(i + vec2(0, 1)), hash(i + vec2(1, 1)), u.x), u.y);\n}\n\nfloat fbm(vec2 p){\n float v = 0.0, a = 0.5;\n for (int i = 0; i < 4; i++){ v += a * noise(p); p *= 2.03; a *= 0.5; }\n return v;\n}\n\n// Curl of a scalar field is divergence-free by construction, which is exactly\n// the property that makes the motion look like an incompressible fluid.\nvec2 curl(vec2 p, float t){\n float e = 0.04;\n float a = fbm(p + vec2(0.0, e) + t);\n float b = fbm(p - vec2(0.0, e) + t);\n float c = fbm(p + vec2(e, 0.0) + t);\n float d = fbm(p - vec2(e, 0.0) + t);\n return vec2(a - b, d - c) / (2.0 * e);\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);\n float t = u_time * 0.05;\n\n // Walk backwards along the velocity field to find where this dye came from.\n vec2 q = p;\n for (int i = 0; i < 4; i++) q -= curl(q * 1.5, t) * 0.022;\n\n float d0 = fbm(q * 1.9 + t * 0.6);\n float d1 = fbm(q * 2.4 - t * 0.45 + 9.1);\n\n vec3 col = u_c3;\n col = mix(col, u_c0, smoothstep(0.35, 0.85, d0) * 0.6);\n col = mix(col, u_c1, smoothstep(0.4, 0.9, d1) * 0.5);\n col = mix(col, u_c2, smoothstep(0.55, 0.95, d0 * d1) * 0.45);\n\n float edge = smoothstep(0.0, 0.25, uv.y) * smoothstep(1.0, 0.7, uv.y);\n outColor = vec4(col, edge * 0.8);\n}`,\n { opacity: 0.85 },\n);\n\n}catch(e){console.warn(\"[ox-content] theme backdrop disabled:\",e)}})();"
|
|
51
|
+
};
|
|
52
|
+
//#endregion
|
|
53
|
+
exports.default = fluid;
|
|
54
|
+
exports.fluid = fluid;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ThemeConfig } from "@ox-content/vite-plugin";
|
|
2
|
+
//#region src/index.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Fluid — Organic blob gradients that drift and morph behind the content.
|
|
5
|
+
*
|
|
6
|
+
* Form only: geometry, texture, typography and motion. It names no colors, so
|
|
7
|
+
* it composes with any `@ox-content/theme-color-*` package:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* theme: [fluid, tokyoNight]
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
declare const fluid: ThemeConfig;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { fluid as default, fluid };
|
|
16
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;;;;;cAea,OAAO"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ThemeConfig } from "@ox-content/vite-plugin";
|
|
2
|
+
//#region src/index.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Fluid — Organic blob gradients that drift and morph behind the content.
|
|
5
|
+
*
|
|
6
|
+
* Form only: geometry, texture, typography and motion. It names no colors, so
|
|
7
|
+
* it composes with any `@ox-content/theme-color-*` package:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* theme: [fluid, tokyoNight]
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
declare const fluid: ThemeConfig;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { fluid as default, fluid };
|
|
16
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;;;;;cAea,OAAO"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
//#endregion
|
|
2
|
+
//#region src/index.ts
|
|
3
|
+
/**
|
|
4
|
+
* Fluid — Organic blob gradients that drift and morph behind the content.
|
|
5
|
+
*
|
|
6
|
+
* Form only: geometry, texture, typography and motion. It names no colors, so
|
|
7
|
+
* it composes with any `@ox-content/theme-color-*` package:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* theme: [fluid, tokyoNight]
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
const fluid = {
|
|
14
|
+
name: "fluid",
|
|
15
|
+
fonts: {
|
|
16
|
+
sans: "\"SF Pro Rounded\", ui-rounded, \"Hiragino Maru Gothic ProN\", \"Varela Round\", system-ui, sans-serif",
|
|
17
|
+
mono: "ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, \"Cascadia Mono\", Consolas, monospace"
|
|
18
|
+
},
|
|
19
|
+
layout: {
|
|
20
|
+
sidebarWidth: "270px",
|
|
21
|
+
headerHeight: "68px",
|
|
22
|
+
maxContentWidth: "920px"
|
|
23
|
+
},
|
|
24
|
+
entryPage: { mode: "default" },
|
|
25
|
+
tokens: {
|
|
26
|
+
"motion-fast": "220ms",
|
|
27
|
+
"motion-base": "420ms",
|
|
28
|
+
"motion-slow": "1000ms",
|
|
29
|
+
"motion-ease": "cubic-bezier(0.65, 0, 0.35, 1)",
|
|
30
|
+
"motion-spring": "cubic-bezier(0.34, 1.56, 0.64, 1)",
|
|
31
|
+
"motion-rise": "0.88rem"
|
|
32
|
+
},
|
|
33
|
+
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-fl-blob:42% 58% 62% 38% / 44% 40% 60% 56%;--octc-fl-blob-alt:58% 42% 38% 62% / 56% 60% 40% 44%;--octc-fl-radius:26px}.header,.sidebar,.toc,.search-modal,.content pre,.content blockquote,.feature-card,.hero-notice,.social-link,.search-button,.theme-toggle,.content img,.content table{border-radius:var(--octc-fl-radius)}.header{border-radius:0 0 var(--octc-fl-radius) var(--octc-fl-radius);background:color-mix(in srgb,var(--octc-color-bg) 90%,transparent);backdrop-filter:blur(16px);border-bottom:1px solid color-mix(in srgb,var(--octc-color-border) 40%,transparent)}.sidebar{border-radius:0 var(--octc-fl-radius) var(--octc-fl-radius) 0;border-right:1px solid color-mix(in srgb,var(--octc-color-border) 35%,transparent);--octc-skin-sidebar-bg:color-mix(in srgb,var(--octc-color-bg-alt) 30%,var(--octc-color-bg));background:color-mix(in srgb,var(--octc-color-bg-alt) 30%,var(--octc-color-bg))}.toc{border-radius:var(--octc-fl-radius) 0 0 var(--octc-fl-radius);border-left:0}.nav-link,.toc-link{border-radius:999px;padding-left:0.85rem}.nav-link:hover,.toc-link:hover{--octc-skin-nav-hover-bg:color-mix(in srgb,var(--octc-color-primary) 12%,transparent);background:color-mix(in srgb,var(--octc-color-primary) 12%,transparent);transform:translateX(5px)}.nav-link.active{background:color-mix(in srgb,var(--octc-color-primary) 20%,transparent);font-weight:600}.search-button,.theme-toggle,.social-link,.mobile-footer-btn,.hero-action{border-radius:999px;border:1px solid color-mix(in srgb,var(--octc-color-border) 45%,transparent)}.search-button:hover,.theme-toggle:hover,.social-link:hover{transform:translateY(-2px) scale(1.04);background:color-mix(in srgb,var(--octc-color-primary) 12%,transparent)}.search-modal{border-radius:28px;box-shadow:0 34px 90px -34px rgba(0,0,0,0.45)}.search-input{border-radius:999px}.content h2{border-bottom:0;padding-bottom:0;position:relative}.content h2::after{content:"";position:absolute;left:0;bottom:-0.35rem;width:3.5rem;height:5px;border-radius:999px;background:linear-gradient(
|
|
34
|
+
90deg,var(--octc-accent-a,var(--octc-color-primary)),var(--octc-accent-b,var(--octc-color-primary))
|
|
35
|
+
)}.content :not(pre)>code{border-radius:999px;padding:0.14em 0.55em}.content blockquote{border:0;border-radius:var(--octc-fl-radius);background:color-mix(in srgb,var(--octc-color-primary) 10%,var(--octc-color-bg-alt));box-shadow:inset 4px 0 0 0 var(--octc-color-primary)}.content table{overflow:hidden}.content hr{border:0;height:5px;border-radius:999px;background:linear-gradient(
|
|
36
|
+
90deg,transparent,color-mix(in srgb,var(--octc-color-primary) 55%,transparent),transparent
|
|
37
|
+
)}.hero{position:relative;overflow:hidden}.hero::before{content:"";position:absolute;inset:-20% -15%;filter:blur(52px);opacity:0.34;pointer-events:none;background:radial-gradient(
|
|
38
|
+
38% 42% at 18% 22%,var(--octc-accent-a,var(--octc-color-primary)),transparent 70%
|
|
39
|
+
),radial-gradient(
|
|
40
|
+
34% 38% at 82% 78%,var(--octc-accent-b,var(--octc-color-primary)),transparent 70%
|
|
41
|
+
)}@media (prefers-reduced-motion:no-preference){.hero::before{animation:octc-fl-drift 38s ease-in-out infinite alternate}}@keyframes octc-fl-drift{0%{transform:translate3d(0,0,0) scale(1) rotate(0deg)}100%{transform:translate3d(4%,3%,0) scale(1.14) rotate(8deg)}}.hero-content{position:relative;z-index:1}.hero-name{letter-spacing:-0.035em}.hero-action{font-weight:650;padding-inline:2rem}.hero-action:hover{transform:translateY(-3px) scale(1.03)}.hero-notice{border-radius:var(--octc-fl-radius)}.features .feature-card{border:1px solid color-mix(in srgb,var(--octc-color-border) 40%,transparent);padding:1.4rem;background:color-mix(in srgb,var(--octc-color-bg-alt) 34%,var(--octc-color-bg))}.features .feature-card:hover{transform:translateY(-5px) scale(1.015);border-radius:34px;border-color:color-mix(in srgb,var(--octc-color-primary) 40%,transparent);box-shadow:0 26px 56px -28px rgba(0,0,0,0.4)}.feature-icon{border-radius:var(--octc-fl-blob)}:focus-visible{outline:2px solid var(--octc-color-primary);outline-offset:4px;border-radius:999px}.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(
|
|
42
|
+
--octc-skin-sidebar-bg,color-mix(in srgb,var(--octc-color-bg-alt) 16%,var(--octc-color-bg))
|
|
43
|
+
)}:root:root .nav-link:not(.active):hover{background:var(
|
|
44
|
+
--octc-skin-nav-hover-bg,color-mix(in srgb,var(--octc-color-bg-alt) 58%,transparent)
|
|
45
|
+
)}.header{border-bottom:0}.header{background-color:transparent;background-image:none;box-shadow:none}.content pre{box-shadow:none}`,
|
|
46
|
+
js: "(()=>{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// Fluid — dye carried by a curl-noise velocity field. Tracing the flow backwards\n// a few steps and sampling there is semi-Lagrangian advection, the same idea a\n// full fluid solver uses, minus the pressure solve it would need to be physical.\n// The result reads as liquid at a fraction of the cost.\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\nfloat noise(vec2 p){\n vec2 i = floor(p), f = fract(p);\n vec2 u = f * f * (3.0 - 2.0 * f);\n return mix(mix(hash(i), hash(i + vec2(1, 0)), u.x),\n mix(hash(i + vec2(0, 1)), hash(i + vec2(1, 1)), u.x), u.y);\n}\n\nfloat fbm(vec2 p){\n float v = 0.0, a = 0.5;\n for (int i = 0; i < 4; i++){ v += a * noise(p); p *= 2.03; a *= 0.5; }\n return v;\n}\n\n// Curl of a scalar field is divergence-free by construction, which is exactly\n// the property that makes the motion look like an incompressible fluid.\nvec2 curl(vec2 p, float t){\n float e = 0.04;\n float a = fbm(p + vec2(0.0, e) + t);\n float b = fbm(p - vec2(0.0, e) + t);\n float c = fbm(p + vec2(e, 0.0) + t);\n float d = fbm(p - vec2(e, 0.0) + t);\n return vec2(a - b, d - c) / (2.0 * e);\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);\n float t = u_time * 0.05;\n\n // Walk backwards along the velocity field to find where this dye came from.\n vec2 q = p;\n for (int i = 0; i < 4; i++) q -= curl(q * 1.5, t) * 0.022;\n\n float d0 = fbm(q * 1.9 + t * 0.6);\n float d1 = fbm(q * 2.4 - t * 0.45 + 9.1);\n\n vec3 col = u_c3;\n col = mix(col, u_c0, smoothstep(0.35, 0.85, d0) * 0.6);\n col = mix(col, u_c1, smoothstep(0.4, 0.9, d1) * 0.5);\n col = mix(col, u_c2, smoothstep(0.55, 0.95, d0 * d1) * 0.45);\n\n float edge = smoothstep(0.0, 0.25, uv.y) * smoothstep(1.0, 0.7, uv.y);\n outColor = vec4(col, edge * 0.8);\n}`,\n { opacity: 0.85 },\n);\n\n}catch(e){console.warn(\"[ox-content] theme backdrop disabled:\",e)}})();"
|
|
47
|
+
};
|
|
48
|
+
//#endregion
|
|
49
|
+
export { fluid as default, fluid };
|
|
50
|
+
|
|
51
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/skin.ts","../src/gl.ts","../src/index.ts"],"sourcesContent":["/**\n * Fluid skin stylesheet.\n *\n * Organic blob gradients that drift and morph behind the content.\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/fluid.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-fl-blob:42% 58% 62% 38% / 44% 40% 60% 56%;--octc-fl-blob-alt:58% 42% 38% 62% / 56% 60% 40% 44%;--octc-fl-radius:26px}.header,.sidebar,.toc,.search-modal,.content pre,.content blockquote,.feature-card,.hero-notice,.social-link,.search-button,.theme-toggle,.content img,.content table{border-radius:var(--octc-fl-radius)}.header{border-radius:0 0 var(--octc-fl-radius) var(--octc-fl-radius);background:color-mix(in srgb,var(--octc-color-bg) 90%,transparent);backdrop-filter:blur(16px);border-bottom:1px solid color-mix(in srgb,var(--octc-color-border) 40%,transparent)}.sidebar{border-radius:0 var(--octc-fl-radius) var(--octc-fl-radius) 0;border-right:1px solid color-mix(in srgb,var(--octc-color-border) 35%,transparent);--octc-skin-sidebar-bg:color-mix(in srgb,var(--octc-color-bg-alt) 30%,var(--octc-color-bg));background:color-mix(in srgb,var(--octc-color-bg-alt) 30%,var(--octc-color-bg))}.toc{border-radius:var(--octc-fl-radius) 0 0 var(--octc-fl-radius);border-left:0}.nav-link,.toc-link{border-radius:999px;padding-left:0.85rem}.nav-link:hover,.toc-link:hover{--octc-skin-nav-hover-bg:color-mix(in srgb,var(--octc-color-primary) 12%,transparent);background:color-mix(in srgb,var(--octc-color-primary) 12%,transparent);transform:translateX(5px)}.nav-link.active{background:color-mix(in srgb,var(--octc-color-primary) 20%,transparent);font-weight:600}.search-button,.theme-toggle,.social-link,.mobile-footer-btn,.hero-action{border-radius:999px;border:1px solid color-mix(in srgb,var(--octc-color-border) 45%,transparent)}.search-button:hover,.theme-toggle:hover,.social-link:hover{transform:translateY(-2px) scale(1.04);background:color-mix(in srgb,var(--octc-color-primary) 12%,transparent)}.search-modal{border-radius:28px;box-shadow:0 34px 90px -34px rgba(0,0,0,0.45)}.search-input{border-radius:999px}.content h2{border-bottom:0;padding-bottom:0;position:relative}.content h2::after{content:\"\";position:absolute;left:0;bottom:-0.35rem;width:3.5rem;height:5px;border-radius:999px;background:linear-gradient(\n90deg,var(--octc-accent-a,var(--octc-color-primary)),var(--octc-accent-b,var(--octc-color-primary))\n)}.content :not(pre)>code{border-radius:999px;padding:0.14em 0.55em}.content blockquote{border:0;border-radius:var(--octc-fl-radius);background:color-mix(in srgb,var(--octc-color-primary) 10%,var(--octc-color-bg-alt));box-shadow:inset 4px 0 0 0 var(--octc-color-primary)}.content table{overflow:hidden}.content hr{border:0;height:5px;border-radius:999px;background:linear-gradient(\n90deg,transparent,color-mix(in srgb,var(--octc-color-primary) 55%,transparent),transparent\n)}.hero{position:relative;overflow:hidden}.hero::before{content:\"\";position:absolute;inset:-20% -15%;filter:blur(52px);opacity:0.34;pointer-events:none;background:radial-gradient(\n38% 42% at 18% 22%,var(--octc-accent-a,var(--octc-color-primary)),transparent 70%\n),radial-gradient(\n34% 38% at 82% 78%,var(--octc-accent-b,var(--octc-color-primary)),transparent 70%\n)}@media (prefers-reduced-motion:no-preference){.hero::before{animation:octc-fl-drift 38s ease-in-out infinite alternate}}@keyframes octc-fl-drift{0%{transform:translate3d(0,0,0) scale(1) rotate(0deg)}100%{transform:translate3d(4%,3%,0) scale(1.14) rotate(8deg)}}.hero-content{position:relative;z-index:1}.hero-name{letter-spacing:-0.035em}.hero-action{font-weight:650;padding-inline:2rem}.hero-action:hover{transform:translateY(-3px) scale(1.03)}.hero-notice{border-radius:var(--octc-fl-radius)}.features .feature-card{border:1px solid color-mix(in srgb,var(--octc-color-border) 40%,transparent);padding:1.4rem;background:color-mix(in srgb,var(--octc-color-bg-alt) 34%,var(--octc-color-bg))}.features .feature-card:hover{transform:translateY(-5px) scale(1.015);border-radius:34px;border-color:color-mix(in srgb,var(--octc-color-primary) 40%,transparent);box-shadow:0 26px 56px -28px rgba(0,0,0,0.4)}.feature-icon{border-radius:var(--octc-fl-blob)}:focus-visible{outline:2px solid var(--octc-color-primary);outline-offset:4px;border-radius:999px}.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 * Fluid 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/fluid.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// Fluid — dye carried by a curl-noise velocity field. Tracing the flow backwards\\n// a few steps and sampling there is semi-Lagrangian advection, the same idea a\\n// full fluid solver uses, minus the pressure solve it would need to be physical.\\n// The result reads as liquid at a fraction of the cost.\\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\\nfloat noise(vec2 p){\\n vec2 i = floor(p), f = fract(p);\\n vec2 u = f * f * (3.0 - 2.0 * f);\\n return mix(mix(hash(i), hash(i + vec2(1, 0)), u.x),\\n mix(hash(i + vec2(0, 1)), hash(i + vec2(1, 1)), u.x), u.y);\\n}\\n\\nfloat fbm(vec2 p){\\n float v = 0.0, a = 0.5;\\n for (int i = 0; i < 4; i++){ v += a * noise(p); p *= 2.03; a *= 0.5; }\\n return v;\\n}\\n\\n// Curl of a scalar field is divergence-free by construction, which is exactly\\n// the property that makes the motion look like an incompressible fluid.\\nvec2 curl(vec2 p, float t){\\n float e = 0.04;\\n float a = fbm(p + vec2(0.0, e) + t);\\n float b = fbm(p - vec2(0.0, e) + t);\\n float c = fbm(p + vec2(e, 0.0) + t);\\n float d = fbm(p - vec2(e, 0.0) + t);\\n return vec2(a - b, d - c) / (2.0 * e);\\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);\\n float t = u_time * 0.05;\\n\\n // Walk backwards along the velocity field to find where this dye came from.\\n vec2 q = p;\\n for (int i = 0; i < 4; i++) q -= curl(q * 1.5, t) * 0.022;\\n\\n float d0 = fbm(q * 1.9 + t * 0.6);\\n float d1 = fbm(q * 2.4 - t * 0.45 + 9.1);\\n\\n vec3 col = u_c3;\\n col = mix(col, u_c0, smoothstep(0.35, 0.85, d0) * 0.6);\\n col = mix(col, u_c1, smoothstep(0.4, 0.9, d1) * 0.5);\\n col = mix(col, u_c2, smoothstep(0.55, 0.95, d0 * d1) * 0.45);\\n\\n float edge = smoothstep(0.0, 0.25, uv.y) * smoothstep(1.0, 0.7, uv.y);\\n outColor = vec4(col, edge * 0.8);\\n}`,\\n { opacity: 0.85 },\\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 * Fluid — Organic blob gradients that drift and morph behind the content.\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: [fluid, tokyoNight]\n * ```\n */\nexport const fluid: ThemeConfig = {\n name: \"fluid\",\n fonts: {\n sans: '\"SF Pro Rounded\", ui-rounded, \"Hiragino Maru Gothic ProN\", \"Varela Round\", system-ui, sans-serif',\n mono: 'ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, \"Cascadia Mono\", Consolas, monospace',\n },\n layout: {\n sidebarWidth: \"270px\",\n headerHeight: \"68px\",\n maxContentWidth: \"920px\",\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.65, 0, 0.35, 1)\",\n \"motion-spring\": \"cubic-bezier(0.34, 1.56, 0.64, 1)\",\n \"motion-rise\": \"0.88rem\",\n },\n css,\n js,\n};\n\nexport default fluid;\n"],"mappings":";;;;;;;;;;;;AEeA,MAAa,QAAqB;CAChC,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
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ox-content/theme-fluid",
|
|
3
|
+
"version": "2.84.0",
|
|
4
|
+
"description": "Organic blob gradients that drift and morph behind the content — an Ox Content skin that composes with any @ox-content/theme-color-* scheme",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"blob",
|
|
7
|
+
"documentation",
|
|
8
|
+
"fluid",
|
|
9
|
+
"gradient",
|
|
10
|
+
"markdown",
|
|
11
|
+
"mesh",
|
|
12
|
+
"organic",
|
|
13
|
+
"ox-content",
|
|
14
|
+
"skin",
|
|
15
|
+
"ssg",
|
|
16
|
+
"theme"
|
|
17
|
+
],
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"author": "ubugeeei",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/ubugeeei-prod/ox-content.git",
|
|
23
|
+
"directory": "npm/theme/fluid"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
28
|
+
"type": "module",
|
|
29
|
+
"sideEffects": false,
|
|
30
|
+
"main": "./dist/index.cjs",
|
|
31
|
+
"types": "./dist/index.d.mts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"import": "./dist/index.mjs",
|
|
35
|
+
"require": "./dist/index.cjs",
|
|
36
|
+
"types": "./dist/index.d.mts"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public",
|
|
41
|
+
"provenance": true
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^26.1.2",
|
|
45
|
+
"@typescript/native-preview": "^7.0.0-dev.20260707.2",
|
|
46
|
+
"typescript": "^7.0.2",
|
|
47
|
+
"vite-plus": "0.2.8",
|
|
48
|
+
"@ox-content/vite-plugin": "2.84.0"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@ox-content/vite-plugin": ">=2.84.0"
|
|
52
|
+
},
|
|
53
|
+
"peerDependenciesMeta": {
|
|
54
|
+
"@ox-content/vite-plugin": {
|
|
55
|
+
"optional": true
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "vp pack",
|
|
60
|
+
"dev": "vp pack --watch",
|
|
61
|
+
"typecheck": "tsgo --noEmit"
|
|
62
|
+
}
|
|
63
|
+
}
|