zx-kit 0.8.0 → 0.9.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 CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  Reusable ZX Spectrum primitives for browser games built with Vite + TypeScript + Canvas + Web Audio API.
4
4
 
5
- Current npm package: `zx-kit@0.6.7`.
6
-
7
5
  Extracted from [Minefield](https://github.com/zrebec/minefield) — a ZX Spectrum-style minesweeper game. All modules enforce strict Spectrum authenticity: 8×8 pixel grid, 15-color palette, 1-bit square-wave audio, bitmap font.
8
6
 
9
7
  ---
@@ -660,30 +658,308 @@ console.log(`${gemsLeft} gems remaining`)
660
658
 
661
659
  ---
662
660
 
661
+ ### `sprite.ts` — Free-roaming entity system
662
+
663
+ Sprites are entities that move freely in pixel space — not locked to the tile grid like `TileMap` cells. Use sprites for players, enemies, bullets, particles: anything that moves at sub-pixel precision or with physics.
664
+
665
+ Sprites use the same 8×8 bitmap format as `drawSprite` and the same `SpectrumColor` palette. They integrate directly with `resolveX` / `resolveY` from `collision.ts` and with `TileMap.isSolid` for platformer-style collision detection.
666
+
667
+ #### `Sprite` interface
668
+
669
+ | Field | Type | Default | Description |
670
+ |-------|------|---------|-------------|
671
+ | `x` | `number` | `0` | Horizontal position in game pixels (float allowed — rounded on render) |
672
+ | `y` | `number` | `0` | Vertical position in game pixels |
673
+ | `vx` | `number` | `0` | Horizontal velocity in pixels per millisecond |
674
+ | `vy` | `number` | `0` | Vertical velocity in pixels per millisecond |
675
+ | `bitmap` | `Uint8Array` | — | 8-byte sprite — one byte per row, bit 7 = leftmost pixel |
676
+ | `ink` | `SpectrumColor` | — | Foreground colour (`C.*` palette value) |
677
+ | `paper` | `SpectrumColor \| null` | `null` | Background colour, or `null` for transparent background |
678
+ | `flipX` | `boolean` | `false` | Render mirrored horizontally. Flipped bitmap is cached — no per-frame allocation |
679
+ | `visible` | `boolean` | `true` | When `false`, `renderSprite` skips this entity entirely |
680
+
681
+ #### `createSprite(bitmap, ink, paper?): Sprite`
682
+
683
+ Creates a `Sprite` at `(0, 0)` with zero velocity. `paper` defaults to `null` (transparent background).
684
+
685
+ ```ts
686
+ import { createSprite, C } from 'zx-kit'
687
+
688
+ const PLAYER_BM = new Uint8Array([0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0x7E, 0x18, 0x3C])
689
+ const BULLET_BM = new Uint8Array([0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00])
690
+
691
+ const player = createSprite(PLAYER_BM, C.B_CYAN) // transparent bg
692
+ const bullet = createSprite(BULLET_BM, C.B_WHITE, C.BLACK) // opaque bg
693
+ player.x = 16
694
+ player.y = 80
695
+ ```
696
+
697
+ #### `moveSprite(sprite, dt): void`
698
+
699
+ Advances `sprite.x` by `vx * dt` and `sprite.y` by `vy * dt`. Call once per frame **before** collision resolution.
700
+
701
+ ```ts
702
+ // Typical frame update order:
703
+ applyGravity(player, 0.003, dt)
704
+ moveSprite(player, dt)
705
+ const rx = resolveX(player, map, player.x)
706
+ const ry = resolveY(player, map, player.y)
707
+ player.x = rx.x
708
+ player.y = ry.y
709
+ if (ry.hitBottom) player.vy = 0
710
+ ```
711
+
712
+ #### `applyGravity(sprite, gravity, dt): void`
713
+
714
+ Adds `gravity * dt` to `sprite.vy`. Call once per frame **before** `moveSprite`.
715
+
716
+ - `gravity` is in pixels per millisecond² — typical values: `0.002`–`0.005`
717
+ - Accumulates naturally across frames so the sprite accelerates while airborne
718
+
719
+ ```ts
720
+ // Gentle gravity (platformer)
721
+ applyGravity(player, 0.003, dt)
722
+
723
+ // Strong gravity (bullet hell with falling debris)
724
+ applyGravity(debris, 0.008, dt)
725
+ ```
726
+
727
+ #### `renderSprite(ctx, sprite): void`
728
+
729
+ Draws the sprite at `(Math.round(sprite.x), Math.round(sprite.y))`.
730
+ Skips if `sprite.visible === false`.
731
+ Respects `flipX` (cached mirror) and `paper: null` (transparent — only ink pixels drawn).
732
+
733
+ Call **after** all physics updates and collision resolution, as the last step before `drawScanlines`.
734
+
735
+ ```ts
736
+ // Transparent sprite over a scrolling background
737
+ player.paper = null // default — no background square
738
+ renderSprite(ctx, player)
739
+
740
+ // Opaque sprite (always paints its 8×8 cell background)
741
+ bullet.paper = C.BLACK
742
+ renderSprite(ctx, bullet)
743
+
744
+ // Facing direction
745
+ player.flipX = (player.vx < 0)
746
+ renderSprite(ctx, player)
747
+ ```
748
+
749
+ #### Full example — platformer player
750
+
751
+ ```ts
752
+ import { createSprite, moveSprite, applyGravity, renderSprite, C, CELL } from 'zx-kit'
753
+ import { resolveX, resolveY } from 'zx-kit'
754
+ import { tickMovement, consumeFlag } from 'zx-kit'
755
+
756
+ const PLAYER_BM = new Uint8Array([0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0x7E, 0x24, 0x66])
757
+
758
+ const player = createSprite(PLAYER_BM, C.B_CYAN)
759
+ player.x = 2 * CELL
760
+ player.y = 10 * CELL
761
+
762
+ let onGround = false
763
+ const JUMP_VELOCITY = -0.25 // pixels/ms upward
764
+ const WALK_VELOCITY = 0.08 // pixels/ms horizontal
765
+ const GRAVITY = 0.003 // pixels/ms²
766
+
767
+ function updatePlayer(dt: number, map: TileMap) {
768
+ // Input
769
+ const dir = tickMovement(dt)
770
+ player.vx = dir === 'right' ? WALK_VELOCITY
771
+ : dir === 'left' ? -WALK_VELOCITY
772
+ : 0
773
+ if (dir === 'up' && onGround) player.vy = JUMP_VELOCITY
774
+
775
+ // Flip sprite to face movement direction
776
+ if (player.vx < 0) player.flipX = true
777
+ if (player.vx > 0) player.flipX = false
778
+
779
+ // Physics
780
+ applyGravity(player, GRAVITY, dt)
781
+ moveSprite(player, dt)
782
+
783
+ // Collision
784
+ const rx = resolveX(player, map, player.x)
785
+ player.x = rx.x
786
+ if (rx.hitLeft || rx.hitRight) player.vx = 0
787
+
788
+ const ry = resolveY(player, map, player.y)
789
+ player.y = ry.y
790
+ onGround = ry.hitBottom
791
+ if (ry.hitBottom || ry.hitTop) player.vy = 0
792
+ }
793
+ ```
794
+
795
+ ---
796
+
797
+ ### `collision.ts` — AABB collision detection and resolution
798
+
799
+ Axis-aligned bounding box (AABB) helpers for sprite-vs-sprite overlap tests and sprite-vs-`TileMap` collision resolution. All coordinates are in game pixels.
800
+
801
+ #### `Rect` interface
802
+
803
+ | Field | Type | Description |
804
+ |-------|------|-------------|
805
+ | `x` | `number` | Left edge in game pixels |
806
+ | `y` | `number` | Top edge in game pixels |
807
+ | `w` | `number` | Width in game pixels |
808
+ | `h` | `number` | Height in game pixels |
809
+
810
+ #### `spriteRect(sprite): Rect`
811
+
812
+ Returns the bounding `Rect` for a sprite — always `CELL × CELL` at the sprite's current position. Uses the raw float position (no rounding) for accurate same-frame overlap tests.
813
+
814
+ ```ts
815
+ const rect = spriteRect(player) // { x: 24.5, y: 80.0, w: 8, h: 8 }
816
+ ```
817
+
818
+ #### `rectsOverlap(a, b): boolean`
819
+
820
+ Returns `true` when rectangles share at least one pixel. Touching edges (zero-area overlap) returns `false`.
821
+
822
+ ```ts
823
+ rectsOverlap(spriteRect(bullet), spriteRect(enemy)) // hit test
824
+ rectsOverlap({ x: 0, y: 0, w: 32, h: 32 }, { x: 16, y: 16, w: 8, h: 8 }) // contained → true
825
+ ```
826
+
827
+ #### `spritesOverlap(a, b): boolean`
828
+
829
+ Shorthand for `rectsOverlap(spriteRect(a), spriteRect(b))`.
830
+
831
+ ```ts
832
+ if (spritesOverlap(player, coin)) collectCoin(coin)
833
+ if (bullets.some(b => spritesOverlap(b, player))) loseLife()
834
+ ```
835
+
836
+ #### `isSolidAt(map, px, py): boolean`
837
+
838
+ Tests whether the game pixel `(px, py)` falls inside a solid tile. Converts to tile coordinates via `Math.floor(px / CELL)`. Out-of-bounds pixels return `true` — the map boundary is an implicit solid wall.
839
+
840
+ ```ts
841
+ // Is the pixel directly below the player's feet solid?
842
+ if (isSolidAt(map, player.x, player.y + CELL)) onGround = true
843
+
844
+ // Same check via resolveY — usually more convenient
845
+ ```
846
+
847
+ #### `resolveX(sprite, map, newX): { x, hitLeft, hitRight }`
848
+
849
+ Resolves a proposed horizontal move against solid tiles. Tests the leading edge of the sprite's bounding box at `newX` and returns the clamped position plus hit flags.
850
+
851
+ - `hitLeft` — the sprite hit a wall while moving left
852
+ - `hitRight` — the sprite hit a wall while moving right
853
+ - Always returns the original `newX` (unclamped) when no collision occurs
854
+ - Out-of-bounds overshoot is clamped correctly — the map boundary acts as an implicit wall
855
+
856
+ ```ts
857
+ moveSprite(player, dt)
858
+ const { x, hitLeft, hitRight } = resolveX(player, map, player.x)
859
+ player.x = x
860
+ if (hitLeft || hitRight) player.vx = 0
861
+ ```
862
+
863
+ #### `resolveY(sprite, map, newY): { y, hitTop, hitBottom }`
864
+
865
+ Resolves a proposed vertical move against solid tiles. Same contract as `resolveX`.
866
+
867
+ - `hitBottom` — the sprite landed on a floor tile (use to detect "on ground" for jump logic)
868
+ - `hitTop` — the sprite bumped its head on a ceiling tile
869
+
870
+ ```ts
871
+ applyGravity(player, 0.003, dt)
872
+ moveSprite(player, dt)
873
+ const { y, hitBottom, hitTop } = resolveY(player, map, player.y)
874
+ player.y = y
875
+ if (hitBottom) { player.vy = 0; onGround = true }
876
+ if (hitTop) { player.vy = 0 }
877
+ ```
878
+
879
+ #### Full example — bullet hell enemy swarm
880
+
881
+ ```ts
882
+ import { createSprite, moveSprite, renderSprite, spritesOverlap, C, CELL } from 'zx-kit'
883
+ import type { Sprite } from 'zx-kit'
884
+
885
+ const BULLET_BM = new Uint8Array([0x00, 0x18, 0x3C, 0x3C, 0x3C, 0x3C, 0x18, 0x00])
886
+ const ENEMY_BM = new Uint8Array([0x3C, 0x7E, 0xFF, 0xDB, 0xFF, 0x66, 0x42, 0x81])
887
+
888
+ const bullets: Sprite[] = []
889
+ const enemies: Sprite[] = []
890
+
891
+ // Spawn a bullet traveling upward
892
+ function fireBullet(x: number, y: number) {
893
+ const b = createSprite(BULLET_BM, C.B_YELLOW)
894
+ b.x = x; b.y = y; b.vy = -0.3 // pixels/ms upward
895
+ bullets.push(b)
896
+ }
897
+
898
+ // Spawn enemies in a row
899
+ for (let i = 0; i < 8; i++) {
900
+ const e = createSprite(ENEMY_BM, C.B_RED, C.BLACK)
901
+ e.x = i * 2 * CELL + 8
902
+ e.y = 2 * CELL
903
+ e.vx = 0.03 * (i % 2 === 0 ? 1 : -1) // alternate directions
904
+ enemies.push(e)
905
+ }
906
+
907
+ // Per-frame update
908
+ function updateBulletHell(dt: number) {
909
+ // Move all bullets
910
+ for (const b of bullets) moveSprite(b, dt)
911
+
912
+ // Move all enemies
913
+ for (const e of enemies) moveSprite(e, dt)
914
+
915
+ // Hit tests — O(bullets × enemies), fine for dozens of sprites
916
+ for (const b of bullets) {
917
+ for (const e of enemies) {
918
+ if (e.visible && b.visible && spritesOverlap(b, e)) {
919
+ b.visible = false // bullet disappears
920
+ e.visible = false // enemy explodes
921
+ }
922
+ }
923
+ }
924
+
925
+ // Remove off-screen bullets
926
+ bullets.splice(0, bullets.length, ...bullets.filter(b => b.visible && b.y > -CELL))
927
+
928
+ // Render
929
+ for (const e of enemies) if (e.visible) renderSprite(ctx, e)
930
+ for (const b of bullets) if (b.visible) renderSprite(ctx, b)
931
+ }
932
+ ```
933
+
934
+ ---
935
+
663
936
  ## File structure
664
937
 
665
938
  ```
666
939
  zx-kit/
667
- ├── package.json # exports: { ".": "./dist/index.js" }
668
- ├── tsconfig.json # strict, emits to dist/
940
+ ├── package.json # exports: { ".": "./dist/index.js" }
941
+ ├── tsconfig.json # strict, emits to dist/
669
942
  ├── README.md
670
- ├── src/ # TypeScript source
671
- │ ├── index.ts # barrel — re-exports everything
672
- │ ├── palette.ts # SCALE, CELL, C, SpectrumColor
673
- │ ├── font.ts # FONT, getCharRow
674
- │ ├── renderer.ts # setupCanvas, mirrorSprite, drawSprite, drawChar, drawText,
675
- │ │ # drawTextCentered, flashBorder
676
- │ ├── audio.ts # initAudio, resumeAudio, beep, playPattern, Note,
677
- │ │ # getAudioContext, getMasterGain,
678
- │ │ # getMasterVolume, setMasterVolume,
679
- │ │ # increaseVolume, decreaseVolume
680
- │ ├── input.ts # initInput, tickMovement, consumeFlag/Debug/Pause/AnyKey,
681
- │ │ # isHeld, resetInput, Direction
682
- │ ├── ui.ts # drawBox, drawFrame, drawPanelTitle,
683
- │ │ # drawProgressBar, tickUI, renderUI, resetUI,
684
- │ │ # BorderOptions, DrawProgressBarOptions
685
- └── tilemap.ts # createTileMap, Tile, Viewport, TileMap
686
- └── dist/ # compiled output (generated by npm run build)
943
+ ├── src/ # TypeScript source
944
+ │ ├── index.ts # barrel — re-exports everything
945
+ │ ├── palette.ts # SCALE, CELL, C, SpectrumColor
946
+ │ ├── font.ts # FONT, getCharRow
947
+ │ ├── renderer.ts # setupCanvas, mirrorSprite, drawSprite, drawChar, drawText,
948
+ │ │ # drawTextCentered, flashBorder, drawScanlines
949
+ │ ├── audio.ts # initAudio, resumeAudio, beep, playPattern, Note,
950
+ │ │ # getAudioContext, getMasterGain,
951
+ │ │ # getMasterVolume, setMasterVolume,
952
+ │ │ # increaseVolume, decreaseVolume
953
+ │ ├── input.ts # initInput, tickMovement, consumeFlag/Debug/Pause/AnyKey,
954
+ │ │ # isHeld, resetInput, Direction
955
+ │ ├── ui.ts # drawBox, drawFrame, drawPanelTitle,
956
+ │ │ # drawProgressBar, tickUI, renderUI, resetUI,
957
+ │ │ # BorderOptions, DrawProgressBarOptions
958
+ ├── tilemap.ts # createTileMap, Tile, Viewport, TileMap
959
+ │ ├── sprite.ts # Sprite, createSprite, moveSprite, applyGravity, renderSprite
960
+ │ └── collision.ts # Rect, spriteRect, rectsOverlap, spritesOverlap,
961
+ │ # isSolidAt, resolveX, resolveY
962
+ └── dist/ # compiled output (generated by npm run build)
687
963
  ├── index.js / .d.ts
688
964
  └── ...
689
965
  ```
@@ -17,6 +17,26 @@ import { type SpectrumColor } from './palette.js';
17
17
  * const ctx = setupCanvas(canvas, 4, 256, 208) // taller canvas for status rows
18
18
  */
19
19
  export declare function setupCanvas(canvas: HTMLCanvasElement, scale: number, width?: number, height?: number): CanvasRenderingContext2D;
20
+ /**
21
+ * Applies a CRT monitor curvature effect to the canvas element via CSS.
22
+ *
23
+ * Current implementation: rounded corners (`border-radius`) and an inset edge shadow
24
+ * to simulate the dark vignette of a CRT bezel. This is a deliberate abstraction —
25
+ * the CSS internals may be replaced by WebGL barrel distortion in a future version
26
+ * without changing this API signature.
27
+ *
28
+ * Call once after `setupCanvas`. Call again with `intensity = 0` to remove.
29
+ *
30
+ * @param canvas - The `<canvas>` element to apply the effect to
31
+ * @param intensity - Strength 0–1 (default `1`; `0` removes all effect)
32
+ *
33
+ * @example
34
+ * const ctx = setupCanvas(canvas, 4)
35
+ * curveDisplay(canvas) // default full intensity
36
+ * curveDisplay(canvas, 0.5) // subtle effect
37
+ * curveDisplay(canvas, 0) // remove effect
38
+ */
39
+ export declare function curveDisplay(canvas: HTMLCanvasElement, intensity?: number): void;
20
40
  /**
21
41
  * Flips an 8×8 sprite horizontally. Returns a new `Uint8Array`.
22
42
  * Use to derive left-facing sprites from right-facing definitions at module load time.
@@ -1 +1 @@
1
- {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA;AAiB1D;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,iBAAiB,EACzB,KAAK,EAAE,MAAM,EACb,KAAK,SAAM,EACX,MAAM,SAAM,GACX,wBAAwB,CAS1B;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,UAAU,GAAG,UAAU,CAUxD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,wBAAwB,EAC7B,MAAM,EAAE,UAAU,EAClB,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EACpB,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,GACvC,IAAI,CAKN;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,QAAQ,CACtB,GAAG,EAAE,wBAAwB,EAC7B,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EACpB,GAAG,EAAE,aAAa,EAAE,KAAK,CAAC,EAAE,aAAa,GACxC,IAAI,CAON;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,QAAQ,CACtB,GAAG,EAAE,wBAAwB,EAC7B,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EACpB,GAAG,EAAE,aAAa,EAAE,KAAK,CAAC,EAAE,aAAa,GACxC,IAAI,CAIN;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,wBAAwB,EAC7B,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,MAAM,EACT,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,aAAa,EAAE,KAAK,CAAC,EAAE,aAAa,GACxC,IAAI,CAGN;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,wBAAwB,EAC7B,KAAK,SAAO,GACX,IAAI,CASN;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,UAAU,GAAE,aAAuB,GAClC,IAAI,CAWN"}
1
+ {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA;AAiB1D;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,iBAAiB,EACzB,KAAK,EAAE,MAAM,EACb,KAAK,SAAM,EACX,MAAM,SAAM,GACX,wBAAwB,CAS1B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,iBAAiB,EAAE,SAAS,SAAI,GAAG,IAAI,CAS3E;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,UAAU,GAAG,UAAU,CAUxD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,wBAAwB,EAC7B,MAAM,EAAE,UAAU,EAClB,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EACpB,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,GACvC,IAAI,CAKN;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,QAAQ,CACtB,GAAG,EAAE,wBAAwB,EAC7B,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EACpB,GAAG,EAAE,aAAa,EAAE,KAAK,CAAC,EAAE,aAAa,GACxC,IAAI,CAON;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,QAAQ,CACtB,GAAG,EAAE,wBAAwB,EAC7B,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EACpB,GAAG,EAAE,aAAa,EAAE,KAAK,CAAC,EAAE,aAAa,GACxC,IAAI,CAIN;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,wBAAwB,EAC7B,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,MAAM,EACT,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,aAAa,EAAE,KAAK,CAAC,EAAE,aAAa,GACxC,IAAI,CAGN;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,wBAAwB,EAC7B,KAAK,SAAO,GACX,IAAI,CASN;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,UAAU,GAAE,aAAuB,GAClC,IAAI,CAWN"}
package/dist/renderer.js CHANGED
@@ -36,6 +36,35 @@ export function setupCanvas(canvas, scale, width = 256, height = 192) {
36
36
  ctx.scale(scale, scale);
37
37
  return ctx;
38
38
  }
39
+ /**
40
+ * Applies a CRT monitor curvature effect to the canvas element via CSS.
41
+ *
42
+ * Current implementation: rounded corners (`border-radius`) and an inset edge shadow
43
+ * to simulate the dark vignette of a CRT bezel. This is a deliberate abstraction —
44
+ * the CSS internals may be replaced by WebGL barrel distortion in a future version
45
+ * without changing this API signature.
46
+ *
47
+ * Call once after `setupCanvas`. Call again with `intensity = 0` to remove.
48
+ *
49
+ * @param canvas - The `<canvas>` element to apply the effect to
50
+ * @param intensity - Strength 0–1 (default `1`; `0` removes all effect)
51
+ *
52
+ * @example
53
+ * const ctx = setupCanvas(canvas, 4)
54
+ * curveDisplay(canvas) // default full intensity
55
+ * curveDisplay(canvas, 0.5) // subtle effect
56
+ * curveDisplay(canvas, 0) // remove effect
57
+ */
58
+ export function curveDisplay(canvas, intensity = 1) {
59
+ const i = Math.max(0, Math.min(1, intensity));
60
+ if (i === 0) {
61
+ canvas.style.borderRadius = '';
62
+ canvas.style.boxShadow = '';
63
+ return;
64
+ }
65
+ canvas.style.borderRadius = `${Math.round(18 * i)}px`;
66
+ canvas.style.boxShadow = `inset 0 0 ${Math.round(60 * i)}px rgba(0,0,0,${(0.45 * i).toFixed(2)})`;
67
+ }
39
68
  /**
40
69
  * Flips an 8×8 sprite horizontally. Returns a new `Uint8Array`.
41
70
  * Use to derive left-facing sprites from right-facing definitions at module load time.
@@ -1 +1 @@
1
- {"version":3,"file":"renderer.js","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"renderer.js","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zx-kit",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/zrebec/zx-kit.git"