Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 64 additions & 132 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,167 +1,99 @@
# Keep7

Testing a 100-card Magic deck on a phone is a pain.
MTG deck testing tool for opening hands, early turns, and deck comparison.

Most tools feel cramped, slow, or built for desktop first. Keep7 fixes that.
Paste a decklist. Draw hands. Goldfish turns 1–5. Compare two decks side by side. Works on mobile.

Paste your list. Tap through opening hands. Goldfish turns. Compare two decks. Do it fast on mobile.
**[→ Try it at keep7.vercel.app](https://keep7.vercel.app)**

## 60-Second Recruiter Scan
---

**What it is:**
Mobile-first MTG deck testing app for opening-hand consistency and early-turn simulation.
## What it does

**Problem solved:**
Commander deck testing on a phone is usually slow and clumsy.
Keep7 helps you answer real play questions before you sit down at a table:

**What I built:**
- Hand tester with keep/mull flow and draw reveal
- Turn 1-5 goldfish simulator
- Deck-vs-deck race mode with 20-hand trial verdicts
- Web deck loader (MTGJSON) with retries and fallback behavior
- Parser cleanup for noisy Moxfield export lines
- Full-screen tap-to-zoom card modal for mobile use
- **Is this opener keepable?** Draw 7-card hands, keep or mulligan, see your next 3 draws.
- **How does my curve play out?** Goldfish turns 1–5 — tap lands, cast spells, see available mana.
- **Which version of this deck starts better?** Run 20 opener trials for each list, get a plain verdict.

**Stack:**
Vanilla JS (ES modules), HTML, CSS, Scryfall API, MTGJSON API
## Modes

**Why this matters:**
Shows product thinking, practical front-end architecture, API resilience, and mobile UX execution in one project.
### Hand Test
- Parse any standard decklist format
- Draw a 7-card opener, keep or mulligan
- Reveal next 3 draws after you keep
- Track session stats: keep rate, land distribution, flood/screw risk

## What This App Does
### Goldfish
- Simulate turns 1–5 against an empty board
- Play lands from hand, tap for mana, cast spells
- Action log tracks what happened each turn

Keep7 is a client-side MTG deck testing app focused on Commander-sized lists.
### Deck Race
- Compare two decklists across 20 simulated openers each
- Stats: keep rate, avg lands, T3 land %, flood/screw risk
- Plain-English verdict you can act on

It helps you answer real play questions:
## Stack

- Is this opener keepable?
- How often do I hit land drops?
- Which version of this deck starts better?
- **Next.js 15** (App Router) + **React 19**
- **TypeScript** throughout
- **CSS Modules** — no CSS-in-JS, no utility framework
- **Scryfall API** — card data and images, with retry/fallback
- **MTGJSON** — random web deck loading
- Fully client-side — no custom backend, no auth, no database

## Why People Use Keep7

- **You can test in seconds, not minutes.**
Paste a decklist and start drawing hands right away.

- **It works well on a phone.**
Big tap targets, quick flows, full-screen card zoom.

- **You can compare deck versions side by side.**
Run 20 opener trials for each list and read a plain verdict.

- **You can load random web decks for fun testing.**
Pull from MTGJSON by deck type, then tap "Try another."

- **It keeps your choices.**
Deck text, web deck type, and card cache are saved in local storage.

## Features

### Test Opening Hands

- Parse decklists from plain text
- Draw a 7-card opener
- Mulligan or keep
- Reveal the next 3 draws
- Track session stats (keep rate, land distribution, flood/screw signals)

### Goldfish Turns 1-5

- Simulate early turns with your actual list
- Play lands from hand
- See available mana and castable cards

### Compare Two Decks

- Run 20 opener simulations for Deck A and Deck B
- See both opening hands
- Review core stats for each deck
- Get a short verdict line you can act on

### Load Random Web Decks

- Load random decks from MTGJSON
- Filter by deck type
- Fallback flow: selected type -> any type -> local sample deck
- One-tap "Try another"

## Setup (Low Pressure)

No account. No backend setup. No database.

1. Clone this repo.
2. Start a local static server.
3. Open the app in your browser.

### Option A: Python

```bash
python -m http.server 5173
```

### Option B: Node
## Setup

```bash
npx http-server -p 5173
git clone https://github.com/fuzmaster/keep7.git
cd keep7
npm install
npm run dev
```

Then open:
Open [http://localhost:3000](http://localhost:3000).

`http://localhost:5173`
## Decklist format

## Decklist Input Format
One card per line:

Use one card per line:

```text
1 Sol Ring
1 Arcane Signet
35 Plains
```
4 Lightning Bolt
4 Goblin Guide
20 Mountain
```

The parser handles common export noise, including set codes and collector numbers from Moxfield-style lines.

## Tech and APIs

- Vanilla HTML/CSS/JavaScript (ES modules)
- Scryfall API for card data and images
- MTGJSON API for random web deck loading
- Fully client-side (no custom backend)

## Project Layout

- `index.html` - app shell and mode panels
- `styles.css` - visual system and responsive layout
- `js/ui.js` - hand test flow and interactions
- `js/goldfishUi.js` - goldfish mode UI
- `js/raceUi.js` - deck race mode UI
- `js/remoteDeck.js` - MTGJSON deck loading logic
- `js/parser.js` - decklist parsing and cleanup
- `js/storage.js` - local cache and saved settings

## Common Issues

### App does not load modules
Handles Moxfield export format automatically (set codes, collector numbers, etc.).

Run from a local server, not `file://`.
## Importing decks

### Card images do not appear
- **Paste a decklist** — plain text, one card per line
- **Moxfield / Archidekt URLs** — import via the URL field
- **Random web deck** — loads from MTGJSON by deck type

Check internet access and Scryfall availability.
## Reliability

### Random web deck fails
- Scryfall requests are batched, rate-limited, and retried on failure
- If Scryfall is unavailable, the app falls back to placeholder cards — simulation still works
- Card data is cached in `localStorage` by deck hash (7-day TTL, up to 5 decks)
- `localStorage` failures are handled gracefully

MTGJSON may be down. Keep7 falls back to a local sample deck.
## Project layout

## Portfolio Note
```
src/
app/ Next.js App Router pages and layout
components/ UI components with colocated CSS modules
hooks/ State logic for each mode (useHandTest, useGoldfish, useRace)
lib/ Business logic: parser, engine, scryfall, storage, metrics
types/ TypeScript interfaces
```

This project shows product thinking plus front-end execution:
## Privacy

- Mobile-first UX for a niche workflow
- Real-world parser cleanup for noisy deck exports
- Resilient API fallbacks
- Fast, no-framework JavaScript architecture
Keep7 runs entirely in your browser. Deck text and session data never leave your device. The only external requests are to Scryfall (card images/data) and MTGJSON (optional random deck loading).

## License

MIT License. See `LICENSE`.
MIT
72 changes: 47 additions & 25 deletions src/app/globals.css
Original file line number Diff line number Diff line change
@@ -1,33 +1,52 @@
/* ── Design Tokens ─────────────────────────────────────── */
:root {
--font-display: "Avenir Next", "Trebuchet MS", "Segoe UI", sans-serif;
--font-mono: "Consolas", "Courier New", monospace;
--bg-root: #121212;
--bg-surface: #181818;
--bg-card: #202020;
--bg-input: #fff8e7;
--bg-elevated: #242424;

--text-primary: #ffffff;
--text-secondary: #d7d7d7;
--text-muted: #a7a7a7;

--keep: #1db954;
--keep-dim: rgba(29, 185, 84, 0.16);
--mull: #ef6c4a;
--mull-dim: rgba(239, 108, 74, 0.16);
--gold: #ffd23f;
--gold-dim: rgba(255, 210, 63, 0.16);
--purple: #5dade2;
/* Backgrounds — deep navy, MTG Arena-coded */
--bg-root: #07090f;
--bg-surface: #0c1020;
--bg-card: #131826;
--bg-input: #0a0e1c;
--bg-elevated: #161e30;

/* Text */
--text-primary: #eceef8;
--text-secondary: #8892b8;
--text-muted: #505878;

/* Semantic: green = keep, red = mull — matches MTG color philosophy */
--keep: #00c877;
--keep-dim: rgba(0, 200, 119, 0.12);
--mull: #e03c3c;
--mull-dim: rgba(224, 60, 60, 0.12);

/* Gold — MTG card-frame amber, warmer than warning yellow */
--gold: #c9a84c;
--gold-bright: #e2c468;
--gold-dim: rgba(201, 168, 76, 0.12);

--border-faint: #282828;
--border-subtle: #343434;
--border-mid: #4a4a4a;
--border-focus: #ffd23f;
/* MTG mana palette — used for card highlight states */
--mana-white: #f0e6c0;
--mana-blue: #1e6eaa;
--mana-black: #3d2b5e;
--mana-red: #cc3b28;
--mana-green: #1a7a47;

/* Borders */
--border-faint: #181f38;
--border-subtle: #1f2842;
--border-mid: #293356;
--border-focus: #c9a84c; /* gold focus — replaces purple */

/* Glow effects — MTG Arena interactive signature */
--glow-gold: 0 0 14px rgba(201, 168, 76, 0.22), 0 0 4px rgba(201, 168, 76, 0.12);
--glow-keep: 0 0 12px rgba(0, 200, 119, 0.25);
--glow-mull: 0 0 12px rgba(224, 60, 60, 0.25);
--shadow-card: 0 10px 24px rgba(0, 0, 0, 0.28);
--shadow-panel: 0 16px 40px rgba(0, 0, 0, 0.34);
--glow-keep: 0 0 0 1px rgba(29, 185, 84, 0.35), 0 0 18px rgba(29, 185, 84, 0.18);
--glow-gold: 0 0 0 1px rgba(255, 210, 63, 0.38), 0 0 18px rgba(255, 210, 63, 0.18);
--purple: #5dade2;

/* Display font — Cinzel (Beleren-adjacent serif for logo/headings) */
--font-display: 'Cinzel', serif;
--font-mono: "Consolas", "Courier New", monospace;
}

/* ── Reset ────────────────────────────────────────────── */
Expand All @@ -51,6 +70,8 @@ body {
color: var(--text-primary);
line-height: 1.5;
min-height: 100dvh;
display: flex;
flex-direction: column;
font-size: 16px;
}

Expand All @@ -68,6 +89,7 @@ button {
}

main {
flex: 1;
padding-bottom: calc(5.8rem + env(safe-area-inset-bottom, 0px));
}

Expand Down
38 changes: 32 additions & 6 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,52 @@
import type { Metadata } from 'next';
import { Cinzel } from 'next/font/google';
import { Footer } from '@/components/Footer';
import './globals.css';

const cinzel = Cinzel({
subsets: ['latin'],
weight: ['700'],
variable: '--font-display',
display: 'swap',
});

export const metadata: Metadata = {
title: 'Keep7 — MTG Deck Consistency Lab',
description: 'Test opening hands, goldfish turns 1–5, and compare two decks side-by-side. Free, instant, no signup.',
title: 'Keep7 — MTG Deck Testing Tool',
description: 'Test Magic: The Gathering opening hands, goldfish turns 1–5, and compare two decks side by side. Free, instant, no account needed. Works on mobile.',
keywords: [
'MTG deck tester', 'Magic the Gathering', 'opening hand simulator',
'goldfish MTG', 'deck consistency', 'Commander deck testing',
'MTG hand test', 'mulligan helper', 'deck race', 'Scryfall',
],
openGraph: {
title: 'Keep7 — MTG Deck Consistency Lab',
description: 'Test MTG opening hands, goldfish, and compare decks.',
title: 'Keep7 — MTG Deck Testing Tool',
description: 'Test MTG opening hands, goldfish turns 1–5, and compare two decks side by side. Free and instant — no account needed.',
url: 'https://keep7.vercel.app',
siteName: 'Keep7',
type: 'website',
},
twitter: {
card: 'summary',
title: 'Keep7 — MTG Deck Testing Tool',
description: 'Test MTG opening hands, goldfish, and compare decks. Free, instant, no signup.',
},
icons: {
icon: '/favicon-card.svg',
},
manifest: '/manifest.json',
robots: {
index: true,
follow: true,
},
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
<html lang="en" className={cinzel.variable}>
<body>
{children}
<Footer />
</body>
</html>
);
}
Loading
Loading