Initialize Black 7 PWA with React, Vite, and app icons

Sets up Vite + React + TypeScript with vite-plugin-pwa for offline support.
Adds app icon (line-drawn 7 on black) in all required sizes with a generation script.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-26 19:30:46 -05:00
commit a09e8dac7f
16 changed files with 6430 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
/tmp
/out-tsc
/dist
/node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*
/.pnp
.pnp.js
.vscode/*
+10
View File
@@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Ignored default folder with query files
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
+36
View File
@@ -0,0 +1,36 @@
# Black 7
Scoring app for the card game **Last Panther**, hosted at black7.org.
## What this is
A mobile-first progressive web app (PWA) built with React and TypeScript. No backend — everything runs in the browser. Players use it to track scores during a game of Last Panther.
## Tech stack
- React (UI)
- TypeScript (strict mode)
- PWA (installable, offline-capable)
- No server, no API — browser-only
## Key constraints
- **Mobile-first**: design and test for phone-sized screens first; desktop is secondary
- **No backend**: all state lives in the browser (localStorage, sessionStorage, or in-memory)
- **Installable**: must meet PWA requirements (manifest, service worker, HTTPS)
## Running the app
```
npm install
npm run dev # start dev server
npm run build # production build
```
## Project conventions
- Components live in `src/components/`
- Game logic (scoring rules, state) lives in `src/game/`
- Types shared across the app live in `src/types.ts`
- Prefer small, focused components; keep game logic out of UI components
- No unnecessary comments — name things clearly instead
+15
View File
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#000000" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<title>Black 7</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+6240
View File
File diff suppressed because it is too large Load Diff
+24
View File
@@ -0,0 +1,24 @@
{
"name": "black7",
"version": "1.0.0",
"description": "Scoring app for Last Panther card game",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^19.2.6",
"react-dom": "^19.2.6"
},
"devDependencies": {
"@types/react": "^19.2.15",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.2",
"typescript": "^5.9.3",
"vite": "^8.0.14",
"vite-plugin-pwa": "^1.3.0"
},
"private": true
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+9
View File
@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<rect width="512" height="512" fill="#0a0a0a"/>
<g fill="none" stroke="#f0ede6" stroke-linecap="round" stroke-linejoin="round">
<!-- horizontal top bar + diagonal leg of the 7 -->
<path d="M 90 120 L 408 112 L 206 450" stroke-width="30"/>
<!-- European crossbar -->
<path d="M 248 268 L 354 262" stroke-width="23"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 413 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/../public"
rsvg-convert -w 512 -h 512 icon.svg -o pwa-512x512.png
rsvg-convert -w 192 -h 192 icon.svg -o pwa-192x192.png
rsvg-convert -w 180 -h 180 icon.svg -o apple-touch-icon.png
rsvg-convert -w 32 -h 32 icon.svg -o favicon-32.png
magick favicon-32.png favicon.ico
rm favicon-32.png
echo "Icons generated."
+7
View File
@@ -0,0 +1,7 @@
export default function App() {
return (
<div>
<h1>Black 7</h1>
</div>
)
}
+9
View File
@@ -0,0 +1,9 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>
)
+16
View File
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"noEmit": true
},
"include": ["src"]
}
+39
View File
@@ -0,0 +1,39 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { VitePWA } from 'vite-plugin-pwa'
export default defineConfig({
plugins: [
react(),
VitePWA({
registerType: 'autoUpdate',
includeAssets: ['favicon.ico', 'apple-touch-icon.png'],
manifest: {
name: 'Black 7',
short_name: 'Black 7',
description: 'Scoring app for Last Panther',
theme_color: '#000000',
background_color: '#000000',
display: 'standalone',
icons: [
{
src: 'pwa-192x192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: 'pwa-512x512.png',
sizes: '512x512',
type: 'image/png',
},
{
src: 'pwa-512x512.png',
sizes: '512x512',
type: 'image/png',
purpose: 'any maskable',
},
],
},
}),
],
})