split-flap-board 0.0.3 → 0.0.4

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 CHANGED
@@ -300,7 +300,7 @@ The variant elements can also be used directly if you prefer not to use the wrap
300
300
  | ------------------ | -------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------- |
301
301
  | `spools` | `TSpool[][]` | — | 2D spool configuration, one per cell. Board dimensions are inferred from this. Always assign a new array reference to update. |
302
302
  | `grid` | `string[][]` | `[]` | 2D array of target keys. Always assign a new array reference to trigger a re-render. |
303
- | `speed` | `number` | `60` | Flip speed in milliseconds forwarded to every child spool. |
303
+ | `speed` | `number` | `60` | Flip speed in milliseconds forwarded to every child spool. |
304
304
  | `variant` | `'minimal' \| 'realistic'` | `'minimal'` | Visual variant forwarded to every child spool. |
305
305
  | `visibleSideCount` | `number` | `-1` | Forwarded to child spools. Only affects the `realistic` variant. |
306
306
 
@@ -366,12 +366,19 @@ board.grid = grid;
366
366
  Set these on the board to theme all spools at once, or override on individual spools via CSS selectors.
367
367
 
368
368
  ```css
369
- /* Shared */
369
+ /* Board panel */
370
370
  split-flap-board {
371
- --sfb-bg: #111; /* flap background */
372
- --sfb-color: #f5f0e0; /* flap text color */
373
- --sfb-spool-radius: 4px; /* corner radius on each flap */
374
- --sfb-gap: 2px; /* gap between spool cells */
371
+ --sfb-board-bg: #1c1c1c; /* panel and frame background */
372
+ --sfb-board-padding: 10px; /* inset spacing around the cell grid */
373
+ --sfb-board-radius: 8px; /* corner radius of the panel itself */
374
+ --sfb-gap: 3px; /* gap between spool cells */
375
+ }
376
+
377
+ /* Shared (flap) */
378
+ split-flap-board {
379
+ --sfb-flap-bg: #111; /* flap background */
380
+ --sfb-flap-color: #f5f0e0; /* flap text color */
381
+ --sfb-flap-radius: 4px; /* corner radius on each flap */
375
382
  }
376
383
 
377
384
  /* Minimal variant */
@@ -395,8 +402,8 @@ split-flap-board {
395
402
 
396
403
  /* Per-spool override */
397
404
  split-flap-spool.highlight {
398
- --sfb-bg: #16a34a;
399
- --sfb-color: #fff;
405
+ --sfb-flap-bg: #16a34a;
406
+ --sfb-flap-color: #fff;
400
407
  }
401
408
  ```
402
409
 
