split-flap-board 0.0.1 → 0.0.3
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 +445 -1
- package/dist/cjs/SplitFlapBoard.js +18 -0
- package/dist/cjs/index.js +1 -7
- package/dist/cjs/lib/board.js +1 -0
- package/dist/cjs/lib/flap.js +1 -0
- package/dist/cjs/lib/spool-layout.js +1 -0
- package/dist/cjs/spools/SplitFlapSpool.js +18 -0
- package/dist/cjs/spools/SplitFlapSpoolBase.js +16 -0
- package/dist/cjs/spools/SplitFlapSpoolMinimal.js +121 -0
- package/dist/cjs/spools/SplitFlapSpoolRealistic.js +144 -0
- package/dist/cjs/spools/presets.js +1 -0
- package/dist/esm/SplitFlapBoard.js +18 -0
- package/dist/esm/index.js +1 -5
- package/dist/esm/lib/board.js +1 -0
- package/dist/esm/lib/flap.js +1 -0
- package/dist/esm/lib/spool-layout.js +1 -0
- package/dist/esm/spools/SplitFlapSpool.js +18 -0
- package/dist/esm/spools/SplitFlapSpoolBase.js +16 -0
- package/dist/esm/spools/SplitFlapSpoolMinimal.js +121 -0
- package/dist/esm/spools/SplitFlapSpoolRealistic.js +144 -0
- package/dist/esm/spools/presets.js +1 -0
- package/dist/types/SplitFlapBoard.d.ts +28 -0
- package/dist/types/index.d.ts +4 -2
- package/dist/types/lib/board.d.ts +30 -0
- package/dist/types/lib/flap.d.ts +3 -0
- package/dist/types/lib/index.d.ts +3 -0
- package/dist/types/lib/spool-layout.d.ts +16 -0
- package/dist/types/spools/SplitFlapSpool.d.ts +25 -0
- package/dist/types/spools/SplitFlapSpoolBase.d.ts +52 -0
- package/dist/types/spools/SplitFlapSpoolMinimal.d.ts +11 -0
- package/dist/types/spools/SplitFlapSpoolRealistic.d.ts +39 -0
- package/dist/types/spools/index.d.ts +5 -0
- package/dist/types/spools/presets.d.ts +4 -0
- package/dist/types/types.d.ts +30 -0
- package/package.json +13 -4
- package/dist/cjs/index.js.map +0 -1
- package/dist/esm/index.js.map +0 -1
- package/dist/types/index.d.ts.map +0 -1
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";var f=require("lit"),p=require("lit/decorators.js"),d=require("lit/directives/ref.js"),u=require("lit/directives/style-map.js");require("./presets.js");var m=require("../lib/spool-layout.js"),v=require("./SplitFlapSpoolBase.js"),b=Object.defineProperty,x=Object.getOwnPropertyDescriptor,h=(n,e,a,t)=>{for(var r=t>1?void 0:t?x(e,a):e,s=n.length-1,i;s>=0;s--)(i=n[s])&&(r=(t?i(e,a,r):i(r))||r);return t&&r&&b(e,a,r),r},g=(n,e,a)=>new Promise((t,r)=>{var s=l=>{try{o(a.next(l))}catch(c){r(c)}},i=l=>{try{o(a.throw(l))}catch(c){r(c)}},o=l=>l.done?t(l.value):Promise.resolve(l.value).then(s,i);o((a=a.apply(n,e)).next())});exports.SplitFlapSpoolRealistic=class extends v.SplitFlapSpoolBase{constructor(){super(...arguments),this.visibleSideCount=-1,this._slotRef=d.createRef(),this._wrapResetTimer=null,this._skipNextIndexAnimation=!1}firstUpdated(){this._syncSlotState()}updated(e){var a;const t=this._currentIndex,r=this._prevIndex;if(super.updated(e),e.has("flaps")){this._clearWrapTimer();const s=this._currentIndex!==t||this._prevIndex!==r;this._skipNextIndexAnimation=s,this._syncSlotState()}if(e.has("_currentIndex")){if(this._skipNextIndexAnimation){this._skipNextIndexAnimation=!1,this._syncSlotState();return}const s=(a=e.get("_currentIndex"))!=null?a:0;this._animateStep(s,this._currentIndex)}}disconnectedCallback(){super.disconnectedCallback(),this._clearWrapTimer()}_clearWrapTimer(){this._wrapResetTimer!=null&&(clearTimeout(this._wrapResetTimer),this._wrapResetTimer=null)}_syncSlotState(){const e=this._slotRef.value;e!=null&&(e.style.setProperty("--current-character-index",String(this._currentIndex)),e.style.setProperty("--_flip-dur","0ms"))}_animateStep(e,a){return g(this,null,function*(){const t=this._slotRef.value;if(t==null)return;this._clearWrapTimer(),t.style.setProperty("--_flip-dur","0ms"),t.style.setProperty("--current-character-index",String(e)),yield new Promise(i=>requestAnimationFrame(()=>{requestAnimationFrame(()=>i())}));const r=e===this.flaps.length-1&&a===0,s=r?this.flaps.length:a;t.style.setProperty("--_flip-dur",`${this._animDur}ms`),t.style.setProperty("--current-character-index",String(s)),r&&(this._wrapResetTimer=setTimeout(()=>{t.style.setProperty("--_flip-dur","0ms"),t.style.setProperty("--current-character-index","0"),this._wrapResetTimer=null,this.requestUpdate()},this._animDur))})}_getRenderCenter(){return this.flaps.length>0&&this._prevIndex===this.flaps.length-1&&this._currentIndex===0&&(this._stepping||this._wrapResetTimer!=null)?this.flaps.length:this._currentIndex}render(){const e=this._getRenderCenter(),a=m.getRenderedFlaps(this.flaps,this._currentIndex,{visibleSideCount:this.visibleSideCount,renderCenter:e});return f.html`
|
|
2
|
+
<div
|
|
3
|
+
${d.ref(this._slotRef)}
|
|
4
|
+
class="slot"
|
|
5
|
+
style=${u.styleMap({"--total":String(this.flaps.length)})}
|
|
6
|
+
>
|
|
7
|
+
${a.map(({flap:t,actualIndex:r,renderedIndex:s})=>f.html`
|
|
8
|
+
<div class="character" style="--index: ${s}" data-index=${r}>
|
|
9
|
+
<div class="flap">${this._renderHalf(t,"top")}</div>
|
|
10
|
+
<div class="flap" aria-hidden="true">${this._renderHalf(t,"bottom")}</div>
|
|
11
|
+
</div>
|
|
12
|
+
`)}
|
|
13
|
+
</div>
|
|
14
|
+
`}},exports.SplitFlapSpoolRealistic.styles=f.css`
|
|
15
|
+
:host {
|
|
16
|
+
display: inline-block;
|
|
17
|
+
perspective: var(--sfb-perspective, 400px);
|
|
18
|
+
font-size: 2rem;
|
|
19
|
+
font-family: monospace;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.slot {
|
|
23
|
+
display: grid;
|
|
24
|
+
place-content: center;
|
|
25
|
+
/* Keep board tilt on the inner slot so the drum stays inside host perspective. */
|
|
26
|
+
transform: var(--sfb-view-transform, none);
|
|
27
|
+
transform-style: preserve-3d;
|
|
28
|
+
transition: transform 0.5s cubic-bezier(0.25, 0, 0.3, 1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Derive drum placement and fold angles from the flap offset. */
|
|
32
|
+
.character {
|
|
33
|
+
--offset: calc(var(--index) - var(--current-character-index));
|
|
34
|
+
--abs-offset: max(var(--offset), calc(var(--offset) * -1));
|
|
35
|
+
--safe-abs-offset: max(var(--abs-offset), 0.001);
|
|
36
|
+
--direction: calc(var(--offset) / var(--safe-abs-offset));
|
|
37
|
+
--past: min(0, var(--direction)); /* -1 when behind current, else 0 */
|
|
38
|
+
--future: max(0, var(--direction)); /* +1 when ahead of current, else 0 */
|
|
39
|
+
/* Treat exact offsets as booleans so CSS can target the active neighbors. */
|
|
40
|
+
--is-current: clamp(0, calc(1 - var(--abs-offset) * 1000), 1);
|
|
41
|
+
--is-previous: clamp(0, calc(1 - max(var(--offset) + 1, (var(--offset) + 1) * -1) * 1000), 1);
|
|
42
|
+
--is-next: clamp(0, calc(1 - max(var(--offset) - 1, (var(--offset) - 1) * -1) * 1000), 1);
|
|
43
|
+
/*
|
|
44
|
+
* Keep the outer visible odd-count flap inside the silhouette so it does not
|
|
45
|
+
* vanish at the drum edge.
|
|
46
|
+
*/
|
|
47
|
+
--natural-angle: calc((0.5 / var(--total)) * 1turn);
|
|
48
|
+
/* Cap the visual step angle for small spools so the gap between flaps stays tight. */
|
|
49
|
+
--angle: min(var(--natural-angle), var(--sfb-max-step-angle, 1turn));
|
|
50
|
+
/* Unwrapped drum position; places the fold hinge on the cylinder surface. */
|
|
51
|
+
--drum-a: calc(var(--abs-offset) * var(--direction) * var(--angle));
|
|
52
|
+
/* Top-half angle; +0.5turn for past flaps puts them on the drum backface. */
|
|
53
|
+
--a: calc(var(--abs-offset) * var(--direction) * var(--angle) + var(--past) * 0.5turn);
|
|
54
|
+
/* Bottom-half angle; +0.5turn for future flaps slides them in from below. */
|
|
55
|
+
--a2: calc(
|
|
56
|
+
max(var(--abs-offset) - 1, 0) * var(--direction) * var(--angle) + var(--future) * 0.5turn
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
display: flex;
|
|
60
|
+
grid-area: 1 / 1;
|
|
61
|
+
flex-direction: column;
|
|
62
|
+
gap: var(--sfb-crease, 1px);
|
|
63
|
+
/* Move the fold line onto the drum surface so translation and fold stay aligned. */
|
|
64
|
+
transform: translateZ(calc(var(--sfb-drum-radius, 0px) * cos(var(--drum-a))))
|
|
65
|
+
translateY(calc(var(--sfb-drum-radius, 0px) * sin(var(--drum-a)) * -1));
|
|
66
|
+
transform-style: preserve-3d;
|
|
67
|
+
z-index: calc(var(--is-current) * 2 + var(--is-previous) + var(--is-next));
|
|
68
|
+
transition: transform var(--_flip-dur, 0ms) cubic-bezier(0.25, 0, 0.5, 1);
|
|
69
|
+
pointer-events: none;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.flap {
|
|
73
|
+
position: relative;
|
|
74
|
+
transform-style: preserve-3d;
|
|
75
|
+
backface-visibility: hidden;
|
|
76
|
+
transition: transform var(--_flip-dur, 0ms) cubic-bezier(0.25, 0, 0.5, 1);
|
|
77
|
+
will-change: transform;
|
|
78
|
+
box-sizing: border-box;
|
|
79
|
+
border: 1px solid var(--sfb-flap-border, #2a2a2a);
|
|
80
|
+
border-radius: var(--sfb-spool-radius, 3px);
|
|
81
|
+
background: var(--sfb-bg, #111);
|
|
82
|
+
width: var(--sfb-spool-width, 1em);
|
|
83
|
+
height: calc(var(--sfb-spool-height, 2em) / 2);
|
|
84
|
+
overflow: hidden;
|
|
85
|
+
color: var(--sfb-color, #f5f0e0);
|
|
86
|
+
line-height: 1;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* Nudge the front flap forward so the center seam stays stable during the flip. */
|
|
90
|
+
.flap:first-child {
|
|
91
|
+
transform: translateZ(calc(var(--is-current) * 0.1px)) rotateX(var(--a));
|
|
92
|
+
transform-origin: center calc(100% + var(--sfb-crease, 1px) * 0.5);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.flap:last-child {
|
|
96
|
+
transform: translateZ(calc(var(--is-current) * 0.1px)) rotateX(var(--a2));
|
|
97
|
+
transform-origin: center calc(var(--sfb-crease, 1px) * -0.5);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Use 200% fills so each half crops the same flap content at the fold. */
|
|
101
|
+
.char-inner,
|
|
102
|
+
.image-fill,
|
|
103
|
+
.custom-fill {
|
|
104
|
+
position: absolute;
|
|
105
|
+
left: 0;
|
|
106
|
+
width: 100%;
|
|
107
|
+
height: 200%;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.flap:first-child .char-inner,
|
|
111
|
+
.flap:first-child .image-fill,
|
|
112
|
+
.flap:first-child .custom-fill {
|
|
113
|
+
top: 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.flap:last-child .char-inner,
|
|
117
|
+
.flap:last-child .image-fill,
|
|
118
|
+
.flap:last-child .custom-fill {
|
|
119
|
+
bottom: 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.char-inner {
|
|
123
|
+
display: flex;
|
|
124
|
+
justify-content: center;
|
|
125
|
+
align-items: center;
|
|
126
|
+
font-weight: bold;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.color-fill {
|
|
130
|
+
position: absolute;
|
|
131
|
+
inset: 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.image-fill {
|
|
135
|
+
object-fit: cover;
|
|
136
|
+
object-position: center center;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.custom-fill {
|
|
140
|
+
display: flex;
|
|
141
|
+
justify-content: center;
|
|
142
|
+
align-items: center;
|
|
143
|
+
}
|
|
144
|
+
`,h([p.property({type:Number})],exports.SplitFlapSpoolRealistic.prototype,"visibleSideCount",2),exports.SplitFlapSpoolRealistic=h([p.customElement("split-flap-spool-realistic")],exports.SplitFlapSpoolRealistic);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";const l=[{type:"char",value:" "},..."ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("").map(e=>({type:"char",value:e})),..."0123456789".split("").map(e=>({type:"char",value:e})),{type:"char",value:"."},{type:"char",value:"-"},{type:"char",value:"/"},{type:"char",value:":"}],o=[{type:"char",value:" "},..."0123456789".split("").map(e=>({type:"char",value:e}))],a=[{type:"color",key:"blank",value:"#111111"},{type:"color",key:"red",value:"#ef4444"},{type:"color",key:"orange",value:"#f97316"},{type:"color",key:"yellow",value:"#eab308"},{type:"color",key:"green",value:"#22c55e"},{type:"color",key:"blue",value:"#3b82f6"},{type:"color",key:"purple",value:"#8b5cf6"},{type:"color",key:"pink",value:"#ec4899"}];exports.charSpool=l,exports.colorSpool=a,exports.numericSpool=o;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import{css as h,LitElement as u,html as d}from"lit";import{property as a,customElement as v}from"lit/decorators.js";import"./spools/SplitFlapSpool.js";var m=Object.defineProperty,c=Object.getOwnPropertyDescriptor,p=(t,e,l,s)=>{for(var r=s>1?void 0:s?c(e,l):e,i=t.length-1,n;i>=0;i--)(n=t[i])&&(r=(s?n(e,l,r):n(r))||r);return s&&r&&m(e,l,r),r};let o=class extends u{constructor(){super(...arguments),this.spools=[],this.grid=[],this.speed=60,this.variant="minimal",this.visibleSideCount=-1,this._pendingSettle=!1}updated(t){var e,l;super.updated(t);const s=(l=(e=this.spools[0])==null?void 0:e.length)!=null?l:0;this.style.gridTemplateColumns=s>0?`repeat(${s}, auto)`:"",(t.has("spools")||t.has("grid"))&&(this._pendingSettle=!0,Promise.resolve().then(()=>this._checkAllSettled()))}_getSpoolEls(){return Array.from(this.renderRoot.querySelectorAll("split-flap-spool"))}_checkAllSettled(){if(!this._pendingSettle)return;const t=this._getSpoolEls();t.length!==0&&t.every(e=>e.isSettled)&&(this._pendingSettle=!1,this._dispatchBoardSettled(t))}_dispatchBoardSettled(t){let e=0;const l=this.spools.map(s=>s.map(()=>{var r,i;return(i=(r=t[e++])==null?void 0:r.currentValue)!=null?i:""}));this.dispatchEvent(new CustomEvent("board-settled",{detail:{grid:l},bubbles:!0,composed:!0}))}render(){return d`
|
|
2
|
+
${this.spools.flatMap((t,e)=>t.map((l,s)=>{var r,i;return d`
|
|
3
|
+
<split-flap-spool
|
|
4
|
+
.flaps=${l}
|
|
5
|
+
.value=${(i=(r=this.grid[e])==null?void 0:r[s])!=null?i:""}
|
|
6
|
+
.speed=${this.speed}
|
|
7
|
+
.variant=${this.variant}
|
|
8
|
+
.visibleSideCount=${this.visibleSideCount}
|
|
9
|
+
@settled=${()=>this._checkAllSettled()}
|
|
10
|
+
></split-flap-spool>
|
|
11
|
+
`}))}
|
|
12
|
+
`}};o.styles=h`
|
|
13
|
+
:host {
|
|
14
|
+
display: inline-grid;
|
|
15
|
+
gap: var(--sfb-gap, 2px);
|
|
16
|
+
box-sizing: border-box;
|
|
17
|
+
}
|
|
18
|
+
`,p([a({type:Array})],o.prototype,"spools",2),p([a({type:Array})],o.prototype,"grid",2),p([a({type:Number})],o.prototype,"speed",2),p([a({type:String})],o.prototype,"variant",2),p([a({type:Number})],o.prototype,"visibleSideCount",2),o=p([v("split-flap-board")],o);export{o as SplitFlapBoard};
|
package/dist/esm/index.js
CHANGED
|
@@ -1,5 +1 @@
|
|
|
1
|
-
|
|
2
|
-
}
|
|
3
|
-
|
|
4
|
-
export { helloWorld };
|
|
5
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
import{fromLines as r,spoolGrid as e}from"./lib/board.js";import{getFlapKey as t}from"./lib/flap.js";import{getMaxVisibleSideCount as a,getRenderedFlaps as S,getSignedCircularOffset as f}from"./lib/spool-layout.js";import{SplitFlapBoard as x}from"./SplitFlapBoard.js";import{charSpool as F,colorSpool as d,numericSpool as n}from"./spools/presets.js";import{SplitFlapSpool as g}from"./spools/SplitFlapSpool.js";import{SplitFlapSpoolBase as B}from"./spools/SplitFlapSpoolBase.js";import{SplitFlapSpoolMinimal as M}from"./spools/SplitFlapSpoolMinimal.js";import{SplitFlapSpoolRealistic as b}from"./spools/SplitFlapSpoolRealistic.js";export{x as SplitFlapBoard,g as SplitFlapSpool,B as SplitFlapSpoolBase,M as SplitFlapSpoolMinimal,b as SplitFlapSpoolRealistic,F as charSpool,d as colorSpool,r as fromLines,t as getFlapKey,a as getMaxVisibleSideCount,S as getRenderedFlaps,f as getSignedCircularOffset,n as numericSpool,e as spoolGrid};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{charSpool as f}from"../spools/presets.js";var b=Object.defineProperty,y=Object.getOwnPropertySymbols,g=Object.prototype.hasOwnProperty,v=Object.prototype.propertyIsEnumerable,h=(t,r,e)=>r in t?b(t,r,{enumerable:!0,configurable:!0,writable:!0,value:e}):t[r]=e,i=(t,r)=>{for(var e in r||(r={}))g.call(r,e)&&h(t,e,r[e]);if(y)for(var e of y(r))v.call(r,e)&&h(t,e,r[e]);return t};function d(t,r,e){const o=t.length>0&&Array.isArray(t[0])?t:Array.from({length:r},()=>t);return Array.from({length:e},()=>Array.from({length:r},(l,a)=>{var n;return(n=o[a%o.length])!=null?n:f}))}function A(t,r){const e=[],o=[];for(const l of t){const a=typeof l=="object",n=a?l.text:l,p=a?l.bg:void 0,s=a?l.color:void 0,u=n.toUpperCase().padEnd(r," ").slice(0,r).split("");if(p!=null||s!=null){const m=u.map(()=>f.map(c=>c.type!=="char"?c:i(i(i({},c),p!=null?{bg:p}:{}),s!=null?{color:s}:{})));e.push(m)}else e.push(Array.from({length:r},()=>f));o.push(u)}return{spools:e,grid:o}}export{A as fromLines,d as spoolGrid};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function r(e){if(e.key!=null)return e.key;switch(e.type){case"char":case"color":return e.value;case"image":return e.src;case"custom":return e.key}}export{r as getFlapKey};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function l(t,n,e){if(e<=0)return 0;const a=(t-n+e)%e;if(a===0)return 0;const o=a-e;return Math.abs(o)<=Math.abs(a)?o:a}function u(t,n){const e=Math.floor(t/2);return n==null||n<0?e:Math.min(Math.floor(n),e)}function i(t,n,{visibleSideCount:e,renderCenter:a=n}={}){const o=u(t.length,e);return t.map((r,d)=>{const f=l(d,n,t.length);return{flap:r,actualIndex:d,offset:f,renderedIndex:a+f}}).filter(({offset:r})=>Math.abs(r)<=o).sort((r,d)=>r.renderedIndex-d.renderedIndex).map(({flap:r,actualIndex:d,renderedIndex:f})=>({flap:r,actualIndex:d,renderedIndex:f}))}export{u as getMaxVisibleSideCount,i as getRenderedFlaps,l as getSignedCircularOffset};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import{css as m,LitElement as h,html as u}from"lit";import{property as o,customElement as c}from"lit/decorators.js";import{charSpool as d}from"./presets.js";import{getFlapKey as v}from"../lib/flap.js";import"./SplitFlapSpoolMinimal.js";import"./SplitFlapSpoolRealistic.js";var f=Object.defineProperty,y=Object.getOwnPropertyDescriptor,p=(e,t,s,r)=>{for(var i=r>1?void 0:r?y(t,s):t,a=e.length-1,n;a>=0;a--)(n=e[a])&&(i=(r?n(t,s,i):n(i))||i);return r&&i&&f(t,s,i),i};let l=class extends h{constructor(){super(...arguments),this.variant="minimal",this.value=" ",this.flaps=d,this.speed=60,this.visibleSideCount=-1}get currentValue(){var e,t;return(t=(e=this._getActiveSpool())==null?void 0:e.currentValue)!=null?t:this.flaps[0]!=null?v(this.flaps[0]):void 0}get isSettled(){var e,t;return(t=(e=this._getActiveSpool())==null?void 0:e.isSettled)!=null?t:!0}hasKey(e){var t,s;return(s=(t=this._getActiveSpool())==null?void 0:t.hasKey(e))!=null?s:this.flaps.some(r=>v(r)===e)}_getActiveSpool(){return this.renderRoot.querySelector("split-flap-spool-minimal, split-flap-spool-realistic")}render(){return this.variant==="realistic"?u`
|
|
2
|
+
<split-flap-spool-realistic
|
|
3
|
+
.value=${this.value}
|
|
4
|
+
.flaps=${this.flaps}
|
|
5
|
+
.speed=${this.speed}
|
|
6
|
+
.visibleSideCount=${this.visibleSideCount}
|
|
7
|
+
></split-flap-spool-realistic>
|
|
8
|
+
`:u`
|
|
9
|
+
<split-flap-spool-minimal
|
|
10
|
+
.value=${this.value}
|
|
11
|
+
.flaps=${this.flaps}
|
|
12
|
+
.speed=${this.speed}
|
|
13
|
+
></split-flap-spool-minimal>
|
|
14
|
+
`}};l.styles=m`
|
|
15
|
+
:host {
|
|
16
|
+
display: contents;
|
|
17
|
+
}
|
|
18
|
+
`,p([o({type:String})],l.prototype,"variant",2),p([o({type:String})],l.prototype,"value",2),p([o({type:Array})],l.prototype,"flaps",2),p([o({type:Number})],l.prototype,"speed",2),p([o({type:Number})],l.prototype,"visibleSideCount",2),l=p([c("split-flap-spool")],l);export{l as SplitFlapSpool};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import{LitElement as y,html as p,nothing as v}from"lit";import{property as d,state as m}from"lit/decorators.js";import{styleMap as b}from"lit/directives/style-map.js";import{charSpool as F}from"./presets.js";import{getFlapKey as n}from"../lib/flap.js";var x=Object.defineProperty,I=Object.getOwnPropertySymbols,S=Object.prototype.hasOwnProperty,A=Object.prototype.propertyIsEnumerable,T=(s,t,e)=>t in s?x(s,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):s[t]=e,o=(s,t)=>{for(var e in t||(t={}))S.call(t,e)&&T(s,e,t[e]);if(I)for(var e of I(t))A.call(t,e)&&T(s,e,t[e]);return s},u=(s,t,e,r)=>{for(var i=void 0,a=s.length-1,h;a>=0;a--)(h=s[a])&&(i=h(t,e,i)||i);return i&&x(t,e,i),i};class l extends y{constructor(){super(...arguments),this.value=" ",this.flaps=F,this.speed=60,this._currentIndex=0,this._prevIndex=0,this._stepping=!1,this._targetIndex=-1,this._stepTimer=null,this._animTimer=null,this._animEndsAt=0}get _animDur(){return Math.max(Math.floor(this.speed*.85),1)}updated(t){super.updated(t),t.has("flaps")&&this._syncIndicesToFlaps(t.get("flaps")),(t.has("value")||t.has("flaps"))&&this._startStepping()}disconnectedCallback(){super.disconnectedCallback(),this._clearTimers()}get currentFlap(){var t;return(t=this.flaps[this._currentIndex])!=null?t:this.flaps[0]}get currentValue(){return this.currentFlap!=null?n(this.currentFlap):void 0}get isSettled(){return!this._stepping&&this._targetIndex===-1&&this._stepTimer==null&&this._animTimer==null}hasKey(t){return this.flaps.some(e=>n(e)===t)}_startStepping(){if(!this.flaps.length){this._resetForEmptyFlaps();return}const t=this.flaps.findIndex(r=>n(r)===this.value),e=this._stepping||this._stepTimer!=null;if(t===-1){this._targetIndex=-1,e&&this._scheduleAdvanceOrSettle(this._getRemainingAnimTime());return}if(this._targetIndex=t,this._currentIndex===this._targetIndex){e?this._scheduleAdvanceOrSettle(this._getRemainingAnimTime()):this._targetIndex=-1;return}e||this._doStep()}_doStep(){if(!this.flaps.length){this._resetForEmptyFlaps();return}const t=(this._currentIndex+1)%this.flaps.length;this._prevIndex=this._currentIndex,this._currentIndex=t,this._stepping=!0,this._animEndsAt=Date.now()+this._animDur,this._animTimer!=null&&clearTimeout(this._animTimer),this._animTimer=setTimeout(()=>{this._stepping=!1,this._animTimer=null,this._animEndsAt=0},this._animDur);const e=this._currentIndex!==this._targetIndex?this.speed:this._getRemainingAnimTime();this._scheduleAdvanceOrSettle(e)}_scheduleAdvanceOrSettle(t){this._stepTimer!=null&&clearTimeout(this._stepTimer),this._stepTimer=setTimeout(()=>{if(this._stepTimer=null,this._targetIndex!==-1&&this._currentIndex!==this._targetIndex){this._doStep();return}this._finishSettling()},Math.max(t,0))}_finishSettling(){if(this._stepping){this._scheduleAdvanceOrSettle(this._getRemainingAnimTime());return}this._targetIndex=-1;const t=this.currentValue;t!=null&&this.dispatchEvent(new CustomEvent("settled",{detail:{value:t},bubbles:!0,composed:!0}))}_getRemainingAnimTime(){return this._stepping?Math.max(this._animEndsAt-Date.now(),1):0}_resetForEmptyFlaps(){this._clearTimers(),this._targetIndex=-1,this._currentIndex=0,this._prevIndex=0,this._stepping=!1}_syncIndicesToFlaps(t){var e,r;if(!this.flaps.length){this._resetForEmptyFlaps();return}if(t==null||!t.length){this._currentIndex=0,this._prevIndex=0,this._targetIndex=-1,this._clearTimers(),this._stepping=!1;return}const i=(e=t[this._currentIndex])!=null?e:t[0],a=(r=t[this._prevIndex])!=null?r:i,h=i!=null?n(i):void 0,c=a!=null?n(a):h;this._clearTimers(),this._stepping=!1,this._targetIndex=-1;const f=h!=null?this.flaps.findIndex(_=>n(_)===h):-1,g=c!=null?this.flaps.findIndex(_=>n(_)===c):-1;this._currentIndex=f>=0?f:0,this._prevIndex=g>=0?g:this._currentIndex}_clearTimers(){this._stepTimer!=null&&(clearTimeout(this._stepTimer),this._stepTimer=null),this._animTimer!=null&&(clearTimeout(this._animTimer),this._animTimer=null),this._animEndsAt=0}_renderHalf(t,e){var r;switch(t.type){case"char":return p`
|
|
2
|
+
<div
|
|
3
|
+
class="char-inner"
|
|
4
|
+
style=${b(o(o(o(o(o({},t.color!=null?{color:t.color}:{}),t.bg!=null?{background:t.bg}:{}),t.fontSize!=null?{fontSize:t.fontSize}:{}),t.fontFamily!=null?{fontFamily:t.fontFamily}:{}),t.fontWeight!=null?{fontWeight:t.fontWeight}:{}))}
|
|
5
|
+
>
|
|
6
|
+
${t.value}
|
|
7
|
+
</div>
|
|
8
|
+
`;case"color":return p`<div class="color-fill" style="background: ${t.value}"></div>`;case"image":return p`<img class="image-fill" src=${t.src} alt=${(r=t.alt)!=null?r:""} />`;case"custom":return p`<div class="custom-fill">${t[e]}</div>`}}_getFlaps(){var t;const e=this.currentFlap;return e==null?null:{current:e,prev:(t=this.flaps[this._prevIndex])!=null?t:e}}_renderCard(){const t=this._getFlaps();if(t==null)return v;const{current:e,prev:r}=t,i=this._stepping?r:e;return p`
|
|
9
|
+
<div class="half top">${this._renderHalf(e,"top")}</div>
|
|
10
|
+
<div class="half bottom">${this._renderHalf(i,"bottom")}</div>
|
|
11
|
+
|
|
12
|
+
${this._stepping?p`
|
|
13
|
+
<div class="half top flipping">${this._renderHalf(r,"top")}</div>
|
|
14
|
+
<div class="half bottom flipping">${this._renderHalf(e,"bottom")}</div>
|
|
15
|
+
`:v}
|
|
16
|
+
`}}u([d({type:String})],l.prototype,"value"),u([d({type:Array})],l.prototype,"flaps"),u([d({type:Number})],l.prototype,"speed"),u([m()],l.prototype,"_currentIndex"),u([m()],l.prototype,"_prevIndex"),u([m()],l.prototype,"_stepping");export{l as SplitFlapSpoolBase};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import{css as l,html as s}from"lit";import{customElement as n}from"lit/decorators.js";import{SplitFlapSpoolBase as f}from"./SplitFlapSpoolBase.js";var p=(i,a,m,d)=>{for(var o=a,r=i.length-1,e;r>=0;r--)(e=i[r])&&(o=e(o)||o);return o};let t=class extends f{render(){return s`
|
|
2
|
+
<div class="spool" style="--_anim-dur: ${this._animDur}ms">${this._renderCard()}</div>
|
|
3
|
+
`}};t.styles=l`
|
|
4
|
+
:host {
|
|
5
|
+
display: inline-block;
|
|
6
|
+
font-size: 2rem;
|
|
7
|
+
font-family: monospace;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.spool {
|
|
11
|
+
position: relative;
|
|
12
|
+
perspective: 300px;
|
|
13
|
+
perspective-origin: center center;
|
|
14
|
+
width: var(--sfb-spool-width, 1.2em);
|
|
15
|
+
height: var(--sfb-spool-height, 2em);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.half {
|
|
19
|
+
position: absolute;
|
|
20
|
+
right: 0;
|
|
21
|
+
left: 0;
|
|
22
|
+
background: var(--sfb-bg, #111);
|
|
23
|
+
height: 50%;
|
|
24
|
+
overflow: hidden;
|
|
25
|
+
color: var(--sfb-color, #f5f0e0);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.half.top {
|
|
29
|
+
top: 0;
|
|
30
|
+
border-bottom: 1px solid var(--sfb-fold-color, #0a0a0a);
|
|
31
|
+
border-top-right-radius: var(--sfb-spool-radius, 4px);
|
|
32
|
+
border-top-left-radius: var(--sfb-spool-radius, 4px);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.half.bottom {
|
|
36
|
+
bottom: 0;
|
|
37
|
+
border-bottom-right-radius: var(--sfb-spool-radius, 4px);
|
|
38
|
+
border-bottom-left-radius: var(--sfb-spool-radius, 4px);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.half.flipping {
|
|
42
|
+
z-index: 1;
|
|
43
|
+
backface-visibility: hidden;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.half.top.flipping {
|
|
47
|
+
transform-origin: bottom center;
|
|
48
|
+
animation: fold-top var(--_anim-dur) linear forwards;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.half.bottom.flipping {
|
|
52
|
+
transform-origin: top center;
|
|
53
|
+
animation: fold-bottom var(--_anim-dur) linear forwards;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@keyframes fold-top {
|
|
57
|
+
from {
|
|
58
|
+
transform: rotateX(0deg);
|
|
59
|
+
}
|
|
60
|
+
to {
|
|
61
|
+
transform: rotateX(90deg);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@keyframes fold-bottom {
|
|
66
|
+
from {
|
|
67
|
+
transform: rotateX(-90deg);
|
|
68
|
+
}
|
|
69
|
+
to {
|
|
70
|
+
transform: rotateX(0deg);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.char-inner {
|
|
75
|
+
display: flex;
|
|
76
|
+
position: absolute;
|
|
77
|
+
right: 0;
|
|
78
|
+
left: 0;
|
|
79
|
+
justify-content: center;
|
|
80
|
+
align-items: center;
|
|
81
|
+
height: 200%;
|
|
82
|
+
font-weight: bold;
|
|
83
|
+
letter-spacing: 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.half.top .char-inner {
|
|
87
|
+
top: 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.half.bottom .char-inner {
|
|
91
|
+
bottom: 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.color-fill {
|
|
95
|
+
position: absolute;
|
|
96
|
+
inset: 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.image-fill {
|
|
100
|
+
position: absolute;
|
|
101
|
+
left: 0;
|
|
102
|
+
width: 100%;
|
|
103
|
+
height: 200%;
|
|
104
|
+
object-fit: cover;
|
|
105
|
+
object-position: center center;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.half.top .image-fill {
|
|
109
|
+
top: 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.half.bottom .image-fill {
|
|
113
|
+
bottom: 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.custom-fill {
|
|
117
|
+
position: absolute;
|
|
118
|
+
inset: 0;
|
|
119
|
+
overflow: hidden;
|
|
120
|
+
}
|
|
121
|
+
`,t=p([n("split-flap-spool-minimal")],t);export{t as SplitFlapSpoolMinimal};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import{css as h,html as p}from"lit";import{property as u,customElement as m}from"lit/decorators.js";import{createRef as v,ref as b}from"lit/directives/ref.js";import{styleMap as x}from"lit/directives/style-map.js";import"./presets.js";import{getRenderedFlaps as g}from"../lib/spool-layout.js";import{SplitFlapSpoolBase as y}from"./SplitFlapSpoolBase.js";var _=Object.defineProperty,w=Object.getOwnPropertyDescriptor,d=(t,a,e,s)=>{for(var r=s>1?void 0:s?w(a,e):a,i=t.length-1,l;i>=0;i--)(l=t[i])&&(r=(s?l(a,e,r):l(r))||r);return s&&r&&_(a,e,r),r},S=(t,a,e)=>new Promise((s,r)=>{var i=n=>{try{c(e.next(n))}catch(f){r(f)}},l=n=>{try{c(e.throw(n))}catch(f){r(f)}},c=n=>n.done?s(n.value):Promise.resolve(n.value).then(i,l);c((e=e.apply(t,a)).next())});let o=class extends y{constructor(){super(...arguments),this.visibleSideCount=-1,this._slotRef=v(),this._wrapResetTimer=null,this._skipNextIndexAnimation=!1}firstUpdated(){this._syncSlotState()}updated(t){var a;const e=this._currentIndex,s=this._prevIndex;if(super.updated(t),t.has("flaps")){this._clearWrapTimer();const r=this._currentIndex!==e||this._prevIndex!==s;this._skipNextIndexAnimation=r,this._syncSlotState()}if(t.has("_currentIndex")){if(this._skipNextIndexAnimation){this._skipNextIndexAnimation=!1,this._syncSlotState();return}const r=(a=t.get("_currentIndex"))!=null?a:0;this._animateStep(r,this._currentIndex)}}disconnectedCallback(){super.disconnectedCallback(),this._clearWrapTimer()}_clearWrapTimer(){this._wrapResetTimer!=null&&(clearTimeout(this._wrapResetTimer),this._wrapResetTimer=null)}_syncSlotState(){const t=this._slotRef.value;t!=null&&(t.style.setProperty("--current-character-index",String(this._currentIndex)),t.style.setProperty("--_flip-dur","0ms"))}_animateStep(t,a){return S(this,null,function*(){const e=this._slotRef.value;if(e==null)return;this._clearWrapTimer(),e.style.setProperty("--_flip-dur","0ms"),e.style.setProperty("--current-character-index",String(t)),yield new Promise(i=>requestAnimationFrame(()=>{requestAnimationFrame(()=>i())}));const s=t===this.flaps.length-1&&a===0,r=s?this.flaps.length:a;e.style.setProperty("--_flip-dur",`${this._animDur}ms`),e.style.setProperty("--current-character-index",String(r)),s&&(this._wrapResetTimer=setTimeout(()=>{e.style.setProperty("--_flip-dur","0ms"),e.style.setProperty("--current-character-index","0"),this._wrapResetTimer=null,this.requestUpdate()},this._animDur))})}_getRenderCenter(){return this.flaps.length>0&&this._prevIndex===this.flaps.length-1&&this._currentIndex===0&&(this._stepping||this._wrapResetTimer!=null)?this.flaps.length:this._currentIndex}render(){const t=this._getRenderCenter(),a=g(this.flaps,this._currentIndex,{visibleSideCount:this.visibleSideCount,renderCenter:t});return p`
|
|
2
|
+
<div
|
|
3
|
+
${b(this._slotRef)}
|
|
4
|
+
class="slot"
|
|
5
|
+
style=${x({"--total":String(this.flaps.length)})}
|
|
6
|
+
>
|
|
7
|
+
${a.map(({flap:e,actualIndex:s,renderedIndex:r})=>p`
|
|
8
|
+
<div class="character" style="--index: ${r}" data-index=${s}>
|
|
9
|
+
<div class="flap">${this._renderHalf(e,"top")}</div>
|
|
10
|
+
<div class="flap" aria-hidden="true">${this._renderHalf(e,"bottom")}</div>
|
|
11
|
+
</div>
|
|
12
|
+
`)}
|
|
13
|
+
</div>
|
|
14
|
+
`}};o.styles=h`
|
|
15
|
+
:host {
|
|
16
|
+
display: inline-block;
|
|
17
|
+
perspective: var(--sfb-perspective, 400px);
|
|
18
|
+
font-size: 2rem;
|
|
19
|
+
font-family: monospace;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.slot {
|
|
23
|
+
display: grid;
|
|
24
|
+
place-content: center;
|
|
25
|
+
/* Keep board tilt on the inner slot so the drum stays inside host perspective. */
|
|
26
|
+
transform: var(--sfb-view-transform, none);
|
|
27
|
+
transform-style: preserve-3d;
|
|
28
|
+
transition: transform 0.5s cubic-bezier(0.25, 0, 0.3, 1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Derive drum placement and fold angles from the flap offset. */
|
|
32
|
+
.character {
|
|
33
|
+
--offset: calc(var(--index) - var(--current-character-index));
|
|
34
|
+
--abs-offset: max(var(--offset), calc(var(--offset) * -1));
|
|
35
|
+
--safe-abs-offset: max(var(--abs-offset), 0.001);
|
|
36
|
+
--direction: calc(var(--offset) / var(--safe-abs-offset));
|
|
37
|
+
--past: min(0, var(--direction)); /* -1 when behind current, else 0 */
|
|
38
|
+
--future: max(0, var(--direction)); /* +1 when ahead of current, else 0 */
|
|
39
|
+
/* Treat exact offsets as booleans so CSS can target the active neighbors. */
|
|
40
|
+
--is-current: clamp(0, calc(1 - var(--abs-offset) * 1000), 1);
|
|
41
|
+
--is-previous: clamp(0, calc(1 - max(var(--offset) + 1, (var(--offset) + 1) * -1) * 1000), 1);
|
|
42
|
+
--is-next: clamp(0, calc(1 - max(var(--offset) - 1, (var(--offset) - 1) * -1) * 1000), 1);
|
|
43
|
+
/*
|
|
44
|
+
* Keep the outer visible odd-count flap inside the silhouette so it does not
|
|
45
|
+
* vanish at the drum edge.
|
|
46
|
+
*/
|
|
47
|
+
--natural-angle: calc((0.5 / var(--total)) * 1turn);
|
|
48
|
+
/* Cap the visual step angle for small spools so the gap between flaps stays tight. */
|
|
49
|
+
--angle: min(var(--natural-angle), var(--sfb-max-step-angle, 1turn));
|
|
50
|
+
/* Unwrapped drum position; places the fold hinge on the cylinder surface. */
|
|
51
|
+
--drum-a: calc(var(--abs-offset) * var(--direction) * var(--angle));
|
|
52
|
+
/* Top-half angle; +0.5turn for past flaps puts them on the drum backface. */
|
|
53
|
+
--a: calc(var(--abs-offset) * var(--direction) * var(--angle) + var(--past) * 0.5turn);
|
|
54
|
+
/* Bottom-half angle; +0.5turn for future flaps slides them in from below. */
|
|
55
|
+
--a2: calc(
|
|
56
|
+
max(var(--abs-offset) - 1, 0) * var(--direction) * var(--angle) + var(--future) * 0.5turn
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
display: flex;
|
|
60
|
+
grid-area: 1 / 1;
|
|
61
|
+
flex-direction: column;
|
|
62
|
+
gap: var(--sfb-crease, 1px);
|
|
63
|
+
/* Move the fold line onto the drum surface so translation and fold stay aligned. */
|
|
64
|
+
transform: translateZ(calc(var(--sfb-drum-radius, 0px) * cos(var(--drum-a))))
|
|
65
|
+
translateY(calc(var(--sfb-drum-radius, 0px) * sin(var(--drum-a)) * -1));
|
|
66
|
+
transform-style: preserve-3d;
|
|
67
|
+
z-index: calc(var(--is-current) * 2 + var(--is-previous) + var(--is-next));
|
|
68
|
+
transition: transform var(--_flip-dur, 0ms) cubic-bezier(0.25, 0, 0.5, 1);
|
|
69
|
+
pointer-events: none;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.flap {
|
|
73
|
+
position: relative;
|
|
74
|
+
transform-style: preserve-3d;
|
|
75
|
+
backface-visibility: hidden;
|
|
76
|
+
transition: transform var(--_flip-dur, 0ms) cubic-bezier(0.25, 0, 0.5, 1);
|
|
77
|
+
will-change: transform;
|
|
78
|
+
box-sizing: border-box;
|
|
79
|
+
border: 1px solid var(--sfb-flap-border, #2a2a2a);
|
|
80
|
+
border-radius: var(--sfb-spool-radius, 3px);
|
|
81
|
+
background: var(--sfb-bg, #111);
|
|
82
|
+
width: var(--sfb-spool-width, 1em);
|
|
83
|
+
height: calc(var(--sfb-spool-height, 2em) / 2);
|
|
84
|
+
overflow: hidden;
|
|
85
|
+
color: var(--sfb-color, #f5f0e0);
|
|
86
|
+
line-height: 1;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* Nudge the front flap forward so the center seam stays stable during the flip. */
|
|
90
|
+
.flap:first-child {
|
|
91
|
+
transform: translateZ(calc(var(--is-current) * 0.1px)) rotateX(var(--a));
|
|
92
|
+
transform-origin: center calc(100% + var(--sfb-crease, 1px) * 0.5);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.flap:last-child {
|
|
96
|
+
transform: translateZ(calc(var(--is-current) * 0.1px)) rotateX(var(--a2));
|
|
97
|
+
transform-origin: center calc(var(--sfb-crease, 1px) * -0.5);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Use 200% fills so each half crops the same flap content at the fold. */
|
|
101
|
+
.char-inner,
|
|
102
|
+
.image-fill,
|
|
103
|
+
.custom-fill {
|
|
104
|
+
position: absolute;
|
|
105
|
+
left: 0;
|
|
106
|
+
width: 100%;
|
|
107
|
+
height: 200%;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.flap:first-child .char-inner,
|
|
111
|
+
.flap:first-child .image-fill,
|
|
112
|
+
.flap:first-child .custom-fill {
|
|
113
|
+
top: 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.flap:last-child .char-inner,
|
|
117
|
+
.flap:last-child .image-fill,
|
|
118
|
+
.flap:last-child .custom-fill {
|
|
119
|
+
bottom: 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.char-inner {
|
|
123
|
+
display: flex;
|
|
124
|
+
justify-content: center;
|
|
125
|
+
align-items: center;
|
|
126
|
+
font-weight: bold;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.color-fill {
|
|
130
|
+
position: absolute;
|
|
131
|
+
inset: 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.image-fill {
|
|
135
|
+
object-fit: cover;
|
|
136
|
+
object-position: center center;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.custom-fill {
|
|
140
|
+
display: flex;
|
|
141
|
+
justify-content: center;
|
|
142
|
+
align-items: center;
|
|
143
|
+
}
|
|
144
|
+
`,d([u({type:Number})],o.prototype,"visibleSideCount",2),o=d([m("split-flap-spool-realistic")],o);export{o as SplitFlapSpoolRealistic};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const l=[{type:"char",value:" "},..."ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("").map(e=>({type:"char",value:e})),..."0123456789".split("").map(e=>({type:"char",value:e})),{type:"char",value:"."},{type:"char",value:"-"},{type:"char",value:"/"},{type:"char",value:":"}],a=[{type:"char",value:" "},..."0123456789".split("").map(e=>({type:"char",value:e}))],p=[{type:"color",key:"blank",value:"#111111"},{type:"color",key:"red",value:"#ef4444"},{type:"color",key:"orange",value:"#f97316"},{type:"color",key:"yellow",value:"#eab308"},{type:"color",key:"green",value:"#22c55e"},{type:"color",key:"blue",value:"#3b82f6"},{type:"color",key:"purple",value:"#8b5cf6"},{type:"color",key:"pink",value:"#ec4899"}];export{l as charSpool,p as colorSpool,a as numericSpool};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { LitElement, type TemplateResult } from 'lit';
|
|
2
|
+
import './spools/SplitFlapSpool';
|
|
3
|
+
import type { TSpool } from './types';
|
|
4
|
+
export declare class SplitFlapBoard extends LitElement {
|
|
5
|
+
static readonly styles: import("lit").CSSResult;
|
|
6
|
+
/** 2-D grid of spool configs — defines what each position can show. Row-major order. */
|
|
7
|
+
spools: TSpool[][];
|
|
8
|
+
/** 2-D grid of target keys — defines what each position currently shows. Row-major order. */
|
|
9
|
+
grid: string[][];
|
|
10
|
+
/** Flip speed in ms, forwarded to every child spool. */
|
|
11
|
+
speed: number;
|
|
12
|
+
/** Visual variant forwarded to every child spool. */
|
|
13
|
+
variant: 'minimal' | 'realistic';
|
|
14
|
+
/** Number of visible drum sides forwarded to every child spool (-1 = default). */
|
|
15
|
+
visibleSideCount: number;
|
|
16
|
+
/** True while at least one spool is still animating toward its target. */
|
|
17
|
+
private _pendingSettle;
|
|
18
|
+
updated(changed: Map<string, unknown>): void;
|
|
19
|
+
private _getSpoolEls;
|
|
20
|
+
private _checkAllSettled;
|
|
21
|
+
private _dispatchBoardSettled;
|
|
22
|
+
render(): TemplateResult;
|
|
23
|
+
}
|
|
24
|
+
declare global {
|
|
25
|
+
interface HTMLElementTagNameMap {
|
|
26
|
+
'split-flap-board': SplitFlapBoard;
|
|
27
|
+
}
|
|
28
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { TSpool } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Fill a uniform 2-D spool grid.
|
|
4
|
+
*
|
|
5
|
+
* Pass a single `TSpool` to use the same flap set for every cell, or a `TSpool[]`
|
|
6
|
+
* (one spool per column, cycled) to vary the flap set by column.
|
|
7
|
+
*
|
|
8
|
+
* @param spool - Single spool or one spool per column.
|
|
9
|
+
* @param cols - Number of columns.
|
|
10
|
+
* @param rows - Number of rows.
|
|
11
|
+
*/
|
|
12
|
+
export declare function spoolGrid(spool: TSpool | TSpool[], cols: number, rows: number): TSpool[][];
|
|
13
|
+
/**
|
|
14
|
+
* Convert lines of text into a `{ spools, grid }` pair ready for `<split-flap-board>`.
|
|
15
|
+
*
|
|
16
|
+
* Each line can be a plain string or `{ text, bg?, color? }`. Text is uppercased
|
|
17
|
+
* and padded/truncated to `cols` characters to match the default `charSpool` key set.
|
|
18
|
+
*
|
|
19
|
+
* @param lines - Text lines to display.
|
|
20
|
+
* @param cols - Board width in columns.
|
|
21
|
+
*/
|
|
22
|
+
export declare function fromLines(lines: TLineInput[], cols: number): {
|
|
23
|
+
spools: TSpool[][];
|
|
24
|
+
grid: string[][];
|
|
25
|
+
};
|
|
26
|
+
export type TLineInput = string | {
|
|
27
|
+
text: string;
|
|
28
|
+
bg?: string;
|
|
29
|
+
color?: string;
|
|
30
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { TFlap } from '../types';
|
|
2
|
+
/** One flap instance prepared for rendering around the realistic drum. */
|
|
3
|
+
export interface TRenderedFlap {
|
|
4
|
+
flap: TFlap;
|
|
5
|
+
actualIndex: number;
|
|
6
|
+
renderedIndex: number;
|
|
7
|
+
}
|
|
8
|
+
/** Returns the shortest signed distance between two indices on a circular spool. */
|
|
9
|
+
export declare function getSignedCircularOffset(index: number, currentIndex: number, total: number): number;
|
|
10
|
+
/** Clamps visible side count to the maximum non-overlapping half of the drum. */
|
|
11
|
+
export declare function getMaxVisibleSideCount(total: number, visibleSideCount?: number): number;
|
|
12
|
+
/** Returns the subset of flaps that should be rendered around the current center. */
|
|
13
|
+
export declare function getRenderedFlaps(flaps: TFlap[], currentIndex: number, { visibleSideCount, renderCenter }?: {
|
|
14
|
+
visibleSideCount?: number;
|
|
15
|
+
renderCenter?: number;
|
|
16
|
+
}): TRenderedFlap[];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { LitElement, type TemplateResult } from 'lit';
|
|
2
|
+
import type { TSpool } from '../types';
|
|
3
|
+
import './SplitFlapSpoolMinimal';
|
|
4
|
+
import './SplitFlapSpoolRealistic';
|
|
5
|
+
export declare class SplitFlapSpool extends LitElement {
|
|
6
|
+
static readonly styles: import("lit").CSSResult;
|
|
7
|
+
variant: 'minimal' | 'realistic';
|
|
8
|
+
value: string;
|
|
9
|
+
flaps: TSpool;
|
|
10
|
+
speed: number;
|
|
11
|
+
visibleSideCount: number;
|
|
12
|
+
/** Key currently shown by the active variant; falls back to the first flap key before the variant mounts. */
|
|
13
|
+
get currentValue(): string | undefined;
|
|
14
|
+
/** True when the active variant is idle with no pending target. */
|
|
15
|
+
get isSettled(): boolean;
|
|
16
|
+
/** Returns true when the given key exists in the loaded flaps. */
|
|
17
|
+
hasKey(value: string): boolean;
|
|
18
|
+
private _getActiveSpool;
|
|
19
|
+
render(): TemplateResult;
|
|
20
|
+
}
|
|
21
|
+
declare global {
|
|
22
|
+
interface HTMLElementTagNameMap {
|
|
23
|
+
'split-flap-spool': SplitFlapSpool;
|
|
24
|
+
}
|
|
25
|
+
}
|