boxels-in-js 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,188 @@
1
+ # boxels
2
+
3
+ A tiny CSS 3D library for composing interactive boxel scenes from DOM divs. Per-face styling, edge fusion, textures, image mapping, click interactions, and spin animation — zero dependencies.
4
+
5
+ **[Live Demo](https://asitparida.github.io/boxels-in-js/)**
6
+
7
+ ## Quick Start
8
+
9
+ ```js
10
+ import { Boxels } from 'boxels'
11
+
12
+ const b = new Boxels({
13
+ boxelSize: 100,
14
+ gap: 1,
15
+ edgeWidth: 1,
16
+ camera: { rotation: [-25, 35] },
17
+ })
18
+
19
+ b.addBox({ position: [0, 0, 0], size: [2, 2, 2] })
20
+ b.mount(document.getElementById('scene'))
21
+ b.setTexture('glass', 220, 0.70)
22
+ ```
23
+
24
+ ## Features
25
+
26
+ ### Textures
27
+
28
+ 5 built-in material textures, controlled by hue and opacity:
29
+
30
+ ```js
31
+ b.setTexture('solid', 220, 1.0) // Opaque blocks with visible edges
32
+ b.setTexture('hollow', 220, 1.0) // Wireframe — transparent fill, colored edges
33
+ b.setTexture('glass', 220, 0.7) // Translucent with backdrop blur
34
+ b.setTexture('frosted', 220, 0.8) // Heavy blur, milky
35
+ b.setTexture('neon', 220, 1.0) // Dark blocks with bright glowing edges
36
+ ```
37
+
38
+ ### Geometry & Boolean Operations
39
+
40
+ ```js
41
+ b.addBox({ position: [0, 0, 0], size: [4, 4, 4] })
42
+ b.addSphere({ center: [2, 2, 2], radius: 1.5, mode: 'subtract' })
43
+ b.addLine({ from: [0, 0, 0], to: [5, 5, 5], radius: 1 })
44
+ b.removeBox({ position: [1, 1, 0], size: [2, 2, 1] })
45
+
46
+ // Boolean modes: 'union' (default), 'subtract', 'intersect', 'exclude'
47
+ ```
48
+
49
+ ### Per-Face Styling
50
+
51
+ ```js
52
+ b.addBox({
53
+ position: [0, 0, 0],
54
+ size: [3, 3, 3],
55
+ style: {
56
+ front: { fill: '#ff0000', stroke: '#333' },
57
+ back: { fill: '#ff8c00', stroke: '#333' },
58
+ left: { fill: '#00cc00', stroke: '#333' },
59
+ right: { fill: '#0000ff', stroke: '#333' },
60
+ top: { fill: '#ffff00', stroke: '#333' },
61
+ bottom: { fill: '#ffffff', stroke: '#333' },
62
+ }
63
+ })
64
+ ```
65
+
66
+ ### Functional Styles
67
+
68
+ ```js
69
+ b.addBox({
70
+ position: [0, 0, 0],
71
+ size: [6, 6, 6],
72
+ style: {
73
+ default: (x, y, z) => ({
74
+ fill: `oklch(${0.4 + y / 12} 0.15 ${x / 6 * 360})`,
75
+ stroke: 'transparent',
76
+ })
77
+ }
78
+ })
79
+ ```
80
+
81
+ ### Image Mapping
82
+
83
+ ```js
84
+ // One image distributed across all exposed faces
85
+ b.mapImage('landscape.jpg')
86
+
87
+ // Different image per face type
88
+ b.mapImage('icon-x.png', 'front')
89
+ b.mapImage('icon-github.png', 'back')
90
+ ```
91
+
92
+ ### Edge Fusion
93
+
94
+ When `gap: 0`, internal faces between adjacent boxels are culled and shared edges merge — only the outer silhouette remains. A 10x10x10 cube (1,000 boxels) renders ~600 faces instead of 6,000.
95
+
96
+ ### Animation
97
+
98
+ ```js
99
+ // Continuous spin
100
+ b.startSpin({ x: true, y: true, speed: 3 })
101
+ b.stopSpin()
102
+
103
+ // Gap animation
104
+ b.animateGap({ from: 0, to: 10, duration: 800 })
105
+ ```
106
+
107
+ ### Click Interaction
108
+
109
+ ```js
110
+ b.enableClick(({ boxel, face }) => {
111
+ console.log(`Clicked ${face} face at [${boxel}]`)
112
+ })
113
+ // Plays a dip animation + active flash on the clicked face
114
+ // Pauses spin during interaction, resumes after
115
+
116
+ b.disableClick()
117
+ ```
118
+
119
+ ### Axes
120
+
121
+ ```js
122
+ b.showAxes() // L/R (red), T/B (blue), F/Bk (green) through center
123
+ b.hideAxes()
124
+ ```
125
+
126
+ ### Positioning
127
+
128
+ ```js
129
+ // Fixed position on page
130
+ const b = new Boxels({
131
+ position: { x: '50%', y: 100, zIndex: 999, fixed: true }
132
+ })
133
+
134
+ // Update at runtime
135
+ b.setPosition({ x: 200, y: 300 })
136
+ ```
137
+
138
+ ### Camera
139
+
140
+ ```js
141
+ b.updateTransform(-30, 45) // rotateX, rotateY in degrees
142
+ b.getRotation() // { rotX, rotY }
143
+ ```
144
+
145
+ ## Constructor Options
146
+
147
+ ```js
148
+ new Boxels({
149
+ boxelSize: 50, // Size of each boxel in px
150
+ gap: 0, // Gap between boxels in px
151
+ edgeWidth: 1, // Border width in px
152
+ edgeColor: '#333', // Default border color
153
+ camera: {
154
+ type: 'perspective',
155
+ distance: 1200,
156
+ rotation: [-25, 35], // [rotX, rotY] in degrees
157
+ },
158
+ orbit: true, // Drag to rotate
159
+ zoom: true, // Scroll to zoom
160
+ showBackfaces: false, // Show internal faces for translucent styles
161
+ })
162
+ ```
163
+
164
+ ## Project Structure
165
+
166
+ ```
167
+ boxels-in-js/
168
+ src/lib/ # The library (zero dependencies)
169
+ core/ # Grid, boolean ops, geometry, style, edge fusion, textures
170
+ renderers/dom/ # CSS 3D renderer, orbit controls, axes
171
+ animation/ # RAF animator, explode, tweens, layer rotation
172
+ presets/ # Built-in style presets
173
+ src/react/ # React 19+ wrapper
174
+ docs/ # Interactive showcase app
175
+ ```
176
+
177
+ ## Development
178
+
179
+ ```bash
180
+ npm install
181
+ npm run dev # Docs app at localhost:5173
182
+ npm run build:lib # Build library to dist/lib/
183
+ npm test # Run tests
184
+ ```
185
+
186
+ ## License
187
+
188
+ MIT
@@ -0,0 +1,27 @@
1
+ export interface AnimationConfig {
2
+ duration: number;
3
+ tick: (t: number) => void;
4
+ onComplete?: () => void;
5
+ easing?: (t: number) => number;
6
+ loop?: boolean;
7
+ }
8
+ export interface AnimationHandle {
9
+ cancel: () => void;
10
+ readonly done: boolean;
11
+ }
12
+ export declare const easings: {
13
+ linear: (t: number) => number;
14
+ easeInOut: (t: number) => number;
15
+ easeOut: (t: number) => number;
16
+ easeIn: (t: number) => number;
17
+ bounce: (t: number) => number;
18
+ };
19
+ export declare function parseCubicBezier(str: string): ((t: number) => number) | null;
20
+ export declare class Animator {
21
+ private animations;
22
+ private rafId;
23
+ add(config: AnimationConfig): AnimationHandle;
24
+ cancelAll(): void;
25
+ private start;
26
+ private tick;
27
+ }
@@ -0,0 +1,10 @@
1
+ import type { Vec3, BoxelRenderer } from '../types';
2
+ import type { BoxelGrid } from '../core/grid';
3
+ import type { Animator, AnimationHandle } from './animator';
4
+ export declare function computeExplodeOffsets(positions: Vec3[], center: Vec3, factor: number): Vec3[];
5
+ export declare function createExplodeAnimation(grid: BoxelGrid, renderer: BoxelRenderer, animator: Animator, boxelSize: number, options?: {
6
+ factor?: number;
7
+ stagger?: number;
8
+ easing?: string;
9
+ duration?: number;
10
+ }): AnimationHandle;
@@ -0,0 +1,4 @@
1
+ import type { LayerRotateOptions, BoxelRenderer } from '../types';
2
+ import type { BoxelGrid } from '../core/grid';
3
+ import type { Animator, AnimationHandle } from './animator';
4
+ export declare function createLayerRotateAnimation(grid: BoxelGrid, renderer: BoxelRenderer, animator: Animator, options: LayerRotateOptions, onComplete: () => void): AnimationHandle;
@@ -0,0 +1,6 @@
1
+ import type { AnimateEachCallback, AnimateEachOptions, TweenOptions, BoxelRenderer } from '../types';
2
+ import type { BoxelGrid } from '../core/grid';
3
+ import type { Animator, AnimationHandle } from './animator';
4
+ export declare function createGapTween(animator: Animator, onGapChange: (gap: number) => void, options: TweenOptions): AnimationHandle;
5
+ export declare function createOpacityTween(animator: Animator, renderer: BoxelRenderer, options: TweenOptions): AnimationHandle;
6
+ export declare function createEachTween(grid: BoxelGrid, renderer: BoxelRenderer, animator: Animator, _boxelSize: number, callback: AnimateEachCallback, options?: AnimateEachOptions): AnimationHandle;
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e={right:[1,0,0],left:[-1,0,0],top:[0,1,0],bottom:[0,-1,0],front:[0,0,1],back:[0,0,-1]},t=[`top`,`bottom`,`front`,`back`,`left`,`right`];function n(e,t,n){return`${e},${t},${n}`}var r=class{data=new Map;get count(){return this.data.size}hasBoxel(e,t,r){return this.data.has(n(e,t,r))}getBoxel(e,t,r){return this.data.get(n(e,t,r))??null}setBoxel(e,t,r,i){let a=n(e,t,r);i===null?this.data.delete(a):this.data.set(a,i)}getNeighbors(n,r,i){let a={};for(let o of t){let[t,s,c]=e[o];a[o]=this.getBoxel(n+t,r+s,i+c)}return a}getExposure(n,r,i){let a=0;for(let o of t){let[t,s,c]=e[o];this.hasBoxel(n+t,r+s,i+c)||a++}return a}forEach(e){for(let[t,n]of this.data)e(n,t.split(`,`).map(Number))}getBounds(){if(this.data.size===0)return{min:[0,0,0],max:[0,0,0]};let e=1/0,t=1/0,n=1/0,r=-1/0,i=-1/0,a=-1/0;for(let o of this.data.keys()){let[s,c,l]=o.split(`,`).map(Number);s<e&&(e=s),c<t&&(t=c),l<n&&(n=l),s>r&&(r=s),c>i&&(i=c),l>a&&(a=l)}return{min:[e,t,n],max:[r,i,a]}}clear(){this.data.clear()}};function i(e,t,n,r){switch(n){case`union`:for(let[n,i,a]of t)e.setBoxel(n,i,a,{position:[n,i,a],opaque:!0,style:r});break;case`subtract`:for(let[n,r,i]of t)e.setBoxel(n,r,i,null);break;case`intersect`:{let n=new Set(t.map(([e,t,n])=>`${e},${t},${n}`)),r=[];e.forEach((e,t)=>{let i=`${t[0]},${t[1]},${t[2]}`;n.has(i)||r.push(t)});for(let[t,n,i]of r)e.setBoxel(t,n,i,null);break}case`exclude`:for(let[n,i,a]of t)e.hasBoxel(n,i,a)?e.setBoxel(n,i,a,null):e.setBoxel(n,i,a,{position:[n,i,a],opaque:!0,style:r});break}}function a(e,t){let[n,r,i]=e,[a,o,s]=t,c=[];for(let e=n;e<n+a;e++)for(let t=r;t<r+o;t++)for(let n=i;n<i+s;n++)c.push([e,t,n]);return c}function o(e,t){let[n,r,i]=e,a=t,o=[];for(let e=Math.floor(n-a);e<=Math.ceil(n+a);e++)for(let t=Math.floor(r-a);t<=Math.ceil(r+a);t++)for(let s=Math.floor(i-a);s<=Math.ceil(i+a);s++){let c=e-n,l=t-r,u=s-i;c*c+l*l+u*u<=a*a&&o.push([e,t,s])}return o}function s(e,t,n=0){let[r,i,a]=e,[s,c,l]=t,u=s-r,d=c-i,f=l-a,p=Math.max(Math.abs(u),Math.abs(d),Math.abs(f));if(p===0)return n===0?[e]:o(e,n);let m=[],h=new Set;for(let e=0;e<=p;e++){let t=e/p,n=Math.round(r+u*t),o=Math.round(i+d*t),s=Math.round(a+f*t),c=`${n},${o},${s}`;h.has(c)||(h.add(c),m.push([n,o,s]))}if(n===0)return m;let g=new Set,_=[];for(let e of m){let t=o(e,n);for(let e of t){let t=`${e[0]},${e[1]},${e[2]}`;g.has(t)||(g.add(t),_.push(e))}}return _}function c(e,t,n,r){switch(e){case`x`:return[t,-r,n];case`y`:return[r,n,-t];case`z`:return[-n,t,r]}}function l(e,t,n,r=[0,0,0]){let i=(n%4+4)%4;if(i===0)return;let a=[];e.forEach((e,t)=>{a.push([t,e])}),e.clear();for(let[n,o]of a){let a=n[0]-r[0],s=n[1]-r[1],l=n[2]-r[2];for(let e=0;e<i;e++)[a,s,l]=c(t,a,s,l);let u=[Math.round(a+r[0]),Math.round(s+r[1]),Math.round(l+r[2])];e.setBoxel(u[0],u[1],u[2],{...o,position:u})}}var u={right:[1,0,0],left:[-1,0,0],top:[0,1,0],bottom:[0,-1,0],front:[0,0,1],back:[0,0,-1]},d={top:{Left:[-1,0,0],Right:[1,0,0],Top:[0,0,-1],Bottom:[0,0,1]},bottom:{Left:[-1,0,0],Right:[1,0,0],Top:[0,0,1],Bottom:[0,0,-1]},front:{Left:[-1,0,0],Right:[1,0,0],Top:[0,1,0],Bottom:[0,-1,0]},back:{Left:[1,0,0],Right:[-1,0,0],Top:[0,1,0],Bottom:[0,-1,0]},left:{Left:[0,0,1],Right:[0,0,-1],Top:[0,1,0],Bottom:[0,-1,0]},right:{Left:[0,0,-1],Right:[0,0,1],Top:[0,1,0],Bottom:[0,-1,0]}},f=[`top`,`bottom`,`front`,`back`,`left`,`right`];function p(e,t,n,r){let i=[];for(let a of f){let[o,s,c]=u[a];e.hasBoxel(t+o,n+s,r+c)||i.push(a)}return i}function m(e,t,n,r,i){let a=d[i];return{left:!h(e,t,n,r,i,a.Left),right:!h(e,t,n,r,i,a.Right),top:!h(e,t,n,r,i,a.Top),bottom:!h(e,t,n,r,i,a.Bottom)}}function h(e,t,n,r,i,a){let o=t+a[0],s=n+a[1],c=r+a[2];if(!e.hasBoxel(o,s,c))return!1;let l=u[i];return!e.hasBoxel(o+l[0],s+l[1],c+l[2])}function g(e,t,n,r,i,a,o){switch(e){case`front`:return{col:t,row:a-1-n,cols:i,rows:a};case`back`:return{col:i-1-t,row:a-1-n,cols:i,rows:a};case`left`:return{col:r,row:a-1-n,cols:o,rows:a};case`right`:return{col:o-1-r,row:a-1-n,cols:o,rows:a};case`top`:return{col:t,row:r,cols:i,rows:o};case`bottom`:return{col:t,row:o-1-r,cols:i,rows:o}}}function _(e,t,n,r,i,a,o){let{col:s,row:c,cols:l,rows:u}=g(e,t,n,r,i,a,o);return{backgroundSize:`${l*100}% ${u*100}%`,backgroundPosition:`${l>1?s/(l-1)*100:0}% ${u>1?c/(u-1)*100:0}%`}}function v(e,t){let n=[],r=e.getBounds(),i=r.max[0]-r.min[0]+1,a=r.max[1]-r.min[1]+1,o=r.max[2]-r.min[2]+1;return e.forEach((s,c)=>{let[l,u,d]=c,f=p(e,l,u,d);for(let e of f){if(t&&e!==t)continue;let s=_(e,l-r.min[0],u-r.min[1],d-r.min[2],i,a,o);n.push({position:c,face:e,...s})}}),n}var y={fill:`#ddd`,stroke:`#333`,opacity:1};function b(e,t,n,r){return typeof e==`function`?e(t,n,r):e}function x(e,t,n,r){if(e!==void 0)return typeof e==`function`?e(t,n,r):e}function S(e,t,n,r){if(!e)return{};let i={};return e.fill!==void 0&&(i.fill=b(e.fill,t,n,r)),e.stroke!==void 0&&(i.stroke=b(e.stroke,t,n,r)),e.opacity!==void 0&&(i.opacity=e.opacity),e.className!==void 0&&(i.className=e.className),e.backdropFilter!==void 0&&(i.backdropFilter=e.backdropFilter),i}function C(e,t,n,r,i,a){let o={...y};if(a){let i=x(a.default,t,n,r);Object.assign(o,S(i,t,n,r));let s=x(a[e],t,n,r);Object.assign(o,S(s,t,n,r))}if(i){let a=x(i.default,t,n,r);Object.assign(o,S(a,t,n,r));let s=x(i[e],t,n,r);Object.assign(o,S(s,t,n,r))}return o}function w(e,t,n,r){e.style.borderStyle=`solid`,e.style.borderLeftWidth=t.left?`${n}px`:`0`,e.style.borderRightWidth=t.right?`${n}px`:`0`,e.style.borderTopWidth=t.top?`${n}px`:`0`,e.style.borderBottomWidth=t.bottom?`${n}px`:`0`,e.style.borderLeftColor=t.left?r:`transparent`,e.style.borderRightColor=t.right?r:`transparent`,e.style.borderTopColor=t.top?r:`transparent`,e.style.borderBottomColor=t.bottom?r:`transparent`}var T={front:e=>`translateZ(${e/2}px)`,back:e=>`rotateY(180deg) translateZ(${e/2}px)`,left:e=>`rotateY(-90deg) translateZ(${e/2}px)`,right:e=>`rotateY(90deg) translateZ(${e/2}px)`,top:e=>`rotateX(90deg) translateZ(${e/2}px)`,bottom:e=>`rotateX(-90deg) translateZ(${e/2}px)`};function E(e,t,n,r,i,a,o){let s=document.createElement(`div`);return s.dataset.face=e,s.dataset.faceIndex=e,s.style.position=`absolute`,s.style.width=`${t}px`,s.style.height=`${t}px`,s.style.transform=T[e](t),s.style.backfaceVisibility=o?`visible`:`hidden`,s.style.boxSizing=`border-box`,s.style.willChange=`transform`,s.style.contain=`layout style paint`,s.style.backgroundColor=n.fill,s.style.opacity=String(n.opacity),n.backdropFilter&&(s.style.backdropFilter=n.backdropFilter,s.style.webkitBackdropFilter=n.backdropFilter),n.className&&(s.className=n.className),w(s,r,i,n.stroke===`transparent`?a:n.stroke),s}function D(e,t,n,r,i,a,o){let s=document.createElement(`div`);s.dataset.boxel=`${e[0]},${e[1]},${e[2]}`,s.dataset.x=String(e[0]),s.dataset.y=String(e[1]),s.dataset.z=String(e[2]),s.style.position=`absolute`,s.style.width=`${t}px`,s.style.height=`${t}px`,s.style.transformStyle=`preserve-3d`,s.style.willChange=`transform`;let[c,l,u]=e,d=t+n;s.style.transform=`translate3d(${c*d}px, ${-l*d}px, ${u*d}px)`;for(let e of r){let n=E(e.name,t,e.style,e.edges,i,a,o);s.appendChild(n)}return s}var O=class{rotX;rotY;scale;isDragging=!1;lastPointerX=0;lastPointerY=0;onChange;container=null;boundPointerDown=this.onPointerDown.bind(this);boundPointerMove=this.onPointerMove.bind(this);boundPointerUp=this.onPointerUp.bind(this);boundWheel=this.onWheel.bind(this);constructor(e=[-25,35],t){this.rotX=e[0],this.rotY=e[1],this.scale=1,this.onChange=t}attach(e){this.container=e,e.addEventListener(`pointerdown`,this.boundPointerDown),e.addEventListener(`wheel`,this.boundWheel,{passive:!1})}detach(){this.container&&=(this.container.removeEventListener(`pointerdown`,this.boundPointerDown),this.container.removeEventListener(`wheel`,this.boundWheel),document.removeEventListener(`pointermove`,this.boundPointerMove),document.removeEventListener(`pointerup`,this.boundPointerUp),null)}zoomEnabled=!0;disableZoom(){this.zoomEnabled=!1}setState(e,t){this.rotX=e,this.rotY=t}getState(){return{rotX:this.rotX,rotY:this.rotY,scale:this.scale}}onPointerDown(e){e.button===0&&(this.isDragging=!0,this.lastPointerX=e.clientX,this.lastPointerY=e.clientY,document.addEventListener(`pointermove`,this.boundPointerMove),document.addEventListener(`pointerup`,this.boundPointerUp),e.preventDefault())}onPointerMove(e){if(!this.isDragging)return;let t=e.clientX-this.lastPointerX,n=e.clientY-this.lastPointerY;this.lastPointerX=e.clientX,this.lastPointerY=e.clientY,this.rotY+=t*.5,this.rotX-=n*.5,this.onChange({rotX:this.rotX,rotY:this.rotY,scale:this.scale})}onPointerUp(){this.isDragging=!1,document.removeEventListener(`pointermove`,this.boundPointerMove),document.removeEventListener(`pointerup`,this.boundPointerUp)}onWheel(e){if(!this.zoomEnabled)return;e.preventDefault();let t=e.deltaY>0?.95:1.05;this.scale=Math.max(.1,Math.min(5,this.scale*t)),this.onChange({rotX:this.rotX,rotY:this.rotY,scale:this.scale})}},k=class{sceneEl=null;worldEl=null;containerEl=null;orbitControls=null;options;currentRotX=-25;currentRotY=35;constructor(e={}){this.options=e}mount(e){this.containerEl=e,this.sceneEl=document.createElement(`div`),this.sceneEl.style.perspective=`${this.options.cameraDistance??1200}px`,this.sceneEl.style.overflow=`hidden`,this.sceneEl.style.transformStyle=`preserve-3d`;let t=this.options.position;t?(this.sceneEl.style.position=t.fixed?`fixed`:`absolute`,t.x!==void 0&&(this.sceneEl.style.left=typeof t.x==`number`?`${t.x}px`:t.x),t.y!==void 0&&(this.sceneEl.style.top=typeof t.y==`number`?`${t.y}px`:t.y),t.zIndex!==void 0&&(this.sceneEl.style.zIndex=String(t.zIndex)),this.sceneEl.style.width=`auto`,this.sceneEl.style.height=`auto`):(this.sceneEl.style.position=`relative`,this.sceneEl.style.width=`100%`,this.sceneEl.style.height=`100%`),this.worldEl=document.createElement(`div`),this.worldEl.style.position=`absolute`,this.worldEl.style.top=`50%`,this.worldEl.style.left=`50%`,this.worldEl.style.transformStyle=`preserve-3d`,this.worldEl.style.willChange=`transform`;let[n,r]=this.options.cameraRotation??[-25,35];this.currentRotX=n,this.currentRotY=r,this.worldEl.style.transform=`rotateX(${n}deg) rotateY(${r}deg)`,this.sceneEl.appendChild(this.worldEl),e.appendChild(this.sceneEl),this.options.orbit!==!1&&(this.orbitControls=new O(this.options.cameraRotation??[-25,35],e=>{this.currentRotX=e.rotX,this.currentRotY=e.rotY,this.worldEl&&(this.worldEl.style.transform=`rotateX(${e.rotX}deg) rotateY(${e.rotY}deg) scale(${e.scale})`)}),this.options.zoom===!1&&this.orbitControls.disableZoom(),this.orbitControls.attach(this.sceneEl))}unmount(){this.orbitControls&&=(this.orbitControls.detach(),null),this.sceneEl&&this.containerEl&&this.containerEl.removeChild(this.sceneEl),this.sceneEl=null,this.worldEl=null,this.containerEl=null}render(e){if(!this.worldEl)return;this.worldEl.innerHTML=``;let{grid:t,boxelSize:n,gap:r,edgeWidth:i,edgeColor:a,globalStyle:o,showBackfaces:s}=e,c=t.getBounds(),l=n+r,u=n/2,d=(c.min[0]+c.max[0])/2*l+u,f=-(c.min[1]+c.max[1])/2*l+u,h=(c.min[2]+c.max[2])/2*l,g=document.createElement(`div`);g.style.position=`absolute`,g.style.transformStyle=`preserve-3d`,g.style.transform=`translate3d(${-d}px, ${-f}px, ${-h}px)`,this.worldEl.appendChild(g);let _=[`top`,`bottom`,`front`,`back`,`left`,`right`];t.forEach((e,c)=>{let[l,u,d]=c,f=s?_:p(t,l,u,d);if(f.length===0)return;let h=D(c,n,r,f.map(n=>{let i=r===0&&!s?m(t,l,u,d,n):{left:!0,right:!0,top:!0,bottom:!0};return{name:n,style:C(n,l,u,d,e.style,o),edges:i}}),i,a,s);g.appendChild(h)})}updateTransform(e,t){this.currentRotX=e,this.currentRotY=t,this.orbitControls&&this.orbitControls.setState(e,t),this.worldEl&&(this.worldEl.style.transform=`rotateX(${e}deg) rotateY(${t}deg)`)}updateGap(e){}updateOpacity(e){this.worldEl&&this.worldEl.style.setProperty(`--face-opacity`,String(e))}setBoxelTransform(e,t,n,r){if(!this.worldEl)return;let i=this.worldEl.querySelector(`[data-boxel="${e}"]`);if(!i)return;let a=i.style.transform;(t[0]!==0||t[1]!==0||t[2]!==0)&&(i.style.transform=a+` translate3d(${t[0]}px, ${-t[1]}px, ${t[2]}px)`),n!==void 0&&(i.style.transform+=` scale(${n})`),r!==void 0&&(i.style.opacity=String(r))}getWorldContainer(){return this.worldEl}getSceneElement(){return this.sceneEl}getOrbitState(){return{rotX:this.currentRotX,rotY:this.currentRotY,scale:1}}dispose(){this.unmount()}};function A(e,t,n){let r=document.createElement(`div`);return r.className=`boxel-axis-label`,r.textContent=e,r.style.position=`absolute`,r.style.color=t,r.style.fontSize=`16px`,r.style.fontFamily=`monospace`,r.style.fontWeight=`700`,r.style.transform=n,r.style.pointerEvents=`none`,r.style.textShadow=`0 0 16px ${t}, 0 2px 8px rgba(0,0,0,0.9)`,r.style.whiteSpace=`nowrap`,r}function j(e){let t=document.createElement(`div`);t.className=`boxel-axes`,t.style.position=`absolute`,t.style.transformStyle=`preserve-3d`,t.style.pointerEvents=`none`;let n=`rgba(255, 100, 100, 0.9)`,r=`rgba(100, 180, 255, 0.9)`,i=`rgba(100, 255, 160, 0.9)`,a=document.createElement(`div`);a.style.position=`absolute`,a.style.width=`${e*2}px`,a.style.height=`4px`,a.style.background=`linear-gradient(90deg, transparent, ${n} 10%, ${n} 90%, transparent)`,a.style.transform=`translate3d(${-e}px, -2px, 2px)`,t.appendChild(a),t.appendChild(A(`L`,n,`translate3d(${-e-22}px, -10px, 2px)`)),t.appendChild(A(`R`,n,`translate3d(${e+8}px, -10px, 2px)`));let o=document.createElement(`div`);o.style.position=`absolute`,o.style.width=`4px`,o.style.height=`${e*2}px`,o.style.background=`linear-gradient(180deg, transparent, ${r} 10%, ${r} 90%, transparent)`,o.style.transform=`translate3d(-2px, ${-e}px, 2px)`,t.appendChild(o),t.appendChild(A(`T`,r,`translate3d(-10px, ${-e-26}px, 2px)`)),t.appendChild(A(`B`,r,`translate3d(-10px, ${e+8}px, 2px)`));let s=document.createElement(`div`);s.style.position=`absolute`,s.style.width=`${e*2}px`,s.style.height=`4px`,s.style.left=`${-e}px`,s.style.top=`-2px`,s.style.background=`linear-gradient(90deg, transparent, ${i} 10%, ${i} 90%, transparent)`,s.style.transformOrigin=`${e}px 2px`,s.style.transform=`rotateY(90deg)`,t.appendChild(s);let c=document.createElement(`div`);c.style.position=`absolute`,c.style.width=`4px`,c.style.height=`${e*2}px`,c.style.left=`-2px`,c.style.top=`${-e}px`,c.style.background=`linear-gradient(180deg, transparent, ${i} 10%, ${i} 90%, transparent)`,c.style.transformOrigin=`2px ${e}px`,c.style.transform=`rotateX(90deg)`,t.appendChild(c),t.appendChild(A(`F`,i,`translate3d(-8px, -10px, ${e+12}px)`)),t.appendChild(A(`Bk`,i,`translate3d(-12px, -10px, ${-e-28}px)`));let l=document.createElement(`div`);return l.style.position=`absolute`,l.style.width=`10px`,l.style.height=`10px`,l.style.borderRadius=`50%`,l.style.background=`rgba(255,255,255,0.7)`,l.style.boxShadow=`0 0 12px rgba(255,255,255,0.5)`,l.style.transform=`translate3d(-5px, -5px, 2px)`,t.appendChild(l),t}var M={linear:e=>e,easeInOut:e=>e<.5?2*e*e:1-(-2*e+2)**2/2,easeOut:e=>1-(1-e)**3,easeIn:e=>e*e*e,bounce:e=>{let t=7.5625,n=2.75;return e<1/n?t*e*e:e<2/n?t*(e-=1.5/n)*e+.75:e<2.5/n?t*(e-=2.25/n)*e+.9375:t*(e-=2.625/n)*e+.984375}};function N(e){let t=e.match(/cubic-bezier\(\s*([^,]+),\s*([^,]+),\s*([^,]+),\s*([^)]+)\)/);if(!t)return null;let[,n,r,i,a]=t,o=parseFloat(r),s=parseFloat(a);return e=>{let t=1-e,n=3*t*t*e,r=3*t*e*e,i=e*e*e;return n*o+r*s+i}}var P=class{animations=[];rafId=null;add(e){let t={config:e,startTime:null,cancelled:!1};return this.animations.push(t),this.start(),{cancel:()=>{t.cancelled=!0},get done(){return t.cancelled}}}cancelAll(){for(let e of this.animations)e.cancelled=!0;this.animations=[],this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null)}start(){this.rafId===null&&(this.rafId=requestAnimationFrame(this.tick))}tick=e=>{let t=[];for(let n=0;n<this.animations.length;n++){let r=this.animations[n];if(r.cancelled){t.push(n);continue}r.startTime===null&&(r.startTime=e);let i=e-r.startTime,a=r.config.duration,o=Math.min(i/a,1);r.config.loop&&o>=1&&(r.startTime=e,o=0);let s=(r.config.easing??M.linear)(o);r.config.tick(s),o>=1&&!r.config.loop&&(r.config.tick(1),r.config.onComplete?.(),t.push(n))}for(let e=t.length-1;e>=0;e--)this.animations.splice(t[e],1);this.animations.length>0?this.rafId=requestAnimationFrame(this.tick):this.rafId=null}};function F(e,t,n){return e.map(([e,r,i])=>{let a=e-t[0],o=r-t[1],s=i-t[2];return[a*n,o*n,s*n]})}function I(e,t,n,r,i={}){let a=i.factor??2,o=i.duration??800,s=[];e.forEach((e,t)=>s.push(t));let c=e.getBounds(),l=F(s,[(c.min[0]+c.max[0])/2,(c.min[1]+c.max[1])/2,(c.min[2]+c.max[2])/2],a),u=M.easeOut;if(i.easing){let e=N(i.easing);e&&(u=e)}return n.add({duration:o,easing:u,tick:e=>{for(let n=0;n<s.length;n++){let[i,a,o]=l[n],c=`${s[n][0]},${s[n][1]},${s[n][2]}`;t.setBoxelTransform(c,[i*e*r,a*e*r,o*e*r])}}})}function L(e,t,n,i,a){let{axis:o,layer:s,direction:c,duration:u=350}=i,d=t.getWorldContainer();if(!d)return{cancel:()=>{},done:!0};let f=o===`x`?0:o===`y`?1:2,p=[];if(e.forEach((e,t)=>{t[f]===s&&p.push(t)}),p.length===0)return{cancel:()=>{},done:!0};let m=document.createElement(`div`);m.style.position=`absolute`,m.style.transformStyle=`preserve-3d`,d.appendChild(m);let h=[];for(let e of p){let t=`${e[0]},${e[1]},${e[2]}`,n=d.querySelector(`[data-boxel="${t}"]`);n&&(h.push(n),m.appendChild(n))}let g=o===`x`?`rotateX`:o===`y`?`rotateY`:`rotateZ`,_=c*90;return n.add({duration:u,easing:M.easeInOut,tick:e=>{m.style.transform=`${g}(${_*e}deg)`},onComplete:()=>{for(let e of h)d.appendChild(e);m.remove();let t=new r;for(let n of p){let r=e.getBoxel(n[0],n[1],n[2]);r&&(t.setBoxel(n[0],n[1],n[2],r),e.setBoxel(n[0],n[1],n[2],null))}l(t,o,c),t.forEach((t,n)=>{e.setBoxel(n[0],n[1],n[2],t)}),a()}})}function R(e){return e?e in M?M[e]:N(e)??M.easeInOut:M.easeInOut}function z(e,t,n){let{from:r,to:i,duration:a=600}=n,o=R(n.easing);return e.add({duration:a,easing:o,tick:e=>{t(r+(i-r)*e)}})}function B(e,t,n){let{from:r,to:i,duration:a=600}=n,o=R(n.easing);return e.add({duration:a,easing:o,tick:e=>{let n=r+(i-r)*e;t.updateOpacity(n)}})}function V(e,t,n,r,i,a={}){let{duration:o=2e3,loop:s=!1}=a,c=R(a.easing);return n.add({duration:o,easing:c,loop:s,tick:n=>{e.forEach((e,r)=>{let a=i(e,r,n),o=`${r[0]},${r[1]},${r[2]}`;t.setBoxelTransform(o,a.translate??[0,0,0],a.scale,a.opacity)})}})}var H={heerich(e,t,n){return{default:(e,t,n)=>{let r=.75+t*.02;return{fill:`oklch(${r} 0.03 80)`,stroke:`oklch(${r-.15} 0.03 80)`}},top:{fill:`oklch(0.85 0.02 80)`,stroke:`oklch(0.65 0.03 80)`}}},rubik(e,t,n){return{default:{fill:`#111`,stroke:`#000`},top:{fill:`#ffff00`,stroke:`#333`},bottom:{fill:`#ffffff`,stroke:`#333`},front:{fill:`#ff0000`,stroke:`#333`},back:{fill:`#ff8c00`,stroke:`#333`},left:{fill:`#00ff00`,stroke:`#333`},right:{fill:`#0000ff`,stroke:`#333`}}},gradient(e,t,n){return{default:(r,i,a)=>{let o=r/Math.max(e,1)*360,s=.4+i/Math.max(t,1)*.4,c=.1+a/Math.max(n,1)*.1;return{fill:`oklch(${s} ${c} ${o})`,stroke:`oklch(${s-.1} ${c} ${o})`}}}},wireframe(e,t,n){return{default:{fill:`transparent`,stroke:`#666`,opacity:1}}},xray(e,t,n){let r=e/2,i=t/2,a=n/2,o=Math.sqrt(r*r+i*i+a*a);return{default:(e,t,n)=>({fill:`oklch(0.7 0.12 220)`,stroke:`oklch(0.5 0.12 220)`,opacity:.1+Math.sqrt((e-r)**2+(t-i)**2+(n-a)**2)/Math.max(o,1)*.6})}},glass(e,t,n){return{default:{fill:`rgba(200, 220, 240, 0.15)`,stroke:`rgba(100, 140, 180, 0.4)`,opacity:.8,backdropFilter:`blur(4px)`}}},marble(e,t,n){return{default:(e,t,n)=>{let r=Math.sin(e*12.9898+t*78.233+n*37.719)*43758.5453,i=.88+(r-Math.floor(r))*.08;return{fill:`oklch(${i} 0.005 90)`,stroke:`oklch(${i-.12} 0.01 90)`}}}},neon(e,t,n){return{default:{fill:`rgba(10, 10, 15, 0.9)`,stroke:`#0ff`,opacity:1}}}},U=[`solid`,`hollow`,`glass`,`frosted`,`neon`];function W(e,t,n=1,r=1,i=1,a=1){let o=n;switch(e){case`solid`:return{default:{fill:`oklch(0.65 0.15 ${t})`,stroke:`oklch(0.45 0.12 ${t})`,opacity:o}};case`hollow`:return{default:{fill:`transparent`,stroke:`oklch(0.7 0.15 ${t})`,opacity:o}};case`glass`:return{default:{fill:`oklch(0.8 0.06 ${t} / ${.15*o})`,stroke:`oklch(0.5 0.08 ${t} / ${.4*o})`,opacity:Math.min(o,.85),backdropFilter:`blur(${Math.round(4*o)}px)`}};case`frosted`:return{default:{fill:`oklch(0.9 0.02 ${t} / ${.4*o})`,stroke:`oklch(0.7 0.04 ${t} / ${.3*o})`,opacity:Math.min(o,.9),backdropFilter:`blur(${Math.round(12*o)}px)`}};case`neon`:return{default:{fill:`oklch(0.15 0.02 ${t} / ${.9*o})`,stroke:`oklch(0.85 0.25 ${t})`,opacity:o}}}}var G=class{static presets=H;static textures=U;grid;renderer;animator;options;globalStyle;listeners=new Map;mounted=!1;eventsBound=!1;constructor(e={}){this.grid=new r,this.animator=new P,this.options={boxelSize:e.boxelSize??50,gap:e.gap??0,edgeWidth:e.edgeWidth??1,edgeColor:e.edgeColor??`#333`,showBackfaces:e.showBackfaces??!1},this.globalStyle=e.style,this.renderer=new k({orbit:e.orbit??!0,zoom:e.zoom??!0,cameraRotation:e.camera?.rotation,cameraDistance:e.camera?.distance,position:e.position}),e.container&&this.mount(e.container)}mount(e){this.renderer.mount(e),this.mounted=!0,this.renderScene()}unmount(){this.animator.cancelAll(),this.renderer.unmount(),this.mounted=!1,this.eventsBound=!1}addBox(e){let t=a(e.position,e.size);i(this.grid,t,e.mode??`union`,e.style),this.renderScene()}removeBox(e){let t=a(e.position,e.size);i(this.grid,t,`subtract`),this.renderScene()}addSphere(e){let t=o(e.center,e.radius);i(this.grid,t,e.mode??`union`,e.style),this.renderScene()}removeSphere(e){let t=o(e.center,e.radius);i(this.grid,t,`subtract`),this.renderScene()}addLine(e){let t=s(e.from,e.to,e.radius);i(this.grid,t,e.mode??`union`,e.style),this.renderScene()}setBoxel(e,t){let[n,r,i]=e;t?this.grid.setBoxel(n,r,i,{position:e,opaque:!0}):this.grid.setBoxel(n,r,i,null),this.renderScene()}hasBoxel(e){return this.grid.hasBoxel(e[0],e[1],e[2])}getBoxel(e){return this.grid.getBoxel(e[0],e[1],e[2])}clear(){this.grid.clear(),this.renderScene()}rotate(e){l(this.grid,e.axis,e.turns,e.center),this.renderScene()}forEach(e){this.grid.forEach(e)}getNeighbors(e){return this.grid.getNeighbors(e[0],e[1],e[2])}getExposure(e){return this.grid.getExposure(e[0],e[1],e[2])}getWorldContainer(){return this.renderer.getWorldContainer()}updateTransform(e,t){this.renderer.updateTransform(e,t)}getRotation(){let e=this.renderer;if(e.getOrbitState){let t=e.getOrbitState();return{rotX:t.rotX,rotY:t.rotY}}return{rotX:-25,rotY:35}}setPosition(e){let t=this.renderer.getSceneElement?.();t&&(t.style.position=e.fixed?`fixed`:`absolute`,e.x!==void 0&&(t.style.left=typeof e.x==`number`?`${e.x}px`:e.x),e.y!==void 0&&(t.style.top=typeof e.y==`number`?`${e.y}px`:e.y),e.zIndex!==void 0&&(t.style.zIndex=String(e.zIndex)))}spinRafId=null;lastSpinOptions=null;startSpin(e={}){this.lastSpinOptions={...e},this.stopSpin();let t=e.x??!1,n=e.y??!0,r=e.xDir??1,i=e.yDir??1,a=(e.speed??1)*.5,o=()=>{let e=this.getRotation(),s=e.rotX,c=e.rotY;t&&(s+=a*r),n&&(c+=a*i),this.updateTransform(s,c),this.spinRafId=requestAnimationFrame(o)};this.spinRafId=requestAnimationFrame(o)}stopSpin(){this.spinRafId!==null&&(cancelAnimationFrame(this.spinRafId),this.spinRafId=null)}showAxes(){this.hideAxes();let e=this.renderer.getWorldContainer();if(!e)return;let t=this.grid.getBounds(),n=Math.max(t.max[0]-t.min[0]+1,t.max[1]-t.min[1]+1,t.max[2]-t.min[2]+1)*(this.options.boxelSize+this.options.gap)*.8;e.appendChild(j(n))}hideAxes(){let e=this.renderer.getWorldContainer();e&&e.querySelectorAll(`.boxel-axes`).forEach(e=>e.remove())}styleBox(e){let t=a(e.position,e.size);for(let[n,r,i]of t){let t=this.grid.getBoxel(n,r,i);t&&this.grid.setBoxel(n,r,i,{...t,style:e.style})}this.renderScene()}setTexture(e,t=220,n=1){let r=this.grid.getBounds();this.globalStyle=W(e,t,n,r.max[0]-r.min[0]+1,r.max[1]-r.min[1]+1,r.max[2]-r.min[2]+1),this.renderScene()}mapImage(e,t){let n=v(this.grid,t),r=this.renderer.getWorldContainer();if(r)for(let t of n){let n=`${t.position[0]},${t.position[1]},${t.position[2]}`,i=r.querySelector(`[data-boxel="${n}"] [data-face="${t.face}"]`);i&&(i.style.backgroundImage=`url(${e})`,i.style.backgroundSize=t.backgroundSize,i.style.backgroundPosition=t.backgroundPosition)}}clearImage(){let e=this.renderer.getWorldContainer();e&&e.querySelectorAll(`[data-face]`).forEach(e=>{e.style.backgroundImage=``,e.style.backgroundSize=``,e.style.backgroundPosition=``})}explode(e={}){I(this.grid,this.renderer,this.animator,this.options.boxelSize,e)}collapse(){this.renderScene()}rotateLayer(e){L(this.grid,this.renderer,this.animator,e,()=>this.renderScene())}animateGap(e){z(this.animator,e=>{this.options.gap=e,this.renderScene()},e)}animateOpacity(e){B(this.animator,this.renderer,e)}animateEach(e,t){V(this.grid,this.renderer,this.animator,this.options.boxelSize,e,t)}clickEnabled=!1;clickHandler=null;boundClickListener=null;enableClick(e){this.disableClick(),this.clickEnabled=!0,this.clickHandler=e;let t=this.renderer.getWorldContainer();t&&(t.style.cursor=`pointer`,this.boundClickListener=e=>{let t=e.target.closest(`[data-face]`);if(!t)return;let n=t.closest(`[data-boxel]`);if(!n)return;let r=n.dataset.boxel.split(`,`).map(Number),i=t.dataset.face,a=this.spinRafId!==null;a&&this.stopSpin();let o=t.style.backgroundColor,s=t.style.borderColor,c=t.style.boxShadow,l=t.style.opacity;t.style.backgroundColor=`rgba(255, 255, 255, 0.9)`,t.style.borderColor=`rgba(255, 255, 255, 0.8)`,t.style.boxShadow=`0 0 20px rgba(255, 255, 255, 0.5), inset 0 0 10px rgba(255, 255, 255, 0.3)`,t.style.opacity=`1`,t.style.transition=`all 0.1s ease-in`;let u=n.style.transform;n.style.transition=`transform 0.15s ease-in`,n.style.transform=u+` scale(0.85)`,setTimeout(()=>{n.style.transition=`transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1)`,n.style.transform=u,t.style.transition=`all 0.3s ease-out`,t.style.backgroundColor=o,t.style.borderColor=s,t.style.boxShadow=c,t.style.opacity=l,setTimeout(()=>{n.style.transition=``,t.style.transition=``,a&&this.startSpin(this.lastSpinOptions??{})},300)},150),this.clickHandler?.({boxel:r,face:i})},t.addEventListener(`click`,this.boundClickListener))}disableClick(){if(!this.clickEnabled)return;let e=this.renderer.getWorldContainer();e&&(e.style.cursor=``,this.boundClickListener&&e.removeEventListener(`click`,this.boundClickListener)),this.clickEnabled=!1,this.clickHandler=null,this.boundClickListener=null}on(e,t){this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t)}off(e,t){this.listeners.get(e)?.delete(t)}renderScene(){this.mounted&&(this.renderer.render({grid:this.grid,boxelSize:this.options.boxelSize,gap:this.options.gap,edgeWidth:this.options.edgeWidth,edgeColor:this.options.edgeColor,globalStyle:this.globalStyle,showBackfaces:this.options.showBackfaces}),this.eventsBound||=(this.setupFaceEvents(),!0),this.emit(`render`,new Event(`render`)))}setupFaceEvents(){let e=this.renderer.getWorldContainer();e&&(e.addEventListener(`click`,e=>{let t=this.buildBoxelEvent(e);t&&this.emit(`boxel:click`,t)}),e.addEventListener(`pointerover`,e=>{let t=this.buildBoxelEvent(e);t&&this.emit(`boxel:hover`,t)}),e.addEventListener(`pointerdown`,e=>{let t=this.buildBoxelEvent(e);t&&this.emit(`boxel:pointerdown`,t)}))}buildBoxelEvent(e){let t=e.target.closest(`[data-face]`);if(!t)return null;let n=t.closest(`[data-boxel]`);if(!n)return null;let r=n.dataset.boxel.split(`,`).map(Number),i=t.dataset.face,a=this.grid.getBoxel(r[0],r[1],r[2]);return a?{boxel:a,position:r,face:i,originalEvent:e}:null}emit(e,t){let n=this.listeners.get(e);if(n)for(let e of n)e(t)}};exports.ALL_TEXTURES=U,exports.Boxels=G;