@@ -1,18 +1,61 @@
1
- "use strict";var a=require("lit"),i=require("lit/decorators.js");require("./spools/SplitFlapSpool.js");var n=Object.defineProperty,u=Object.getOwnPropertyDescriptor,o=(d,t,r,l)=>{for(var e=l>1?void 0:l?u(t,r):t,s=d.length-1,p;s>=0;s--)(p=d[s])&&(e=(l?p(t,r,e):p(e))||e);return l&&e&&n(t,r,e),e};exports.SplitFlapBoard=class extends a.LitElement{constructor(){super(...arguments),this.spools=[],this.grid=[],this.speed=60,this.variant="minimal",this.visibleSideCount=-1,this._pendingSettle=!1}updated(t){var r,l;super.updated(t);const e=(l=(r=this.spools[0])==null?void 0:r.length)!=null?l:0;this.style.gridTemplateColumns=e>0?`repeat(${e}, 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(r=>r.isSettled)&&(this._pendingSettle=!1,this._dispatchBoardSettled(t))}_dispatchBoardSettled(t){let r=0;const l=this.spools.map(e=>e.map(()=>{var s,p;return(p=(s=t[r++])==null?void 0:s.currentValue)!=null?p:""}));this.dispatchEvent(new CustomEvent("board-settled",{detail:{grid:l},bubbles:!0,composed:!0}))}render(){return a.html`
2
- ${this.spools.flatMap((t,r)=>t.map((l,e)=>{var s,p;return a.html`
3
- <split-flap-spool
4
- .flaps=${l}
5
- .value=${(p=(s=this.grid[r])==null?void 0:s[e])!=null?p:""}
6
- .speed=${this.speed}
7
- .variant=${this.variant}
8
- .visibleSideCount=${this.visibleSideCount}
9
- @settled=${()=>this._checkAllSettled()}
10
- ></split-flap-spool>
11
- `}))}
12
- `}},exports.SplitFlapBoard.styles=a.css`
1
+ "use strict";var p=require("lit"),i=require("lit/decorators.js");require("./spools/SplitFlapSpool.js");var n=Object.defineProperty,b=Object.getOwnPropertyDescriptor,l=(d,e,r,s)=>{for(var t=s>1?void 0:s?b(e,r):e,o=d.length-1,a;o>=0;o--)(a=d[o])&&(t=(s?a(e,r,t):a(t))||t);return s&&t&&n(e,r,t),t};exports.SplitFlapBoard=class extends p.LitElement{constructor(){super(...arguments),this.spools=[],this.grid=[],this.speed=60,this.variant="minimal",this.visibleSideCount=-1,this._pendingSettle=!1}updated(e){super.updated(e),(e.has("spools")||e.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 e=this._getSpoolEls();e.length!==0&&e.every(r=>r.isSettled)&&(this._pendingSettle=!1,this._dispatchBoardSettled(e))}_dispatchBoardSettled(e){let r=0;const s=this.spools.map(t=>t.map(()=>{var o,a;return(a=(o=e[r++])==null?void 0:o.currentValue)!=null?a:""}));this.dispatchEvent(new CustomEvent("board-settled",{detail:{grid:s},bubbles:!0,composed:!0}))}render(){return p.html`
2
+ ${this.spools.map((e,r)=>p.html`
3
+ <div class="board-row" style="z-index: ${r+1}">
4
+ ${e.map((s,t)=>{var o,a;return p.html`
5
+ <split-flap-spool
6
+ .flaps=${s}
7
+ .value=${(a=(o=this.grid[r])==null?void 0:o[t])!=null?a:""}
8
+ .speed=${this.speed}
9
+ .variant=${this.variant}
10
+ .visibleSideCount=${this.visibleSideCount}
11
+ @settled=${()=>this._checkAllSettled()}
12
+ ></split-flap-spool>
13
+ `})}
14
+ </div>
15
+ `)}
16
+ `}},exports.SplitFlapBoard.styles=p.css`
13
17
  :host {
14
- display: inline-grid;
15
- gap: var(--sfb-gap, 2px);
18
+ display: inline-flex;
19
+ position: relative;
20
+ flex-direction: column;
16
21
  box-sizing: border-box;
22
+ /* Inset shadows for side rails; render below all children so frame bars
23
+ * appear at the same visual level. */
24
+ box-shadow:
25
+ inset 14px 0 18px rgba(0, 0, 0, 0.55),
26
+ inset -14px 0 18px rgba(0, 0, 0, 0.55);
27
+ border-radius: var(--sfb-board-radius, 8px);
28
+ background: var(--sfb-board-bg, #1c1c1c);
29
+ padding: var(--sfb-board-padding, 10px);
17
30
  }
18
- `,o([i.property({type:Array})],exports.SplitFlapBoard.prototype,"spools",2),o([i.property({type:Array})],exports.SplitFlapBoard.prototype,"grid",2),o([i.property({type:Number})],exports.SplitFlapBoard.prototype,"speed",2),o([i.property({type:String})],exports.SplitFlapBoard.prototype,"variant",2),o([i.property({type:Number})],exports.SplitFlapBoard.prototype,"visibleSideCount",2),exports.SplitFlapBoard=o([i.customElement("split-flap-board")],exports.SplitFlapBoard);
31
+
32
+ /* Absolute overlay so the outer frame ring adds no layout space. */
33
+ :host::after {
34
+ position: absolute;
35
+ z-index: 10000;
36
+ inset: 0;
37
+ border: 10px solid var(--sfb-board-bg, #1c1c1c);
38
+ border-radius: var(--sfb-board-radius, 8px);
39
+ pointer-events: none;
40
+ content: '';
41
+ }
42
+
43
+ /* Frame bar caps the top padding of each row. z-index increases per row in
44
+ * render() so each bar sits above the drum content of the row above it. */
45
+ .board-row {
46
+ display: flex;
47
+ position: relative;
48
+ gap: var(--sfb-gap, 3px);
49
+ padding-block: 12px;
50
+ }
51
+
52
+ .board-row::before {
53
+ position: absolute;
54
+ z-index: 1;
55
+ inset: 0 0 auto 0;
56
+ box-shadow: 0 3px 10px rgba(0, 0, 0, 0.65);
57
+ background: var(--sfb-board-bg, #1c1c1c);
58
+ height: 10px;
59
+ content: '';
60
+ }
61
+ `,l([i.property({type:Array})],exports.SplitFlapBoard.prototype,"spools",2),l([i.property({type:Array})],exports.SplitFlapBoard.prototype,"grid",2),l([i.property({type:Number})],exports.SplitFlapBoard.prototype,"speed",2),l([i.property({type:String})],exports.SplitFlapBoard.prototype,"variant",2),l([i.property({type:Number})],exports.SplitFlapBoard.prototype,"visibleSideCount",2),exports.SplitFlapBoard=l([i.customElement("split-flap-board")],exports.SplitFlapBoard);
@@ -1,4 +1,4 @@
1
- "use strict";var e=require("lit"),a=require("lit/decorators.js"),s=require("./SplitFlapSpoolBase.js"),n=(t,l,p,f)=>{for(var o=l,i=t.length-1,r;i>=0;i--)(r=t[i])&&(o=r(o)||o);return o};exports.SplitFlapSpoolMinimal=class extends s.SplitFlapSpoolBase{render(){return e.html`
1
+ "use strict";var e=require("lit"),l=require("lit/decorators.js"),s=require("./SplitFlapSpoolBase.js"),n=(t,a,p,f)=>{for(var o=a,i=t.length-1,r;i>=0;i--)(r=t[i])&&(o=r(o)||o);return o};exports.SplitFlapSpoolMinimal=class extends s.SplitFlapSpoolBase{render(){return e.html`
2
2
  <div class="spool" style="--_anim-dur: ${this._animDur}ms">${this._renderCard()}</div>
3
3
  `}},exports.SplitFlapSpoolMinimal.styles=e.css`
4
4
  :host {
@@ -19,23 +19,23 @@
19
19
  position: absolute;
20
20
  right: 0;
21
21
  left: 0;
22
- background: var(--sfb-bg, #111);
22
+ background: var(--sfb-flap-bg, #111);
23
23
  height: 50%;
24
24
  overflow: hidden;
25
- color: var(--sfb-color, #f5f0e0);
25
+ color: var(--sfb-flap-color, #f5f0e0);
26
26
  }
27
27
 
28
28
  .half.top {
29
29
  top: 0;
30
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);
31
+ border-top-right-radius: var(--sfb-flap-radius, 4px);
32
+ border-top-left-radius: var(--sfb-flap-radius, 4px);
33
33
  }
34
34
 
35
35
  .half.bottom {
36
36
  bottom: 0;
37
- border-bottom-right-radius: var(--sfb-spool-radius, 4px);
38
- border-bottom-left-radius: var(--sfb-spool-radius, 4px);
37
+ border-bottom-right-radius: var(--sfb-flap-radius, 4px);
38
+ border-bottom-left-radius: var(--sfb-flap-radius, 4px);
39
39
  }
40
40
 
41
41
  .half.flipping {
@@ -118,4 +118,4 @@
118
118
  inset: 0;
119
119
  overflow: hidden;
120
120
  }
121
- `,exports.SplitFlapSpoolMinimal=n([a.customElement("split-flap-spool-minimal")],exports.SplitFlapSpoolMinimal);
121
+ `,exports.SplitFlapSpoolMinimal=n([l.customElement("split-flap-spool-minimal")],exports.SplitFlapSpoolMinimal);
@@ -1,17 +1,21 @@
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`
1
+ "use strict";var p=require("lit"),f=require("lit/decorators.js"),d=require("lit/directives/ref.js"),u=require("lit/directives/style-map.js");require("./presets.js");var v=require("../lib/spool-layout.js"),m=require("./SplitFlapSpoolBase.js"),b=Object.defineProperty,g=Object.getOwnPropertyDescriptor,h=(n,e,a,t)=>{for(var r=t>1?void 0:t?g(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},x=(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 m.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 x(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=v.getRenderedFlaps(this.flaps,this._currentIndex,{visibleSideCount:this.visibleSideCount,renderCenter:e});return p.html`
2
2
  <div
3
3
  ${d.ref(this._slotRef)}
4
4
  class="slot"
5
5
  style=${u.styleMap({"--total":String(this.flaps.length)})}
6
6
  >
7
- ${a.map(({flap:t,actualIndex:r,renderedIndex:s})=>f.html`
8
- <div class="character" style="--index: ${s}" data-index=${r}>
7
+ ${a.map(({flap:t,actualIndex:r,renderedIndex:s})=>{const i=this.flaps.length>2&&this.flaps.length%2===0&&Math.abs(s-e)===this.flaps.length/2;return p.html`
8
+ <div
9
+ class="character ${i?"is-background":""}"
10
+ style="--index: ${s}"
11
+ data-index=${r}
12
+ >
9
13
  <div class="flap">${this._renderHalf(t,"top")}</div>
10
14
  <div class="flap" aria-hidden="true">${this._renderHalf(t,"bottom")}</div>
11
15
  </div>
12
- `)}
16
+ `})}
13
17
  </div>
14
- `}},exports.SplitFlapSpoolRealistic.styles=f.css`
18
+ `}},exports.SplitFlapSpoolRealistic.styles=p.css`
15
19
  :host {
16
20
  display: inline-block;
17
21
  perspective: var(--sfb-perspective, 400px);
@@ -69,6 +73,11 @@
69
73
  pointer-events: none;
70
74
  }
71
75
 
76
+ .character.is-background {
77
+ opacity: 0;
78
+ z-index: 0;
79
+ }
80
+
72
81
  .flap {
73
82
  position: relative;
74
83
  transform-style: preserve-3d;
@@ -76,13 +85,16 @@
76
85
  transition: transform var(--_flip-dur, 0ms) cubic-bezier(0.25, 0, 0.5, 1);
77
86
  will-change: transform;
78
87
  box-sizing: border-box;
88
+ box-shadow:
89
+ 0 2px 6px rgba(0, 0, 0, 0.7),
90
+ inset 0 1px 0 rgba(255, 255, 255, 0.04);
79
91
  border: 1px solid var(--sfb-flap-border, #2a2a2a);
80
- border-radius: var(--sfb-spool-radius, 3px);
81
- background: var(--sfb-bg, #111);
92
+ border-radius: var(--sfb-flap-radius, 5px);
93
+ background: var(--sfb-flap-bg, #111);
82
94
  width: var(--sfb-spool-width, 1em);
83
95
  height: calc(var(--sfb-spool-height, 2em) / 2);
84
96
  overflow: hidden;
85
- color: var(--sfb-color, #f5f0e0);
97
+ color: var(--sfb-flap-color, #f5f0e0);
86
98
  line-height: 1;
87
99
  }
88
100
 
@@ -141,4 +153,4 @@
141
153
  justify-content: center;
142
154
  align-items: center;
143
155
  }
144
- `,h([p.property({type:Number})],exports.SplitFlapSpoolRealistic.prototype,"visibleSideCount",2),exports.SplitFlapSpoolRealistic=h([p.customElement("split-flap-spool-realistic")],exports.SplitFlapSpoolRealistic);
156
+ `,h([f.property({type:Number})],exports.SplitFlapSpoolRealistic.prototype,"visibleSideCount",2),exports.SplitFlapSpoolRealistic=h([f.customElement("split-flap-spool-realistic")],exports.SplitFlapSpoolRealistic);
@@ -1,18 +1,61 @@
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`
1
+ import{css as b,LitElement as h,html as n}from"lit";import{property as p,customElement as c}from"lit/decorators.js";import"./spools/SplitFlapSpool.js";var u=Object.defineProperty,v=Object.getOwnPropertyDescriptor,l=(e,r,a,i)=>{for(var t=i>1?void 0:i?v(r,a):r,o=e.length-1,d;o>=0;o--)(d=e[o])&&(t=(i?d(r,a,t):d(t))||t);return i&&t&&u(r,a,t),t};let s=class extends h{constructor(){super(...arguments),this.spools=[],this.grid=[],this.speed=60,this.variant="minimal",this.visibleSideCount=-1,this._pendingSettle=!1}updated(e){super.updated(e),(e.has("spools")||e.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 e=this._getSpoolEls();e.length!==0&&e.every(r=>r.isSettled)&&(this._pendingSettle=!1,this._dispatchBoardSettled(e))}_dispatchBoardSettled(e){let r=0;const a=this.spools.map(i=>i.map(()=>{var t,o;return(o=(t=e[r++])==null?void 0:t.currentValue)!=null?o:""}));this.dispatchEvent(new CustomEvent("board-settled",{detail:{grid:a},bubbles:!0,composed:!0}))}render(){return n`
2
+ ${this.spools.map((e,r)=>n`
3
+ <div class="board-row" style="z-index: ${r+1}">
4
+ ${e.map((a,i)=>{var t,o;return n`
5
+ <split-flap-spool
6
+ .flaps=${a}
7
+ .value=${(o=(t=this.grid[r])==null?void 0:t[i])!=null?o:""}
8
+ .speed=${this.speed}
9
+ .variant=${this.variant}
10
+ .visibleSideCount=${this.visibleSideCount}
11
+ @settled=${()=>this._checkAllSettled()}
12
+ ></split-flap-spool>
13
+ `})}
14
+ </div>
15
+ `)}
16
+ `}};s.styles=b`
13
17
  :host {
14
- display: inline-grid;
15
- gap: var(--sfb-gap, 2px);
18
+ display: inline-flex;
19
+ position: relative;
20
+ flex-direction: column;
16
21
  box-sizing: border-box;
22
+ /* Inset shadows for side rails; render below all children so frame bars
23
+ * appear at the same visual level. */
24
+ box-shadow:
25
+ inset 14px 0 18px rgba(0, 0, 0, 0.55),
26
+ inset -14px 0 18px rgba(0, 0, 0, 0.55);
27
+ border-radius: var(--sfb-board-radius, 8px);
28
+ background: var(--sfb-board-bg, #1c1c1c);
29
+ padding: var(--sfb-board-padding, 10px);
17
30
  }
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};
31
+
32
+ /* Absolute overlay so the outer frame ring adds no layout space. */
33
+ :host::after {
34
+ position: absolute;
35
+ z-index: 10000;
36
+ inset: 0;
37
+ border: 10px solid var(--sfb-board-bg, #1c1c1c);
38
+ border-radius: var(--sfb-board-radius, 8px);
39
+ pointer-events: none;
40
+ content: '';
41
+ }
42
+
43
+ /* Frame bar caps the top padding of each row. z-index increases per row in
44
+ * render() so each bar sits above the drum content of the row above it. */
45
+ .board-row {
46
+ display: flex;
47
+ position: relative;
48
+ gap: var(--sfb-gap, 3px);
49
+ padding-block: 12px;
50
+ }
51
+
52
+ .board-row::before {
53
+ position: absolute;
54
+ z-index: 1;
55
+ inset: 0 0 auto 0;
56
+ box-shadow: 0 3px 10px rgba(0, 0, 0, 0.65);
57
+ background: var(--sfb-board-bg, #1c1c1c);
58
+ height: 10px;
59
+ content: '';
60
+ }
61
+ `,l([p({type:Array})],s.prototype,"spools",2),l([p({type:Array})],s.prototype,"grid",2),l([p({type:Number})],s.prototype,"speed",2),l([p({type:String})],s.prototype,"variant",2),l([p({type:Number})],s.prototype,"visibleSideCount",2),s=l([c("split-flap-board")],s);export{s as SplitFlapBoard};
@@ -1,4 +1,4 @@
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`
1
+ import{css as l,html as s}from"lit";import{customElement as f}from"lit/decorators.js";import{SplitFlapSpoolBase as n}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 n{render(){return s`
2
2
  <div class="spool" style="--_anim-dur: ${this._animDur}ms">${this._renderCard()}</div>
3
3
  `}};t.styles=l`
4
4
  :host {
@@ -19,23 +19,23 @@ import{css as l,html as s}from"lit";import{customElement as n}from"lit/decorator
19
19
  position: absolute;
20
20
  right: 0;
21
21
  left: 0;
22
- background: var(--sfb-bg, #111);
22
+ background: var(--sfb-flap-bg, #111);
23
23
  height: 50%;
24
24
  overflow: hidden;
25
- color: var(--sfb-color, #f5f0e0);
25
+ color: var(--sfb-flap-color, #f5f0e0);
26
26
  }
27
27
 
28
28
  .half.top {
29
29
  top: 0;
30
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);
31
+ border-top-right-radius: var(--sfb-flap-radius, 4px);
32
+ border-top-left-radius: var(--sfb-flap-radius, 4px);
33
33
  }
34
34
 
35
35
  .half.bottom {
36
36
  bottom: 0;
37
- border-bottom-right-radius: var(--sfb-spool-radius, 4px);
38
- border-bottom-left-radius: var(--sfb-spool-radius, 4px);
37
+ border-bottom-right-radius: var(--sfb-flap-radius, 4px);
38
+ border-bottom-left-radius: var(--sfb-flap-radius, 4px);
39
39
  }
40
40
 
41
41
  .half.flipping {
@@ -118,4 +118,4 @@ import{css as l,html as s}from"lit";import{customElement as n}from"lit/decorator
118
118
  inset: 0;
119
119
  overflow: hidden;
120
120
  }
121
- `,t=p([n("split-flap-spool-minimal")],t);export{t as SplitFlapSpoolMinimal};
121
+ `,t=p([f("split-flap-spool-minimal")],t);export{t as SplitFlapSpoolMinimal};
@@ -4,12 +4,16 @@ import{css as h,html as p}from"lit";import{property as u,customElement as m}from
4
4
  class="slot"
5
5
  style=${x({"--total":String(this.flaps.length)})}
6
6
  >
7
- ${a.map(({flap:e,actualIndex:s,renderedIndex:r})=>p`
8
- <div class="character" style="--index: ${r}" data-index=${s}>
7
+ ${a.map(({flap:e,actualIndex:s,renderedIndex:r})=>{const i=this.flaps.length>2&&this.flaps.length%2===0&&Math.abs(r-t)===this.flaps.length/2;return p`
8
+ <div
9
+ class="character ${i?"is-background":""}"
10
+ style="--index: ${r}"
11
+ data-index=${s}
12
+ >
9
13
  <div class="flap">${this._renderHalf(e,"top")}</div>
10
14
  <div class="flap" aria-hidden="true">${this._renderHalf(e,"bottom")}</div>
11
15
  </div>
12
- `)}
16
+ `})}
13
17
  </div>
14
18
  `}};o.styles=h`
15
19
  :host {
@@ -69,6 +73,11 @@ import{css as h,html as p}from"lit";import{property as u,customElement as m}from
69
73
  pointer-events: none;
70
74
  }
71
75
 
76
+ .character.is-background {
77
+ opacity: 0;
78
+ z-index: 0;
79
+ }
80
+
72
81
  .flap {
73
82
  position: relative;
74
83
  transform-style: preserve-3d;
@@ -76,13 +85,16 @@ import{css as h,html as p}from"lit";import{property as u,customElement as m}from
76
85
  transition: transform var(--_flip-dur, 0ms) cubic-bezier(0.25, 0, 0.5, 1);
77
86
  will-change: transform;
78
87
  box-sizing: border-box;
88
+ box-shadow:
89
+ 0 2px 6px rgba(0, 0, 0, 0.7),
90
+ inset 0 1px 0 rgba(255, 255, 255, 0.04);
79
91
  border: 1px solid var(--sfb-flap-border, #2a2a2a);
80
- border-radius: var(--sfb-spool-radius, 3px);
81
- background: var(--sfb-bg, #111);
92
+ border-radius: var(--sfb-flap-radius, 5px);
93
+ background: var(--sfb-flap-bg, #111);
82
94
  width: var(--sfb-spool-width, 1em);
83
95
  height: calc(var(--sfb-spool-height, 2em) / 2);
84
96
  overflow: hidden;
85
- color: var(--sfb-color, #f5f0e0);
97
+ color: var(--sfb-flap-color, #f5f0e0);
86
98
  line-height: 1;
87
99
  }
88
100
 
@@ -3,9 +3,9 @@ import './spools/SplitFlapSpool';
3
3
  import type { TSpool } from './types';
4
4
  export declare class SplitFlapBoard extends LitElement {
5
5
  static readonly styles: import("lit").CSSResult;
6
- /** 2-D grid of spool configs defines what each position can show. Row-major order. */
6
+ /** 2-D grid of spool configs; defines what each position can show. Row-major order. */
7
7
  spools: TSpool[][];
8
- /** 2-D grid of target keys defines what each position currently shows. Row-major order. */
8
+ /** 2-D grid of target keys; defines what each position currently shows. Row-major order. */
9
9
  grid: string[][];
10
10
  /** Flip speed in ms, forwarded to every child spool. */
11
11
  speed: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "split-flap-board",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "private": false,
5
5
  "description": "Web component that simulates a split-flap display inspired by airport and train station boards",
6
6
  "keywords": [