Applied The Nick E. Playbook

Coaching Playbook Builder

Last reviewed · content updated

Intermediate

What you'll learn

~40 min
  • Build a browser-based play diagramming tool with drag-and-drop player positioning
  • Draw movement routes and passing lanes with SVG path rendering
  • Organize plays into categories (offense, defense, set pieces, drills)
  • Export play diagrams as PNG images and a printable playbook PDF

What you’re building

Nick coaches his daughters’ youth teams. Before every practice he draws circles for players and arrows for movement on a whiteboard, snaps a photo, and texts it to the other parents. By the second week of the season he has 40 blurry whiteboard photos in his camera roll with no labels and no way to find the corner kick play he drew three Saturdays ago. Tactic-board apps cost $10-15 a month and are built for pro clubs, not a U10 futsal team running a 3-1 diamond rotation. He just needs a clean field, draggable players, movement arrows, and a way to save and print.

You are going to build a browser-based play diagramming tool. Pick a sport (soccer, futsal, or football). Drag player tokens onto an accurately marked field. Draw movement routes — runs, passes, dribbles, screens — as SVG paths with arrow heads. Save plays into a library organized by category. Chain plays into a practice plan with time blocks. Export any play as a PNG image or generate a full printable playbook PDF. One tool for the U8 soccer team’s 4-3-3 attack, the U10 futsal squad’s kickoff play, and — when nostalgia hits — the punt return scheme from Nick’s JMU days.

💬How youth coaches actually share plays

Here is how play communication works in youth sports: the head coach draws something on a whiteboard, takes a blurry photo, texts it to the group chat, and the assistant coach replies “what’s the triangle near the goal?” A week later, nobody can find it. Or the coach just describes the play verbally at practice and hopes eight-year-olds remember the shape. A visual tool that produces clean, labeled, shareable diagrams is the difference between “I think we practiced this” and “here’s the play, page 3 of the playbook.” Kids learn visually. Clean diagrams stick. Whiteboard photos do not.

Software pattern: Canvas/SVG visual editor with drag-and-drop, state management, and export pipeline

This is the same architectural pattern behind Figma, Miro, Canva, and every tactical board app on the market. An HTML5 Canvas renders the static background (the field). An SVG overlay handles interactive elements (player tokens, route paths) because SVG elements are DOM nodes you can attach event listeners to. A state manager tracks positions, routes, and metadata. An export pipeline converts the visual state to PNG (via canvas.toDataURL or html2canvas) and PDF (via Puppeteer). The core concepts — hit testing, drag state, coordinate transforms, SVG path rendering — are the same whether you are building a whiteboard app or a $50M design tool.

Nick played football at James Madison University as a punter and has a kinesiology and sports management degree, so he thinks in movement patterns and X’s and O’s natively. He is helping coach his daughters’ U10 futsal team (3-1 diamond rotation, two kickoff set pieces) and U8 soccer team (basic 4-3-3, learning corner kicks). He wants a tool that makes him look as organized as he actually is — and helps the kids learn the plays faster because the diagrams are clean, labeled, and consistent.


The showcase

The finished tool produces:

  • Browser-based play editor with a configurable field — soccer, futsal, or football — accurately marked and scaled to the viewport.
  • Drag-and-drop player tokens: colored circles with jersey numbers, draggable to any position, right-click (or long-press) to edit number/name/position label, snap-to-grid option.
  • Route drawing: click a player, click a destination, and an arrowed SVG path appears. Line types: solid (run), dashed (pass), wavy (dribble), thick (screen). Curve any route by dragging its midpoint. Ghost positions show the “before” state.
  • Play library organized by category (offense, defense, set pieces, transition, drills) with search, duplicate, rename, delete, and JSON import/export.
  • Drill sequencer: drag plays into a timeline, set durations and notes per segment, watch the total practice time counter update, export the plan as a PDF.
  • PNG export per play and a full playbook PDF (one play per page with a table of contents) to hand the assistant coach on game day.
  • Mobile-friendly touch support for sideline adjustments from a phone or tablet.

The prompt

Start your AI CLI tool in an empty directory and paste this prompt:

