@norarcasey/arkanora 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -0
- package/dist/arkanora.js +231 -0
- package/dist/index.d.ts +88 -0
- package/dist/style.css +1 -0
- package/package.json +86 -0
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Arkanora 🧱
|
|
2
|
+
|
|
3
|
+
An embeddable React + TypeScript breakout game. Slide the paddle with your
|
|
4
|
+
**mouse** (or the **arrow keys** / **A,D**), keep the ball alive, and smash
|
|
5
|
+
every brick. Miss the ball and you lose a life.
|
|
6
|
+
|
|
7
|
+
Built with **Vite** and tested with **Vitest** + **React Testing Library**.
|
|
8
|
+
|
|
9
|
+
## Quick start
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install
|
|
13
|
+
npm run dev # demo site at http://localhost:5173
|
|
14
|
+
npm test # run the test suite
|
|
15
|
+
npm run build # build the demo site for deployment
|
|
16
|
+
npm run build:lib # build the embeddable component library
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Embedding the component
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { Arkanora } from '@norarcasey/arkanora'
|
|
23
|
+
import '@norarcasey/arkanora/style.css'
|
|
24
|
+
|
|
25
|
+
export function App() {
|
|
26
|
+
return <Arkanora />
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`react` / `react-dom` are peer dependencies you already have.
|
|
31
|
+
|
|
32
|
+
### Props
|
|
33
|
+
|
|
34
|
+
| Prop | Type | Default | Description |
|
|
35
|
+
| ---------------- | ---------------- | ------------ | ---------------------------------------------------- |
|
|
36
|
+
| `rows` | `number` | `5` | Number of brick rows. |
|
|
37
|
+
| `cols` | `number` | `9` | Number of brick columns. |
|
|
38
|
+
| `lives` | `number` | `3` | Lives before the game is over. |
|
|
39
|
+
| `speed` | `number` | `16` | Milliseconds between physics ticks (lower = faster). |
|
|
40
|
+
| `enableKeyboard` | `boolean` | `true` | Steer the paddle with the arrow keys / A,D. |
|
|
41
|
+
| `title` | `string \| null` | `"Arkanora"` | Heading above the board; pass `null` to hide it. |
|
|
42
|
+
| `className` | `string` | — | Extra class on the root element. |
|
|
43
|
+
|
|
44
|
+
### Headless engine
|
|
45
|
+
|
|
46
|
+
The game logic lives in a framework-free hook if you want to build your own UI:
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { useArkanora } from '@norarcasey/arkanora'
|
|
50
|
+
|
|
51
|
+
const game = useArkanora({ rows: 6, cols: 10 })
|
|
52
|
+
// game.ball, game.bricks, game.paddleX, game.score, game.lives, game.status
|
|
53
|
+
// game.start(), game.reset(), game.movePaddle(x), game.nudgePaddle(dx)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The playfield is a fixed 100×100 unit square; positions and sizes are in those
|
|
57
|
+
units so the board scales to any pixel size.
|
|
58
|
+
|
|
59
|
+
## Roadmap
|
|
60
|
+
|
|
61
|
+
This is the MVP: paddle, ball, bricks, lives, win/lose. Planned flavor —
|
|
62
|
+
multi-hit bricks, power-ups (multiball, wide paddle), and per-row scoring.
|
|
63
|
+
|
|
64
|
+
Rendering is currently DOM-based, which is fine at this scale; a `<canvas>`
|
|
65
|
+
renderer is the natural next step if we add particle effects or smooth trails
|
|
66
|
+
(the engine is already decoupled from the view, so it'd be a Board-only swap).
|
|
67
|
+
|
|
68
|
+
## License
|
|
69
|
+
|
|
70
|
+
MIT © Nora Casey
|
package/dist/arkanora.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { jsxs as k, jsx as y } from "react/jsx-runtime";
|
|
2
|
+
import { useReducer as Y, useEffect as A, useCallback as b } from "react";
|
|
3
|
+
const m = 100, S = 100, x = 18, R = 2.6, _ = 92, c = 1.6, P = 1.25, E = 0.35, H = 1.05, M = 6, I = 10, X = 4, $ = 1.4, D = 1.4, B = 3, g = { rows: 5, cols: 9, lives: 3, speed: 16 };
|
|
4
|
+
function L(a, n, e) {
|
|
5
|
+
return Math.min(e, Math.max(n, a));
|
|
6
|
+
}
|
|
7
|
+
function O(a, n) {
|
|
8
|
+
const e = (m - 2 * B - D * (n - 1)) / n, i = [];
|
|
9
|
+
for (let l = 0; l < a; l++)
|
|
10
|
+
for (let s = 0; s < n; s++)
|
|
11
|
+
i.push({
|
|
12
|
+
x: B + s * (e + D),
|
|
13
|
+
y: I + l * (X + $),
|
|
14
|
+
w: e,
|
|
15
|
+
h: X,
|
|
16
|
+
alive: !0
|
|
17
|
+
});
|
|
18
|
+
return i;
|
|
19
|
+
}
|
|
20
|
+
function C(a) {
|
|
21
|
+
return {
|
|
22
|
+
x: a,
|
|
23
|
+
y: _ - c - 0.2,
|
|
24
|
+
vx: P * Math.sin(E),
|
|
25
|
+
vy: -P * Math.cos(E)
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function w(a, n, e) {
|
|
29
|
+
return {
|
|
30
|
+
rows: a,
|
|
31
|
+
cols: n,
|
|
32
|
+
maxLives: e,
|
|
33
|
+
ball: C(m / 2),
|
|
34
|
+
paddleX: m / 2,
|
|
35
|
+
bricks: O(a, n),
|
|
36
|
+
status: "idle",
|
|
37
|
+
score: 0,
|
|
38
|
+
lives: e
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function G(a, n) {
|
|
42
|
+
switch (n.type) {
|
|
43
|
+
case "configure":
|
|
44
|
+
return w(n.rows, n.cols, n.lives);
|
|
45
|
+
case "reset":
|
|
46
|
+
return w(a.rows, a.cols, a.maxLives);
|
|
47
|
+
case "start":
|
|
48
|
+
return { ...w(a.rows, a.cols, a.maxLives), status: "running" };
|
|
49
|
+
case "movePaddle":
|
|
50
|
+
return { ...a, paddleX: L(n.x, x / 2, m - x / 2) };
|
|
51
|
+
case "nudgePaddle":
|
|
52
|
+
return { ...a, paddleX: L(a.paddleX + n.dx, x / 2, m - x / 2) };
|
|
53
|
+
case "tick": {
|
|
54
|
+
if (a.status !== "running") return a;
|
|
55
|
+
const e = {
|
|
56
|
+
x: a.ball.x + a.ball.vx,
|
|
57
|
+
y: a.ball.y + a.ball.vy,
|
|
58
|
+
vx: a.ball.vx,
|
|
59
|
+
vy: a.ball.vy
|
|
60
|
+
};
|
|
61
|
+
e.x - c < 0 ? (e.x = c, e.vx = Math.abs(e.vx)) : e.x + c > m && (e.x = m - c, e.vx = -Math.abs(e.vx)), e.y - c < 0 && (e.y = c, e.vy = Math.abs(e.vy));
|
|
62
|
+
const i = x / 2;
|
|
63
|
+
if (e.vy > 0 && e.y + c >= _ && e.y - c <= _ + R && e.x >= a.paddleX - i && e.x <= a.paddleX + i) {
|
|
64
|
+
const r = L((e.x - a.paddleX) / i, -1, 1) * H;
|
|
65
|
+
e.vx = P * Math.sin(r), e.vy = -P * Math.cos(r), e.y = _ - c;
|
|
66
|
+
}
|
|
67
|
+
let l = a.bricks, s = a.score;
|
|
68
|
+
for (let t = 0; t < l.length; t++) {
|
|
69
|
+
const r = l[t];
|
|
70
|
+
if (!r.alive) continue;
|
|
71
|
+
const u = e.x + c > r.x && e.x - c < r.x + r.w, o = e.y + c > r.y && e.y - c < r.y + r.h;
|
|
72
|
+
if (!u || !o) continue;
|
|
73
|
+
const p = Math.min(e.x + c, r.x + r.w) - Math.max(e.x - c, r.x), h = Math.min(e.y + c, r.y + r.h) - Math.max(e.y - c, r.y);
|
|
74
|
+
p < h ? e.vx = -e.vx : e.vy = -e.vy, l = l.map((f, d) => d === t ? { ...f, alive: !1 } : f), s += 1;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
if (e.y - c > S) {
|
|
78
|
+
const t = a.lives - 1;
|
|
79
|
+
return t <= 0 ? { ...a, ball: e, bricks: l, score: s, lives: 0, status: "over" } : { ...a, bricks: l, score: s, lives: t, ball: C(a.paddleX) };
|
|
80
|
+
}
|
|
81
|
+
return l.every((t) => !t.alive) ? { ...a, ball: e, bricks: l, score: s, status: "won" } : { ...a, ball: e, bricks: l, score: s };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function T(a = {}) {
|
|
86
|
+
const n = a.rows ?? g.rows, e = a.cols ?? g.cols, i = a.lives ?? g.lives, l = a.speed ?? g.speed, [s, t] = Y(G, void 0, () => w(n, e, i));
|
|
87
|
+
A(() => {
|
|
88
|
+
t({ type: "configure", rows: n, cols: e, lives: i });
|
|
89
|
+
}, [n, e, i]);
|
|
90
|
+
const r = b(() => t({ type: "start" }), []), u = b(() => t({ type: "reset" }), []), o = b((h) => t({ type: "movePaddle", x: h }), []), p = b((h) => t({ type: "nudgePaddle", dx: h }), []);
|
|
91
|
+
return A(() => {
|
|
92
|
+
if (s.status !== "running") return;
|
|
93
|
+
const h = setInterval(() => t({ type: "tick" }), l);
|
|
94
|
+
return () => clearInterval(h);
|
|
95
|
+
}, [s.status, l]), {
|
|
96
|
+
width: m,
|
|
97
|
+
height: S,
|
|
98
|
+
ball: s.ball,
|
|
99
|
+
ballRadius: c,
|
|
100
|
+
paddleX: s.paddleX,
|
|
101
|
+
paddleWidth: x,
|
|
102
|
+
paddleHeight: R,
|
|
103
|
+
paddleY: _,
|
|
104
|
+
bricks: s.bricks,
|
|
105
|
+
status: s.status,
|
|
106
|
+
score: s.score,
|
|
107
|
+
lives: s.lives,
|
|
108
|
+
start: r,
|
|
109
|
+
reset: u,
|
|
110
|
+
movePaddle: o,
|
|
111
|
+
nudgePaddle: p
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function j({
|
|
115
|
+
width: a,
|
|
116
|
+
height: n,
|
|
117
|
+
ball: e,
|
|
118
|
+
ballRadius: i,
|
|
119
|
+
paddleX: l,
|
|
120
|
+
paddleWidth: s,
|
|
121
|
+
paddleHeight: t,
|
|
122
|
+
paddleY: r,
|
|
123
|
+
bricks: u
|
|
124
|
+
}) {
|
|
125
|
+
const o = (d, v) => `${d / v * 100}%`, p = { aspectRatio: `${a} / ${n}` }, h = {
|
|
126
|
+
left: o(e.x - i, a),
|
|
127
|
+
top: o(e.y - i, n),
|
|
128
|
+
width: o(i * 2, a),
|
|
129
|
+
height: o(i * 2, n)
|
|
130
|
+
}, f = {
|
|
131
|
+
left: o(l - s / 2, a),
|
|
132
|
+
top: o(r, n),
|
|
133
|
+
width: o(s, a),
|
|
134
|
+
height: o(t, n)
|
|
135
|
+
};
|
|
136
|
+
return /* @__PURE__ */ k("div", { className: "arkanora__board", style: p, "data-testid": "board", children: [
|
|
137
|
+
u.map(
|
|
138
|
+
(d, v) => d.alive ? /* @__PURE__ */ y(
|
|
139
|
+
"div",
|
|
140
|
+
{
|
|
141
|
+
className: "arkanora__brick",
|
|
142
|
+
style: {
|
|
143
|
+
left: o(d.x, a),
|
|
144
|
+
top: o(d.y, n),
|
|
145
|
+
width: o(d.w, a),
|
|
146
|
+
height: o(d.h, n),
|
|
147
|
+
// Color rows along a hue ramp for a retro rainbow wall.
|
|
148
|
+
backgroundColor: `hsl(${d.y * 7 % 360} 80% 58%)`
|
|
149
|
+
},
|
|
150
|
+
"aria-hidden": !0
|
|
151
|
+
},
|
|
152
|
+
v
|
|
153
|
+
) : null
|
|
154
|
+
),
|
|
155
|
+
/* @__PURE__ */ y("div", { className: "arkanora__paddle", style: f, "aria-hidden": !0 }),
|
|
156
|
+
/* @__PURE__ */ y("div", { className: "arkanora__ball", style: h, "aria-hidden": !0 })
|
|
157
|
+
] });
|
|
158
|
+
}
|
|
159
|
+
function F({
|
|
160
|
+
rows: a,
|
|
161
|
+
cols: n,
|
|
162
|
+
lives: e,
|
|
163
|
+
speed: i,
|
|
164
|
+
enableKeyboard: l = !0,
|
|
165
|
+
title: s = "Arkanora",
|
|
166
|
+
className: t
|
|
167
|
+
}) {
|
|
168
|
+
const r = T({ rows: a, cols: n, lives: e, speed: i }), { status: u, start: o, nudgePaddle: p } = r;
|
|
169
|
+
A(() => {
|
|
170
|
+
if (!l) return;
|
|
171
|
+
const d = (v) => {
|
|
172
|
+
const N = v.key === "ArrowLeft" || v.key === "a", W = v.key === "ArrowRight" || v.key === "d";
|
|
173
|
+
!N && !W || (v.preventDefault(), u !== "running" && o(), p(N ? -M : M));
|
|
174
|
+
};
|
|
175
|
+
return window.addEventListener("keydown", d), () => window.removeEventListener("keydown", d);
|
|
176
|
+
}, [l, u, o, p]);
|
|
177
|
+
const h = (d) => {
|
|
178
|
+
const v = d.currentTarget.getBoundingClientRect();
|
|
179
|
+
v.width !== 0 && r.movePaddle((d.clientX - v.left) / v.width * r.width);
|
|
180
|
+
}, f = u === "over" || u === "won";
|
|
181
|
+
return /* @__PURE__ */ k(
|
|
182
|
+
"section",
|
|
183
|
+
{
|
|
184
|
+
className: `arkanora${t ? ` ${t}` : ""}`,
|
|
185
|
+
"aria-label": s ?? "Breakout game",
|
|
186
|
+
children: [
|
|
187
|
+
/* @__PURE__ */ k("header", { className: "arkanora__header", children: [
|
|
188
|
+
s !== null && /* @__PURE__ */ y("h2", { className: "arkanora__title", children: s }),
|
|
189
|
+
/* @__PURE__ */ k("span", { className: "arkanora__stat", "aria-live": "polite", children: [
|
|
190
|
+
"Score: ",
|
|
191
|
+
r.score
|
|
192
|
+
] }),
|
|
193
|
+
/* @__PURE__ */ k("span", { className: "arkanora__stat", "aria-live": "polite", children: [
|
|
194
|
+
"Lives: ",
|
|
195
|
+
r.lives
|
|
196
|
+
] })
|
|
197
|
+
] }),
|
|
198
|
+
/* @__PURE__ */ k("div", { className: "arkanora__stage", onPointerMove: h, children: [
|
|
199
|
+
/* @__PURE__ */ y(
|
|
200
|
+
j,
|
|
201
|
+
{
|
|
202
|
+
width: r.width,
|
|
203
|
+
height: r.height,
|
|
204
|
+
ball: r.ball,
|
|
205
|
+
ballRadius: r.ballRadius,
|
|
206
|
+
paddleX: r.paddleX,
|
|
207
|
+
paddleWidth: r.paddleWidth,
|
|
208
|
+
paddleHeight: r.paddleHeight,
|
|
209
|
+
paddleY: r.paddleY,
|
|
210
|
+
bricks: r.bricks
|
|
211
|
+
}
|
|
212
|
+
),
|
|
213
|
+
u !== "running" && /* @__PURE__ */ k("div", { className: "arkanora__overlay", role: "status", children: [
|
|
214
|
+
u === "idle" && /* @__PURE__ */ y("p", { className: "arkanora__message", children: "Break some bricks!" }),
|
|
215
|
+
u === "over" && /* @__PURE__ */ k("p", { className: "arkanora__message", children: [
|
|
216
|
+
"Game over — score ",
|
|
217
|
+
r.score
|
|
218
|
+
] }),
|
|
219
|
+
u === "won" && /* @__PURE__ */ y("p", { className: "arkanora__message", children: "You cleared the board! 🏆" }),
|
|
220
|
+
/* @__PURE__ */ y("button", { type: "button", className: "arkanora__button", onClick: o, children: f ? "Play again" : "Start" }),
|
|
221
|
+
/* @__PURE__ */ y("p", { className: "arkanora__hint", children: "Move with the mouse, or arrow keys / A,D" })
|
|
222
|
+
] })
|
|
223
|
+
] })
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
export {
|
|
229
|
+
F as Arkanora,
|
|
230
|
+
T as useArkanora
|
|
231
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { JSX as JSX_2 } from 'react';
|
|
2
|
+
|
|
3
|
+
export declare function Arkanora({ rows, cols, lives, speed, enableKeyboard, title, className, }: ArkanoraProps): JSX_2.Element;
|
|
4
|
+
|
|
5
|
+
export declare interface ArkanoraApi {
|
|
6
|
+
/** Playfield width in units (always 100). */
|
|
7
|
+
width: number;
|
|
8
|
+
/** Playfield height in units (always 100). */
|
|
9
|
+
height: number;
|
|
10
|
+
ball: Ball;
|
|
11
|
+
ballRadius: number;
|
|
12
|
+
/** Center x of the paddle, in units. */
|
|
13
|
+
paddleX: number;
|
|
14
|
+
paddleWidth: number;
|
|
15
|
+
paddleHeight: number;
|
|
16
|
+
/** The paddle's top edge, in units. */
|
|
17
|
+
paddleY: number;
|
|
18
|
+
bricks: Brick[];
|
|
19
|
+
status: GameStatus;
|
|
20
|
+
score: number;
|
|
21
|
+
lives: number;
|
|
22
|
+
/** Reset the board and serve the ball. */
|
|
23
|
+
start: () => void;
|
|
24
|
+
/** Reset the board to `idle` without serving. */
|
|
25
|
+
reset: () => void;
|
|
26
|
+
/** Move the paddle so its center sits at unit-x `x` (clamped to the walls). */
|
|
27
|
+
movePaddle: (x: number) => void;
|
|
28
|
+
/** Nudge the paddle by `dx` units (clamped to the walls). */
|
|
29
|
+
nudgePaddle: (dx: number) => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export declare interface ArkanoraProps {
|
|
33
|
+
/** Number of brick rows. Default `5`. */
|
|
34
|
+
rows?: number;
|
|
35
|
+
/** Number of brick columns. Default `9`. */
|
|
36
|
+
cols?: number;
|
|
37
|
+
/** Lives before the game is over. Default `3`. */
|
|
38
|
+
lives?: number;
|
|
39
|
+
/** Milliseconds between physics ticks; lower is smoother/faster. Default `16`. */
|
|
40
|
+
speed?: number;
|
|
41
|
+
/** Steer the paddle with the arrow keys / A,D. Default `true`. */
|
|
42
|
+
enableKeyboard?: boolean;
|
|
43
|
+
/** Heading shown above the board. Pass `null` to hide it. */
|
|
44
|
+
title?: string | null;
|
|
45
|
+
/** Extra class on the root element. */
|
|
46
|
+
className?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The ball: a position and a velocity (units per tick). */
|
|
50
|
+
export declare interface Ball {
|
|
51
|
+
x: number;
|
|
52
|
+
y: number;
|
|
53
|
+
vx: number;
|
|
54
|
+
vy: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** A single brick. Axis-aligned, top-left origin. */
|
|
58
|
+
export declare interface Brick {
|
|
59
|
+
x: number;
|
|
60
|
+
y: number;
|
|
61
|
+
w: number;
|
|
62
|
+
h: number;
|
|
63
|
+
alive: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Lifecycle of a single game:
|
|
68
|
+
* - `idle` — board is set up, ball resting on the paddle, waiting to start.
|
|
69
|
+
* - `running` — the ball is in play.
|
|
70
|
+
* - `over` — the ball was missed with no lives left.
|
|
71
|
+
* - `won` — every brick has been cleared.
|
|
72
|
+
*/
|
|
73
|
+
export declare type GameStatus = 'idle' | 'running' | 'over' | 'won';
|
|
74
|
+
|
|
75
|
+
export declare function useArkanora(options?: UseArkanoraOptions): ArkanoraApi;
|
|
76
|
+
|
|
77
|
+
export declare interface UseArkanoraOptions {
|
|
78
|
+
/** Number of brick rows. Default `5`. */
|
|
79
|
+
rows?: number;
|
|
80
|
+
/** Number of brick columns. Default `9`. */
|
|
81
|
+
cols?: number;
|
|
82
|
+
/** Lives before the game is over. Default `3`. */
|
|
83
|
+
lives?: number;
|
|
84
|
+
/** Milliseconds between physics ticks; lower is smoother/faster. Default `16`. */
|
|
85
|
+
speed?: number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { }
|
package/dist/style.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.arkanora{--arkanora-bg: #0b1020;--arkanora-board: #0e1530;--arkanora-wall: #1b2550;--arkanora-ball: #ffffff;--arkanora-paddle: #38e1ff;--arkanora-accent: #38e1ff;--arkanora-text: #e7ecff;box-sizing:border-box;width:100%;max-width:560px;margin:0 auto;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Helvetica Neue,sans-serif;color:var(--arkanora-text)}.arkanora *,.arkanora *:before,.arkanora *:after{box-sizing:border-box}.arkanora__header{display:flex;align-items:baseline;gap:1rem;margin-bottom:.75rem}.arkanora__title{margin:0 auto 0 0;font-size:1.4rem;font-weight:700}.arkanora__stat{font-variant-numeric:tabular-nums;font-weight:600;color:var(--arkanora-accent)}.arkanora__stage{position:relative;touch-action:none}.arkanora__board{position:relative;width:100%;background:radial-gradient(120% 120% at 50% 0%,var(--arkanora-board),var(--arkanora-bg));border:2px solid var(--arkanora-wall);border-radius:8px;overflow:hidden;cursor:none}.arkanora__brick{position:absolute;border-radius:2px;box-shadow:inset 0 -2px #00000040}.arkanora__paddle{position:absolute;background:var(--arkanora-paddle);border-radius:3px;box-shadow:0 0 12px var(--arkanora-paddle)}.arkanora__ball{position:absolute;background:var(--arkanora-ball);border-radius:50%;box-shadow:0 0 10px var(--arkanora-ball)}.arkanora__overlay{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.75rem;background:#060a18cc;border-radius:8px;text-align:center}.arkanora__message{margin:0;font-size:1.25rem;font-weight:700}.arkanora__hint{margin:0;font-size:.85rem;color:var(--arkanora-accent)}.arkanora__button{-webkit-appearance:none;-moz-appearance:none;appearance:none;font:inherit;font-weight:600;cursor:pointer;padding:.55rem 1.4rem;color:#06122a;background:var(--arkanora-accent);border:none;border-radius:999px;transition:filter .12s ease}.arkanora__button:hover{filter:brightness(1.1)}.arkanora__button:active{filter:brightness(.95)}
|
package/package.json
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@norarcasey/arkanora",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "An embeddable React breakout game — bounce the ball with your paddle and smash every brick.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Nora Casey",
|
|
8
|
+
"homepage": "https://github.com/norarcasey/arkanora#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/norarcasey/arkanora.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/norarcasey/arkanora/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"breakout",
|
|
18
|
+
"arkanoid",
|
|
19
|
+
"game",
|
|
20
|
+
"retro",
|
|
21
|
+
"arcade",
|
|
22
|
+
"react",
|
|
23
|
+
"component",
|
|
24
|
+
"typescript"
|
|
25
|
+
],
|
|
26
|
+
"main": "./dist/arkanora.js",
|
|
27
|
+
"module": "./dist/arkanora.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"import": "./dist/arkanora.js"
|
|
33
|
+
},
|
|
34
|
+
"./style.css": "./dist/style.css"
|
|
35
|
+
},
|
|
36
|
+
"sideEffects": [
|
|
37
|
+
"**/*.css"
|
|
38
|
+
],
|
|
39
|
+
"files": [
|
|
40
|
+
"dist"
|
|
41
|
+
],
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"dev": "vite",
|
|
47
|
+
"build": "tsc && vite build",
|
|
48
|
+
"build:lib": "tsc -p tsconfig.lib.json && vite build --mode lib",
|
|
49
|
+
"preview": "vite preview",
|
|
50
|
+
"test": "vitest run",
|
|
51
|
+
"test:watch": "vitest",
|
|
52
|
+
"typecheck": "tsc --noEmit",
|
|
53
|
+
"lint": "eslint .",
|
|
54
|
+
"format": "prettier --write .",
|
|
55
|
+
"format:check": "prettier --check .",
|
|
56
|
+
"prepublishOnly": "npm run lint && npm run typecheck && npm run test && npm run build:lib"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
60
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@eslint/js": "^9.39.4",
|
|
64
|
+
"@testing-library/jest-dom": "^6.4.8",
|
|
65
|
+
"@testing-library/react": "^16.0.1",
|
|
66
|
+
"@testing-library/user-event": "^14.5.2",
|
|
67
|
+
"@types/node": "^20.19.41",
|
|
68
|
+
"@types/react": "^18.3.5",
|
|
69
|
+
"@types/react-dom": "^18.3.0",
|
|
70
|
+
"@vitejs/plugin-react": "^4.3.1",
|
|
71
|
+
"eslint": "^9.39.4",
|
|
72
|
+
"eslint-config-prettier": "^9.1.2",
|
|
73
|
+
"eslint-plugin-react-hooks": "^5.2.0",
|
|
74
|
+
"eslint-plugin-react-refresh": "^0.4.26",
|
|
75
|
+
"globals": "^15.15.0",
|
|
76
|
+
"jsdom": "^25.0.0",
|
|
77
|
+
"prettier": "^3.8.3",
|
|
78
|
+
"react": "^18.3.1",
|
|
79
|
+
"react-dom": "^18.3.1",
|
|
80
|
+
"typescript": "^5.5.4",
|
|
81
|
+
"typescript-eslint": "^8.60.1",
|
|
82
|
+
"vite": "^5.4.2",
|
|
83
|
+
"vite-plugin-dts": "^4.0.3",
|
|
84
|
+
"vitest": "^2.0.5"
|
|
85
|
+
}
|
|
86
|
+
}
|