Dugout Value
Baseball research on pitching, hitting, and projections, plus tools for comparing players, contracts, and trade value.
Football play, formation, and playbook designer with print-ready NFL-style sheets.
Sports & fitnessTypeScript · PythonGitHub @PatrickweshaOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
NFL/Visio-style football play, formation, and playbook designer with print-ready sheets
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
Football play, formation, and playbook designer that prints NFL/Visio-style play sheets from the browser.Read the full README ↗Football play, formation, and playbook designer that prints NFL/Visio-style play sheets from the browser.
G or the Guides button shows them all faintly; they never print or export. The landmark is saved on the player (alignment), Flip sends him to the same landmark on the other side, and the right panel reads out where the selected player sits ("Hash +3 (R)", "0.5 yd outside Bottom #s (L)"). Add or remove landmarks in src/geometry/landmarks.ts (LANDMARK_DEFS); field levels (NFL, College, High School) live in FIELD_PRESETS in src/model/constants.ts and are picked in Settings.npm install
npm run dev
Open http://localhost:3000. The gallery of seed formations and demo plays is at /dev/gallery. Unit tests: npm test.
The 2019 Packers pack (71 formations) is checked in as seeds. Source of truth: data/formations/packers-2019.source.json.
npm run import:formations # rebuild src/seeds/data/packers2019.json from the source, then validate it
npm run import:formations -- path/to/new.json # replace the source with a new file first
npm run render:formations # SVG + PNG + an index.html contact sheet in renders/packers-2019/
Entries are keyed on name + personnel, so re-running never duplicates. Alignment constants come from src/model/constants.ts; a source authored with a different line spacing is remapped. Open browsers pick up regenerated data on the next load, and a formation you edited in the app is never overwritten. Filter the library to Needs review to work through the entries whose positions are only a starting shape.
Formations > Import turns an All-22 charting workbook into a formation library with usage counts, and keeps every charted snap (week, quarter, down and distance, field zone, hash, play type, target, result, yards, motion, notes) linked to its formation in a snaps table, so formations can later be filtered by down, distance or run/pass rate. Upload the chart (.xlsx, sheet "Chart", header on row 4), the per-player alignment JSON (playforge-formations/v1), or both; the preview shows every unique formation's diagram, usage count, snap ids and source (W2 exact vs W1 template); tick what you want and import. Re-importing the same files merges and never duplicates: formations are keyed on an alignment signature, snaps on their id, and usage is recomputed from the snaps table. Rows the import could not use are listed with the reason.
template, verify and shows as needs review; its note lists the charted "Set:" text of each snap so it can be checked against film.src/importers/snapChart/config.ts (every constant is there): the hash sets where the numbers are (NFL hashes 23.6 yd from the sideline, numbers 12 yd; left hash = near numbers 11.6 yd, far 17.7), inline = 1 yd outside the end man, wing = 1 outside and 1 off, tight = 3 outside, slot = halfway from the tackle to the numbers, numbers, wide = 3 outside the numbers, off the ball = 1 yd back, stack = 2 yd behind; QB 1 / 4 / 5 yd (under center / pistol / gun); backs deep 7, offset 7 and 1.5 over, gun offset level with the QB, fullback 4.5. A standard five-man line is always drawn; a player with an unknown name is drawn and labelled by position.11 Gun 2x2 Rt; a second alignment with the same name gets #2, #3 by usage. Merge Rt and Lt mirrors folds a Lt snap into the Rt formation (the snap is stored mirrored).import-data/; npm run import:snaps rebuilds src/seeds/data/eagles2026.json and import-data/import-report.json with the same engine the Import page uses, so importing the same files in the app merges into the seeded rows. Snaps stay on the device (they are not synced); formations sync like any other.One language for charting and drawing the Eagles every week, at System in the nav (/systems/eagles-2026). It is rebuilt from the two books closest to Sean Mannion: Green Bay 2019 (LaFleur) and the Rams 2022 (McVay). When both books have a word for the same thing, the Green Bay word is the main one and the Rams word is listed as "also called". Nobody outside the building knows the real Eagles words; this is the family's language, used the same way every week.
data/systems/eagles-2026/core.json (hand-written): lineage, call order, positions, personnel, splits, formations by family, variations, the empty letters, motions, shifts, the words used at the line, and the charting rules. runs.json and pass.json are the run and pass catalogs (families, concepts, tags, blocking words, protections, route name map). Every entry is a paraphrase with the page it came from; no book text is checked in. Green Bay pages link into the reader.src/systems/eagles/nameFormation.ts names a charted alignment: the call goes to the Y (not the chart's passing strength), the word comes from the picture (Trips, Dice, Dyno, Fast, West, Bunch, Deuce ...), variations from who is reduced or off the ball (Close, Tight, Open, Off, Slot ...), and an empty set is the one-back word plus the back's letter (A to G). Words that differ only by which receiver is the F (Trips / Trio / Trax, Dice / Dixie, Stack / South) are listed as "same picture, different jobs". A picture that fits no definition takes the nearest word and is flagged.src/systems/eagles/nameMotion.ts turns the chart's motion note (who / from / path / timing) into a motion word: Rt / Lt, Righty / Lefty, Short, Fly, Behind, Lab / Rat. Tight ends are the Y and backs the H (roster.ts); a receiver whose job film cannot show is written WR.npm run import:snaps) names formations in the system: Gun Dice Rt, West Lt Slot, Gun Trips Lt G. The chart's own name stays in chartName and in the note, with the reasoning. Ids are still the alignment signature, so re-imports merge as before.My Beast playbook (Playmaker export: formations, run plays, pass plays, Call Sheet V3) rebuilt for 7 and 8 year olds, at Beast in the nav (/systems/beast-2026). One language: wristband number = the play (1 to 10, the V3 order), color = the side the Y and the wings line up on (Red right, Yellow left), play number = who carries and the hole (even right, odd left), and the line blocks GOD (Gap, On, Down) on every run. The no-huddle is the check-with-me system: City + Color + Day.
src/systems/beast/content.ts: the ten concepts with kid names (Train, Hammer, Rocket, Crossover, Limo, Elevator, Magic, Pop, Boomerang, Ice), city words, hand signals, positions, GOD, cadence and days, the call sheet sections, the opening script, IF / THEN, and the 12-week install plan.src/systems/beast/plays.ts: 9 formations and 20 plays. Red is drawn here, Yellow is the exact mirror. Every player's job is a position note. Plays where my sheets disagree with themselves are needs-review with the reason.repo.addPack) writes the rows once and never overwrites an edit. Print sheets: /systems/beast-2026/print?sheet=callsheet|wristband|jobs|install|teach. npm run render:beast writes PNGs to renders/beast-2026/.The offense McVay and Shanahan would build for their own kids, at West Coast in the nav (/systems/wco-2026). Same ten-play count and the same language as the Beast (wristband number, Red / Yellow, play number = carrier + hole, check-with-me), built on one look (Pistol: Q at 3 yards, H behind him, F beside him; Bunch from the Gun) and one main run (Surf, the wide zone) that every other play copies for two steps: Dart (duo with the fullback), Jet, Cutback (GT counter), Boot (naked), Leak, Hook (stick from Gun Bunch), Web (halfback screen), Zipper (mesh), Ice. Line rules: STEP-HIT-UP on zone, GOD on gap runs, Pass GOD on passes.
Both youth offenses run on one engine: src/systems/youth/ (types.ts the system shape, build.ts formations, Red plays, Yellow mirrors, playbook and job helpers, registry.ts the list). A system is data: src/systems/beast/ and src/systems/wco/. The system page, print sheets and the Rams-style book (/systems/<id>, /print?sheet=..., /book) work for any of them; src/systems/youth/youth.test.ts checks every rule for every system. Book PDF: node scripts/export-beast-book.mjs http://localhost:3000/systems/wco-2026/book out.pdf. Drawings: SYSTEM=wco-2026 npm run render:beast.
The 2019 Packers route library (159 routes, pages 70-103) and the Install #1 plays are checked in as seeds next to the formation pack. Nothing is traced from the scanned diagrams: route geometry is derived from the depths and breaks the book states in words, and a play is an imported formation + a protection + a route word per receiver (or a run family), drawn with src/geometry/routeLibrary.ts and the block presets.
python scripts/playbook/rasterize.py "path/to/playbook.pdf" # one PNG per page at 200 DPI (needs: pip install pymupdf)
npm i --no-save tesseract.js && node scripts/playbook/ocr.mjs # Tesseract OCR: text + word boxes per page
python scripts/playbook/parse_route_pages.py # route tree tables -> local raw records
python scripts/playbook/parse_calls.py 1 104 157 # install N, first page, last page -> calls.install-N.json
npm run import:plays # bind calls + route words + formations, write the seed data, validate
npm run render:plays # SVG + PNG + an index.html contact sheet in renders/packers-2019-plays/
data/packers-2019/routes.source.json (one record per route) and data/packers-2019/tags.install-N.json (route word per player, read from the page because those labels are too small for OCR).data/packers-2019/ocr/ and local/, which are gitignored. The checked-in records carry depths, breaks, page numbers, and short paraphrased notes.src/geometry/formationTags.ts: alignment tags after the direction (Close, Off, Book, Hip ...) change where a player starts; shift and motion tags before or after the formation (Y Mo, F Sh, Hax, Z Lt, F Ctr ...) give the player a pre-snap spot, a final spot, and a path (Player.motion), drawn as a dashed orange ghost and a dotted red path behind the line. Routes and blocks always hang off the final spot. Seven men on the line is asserted after every tag and for every composed play; a tag that breaks it throws. A tag the book only draws (no split or landmark in words) is applied and the play is marked needs-review.alternate: name, trigger, run family or route words) and shown on the play card.python scripts/apply-formation-corrections.py data/formations/corrections-<date>.json, then npm run import:formations.install, sourcePage, rawCall, personnel, protection, concept, routeTags, confidence, and reviewNotes as real fields. data/packers-2019/import-report.json lists what was flagged UNPARSED or needs-review and why.Rams 2022 in the nav (/playbooks/rams-2022): every diagram of the McVay 2022 book as a PlayForge play, named with the Green Bay words (the Rams call stays on the play as alias, so a search for either word finds it), grouped the way the Green Bay installs read (Install 1 to 7, then the family heading, then the Rams concept page) with the situations apart (red zone, two minute, run alerts, protections). The page searches every word a play carries: name, Rams call, formation, personnel, concept, protection, blocking calls, route words, motion and shift words, front, install, notes. Add the whole book to my library writes the plays and the playbook Rams 2022 (Eagles system) with one section per install heading and situation; a play you then edit in the editor is never overwritten by an update. Nothing is seeded: the book stays on this page until you add it.
The Rams section PDFs are real vector drawings with a text layer, so nothing is traced from a scan:
python scripts/rams/extract_cells.py source/rams-2022/pdf source/rams-2022/cells # every diagram cell: offense rings, defender letters, labels, line work (yards)
python scripts/rams/extract_fronts.py "source/rams-2022/pdf/02. Defensive Identification 2022.pdf" data/rams-2022/fronts.json
python scripts/rams/build_plays.py source/rams-2022/cells public/rams-2022/plays.json # system words, concept, install, notes, per-player words -> the pack the page loads
npx vitest run src/systems/rams/compose.test.ts # every cell composes, seven on the line, names, installs, drawings
KEYS="wide-zone-p5-c1,3-step-p5-c1" npx vitest run --config vitest.render.config.mts scripts/render-rams.render.tsx # PNGs in renders/rams-2022/ to check against the page
src/systems/rams/compose.ts turns a cell into a play: the offense stands on the pack formation the call names (the Rams picture on the editor's landmarks), else on the base formation with the call's tags applied by the tag engine, else on the drawing's spots snapped to the nearest landmark (flagged). When the book draws the mirror of its own formation line, or a gun look the line does not name, the picture follows the drawing and says so. The drawing is warped onto that formation with a monotone x map from every drawn man to his final spot, so a crosser still crosses between the same men and a sideline route still widens with the split; a man's own line work keeps its drawn shape within a few yards of him (a 3-yard hook stays 3 yards), and the formation's core keeps true lengths. A back's track keeps its crossing point and only its backfield part stretches to start at the man (the book draws the back at 4.7, he stands at 7.5). Routes, blocks, fakes and motions come from the drawing's line work; labels become annotations; defenders keep their alignment over the line; the front name goes in the footer.scripts/rams/build_plays.py carries the word map (Rams base word to system word, variations, motions, concept and route words: Zap Fup = Quatro, Barkley = Stanley, Race = Bounce, Faulk = Arches, Dagger = Dover, Special = Thru, 58 / 59 = Y6 / Y7, ...). data/systems/eagles-2026/runs.json and pass.json give the concept entry (its paraphrase and film notes go on the play).data/rams-2022/installs.json maps every Rams concept to the Green Bay install whose index lists it (43 found on an index page, the rest by family rule, each with its basis and a note). Situations are their own groups.In the editor, Branch starts a new line at a line's end and Branch here (select a point of the line first) starts one at that point, as many times as the play needs: a back's two options off one spot on his track, a route that forks.
The whole scanned playbook recreated as a readable, searchable document: /playbooks/gb-2019/read. Every one of the 477 pages, in the original order, each with its original page number and a #page-N anchor; a sticky contents sidebar (the book's own sections, page titles, and play names); all words as HTML or SVG text, so Ctrl+F finds them and everything can be selected. The book's index pages link to the pages they list. Every diagram is a PlayForge drawing (nothing is a scan): traced from the page, then completed. Players the scan cut off or hid are placed by educated guess (the formation pack's spot for that formation line, else the mirror of the opposite man, else a default spot for the letter) and every guess is listed on the cell ("guessed: X, C"). Text labels the scan cut off are restored from the rest of the book and underlined dotted. /playbooks/gb-2019/review puts every rebuild below high confidence next to its scan. "Add to my library" on the reader imports every diagram as a play and the book as the Green Bay 2019 playbook; every cell then has an Edit link into the play editor. A play you save in the editor shows in the reader at once ("your edit" tag), "Update from this build" never overwrites a play you edited, and "Save edits into the book" writes them to source/book/edits/gb-2019.json so the next build_book.py and PDF carry them (the cell is marked "edited by you").
Everything built from the scan is copyrighted source material and stays on the computer that built it (source/ and public/book/ are gitignored). A deployment serves a sealed copy instead: python scripts/playbook/pack_book.py gzips and AES-256-GCM-encrypts book.json and library.json into data/book/gb-2019.*.enc (committed, unreadable without the key). Set two environment variables on Vercel and redeploy: BOOK_KEY (the hex key in source/book/book.key, written on the first pack) opens the book, and BOOK_ACCESS_CODE (any phrase) is what the reader asks for once per browser, so the book is not on the open web. The scans, the review page and "Save edits into the book" exist only on the building machine; editing plays and the live "your edit" view work anywhere.
python scripts/playbook/render300.py # source/pages: every page at 300 DPI (the ink layers' native resolution)
python scripts/playbook/ocr_rapid.py # source/ocr: RapidOCR lines (pip install rapidocr_onnxruntime)
python scripts/playbook/detect_cells.py # source/cells: the ruled diagram cells
python scripts/playbook/agent_inputs.py # source/book/input: compact per-page briefs for the transcription pass
# transcription: Claude agents follow source/book/INSTRUCTIONS.md and write source/book/pages/p-NNN.json,
# every page checked against its image (titles, text, index tables, per-diagram titles, labels, ring letters, defenders)
bash scripts/playbook/run_vectorize.sh # source/vector: traced diagrams + scores (one process: this PC runs short of memory)
python scripts/playbook/clean_crops.py # public/book/gb-2019/crops + pages: cleaned scans (the review page and the "Original scan" links)
python scripts/playbook/compose_requests.py && npx vitest run --config vitest.render.config.mts scripts/compose-book.render.tsx
# source/book/compositions.json: the formation pack's positions for every formation line, and every library route
python scripts/playbook/build_book.py # public/book/gb-2019/book.json + library.json, source/book/review.json
PLAYFORGE_LOW_MEMORY_BUILD=1 npx next build && npm run start # then open /playbooks/gb-2019/read (low-memory flag: see next.config.ts)
python scripts/playbook/pack_book.py # data/book/gb-2019.*.enc for the deployment (commit these; the key stays in source/book/book.key)
node scripts/playbook/export_pdf.mjs # prints the reader in chunks with Edge, then finish_pdf.py merges them:
# source/book/Green-Bay-2019-Playbook.pdf, contents links + bookmarks (needs the app running)
python scripts/playbook/preview.py <page> [cell] # side-by-side scan vs rebuild PNGs, for checking the tracer
vectorize.py): players are closed rings / squares / the filled HB disc; ring letters are read with the OCR recogniser on the ring's inside, then the template classifier (glyphs.py); defenders are coloured (or, on the front pages, black) letter words matched against the page's transcribed defender list; lines are the remaining ink, thinned and traced, with dashes chained, arrowheads / dots / block T's at the ends, and lines through a player joined. Frame: OL centres are 1 yd apart, x = 0 at the centre square.public/book/gb-2019/review.json), and the library play carries the same flag plus the guesses in its notes. Completion (scripts/playbook/complete.py): unread rings take the page's letters, missing players come from the composition / mirror / defaults, the view window grows to show everyone.Without setup the app is fully local, exactly as before. With a free Supabase project it keeps plays, formations, and playbooks the same on every device you sign in on. The newest edit wins per play, deletes carry over, and it keeps working offline and catches up later. Untouched built-ins never upload. Settings (look, hashes, paper) stay per device.
Setup, done once in your own Supabase and Vercel dashboards:
supabase/schema.sql.https://playforge-kohl.vercel.app), and add https://playforge-kohl.vercel.app/** to Redirect URLs.Your code: {{ .Token }}. The very first sign-in uses Confirm signup.NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY. Redeploy, because these are baked in at build time.Good to know: the free tier sends only a few emails per hour, and it pauses a project after about a week with no activity (press Restore in the dashboard; the app keeps working locally meanwhile). On iPad, Add to Home Screen keeps Safari from clearing the site's storage after a week away; sign in there with the code, not the link.
src/model types, zod schemas, constants (all sizes in yards)src/geometry pure math: yards to SVG, paths (Catmull-Rom curves), markers, snapping, flip, route tree, block presets, sheet layoutsrc/render the one SVG renderer shared by the editor, thumbnails, print, and PNG exportsrc/editor pointer state machine, canvas, mini toolbar, inspector, shortcutssrc/store Dexie database, repo, editor store (zustand + immer, snapshot undo), autosavesrc/sync cloud sync: pure newest-wins merge, engine, Supabase adapter, sign-in, status storesrc/print sheets, cells, cover, call sheet, PNG exportsrc/io backup and PlayForge-Lite importsrc/importers/snapChart snap-chart import: xlsx reader, chart and JSON parsers, alignment labels to yards (config.ts), signatures, templates, the import plansrc/seeds built-in formations, fronts, demo plays, the Packers 2019 pack, and the Eagles 2026 snap chartCoordinates: x = 0 at the ball (positive right), y = 0 at the line of scrimmage (positive downfield). Route points are stored relative to their player, so moving a player moves its routes.
In the Chrome or Edge print dialog turn off "Headers and footers", keep margins at "Default" or "None", and enable "Background graphics" only if you use the yard-line theme. The page size and orientation are set by the app for each layout.
data/rams-2022/notes/<section>.json (paraphrased concept, QB, HB, criteria, per-position rules, alerts, can), blocking-glossary.json (every line call), routes-glossary.json (every route), defense-glossary.json (fronts, alignments, movements, blitzes, coverages). Every string is a paraphrase with the page it came from; no book text is checked in. The cells themselves (positions and labels) live in the private source repo.data/rams-2022/fronts.json is also seeded as built-in defense formations (Formations page, side defense, tag rams-2022): every front of the Defensive Identification section with its defenders by technique.check on the page (and needs-review in the library) when the offense came from the drawing instead of the pack, when the tag engine did not know a word, or when the book prints two drawings in one cell. data/rams-2022/build-report.json lists the cells that had no formation line, no call, or no pack formation.