Build a Node.js + Express application called coaching-playbook that serves a
browser-based play diagramming tool for youth sports coaching. The tool lets
coaches drag players onto a field, draw movement routes, organize plays into
a library, build practice plans, and export diagrams as PNG images and PDF
playbooks.
PROJECT STRUCTURE:
coaching-playbook/
package.json
server.js (Express server, routes, static file serving)
public/
index.html (main editor page)
library.html (play library browser)
practice-plan.html (drill sequencer / practice plan builder)
css/
style.css (all application styles)
js/
field-renderer.js (draws the field on HTML5 Canvas)
player-manager.js (drag-and-drop player tokens on SVG overlay)
route-drawer.js (SVG path drawing for movement routes)
play-library.js (save, load, organize, search plays)
drill-sequencer.js (practice plan timeline builder)
export-engine.js (PNG and PDF export via html2canvas)
app.js (main application controller, wires everything)
data/
plays/ (JSON storage, one file per play)
practice-plans/ (JSON storage for practice plans)
sample-plays.json (pre-loaded sample plays)
views/
playbook-pdf.hbs (Handlebars template for full playbook PDF)
practice-plan-pdf.hbs (Handlebars template for practice plan PDF)
REQUIREMENTS:
1. EXPRESS SERVER (server.js)
- Runs on port 3000 (configurable via PORT env var)
- Serves static files from public/
- API routes:
GET /api/plays -> list all saved plays (summary: id, name,
category, sport, updatedAt)
GET /api/plays/:id -> get full play data (positions, routes, meta)
POST /api/plays -> save a new play (generate UUID for id)
PUT /api/plays/:id -> update existing play
DELETE /api/plays/:id -> delete a play
GET /api/plays/export -> export all plays as a single JSON file
POST /api/plays/import -> import plays from a JSON file
GET /api/practice-plans -> list all practice plans
POST /api/practice-plans -> save a practice plan
GET /api/practice-plans/:id -> get a practice plan
POST /api/export/png -> receive play state JSON, return PNG image
POST /api/export/playbook -> receive play IDs, generate multi-page PDF
POST /api/export/practice -> receive plan ID, generate practice plan PDF
- Store plays as individual JSON files in data/plays/ (filename: {id}.json)
- On first start, if data/plays/ is empty, load sample-plays.json and
save each play as a separate file
- Use express-handlebars for PDF template rendering
- Dependencies: express, express-handlebars, uuid, puppeteer, cors
2. FIELD RENDERER (public/js/field-renderer.js)
Export a FieldRenderer class that draws sport-specific fields on an
HTML5 Canvas element.
Constructor: new FieldRenderer(canvasElement, sport, options)
- sport: "soccer", "futsal", or "football"
- options: { gridOverlay: false, gridSpacing: 10, fieldColor: "#1a472a",
lineColor: "#ffffff", lineWidth: 2 }
SOCCER FIELD (105m x 68m, scaled to canvas):
- Outer boundary lines (touchlines and goal lines)
- Center line and center circle (9.15m radius)
- Center spot
- Penalty areas: 40.3m x 16.5m from each goal line
- Goal areas: 18.3m x 5.5m from each goal line
- Penalty spots: 11m from goal line
- Penalty arcs: arc of 9.15m radius from penalty spot, outside penalty area
- Corner arcs: quarter circle of 1m radius at each corner
- Goals: 7.32m wide, drawn as rectangles extending behind the goal line
FUTSAL COURT (40m x 20m, scaled to canvas):
- Outer boundary lines
- Center line and center circle (3m radius)
- Center spot
- Penalty areas: 6m radius quarter circles from each goal post
- Penalty spots: 6m from goal line center
- Second penalty spots: 10m from goal line center
- Goals: 3m wide
FOOTBALL FIELD (100 yards + 10-yard end zones x 53.3 yards):
- Outer boundary lines
- End zones with diagonal hash pattern fill (slightly different shade)
- Yard lines every 5 yards (solid white)
- Yard numbers every 10 yards (10, 20, 30, 40, 50, 40, 30, 20, 10)
positioned on both sides of the field
- Hash marks: short tick marks at each yard on the sideline
- Center hash marks at the NFL/college standard width
All fields:
- Scale to fill the canvas while maintaining aspect ratio
- Add 20px padding around the field
- Optional grid overlay (light dashed lines at gridSpacing intervals)
- Expose a getFieldBounds() method returning { x, y, width, height } of
the playable area in canvas pixels (used for constraining player positions)
- Expose a fieldToCanvas(fieldX, fieldY) and canvasToField(canvasX, canvasY)
for coordinate conversion between field meters/yards and canvas pixels
- Redraw method: render() clears and repaints the field
3. PLAYER MANAGER (public/js/player-manager.js)
Export a PlayerManager class that handles player tokens as SVG elements
overlaid on the canvas.
Constructor: new PlayerManager(svgElement, fieldRenderer)
- svgElement: an SVG element positioned exactly over the canvas
Player token structure (SVG group):
- Circle: 18px radius, fill color based on team
- Text: jersey number centered in the circle, white, bold, 14px
- Label: position abbreviation below the circle (GK, CB, LW, ST, etc.),
10px, team color
- Ghost circle: semi-transparent version at the original position when a
route has been drawn from this player (shows the "before" state)
Team colors:
- Home: #0F766E (teal, matches course accent)
- Away: #ef4444 (red)
- Neutral/drill: #6b7280 (gray, for cone markers or reference points)
Features:
- addPlayer(team, number, positionLabel, fieldX, fieldY) -> returns player id
- removePlayer(id)
- Drag and drop: mousedown/touchstart on a player token starts drag,
mousemove/touchmove updates position, mouseup/touchend ends drag.
Constrain to field bounds from fieldRenderer.getFieldBounds().
- Snap-to-grid: if enabled, snap to nearest grid intersection on drop
- Right-click (contextmenu event) or long-press (500ms touch hold) on a
player opens an edit popup: text inputs for jersey number, player name,
and position label. Save button updates the token. Delete button removes it.
- getPlayerPositions() -> returns array of { id, team, number, label,
fieldX, fieldY } for serialization
- setPlayerPositions(positions) -> restores players from saved data
- Clear all: removeAllPlayers()
Toolbar integration (handled in app.js):
- "Add Home Player" button: adds a home team player at field center
with the next available jersey number
- "Add Away Player" button: same for away team
- "Add Marker" button: adds a neutral cone/marker token
4. ROUTE DRAWER (public/js/route-drawer.js)
Export a RouteDrawer class that handles SVG path drawing for movement routes.
Constructor: new RouteDrawer(svgElement, playerManager)
Route drawing flow:
1. User clicks a player token (source) -- token highlights with a glow
2. User clicks a destination point on the field (or another player)
3. A route path is created between source and destination
Route types (selectable from toolbar):
- "run": solid line, 3px stroke
- "pass": dashed line (8px dash, 6px gap), 3px stroke
- "dribble": wavy line (SVG path with sine-wave curves), 3px stroke
- "screen": thick solid line, 6px stroke
Route rendering:
- SVG <path> element with appropriate stroke-dasharray for line type
- Arrow head at the endpoint: SVG <marker> element with a triangular
arrowhead, size 10x7, filled with the route color
- Wavy lines: generate a path string with cubic bezier curves that
oscillate perpendicular to the line direction (amplitude 6px,
wavelength 16px)
- Route color: matches the source player's team color
(teal for home, red for away)
- Curved routes: each route has a draggable control point at the midpoint.
Dragging this point converts the straight line into a quadratic bezier
curve. The control point is a small circle (6px radius, white fill)
visible on hover.
Features:
- addRoute(sourcePlayerId, destX, destY, type) -> returns route id
- removeRoute(id)
- getRoutes() -> returns array of { id, sourceId, destX, destY, type,
controlX, controlY } for serialization
- setRoutes(routes) -> restores routes from saved data
- Clear all: removeAllRoutes()
- When a route is drawn FROM a player, create the ghost position on that
player (via playerManager) showing their starting position
5. PLAY LIBRARY (public/js/play-library.js)
Export a PlayLibrary class that handles saving, loading, and organizing plays.
Play data structure:
{
id: "uuid",
name: "4-3-3 High Press Attack",
category: "offense", // offense, defense, set-piece, transition, drill
sport: "soccer", // soccer, futsal, football
formation: "4-3-3",
notes: "Left winger stays wide, right winger cuts inside...",
tags: ["press", "attack", "wide-play"],
players: [array from playerManager.getPlayerPositions()],
routes: [array from routeDrawer.getRoutes()],
fieldConfig: { sport: "soccer", gridOverlay: false },
createdAt: "ISO timestamp",
updatedAt: "ISO timestamp"
}
Features:
- saveCurrent(name, category, sport, formation, notes, tags) -> POST to API
- updateCurrent(id) -> PUT to API (saves current editor state to existing play)
- loadPlay(id) -> GET from API, restore field, players, and routes in editor
- listPlays() -> GET from API, return summary list
- deletePlay(id) -> DELETE via API
- duplicatePlay(id) -> load play, save as new with " (copy)" appended to name
- searchPlays(query) -> filter plays by name or tag (client-side filtering)
- exportAll() -> GET /api/plays/export, trigger download of JSON file
- importPlays(file) -> POST /api/plays/import with JSON file contents
Sidebar UI (in index.html):
- Collapsible sidebar on the right side of the editor (320px wide)
- Grouped by category with collapsible headers:
Offense (count), Defense (count), Set Pieces (count),
Transition (count), Drills (count)
- Each play entry shows: name, sport badge, formation, last updated
- Click to load into editor
- Hover shows action buttons: duplicate, rename, delete
- Search input at the top of the sidebar
- "Import" and "Export All" buttons at the bottom
6. DRILL SEQUENCER (public/js/drill-sequencer.js)
Export a DrillSequencer class for building practice plans from plays.
Practice plan data structure:
{
id: "uuid",
name: "Tuesday Practice - U10 Futsal",
date: "2026-03-10",
segments: [
{ order: 1, playId: null, title: "Warmup - Dynamic Stretching",
duration: 10, notes: "High knees, butt kicks, lateral shuffles" },
{ order: 2, playId: "uuid-of-play", title: "3-1 Diamond Rotation",
duration: 15, notes: "Focus on pivot movement and wall passes" },
{ order: 3, playId: "uuid-of-play", title: "Kickoff Set Piece",
duration: 10, notes: "Run it 5 times, rotate positions" },
{ order: 4, playId: null, title: "3v3 Scrimmage",
duration: 15, notes: "Apply rotation from drill 2" },
{ order: 5, playId: null, title: "Cooldown and Review",
duration: 5, notes: "Stretch, review key points, hand out playbook page" }
],
totalDuration: 55,
createdAt: "ISO timestamp"
}
Features:
- practice-plan.html page with a timeline view
- Drag plays from a play list into the timeline
- Add non-play segments (warmup, scrimmage, cooldown) via an "Add Segment"
button with title and duration inputs
- Reorder segments by dragging within the timeline
- Set duration per segment (5, 10, 15, 20, 30 min presets or custom)
- Add notes per segment
- Total practice time counter at the top, updates as segments are added
or removed
- Remove segments with a delete button on each
- Save practice plan to API
- Load saved practice plans
- Export as PDF: timeline layout with play diagrams embedded at each
segment that references a play. Non-play segments show title, duration,
and notes. Header shows plan name, date, total duration.
7. EXPORT ENGINE (public/js/export-engine.js)
Export an ExportEngine class that handles PNG and PDF generation.
PNG export:
- Capture the current editor state (canvas field + SVG overlay) as a
single PNG image
- Use html2canvas (loaded via CDN: https://html2canvas.hertzen.com/dist/
html2canvas.min.js) to capture the editor container element
- Add a title bar at the top of the image: play name, formation, sport
badge. White text on dark background (#111118).
- Resolution: 2x for retina quality (set html2canvas scale: 2)
- Trigger download as "{play-name}.png"
Playbook PDF export:
- POST selected play IDs to /api/export/playbook
- Server generates a multi-page PDF using Puppeteer:
Page 1: Cover page -- "Coaching Playbook" title, coach name (from config),
team name, date generated, play count
Page 2: Table of contents -- plays grouped by category with page numbers
Pages 3+: One play per page -- play name as header, field diagram
(rendered server-side by loading the editor in headless Chrome with
the play data), formation name, notes, tags
- Puppeteer renders the playbook-pdf.hbs template with play data
- Return PDF as download: "{team-name}-playbook.pdf"
Practice plan PDF export:
- POST plan ID to /api/export/practice
- Server generates a PDF with the timeline and embedded play diagrams
- Header: plan name, date, total duration
- Each segment: time block, title, duration, notes, and play diagram
if the segment references a play
- Return PDF as download: "practice-plan-{date}.pdf"
8. MAIN APPLICATION CONTROLLER (public/js/app.js)
Wire everything together:
- Initialize FieldRenderer, PlayerManager, RouteDrawer, PlayLibrary,
DrillSequencer, ExportEngine on page load
- Toolbar at the top of the editor:
Left section: Sport selector (soccer/futsal/football dropdown), Grid
toggle checkbox
Center section: Mode selector (select/move, add-home, add-away,
add-marker, draw-route buttons). Draw-route shows route type sub-options
(run, pass, dribble, screen) when active.
Right section: Save button, Export PNG button, Export Playbook PDF button
- Editor layout:
Left: toolbar + canvas/SVG editor (fills remaining width)
Right: play library sidebar (320px, collapsible)
- Save dialog: when clicking Save, show a modal with inputs for play name,
category dropdown, sport (pre-filled from current field), formation name,
notes textarea, tags input (comma-separated). If editing an existing play,
pre-fill all fields and show "Update" instead of "Save".
- Keyboard shortcuts:
Delete/Backspace: remove selected player or route
Ctrl+S: save current play
Ctrl+Z: undo last action (maintain an undo stack of player/route changes)
Escape: cancel current drawing mode, deselect all
G: toggle grid overlay
1/2/3/4: switch route type (run/pass/dribble/screen)
9. STYLING (public/css/style.css)
Dark theme matching the course palette:
- Page background: #09090b
- Editor background: #111118
- Toolbar background: #141414, border-bottom: 1px solid #1e1e2a
- Sidebar background: #0d0d12, border-left: 1px solid #1e1e2a
- Text: #e5e5e5 (primary), #9ca3af (secondary)
- Accent: #0F766E (teal) for active buttons, selected items, highlights
- Button styles: #1e1e2a background, #2d2d3a hover, teal border when active
- Player tokens: team colors with subtle drop shadow
- Route paths: team color with 0.8 opacity
- Save dialog modal: centered, #141414 background, subtle box-shadow
- Field container: rounded corners (8px), subtle border (#1e1e2a)
- Sidebar play entries: hover background #1a1a24, active (loaded) has
teal left border
- Sport badges: small colored pills (soccer: green, futsal: orange,
football: brown)
- Responsive: on screens < 768px, sidebar moves to bottom as a collapsible
drawer. Toolbar wraps to two rows. Touch targets minimum 44px.
- Print styles: white background for exported PDFs
10. SAMPLE DATA (data/sample-plays.json)
Pre-load 6 sample plays so the tool is useful immediately:
a. Soccer - "4-3-3 High Press Attack" (category: offense)
- 11 home players in a 4-3-3 formation
- Routes: left winger runs wide, right winger cuts inside, striker
makes a curved run behind the defense, central midfielder passes
to the winger
- Notes: "Left winger stays wide to stretch the defense. Right winger
drifts central to create overload. Striker times the run to stay
onside."
b. Soccer - "Corner Kick Near Post" (category: set-piece)
- 11 home players + 4 away defenders positioned for a corner kick
- Routes: one runner to near post, one to far post, one short option
- Notes: "First runner near post drags a defender. Second runner
attacks the space behind. Short option if defense is packed."
c. Futsal - "3-1 Diamond Rotation" (category: offense)
- 4 outfield home players in a 3-1 diamond + goalkeeper
- Routes: circular rotation pattern -- pivot drops, wings rotate up,
fixo pushes forward
- Notes: "Continuous rotation. Ball follows the movement. Pivot
always receives to feet, never with back to goal."
d. Futsal - "Kickoff Play" (category: set-piece)
- 4 outfield home players + goalkeeper at kickoff positions
- Routes: quick pass back, wall pass through the middle, shot
- Notes: "Must execute in under 4 seconds. Surprise element is
the wall pass -- most teams expect the ball to go wide."
e. Football - "I-Formation Power Run" (category: offense)
- 11 home players in I-formation (QB, FB, HB, 2 WR, TE, 5 OL)
- Routes: pulling guard, fullback lead block, halfback follows
through the hole
- Notes: "Power run to the strong side. Guard pulls to the play-side.
Fullback kicks out the end. Halfback reads the fullback's block
and cuts upfield."
f. Football - "Punt Return Right" (category: set-piece)
- 11 home players in punt return formation
- Routes: return man catches, wall forms on the right sideline,
two personal protectors peel back to lead block
- Notes: "This one is personal. JMU punt return scheme, right
return. The wall sets up at the 30 and the return man aims for
the sideline. If the wall holds, it is a big play."
DEPENDENCIES: express, express-handlebars, uuid, puppeteer, cors
CDN LIBRARIES (loaded in HTML files, not npm):
- html2canvas: https://html2canvas.hertzen.com/dist/html2canvas.min.js
- Chart.js (optional, for practice plan time visualization):
https://cdn.jsdelivr.net/npm/chart.js
💡Start with the editor, add export later

The prompt asks for PNG and PDF export via Puppeteer. Puppeteer downloads a Chromium binary (~300 MB) and can be finicky. If you want to start light, tell the LLM to skip the Puppeteer dependency and the PDF export routes. You can always add them later with a follow-up prompt. The editor, player management, route drawing, and play library work without Puppeteer. PNG export via html2canvas works client-side with no server dependency.


What you get

After your AI CLI tool finishes, set up the project:

Terminal window
cd coaching-playbook
npm install
node server.js

Open http://localhost:3000. You should see the play editor: a soccer field on the canvas, a toolbar across the top, and a play library sidebar on the right.

First look checklist

  1. Field renders correctly: a green soccer pitch with center circle, penalty areas, goal areas, and corner arcs. Switch the dropdown to futsal (smaller court) and football (yard lines, end zones, hash marks) and verify the markings change.
  2. Add players: “Add Home Player” drops a teal circle at center field; add a few and some red away players. All tokens drag smoothly within the field bounds.
  3. Draw routes: in “Draw Route” mode, pick “pass” (dashed), click a player, then a destination — a dashed arrow appears. Try “run” (solid), “dribble” (wavy), and “screen” (thick); each looks distinct.
  4. Curved routes: hover a route, grab the white control point at its midpoint, and drag to curve it into a bezier arc.
  5. Save and load: Save with a name and category, click the play in the sidebar, and confirm every position and route restores exactly.
  6. Sample plays: the sidebar shows 6 pre-loaded plays. Open “4-3-3 High Press Attack” — 11 players in formation with routes drawn.
  7. PNG export: “Export PNG” triggers a download of the diagram as an image.

Common issues and fixes

ProblemFollow-up prompt
Canvas and SVG are misalignedThe player tokens on the SVG overlay don't line up with the field on the canvas. The SVG element needs to have the exact same dimensions and position as the canvas. Set both to the same width and height, position them both as absolute within a relative container div, and make sure neither has any padding or margin.
Players can be dragged outside the fieldPlayers can be dragged off the field into the padding area. The drag handler needs to clamp the player position to the field bounds returned by fieldRenderer.getFieldBounds(). Use Math.max(bounds.x, Math.min(bounds.x + bounds.width, newX)) for the x coordinate and the same pattern for y.
Route arrow heads don’t appearThe route lines are drawn but there are no arrow heads at the end. SVG marker elements need to be defined in a <defs> section of the SVG element. Create a <marker> with id="arrowhead", viewBox="0 0 10 7", refX="10", refY="3.5", markerWidth="10", markerHeight="7", orient="auto". Inside it, put a <polygon points="0 0, 10 3.5, 0 7" />. Then add marker-end="url(#arrowhead)" to each route path element.
Wavy dribble lines look like straight linesThe dribble route type should render as a wavy line but it appears straight. The SVG path needs cubic bezier curves that oscillate. Generate the path by stepping along the line from start to end, and at each step, offset the control points perpendicular to the line direction by alternating positive and negative amounts (amplitude 6px, wavelength 16px). Build the path string with C commands, not L commands.
Touch drag doesn’t work on mobileDragging players works with mouse but not on touch devices. Add touchstart, touchmove, and touchend event listeners alongside the mouse events. Use e.touches[0].clientX and e.touches[0].clientY to get coordinates. Call e.preventDefault() on touchmove to prevent page scrolling while dragging.

🔧

When Things Go Wrong

Use the Symptom → Evidence → Request pattern: describe what you see, paste the error, then ask for a fix.

Symptom
Drag-and-drop doesn't work at all -- players don't move
Evidence
Clicking and dragging a player token does nothing. The cursor changes to a pointer on hover but the token stays in place. No errors in the browser console.
What to ask the AI
"The SVG player tokens are not receiving mouse events because the canvas is on top, intercepting all clicks. The layering needs to be: canvas at z-index 0 (field background), SVG at z-index 1 (interactive elements). Set pointer-events: none on the canvas element and pointer-events: all on the SVG element. The canvas only needs to render -- it never needs to receive clicks. All interaction (dragging, clicking, drawing) happens on the SVG layer."
Symptom
SVG routes are offset from player positions after scrolling or resizing
Evidence
When I draw a route, it appears in the wrong location -- offset to the right and down from where I clicked. The offset gets worse as I scroll the page. If I resize the window the routes are misaligned with the players.
What to ask the AI
"The route drawing is using clientX/clientY directly instead of converting to SVG coordinates. When the page is scrolled or the SVG element is not at the top-left of the viewport, client coordinates don't match SVG coordinates. Use svgElement.getBoundingClientRect() to get the SVG's position, then subtract rect.left and rect.top from clientX/clientY. Also recalculate on window resize. Alternatively, use SVG's built-in coordinate transformation: var pt = svgElement.createSVGPoint(); pt.x = e.clientX; pt.y = e.clientY; var svgPt = pt.matrixTransform(svgElement.getScreenCTM().inverse())."
Symptom
PDF playbook export generates a blank or single-page document
Evidence
Clicking 'Export Playbook PDF' downloads a PDF but it's either blank or shows only the cover page. The individual plays are not rendered.
What to ask the AI
"Puppeteer is generating the PDF before the play diagrams render. The playbook template loads each play's diagram by re-creating the canvas and SVG in the headless browser, which takes time. In the export route, after page.setContent() with the rendered Handlebars template, add await page.waitForFunction('document.querySelectorAll("canvas").length >= expectedCount') where expectedCount is the number of plays. Then add a 2-second delay with await new Promise(r => setTimeout(r, 2000)) to let all canvases finish drawing. Set the PDF to format: 'Letter', printBackground: true."
Symptom
Play library shows plays but clicking doesn't load them into the editor
Evidence
The sidebar lists all saved plays with correct names and categories, but clicking a play does nothing. The editor stays on whatever was previously loaded.
What to ask the AI
"The click handler on sidebar play entries is calling playLibrary.loadPlay(id) but the function is not restoring the editor state. Check that loadPlay fetches the full play data from /api/plays/:id, then calls fieldRenderer to set the correct sport, playerManager.setPlayerPositions(play.players), and routeDrawer.setRoutes(play.routes). The field sport might not be switching -- call fieldRenderer.setSport(play.fieldConfig.sport) and fieldRenderer.render() before restoring player positions so the coordinate system matches."

How it works

The application uses a layered rendering architecture:

  1. Canvas layer (field-renderer.js) draws the static field background. It only redraws when the sport changes or the window resizes — Canvas renders as a flat bitmap, so a field is just a sequence of API calls (arc, moveTo, lineTo, stroke, fill).
  2. SVG overlay (player-manager.js, route-drawer.js) sits on top and handles everything interactive. Player tokens are <circle>/<text> groups; routes are <path> elements. Because each is a DOM node, it carries its own listeners for drag, click, hover, and context menu — no manual hit testing.
  3. Play Library (play-library.js) serializes the editor state (positions + routes + field config + metadata) to JSON and persists it through Express. Loading reverses the process: fetch, set the sport, place players, draw routes.
  4. Drill Sequencer (drill-sequencer.js) is a separate page that references saved plays by ID and fetches the diagram data at export time. The timeline is a list of draggable segments with duration and notes fields.
  5. Export Engine (export-engine.js) bridges the editor and the file system. PNG uses html2canvas to composite the layers; PDF sends play data to the server, where Puppeteer renders the Handlebars template in headless Chrome.

Customize it

Add animated play replay

Add a "Replay" button to the editor toolbar. When clicked, it animates the
play step by step: players start at their original positions (ghost positions)
and move along their routes to their destination positions over 2 seconds.
Use requestAnimationFrame to animate the SVG player tokens along their route
paths. Show routes drawing progressively (the path stroke-dashoffset technique)
so the lines appear to draw themselves as the players move. Add a speed control
(0.5x, 1x, 2x) and a replay/pause button. This is like watching coach's film
but for a diagram -- it shows the timing and sequence of movement.

Add formation templates

Add a "Formations" dropdown to the toolbar that lists common formations for
the current sport. For soccer: 4-4-2, 4-3-3, 3-5-2, 4-2-3-1, 3-4-3. For
futsal: 3-1, 2-2, 1-2-1, 4-0. For football: I-Formation, Shotgun, Spread,
4-3 Defense, 3-4 Defense, Nickel. When a formation is selected, place the
correct number of home team players at standard positions for that formation.
If players already exist, ask to replace or merge. Store the formation
definitions in a formations.json file.

Add team roster integration

Add a "Roster" tab in the sidebar that lets the coach enter their team roster:
player name, jersey number, preferred positions. Store the roster in a
roster.json file via the API. When adding players to the field, show the
roster as a dropdown so the coach picks a real player instead of a generic
number. When a play is saved, the player tokens reference roster entries
by ID so if a name or number changes, all plays update automatically.

Add practice plan sharing via email

Add a "Share" button on the practice plan page. When clicked, generate the
practice plan PDF and open a mailto: link with the PDF as context. The email
subject should be "[Team Name] Practice Plan - [Date]" and the body should
list the segment titles and durations as plain text. For actual file
attachment, add a nodemailer integration with SMTP config from environment
variables (SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS) and send the PDF
as an attachment to a comma-separated list of email addresses.

Try it yourself

Work the First Look checklist above: render all three fields, drag players into a formation, draw and curve a few routes, then save and reload a play and export it as a PNG. Then go one step further — open the practice plan page, add 4-5 segments mixing warmup blocks, saved plays, and a scrimmage, set durations, and confirm the total practice time counter updates as you go.


Key takeaways

  • Canvas and SVG serve different roles. Canvas is fast for static graphics like a field background; SVG is ideal for interactive elements because each is a DOM node with its own listeners. Layering them — canvas behind, SVG in front — gives you both.
  • Coordinate transformation is the core skill in any visual editor. Converting between field coordinates (meters or yards) and screen pixels is what makes dragging, drawing, and exporting work. Every visual tool, from Figma to Google Maps, solves this same problem.
  • Serialization turns visual state into portable data. Saving a play converts positions and paths into JSON; loading reverses it. This serialize-deserialize pattern is how every design tool, game save, and document editor works.
  • The export pipeline bridges the screen and the real world. A PNG goes to the group chat, a PDF goes to the printer — the same data, rendered for different outputs. It’s the same dual-format pattern from the pulse reporter.
  • Domain expertise is the differentiator, not code. The LLM wrote the Canvas calls, the SVG handlers, and the Express routes. Nick supplied the knowledge that a 3-1 diamond uses continuous rotation, that corner kicks need a near-post runner, and that a punt return wall forms at the 30. The code is generic; the coaching knowledge is what makes the tool useful.

KNOWLEDGE CHECK

The coaching playbook tool uses an HTML5 Canvas for the field background and an SVG overlay for player tokens and routes. Why does the route drawer use SVG paths instead of drawing routes directly on the Canvas?


The complete Nick E. Playbook

Eight lessons. Eight tools. Here is everything you built:

LessonToolWhat it does
1Resident Response StudioConverts rough notes into polished, brand-appropriate resident communications
2Morning Maintenance Triage BoardParses overnight work orders, prioritizes by urgency, generates a daily action queue
3Renewal Risk & Outreach PlannerScores renewal risk, segments residents into cohorts, drafts personalized outreach
4Vendor & Make-Ready OrchestratorSchedules unit turn workflows with dependency logic, vendor calendars, and delay alerts
5Occupancy & Revenue Pulse ReporterOne-command weekly KPI report with trends, charts, and auto-generated narrative
6The Daily Ops Command CenterUnifies all five property tools into a single daily operating hub with Morning Run automation
7Kids Sports Schedule CoordinatorMulti-kid activity calendar with conflict detection, carpool planning, and weekly family view
8Coaching Playbook BuilderVisual play diagramming tool with drag-and-drop formations, movement routes, and drill sequences

Lessons 1 through 6 built the operational backbone of the Concord Crystal City — resident communications, maintenance triage, renewal outreach, vendor scheduling, weekly reporting, and a command center that ties it together with one click at 7 AM. Lessons 7 and 8 took the same workflow and applied it to Nick’s life outside the Concord: a family calendar that prevents collisions between two kids, three sports, and two parents, and a coaching playbook that replaces whiteboard photos with clean, exportable diagrams.

The point is not the eight specific tools. It is that the skill transfers to any domain Nick understands — property management, youth sports, family logistics, kinesiology, JMU alumni events. The workflow is identical every time: start with a problem, describe it precisely, let the AI write the code, test it, iterate, deploy.

Nick started this track having never written a line of code. He ends it with a full property operations toolkit, a family calendar, and a coaching playbook — all built by describing what he needed in plain English. The code is not the product. The product is the ability to see a problem, reach for a CLI tool, and have a working solution in 30 minutes. That ability does not expire, require a subscription, or need a developer. It belongs to Nick.

The Concord runs a little smoother. Practice is a little more organized. The kids can actually read the plays. And the next time Nick sees a problem that looks like it needs software, he already knows what to do.

Search lessons