diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 359de709..18622f76 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -114,6 +114,37 @@ jobs: run: node hexchess build:engine - name: Test run: node hexchess test:engine + + ui: + name: ${{ matrix.browser }} + runs-on: ubuntu-latest + strategy: + matrix: + browser: [chromium, firefox, webkit] + steps: + - uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + version: 9 + run_install: false + - name: Install dependencies + run: | + pnpm install --no-frozen-lockfile + cd ui + pnpm install --no-frozen-lockfile + - name: Install Playwright browsers + run: cd ui && pnpm exec playwright install --with-deps ${{ matrix.browser }} + - name: Build + run: node hexchess build:ui + - name: Test + env: + VITEST_BROWSER: ${{ matrix.browser }} + run: node hexchess test:ui php: name: PHP diff --git a/.gitignore b/.gitignore index c070757c..d5aa5cb5 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ php/junit.xml rust/README.md sandbox.mjs target +ui/src/test/__screenshots__ vendor \ No newline at end of file diff --git a/docs/components/hexboard/Hexboard.vue b/docs/components/hexboard/Hexboard.vue deleted file mode 100644 index c013a6d7..00000000 --- a/docs/components/hexboard/Hexboard.vue +++ /dev/null @@ -1,135 +0,0 @@ - - - \ No newline at end of file diff --git a/docs/components/hexboard/Piece.vue b/docs/components/hexboard/Piece.vue deleted file mode 100644 index 16ea80c2..00000000 --- a/docs/components/hexboard/Piece.vue +++ /dev/null @@ -1,677 +0,0 @@ - - - - \ No newline at end of file diff --git a/docs/components/hexboard/constants.ts b/docs/components/hexboard/constants.ts deleted file mode 100644 index 06283d6c..00000000 --- a/docs/components/hexboard/constants.ts +++ /dev/null @@ -1,372 +0,0 @@ -import { positions } from '../../../js/src/constants' -import { bilerp, flip, hexagon, pivot, reflect, sum } from './geometry' -import type { Vec } from './types' - -/** svg viewbox, increasing this zooms out away from the board */ -export const box = 23.6 - -/** position colors */ -export const colors: Vec<3, string> = [ - 'oklch(0.9015 0.0729 70.7)', - 'oklch(0.8366 0.1165 66.29)', - 'oklch(0.6806 0.1423 75.83)', -] - -/** label offset */ -export const labelOffset = 1.7 - -/** piece size */ -export const pieceSize = 1.7 - -/** side length of regular hexagon circumscribed around a unit circle */ -export const sideLength = 2 / Math.sqrt(3) - -// distance between the origin of neighboring hexagons -// one = direct neighbors, two = neighbor of neighbor, etc... -export const one = sideLength * (Math.sqrt(3) / 2) * 2 // <- inscribed radius * 2 -export const two = one * 2 -export const three = one * 3 -export const four = one * 4 -export const five = one * 5 - -// calculate the center for each position, using f6 as the origin -const f6: Vec<2> = [0, 0] - -// the 6th rank is calculated as angled distances from F6 -const a6 = pivot(f6, 150, five) -const b6 = pivot(f6, 150, four) -const c6 = pivot(f6, 150, three) -const d6 = pivot(f6, 150, two) -const e6 = pivot(f6, 150, one) -const g6 = pivot(f6, 30, one) -const h6 = pivot(f6, 30, two) -const i6 = pivot(f6, 30, three) -const k6 = pivot(f6, 30, four) -const l6 = pivot(f6, 30, five) - -// other ranks are calculated by vertically translating the 6th rank -const a1 = sum(a6, [0, -five]) -const b1 = sum(b6, [0, -five]) -const c1 = sum(c6, [0, -five]) -const d1 = sum(d6, [0, -five]) -const e1 = sum(e6, [0, -five]) -const f1 = sum(f6, [0, -five]) -const g1 = sum(g6, [0, -five]) -const h1 = sum(h6, [0, -five]) -const i1 = sum(i6, [0, -five]) -const k1 = sum(k6, [0, -five]) -const l1 = sum(l6, [0, -five]) - -const a2 = sum(a6, [0, -four]) -const b2 = sum(b6, [0, -four]) -const c2 = sum(c6, [0, -four]) -const d2 = sum(d6, [0, -four]) -const e2 = sum(e6, [0, -four]) -const f2 = sum(f6, [0, -four]) -const g2 = sum(g6, [0, -four]) -const h2 = sum(h6, [0, -four]) -const i2 = sum(i6, [0, -four]) -const k2 = sum(k6, [0, -four]) -const l2 = sum(l6, [0, -four]) - -const a3 = sum(a6, [0, -three]) -const b3 = sum(b6, [0, -three]) -const c3 = sum(c6, [0, -three]) -const d3 = sum(d6, [0, -three]) -const e3 = sum(e6, [0, -three]) -const f3 = sum(f6, [0, -three]) -const g3 = sum(g6, [0, -three]) -const h3 = sum(h6, [0, -three]) -const i3 = sum(i6, [0, -three]) -const k3 = sum(k6, [0, -three]) -const l3 = sum(l6, [0, -three]) - -const a4 = sum(a6, [0, -two]) -const b4 = sum(b6, [0, -two]) -const c4 = sum(c6, [0, -two]) -const d4 = sum(d6, [0, -two]) -const e4 = sum(e6, [0, -two]) -const f4 = sum(f6, [0, -two]) -const g4 = sum(g6, [0, -two]) -const h4 = sum(h6, [0, -two]) -const i4 = sum(i6, [0, -two]) -const k4 = sum(k6, [0, -two]) -const l4 = sum(l6, [0, -two]) - -const a5 = sum(a6, [0, -one]) -const b5 = sum(b6, [0, -one]) -const c5 = sum(c6, [0, -one]) -const d5 = sum(d6, [0, -one]) -const e5 = sum(e6, [0, -one]) -const f5 = sum(f6, [0, -one]) -const g5 = sum(g6, [0, -one]) -const h5 = sum(h6, [0, -one]) -const i5 = sum(i6, [0, -one]) -const k5 = sum(k6, [0, -one]) -const l5 = sum(l6, [0, -one]) - -// 7th rank and higher start to have fewer files -const b7 = sum(b6, [0, one]) -const c7 = sum(c6, [0, one]) -const d7 = sum(d6, [0, one]) -const e7 = sum(e6, [0, one]) -const f7 = sum(f6, [0, one]) -const g7 = sum(g6, [0, one]) -const h7 = sum(h6, [0, one]) -const i7 = sum(i6, [0, one]) -const k7 = sum(k6, [0, one]) - -const c8 = sum(c6, [0, two]) -const d8 = sum(d6, [0, two]) -const e8 = sum(e6, [0, two]) -const f8 = sum(f6, [0, two]) -const g8 = sum(g6, [0, two]) -const h8 = sum(h6, [0, two]) -const i8 = sum(i6, [0, two]) - -const d9 = sum(d6, [0, three]) -const e9 = sum(e6, [0, three]) -const f9 = sum(f6, [0, three]) -const g9 = sum(g6, [0, three]) -const h9 = sum(h6, [0, three]) - -const e10 = sum(e6, [0, four]) -const f10 = sum(f6, [0, four]) -const g10 = sum(g6, [0, four]) - -const f11 = sum(f6, [0, five]) - -/** board position data, sorted by fen order */ -export const board: { - color: number - origin: [Vec<2>, Vec<2>] - path: [Vec<6, Vec<2>>, Vec<6, Vec<2>>] -}[] = [ - { color: 2, origin: [f11, reflect(f11)], path: [hexagon(f11), flip(hexagon(f11))] }, - { color: 1, origin: [e10, reflect(e10)], path: [hexagon(e10), flip(hexagon(e10))] }, - { color: 0, origin: [f10, reflect(f10)], path: [hexagon(f10), flip(hexagon(f10))] }, - { color: 1, origin: [g10, reflect(g10)], path: [hexagon(g10), flip(hexagon(g10))] }, - { color: 0, origin: [d9, reflect(d9)], path: [hexagon(d9), flip(hexagon(d9))] }, - { color: 2, origin: [e9, reflect(e9)], path: [hexagon(e9), flip(hexagon(e9))] }, - { color: 1, origin: [f9, reflect(f9)], path: [hexagon(f9), flip(hexagon(f9))] }, - { color: 2, origin: [g9, reflect(g9)], path: [hexagon(g9), flip(hexagon(g9))] }, - { color: 0, origin: [h9, reflect(h9)], path: [hexagon(h9), flip(hexagon(h9))] }, - { color: 2, origin: [c8, reflect(c8)], path: [hexagon(c8), flip(hexagon(c8))] }, - { color: 1, origin: [d8, reflect(d8)], path: [hexagon(d8), flip(hexagon(d8))] }, - { color: 0, origin: [e8, reflect(e8)], path: [hexagon(e8), flip(hexagon(e8))] }, - { color: 2, origin: [f8, reflect(f8)], path: [hexagon(f8), flip(hexagon(f8))] }, - { color: 0, origin: [g8, reflect(g8)], path: [hexagon(g8), flip(hexagon(g8))] }, - { color: 1, origin: [h8, reflect(h8)], path: [hexagon(h8), flip(hexagon(h8))] }, - { color: 2, origin: [i8, reflect(i8)], path: [hexagon(i8), flip(hexagon(i8))] }, - { color: 1, origin: [b7, reflect(b7)], path: [hexagon(b7), flip(hexagon(b7))] }, - { color: 0, origin: [c7, reflect(c7)], path: [hexagon(c7), flip(hexagon(c7))] }, - { color: 2, origin: [d7, reflect(d7)], path: [hexagon(d7), flip(hexagon(d7))] }, - { color: 1, origin: [e7, reflect(e7)], path: [hexagon(e7), flip(hexagon(e7))] }, - { color: 0, origin: [f7, reflect(f7)], path: [hexagon(f7), flip(hexagon(f7))] }, - { color: 1, origin: [g7, reflect(g7)], path: [hexagon(g7), flip(hexagon(g7))] }, - { color: 2, origin: [h7, reflect(h7)], path: [hexagon(h7), flip(hexagon(h7))] }, - { color: 0, origin: [i7, reflect(i7)], path: [hexagon(i7), flip(hexagon(i7))] }, - { color: 1, origin: [k7, reflect(k7)], path: [hexagon(k7), flip(hexagon(k7))] }, - { color: 0, origin: [a6, reflect(a6)], path: [hexagon(a6), flip(hexagon(a6))] }, - { color: 2, origin: [b6, reflect(b6)], path: [hexagon(b6), flip(hexagon(b6))] }, - { color: 1, origin: [c6, reflect(c6)], path: [hexagon(c6), flip(hexagon(c6))] }, - { color: 0, origin: [d6, reflect(d6)], path: [hexagon(d6), flip(hexagon(d6))] }, - { color: 2, origin: [e6, reflect(e6)], path: [hexagon(e6), flip(hexagon(e6))] }, - { color: 1, origin: [f6, reflect(f6)], path: [hexagon(f6), flip(hexagon(f6))] }, - { color: 2, origin: [g6, reflect(g6)], path: [hexagon(g6), flip(hexagon(g6))] }, - { color: 0, origin: [h6, reflect(h6)], path: [hexagon(h6), flip(hexagon(h6))] }, - { color: 1, origin: [i6, reflect(i6)], path: [hexagon(i6), flip(hexagon(i6))] }, - { color: 2, origin: [k6, reflect(k6)], path: [hexagon(k6), flip(hexagon(k6))] }, - { color: 0, origin: [l6, reflect(l6)], path: [hexagon(l6), flip(hexagon(l6))] }, - { color: 1, origin: [a5, reflect(a5)], path: [hexagon(a5), flip(hexagon(a5))] }, - { color: 0, origin: [b5, reflect(b5)], path: [hexagon(b5), flip(hexagon(b5))] }, - { color: 2, origin: [c5, reflect(c5)], path: [hexagon(c5), flip(hexagon(c5))] }, - { color: 1, origin: [d5, reflect(d5)], path: [hexagon(d5), flip(hexagon(d5))] }, - { color: 0, origin: [e5, reflect(e5)], path: [hexagon(e5), flip(hexagon(e5))] }, - { color: 2, origin: [f5, reflect(f5)], path: [hexagon(f5), flip(hexagon(f5))] }, - { color: 0, origin: [g5, reflect(g5)], path: [hexagon(g5), flip(hexagon(g5))] }, - { color: 1, origin: [h5, reflect(h5)], path: [hexagon(h5), flip(hexagon(h5))] }, - { color: 2, origin: [i5, reflect(i5)], path: [hexagon(i5), flip(hexagon(i5))] }, - { color: 0, origin: [k5, reflect(k5)], path: [hexagon(k5), flip(hexagon(k5))] }, - { color: 1, origin: [l5, reflect(l5)], path: [hexagon(l5), flip(hexagon(l5))] }, - { color: 2, origin: [a4, reflect(a4)], path: [hexagon(a4), flip(hexagon(a4))] }, - { color: 1, origin: [b4, reflect(b4)], path: [hexagon(b4), flip(hexagon(b4))] }, - { color: 0, origin: [c4, reflect(c4)], path: [hexagon(c4), flip(hexagon(c4))] }, - { color: 2, origin: [d4, reflect(d4)], path: [hexagon(d4), flip(hexagon(d4))] }, - { color: 1, origin: [e4, reflect(e4)], path: [hexagon(e4), flip(hexagon(e4))] }, - { color: 0, origin: [f4, reflect(f4)], path: [hexagon(f4), flip(hexagon(f4))] }, - { color: 1, origin: [g4, reflect(g4)], path: [hexagon(g4), flip(hexagon(g4))] }, - { color: 2, origin: [h4, reflect(h4)], path: [hexagon(h4), flip(hexagon(h4))] }, - { color: 0, origin: [i4, reflect(i4)], path: [hexagon(i4), flip(hexagon(i4))] }, - { color: 1, origin: [k4, reflect(k4)], path: [hexagon(k4), flip(hexagon(k4))] }, - { color: 2, origin: [l4, reflect(l4)], path: [hexagon(l4), flip(hexagon(l4))] }, - { color: 0, origin: [a3, reflect(a3)], path: [hexagon(a3), flip(hexagon(a3))] }, - { color: 2, origin: [b3, reflect(b3)], path: [hexagon(b3), flip(hexagon(b3))] }, - { color: 1, origin: [c3, reflect(c3)], path: [hexagon(c3), flip(hexagon(c3))] }, - { color: 0, origin: [d3, reflect(d3)], path: [hexagon(d3), flip(hexagon(d3))] }, - { color: 2, origin: [e3, reflect(e3)], path: [hexagon(e3), flip(hexagon(e3))] }, - { color: 1, origin: [f3, reflect(f3)], path: [hexagon(f3), flip(hexagon(f3))] }, - { color: 2, origin: [g3, reflect(g3)], path: [hexagon(g3), flip(hexagon(g3))] }, - { color: 0, origin: [h3, reflect(h3)], path: [hexagon(h3), flip(hexagon(h3))] }, - { color: 1, origin: [i3, reflect(i3)], path: [hexagon(i3), flip(hexagon(i3))] }, - { color: 2, origin: [k3, reflect(k3)], path: [hexagon(k3), flip(hexagon(k3))] }, - { color: 0, origin: [l3, reflect(l3)], path: [hexagon(l3), flip(hexagon(l3))] }, - { color: 1, origin: [a2, reflect(a2)], path: [hexagon(a2), flip(hexagon(a2))] }, - { color: 0, origin: [b2, reflect(b2)], path: [hexagon(b2), flip(hexagon(b2))] }, - { color: 2, origin: [c2, reflect(c2)], path: [hexagon(c2), flip(hexagon(c2))] }, - { color: 1, origin: [d2, reflect(d2)], path: [hexagon(d2), flip(hexagon(d2))] }, - { color: 0, origin: [e2, reflect(e2)], path: [hexagon(e2), flip(hexagon(e2))] }, - { color: 2, origin: [f2, reflect(f2)], path: [hexagon(f2), flip(hexagon(f2))] }, - { color: 0, origin: [g2, reflect(g2)], path: [hexagon(g2), flip(hexagon(g2))] }, - { color: 1, origin: [h2, reflect(h2)], path: [hexagon(h2), flip(hexagon(h2))] }, - { color: 2, origin: [i2, reflect(i2)], path: [hexagon(i2), flip(hexagon(i2))] }, - { color: 0, origin: [k2, reflect(k2)], path: [hexagon(k2), flip(hexagon(k2))] }, - { color: 1, origin: [l2, reflect(l2)], path: [hexagon(l2), flip(hexagon(l2))] }, - { color: 2, origin: [a1, reflect(a1)], path: [hexagon(a1), flip(hexagon(a1))] }, - { color: 1, origin: [b1, reflect(b1)], path: [hexagon(b1), flip(hexagon(b1))] }, - { color: 0, origin: [c1, reflect(c1)], path: [hexagon(c1), flip(hexagon(c1))] }, - { color: 2, origin: [d1, reflect(d1)], path: [hexagon(d1), flip(hexagon(d1))] }, - { color: 1, origin: [e1, reflect(e1)], path: [hexagon(e1), flip(hexagon(e1))] }, - { color: 0, origin: [f1, reflect(f1)], path: [hexagon(f1), flip(hexagon(f1))] }, - { color: 1, origin: [g1, reflect(g1)], path: [hexagon(g1), flip(hexagon(g1))] }, - { color: 2, origin: [h1, reflect(h1)], path: [hexagon(h1), flip(hexagon(h1))] }, - { color: 0, origin: [i1, reflect(i1)], path: [hexagon(i1), flip(hexagon(i1))] }, - { color: 1, origin: [k1, reflect(k1)], path: [hexagon(k1), flip(hexagon(k1))] }, - { color: 2, origin: [l1, reflect(l1)], path: [hexagon(l1), flip(hexagon(l1))] }, -] - -/** board labels */ -export const labels: [string, Vec<2>, Vec<2>][] = [ - ['11', bilerp(g10, f11, labelOffset), reflect(bilerp(g10, f11, labelOffset))], - ['10', bilerp(f10, e10, labelOffset), reflect(bilerp(f10, e10, labelOffset))], - ['9', bilerp(e9, d9, labelOffset), reflect(bilerp(e9, d9, labelOffset))], - ['8', bilerp(d8, c8, labelOffset), reflect(bilerp(d8, c8, labelOffset))], - ['7', bilerp(c7, b7, labelOffset), reflect(bilerp(c7, b7, labelOffset))], - ['6', bilerp(b6, a6, labelOffset), reflect(bilerp(b6, a6, labelOffset))], - ['5', bilerp(b5, a5, labelOffset), reflect(bilerp(b5, a5, labelOffset))], - ['4', bilerp(b4, a4, labelOffset), reflect(bilerp(b4, a4, labelOffset))], - ['3', bilerp(b3, a3, labelOffset), reflect(bilerp(b3, a3, labelOffset))], - ['2', bilerp(b2, a2, labelOffset), reflect(bilerp(b2, a2, labelOffset))], - ['1', bilerp(b1, a1, labelOffset), reflect(bilerp(b1, a1, labelOffset))], - - ['a', bilerp(a2, a1, labelOffset), reflect(bilerp(a2, a1, labelOffset))], - ['b', bilerp(b2, b1, labelOffset), reflect(bilerp(b2, b1, labelOffset))], - ['c', bilerp(c2, c1, labelOffset), reflect(bilerp(c2, c1, labelOffset))], - ['d', bilerp(d2, d1, labelOffset), reflect(bilerp(d2, d1, labelOffset))], - ['e', bilerp(e2, e1, labelOffset), reflect(bilerp(e2, e1, labelOffset))], - ['f', bilerp(f2, f1, labelOffset), reflect(bilerp(f2, f1, labelOffset))], - ['g', bilerp(g2, g1, labelOffset), reflect(bilerp(g2, g1, labelOffset))], - ['h', bilerp(h2, h1, labelOffset), reflect(bilerp(h2, h1, labelOffset))], - ['i', bilerp(i2, i1, labelOffset), reflect(bilerp(i2, i1, labelOffset))], - ['k', bilerp(k2, k1, labelOffset), reflect(bilerp(k2, k1, labelOffset))], - ['l', bilerp(l2, l1, labelOffset), reflect(bilerp(l2, l1, labelOffset))], - - ['1', bilerp(k1, l1, labelOffset), reflect(bilerp(k1, l1, labelOffset))], - ['2', bilerp(k2, l2, labelOffset), reflect(bilerp(k2, l2, labelOffset))], - ['3', bilerp(k3, l3, labelOffset), reflect(bilerp(k3, l3, labelOffset))], - ['4', bilerp(k4, l4, labelOffset), reflect(bilerp(k4, l4, labelOffset))], - ['5', bilerp(k5, l5, labelOffset), reflect(bilerp(k5, l5, labelOffset))], - ['6', bilerp(k6, l6, labelOffset), reflect(bilerp(k6, l6, labelOffset))], - ['7', bilerp(i7, k7, labelOffset), reflect(bilerp(i7, k7, labelOffset))], - ['8', bilerp(h8, i8, labelOffset), reflect(bilerp(h8, i8, labelOffset))], - ['9', bilerp(g9, h9, labelOffset), reflect(bilerp(g9, h9, labelOffset))], - ['10', bilerp(f10, g10, labelOffset), reflect(bilerp(f10, g10, labelOffset))], - ['11', bilerp(e10, f11, labelOffset), reflect(bilerp(e10, f11, labelOffset))], -] - -/** perimeter of the board, used to create a constant background for light/dark mode */ -export const perimeter: Vec<93, Vec<2>> = [ - board[positions.indexOf('f11')]!.path[0][5], - board[positions.indexOf('f11')]!.path[0][0], - board[positions.indexOf('f11')]!.path[0][1], - board[positions.indexOf('g10')]!.path[0][5], - board[positions.indexOf('g10')]!.path[0][0], - board[positions.indexOf('g10')]!.path[0][1], - board[positions.indexOf('h9')]!.path[0][5], - board[positions.indexOf('h9')]!.path[0][0], - board[positions.indexOf('h9')]!.path[0][1], - board[positions.indexOf('i8')]!.path[0][5], - board[positions.indexOf('i8')]!.path[0][0], - board[positions.indexOf('i8')]!.path[0][1], - board[positions.indexOf('k7')]!.path[0][5], - board[positions.indexOf('k7')]!.path[0][0], - board[positions.indexOf('k7')]!.path[0][1], - board[positions.indexOf('l6')]!.path[0][5], - board[positions.indexOf('l6')]!.path[0][0], - board[positions.indexOf('l6')]!.path[0][1], - board[positions.indexOf('l6')]!.path[0][2], - board[positions.indexOf('l5')]!.path[0][0], - board[positions.indexOf('l5')]!.path[0][1], - board[positions.indexOf('l5')]!.path[0][2], - board[positions.indexOf('l4')]!.path[0][0], - board[positions.indexOf('l4')]!.path[0][1], - board[positions.indexOf('l4')]!.path[0][2], - board[positions.indexOf('l3')]!.path[0][0], - board[positions.indexOf('l3')]!.path[0][1], - board[positions.indexOf('l3')]!.path[0][2], - board[positions.indexOf('l2')]!.path[0][0], - board[positions.indexOf('l2')]!.path[0][1], - board[positions.indexOf('l2')]!.path[0][2], - board[positions.indexOf('l1')]!.path[0][0], - board[positions.indexOf('l1')]!.path[0][1], - board[positions.indexOf('l1')]!.path[0][2], - board[positions.indexOf('l1')]!.path[0][3], - board[positions.indexOf('k1')]!.path[0][2], - board[positions.indexOf('k1')]!.path[0][3], - board[positions.indexOf('k1')]!.path[0][4], - board[positions.indexOf('i1')]!.path[0][1], - board[positions.indexOf('i1')]!.path[0][2], - board[positions.indexOf('i1')]!.path[0][3], - board[positions.indexOf('h1')]!.path[0][1], - board[positions.indexOf('h1')]!.path[0][2], - board[positions.indexOf('h1')]!.path[0][3], - board[positions.indexOf('g1')]!.path[0][1], - board[positions.indexOf('g1')]!.path[0][2], - board[positions.indexOf('g1')]!.path[0][3], - board[positions.indexOf('f1')]!.path[0][1], - board[positions.indexOf('f1')]!.path[0][2], - board[positions.indexOf('f1')]!.path[0][3], - board[positions.indexOf('f1')]!.path[0][4], - board[positions.indexOf('e1')]!.path[0][3], - board[positions.indexOf('e1')]!.path[0][4], - board[positions.indexOf('d1')]!.path[0][2], - board[positions.indexOf('d1')]!.path[0][3], - board[positions.indexOf('d1')]!.path[0][4], - board[positions.indexOf('c1')]!.path[0][2], - board[positions.indexOf('c1')]!.path[0][3], - board[positions.indexOf('c1')]!.path[0][4], - board[positions.indexOf('b1')]!.path[0][2], - board[positions.indexOf('b1')]!.path[0][3], - board[positions.indexOf('b1')]!.path[0][4], - board[positions.indexOf('a1')]!.path[0][2], - board[positions.indexOf('a1')]!.path[0][3], - board[positions.indexOf('a1')]!.path[0][4], - board[positions.indexOf('a1')]!.path[0][5], - board[positions.indexOf('a2')]!.path[0][3], - board[positions.indexOf('a2')]!.path[0][4], - board[positions.indexOf('a2')]!.path[0][5], - board[positions.indexOf('a3')]!.path[0][3], - board[positions.indexOf('a3')]!.path[0][4], - board[positions.indexOf('a3')]!.path[0][5], - board[positions.indexOf('a4')]!.path[0][3], - board[positions.indexOf('a4')]!.path[0][4], - board[positions.indexOf('a4')]!.path[0][5], - board[positions.indexOf('a5')]!.path[0][3], - board[positions.indexOf('a5')]!.path[0][4], - board[positions.indexOf('a5')]!.path[0][5], - board[positions.indexOf('a6')]!.path[0][3], - board[positions.indexOf('a6')]!.path[0][4], - board[positions.indexOf('a6')]!.path[0][5], - board[positions.indexOf('b7')]!.path[0][4], - board[positions.indexOf('b7')]!.path[0][5], - board[positions.indexOf('b7')]!.path[0][0], - board[positions.indexOf('c8')]!.path[0][4], - board[positions.indexOf('c8')]!.path[0][5], - board[positions.indexOf('c8')]!.path[0][0], - board[positions.indexOf('d9')]!.path[0][4], - board[positions.indexOf('d9')]!.path[0][5], - board[positions.indexOf('d9')]!.path[0][0], - board[positions.indexOf('e10')]!.path[0][4], - board[positions.indexOf('e10')]!.path[0][5], - board[positions.indexOf('e10')]!.path[0][0], -] diff --git a/docs/components/hexboard/types.ts b/docs/components/hexboard/types.ts deleted file mode 100644 index e2037a67..00000000 --- a/docs/components/hexboard/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** uniform tuple of length `T`, `number` by default */ -export type Vec< - T extends number, - U = number, - V extends unknown[] = [] -> = V['length'] extends T ? V : Vec diff --git a/docs/sandbox/Sandbox.vue b/docs/sandbox/Sandbox.vue index 7ecba77c..84bb69f1 100644 --- a/docs/sandbox/Sandbox.vue +++ b/docs/sandbox/Sandbox.vue @@ -57,23 +57,69 @@ + :highlight + :playing="true" + @move="onMove"> + + diff --git a/hexchess.js b/hexchess.js index 2cc54dcd..1e1125a0 100755 --- a/hexchess.js +++ b/hexchess.js @@ -1,9 +1,10 @@ import { buildEngine } from './scripts/build-engine.js' import { buildJs } from './scripts/build-js.js' import { buildRust } from './scripts/build-rust.js' -import { dim, execAsync, green } from './scripts/utils.js' +import { buildUi } from './scripts/build-ui.js' +import { dim, execAsync, green, resolve } from './scripts/utils.js' import { program } from 'commander' -import { testEngine, testJs, testPhp, testRust } from './scripts/test.js' +import { testEngine, testJs, testPhp, testRust, testUi } from './scripts/test.js' import { versionCheck } from './scripts/version-check.js' import { version } from './scripts/version.js' import ora from 'ora' @@ -27,6 +28,11 @@ program await buildRust({ silent: true }) rust.succeed(`Rust crate ${dim(`(${Date.now() - rustStart}ms)`)}`) + const ui = ora('Building UI library...').start() + const uiStart = Date.now() + await buildUi({ silent: true }) + ui.succeed(`UI package ${dim(`(${Date.now() - uiStart}ms)`)}`) + console.log() console.log(green('Done'), dim(`(${Date.now() - startAt}ms)`)) }) @@ -46,6 +52,11 @@ program .description('Build Rust crate') .action(buildRust) +program + .command('build:ui') + .description('Build UI package') + .action(buildUi) + // // docs // @@ -112,6 +123,11 @@ program await testEngine({ silent: true }) engine.succeed(`Engine ${dim(`(${Date.now() - engineStartAt}ms)`)}`) + const uiStartAt = Date.now() + const ui = ora('UI...').start() + await testUi({ silent: true }) + ui.succeed(`UI ${dim(`(${Date.now() - uiStartAt}ms)`)}`) + console.log() console.log(green('Done'), dim(`(${Date.now() - startAt}ms)`)) }) @@ -171,6 +187,44 @@ program }) }) +program + .command('test:ui') + .description('Run UI tests') + .option('-c, --coverage', 'Generate coverage report') + .option('-f, --filter ', 'Filter tests by name') + .option('-s, --silent', 'Run tests silently') + .option('-u, --ui', 'Run tests with UI') + .action(async (options) => { + await testUi({ + coverage: options?.coverage, + filter: options?.filter, + silent: options?.silent, + ui: options?.ui, + }) + }) + +// +// ui +// + +program + .command('ui:dev') + .description('Develop UI') + .action(async () => { + await execAsync('npx', ['vite'], { + cwd: resolve('ui'), + }) + }) + +program + .command('ui:preview') + .description('Preview UI build') + .action(async () => { + await execAsync('npx', ['vite', 'preview'], { + cwd: resolve('ui'), + }) + }) + // // version // diff --git a/package.json b/package.json index 91f37c1d..b5f16504 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "devDependencies": { + "@bedard/hexchess": "link:js", "@tailwindcss/vite": "^4.1.16", "@types/node": "^24.9.1", "@vueuse/core": "^14.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c6609a81..18d6153f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: devDependencies: + '@bedard/hexchess': + specifier: link:js + version: link:js '@tailwindcss/vite': specifier: ^4.1.16 version: 4.1.16(vite@7.1.12(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)) diff --git a/scripts/build-ui.js b/scripts/build-ui.js new file mode 100644 index 00000000..323e4175 --- /dev/null +++ b/scripts/build-ui.js @@ -0,0 +1,11 @@ +import { execAsync, resolve } from './utils.js' + +/** build ui library */ +export async function buildUi(options) { + // build ui package + await execAsync('npx', ['vite', 'build'], { + cwd: resolve('ui'), + silent: options?.silent, + }) +} + diff --git a/scripts/test.js b/scripts/test.js index 2450fd5d..99cc9cc3 100644 --- a/scripts/test.js +++ b/scripts/test.js @@ -99,3 +99,27 @@ export async function testRust(options) { silent: options?.silent, }) } + +export async function testUi(options) { + const args = [ + 'vitest', + '--browser.headless' + ] + + if (options?.filter) { + args.push(options.filter) + } + + if (options?.coverage) { + args.push('--coverage') + } + + if (options?.ui) { + args.push('--ui') + } + + await execAsync('npx', args, { + cwd: resolve('ui'), + silent: options?.silent, + }) +} diff --git a/ui/index.html b/ui/index.html new file mode 100644 index 00000000..57c2c89d --- /dev/null +++ b/ui/index.html @@ -0,0 +1,14 @@ + + + + + + Hexchess UI + + + +
+ + + + diff --git a/ui/package.json b/ui/package.json new file mode 100644 index 00000000..bf7b7a34 --- /dev/null +++ b/ui/package.json @@ -0,0 +1,35 @@ +{ + "name": "@bedard/hexchess-ui", + "version": "0.1.0", + "type": "module", + "main": "dist/index.js", + "module": "dist/index.js", + "types": "dist/index.d.ts", + "peerDependencies": { + "@bedard/hexchess": "^2.5.0", + "vue": "^3.5.0" + }, + "devDependencies": { + "@bedard/hexchess": "^2.5.1", + "@headlessui/vue": "^1.7.23", + "@heroicons/vue": "^2.2.0", + "@tailwindcss/vite": "^4.1.17", + "@types/node": "^22.19.1", + "@vitejs/plugin-vue": "^6.0.2", + "@vitejs/plugin-vue-jsx": "^5.1.2", + "@vitest/browser": "^4.0.13", + "@vitest/browser-playwright": "^4.0.13", + "@vitest/coverage-v8": "^4.0.13", + "@vitest/ui": "^4.0.13", + "@vue/test-utils": "^2.4.6", + "jsdom": "^27.2.0", + "playwright": "^1.56.1", + "typescript": "^5.9.3", + "vite": "^6.4.1", + "vite-plugin-dts": "^4.5.4", + "vitest": "^4.0.13", + "vitest-browser-vue": "^2.0.1", + "vue": "^3.5.24", + "vue-tsc": "^3.1.5" + } +} diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml new file mode 100644 index 00000000..9bfe51a7 --- /dev/null +++ b/ui/pnpm-lock.yaml @@ -0,0 +1,3532 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@bedard/hexchess': + specifier: ^2.5.1 + version: 2.5.1 + '@headlessui/vue': + specifier: ^1.7.23 + version: 1.7.23(vue@3.5.24(typescript@5.9.3)) + '@heroicons/vue': + specifier: ^2.2.0 + version: 2.2.0(vue@3.5.24(typescript@5.9.3)) + '@tailwindcss/vite': + specifier: ^4.1.17 + version: 4.1.17(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)) + '@types/node': + specifier: ^22.19.1 + version: 22.19.1 + '@vitejs/plugin-vue': + specifier: ^6.0.2 + version: 6.0.2(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2))(vue@3.5.24(typescript@5.9.3)) + '@vitejs/plugin-vue-jsx': + specifier: ^5.1.2 + version: 5.1.2(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2))(vue@3.5.24(typescript@5.9.3)) + '@vitest/browser': + specifier: ^4.0.13 + version: 4.0.13(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2))(vitest@4.0.13(@types/node@22.19.1)(@vitest/browser-playwright@4.0.13)(@vitest/ui@4.0.13)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2)) + '@vitest/browser-playwright': + specifier: ^4.0.13 + version: 4.0.13(playwright@1.56.1)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2))(vitest@4.0.13) + '@vitest/coverage-v8': + specifier: ^4.0.13 + version: 4.0.13(@vitest/browser@4.0.13(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2))(vitest@4.0.13(@types/node@22.19.1)(@vitest/browser-playwright@4.0.13)(@vitest/ui@4.0.13)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2)))(vitest@4.0.13(@types/node@22.19.1)(@vitest/browser-playwright@4.0.13)(@vitest/ui@4.0.13)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2)) + '@vitest/ui': + specifier: ^4.0.13 + version: 4.0.13(vitest@4.0.13) + '@vue/test-utils': + specifier: ^2.4.6 + version: 2.4.6 + jsdom: + specifier: ^27.2.0 + version: 27.2.0 + playwright: + specifier: ^1.56.1 + version: 1.56.1 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vite: + specifier: ^6.4.1 + version: 6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2) + vite-plugin-dts: + specifier: ^4.5.4 + version: 4.5.4(@types/node@22.19.1)(rollup@4.53.3)(typescript@5.9.3)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)) + vitest: + specifier: ^4.0.13 + version: 4.0.13(@types/node@22.19.1)(@vitest/browser-playwright@4.0.13)(@vitest/ui@4.0.13)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2) + vitest-browser-vue: + specifier: ^2.0.1 + version: 2.0.1(vitest@4.0.13(@types/node@22.19.1)(@vitest/browser-playwright@4.0.13)(@vitest/ui@4.0.13)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2))(vue@3.5.24(typescript@5.9.3)) + vue: + specifier: ^3.5.24 + version: 3.5.24(typescript@5.9.3) + vue-tsc: + specifier: ^3.1.5 + version: 3.1.5(typescript@5.9.3) + +packages: + + '@acemir/cssom@0.9.24': + resolution: {integrity: sha512-5YjgMmAiT2rjJZU7XK1SNI7iqTy92DpaYVgG6x63FxkJ11UpYfLndHJATtinWJClAXiOlW9XWaUyAQf8pMrQPg==} + + '@asamuzakjp/css-color@4.1.0': + resolution: {integrity: sha512-9xiBAtLn4aNsa4mDnpovJvBn72tNEIACyvlqaNJ+ADemR+yeMJWnBudOi2qGDviJa7SwcDOU/TRh5dnET7qk0w==} + + '@asamuzakjp/dom-selector@6.7.4': + resolution: {integrity: sha512-buQDjkm+wDPXd6c13534URWZqbz0RP5PAhXZ+LIoa5LgwInT9HVJvGIJivg75vi8I13CxDGdTnz+aY5YUJlIAA==} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.5': + resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.5': + resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.28.5': + resolution: {integrity: sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-replace-supers@7.27.1': + resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.4': + resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-jsx@7.27.1': + resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.27.1': + resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.28.5': + resolution: {integrity: sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.5': + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + + '@bedard/hexchess@2.5.1': + resolution: {integrity: sha512-vuuL1CmGWL/kPrGPSi2wZcZahabp7cF2O6f0azsSHnc5YJxsDO8m5dRRyv/qNjCxF7L9An+TIFO2tExOA5YEJA==} + + '@csstools/color-helpers@5.1.0': + resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.4': + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-color-parser@3.1.0': + resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-syntax-patches-for-csstree@1.0.17': + resolution: {integrity: sha512-LCC++2h8pLUSPY+EsZmrrJ1EOUu+5iClpEiDhhdw3zRJpPbABML/N5lmRuBHjxtKm9VnRcsUzioyD0sekFMF0A==} + engines: {node: '>=18'} + + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} + engines: {node: '>=18'} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@headlessui/vue@1.7.23': + resolution: {integrity: sha512-JzdCNqurrtuu0YW6QaDtR2PIYCKPUWq28csDyMvN4zmGccmE7lz40Is6hc3LA4HFeCI7sekZ/PQMTNmn9I/4Wg==} + engines: {node: '>=10'} + peerDependencies: + vue: ^3.2.0 + + '@heroicons/vue@2.2.0': + resolution: {integrity: sha512-G3dbSxoeEKqbi/DFalhRxJU4mTXJn7GwZ7ae8NuEQzd1bqdd0jAbdaBZlHPcvPD2xI1iGzNVB4k20Un2AguYPw==} + peerDependencies: + vue: '>= 3' + + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.0': + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} + engines: {node: 20 || >=22} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@microsoft/api-extractor-model@7.32.1': + resolution: {integrity: sha512-u4yJytMYiUAnhcNQcZDTh/tVtlrzKlyKrQnLOV+4Qr/5gV+cpufWzCYAB1Q23URFqD6z2RoL2UYncM9xJVGNKA==} + + '@microsoft/api-extractor@7.55.1': + resolution: {integrity: sha512-l8Z+8qrLkZFM3HM95Dbpqs6G39fpCa7O5p8A7AkA6hSevxkgwsOlLrEuPv0ADOyj5dI1Af5WVDiwpKG/ya5G3w==} + hasBin: true + + '@microsoft/tsdoc-config@0.18.0': + resolution: {integrity: sha512-8N/vClYyfOH+l4fLkkr9+myAoR6M7akc8ntBJ4DJdWH2b09uVfr71+LTMpNyG19fNqWDg8KEDZhx5wxuqHyGjw==} + + '@microsoft/tsdoc@0.16.0': + resolution: {integrity: sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==} + + '@one-ini/wasm@0.1.1': + resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + + '@rolldown/pluginutils@1.0.0-beta.50': + resolution: {integrity: sha512-5e76wQiQVeL1ICOZVUg4LSOVYg9jyhGCin+icYozhsUzM+fHE7kddi1bdiE0jwVqTfkjba3jUFbEkoC9WkdvyA==} + + '@rolldown/pluginutils@1.0.0-beta.51': + resolution: {integrity: sha512-51/8cNXMrqWqX3o8DZidhwz1uYq0BhHDDSfVygAND1Skx5s1TDw3APSSxCMcFFedwgqGcx34gRouwY+m404BBQ==} + + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.53.3': + resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.53.3': + resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.53.3': + resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.53.3': + resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.53.3': + resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.53.3': + resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.53.3': + resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.53.3': + resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.53.3': + resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.53.3': + resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.53.3': + resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.53.3': + resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.53.3': + resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.53.3': + resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.53.3': + resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.53.3': + resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openharmony-arm64@4.53.3': + resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.53.3': + resolution: {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.53.3': + resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.53.3': + resolution: {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.53.3': + resolution: {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==} + cpu: [x64] + os: [win32] + + '@rushstack/node-core-library@5.19.0': + resolution: {integrity: sha512-BxAopbeWBvNJ6VGiUL+5lbJXywTdsnMeOS8j57Cn/xY10r6sV/gbsTlfYKjzVCUBZATX2eRzJHSMCchsMTGN6A==} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + '@rushstack/problem-matcher@0.1.1': + resolution: {integrity: sha512-Fm5XtS7+G8HLcJHCWpES5VmeMyjAKaWeyZU5qPzZC+22mPlJzAsOxymHiWIfuirtPckX3aptWws+K2d0BzniJA==} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + '@rushstack/rig-package@0.6.0': + resolution: {integrity: sha512-ZQmfzsLE2+Y91GF15c65L/slMRVhF6Hycq04D4TwtdGaUAbIXXg9c5pKA5KFU7M4QMaihoobp9JJYpYcaY3zOw==} + + '@rushstack/terminal@0.19.4': + resolution: {integrity: sha512-f4XQk02CrKfrMgyOfhYd3qWI944dLC21S4I/LUhrlAP23GTMDNG6EK5effQtFkISwUKCgD9vMBrJZaPSUquxWQ==} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + '@rushstack/ts-command-line@5.1.4': + resolution: {integrity: sha512-H0I6VdJ6sOUbktDFpP2VW5N29w8v4hRoNZOQz02vtEi6ZTYL1Ju8u+TcFiFawUDrUsx/5MQTUhd79uwZZVwVlA==} + + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + + '@tailwindcss/node@4.1.17': + resolution: {integrity: sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==} + + '@tailwindcss/oxide-android-arm64@4.1.17': + resolution: {integrity: sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.17': + resolution: {integrity: sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.17': + resolution: {integrity: sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.17': + resolution: {integrity: sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17': + resolution: {integrity: sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.17': + resolution: {integrity: sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.17': + resolution: {integrity: sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.17': + resolution: {integrity: sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.17': + resolution: {integrity: sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.17': + resolution: {integrity: sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.17': + resolution: {integrity: sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.17': + resolution: {integrity: sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.17': + resolution: {integrity: sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==} + engines: {node: '>= 10'} + + '@tailwindcss/vite@4.1.17': + resolution: {integrity: sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 + + '@tanstack/virtual-core@3.13.12': + resolution: {integrity: sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==} + + '@tanstack/vue-virtual@3.13.12': + resolution: {integrity: sha512-vhF7kEU9EXWXh+HdAwKJ2m3xaOnTTmgcdXcF2pim8g4GvI7eRrk2YRuV5nUlZnd/NbCIX4/Ja2OZu5EjJL06Ww==} + peerDependencies: + vue: ^2.7.0 || ^3.0.0 + + '@types/argparse@1.0.38': + resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/node@22.19.1': + resolution: {integrity: sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==} + + '@vitejs/plugin-vue-jsx@5.1.2': + resolution: {integrity: sha512-3a2BOryRjG/Iih87x87YXz5c8nw27eSlHytvSKYfp8ZIsp5+FgFQoKeA7k2PnqWpjJrv6AoVTMnvmuKUXb771A==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + vue: ^3.0.0 + + '@vitejs/plugin-vue@6.0.2': + resolution: {integrity: sha512-iHmwV3QcVGGvSC1BG5bZ4z6iwa1SOpAPWmnjOErd4Ske+lZua5K9TtAVdx0gMBClJ28DViCbSmZitjWZsWO3LA==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + vue: ^3.2.25 + + '@vitest/browser-playwright@4.0.13': + resolution: {integrity: sha512-oaRY+/pvwS4/sN2rE2aZh9jdli8EkXm5AidmXEbWRu2wW0omG9PmgChWCX2jsD9qRLQxXTSLl5oKezANNF6LnQ==} + peerDependencies: + playwright: '*' + vitest: 4.0.13 + + '@vitest/browser@4.0.13': + resolution: {integrity: sha512-lruSgrYPVAJzKmX6EJYCg9nY+0A4VkeTLpTzf1jRD/XMjNbzD9yy7D499xmVKglwJczANYJXBvZSPGcRlon+0w==} + peerDependencies: + vitest: 4.0.13 + + '@vitest/coverage-v8@4.0.13': + resolution: {integrity: sha512-w77N6bmtJ3CFnL/YHiYotwW/JI3oDlR3K38WEIqegRfdMSScaYxwYKB/0jSNpOTZzUjQkG8HHEz4sdWQMWpQ5g==} + peerDependencies: + '@vitest/browser': 4.0.13 + vitest: 4.0.13 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@4.0.13': + resolution: {integrity: sha512-zYtcnNIBm6yS7Gpr7nFTmq8ncowlMdOJkWLqYvhr/zweY6tFbDkDi8BPPOeHxEtK1rSI69H7Fd4+1sqvEGli6w==} + + '@vitest/mocker@4.0.13': + resolution: {integrity: sha512-eNCwzrI5djoauklwP1fuslHBjrbR8rqIVbvNlAnkq1OTa6XT+lX68mrtPirNM9TnR69XUPt4puBCx2Wexseylg==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.0.13': + resolution: {integrity: sha512-ooqfze8URWbI2ozOeLDMh8YZxWDpGXoeY3VOgcDnsUxN0jPyPWSUvjPQWqDGCBks+opWlN1E4oP1UYl3C/2EQA==} + + '@vitest/runner@4.0.13': + resolution: {integrity: sha512-9IKlAru58wcVaWy7hz6qWPb2QzJTKt+IOVKjAx5vb5rzEFPTL6H4/R9BMvjZ2ppkxKgTrFONEJFtzvnyEpiT+A==} + + '@vitest/snapshot@4.0.13': + resolution: {integrity: sha512-hb7Usvyika1huG6G6l191qu1urNPsq1iFc2hmdzQY3F5/rTgqQnwwplyf8zoYHkpt7H6rw5UfIw6i/3qf9oSxQ==} + + '@vitest/spy@4.0.13': + resolution: {integrity: sha512-hSu+m4se0lDV5yVIcNWqjuncrmBgwaXa2utFLIrBkQCQkt+pSwyZTPFQAZiiF/63j8jYa8uAeUZ3RSfcdWaYWw==} + + '@vitest/ui@4.0.13': + resolution: {integrity: sha512-MFV6GhTflgBj194+vowTB2iLI5niMZhqiW7/NV7U4AfWbX/IAtsq4zA+gzCLyGzpsQUdJlX26hrQ1vuWShq2BQ==} + peerDependencies: + vitest: 4.0.13 + + '@vitest/utils@4.0.13': + resolution: {integrity: sha512-ydozWyQ4LZuu8rLp47xFUWis5VOKMdHjXCWhs1LuJsTNKww+pTHQNK4e0assIB9K80TxFyskENL6vCu3j34EYA==} + + '@volar/language-core@2.4.23': + resolution: {integrity: sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==} + + '@volar/source-map@2.4.23': + resolution: {integrity: sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==} + + '@volar/typescript@2.4.23': + resolution: {integrity: sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==} + + '@vue/babel-helper-vue-transform-on@2.0.1': + resolution: {integrity: sha512-uZ66EaFbnnZSYqYEyplWvn46GhZ1KuYSThdT68p+am7MgBNbQ3hphTL9L+xSIsWkdktwhPYLwPgVWqo96jDdRA==} + + '@vue/babel-plugin-jsx@2.0.1': + resolution: {integrity: sha512-a8CaLQjD/s4PVdhrLD/zT574ZNPnZBOY+IhdtKWRB4HRZ0I2tXBi5ne7d9eCfaYwp5gU5+4KIyFTV1W1YL9xZA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + peerDependenciesMeta: + '@babel/core': + optional: true + + '@vue/babel-plugin-resolve-type@2.0.1': + resolution: {integrity: sha512-ybwgIuRGRRBhOU37GImDoWQoz+TlSqap65qVI6iwg/J7FfLTLmMf97TS7xQH9I7Qtr/gp161kYVdhr1ZMraSYQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@vue/compiler-core@3.5.24': + resolution: {integrity: sha512-eDl5H57AOpNakGNAkFDH+y7kTqrQpJkZFXhWZQGyx/5Wh7B1uQYvcWkvZi11BDhscPgj8N7XV3oRwiPnx1Vrig==} + + '@vue/compiler-dom@3.5.24': + resolution: {integrity: sha512-1QHGAvs53gXkWdd3ZMGYuvQFXHW4ksKWPG8HP8/2BscrbZ0brw183q2oNWjMrSWImYLHxHrx1ItBQr50I/q2zw==} + + '@vue/compiler-sfc@3.5.24': + resolution: {integrity: sha512-8EG5YPRgmTB+YxYBM3VXy8zHD9SWHUJLIGPhDovo3Z8VOgvP+O7UP5vl0J4BBPWYD9vxtBabzW1EuEZ+Cqs14g==} + + '@vue/compiler-ssr@3.5.24': + resolution: {integrity: sha512-trOvMWNBMQ/odMRHW7Ae1CdfYx+7MuiQu62Jtu36gMLXcaoqKvAyh+P73sYG9ll+6jLB6QPovqoKGGZROzkFFg==} + + '@vue/compiler-vue2@2.7.16': + resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} + + '@vue/language-core@2.2.0': + resolution: {integrity: sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/language-core@3.1.5': + resolution: {integrity: sha512-FMcqyzWN+sYBeqRMWPGT2QY0mUasZMVIuHvmb5NT3eeqPrbHBYtCP8JWEUCDCgM+Zr62uuWY/qoeBrPrzfa78w==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/reactivity@3.5.24': + resolution: {integrity: sha512-BM8kBhtlkkbnyl4q+HiF5R5BL0ycDPfihowulm02q3WYp2vxgPcJuZO866qa/0u3idbMntKEtVNuAUp5bw4teg==} + + '@vue/runtime-core@3.5.24': + resolution: {integrity: sha512-RYP/byyKDgNIqfX/gNb2PB55dJmM97jc9wyF3jK7QUInYKypK2exmZMNwnjueWwGceEkP6NChd3D2ZVEp9undQ==} + + '@vue/runtime-dom@3.5.24': + resolution: {integrity: sha512-Z8ANhr/i0XIluonHVjbUkjvn+CyrxbXRIxR7wn7+X7xlcb7dJsfITZbkVOeJZdP8VZwfrWRsWdShH6pngMxRjw==} + + '@vue/server-renderer@3.5.24': + resolution: {integrity: sha512-Yh2j2Y4G/0/4z/xJ1Bad4mxaAk++C2v4kaa8oSYTMJBJ00/ndPuxCnWeot0/7/qafQFLh5pr6xeV6SdMcE/G1w==} + peerDependencies: + vue: 3.5.24 + + '@vue/shared@3.5.24': + resolution: {integrity: sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A==} + + '@vue/test-utils@2.4.6': + resolution: {integrity: sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==} + + abbrev@2.0.0: + resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ajv-draft-04@1.0.0: + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.12.0: + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + + ajv@8.13.0: + resolution: {integrity: sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==} + + alien-signals@0.4.14: + resolution: {integrity: sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==} + + alien-signals@3.1.0: + resolution: {integrity: sha512-yufC6VpSy8tK3I0lO67pjumo5JvDQVQyr38+3OHqe6CHl1t2VZekKZ7EKKZSqk0cRmE7U7tfZbpXiKNzuc+ckg==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-v8-to-istanbul@0.3.8: + resolution: {integrity: sha512-szgSZqUxI5T8mLKvS7WTjF9is+MVbOeLADU73IseOcrqhxr/VAvy6wfoVE39KnKzA7JRhjF5eUagNlHwvZPlKQ==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + baseline-browser-mapping@2.8.30: + resolution: {integrity: sha512-aTUKW4ptQhS64+v2d6IkPzymEzzhw+G0bA1g3uBRV3+ntkH+svttKseW5IOR4Ed6NUVKqnY7qT3dKvzQ7io4AA==} + hasBin: true + + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + browserslist@4.28.0: + resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + caniuse-lite@1.0.30001756: + resolution: {integrity: sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==} + + chai@6.2.1: + resolution: {integrity: sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==} + engines: {node: '>=18'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + + compare-versions@6.1.1: + resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + + config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-tree@3.1.0: + resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + cssstyle@5.3.3: + resolution: {integrity: sha512-OytmFH+13/QXONJcC75QNdMtKpceNk3u8ThBjyyYjkEcy/ekBwR1mMAuNvi3gdBPW3N5TlCzQ0WZw8H0lN/bDw==} + engines: {node: '>=20'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + data-urls@6.0.0: + resolution: {integrity: sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==} + engines: {node: '>=20'} + + de-indent@1.0.2: + resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + diff@8.0.2: + resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} + engines: {node: '>=0.3.1'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + editorconfig@1.0.4: + resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==} + engines: {node: '>=14'} + hasBin: true + + electron-to-chromium@1.5.259: + resolution: {integrity: sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + enhanced-resolve@5.18.3: + resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} + engines: {node: '>=10.13.0'} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.2.2: + resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} + engines: {node: '>=12.0.0'} + + exsolve@1.0.8: + resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + fs-extra@11.3.2: + resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==} + engines: {node: '>=14.14'} + + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + hasBin: true + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + import-lazy@4.0.0: + resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} + engines: {node: '>=8'} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + jju@1.4.0: + resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + + js-beautify@1.15.4: + resolution: {integrity: sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==} + engines: {node: '>=14'} + hasBin: true + + js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + jsdom@27.2.0: + resolution: {integrity: sha512-454TI39PeRDW1LgpyLPyURtB4Zx1tklSr6+OFOipsxGUH1WMTvk6C65JQdrj455+DP2uJ1+veBEHTGFKWVLFoA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + kolorist@1.8.0: + resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + + lightningcss-android-arm64@1.30.2: + resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.30.2: + resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.2: + resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.2: + resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.2: + resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.2: + resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.2: + resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.2: + resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.2: + resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.2: + resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.2: + resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.2: + resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} + engines: {node: '>= 12.0.0'} + + local-pkg@1.1.2: + resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} + engines: {node: '>=14'} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@11.2.2: + resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.5.1: + resolution: {integrity: sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + mdn-data@2.12.2: + resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + + minimatch@10.0.3: + resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} + engines: {node: 20 || >=22} + + minimatch@9.0.1: + resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==} + engines: {node: '>=16 || 14 >=14.17'} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + nopt@7.2.1: + resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + hasBin: true + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + parse5@8.0.0: + resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pixelmatch@7.1.0: + resolution: {integrity: sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==} + hasBin: true + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + + playwright-core@1.56.1: + resolution: {integrity: sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.56.1: + resolution: {integrity: sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==} + engines: {node: '>=18'} + hasBin: true + + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + + rollup@4.53.3: + resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + tailwindcss@4.1.17: + resolution: {integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==} + + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.0.3: + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} + engines: {node: '>=14.0.0'} + + tldts-core@7.0.19: + resolution: {integrity: sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==} + + tldts@7.0.19: + resolution: {integrity: sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==} + hasBin: true + + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + tough-cookie@6.0.0: + resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} + engines: {node: '>=16'} + + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + + typescript@5.8.2: + resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} + engines: {node: '>=14.17'} + hasBin: true + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + update-browserslist-db@1.1.4: + resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + vite-plugin-dts@4.5.4: + resolution: {integrity: sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg==} + peerDependencies: + typescript: '*' + vite: '*' + peerDependenciesMeta: + vite: + optional: true + + vite@6.4.1: + resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest-browser-vue@2.0.1: + resolution: {integrity: sha512-IfTJY3Olr27AXCVAhOqx4g5iUgzdWLmYr40svg7rOrunjsqc9trrzi/eI3i11+UYddbKXu3HJl2bo8o8lqKm4A==} + peerDependencies: + vitest: ^4.0.0-0 + vue: ^3.0.0 + + vitest@4.0.13: + resolution: {integrity: sha512-QSD4I0fN6uZQfftryIXuqvqgBxTvJ3ZNkF6RWECd82YGAYAfhcppBLFXzXJHQAAhVFyYEuFTrq6h0hQqjB7jIQ==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/debug': ^4.1.12 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.13 + '@vitest/browser-preview': 4.0.13 + '@vitest/browser-webdriverio': 4.0.13 + '@vitest/ui': 4.0.13 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + vue-component-type-helpers@2.2.12: + resolution: {integrity: sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==} + + vue-tsc@3.1.5: + resolution: {integrity: sha512-L/G9IUjOWhBU0yun89rv8fKqmKC+T0HfhrFjlIml71WpfBv9eb4E9Bev8FMbyueBIU9vxQqbd+oOsVcDa5amGw==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + + vue@3.5.24: + resolution: {integrity: sha512-uTHDOpVQTMjcGgrqFPSb8iO2m1DUvo+WbGqoXQz8Y1CeBYQ0FXf2z1gLRaBtHjlRz7zZUBHxjVB5VTLzYkvftg==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@8.0.0: + resolution: {integrity: sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==} + engines: {node: '>=20'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@15.1.0: + resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==} + engines: {node: '>=20'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + +snapshots: + + '@acemir/cssom@0.9.24': {} + + '@asamuzakjp/css-color@4.1.0': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 11.2.2 + + '@asamuzakjp/dom-selector@6.7.4': + dependencies: + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.1.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.2.2 + + '@asamuzakjp/nwsapi@2.3.9': {} + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.5': {} + + '@babel/core@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) + '@babel/helpers': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.5': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.28.5 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.5 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.0 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.28.5 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-member-expression-to-functions@7.28.5': + dependencies: + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.27.1': + dependencies: + '@babel/types': 7.28.5 + + '@babel/helper-plugin-utils@7.27.1': {} + + '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + dependencies: + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.4': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-typescript@7.28.5(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + + '@babel/traverse@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@bcoe/v8-coverage@1.0.2': {} + + '@bedard/hexchess@2.5.1': {} + + '@csstools/color-helpers@5.1.0': {} + + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/color-helpers': 5.1.0 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-syntax-patches-for-csstree@1.0.17': {} + + '@csstools/css-tokenizer@3.0.4': {} + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@headlessui/vue@1.7.23(vue@3.5.24(typescript@5.9.3))': + dependencies: + '@tanstack/vue-virtual': 3.13.12(vue@3.5.24(typescript@5.9.3)) + vue: 3.5.24(typescript@5.9.3) + + '@heroicons/vue@2.2.0(vue@3.5.24(typescript@5.9.3))': + dependencies: + vue: 3.5.24(typescript@5.9.3) + + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': + dependencies: + '@isaacs/balanced-match': 4.0.1 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@microsoft/api-extractor-model@7.32.1(@types/node@22.19.1)': + dependencies: + '@microsoft/tsdoc': 0.16.0 + '@microsoft/tsdoc-config': 0.18.0 + '@rushstack/node-core-library': 5.19.0(@types/node@22.19.1) + transitivePeerDependencies: + - '@types/node' + + '@microsoft/api-extractor@7.55.1(@types/node@22.19.1)': + dependencies: + '@microsoft/api-extractor-model': 7.32.1(@types/node@22.19.1) + '@microsoft/tsdoc': 0.16.0 + '@microsoft/tsdoc-config': 0.18.0 + '@rushstack/node-core-library': 5.19.0(@types/node@22.19.1) + '@rushstack/rig-package': 0.6.0 + '@rushstack/terminal': 0.19.4(@types/node@22.19.1) + '@rushstack/ts-command-line': 5.1.4(@types/node@22.19.1) + diff: 8.0.2 + lodash: 4.17.21 + minimatch: 10.0.3 + resolve: 1.22.11 + semver: 7.5.4 + source-map: 0.6.1 + typescript: 5.8.2 + transitivePeerDependencies: + - '@types/node' + + '@microsoft/tsdoc-config@0.18.0': + dependencies: + '@microsoft/tsdoc': 0.16.0 + ajv: 8.12.0 + jju: 1.4.0 + resolve: 1.22.11 + + '@microsoft/tsdoc@0.16.0': {} + + '@one-ini/wasm@0.1.1': {} + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@polka/url@1.0.0-next.29': {} + + '@rolldown/pluginutils@1.0.0-beta.50': {} + + '@rolldown/pluginutils@1.0.0-beta.51': {} + + '@rollup/pluginutils@5.3.0(rollup@4.53.3)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.53.3 + + '@rollup/rollup-android-arm-eabi@4.53.3': + optional: true + + '@rollup/rollup-android-arm64@4.53.3': + optional: true + + '@rollup/rollup-darwin-arm64@4.53.3': + optional: true + + '@rollup/rollup-darwin-x64@4.53.3': + optional: true + + '@rollup/rollup-freebsd-arm64@4.53.3': + optional: true + + '@rollup/rollup-freebsd-x64@4.53.3': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.53.3': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.53.3': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.53.3': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.53.3': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.53.3': + optional: true + + '@rollup/rollup-linux-x64-musl@4.53.3': + optional: true + + '@rollup/rollup-openharmony-arm64@4.53.3': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.53.3': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.53.3': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.53.3': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.53.3': + optional: true + + '@rushstack/node-core-library@5.19.0(@types/node@22.19.1)': + dependencies: + ajv: 8.13.0 + ajv-draft-04: 1.0.0(ajv@8.13.0) + ajv-formats: 3.0.1(ajv@8.13.0) + fs-extra: 11.3.2 + import-lazy: 4.0.0 + jju: 1.4.0 + resolve: 1.22.11 + semver: 7.5.4 + optionalDependencies: + '@types/node': 22.19.1 + + '@rushstack/problem-matcher@0.1.1(@types/node@22.19.1)': + optionalDependencies: + '@types/node': 22.19.1 + + '@rushstack/rig-package@0.6.0': + dependencies: + resolve: 1.22.11 + strip-json-comments: 3.1.1 + + '@rushstack/terminal@0.19.4(@types/node@22.19.1)': + dependencies: + '@rushstack/node-core-library': 5.19.0(@types/node@22.19.1) + '@rushstack/problem-matcher': 0.1.1(@types/node@22.19.1) + supports-color: 8.1.1 + optionalDependencies: + '@types/node': 22.19.1 + + '@rushstack/ts-command-line@5.1.4(@types/node@22.19.1)': + dependencies: + '@rushstack/terminal': 0.19.4(@types/node@22.19.1) + '@types/argparse': 1.0.38 + argparse: 1.0.10 + string-argv: 0.3.2 + transitivePeerDependencies: + - '@types/node' + + '@standard-schema/spec@1.0.0': {} + + '@tailwindcss/node@4.1.17': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.18.3 + jiti: 2.6.1 + lightningcss: 1.30.2 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.1.17 + + '@tailwindcss/oxide-android-arm64@4.1.17': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.17': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.17': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.17': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.17': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.17': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.17': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.17': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.17': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.17': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.17': + optional: true + + '@tailwindcss/oxide@4.1.17': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.17 + '@tailwindcss/oxide-darwin-arm64': 4.1.17 + '@tailwindcss/oxide-darwin-x64': 4.1.17 + '@tailwindcss/oxide-freebsd-x64': 4.1.17 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.17 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.17 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.17 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.17 + '@tailwindcss/oxide-linux-x64-musl': 4.1.17 + '@tailwindcss/oxide-wasm32-wasi': 4.1.17 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.17 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.17 + + '@tailwindcss/vite@4.1.17(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2))': + dependencies: + '@tailwindcss/node': 4.1.17 + '@tailwindcss/oxide': 4.1.17 + tailwindcss: 4.1.17 + vite: 6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2) + + '@tanstack/virtual-core@3.13.12': {} + + '@tanstack/vue-virtual@3.13.12(vue@3.5.24(typescript@5.9.3))': + dependencies: + '@tanstack/virtual-core': 3.13.12 + vue: 3.5.24(typescript@5.9.3) + + '@types/argparse@1.0.38': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.8': {} + + '@types/node@22.19.1': + dependencies: + undici-types: 6.21.0 + + '@vitejs/plugin-vue-jsx@5.1.2(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2))(vue@3.5.24(typescript@5.9.3))': + dependencies: + '@babel/core': 7.28.5 + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5) + '@rolldown/pluginutils': 1.0.0-beta.51 + '@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.28.5) + vite: 6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2) + vue: 3.5.24(typescript@5.9.3) + transitivePeerDependencies: + - supports-color + + '@vitejs/plugin-vue@6.0.2(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2))(vue@3.5.24(typescript@5.9.3))': + dependencies: + '@rolldown/pluginutils': 1.0.0-beta.50 + vite: 6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2) + vue: 3.5.24(typescript@5.9.3) + + '@vitest/browser-playwright@4.0.13(playwright@1.56.1)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2))(vitest@4.0.13)': + dependencies: + '@vitest/browser': 4.0.13(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2))(vitest@4.0.13(@types/node@22.19.1)(@vitest/browser-playwright@4.0.13)(@vitest/ui@4.0.13)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2)) + '@vitest/mocker': 4.0.13(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)) + playwright: 1.56.1 + tinyrainbow: 3.0.3 + vitest: 4.0.13(@types/node@22.19.1)(@vitest/browser-playwright@4.0.13)(@vitest/ui@4.0.13)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2) + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + + '@vitest/browser@4.0.13(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2))(vitest@4.0.13(@types/node@22.19.1)(@vitest/browser-playwright@4.0.13)(@vitest/ui@4.0.13)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2))': + dependencies: + '@vitest/mocker': 4.0.13(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)) + '@vitest/utils': 4.0.13 + magic-string: 0.30.21 + pixelmatch: 7.1.0 + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.0.3 + vitest: 4.0.13(@types/node@22.19.1)(@vitest/browser-playwright@4.0.13)(@vitest/ui@4.0.13)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2) + ws: 8.18.3 + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + + '@vitest/coverage-v8@4.0.13(@vitest/browser@4.0.13(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2))(vitest@4.0.13(@types/node@22.19.1)(@vitest/browser-playwright@4.0.13)(@vitest/ui@4.0.13)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2)))(vitest@4.0.13(@types/node@22.19.1)(@vitest/browser-playwright@4.0.13)(@vitest/ui@4.0.13)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2))': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.0.13 + ast-v8-to-istanbul: 0.3.8 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + magicast: 0.5.1 + std-env: 3.10.0 + tinyrainbow: 3.0.3 + vitest: 4.0.13(@types/node@22.19.1)(@vitest/browser-playwright@4.0.13)(@vitest/ui@4.0.13)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2) + optionalDependencies: + '@vitest/browser': 4.0.13(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2))(vitest@4.0.13(@types/node@22.19.1)(@vitest/browser-playwright@4.0.13)(@vitest/ui@4.0.13)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2)) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@4.0.13': + dependencies: + '@standard-schema/spec': 1.0.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.13 + '@vitest/utils': 4.0.13 + chai: 6.2.1 + tinyrainbow: 3.0.3 + + '@vitest/mocker@4.0.13(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2))': + dependencies: + '@vitest/spy': 4.0.13 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2) + + '@vitest/pretty-format@4.0.13': + dependencies: + tinyrainbow: 3.0.3 + + '@vitest/runner@4.0.13': + dependencies: + '@vitest/utils': 4.0.13 + pathe: 2.0.3 + + '@vitest/snapshot@4.0.13': + dependencies: + '@vitest/pretty-format': 4.0.13 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.0.13': {} + + '@vitest/ui@4.0.13(vitest@4.0.13)': + dependencies: + '@vitest/utils': 4.0.13 + fflate: 0.8.2 + flatted: 3.3.3 + pathe: 2.0.3 + sirv: 3.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vitest: 4.0.13(@types/node@22.19.1)(@vitest/browser-playwright@4.0.13)(@vitest/ui@4.0.13)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2) + + '@vitest/utils@4.0.13': + dependencies: + '@vitest/pretty-format': 4.0.13 + tinyrainbow: 3.0.3 + + '@volar/language-core@2.4.23': + dependencies: + '@volar/source-map': 2.4.23 + + '@volar/source-map@2.4.23': {} + + '@volar/typescript@2.4.23': + dependencies: + '@volar/language-core': 2.4.23 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vue/babel-helper-vue-transform-on@2.0.1': {} + + '@vue/babel-plugin-jsx@2.0.1(@babel/core@7.28.5)': + dependencies: + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + '@vue/babel-helper-vue-transform-on': 2.0.1 + '@vue/babel-plugin-resolve-type': 2.0.1(@babel/core@7.28.5) + '@vue/shared': 3.5.24 + optionalDependencies: + '@babel/core': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@vue/babel-plugin-resolve-type@2.0.1(@babel/core@7.28.5)': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/parser': 7.28.5 + '@vue/compiler-sfc': 3.5.24 + transitivePeerDependencies: + - supports-color + + '@vue/compiler-core@3.5.24': + dependencies: + '@babel/parser': 7.28.5 + '@vue/shared': 3.5.24 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.24': + dependencies: + '@vue/compiler-core': 3.5.24 + '@vue/shared': 3.5.24 + + '@vue/compiler-sfc@3.5.24': + dependencies: + '@babel/parser': 7.28.5 + '@vue/compiler-core': 3.5.24 + '@vue/compiler-dom': 3.5.24 + '@vue/compiler-ssr': 3.5.24 + '@vue/shared': 3.5.24 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.6 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.24': + dependencies: + '@vue/compiler-dom': 3.5.24 + '@vue/shared': 3.5.24 + + '@vue/compiler-vue2@2.7.16': + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + + '@vue/language-core@2.2.0(typescript@5.9.3)': + dependencies: + '@volar/language-core': 2.4.23 + '@vue/compiler-dom': 3.5.24 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.5.24 + alien-signals: 0.4.14 + minimatch: 9.0.5 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + optionalDependencies: + typescript: 5.9.3 + + '@vue/language-core@3.1.5(typescript@5.9.3)': + dependencies: + '@volar/language-core': 2.4.23 + '@vue/compiler-dom': 3.5.24 + '@vue/shared': 3.5.24 + alien-signals: 3.1.0 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + picomatch: 4.0.3 + optionalDependencies: + typescript: 5.9.3 + + '@vue/reactivity@3.5.24': + dependencies: + '@vue/shared': 3.5.24 + + '@vue/runtime-core@3.5.24': + dependencies: + '@vue/reactivity': 3.5.24 + '@vue/shared': 3.5.24 + + '@vue/runtime-dom@3.5.24': + dependencies: + '@vue/reactivity': 3.5.24 + '@vue/runtime-core': 3.5.24 + '@vue/shared': 3.5.24 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.24(vue@3.5.24(typescript@5.9.3))': + dependencies: + '@vue/compiler-ssr': 3.5.24 + '@vue/shared': 3.5.24 + vue: 3.5.24(typescript@5.9.3) + + '@vue/shared@3.5.24': {} + + '@vue/test-utils@2.4.6': + dependencies: + js-beautify: 1.15.4 + vue-component-type-helpers: 2.2.12 + + abbrev@2.0.0: {} + + acorn@8.15.0: {} + + agent-base@7.1.4: {} + + ajv-draft-04@1.0.0(ajv@8.13.0): + optionalDependencies: + ajv: 8.13.0 + + ajv-formats@3.0.1(ajv@8.13.0): + optionalDependencies: + ajv: 8.13.0 + + ajv@8.12.0: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + ajv@8.13.0: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + alien-signals@0.4.14: {} + + alien-signals@3.1.0: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + assertion-error@2.0.1: {} + + ast-v8-to-istanbul@0.3.8: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 9.0.1 + + balanced-match@1.0.2: {} + + baseline-browser-mapping@2.8.30: {} + + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + browserslist@4.28.0: + dependencies: + baseline-browser-mapping: 2.8.30 + caniuse-lite: 1.0.30001756 + electron-to-chromium: 1.5.259 + node-releases: 2.0.27 + update-browserslist-db: 1.1.4(browserslist@4.28.0) + + caniuse-lite@1.0.30001756: {} + + chai@6.2.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@10.0.1: {} + + compare-versions@6.1.1: {} + + confbox@0.1.8: {} + + confbox@0.2.2: {} + + config-chain@1.1.13: + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + + convert-source-map@2.0.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-tree@3.1.0: + dependencies: + mdn-data: 2.12.2 + source-map-js: 1.2.1 + + cssstyle@5.3.3: + dependencies: + '@asamuzakjp/css-color': 4.1.0 + '@csstools/css-syntax-patches-for-csstree': 1.0.17 + css-tree: 3.1.0 + + csstype@3.2.3: {} + + data-urls@6.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 15.1.0 + + de-indent@1.0.2: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decimal.js@10.6.0: {} + + detect-libc@2.1.2: {} + + diff@8.0.2: {} + + eastasianwidth@0.2.0: {} + + editorconfig@1.0.4: + dependencies: + '@one-ini/wasm': 0.1.1 + commander: 10.0.1 + minimatch: 9.0.1 + semver: 7.7.3 + + electron-to-chromium@1.5.259: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + enhanced-resolve@5.18.3: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + + entities@4.5.0: {} + + entities@6.0.1: {} + + es-module-lexer@1.7.0: {} + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + escalade@3.2.0: {} + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + expect-type@1.2.2: {} + + exsolve@1.0.8: {} + + fast-deep-equal@3.1.3: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fflate@0.8.2: {} + + flatted@3.3.3: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fs-extra@11.3.2: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fsevents@2.3.2: + optional: true + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + graceful-fs@4.2.11: {} + + has-flag@4.0.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + he@1.2.0: {} + + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + html-escaper@2.0.2: {} + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + import-lazy@4.0.0: {} + + ini@1.3.8: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-fullwidth-code-point@3.0.0: {} + + is-potential-custom-element-name@1.0.1: {} + + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jiti@2.6.1: {} + + jju@1.4.0: {} + + js-beautify@1.15.4: + dependencies: + config-chain: 1.1.13 + editorconfig: 1.0.4 + glob: 10.5.0 + js-cookie: 3.0.5 + nopt: 7.2.1 + + js-cookie@3.0.5: {} + + js-tokens@4.0.0: {} + + js-tokens@9.0.1: {} + + jsdom@27.2.0: + dependencies: + '@acemir/cssom': 0.9.24 + '@asamuzakjp/dom-selector': 6.7.4 + cssstyle: 5.3.3 + data-urls: 6.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + parse5: 8.0.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 15.1.0 + ws: 8.18.3 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + jsesc@3.1.0: {} + + json-schema-traverse@1.0.0: {} + + json5@2.2.3: {} + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + kolorist@1.8.0: {} + + lightningcss-android-arm64@1.30.2: + optional: true + + lightningcss-darwin-arm64@1.30.2: + optional: true + + lightningcss-darwin-x64@1.30.2: + optional: true + + lightningcss-freebsd-x64@1.30.2: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.2: + optional: true + + lightningcss-linux-arm64-gnu@1.30.2: + optional: true + + lightningcss-linux-arm64-musl@1.30.2: + optional: true + + lightningcss-linux-x64-gnu@1.30.2: + optional: true + + lightningcss-linux-x64-musl@1.30.2: + optional: true + + lightningcss-win32-arm64-msvc@1.30.2: + optional: true + + lightningcss-win32-x64-msvc@1.30.2: + optional: true + + lightningcss@1.30.2: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.30.2 + lightningcss-darwin-arm64: 1.30.2 + lightningcss-darwin-x64: 1.30.2 + lightningcss-freebsd-x64: 1.30.2 + lightningcss-linux-arm-gnueabihf: 1.30.2 + lightningcss-linux-arm64-gnu: 1.30.2 + lightningcss-linux-arm64-musl: 1.30.2 + lightningcss-linux-x64-gnu: 1.30.2 + lightningcss-linux-x64-musl: 1.30.2 + lightningcss-win32-arm64-msvc: 1.30.2 + lightningcss-win32-x64-msvc: 1.30.2 + + local-pkg@1.1.2: + dependencies: + mlly: 1.8.0 + pkg-types: 2.3.0 + quansync: 0.2.11 + + lodash@4.17.21: {} + + lru-cache@10.4.3: {} + + lru-cache@11.2.2: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magicast@0.5.1: + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.7.3 + + mdn-data@2.12.2: {} + + minimatch@10.0.3: + dependencies: + '@isaacs/brace-expansion': 5.0.0 + + minimatch@9.0.1: + dependencies: + brace-expansion: 2.0.2 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minipass@7.1.2: {} + + mlly@1.8.0: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.1 + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + muggle-string@0.4.1: {} + + nanoid@3.3.11: {} + + node-releases@2.0.27: {} + + nopt@7.2.1: + dependencies: + abbrev: 2.0.0 + + package-json-from-dist@1.0.1: {} + + parse5@8.0.0: + dependencies: + entities: 6.0.1 + + path-browserify@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + pixelmatch@7.1.0: + dependencies: + pngjs: 7.0.0 + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 + + pkg-types@2.3.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.8 + pathe: 2.0.3 + + playwright-core@1.56.1: {} + + playwright@1.56.1: + dependencies: + playwright-core: 1.56.1 + optionalDependencies: + fsevents: 2.3.2 + + pngjs@7.0.0: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + proto-list@1.2.4: {} + + punycode@2.3.1: {} + + quansync@0.2.11: {} + + require-from-string@2.0.2: {} + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + rollup@4.53.3: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.53.3 + '@rollup/rollup-android-arm64': 4.53.3 + '@rollup/rollup-darwin-arm64': 4.53.3 + '@rollup/rollup-darwin-x64': 4.53.3 + '@rollup/rollup-freebsd-arm64': 4.53.3 + '@rollup/rollup-freebsd-x64': 4.53.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.53.3 + '@rollup/rollup-linux-arm-musleabihf': 4.53.3 + '@rollup/rollup-linux-arm64-gnu': 4.53.3 + '@rollup/rollup-linux-arm64-musl': 4.53.3 + '@rollup/rollup-linux-loong64-gnu': 4.53.3 + '@rollup/rollup-linux-ppc64-gnu': 4.53.3 + '@rollup/rollup-linux-riscv64-gnu': 4.53.3 + '@rollup/rollup-linux-riscv64-musl': 4.53.3 + '@rollup/rollup-linux-s390x-gnu': 4.53.3 + '@rollup/rollup-linux-x64-gnu': 4.53.3 + '@rollup/rollup-linux-x64-musl': 4.53.3 + '@rollup/rollup-openharmony-arm64': 4.53.3 + '@rollup/rollup-win32-arm64-msvc': 4.53.3 + '@rollup/rollup-win32-ia32-msvc': 4.53.3 + '@rollup/rollup-win32-x64-gnu': 4.53.3 + '@rollup/rollup-win32-x64-msvc': 4.53.3 + fsevents: 2.3.3 + + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + semver@6.3.1: {} + + semver@7.5.4: + dependencies: + lru-cache: 6.0.0 + + semver@7.7.3: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + + source-map-js@1.2.1: {} + + source-map@0.6.1: {} + + sprintf-js@1.0.3: {} + + stackback@0.0.2: {} + + std-env@3.10.0: {} + + string-argv@0.3.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + symbol-tree@3.2.4: {} + + tailwindcss@4.1.17: {} + + tapable@2.3.0: {} + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinyrainbow@3.0.3: {} + + tldts-core@7.0.19: {} + + tldts@7.0.19: + dependencies: + tldts-core: 7.0.19 + + totalist@3.0.1: {} + + tough-cookie@6.0.0: + dependencies: + tldts: 7.0.19 + + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + + typescript@5.8.2: {} + + typescript@5.9.3: {} + + ufo@1.6.1: {} + + undici-types@6.21.0: {} + + universalify@2.0.1: {} + + update-browserslist-db@1.1.4(browserslist@4.28.0): + dependencies: + browserslist: 4.28.0 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + vite-plugin-dts@4.5.4(@types/node@22.19.1)(rollup@4.53.3)(typescript@5.9.3)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)): + dependencies: + '@microsoft/api-extractor': 7.55.1(@types/node@22.19.1) + '@rollup/pluginutils': 5.3.0(rollup@4.53.3) + '@volar/typescript': 2.4.23 + '@vue/language-core': 2.2.0(typescript@5.9.3) + compare-versions: 6.1.1 + debug: 4.4.3 + kolorist: 1.8.0 + local-pkg: 1.1.2 + magic-string: 0.30.21 + typescript: 5.9.3 + optionalDependencies: + vite: 6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2) + transitivePeerDependencies: + - '@types/node' + - rollup + - supports-color + + vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.53.3 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 22.19.1 + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.30.2 + + vitest-browser-vue@2.0.1(vitest@4.0.13(@types/node@22.19.1)(@vitest/browser-playwright@4.0.13)(@vitest/ui@4.0.13)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2))(vue@3.5.24(typescript@5.9.3)): + dependencies: + '@vue/test-utils': 2.4.6 + vitest: 4.0.13(@types/node@22.19.1)(@vitest/browser-playwright@4.0.13)(@vitest/ui@4.0.13)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2) + vue: 3.5.24(typescript@5.9.3) + + vitest@4.0.13(@types/node@22.19.1)(@vitest/browser-playwright@4.0.13)(@vitest/ui@4.0.13)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2): + dependencies: + '@vitest/expect': 4.0.13 + '@vitest/mocker': 4.0.13(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)) + '@vitest/pretty-format': 4.0.13 + '@vitest/runner': 4.0.13 + '@vitest/snapshot': 4.0.13 + '@vitest/spy': 4.0.13 + '@vitest/utils': 4.0.13 + debug: 4.4.3 + es-module-lexer: 1.7.0 + expect-type: 1.2.2 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.19.1 + '@vitest/browser-playwright': 4.0.13(playwright@1.56.1)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2))(vitest@4.0.13) + '@vitest/ui': 4.0.13(vitest@4.0.13) + jsdom: 27.2.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vscode-uri@3.1.0: {} + + vue-component-type-helpers@2.2.12: {} + + vue-tsc@3.1.5(typescript@5.9.3): + dependencies: + '@volar/typescript': 2.4.23 + '@vue/language-core': 3.1.5(typescript@5.9.3) + typescript: 5.9.3 + + vue@3.5.24(typescript@5.9.3): + dependencies: + '@vue/compiler-dom': 3.5.24 + '@vue/compiler-sfc': 3.5.24 + '@vue/runtime-dom': 3.5.24 + '@vue/server-renderer': 3.5.24(vue@3.5.24(typescript@5.9.3)) + '@vue/shared': 3.5.24 + optionalDependencies: + typescript: 5.9.3 + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@8.0.0: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@15.1.0: + dependencies: + tr46: 6.0.0 + webidl-conversions: 8.0.0 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + + ws@8.18.3: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + yallist@3.1.1: {} + + yallist@4.0.0: {} diff --git a/ui/src/App.vue b/ui/src/App.vue new file mode 100644 index 00000000..e7cdf7ca --- /dev/null +++ b/ui/src/App.vue @@ -0,0 +1,253 @@ + + + diff --git a/ui/src/client.ts b/ui/src/client.ts new file mode 100644 index 00000000..01433bca --- /dev/null +++ b/ui/src/client.ts @@ -0,0 +1,4 @@ +import { createApp } from 'vue' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/ui/src/components/Checkbox.vue b/ui/src/components/Checkbox.vue new file mode 100644 index 00000000..5c78523d --- /dev/null +++ b/ui/src/components/Checkbox.vue @@ -0,0 +1,42 @@ + + + \ No newline at end of file diff --git a/ui/src/components/Combobox.vue b/ui/src/components/Combobox.vue new file mode 100644 index 00000000..8ab003a4 --- /dev/null +++ b/ui/src/components/Combobox.vue @@ -0,0 +1,74 @@ + + + diff --git a/ui/src/components/Select.vue b/ui/src/components/Select.vue new file mode 100644 index 00000000..5a481978 --- /dev/null +++ b/ui/src/components/Select.vue @@ -0,0 +1,43 @@ + + + \ No newline at end of file diff --git a/ui/src/components/index.ts b/ui/src/components/index.ts new file mode 100644 index 00000000..9d9fcada --- /dev/null +++ b/ui/src/components/index.ts @@ -0,0 +1,3 @@ +export { default as Checkbox } from './Checkbox.vue' +export { default as Combobox } from './Combobox.vue' +export { default as Select } from './Select.vue' diff --git a/ui/src/composables/use-dom-rect.ts b/ui/src/composables/use-dom-rect.ts new file mode 100644 index 00000000..a96690e0 --- /dev/null +++ b/ui/src/composables/use-dom-rect.ts @@ -0,0 +1,30 @@ +import { onMounted, onUnmounted, shallowRef, type TemplateRef } from 'vue' + +export function useDomRect(el: TemplateRef) { + const rect = shallowRef(new DOMRect()) + + function measure() { + rect.value = el.value?.getBoundingClientRect() ?? new DOMRect() + } + + function reset() { + rect.value = new DOMRect() + } + + onMounted(() => { + measure() + window.addEventListener('resize', measure) + window.addEventListener('scroll', measure) + }) + + onUnmounted(() => { + window.removeEventListener('resize', measure) + window.removeEventListener('scroll', measure) + }) + + return { + measure, + rect, + reset, + } +} \ No newline at end of file diff --git a/ui/src/lib/hexboard/Hexboard.vue b/ui/src/lib/hexboard/Hexboard.vue new file mode 100644 index 00000000..17916447 --- /dev/null +++ b/ui/src/lib/hexboard/Hexboard.vue @@ -0,0 +1,772 @@ + + + diff --git a/ui/src/lib/hexboard/constants.ts b/ui/src/lib/hexboard/constants.ts new file mode 100644 index 00000000..c43d8baf --- /dev/null +++ b/ui/src/lib/hexboard/constants.ts @@ -0,0 +1,387 @@ +import { bilerp, flip, hexagon, pivot, reflect, sum } from './geometry' +import type { HexboardOptions, Vec } from './types' + +/** svg viewbox, increasing this zooms out away from the board */ +export const box = 23.6 + +export const defaultOptions: HexboardOptions = { + colors: [ + 'oklch(0.9015 0.0729 70.7)', + 'oklch(0.8366 0.1165 66.29)', + 'oklch(0.6806 0.1423 75.83)', + ], + highlightColor: 'oklch(90.5% 0.182 98.111 / 75%)', // yellow-300 / 75% opacity + labelActiveColor: 'oklch(76.9% 0.188 70.08)', // amber-500 + labelColor: 'oklch(55.4% 0.046 257.417)', // slate-500 + labelInactiveColor: 'oklch(70.4% 0.04 256.788)', // slate-400 + labels: true, + selectedColor: 'oklch(63.7% 0.237 25.331)', // red-500 + targetColor: 'oklch(63.7% 0.237 25.331)', // red-500 +} as const + +/** empty position */ +export const emptyPosition = '1/3/5/7/9/11/11/11/11/11/11 w - 0 1' + +/** initial position */ +export const initialPosition = 'b/qbk/n1b1n/r5r/ppppppppp/11/5P5/4P1P4/3P1B1P3/2P2B2P2/1PRNQBKNRP1 w - 0 1' + +/** label offset */ +export const labelOffset = 1.7 + +/** piece size */ +export const pieceSize = 1.7 + +/** side length of regular hexagon circumscribed around a unit circle */ +export const sideLength = 2 / Math.sqrt(3) + +// distance between the origin of neighboring hexagons +// one = direct neighbors, two = neighbor of neighbor, etc... +export const one = sideLength * (Math.sqrt(3) / 2) * 2 // <- inscribed radius * 2 +export const two = one * 2 +export const three = one * 3 +export const four = one * 4 +export const five = one * 5 + +// calculate the center for each position, using f6 as the origin +const f6: Vec<2> = [0, 0] + +// the 6th rank is calculated as angled distances from F6 +const a6 = pivot(f6, 150, five) +const b6 = pivot(f6, 150, four) +const c6 = pivot(f6, 150, three) +const d6 = pivot(f6, 150, two) +const e6 = pivot(f6, 150, one) +const g6 = pivot(f6, 30, one) +const h6 = pivot(f6, 30, two) +const i6 = pivot(f6, 30, three) +const k6 = pivot(f6, 30, four) +const l6 = pivot(f6, 30, five) + +// other ranks are calculated by vertically translating the 6th rank +const a1 = sum(a6, [0, -five]) +const b1 = sum(b6, [0, -five]) +const c1 = sum(c6, [0, -five]) +const d1 = sum(d6, [0, -five]) +const e1 = sum(e6, [0, -five]) +const f1 = sum(f6, [0, -five]) +const g1 = sum(g6, [0, -five]) +const h1 = sum(h6, [0, -five]) +const i1 = sum(i6, [0, -five]) +const k1 = sum(k6, [0, -five]) +const l1 = sum(l6, [0, -five]) + +const a2 = sum(a6, [0, -four]) +const b2 = sum(b6, [0, -four]) +const c2 = sum(c6, [0, -four]) +const d2 = sum(d6, [0, -four]) +const e2 = sum(e6, [0, -four]) +const f2 = sum(f6, [0, -four]) +const g2 = sum(g6, [0, -four]) +const h2 = sum(h6, [0, -four]) +const i2 = sum(i6, [0, -four]) +const k2 = sum(k6, [0, -four]) +const l2 = sum(l6, [0, -four]) + +const a3 = sum(a6, [0, -three]) +const b3 = sum(b6, [0, -three]) +const c3 = sum(c6, [0, -three]) +const d3 = sum(d6, [0, -three]) +const e3 = sum(e6, [0, -three]) +const f3 = sum(f6, [0, -three]) +const g3 = sum(g6, [0, -three]) +const h3 = sum(h6, [0, -three]) +const i3 = sum(i6, [0, -three]) +const k3 = sum(k6, [0, -three]) +const l3 = sum(l6, [0, -three]) + +const a4 = sum(a6, [0, -two]) +const b4 = sum(b6, [0, -two]) +const c4 = sum(c6, [0, -two]) +const d4 = sum(d6, [0, -two]) +const e4 = sum(e6, [0, -two]) +const f4 = sum(f6, [0, -two]) +const g4 = sum(g6, [0, -two]) +const h4 = sum(h6, [0, -two]) +const i4 = sum(i6, [0, -two]) +const k4 = sum(k6, [0, -two]) +const l4 = sum(l6, [0, -two]) + +const a5 = sum(a6, [0, -one]) +const b5 = sum(b6, [0, -one]) +const c5 = sum(c6, [0, -one]) +const d5 = sum(d6, [0, -one]) +const e5 = sum(e6, [0, -one]) +const f5 = sum(f6, [0, -one]) +const g5 = sum(g6, [0, -one]) +const h5 = sum(h6, [0, -one]) +const i5 = sum(i6, [0, -one]) +const k5 = sum(k6, [0, -one]) +const l5 = sum(l6, [0, -one]) + +// 7th rank and higher start to have fewer files +const b7 = sum(b6, [0, one]) +const c7 = sum(c6, [0, one]) +const d7 = sum(d6, [0, one]) +const e7 = sum(e6, [0, one]) +const f7 = sum(f6, [0, one]) +const g7 = sum(g6, [0, one]) +const h7 = sum(h6, [0, one]) +const i7 = sum(i6, [0, one]) +const k7 = sum(k6, [0, one]) + +const c8 = sum(c6, [0, two]) +const d8 = sum(d6, [0, two]) +const e8 = sum(e6, [0, two]) +const f8 = sum(f6, [0, two]) +const g8 = sum(g6, [0, two]) +const h8 = sum(h6, [0, two]) +const i8 = sum(i6, [0, two]) + +const d9 = sum(d6, [0, three]) +const e9 = sum(e6, [0, three]) +const f9 = sum(f6, [0, three]) +const g9 = sum(g6, [0, three]) +const h9 = sum(h6, [0, three]) + +const e10 = sum(e6, [0, four]) +const f10 = sum(f6, [0, four]) +const g10 = sum(g6, [0, four]) + +const f11 = sum(f6, [0, five]) + +/** board position data, sorted by fen order */ +export const board: [ + color: number, + origin: Vec<2>, + reflectedOrigin: Vec<2>, + path: Vec<6, Vec<2>>, + reflectedPath: Vec<6, Vec<2>> +][] = [ + [2, f11, reflect(f11), hexagon(f11), flip(hexagon(f11))], + [1, e10, reflect(e10), hexagon(e10), flip(hexagon(e10))], + [0, f10, reflect(f10), hexagon(f10), flip(hexagon(f10))], + [1, g10, reflect(g10), hexagon(g10), flip(hexagon(g10))], + [0, d9, reflect(d9), hexagon(d9), flip(hexagon(d9))], + [2, e9, reflect(e9), hexagon(e9), flip(hexagon(e9))], + [1, f9, reflect(f9), hexagon(f9), flip(hexagon(f9))], + [2, g9, reflect(g9), hexagon(g9), flip(hexagon(g9))], + [0, h9, reflect(h9), hexagon(h9), flip(hexagon(h9))], + [2, c8, reflect(c8), hexagon(c8), flip(hexagon(c8))], + [1, d8, reflect(d8), hexagon(d8), flip(hexagon(d8))], + [0, e8, reflect(e8), hexagon(e8), flip(hexagon(e8))], + [2, f8, reflect(f8), hexagon(f8), flip(hexagon(f8))], + [0, g8, reflect(g8), hexagon(g8), flip(hexagon(g8))], + [1, h8, reflect(h8), hexagon(h8), flip(hexagon(h8))], + [2, i8, reflect(i8), hexagon(i8), flip(hexagon(i8))], + [1, b7, reflect(b7), hexagon(b7), flip(hexagon(b7))], + [0, c7, reflect(c7), hexagon(c7), flip(hexagon(c7))], + [2, d7, reflect(d7), hexagon(d7), flip(hexagon(d7))], + [1, e7, reflect(e7), hexagon(e7), flip(hexagon(e7))], + [0, f7, reflect(f7), hexagon(f7), flip(hexagon(f7))], + [1, g7, reflect(g7), hexagon(g7), flip(hexagon(g7))], + [2, h7, reflect(h7), hexagon(h7), flip(hexagon(h7))], + [0, i7, reflect(i7), hexagon(i7), flip(hexagon(i7))], + [1, k7, reflect(k7), hexagon(k7), flip(hexagon(k7))], + [0, a6, reflect(a6), hexagon(a6), flip(hexagon(a6))], + [2, b6, reflect(b6), hexagon(b6), flip(hexagon(b6))], + [1, c6, reflect(c6), hexagon(c6), flip(hexagon(c6))], + [0, d6, reflect(d6), hexagon(d6), flip(hexagon(d6))], + [2, e6, reflect(e6), hexagon(e6), flip(hexagon(e6))], + [1, f6, reflect(f6), hexagon(f6), flip(hexagon(f6))], + [2, g6, reflect(g6), hexagon(g6), flip(hexagon(g6))], + [0, h6, reflect(h6), hexagon(h6), flip(hexagon(h6))], + [1, i6, reflect(i6), hexagon(i6), flip(hexagon(i6))], + [2, k6, reflect(k6), hexagon(k6), flip(hexagon(k6))], + [0, l6, reflect(l6), hexagon(l6), flip(hexagon(l6))], + [1, a5, reflect(a5), hexagon(a5), flip(hexagon(a5))], + [0, b5, reflect(b5), hexagon(b5), flip(hexagon(b5))], + [2, c5, reflect(c5), hexagon(c5), flip(hexagon(c5))], + [1, d5, reflect(d5), hexagon(d5), flip(hexagon(d5))], + [0, e5, reflect(e5), hexagon(e5), flip(hexagon(e5))], + [2, f5, reflect(f5), hexagon(f5), flip(hexagon(f5))], + [0, g5, reflect(g5), hexagon(g5), flip(hexagon(g5))], + [1, h5, reflect(h5), hexagon(h5), flip(hexagon(h5))], + [2, i5, reflect(i5), hexagon(i5), flip(hexagon(i5))], + [0, k5, reflect(k5), hexagon(k5), flip(hexagon(k5))], + [1, l5, reflect(l5), hexagon(l5), flip(hexagon(l5))], + [2, a4, reflect(a4), hexagon(a4), flip(hexagon(a4))], + [1, b4, reflect(b4), hexagon(b4), flip(hexagon(b4))], + [0, c4, reflect(c4), hexagon(c4), flip(hexagon(c4))], + [2, d4, reflect(d4), hexagon(d4), flip(hexagon(d4))], + [1, e4, reflect(e4), hexagon(e4), flip(hexagon(e4))], + [0, f4, reflect(f4), hexagon(f4), flip(hexagon(f4))], + [1, g4, reflect(g4), hexagon(g4), flip(hexagon(g4))], + [2, h4, reflect(h4), hexagon(h4), flip(hexagon(h4))], + [0, i4, reflect(i4), hexagon(i4), flip(hexagon(i4))], + [1, k4, reflect(k4), hexagon(k4), flip(hexagon(k4))], + [2, l4, reflect(l4), hexagon(l4), flip(hexagon(l4))], + [0, a3, reflect(a3), hexagon(a3), flip(hexagon(a3))], + [2, b3, reflect(b3), hexagon(b3), flip(hexagon(b3))], + [1, c3, reflect(c3), hexagon(c3), flip(hexagon(c3))], + [0, d3, reflect(d3), hexagon(d3), flip(hexagon(d3))], + [2, e3, reflect(e3), hexagon(e3), flip(hexagon(e3))], + [1, f3, reflect(f3), hexagon(f3), flip(hexagon(f3))], + [2, g3, reflect(g3), hexagon(g3), flip(hexagon(g3))], + [0, h3, reflect(h3), hexagon(h3), flip(hexagon(h3))], + [1, i3, reflect(i3), hexagon(i3), flip(hexagon(i3))], + [2, k3, reflect(k3), hexagon(k3), flip(hexagon(k3))], + [0, l3, reflect(l3), hexagon(l3), flip(hexagon(l3))], + [1, a2, reflect(a2), hexagon(a2), flip(hexagon(a2))], + [0, b2, reflect(b2), hexagon(b2), flip(hexagon(b2))], + [2, c2, reflect(c2), hexagon(c2), flip(hexagon(c2))], + [1, d2, reflect(d2), hexagon(d2), flip(hexagon(d2))], + [0, e2, reflect(e2), hexagon(e2), flip(hexagon(e2))], + [2, f2, reflect(f2), hexagon(f2), flip(hexagon(f2))], + [0, g2, reflect(g2), hexagon(g2), flip(hexagon(g2))], + [1, h2, reflect(h2), hexagon(h2), flip(hexagon(h2))], + [2, i2, reflect(i2), hexagon(i2), flip(hexagon(i2))], + [0, k2, reflect(k2), hexagon(k2), flip(hexagon(k2))], + [1, l2, reflect(l2), hexagon(l2), flip(hexagon(l2))], + [2, a1, reflect(a1), hexagon(a1), flip(hexagon(a1))], + [1, b1, reflect(b1), hexagon(b1), flip(hexagon(b1))], + [0, c1, reflect(c1), hexagon(c1), flip(hexagon(c1))], + [2, d1, reflect(d1), hexagon(d1), flip(hexagon(d1))], + [1, e1, reflect(e1), hexagon(e1), flip(hexagon(e1))], + [0, f1, reflect(f1), hexagon(f1), flip(hexagon(f1))], + [1, g1, reflect(g1), hexagon(g1), flip(hexagon(g1))], + [2, h1, reflect(h1), hexagon(h1), flip(hexagon(h1))], + [0, i1, reflect(i1), hexagon(i1), flip(hexagon(i1))], + [1, k1, reflect(k1), hexagon(k1), flip(hexagon(k1))], + [2, l1, reflect(l1), hexagon(l1), flip(hexagon(l1))], +] + +/** board labels */ +export const labels: [string, Vec<2>, Vec<2>][] = [ + ['11', bilerp(g10, f11, labelOffset), reflect(bilerp(g10, f11, labelOffset))], + ['10', bilerp(f10, e10, labelOffset), reflect(bilerp(f10, e10, labelOffset))], + ['9', bilerp(e9, d9, labelOffset), reflect(bilerp(e9, d9, labelOffset))], + ['8', bilerp(d8, c8, labelOffset), reflect(bilerp(d8, c8, labelOffset))], + ['7', bilerp(c7, b7, labelOffset), reflect(bilerp(c7, b7, labelOffset))], + ['6', bilerp(b6, a6, labelOffset), reflect(bilerp(b6, a6, labelOffset))], + ['5', bilerp(b5, a5, labelOffset), reflect(bilerp(b5, a5, labelOffset))], + ['4', bilerp(b4, a4, labelOffset), reflect(bilerp(b4, a4, labelOffset))], + ['3', bilerp(b3, a3, labelOffset), reflect(bilerp(b3, a3, labelOffset))], + ['2', bilerp(b2, a2, labelOffset), reflect(bilerp(b2, a2, labelOffset))], + ['1', bilerp(b1, a1, labelOffset), reflect(bilerp(b1, a1, labelOffset))], + + ['a', bilerp(a2, a1, labelOffset), reflect(bilerp(a2, a1, labelOffset))], + ['b', bilerp(b2, b1, labelOffset), reflect(bilerp(b2, b1, labelOffset))], + ['c', bilerp(c2, c1, labelOffset), reflect(bilerp(c2, c1, labelOffset))], + ['d', bilerp(d2, d1, labelOffset), reflect(bilerp(d2, d1, labelOffset))], + ['e', bilerp(e2, e1, labelOffset), reflect(bilerp(e2, e1, labelOffset))], + ['f', bilerp(f2, f1, labelOffset), reflect(bilerp(f2, f1, labelOffset))], + ['g', bilerp(g2, g1, labelOffset), reflect(bilerp(g2, g1, labelOffset))], + ['h', bilerp(h2, h1, labelOffset), reflect(bilerp(h2, h1, labelOffset))], + ['i', bilerp(i2, i1, labelOffset), reflect(bilerp(i2, i1, labelOffset))], + ['k', bilerp(k2, k1, labelOffset), reflect(bilerp(k2, k1, labelOffset))], + ['l', bilerp(l2, l1, labelOffset), reflect(bilerp(l2, l1, labelOffset))], + + ['1', bilerp(k1, l1, labelOffset), reflect(bilerp(k1, l1, labelOffset))], + ['2', bilerp(k2, l2, labelOffset), reflect(bilerp(k2, l2, labelOffset))], + ['3', bilerp(k3, l3, labelOffset), reflect(bilerp(k3, l3, labelOffset))], + ['4', bilerp(k4, l4, labelOffset), reflect(bilerp(k4, l4, labelOffset))], + ['5', bilerp(k5, l5, labelOffset), reflect(bilerp(k5, l5, labelOffset))], + ['6', bilerp(k6, l6, labelOffset), reflect(bilerp(k6, l6, labelOffset))], + ['7', bilerp(i7, k7, labelOffset), reflect(bilerp(i7, k7, labelOffset))], + ['8', bilerp(h8, i8, labelOffset), reflect(bilerp(h8, i8, labelOffset))], + ['9', bilerp(g9, h9, labelOffset), reflect(bilerp(g9, h9, labelOffset))], + ['10', bilerp(f10, g10, labelOffset), reflect(bilerp(f10, g10, labelOffset))], + ['11', bilerp(e10, f11, labelOffset), reflect(bilerp(e10, f11, labelOffset))], +] + +/** perimeter of the board */ +export const perimeter: Vec<93, Vec<2>> = [ + board[0 /* f11 */][3][5], + board[0 /* f11 */][3][0], + board[0 /* f11 */][3][1], + board[3 /* g10 */][3][5], + board[3 /* g10 */][3][0], + board[3 /* g10 */][3][1], + board[8 /* h9 */][3][5], + board[8 /* h9 */][3][0], + board[8 /* h9 */][3][1], + board[15 /* i8 */][3][5], + board[15 /* i8 */][3][0], + board[15 /* i8 */][3][1], + board[24 /* k7 */][3][5], + board[24 /* k7 */][3][0], + board[24 /* k7 */][3][1], + board[35 /* l6 */][3][5], + board[35 /* l6 */][3][0], + board[35 /* l6 */][3][1], + board[35 /* l6 */][3][2], + board[46 /* l5 */][3][0], + board[46 /* l5 */][3][1], + board[46 /* l5 */][3][2], + board[57 /* l4 */][3][0], + board[57 /* l4 */][3][1], + board[57 /* l4 */][3][2], + board[68 /* l3 */][3][0], + board[68 /* l3 */][3][1], + board[68 /* l3 */][3][2], + board[79 /* l2 */][3][0], + board[79 /* l2 */][3][1], + board[79 /* l2 */][3][2], + board[90 /* l1 */][3][0], + board[90 /* l1 */][3][1], + board[90 /* l1 */][3][2], + board[90 /* l1 */][3][3], + board[89 /* k1 */][3][2], + board[89 /* k1 */][3][3], + board[89 /* k1 */][3][4], + board[88 /* i1 */][3][1], + board[88 /* i1 */][3][2], + board[88 /* i1 */][3][3], + board[87 /* h1 */][3][1], + board[87 /* h1 */][3][2], + board[87 /* h1 */][3][3], + board[86 /* g1 */][3][1], + board[86 /* g1 */][3][2], + board[86 /* g1 */][3][3], + board[85 /* f1 */][3][1], + board[85 /* f1 */][3][2], + board[85 /* f1 */][3][3], + board[85 /* f1 */][3][4], + board[84 /* e1 */][3][3], + board[84 /* e1 */][3][4], + board[83 /* d1 */][3][2], + board[83 /* d1 */][3][3], + board[83 /* d1 */][3][4], + board[82 /* c1 */][3][2], + board[82 /* c1 */][3][3], + board[82 /* c1 */][3][4], + board[81 /* b1 */][3][2], + board[81 /* b1 */][3][3], + board[81 /* b1 */][3][4], + board[80 /* a1 */][3][2], + board[80 /* a1 */][3][3], + board[80 /* a1 */][3][4], + board[80 /* a1 */][3][5], + board[69 /* a2 */][3][3], + board[69 /* a2 */][3][4], + board[69 /* a2 */][3][5], + board[58 /* a3 */][3][3], + board[58 /* a3 */][3][4], + board[58 /* a3 */][3][5], + board[47 /* a4 */][3][3], + board[47 /* a4 */][3][4], + board[47 /* a4 */][3][5], + board[36 /* a5 */][3][3], + board[36 /* a5 */][3][4], + board[36 /* a5 */][3][5], + board[25 /* a6 */][3][3], + board[25 /* a6 */][3][4], + board[25 /* a6 */][3][5], + board[16 /* b7 */][3][4], + board[16 /* b7 */][3][5], + board[16 /* b7 */][3][0], + board[9 /* c8 */][3][4], + board[9 /* c8 */][3][5], + board[9 /* c8 */][3][0], + board[4 /* d9 */][3][4], + board[4 /* d9 */][3][5], + board[4 /* d9 */][3][0], + board[1 /* e10 */][3][4], + board[1 /* e10 */][3][5], + board[1 /* e10 */][3][0], +] diff --git a/ui/src/lib/hexboard/dom.ts b/ui/src/lib/hexboard/dom.ts new file mode 100644 index 00000000..c8d94fb3 --- /dev/null +++ b/ui/src/lib/hexboard/dom.ts @@ -0,0 +1,19 @@ +import { x, y } from './geometry' +import type { Vec } from './types' + +/** create svg vector path */ +export function d(arr: Vec<2>[]) { + if (arr.length === 0) { + return '' + } + + const [origin, ...points] = arr + + let path = `M ${x(origin[0])} ${y(origin[1])} L ` + + for (const point of points) { + path += `${x(point[0])} ${y(point[1])} ` + } + + return `${path} Z` +} diff --git a/docs/components/hexboard/geometry.ts b/ui/src/lib/hexboard/geometry.ts similarity index 81% rename from docs/components/hexboard/geometry.ts rename to ui/src/lib/hexboard/geometry.ts index d6ce5acf..e424ed86 100644 --- a/docs/components/hexboard/geometry.ts +++ b/ui/src/lib/hexboard/geometry.ts @@ -10,21 +10,6 @@ export function bilerp(p1: Vec<2>, p2: Vec<2>, t: number): Vec<2> { ] } -/** create svg vector path */ -export function d(arr: [number, number][]) { - const p = 4 // <- svg rounding precision - - const [origin, ...points] = arr - - let path = `M ${x(origin[0]).toFixed(p)} ${y(origin[1]).toFixed(p)} L ` - - for (const point of points) { - path += `${x(point[0]).toFixed(p)} ${y(point[1]).toFixed(p)} ` - } - - return `${path} Z` -} - /** reflect a path of points across the origin */ export function flip[]>(path: T) { return path.map(reflect) as T diff --git a/ui/src/lib/hexboard/types.ts b/ui/src/lib/hexboard/types.ts new file mode 100644 index 00000000..56995e99 --- /dev/null +++ b/ui/src/lib/hexboard/types.ts @@ -0,0 +1,26 @@ +/** hexboard options */ +export interface HexboardOptions { + /** position colors */ + colors: [string, string, string] + /** color of highlighted position */ + highlightColor: string + /** color of active label relative to mouseover */ + labelActiveColor: string + /** label color */ + labelColor: string + /** fill color of inactive label relative to mouseover */ + labelInactiveColor: string + /** show labels */ + labels: boolean + /** color of target circles */ + targetColor: string + /** color of selected position */ + selectedColor: string +} + +/** uniform tuple of length `T`, `number` by default */ +export type Vec< + T extends number, + U = number, + V extends unknown[] = [] +> = V['length'] extends T ? V : Vec diff --git a/ui/src/lib/index.ts b/ui/src/lib/index.ts new file mode 100644 index 00000000..05555af9 --- /dev/null +++ b/ui/src/lib/index.ts @@ -0,0 +1,40 @@ +export { default as AlphaPieces } from './pieces/AlphaPieces.vue' +export { default as AnarcandyPieces } from './pieces/AnarcandyPieces.vue' +export { default as CalientePieces } from './pieces/CalientePieces.vue' +export { default as CaliforniaPieces } from './pieces/CaliforniaPieces.vue' +export { default as CardinalPieces } from './pieces/CardinalPieces.vue' +export { default as CburnettPieces } from './pieces/CburnettPieces.vue' +// export { default as CelticPieces } from './pieces/CelticPieces.vue' +export { default as Chess7Pieces } from './pieces/Chess7Pieces.vue' +export { default as ChessnutPieces } from './pieces/ChessnutPieces.vue' +export { default as CompanionPieces } from './pieces/CompanionPieces.vue' +// export { default as CookePieces } from './pieces/CookePieces.vue' +export { default as DisguisedPieces } from './pieces/DisguisedPieces.vue' +export { default as DubrovnyPieces } from './pieces/DubrovnyPieces.vue' +// export { default as FantasyPieces } from './pieces/FantasyPieces.vue' +export { default as FiriPieces } from './pieces/FiriPieces.vue' +export { default as FrescaPieces } from './pieces/FrescaPieces.vue' +export { default as GiocoPieces } from './pieces/GiocoPieces.vue' +export { default as GovernorPieces } from './pieces/GovernorPieces.vue' +export { default as HorseyPieces } from './pieces/HorseyPieces.vue' +export { default as IcpiecesPieces } from './pieces/IcpiecesPieces.vue' +export { default as KiwenSuwiPieces } from './pieces/KiwenSuwiPieces.vue' +// export { default as KosalPieces } from './pieces/KosalPieces.vue' +export { default as LeipzigPieces } from './pieces/LeipzigPieces.vue' +export { default as LetterPieces } from './pieces/LetterPieces.vue' +export { default as MaestroPieces } from './pieces/MaestroPieces.vue' +export { default as MeridaPieces } from './pieces/MeridaPieces.vue' +export { default as MonarchyPieces } from './pieces/MonarchyPieces.vue' +export { default as MonoPieces } from './pieces/MonoPieces.vue' +export { default as MpchessPieces } from './pieces/MpchessPieces.vue' +export { default as PirouettiPieces } from './pieces/PirouettiPieces.vue' +export { default as PixelPieces } from './pieces/PixelPieces.vue' +export { default as ReillycraigPieces } from './pieces/ReillycraigPieces.vue' +// export { default as RhosgfxPieces } from './pieces/RhosgfxPieces.vue' +export { default as RiohachaPieces } from './pieces/RiohachaPieces.vue' +export { default as ShapesPieces } from './pieces/ShapesPieces.vue' +// export { default as SpatialPieces } from './pieces/SpatialPieces.vue' +export { default as StauntyPieces } from './pieces/StauntyPieces.vue' +export { default as TatianaPieces } from './pieces/TatianaPieces.vue' +export { default as XkcdPieces } from './pieces/XkcdPieces.vue' +export { default as Hexboard } from './hexboard/Hexboard.vue' diff --git a/ui/src/lib/pieces/AlphaPieces.vue b/ui/src/lib/pieces/AlphaPieces.vue new file mode 100644 index 00000000..64910086 --- /dev/null +++ b/ui/src/lib/pieces/AlphaPieces.vue @@ -0,0 +1,23 @@ + + + + diff --git a/ui/src/lib/pieces/AnarcandyPieces.vue b/ui/src/lib/pieces/AnarcandyPieces.vue new file mode 100644 index 00000000..51a0ecaf --- /dev/null +++ b/ui/src/lib/pieces/AnarcandyPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/CalientePieces.vue b/ui/src/lib/pieces/CalientePieces.vue new file mode 100644 index 00000000..b7864a24 --- /dev/null +++ b/ui/src/lib/pieces/CalientePieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/CaliforniaPieces.vue b/ui/src/lib/pieces/CaliforniaPieces.vue new file mode 100644 index 00000000..7744db7a --- /dev/null +++ b/ui/src/lib/pieces/CaliforniaPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/CardinalPieces.vue b/ui/src/lib/pieces/CardinalPieces.vue new file mode 100644 index 00000000..9f2817aa --- /dev/null +++ b/ui/src/lib/pieces/CardinalPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/CburnettPieces.vue b/ui/src/lib/pieces/CburnettPieces.vue new file mode 100644 index 00000000..dc5511ce --- /dev/null +++ b/ui/src/lib/pieces/CburnettPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/CelticPieces.vue b/ui/src/lib/pieces/CelticPieces.vue new file mode 100644 index 00000000..91b6834c --- /dev/null +++ b/ui/src/lib/pieces/CelticPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/Chess7Pieces.vue b/ui/src/lib/pieces/Chess7Pieces.vue new file mode 100644 index 00000000..d00a26c9 --- /dev/null +++ b/ui/src/lib/pieces/Chess7Pieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/ChessnutPieces.vue b/ui/src/lib/pieces/ChessnutPieces.vue new file mode 100644 index 00000000..d3e13734 --- /dev/null +++ b/ui/src/lib/pieces/ChessnutPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/CompanionPieces.vue b/ui/src/lib/pieces/CompanionPieces.vue new file mode 100644 index 00000000..7304e264 --- /dev/null +++ b/ui/src/lib/pieces/CompanionPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/CookePieces.vue b/ui/src/lib/pieces/CookePieces.vue new file mode 100644 index 00000000..9ea873d0 --- /dev/null +++ b/ui/src/lib/pieces/CookePieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/DisguisedPieces.vue b/ui/src/lib/pieces/DisguisedPieces.vue new file mode 100644 index 00000000..65a32aa6 --- /dev/null +++ b/ui/src/lib/pieces/DisguisedPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/DubrovnyPieces.vue b/ui/src/lib/pieces/DubrovnyPieces.vue new file mode 100644 index 00000000..710a9d25 --- /dev/null +++ b/ui/src/lib/pieces/DubrovnyPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/FantasyPieces.vue b/ui/src/lib/pieces/FantasyPieces.vue new file mode 100644 index 00000000..a5010b9b --- /dev/null +++ b/ui/src/lib/pieces/FantasyPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/FiriPieces.vue b/ui/src/lib/pieces/FiriPieces.vue new file mode 100644 index 00000000..df01107a --- /dev/null +++ b/ui/src/lib/pieces/FiriPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/FrescaPieces.vue b/ui/src/lib/pieces/FrescaPieces.vue new file mode 100644 index 00000000..21f00342 --- /dev/null +++ b/ui/src/lib/pieces/FrescaPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/GiocoPieces.vue b/ui/src/lib/pieces/GiocoPieces.vue new file mode 100644 index 00000000..c5652801 --- /dev/null +++ b/ui/src/lib/pieces/GiocoPieces.vue @@ -0,0 +1,22 @@ + + + diff --git a/ui/src/lib/pieces/GovernorPieces.vue b/ui/src/lib/pieces/GovernorPieces.vue new file mode 100644 index 00000000..36d76f2f --- /dev/null +++ b/ui/src/lib/pieces/GovernorPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/HorseyPieces.vue b/ui/src/lib/pieces/HorseyPieces.vue new file mode 100644 index 00000000..3d304baf --- /dev/null +++ b/ui/src/lib/pieces/HorseyPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/IcpiecesPieces.vue b/ui/src/lib/pieces/IcpiecesPieces.vue new file mode 100644 index 00000000..399e5b86 --- /dev/null +++ b/ui/src/lib/pieces/IcpiecesPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/KiwenSuwiPieces.vue b/ui/src/lib/pieces/KiwenSuwiPieces.vue new file mode 100644 index 00000000..7023ac58 --- /dev/null +++ b/ui/src/lib/pieces/KiwenSuwiPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/KosalPieces.vue b/ui/src/lib/pieces/KosalPieces.vue new file mode 100644 index 00000000..cf34721d --- /dev/null +++ b/ui/src/lib/pieces/KosalPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/LeipzigPieces.vue b/ui/src/lib/pieces/LeipzigPieces.vue new file mode 100644 index 00000000..7a64eb3d --- /dev/null +++ b/ui/src/lib/pieces/LeipzigPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/LetterPieces.vue b/ui/src/lib/pieces/LetterPieces.vue new file mode 100644 index 00000000..0f4a315d --- /dev/null +++ b/ui/src/lib/pieces/LetterPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/MaestroPieces.vue b/ui/src/lib/pieces/MaestroPieces.vue new file mode 100644 index 00000000..9e983728 --- /dev/null +++ b/ui/src/lib/pieces/MaestroPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/MeridaPieces.vue b/ui/src/lib/pieces/MeridaPieces.vue new file mode 100644 index 00000000..c493b26c --- /dev/null +++ b/ui/src/lib/pieces/MeridaPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/MonarchyPieces.vue b/ui/src/lib/pieces/MonarchyPieces.vue new file mode 100644 index 00000000..25672c4b --- /dev/null +++ b/ui/src/lib/pieces/MonarchyPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/MonoPieces.vue b/ui/src/lib/pieces/MonoPieces.vue new file mode 100644 index 00000000..65a32aa6 --- /dev/null +++ b/ui/src/lib/pieces/MonoPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/MpchessPieces.vue b/ui/src/lib/pieces/MpchessPieces.vue new file mode 100644 index 00000000..40a7e2f0 --- /dev/null +++ b/ui/src/lib/pieces/MpchessPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/PirouettiPieces.vue b/ui/src/lib/pieces/PirouettiPieces.vue new file mode 100644 index 00000000..ddd202f1 --- /dev/null +++ b/ui/src/lib/pieces/PirouettiPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/PixelPieces.vue b/ui/src/lib/pieces/PixelPieces.vue new file mode 100644 index 00000000..5fb045b3 --- /dev/null +++ b/ui/src/lib/pieces/PixelPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/ReillycraigPieces.vue b/ui/src/lib/pieces/ReillycraigPieces.vue new file mode 100644 index 00000000..17c795b5 --- /dev/null +++ b/ui/src/lib/pieces/ReillycraigPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/RhosgfxPieces.vue b/ui/src/lib/pieces/RhosgfxPieces.vue new file mode 100644 index 00000000..b1905763 --- /dev/null +++ b/ui/src/lib/pieces/RhosgfxPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/RiohachaPieces.vue b/ui/src/lib/pieces/RiohachaPieces.vue new file mode 100644 index 00000000..ea747e02 --- /dev/null +++ b/ui/src/lib/pieces/RiohachaPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/ShapesPieces.vue b/ui/src/lib/pieces/ShapesPieces.vue new file mode 100644 index 00000000..4d3c9471 --- /dev/null +++ b/ui/src/lib/pieces/ShapesPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/SpatialPieces.vue b/ui/src/lib/pieces/SpatialPieces.vue new file mode 100644 index 00000000..1d04fbe5 --- /dev/null +++ b/ui/src/lib/pieces/SpatialPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/StauntyPieces.vue b/ui/src/lib/pieces/StauntyPieces.vue new file mode 100644 index 00000000..47c19618 --- /dev/null +++ b/ui/src/lib/pieces/StauntyPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/TatianaPieces.vue b/ui/src/lib/pieces/TatianaPieces.vue new file mode 100644 index 00000000..4582c4fa --- /dev/null +++ b/ui/src/lib/pieces/TatianaPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/lib/pieces/XkcdPieces.vue b/ui/src/lib/pieces/XkcdPieces.vue new file mode 100644 index 00000000..91b46264 --- /dev/null +++ b/ui/src/lib/pieces/XkcdPieces.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/ui/src/style.css b/ui/src/style.css new file mode 100644 index 00000000..c68d0371 --- /dev/null +++ b/ui/src/style.css @@ -0,0 +1,7 @@ +@import "tailwindcss"; + +@media (prefers-color-scheme: dark) { + :root { + color-scheme: dark; + } +} diff --git a/ui/src/test/user-interaction.test.tsx b/ui/src/test/user-interaction.test.tsx new file mode 100644 index 00000000..3dedfa0d --- /dev/null +++ b/ui/src/test/user-interaction.test.tsx @@ -0,0 +1,730 @@ +/** @jsxImportSource vue */ +import { expect, test, vi } from 'vitest' +import { Hexboard } from '../lib' +import { Hexchess } from '@bedard/hexchess' +import { index, San } from '@bedard/hexchess' +import { movePiece, setup } from './utils' +import { page } from 'vitest/browser' +import { ref, nextTick } from 'vue' +import { userEvent } from 'vitest/browser' + +test('update mouseover position on hover', async () => { + const active = ref(false) + const mouseover = ref(-1) + + setup(() => { + return () => + }) + + await page.getByTestId('position-f6').hover() + await expect(mouseover.value).toBe(-1) + + active.value = true + await nextTick() + + await page.getByTestId('position-f1').hover() + await expect(mouseover.value).toBe(index('f1')) +}) + +test('calls handler on position click', async () => { + const active = ref(false) + const onClickPosition = vi.fn() + + setup(() => { + return () => + }) + + await page.getByTestId('position-f6').click() + await expect(onClickPosition).not.toHaveBeenCalled() + + active.value = true + await nextTick() + + await page.getByTestId('position-f6').click() + await expect(onClickPosition).toHaveBeenCalledOnce() + await expect(onClickPosition).toHaveBeenCalledWith(index('f6')) +}) + +test('flipped board', async () => { + const flipped = ref(false) + + setup(() => { + return () => + }) + + const { y: startY } = page + .getByTestId('position-f1') + .element() + .getBoundingClientRect() + + flipped.value = true + + await nextTick() + + const { y: endY } = page + .getByTestId('position-f1') + .element() + .getBoundingClientRect() + + await expect(startY).toBeGreaterThan(endY) // f1 starts at the bottom, then moves to top +}) + +test('custom colors', async () => { + setup(() => { + return () => + }) + + await page.getByTestId('position-a6').hover() + await expect(page.getByTestId('position-a6')).toHaveStyle({ fill: 'red' }) + + await page.getByTestId('position-b7').hover() + await expect(page.getByTestId('position-b7')).toHaveStyle({ fill: 'green' }) + + await page.getByTestId('position-c8').hover() + await expect(page.getByTestId('position-c8')).toHaveStyle({ fill: 'blue' }) +}) + +test('labels and label colors', async () => { + const active = ref(false) + const labels = ref(false) + + setup(() => { + return () => + }) + + // Labels only show when enabled + await expect.element(page.getByTestId('position-a1')).toBeInTheDocument() + await expect.element(page.getByTestId('label-a')).not.toBeInTheDocument() + labels.value = true + await nextTick() + await expect.element(page.getByTestId('label-a')).toBeVisible() + + // When no mouseover, all labels should have default labelColor (red) + await expect.element(page.getByTestId('label-a')).toHaveStyle({ fill: 'red' }) + await expect.element(page.getByTestId('label-b')).toHaveStyle({ fill: 'red' }) + await expect.element(page.getByTestId('label-c')).toHaveStyle({ fill: 'red' }) + + // No mouse events should be bound when inactive + await page.getByTestId('position-f6').hover() + await nextTick() + await expect.element(page.getByTestId('label-a')).toHaveStyle({ fill: 'red' }) + await expect.element(page.getByTestId('label-b')).toHaveStyle({ fill: 'red' }) + await expect.element(page.getByTestId('label-c')).toHaveStyle({ fill: 'red' }) + + // When hovering over f6, labels 'f' and '6' should be active (green) + active.value = true + await nextTick() + await page.getByTestId('position-f5').hover() + await expect.element(page.getByTestId('label-f')).toHaveStyle({ fill: 'green' }) + await expect.element(page.getByTestId('label-5').first()).toHaveStyle({ fill: 'green' }) + await expect.element(page.getByTestId('label-5').last()).toHaveStyle({ fill: 'green' }) + + // Other labels should be inactive (blue) + await expect.element(page.getByTestId('label-a')).toHaveStyle({ fill: 'blue' }) + await expect.element(page.getByTestId('label-1').first()).toHaveStyle({ fill: 'blue' }) + await expect.element(page.getByTestId('label-1').last()).toHaveStyle({ fill: 'blue' }) +}) + +test('targets array controls rendering of target circles', async () => { + const targets = ref([]) + + setup(() => { + return () => + }) + + await expect.element(page.getByTestId('target-a1')).not.toBeInTheDocument() + + targets.value = [index('a1')] + + await expect.element(page.getByTestId('target-a1')).toBeVisible() + await expect.element(page.getByTestId('target-a1')).toHaveStyle({ fill: 'red' }) +}) + +test('autoselect targets', async () => { + setup(() => { + return () => + }) + + await page.getByTestId('position-f5').click() + await expect.element(page.getByTestId('target-f6')).toBeVisible() +}) + +test('select options and logic', async () => { + const active = ref(false) + const hexchess = ref(Hexchess.init()) + const selected = ref(null) + + setup(() => { + return () => <> + + +
+ + }) + + // Initially, no selected path should be in the document + await expect.element(page.getByTestId('selected-f6')).not.toBeInTheDocument() + await expect.element(page.getByTestId('selected-a1')).not.toBeInTheDocument() + + // Clicking when inactive should not set selected + await page.getByTestId('position-a1').click() + await expect.element(page.getByTestId('assertion')).toHaveTextContent('') + + // // Activate the board + active.value = true + await nextTick() + + // Clicking when active should set selected + await page.getByTestId('position-f5').click() + await expect(selected.value).toBe(index('f5')) + await expect.element(page.getByTestId('selected-f5')).toBeVisible() + await expect.element(page.getByTestId('selected-f5')).toHaveStyle({ fill: 'red' }) + + // Clicking an unoccupied position should deselect + await page.getByTestId('position-a1').click() + await expect.element(page.getByTestId('assertion')).toBeEmptyDOMElement() + + // Escape should clear selected + await page.getByTestId('position-f5').click() + await expect.element(page.getByTestId('assertion')).toHaveTextContent(index('f5')) + userEvent.keyboard('{Escape}') + await expect.element(page.getByTestId('assertion')).toBeEmptyDOMElement() +}) + +test('highlight array controls rendering of highlight paths', async () => { + const highlight = ref([]) + + setup(() => { + return () => + }) + + // Initially, no highlight paths should be in the document + await expect.element(page.getByTestId('highlight-f6')).not.toBeInTheDocument() + await expect.element(page.getByTestId('highlight-a1')).not.toBeInTheDocument() + + // Setting highlight to a single position should render one path + highlight.value = [index('f6')] + await nextTick() + await expect.element(page.getByTestId('highlight-f6')).toBeVisible() + await expect.element(page.getByTestId('highlight-f6')).toHaveStyle({ fill: 'pink' }) + await expect.element(page.getByTestId('highlight-a1')).not.toBeInTheDocument() + + // Setting highlight to multiple positions should render multiple paths + highlight.value = [index('f6'), index('a1')] + await nextTick() + await expect.element(page.getByTestId('highlight-f6')).toBeVisible() + await expect.element(page.getByTestId('highlight-f6')).toHaveStyle({ fill: 'pink' }) + await expect.element(page.getByTestId('highlight-a1')).toBeVisible() + await expect.element(page.getByTestId('highlight-a1')).toHaveStyle({ fill: 'pink' }) + + // Clearing highlight should remove all paths + highlight.value = [] + await nextTick() + await expect.element(page.getByTestId('highlight-f6')).not.toBeInTheDocument() + await expect.element(page.getByTestId('highlight-a1')).not.toBeInTheDocument() +}) + +test('cursor shows grab for playable pieces', async () => { + setup(() => { + return () => + }) + + const svg = page.getByTestId('position-f5').element().closest('svg') as SVGElement + + // Hover over a white piece (initial position has white to move) + // f5 should have a white piece in initial position + await page.getByTestId('position-f5').hover() + await nextTick() + await expect(svg).toHaveStyle({ cursor: 'grab' }) // White's turn, so grab +}) + +test('cursor shows grab only when user can drag piece', async () => { + setup(() => { + return () => + }) + + // Hover over a white piece when it's white's turn and user is playing white + // Should show "grab" cursor + await page.getByTestId('position-f5').hover() + const svg = page.getByTestId('position-f5').element().closest('svg') as SVGElement + await expect(svg).toHaveStyle({ cursor: 'grab' }) + + // Hover over an empty position when user is only playing white + // Should show "auto" cursor (no piece to interact with) + await page.getByTestId('position-a6').hover() + await expect(svg).toHaveStyle({ cursor: 'auto' }) + + // Hover over a black piece when user is only playing white + // Should show "pointer" cursor (can't drag black piece as white) + await page.getByTestId('position-f7').hover() + await expect(svg).toHaveStyle({ cursor: 'pointer' }) +}) + +test('cursor behavior when playing both colors', async () => { + setup(() => { + return () => + }) + + const svg = page.getByTestId('position-f5').element().closest('svg') as SVGElement + + // Initial position is white's turn, so white pieces show "grab" + await page.getByTestId('position-f5').hover() + await expect(svg).toHaveStyle({ cursor: 'grab' }) + + // Black pieces show "pointer" because it's not black's turn + await page.getByTestId('position-b7').hover() + await expect(svg).toHaveStyle({ cursor: 'grab' }) + + // Empty positions show "auto" because there's no piece + await page.getByTestId('position-a6').hover() + await expect(svg).toHaveStyle({ cursor: 'auto' }) +}) + +test('cursor shows pointer when user is not playing', async () => { + setup(() => { + return () => + }) + + // When playing is false, should show pointer for any piece + await page.getByTestId('position-f5').hover() + const svg = page.getByTestId('position-f5').element().closest('svg') as SVGElement + await expect(svg).toHaveStyle({ cursor: 'pointer' }) + + await page.getByTestId('position-f7').hover() + await expect(svg).toHaveStyle({ cursor: 'pointer' }) +}) + +test('pieces of any color can be selected, but only playing color is draggable', async () => { + const selected = ref(null) + const targets = ref([]) + + setup(() => { + return () => <> + +
+ + }) + + const svg = page.getByTestId('position-f5').element().closest('svg') as SVGElement + + // White piece shows grab cursor (user can drag) + const whitePiecePosition = page.getByTestId('position-f5') + await whitePiecePosition.hover() + await expect(svg).toHaveStyle({ cursor: 'grab' }) + + // Black piece shows pointer cursor (user cannot drag) + const blackPiecePosition = page.getByTestId('position-b7') + await blackPiecePosition.hover() + await expect(svg).toHaveStyle({ cursor: 'pointer' }) + + // Both pieces can be selected + await whitePiecePosition.click() + await expect.element(page.getByTestId('selected-f5')).toBeVisible() + await expect.element(page.getByTestId('assertion')).toHaveTextContent(index('f5')) + + await blackPiecePosition.click() + await expect.element(page.getByTestId('selected-b7')).toBeVisible() + await expect.element(page.getByTestId('assertion')).toHaveTextContent(index('b7')) +}) + +test('dragging piece off board results in selection only, dragging state resets', async () => { + const selected = ref(null) + const targets = ref([]) + + setup(() => { + return () => <> + +
+ + }) + + const whitePiecePosition = page.getByTestId('position-f5') + + // Start dragging the piece (pointerdown) + await whitePiecePosition.element().dispatchEvent(new PointerEvent('pointerdown', { bubbles: true })) + await nextTick() + + // Verify dragging started - draggable piece SVG should be visible + await expect.element(page.getByTestId('drag-piece')).toBeVisible() + + // Move pointer off the board (simulate pointermove on window) + window.dispatchEvent(new PointerEvent('pointermove', { clientX: 0, clientY: 0, bubbles: true })) + await nextTick() + + // Release pointer (pointerup on window) - this should reset dragging state + window.dispatchEvent(new PointerEvent('pointerup', { bubbles: true })) + await nextTick() + + // Verify piece is still selected (autoselect should have set it on mousedown) + await expect.element(page.getByTestId('selected-f5')).toBeVisible() + await expect.element(page.getByTestId('assertion')).toHaveTextContent(index('f5')) + + // Verify dragging state is reset - draggable piece SVG should no longer exist + await expect.element(page.getByTestId('drag-piece')).not.toBeInTheDocument() +}) + +test('drag and drop piece emits move event', async () => { + const selected = ref(null) + const targets = ref([]) + const onMove = vi.fn() + + setup(() => { + return () => <> + + + }) + + const fromPosition = page.getByTestId('position-f5') + const toPosition = page.getByTestId('position-f6') + + // Start dragging the piece (pointerdown) + await fromPosition.element().dispatchEvent(new PointerEvent('pointerdown', { bubbles: true })) + await nextTick() + + // Verify dragging started + await expect.element(page.getByTestId('drag-piece')).toBeVisible() + + // Move pointer to target position and release (pointerup) + await toPosition.element().dispatchEvent(new PointerEvent('pointerup', { bubbles: true })) + await nextTick() + + // Verify move event was emitted with correct San object + await expect(onMove).toHaveBeenCalledOnce() + await expect(onMove).toHaveBeenCalledWith(expect.any(San)) + const san = onMove.mock.calls[0][0] as San + await expect(san.from).toBe(index('f5')) + await expect(san.to).toBe(index('f6')) +}) + +test('click to move piece emits move event', async () => { + const selected = ref(null) + const targets = ref([]) + const onMove = vi.fn() + + setup(() => { + return () => <> + + + }) + + // Click on a piece to select it (f5 should have a white piece) + await page.getByTestId('position-f5').click() + await nextTick() + + // Verify piece is selected and targets are shown + await expect.element(page.getByTestId('selected-f5')).toBeVisible() + await expect.element(page.getByTestId('target-f6')).toBeVisible() + + // Click on a target position to move + await page.getByTestId('position-f6').click() + await nextTick() + + // Verify move event was emitted with correct San object + await expect(onMove).toHaveBeenCalledOnce() + await expect(onMove).toHaveBeenCalledWith(expect.any(San)) + const san = onMove.mock.calls[0][0] as San + await expect(san.from).toBe(index('f5')) + await expect(san.to).toBe(index('f6')) +}) + +test('cannot move piece of other turn color', async () => { + const selected = ref(null) + const targets = ref([]) + const onMove = vi.fn() + + setup(() => { + return () => <> + + + }) + + // Initial position is white's turn, so try to move a black piece + // Click on a black piece (b7 should have a black rook in initial position) + await page.getByTestId('position-b7').click() + await nextTick() + + // Verify piece is selected and targets are shown + await expect.element(page.getByTestId('selected-b7')).toBeVisible() + await expect.element(page.getByTestId('target-b6')).toBeVisible() + + // Try to click on a target position to move + await page.getByTestId('position-b6').click() + await nextTick() + + // Verify move event was NOT called (can't move black piece on white's turn) + await expect(onMove).not.toHaveBeenCalled() +}) + +test('promotion', async () => { + setup(() => { + const hexchess = ref(Hexchess.parse('1/1P1/5/7/9/11/11/11/11/11/11 w - 0 1')) + + return () => <> + hexchess.value.applyMoveUnsafe(san)}>{{ + promotion: ({ promote }: any) => + }} + + }) + + await movePiece(page, 'f10', 'f11') + await page.getByTestId('promote').click() + await expect(page.getByTestId('piece-f11')).toHaveAttribute('data-piece-type', 'Q') +}) + +test('canceled promotion', async () => { + const selected = ref(null) + const onMove = vi.fn() + + setup(() => { + const hexchess = ref(Hexchess.parse('1/1P1/5/7/9/11/11/11/11/11/11 w - 0 1')) + + return () => <> + {{ + promotion: ({ cancel }: any) => + }} + + }) + + // Move pawn to promotion square + await movePiece(page, 'f10', 'f11') + + // Promotion UI should be visible + await expect.element(page.getByTestId('cancel')).toBeVisible() + + // Cancel the promotion + await page.getByTestId('cancel').click() + await nextTick() + + // Move should not have been emitted + await expect(onMove).not.toHaveBeenCalled() + + // Original piece should still be selected + await expect(selected.value).toBe(index('f10')) + await expect.element(page.getByTestId('selected-f10')).toBeVisible() + + // Pawn should still be at original position + await expect.element(page.getByTestId('piece-f10')).toHaveAttribute('data-piece-type', 'P') +}) + +test('clicking position during promotion cancels it', async () => { + const selected = ref(null) + const onMove = vi.fn() + + setup(() => { + const hexchess = ref(Hexchess.parse('1/1P1/5/7/9/11/11/11/11/11/11 w - 0 1')) + + return () => <> + {{ + promotion: () => + }} + + }) + + // Move pawn to promotion square + await movePiece(page, 'f10', 'f11') + + // Promotion UI should be visible + await expect.element(page.getByTestId('promote')).toBeVisible() + + // Click on a different position to cancel the promotion + await page.getByTestId('position-a1').click() + await nextTick() + + // Promotion UI should be gone + await expect.element(page.getByTestId('promote')).not.toBeInTheDocument() + + // Move should not have been emitted + await expect(onMove).not.toHaveBeenCalled() + + // Original piece should still be selected + await expect.element(page.getByTestId('selected-f10')).toBeVisible() + + // Pawn should still be at original position + await expect.element(page.getByTestId('piece-f10')).toHaveAttribute('data-piece-type', 'P') +}) + +test('ignoreTurn allows moving pieces out of turn', async () => { + const ignoreTurn = ref(false) + const onMove = vi.fn() + + setup(() => { + return () => + }) + + // Initial position is white's turn, try to move black's pawn without ignoreTurn + await movePiece(page, 'f7', 'f6') + await expect(onMove).not.toHaveBeenCalled() + + // Enable ignoreTurn and try again + ignoreTurn.value = true + await nextTick() + + await movePiece(page, 'f7', 'f6') + await expect(onMove).toHaveBeenCalledOnce() + await expect(onMove).toHaveBeenCalledWith(expect.objectContaining({ + from: index('f7'), + to: index('f6'), + })) +}) + +test('dragging piece to non-target position keeps selection', async () => { + const selected = ref(null) + const targets = ref([]) + + setup(() => { + return () => <> + +
+ + }) + + const piecePosition = page.getByTestId('position-f5') + const nonTargetPosition = page.getByTestId('position-a1') + + // Start dragging the piece + await piecePosition.element().dispatchEvent(new PointerEvent('pointerdown', { bubbles: true })) + await nextTick() + + // Verify piece is selected + await expect.element(page.getByTestId('selected-f5')).toBeVisible() + await expect(selected.value).toBe(index('f5')) + + // Release on a non-target position + await nonTargetPosition.element().dispatchEvent(new PointerEvent('pointerup', { bubbles: true })) + await nextTick() + + // Piece should still be selected + await expect.element(page.getByTestId('selected-f5')).toBeVisible() + await expect(selected.value).toBe(index('f5')) +}) + diff --git a/ui/src/test/utils.ts b/ui/src/test/utils.ts new file mode 100644 index 00000000..4a11303c --- /dev/null +++ b/ui/src/test/utils.ts @@ -0,0 +1,28 @@ +import { nextTick } from 'vue' +import type { BrowserPage } from 'vitest/browser' +import { render } from 'vitest-browser-vue' + +/** + * Moves a piece by clicking on the source position and then the target position. + * This simulates the click-to-move interaction pattern. + */ +export async function movePiece(page: BrowserPage, from: string | number, to: string | number): Promise { + const fromTestId = typeof from === 'string' ? `position-${from}` : `position-${from}` + const toTestId = typeof to === 'string' ? `position-${to}` : `position-${to}` + + // Click on the source position to select it + await page.getByTestId(fromTestId).click() + await nextTick() + + // Click on the target position to move + await page.getByTestId(toTestId).click() + await nextTick() +} + +/** + * Sets up a Vue component for testing in the browser. + */ +export function setup(setup: () => any): ReturnType { + return render({ setup }) +} + diff --git a/ui/tsconfig.json b/ui/tsconfig.json new file mode 100644 index 00000000..93692084 --- /dev/null +++ b/ui/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "declaration": true, + "declarationMap": true, + "outDir": "dist", + "rootDir": "src", + "jsx": "preserve" + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} + diff --git a/ui/tsconfig.node.json b/ui/tsconfig.node.json new file mode 100644 index 00000000..e428d506 --- /dev/null +++ b/ui/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} + diff --git a/ui/vite.config.ts b/ui/vite.config.ts new file mode 100644 index 00000000..b79fad2c --- /dev/null +++ b/ui/vite.config.ts @@ -0,0 +1,36 @@ +import { defineConfig } from 'vite' +import { fileURLToPath } from 'node:url' +import { resolve } from 'node:path' +import dts from 'vite-plugin-dts' +import tailwindcss from '@tailwindcss/vite' +import vue from '@vitejs/plugin-vue' + +const __dirname = fileURLToPath(new URL('.', import.meta.url)) + +export default defineConfig({ + plugins: [ + dts({ + include: ['src/**/*'], + outDir: 'dist', + rollupTypes: true + }), + tailwindcss(), + vue(), + ], + build: { + lib: { + entry: resolve(__dirname, 'src/lib/index.ts'), + name: '@bedard/hexchess-ui', + fileName: 'index', + formats: ['es'] + }, + rollupOptions: { + external: ['@bedard/hexchess', 'vue'] + } + }, + server: { + port: 3000, + open: true + } +}) + diff --git a/ui/vitest.config.ts b/ui/vitest.config.ts new file mode 100644 index 00000000..dde815e6 --- /dev/null +++ b/ui/vitest.config.ts @@ -0,0 +1,37 @@ +import { defineConfig } from 'vitest/config' +import { fileURLToPath } from 'node:url' +import { playwright } from '@vitest/browser-playwright' +import { resolve } from 'node:path' +import tailwindcss from '@tailwindcss/vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' + +const __dirname = fileURLToPath(new URL('.', import.meta.url)) + +// Get browser from environment variable, default to chromium +const browser = (process.env.VITEST_BROWSER || 'chromium') as 'chromium' | 'firefox' | 'webkit' + +export default defineConfig({ + plugins: [ + tailwindcss(), + vue(), + vueJsx(), + ], + test: { + browser: { + provider: playwright(), + enabled: true, + instances: [ + { browser }, + ], + }, + environment: 'jsdom', + testTimeout: 3000, + }, + resolve: { + alias: { + '@': resolve(__dirname, './src'), + }, + }, +}) +