尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
React Dashboard - Pokemon Card Collection Manager
Overview
A React-based web application for managing and visualizing a Pokemon Trading Card Game (TCG) collection. The dashboard allows users to browse, filter, sort, and organize cards from their personal collection with pricing information and collection details.檢視完整 README ↗A React-based web application for managing and visualizing a Pokemon Trading Card Game (TCG) collection. The dashboard allows users to browse, filter, sort, and organize cards from their personal collection with pricing information and collection details.
src/
├── components/ # React components
│ ├── inputs/ # Filter, search, and input components
│ ├── views/ # Card list and table views
│ └── ui/ # Sidebar and summary components
├── context/ # React context for state management
│ ├── CardContext.tsx # Card state management
│ └── OptionsContext.tsx # UI options state
├── hooks/ # Custom React hooks
│ └── useLoadCards.ts # Load and process card data
├── services/ # External data services
│ ├── collector.ts # Load personal collection data
│ └── tcg-data.ts # Load TCG card and set data
├── types/ # TypeScript interfaces
├── utils/ # Utility functions
├── constants/ # Application constants
└── scripts/ # Data processing scripts
npm install
npm run dev
Starts the Vite dev server at http://localhost:5173
npm run build
Compiles TypeScript and creates optimized production build
npm lint
Checks code quality with ESLint
Cards are generated in the browser after selecting a series. The runtime flow combines:
seriesId.productID and shared by every series.The browser requests the relevant TCGPlayer catalog from
/api/prices?seriesId=<id> and
persists it in the Cache API for at most 24 hours. Prices are indexed directly by
productID, so loading a series does not require mapping Poke DB sets to
TCGPlayer sets. Each product entry also retains TCGPlayer's exact productName,
collector number, and price-guide setAbbrv. Mass Entry uses TCGPlayer's
canonical format 1 Hisuian Zoroark VSTAR [SWSH11] 147/196 for every series.
In production, /api/prices is a Vercel Function. It makes the cross-origin
TCGPlayer requests server-side, keeps only products used by the requested series,
and caches each series response in Vercel's CDN for 24 hours. The filtering keeps
responses below Vercel's Function payload limit. The first request after
expiration refreshes the catalog; no generated price file or scheduled
deployment is required.
Poke DB browser requests use the same-origin /api/poke-db/* reverse proxy.
Vercel forwards that route to the Poke DB deployment, avoiding browser CORS
restrictions. Vite exposes the same route during local development.
Generated series are cached in browser memory for 15 minutes. During local
development, Vite serves the same /api/prices route with an in-memory 24-hour
cache.
Optional environment variables:
$env:VITE_POKE_DB_API_BASE_URL = "https://example.com/api"
Supabase configuration (required only for the private Collections feature):
$env:VITE_SUPABASE_URL = "https://your-project-ref.supabase.co"
$env:VITE_SUPABASE_PUBLISHABLE_KEY = "sb_publishable_your_key"
The public catalog, prices, search, and non-collection filters work without a session. Opening Collections prompts the user to sign in with Google through the Supabase-hosted OAuth flow; Row Level Security then limits inventory queries to the signed-in owner. Use only the browser-safe publishable key. Never put a service-role key, database password, or connection string in this app.
Open Menú → Escanear cartas (/scanner). The scanner searches the catalog's
current results (eras, sets, filters, search) and lists what it inherited; change
them in the catalog. Choose the batch printing ("Normal" also covers sets that call
their regular print "Unlimited"), condition and destination, start the video and
hold each card inside the guide until it says Enviada a revisión.
Cards are identified by their image. A pretrained model (DINOv2-small, about 24 MB,
downloaded once from Hugging Face and cached by the browser) turns the camera crop
into a vector that is compared with precomputed vectors of the catalog images in
public/card-vectors/v1/. Same-art reprints (Base Set, Base Set 2, Legendary
Collection) look alike, so Tesseract.js only reads the collector number to choose
among them and among other cards with the same name. Series without vectors fall
back to OCR of the name and number. Card images live on other domains that do not
allow pages to read their pixels, so the vectors are made with
node scripts/build-card-vectors.mjs Base Gym (any series names); re-run it when
a series gains sets. Nothing is trained and no image is stored.
Two stable reads create a draft; no collection mutation runs during capture. A clear match preselects the card, a lone candidate is selected automatically, and ambiguous readings require choosing among candidates.
Drafts are backed up in localStorage separately per account and for guests. Pause the video to correct the card/printing, condition and destination, select reviewed entries, and explicitly confirm their addition to the collection. Restoring the page clears review selections, preserving the drafts. Guest drafts can be imported explicitly after signing in. A malformed backup is reported without overwriting it.
On phones and tablets up to 900px wide, the scanner has separate Catálogo, Escanear, and Revisar panes. Starting a scan brings the camera and its controls into view; opening another pane pauses the video. The preview scales with the available screen height, including a compact horizontal layout and safe-area spacing for installed PWAs. Review cards show a compact summary with expandable editing and a save bar above the app navigation. Touch controls are at least 44px high and form text stays at 16px to avoid mobile input zoom.
A held card is latched even if a subsequent frame is blurry. A different card releases that latch; for identical consecutive copies, press +1 copia de la última or set the quantity in review. Pausing and resuming keeps the latch. Each copy has a stable UUID so retrying a confirmed save after a lost response cannot insert it twice. Successful saves leave the review queue; failed ones stay available for retry. Collection writes use the existing authenticated Supabase client and require a connection. No card photos leave the device. Leaving the app pauses the video and releases its camera tracks.
predev and prebuild run scripts/prepare-ocr.mjs to copy the pinned worker,
WASM cores and language data from npm into generated public/ocr/v7/ assets.
They are served by the app's own origin and downloaded only when scanning starts.
The production service worker caches the requested OCR assets; this does not
make collection writes work offline. No schema migration or recognition server
is required. The camera opens the main rear lens at 1x when the phone exposes it.
For a browser smoke test without a camera or live database writes, run npm run dev
and open /tests/scanner-browser.html. It checks real browser OCR against a
synthetic card and runs the stable-read/duplicate gate into a review queue with zero collection writes.
Physical-card recognition accuracy and latency still need validation on target phones.
/tests/scanner-video.html runs the actual scanner UI with a synthetic video feed,
isolated temporary storage and no signed-in session. Its button swaps Pikachu for
Charmander to verify continuous capture, the held-card latch and review-only staging.
/tests/scanner-responsive.html wraps that isolated test in phone, landscape,
tablet and desktop viewports. Medir diseño reports horizontal overflow and
undersized touch targets without using the physical camera or a signed-in account.
public/manifest.webmanifest plus the service worker make the dashboard installable, so
it can live on a home screen and open without browser chrome. Installation needs HTTPS
(or localhost) and the production build - the worker only caches the app shell when
registered with shell=1, which npm run dev does not do.
An installed app always launches at the manifest's start_url, which would drop the query
string the whole dashboard state lives in. src/services/launchUrl.ts remembers the last
URL and restores it on an installed launch, so closing and reopening returns you to the
same filters, sort and view. A launch that carries its own parameters - a shared link -
always wins, and a plain browser tab is left alone so typing the bare address still gives
a clean slate.
Icons are generated, not hand-drawn: node scripts/generate-icons.mjs rewrites
public/icons/. The output is committed, so it only needs running if the mark changes.
The dashboard now consumes the shared Collectr Supabase schema directly. Inventory reads
active card_copies, catalog data from collectr_cards, current collections from
collectr_collections, and collection-level printing labels from collection_printings.
The dashboard never calls the extension's private sync RPCs.
The shared schema and all of its RLS policies live in the collectr-extension repository
(extension/supabase/schema.sql, documented by its collectr-supabase skill); this app
adds no policies or schema migrations of its own.
Run the production build locally:
npm run build
Run the development server (including the local prices endpoint):
npm run dev
Import this repository in Vercel. The committed vercel.json selects Vite,
builds with npm run build, publishes dist, configures SPA fallback routing,
and gives the prices function enough time for a cold-cache refresh. Configure
VITE_SUPABASE_URL and VITE_SUPABASE_PUBLISHABLE_KEY in every Vercel
environment where Collections should be available. The public application can
still run without them; the Poke DB URL remains optional.
Vite inlines import.meta.env at build time, so changing an environment
variable only takes effect after a redeploy - the published bundle otherwise
keeps the value it was built with. A wrong VITE_SUPABASE_URL shows up as
ERR_NAME_NOT_RESOLVED on every auth request, including email and password,
since the browser never resolves the host.
Signing in also needs the deployed origin on Supabase's side, under
Authentication - URL Configuration: both sign-in and the password-reset link
send window.location.origin as their redirectTo, and Supabase refuses any
origin it has not been given. Add the preview wildcard too if sign-in should
work on preview deployments.
The SPA fallback is what serves /c/<id>, the read-only page for a collection
shared with is_public. It is the one route that renders without a session.
/api/prices with 24-hour Vercel CDN and browser cachescard_copies, cards, collections, collection_tags, and collection_printings behind Supabase Auth and RLSpublic/data/fix/Load Phase:
useLoadCards derives sets from the hierarchy API and fetches cards only for selected seriesProcessing Phase:
Display Phase:
CardContext for global stateEach card includes:
Manages all card-related data through multiple states:
allCards: Complete card datasetfilteredCards: After filter applicationsortedCards: After sortingcollectionCards: After collection filteringvisibleCards: Search resultsvariantsFilter / conditionsFilter: Active filter selectionsManages UI options and settings like table/list view toggle and collection selections
utils/filters.tscomponents/inputs/Filters componentCard type in types/dashboard.ts11-13-25)cel25c set requires custom numbering)Create a collection and subcollection in the local collections panel. The bulk add button saves all actual cards in the rendered result, including shadowed cards and results beyond the scroll viewport, after search and all filters. Individual add/remove buttons appear with a selected subcollection in every view. Placeholders are excluded and duplicate additions are ignored.
When signed in, wishlists and their subcollections are persisted as collections with a
wish tag, and their wanted cards are represented by active card_copies keyed by
product_id. Signed-out lists still use the browser backup under
pokedashboard.local-collections.v1; that backup is retained when it lacks the product
identity required by the new schema. Opening a saved list loads its members independently
of catalog filters. Select all subcollections or one, search within it, and use any
existing print format.