// dynamic-help-system.js - Context-aware help system for tournament manager
/**
* HELP CONTENT DATABASE
* Organized by page and context with progressive disclosure
*/
const HELP_CONTENT = {
// Setup Page Help
setup: {
title: "Tournament Setup",
overview: "Start a tournament, or pick one up where you left off. The tournament you're running is at the top, with its next step; starting a new one is a quiet line under it. The panel to act on is framed: the tournament while it is New or Active; once it is completed, the New tournament form moves to the top, open and framed, with Create tournament as the main button (the same when nothing is loaded).",
sections: {
current: {
title: "Current Tournament",
content: `
The loaded tournament, with its status (New, Active or Completed; grey, orange or green) and a short fact beside it: that the bracket isn't drawn, how many matches are completed, or who won. Under the name: players, bracket size, and the matches being played now.
Next step changes with the status:
New: Register players, then draw the bracket from the bracket page
Active: Open bracket
Completed: Open in Analytics (or Add to Analytics, if it isn't there yet)
Export tournament: Save the tournament as a JSON file, for backup or to move it to another computer.
Backup to server: Shown when the Tournament Manager runs on a server. Uploads the tournament, or a tournament file.
Reset tournament: Clears all bracket progress and keeps the players. You type the tournament's name to confirm. Not offered once a tournament is completed: its results are final (to correct one, Developer Console → Toggle Read-Only, then undo the match).
⚠️ Warning: Reset permanently deletes all match results and standings.
`
},
creation: {
title: "New Tournament",
content: `
Click + New tournament to open the form. Once the tournament is completed (or when nothing is loaded), the form is always open at the top, under Start a new tournament.
Enter a name (e.g., "Thursday Cup #42")
Check the date (defaults to today)
Click Create tournament. It becomes the current tournament at the top, and its next step is Register players
Import a tournament file: Load a tournament from an exported JSON file. It is next to + New tournament.
`
},
recent: {
title: "Tournaments",
content: `
Every tournament on this computer, newest first, with date, format, players, status and Analytics.
Load: Switch to that tournament
×: Delete it permanently (asks first)
Loaded: Marks the current tournament, which can't be deleted
Analytics:View opens it in Analytics; + Add adds a completed tournament that isn't there yet
Server: When the Tournament Manager runs on a server, a Server tab lists the tournaments shared there, with Import (or Re-import if it's already on this computer).
Storage: How full this computer's browser storage is. Click it for details.
The current tournament's completed matches, latest first.
The winner in green, then the score
Lane, referee, and where both players went: the next match, or their final placing
Click a match to see its details. Walkovers are greyed out
`
}
}
},
// Registration Page Help
registration: {
title: "Player Registration",
overview: "Pick tonight's players from the club's player database and mark who has paid. Once the bracket is drawn, the page shows the Leaderboard.",
sections: {
adding: {
title: "Adding Players",
content: `
Type in Add from the database: it searches short, first, last and previous names
Click a player, or press Enter to add the first match
Not in the database yet? + New player (or Enter when no one matches) creates them and adds them
Click a player in Players to switch between Paid and Unpaid; × removes an unpaid player
Next step shows the player, paid and unpaid counts, and what's needed before the draw. Open bracket works once there are at least 4 paid players. Only paid players go into the bracket.
Payment QR code: Add payment.png to the images/ folder (Swish, bank details, etc.) and it is shown next to the players. Without it, the panel is hidden.
`
},
database: {
title: "The Player Database",
content: `
Every player has a permanent ID, a short name (what the bracket, Match Controls, the Chalker and the Leaderboard show; it must be unique) and a first and last name. Analytics follows the ID, so a player's results stay together whatever they are called.
Edit: change the names. A changed short name is kept under Previous names, so older results under it still count for the player.
Merge…: for duplicates (“Erik” and “Eirik”). Choose whose details to keep; the other's tournaments and Analytics become theirs. This can't be undone. Two players who played in the same tournament are two people, so they can't be merged.
Archive: hides a player from the list for new tournaments. Players who have played are kept for Analytics, so they can be archived but not deleted. Players who never played can be deleted.
Import from file: adds the players from a tournament export file. It never replaces the database.
The database is kept on this computer and travels with every tournament export and upload, so a shared Analytics instance shows the current names.
Once the bracket is drawn, click a player in the Leaderboard to record or correct:
Short Legs: Legs finished within the dart count set by Short Leg Threshold on the Config page (default 21)
High Outs: Finishing scores of 101+ points
180s: Maximum dart scores
Tons: Any score of 100+ points
Statistics count towards the points.
Player arrived late? Under the player list once the bracket is drawn. It explains how to register a late arrival from the Developer Console.
`
},
results: {
title: "Leaderboard",
content: `
Rank: Tournament placement (1st, 2nd, 3rd, etc.)
Points: Placement plus achievements, using the points in Global Settings
Legs Won/Lost: From completed matches
Export CSV: The Leaderboard as a spreadsheet, for records or league management.
Export JSON: Results and match history, for importing into other systems.
Rankings update as matches are completed.
`
}
}
},
// Tournament Page Help
tournament: {
title: "Tournament Management",
overview: "Run the tournament bracket, manage matches, and track live progress.",
sections: {
bracket: {
title: "Tournament Setup & Bracket Generation",
content: `
Setting Up Your Tournament:
Open the Match Controls tab in the header
Before the draw it shows the players: click a name to mark them paid or unpaid, click × on an unpaid player to remove them, or add a player (found in the player database, or created). Everyone must be paid before the draw: the draw buttons say how many are still unpaid
If seeding is on (Global Settings → Tournaments → Seeding), tick Seed the draw by ranking to keep the best players apart. The panel shows which earlier tournaments the ranking comes from, and who would be seeded
When ready, pick a format under Pick a format (each card says what it means for tonight's players; the picked one shows how it works, roughly how many matches, and its options), then click Draw. It opens on the format of your last tournament
Tournament Formats:
Single Elimination Cup: One loss and you're out — 4 to 48 players
Double Elimination Cup: Losers get a second chance through the backside — 4 to 48 players
Round Robin: everybody plays everybody — in groups (up to four players, or five or six as set) followed by an A cup and a B cup (6 to 32 players), or in one group where the table decides (3 to 8 players), as set in Global Settings → Round Robin (see Round Robin below)
Swiss: a fixed number of rounds, each pairing players with the same record who haven't met; nobody is knocked out, and the top four finish with semifinals and a final (or the table decides). 4 to 48 players
Bracket Sizes:
4 players → 4-player bracket (SE only)
5-8 players → 8-player bracket
9-16 players → 16-player bracket
17-32 players → 32-player bracket
33-48 players → 32-player bracket with qualifiers first (see Qualifiers below)
Qualifiers (33 to 48 players): the players above 32 make it in through qualifier matches (Q1, Q2 …) before round 1, one for each player above 32: 40 players play 8 qualifiers, 48 play 16. The players in them are drawn at random (with seeding, never the seeds; with All, the lowest-ranked), and each winner takes the place in round 1 that shows "Winner Q3". A qualifier only decides who gets to play: the loser is not qualified (placed 33rd–48th), and nothing in a qualifier counts, for the winner either (no achievements, no matches or legs in the statistics, never in Analytics). Its length is the format's regular rounds. Double elimination with qualifiers is drawn with the finals in the middle.
Cup and Plate: pick the Cup and Plate card before you draw (from 5 players). The bracket is then the Cup, and every round 1 loser goes on to the Plate, a second knockout of half the size, at a fixed place: the losers of round 1 match 1 and 2 meet in the Plate's first match, and so on, so a Plate match can start as soon as its two round 1 matches are played. A bye's place in the Plate is a walkover. The Plate is drawn back to back with the Cup, the two finals facing each other. Its places follow the shared places (with 8 players the Plate's finalists are 5th–6th, its bronze pair 7th–8th), and the finished view names the Plate winner. Match lengths and bronze finals as single elimination.
Swiss: pick the Swiss card, with Rounds (by players: 3 for 5 to 8, 4 for 9 to 16, 5 for 17 to 32) and Finish (semifinals and final for the top four, or the table decides). Round 1 is random (with seeding: the top half against the bottom half); every later round is drawn by itself when the last match of the round before is entered, pairing the table 1st v 2nd, 3rd v 4th, skipping anyone already played. An odd number gives one bye a round (a win), never twice to the same player. The table: wins, opponents' wins, leg difference, legs won. Undoing a result of the round just finished takes back the next round, as long as none of its matches has started.
💡 Tip: Match Controls adapts based on tournament state - use it for both setup and active tournament management!
Left: tournament name and date, and links to Setup, Registration, Config and Analytics
Centre: the tabs Bracket | Match Controls (plus Console when the Developer Console is enabled in Config), and Leaderboard. The left and right arrow keys switch between Bracket and Match Controls. Match Controls opens over the bracket: click the bracket showing to its left, ← Bracket, or press Esc to see the bracket
Right:Finals Right | Middle (Groups | Cups in a Round Robin tournament with groups and cups), Fit all, zoom (− / +) and the clock. On Match Controls and the Console only the clock shows
Status line: players, matches played, walkovers, live and ready, with the colour legend on the bracket
Moving around:
Pan: drag the bracket
Zoom: scroll or pinch, at the pointer. Fit all shows the whole bracket, and is as far out as you can go
Hover a match: zoomed out, it is magnified; zoomed in, a tip shows where the winner and loser go
Selecting a match:
Click a match to highlight its lines and the matches it is connected to. The view follows if they are off-screen; markers at the edge point to the rest
Follow [player] traces that player through the bracket. Click it again to stop
Edit match appears on a match you completed by hand: change the score and achievements, and the winner when the matches it feeds are not played
Undo match appears when the result can be undone
Match Controls switches to Match Controls and points out that match
Click empty space or press Esc to clear the selection
Finals Right | Middle: the finals at the right edge, or in the middle: between the frontside and backside in double elimination; in single elimination, the two halves of the bracket face a final in the middle, with the bronze final under it. Also in Global Settings → Bracket.
In Match Controls, click the winner's name (Wins) on the match's lane tile
Enter leg scores (optional but recommended)
Click "Confirm Winner"
Scan Results QR: If the match was scored on the Chalker, click Scan Results QR in the confirmation dialog to import the result and player achievements directly from the Chalker's QR code — no manual entry needed.
Correcting Match Results:
Wrong score, achievements or winner: select the match in the bracket and click Edit match. The winner can only change while none of the matches it feeds are live or played. Not available for matches reported from the Chalker, or in a completed tournament
Select the match in the bracket and click Undo match
Only safe matches can be undone - none of the matches it feeds may be live or played
Multi-step correction: undo several matches by working backwards through the bracket
💡 Tip: Undo match only appears when it is safe to use.
Use the Lane and Referee dropdowns on each match in Match Controls
System prevents multiple matches with same lane/referee
Players in LIVE matches cannot be selected as referees
Players in LIVE matches may be selected as referees in their own match
Completed matches will release their assigned lanes/referees
Lane/referee history is retained for completed matches
Configure max lanes in Config page
In the bracket: a live match shows its lane (L1, L2 …); hover it to see the referee.
QR Integration: When a lane or referee is assigned, it is automatically included in the Chalker assignment QR code. The Chalker displays the lane in its info bar and the referee name throughout the match.
💡 Tip: Assign lanes to organize physical dartboard usage and avoid conflicts.
`
},
groups: {
title: "Round Robin",
content: `
Two ways to play (Global Settings → Round Robin): Groups and cups or One group. A tournament keeps the setting it was drawn with.
One group: everybody plays everybody in a fixed order, each match with a planned referee from the group; the table decides the placings when the last match is played. 3 to 8 players.
Groups and cups: players are drawn into groups of up to four, or five or six as set in Largest group (as few groups as fit, an even number with Top two; by ranking when seeding is ticked, otherwise at random). Everybody plays everybody in their group. Then the A cup and the B cup: single elimination with a bronze final, drawn with the final in the middle. To the A cup: Top half (the default): everyone ranked across the groups and split into two cups of the same size, or the top two of each group (the rest play the B cup).
The group stage:
Each group plays in a fixed order, and each match has a planned referee from the group. Match Controls shows each group's next matches with the referee filled in; it is set when the match starts, if that player is free. Change it like any referee
A player can't start a match while they are playing another one
The group table: wins, then leg difference, then legs won, then head-to-head. Players still level when the group has played every match are marked level; the group seed decides unless you click ▲ in Match Controls' group tables before drawing the cups
Groups on the bracket page shows every group's table and matches. Click a match that hasn't been played to open it in Match Controls; a played one has Undo while it can be undone
Draw the cups: when the last group match is played, Match Controls shows both cups' seeded fields: group winners first, then runners-up, and so on, ranked across the groups by win rate, then leg difference per match (with Top half the A cup takes the first half of that order; with top two, the B cup has the thirds, then the fourths, and so on). Switch off Play the B cup to play without it (whether it starts on is a Global Setting); then the A cup takes the top two of each group, also with Top half, and the rest are placed after it by their group results. Nothing is drawn until you click Draw the cups.
The cups: A cup and B cup side by side in Match Controls, on the same lanes. Round 1's referees are planned (players with a bye, then the players from the bottom of the round, then the first losers), later rounds take losers as they come. Cups on the bracket page shows both.
Undo: group results can be undone until the cups are drawn. Undo the cup draw (Match Controls) is there until a cup match has been started or played; it opens the group stage again. Cup matches undo as in any bracket.
Placings: everyone is placed. A cup: 1st and 2nd from the final, 3rd and 4th from the bronze final. B cup: its final pair 5th–6th, its bronze pair 7th–8th. Then the A cup's other losers (the latest round first), then the B cup's, each round ordered by group results, then anyone who played no cup. Places run on as shared places (9th–12th, 13th–16th …), so places a small B cup can't fill go to the A cup's quarterfinal losers; without a B cup they take 5th–8th. Everyone gets the points for taking part.
Waits: a match waits (Match Controls says why) while one of its players is the referee of an earlier match in the group or cup, or while its planned referee isn't known yet (the loser of a match still to be played). Choose another referee to start it anyway. A referee chosen for a match that hasn't started is only a plan: it blocks no one until that match is live.
Match length: group matches and the cups have their own settings (Global Settings → Match length → Groups and cups): Cup rounds (every round before the semifinals), Cup semifinal, Cup bronze final and Cup final, the same for the A and the B cup.
The Match Controls tab in the header, or Match Controls in the selection bar after clicking a match
The page starts on Match Controls; turn off Start on Match Controls in Config to start on the bracket
Switching tabs keeps the bracket where you left it
The one place where matches are run
While the tournament runs:
Lanes: one tile per live match, by lane: the players as Wins buttons, how long the match has been on the board, its lane and referee (both can be changed while it is live), the handover (QR, Transfer, or the green Result ✓ when a Chalker has sent one back) and Stop. A live match without a lane gets a No lane tile.
Free: the free lanes on one line, with the next ready match. Click a free lane to start that match there.
Ready to start: the matches that can start, by round (frontside and backside side by side), each with Lane, Referee and Start. A player who is refereeing another match is marked, and Start waits until that is sorted.
Referees: live matches without a referee first, then recent losers, recent winners and who refereed recently. Players in live matches aren't suggested.
Scan QR results is in the Lanes heading while matches are live (QR handover).
When the tournament is finished: the podium with Most 180s, Shortest leg and Highest out under it, and a plaque with the club, tournament and date; then 4th, 5th–6th and 7th–8th (the places that still score placement points) and the night's awards, one per row (Most points, Most matches won, Backside run, Best average, Most tons, Lollipops, Busiest lane), the night in numbers, and Tournament Analytics.
Real-time Updates: Interface refreshes automatically after each action.
`
}
}
},
// Config Page Help
config: {
title: "Global Settings",
overview: "Settings for this computer, used by every tournament. Change what you need, then click Save changes in the bar at the bottom. On a shared Analytics instance the page is called Custom Settings and shows the Points only: members try other point values there, kept in their own browser, and see them in Analytics with Custom.",
sections: {
application: {
title: "Saving and the Page Layout",
content: `
Sections:Club, Tournaments, Match day, Bracket, Chalker, Server & backup and Developer. Click a name in the list on the left to jump to it.
Saving:
Any change brings up a bar at the bottom: Discard or Save changes. Nothing is saved until you click Save changes.
An orange dot in the section list marks each section with unsaved changes.
Leaving the page with unsaved changes asks first: Stay, Discard and continue or Save and continue.
Club: the club name is shown in the page title and at the top of the bracket. For a logo, put a square logo.png, logo.jpg, logo.jpeg or logo.svg in the images folder.
Match day:
Lanes: how many dartboards you have. Under Lanes not in use, click a lane to leave it out of lane assignment.
Confirm the winner: ask for the score and statistics when a winner is chosen. When off, matches complete at once with nothing entered.
Start on Match Controls, New players are paid, Referee suggestions.
Bracket:Finals position, Right or Middle (double elimination). The same setting as the Finals toggle in the bracket header.
Developer:Developer Console adds a Console tab to the Tournament Bracket page, with diagnostics, validation checks, lane usage and transaction tools.
Round Robin (Tournaments) shows while the format is ticked under Formats to offer.
Structure:Groups and cups (groups, then an A cup and a B cup; 6 to 32 players) or One group (everybody plays everybody, the table decides; 3 to 8 players)
Play each other (one group): Once (the default) or Twice, a double round robin: when everybody has played everybody once, the return round follows in the same order with the players swapped; both meetings count in the table. It can also be chosen at the draw
Largest group:4 (the default), 5 or 6. The draw makes the fewest groups it can (an even number with Top two), so a larger limit means fewer, longer groups (a group of 4 plays 6 matches, of 5 plays 10, of 6 plays 15)
To the A cup:Top two of each group, or Top half: everyone ranked across the groups and split into two cups of the same size, so a small field doesn't leave a tiny B cup. Without a B cup, the A cup always takes the top two of each group
Group rematches in cup round 1:Allow (top seed against bottom seed, as drawn) or Avoid (a seed gets the nearest opponent from another group, where possible)
Play the B cup: whether the switch at Draw the cups starts on
A tournament keeps the settings it was drawn with; a change here applies to the next draw.
`
},
seeding: {
title: "Seeding",
content: `
Seeding (Tournaments) lets the draw keep the best players apart, in every format. Off: the draw is always random. Available: the option is offered, and you tick it. On: it is ticked to start with.
Seeded players is how many of the best players are seeded, as a share of the bracket: 1/8, 1/4 or 1/2 (2, 4 or 8 players in a 16-player bracket). All seeds everyone with a ranking, so the top seed meets the bottom seed. Seeded players can't meet in round 1, and everyone else is drawn at random. You can change it for each draw.
Byes go to the best seeds. Players with no ranking are never seeded, and only get a bye if there are more byes than seeds.
Round Robin seeds every ranked player into the groups in snake order (the best in group A, the second in B, …, then back again), or into the one group's order; Seeded players doesn't apply. The cups are seeded from the group tables.
The ranking is the Leaderboard's points over earlier tournaments with the same name, from this browser. The name word is taken from the tournament's name ("Måndagscup", "Måndagscup week 43" and "NewTon Måndagscup" are the same cup), and tournaments with "Final" in the name never count. It looks at the current half-year, or the previous one for the first match of a season. In Match Controls you can change the word or the period, and tick tournaments by hand.
💡 New players are never seeded; Match Controls names them. With no earlier tournaments in this browser, or none ticked, the draw is random. To seed from the club's history, restore a backup on this computer first.
Formats to offer (Tournaments) controls which formats can be picked in Match Controls' Pick a format when starting a tournament. A format not offered is still shown there, greyed out. Cup and Plate is one of them.
Swiss (Tournaments, while Swiss is offered): Rounds (by players, or a fixed 3 to 6) and Finish (semifinals and final for the top four, or the table decides): how the draw starts; both can be changed for each draw.
Untick the ones your club never plays so they can't be picked by mistake. At least one format always stays available.
💡 Existing tournaments are unaffected: a tournament already created in a hidden format still opens, renders and exports as normal.
`
},
matches: {
title: "Match Length",
content: `
Match length (Tournaments) sets best-of legs per round, with − and + (Bo1 to Bo21).
Double elimination: regular rounds, frontside and backside semifinal, backside final, grand final
Single elimination: regular rounds, quarterfinal, semifinal, bronze final, final
Swiss: the rounds; the top four's semifinal, bronze final and final
Groups and cups: group matches; cup rounds (every round before the semifinals), cup semifinal, cup bronze final, cup final, the same for the A and the B cup. The cups' lengths are taken when the cups are drawn
Reset to defaults fills in the standard lengths; click Save changes to keep them. New lengths apply to matches that haven't started.
`
},
points: {
title: "Points",
content: `
Points (Tournaments):
Placing: points for taking part, and for each final placing
Also for qualifier losers (under Taking part): whether a player who loses a qualifier (33 to 48 players) gets the Taking part points too. On by default; it applies to earlier tournaments too in Analytics' Custom point mode
Achievements, each: 180s, high outs (101+), short legs and tons (100+)
Reset to defaults fills in the standard values; click Save changes to keep them.
Chalker settings are sent to the Chalker when a match starts:
Game: 301, 501, 701, or Other for a starting score of your own (2–1001; unusual scores make larger QR codes)
Most rounds per leg and Short leg (darts)
Handover, how a match reaches the Chalker:
QR code: show a code on the match for the Chalker to scan, and scan its result code back. Works offline.
Network (experimental): send the match to a Chalker on the same local network; the result comes back on its own and is counted on the Match Controls tab until you accept it. Needs the Docker image, with the Chalker opened from your own server.
Only when running the Docker image. These settings do nothing if you opened the app as a file.
Back up finished tournaments: upload a tournament to the server when it finishes. This fills Shared Tournaments, and lets a separate Analytics instance collect a season's results.
Allow deleting tournaments: off by default. Shows delete buttons for shared tournaments and in the Analytics register.
Remote backup: optional second destination: its address, and its API key if that server has one (NEWTON_API_KEY). Test connection checks both before you rely on them: whether the server answers, and whether it accepts the key. It sends nothing to keep. Live copy while it is played (off by default) sends a slim copy of the tournament to the server after every change, readable by anyone who can open the server, until the finished tournament is backed up. If the upload at the end of a tournament fails, Setup and the bracket page say so (Not uploaded): it is tried again by itself while the page is open, and Upload now tries it at once and tells you what happened. Once the key is accepted, the address and key are locked so nobody changes them by accident; Change… unlocks them again (the key is cleared, so test the new one).
💡 If Shared Tournaments stays empty on a Linux server, the container could not write to its tournament folder. Its startup log says so and gives the command to fix it.
`
}
}
},
// Analytics Page Help
history: {
title: "Analytics",
overview: "Explore your tournament history — standings across a season, player records, and every completed match. Analytics only ever reads your results; it never changes them.",
sections: {
views: {
title: "The Four Views",
content: `
Buttons along the top switch between them. All four show the same set of tournaments — whichever ones the Lens currently selects.
Live now — when a tournament is being played and its club shares a live copy, a line at the top of Analytics says so, with Watch: a list of what is being played (with the lane), what is up next and the latest results with what each recorded, updating by themselves. Bracket opens the bracket and Leaderboard the leaderboard, both read-only; select a finished match in the bracket to see what it recorded. When the tournament is over it says so, and the finished tournament is in Analytics when you come back here.
Dashboard — the headline numbers: tournaments, matches, unique players, total points, 180s, the highest checkout and the shortest leg. Click a card to jump to the view behind it. Under them, the Leaderboard's top 10 and the latest tournaments.
Leaderboard — the standings. One row per player, sortable by any column.
Players — the player list; tick one to see their profile, or several to compare them.
Register — the underlying records: every tournament, and every match within them.
`
},
lens: {
title: "The Lens — Choosing What You Are Looking At",
content: `
The Lens decides which tournaments the numbers are drawn from. Everything else follows it — Dashboard, Leaderboard and Players all reflect whatever the Lens currently selects.
It sits at the top of every view, under the view buttons:
Tournament name — narrows it as you type
From / to — limits it to a date range
Half-year buttons — the current or previous half-year in one click, which is usually what a season is
Show all — back to everything
To leave out single tournaments, click Choose tournaments and untick them in the Register's tournament list.
💡 To the left of the Lens it says how many tournaments are counted, and why — so if a number looks wrong, check there first. It is the usual explanation. The bar turns orange when you are not seeing everything.
Analytics always opens on the current half-year (the previous one until the new half-year has its first tournament). What you change lasts until you leave the page.
The Points buttons sit beside the view buttons, and change every figure on screen as soon as you touch them.
As played — each tournament scored as it was played: its own point values, with placement and attendance points. The honest record, and what Analytics opens with.
Custom — opens a small panel:
Point values: from Global Settings (Custom Settings on a shared Analytics instance; useful for "what would last season look like under this year's rules?"), or as each tournament was played
Count: placement points and attendance points, each on or off. Turn attendance off to see standings on performance alone; turn placement off to see who simply turns up
The button then says what is custom, e.g. Custom: Global Settings values, no attendance (Custom: custom values on a shared Analytics instance). Your choice is kept until you leave the page.
Achievement points — 180s, high outs, short legs, tons — always count.
💡 None of this alters a stored result. It changes how the totals are worked out for display, nothing more.
One row per player across the selected tournaments. Click any column heading to sort by it. In points order, a line marks the top 16.
Placements — 1st, 2nd, 3rd, 4th, 5-6th and 7-8th finishes
Achievements — 180s, high outs and short legs
Personal bests — Best Out (highest checkout) and Best Leg (fewest darts)
Avg — three-dart average. Only matches scored on the Chalker have the detail to calculate this, so it is blank for manually entered results
Matches W / L and Legs W / L — won and lost
Export CSV and Export JSON save the table as it currently stands — same Lens, same point settings — for a spreadsheet or a club website.
`
},
players: {
title: "Players",
content: `
The player list shows everyone who appears in the selected tournaments, with tournaments played and their win/loss record. Tick one player to see their profile, or up to six to compare them; the box at the top ticks the six best. The page opens on the best player in the lens (or those sharing the top points).
The profile has the player's Leaderboard figures, how often they finished in each place, and every tournament they played, with their placing and points. Click a tournament to open it.
Charts show how the player has done over the tournaments in the Lens: their last 10 finishes, then six cards (Position in the standings, Points, Finishes, Average, Matches, Highlights). Click a card for its chart; hover or tap a tournament for the figures. + Compare with adds up to five more players, The field adds the median of everyone who played (Points and Average), and Full screen gives the chart the whole screen. Ticking several players in the list compares them the same way.
Players are matched by name across tournaments, so someone entered as "Dave" in one and "dave " in another is treated as the same person. Genuinely different spellings are not — consistent names are worth the small effort at registration.
Each row shows a finalized tournament — date, format, players, matches, points and the winner. Unticked tournaments are left out of every view.
Click a tournament to open its matches
Delete: Permanently removes the tournament and all its matches. You must type the tournament name to confirm — this cannot be undone
Edit: Correct a player's 180s, tons, lollipops, high outs and short legs for that tournament. Corrections are saved on the server and apply on every device; the recorded tournament is not changed. Reset Player goes back to what was recorded. Docker only, and not shown on the public Analytics page
`
},
matches: {
title: "Match Detail",
content: `
Click any tournament to see its matches. Click a match to open the detail view.
Chalker matches: Show full leg-by-leg breakdown — visit scores, first thrower, checkout darts
Achievements: 180s, tons, high outs, short legs — shown when recorded
Export Register: Download the full match history as a JSON file — useful for backups or moving to a new device.
Import Register: Merge a previously exported file into the current history. Existing records with the same match ID are overwritten; everything else is untouched.
`
}
}
},
// Contextual Help for Common Scenarios
scenarios: {
title: "Common Scenarios",
overview: "Guidance for getting started, and for the problems that come up most often.",
sections: {
firstTime: {
title: "First Time Setup",
content: `
Welcome to Tournament Manager!
Quick Start Guide:
Setup: Create your first tournament with name and date
Registration: Add players and mark them as paid
Tournament: Generate bracket and start managing matches
Config: Customize point values and settings (optional)
💡 Need help? Each page has specific guidance - click the help button (?) for detailed instructions.
If tournament is in progress, use "Reset Tournament" first
Match won't start:
Both players must be determined (not "Awaiting Player")
Previous matches may need to be completed first
Check for walkover situations
Points not calculating correctly:
Verify point values in Config page
Check player statistics are entered correctly
Ensure tournament is properly completed
`
}
}
}
};
/**
* HELP SYSTEM STATE MANAGEMENT
*/
let helpState = {
isVisible: false,
currentPage: 'setup',
currentSection: null,
position: { x: 100, y: 100 }
};
/**
* INITIALIZE HELP SYSTEM
* Call this after DOM is loaded
*/
function initializeHelpSystem() {
console.log('🔧 Initializing dynamic help system...');
// Initialize help icons with tooltips
initializeHelpIcons();
// Create help modal
createHelpModal();
// Setup context detection
setupContextDetection();
// Add keyboard shortcuts
setupHelpKeyboardShortcuts();
console.log('✓ Help system initialized');
}
/**
* INITIALIZE HELP ICONS
* Add click functionality to help info icons
*/
function initializeHelpIcons() {
const helpIcons = document.querySelectorAll('.help-info-icon');
helpIcons.forEach(icon => {
const pageId = icon.getAttribute('data-page');
if (!pageId) return;
// Click icon to open help
icon.addEventListener('click', (e) => {
e.stopPropagation();
showHelp(pageId);
});
});
}
/**
* CREATE HELP MODAL
* Build the floating help interface
*/
function createHelpModal() {
const modal = document.createElement('div');
modal.id = 'dynamicHelpModal';
modal.className = 'help-modal';
modal.style.cssText = `
display: none;
position: fixed;
top: 50px;
right: 50px;
width: 400px;
max-height: 80vh;
background: #ffffff;
border: 2px solid #ff6b35;
border-radius: 12px;
box-shadow: 0 8px 30px rgba(0,0,0,0.3);
z-index: 1000;
overflow: hidden;
resize: both;
min-width: 350px;
min-height: 200px;
`;
modal.innerHTML = `
Tournament Help
Press F1 for help • ESC to close
`;
document.body.appendChild(modal);
// Setup modal interactions
setupHelpModalInteractions(modal);
}
/**
* SETUP HELP MODAL INTERACTIONS
*/
function setupHelpModalInteractions(modal) {
const header = modal.querySelector('.help-header');
const closeBtn = modal.querySelector('#helpClose');
const minimizeBtn = modal.querySelector('#helpMinimize');
const scenariosBtn = modal.querySelector('#helpShowScenarios');
// Make modal draggable
let isDragging = false;
let dragOffset = { x: 0, y: 0 };
header.addEventListener('mousedown', (e) => {
isDragging = true;
dragOffset.x = e.clientX - modal.offsetLeft;
dragOffset.y = e.clientY - modal.offsetTop;
modal.style.cursor = 'grabbing';
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const newX = e.clientX - dragOffset.x;
const newY = e.clientY - dragOffset.y;
// Keep modal within viewport
const maxX = window.innerWidth - modal.offsetWidth;
const maxY = window.innerHeight - modal.offsetHeight;
modal.style.left = Math.max(0, Math.min(newX, maxX)) + 'px';
modal.style.top = Math.max(0, Math.min(newY, maxY)) + 'px';
modal.style.right = 'auto'; // Remove right positioning
});
document.addEventListener('mouseup', () => {
isDragging = false;
modal.style.cursor = 'auto';
});
// Close button
closeBtn.addEventListener('click', hideHelp);
// Minimize button
minimizeBtn.addEventListener('click', () => {
const content = modal.querySelector('.help-content');
if (content.style.display === 'none') {
content.style.display = 'block';
minimizeBtn.textContent = '−';
modal.style.height = 'auto';
} else {
content.style.display = 'none';
minimizeBtn.textContent = '+';
modal.style.height = '60px';
}
});
// Scenarios button
scenariosBtn.addEventListener('click', () => {
showHelpSection('scenarios', 'troubleshooting');
});
}
/**
* Point the help system at a new page (6.4).
*
* Called from showPage() — the single place that moves the `.page.active` class —
* so the help system learns about a navigation directly instead of inferring it.
* This replaced a MutationObserver on the whole body subtree (childList + subtree +
* class attributes), which woke on *every* DOM change in the app — each bracket
* render, each clock tick — and ran a `querySelector('.page.active')` every time,
* to detect an event the app already knows it is causing.
*
* Safe to call before the help system is initialized: it only updates state, and
* updateHelpContext() does nothing while the help modal is closed.
*
* @param {string} pageId - id of the page now active (e.g. 'setup', 'tournament')
* @returns {void}
*/
function setHelpPage(pageId) {
if (!pageId || pageId === helpState.currentPage) return;
helpState.currentPage = pageId;
updateHelpContext();
}
/**
* SETUP CONTEXT DETECTION
* Automatically detect user context and show relevant help
*/
function setupContextDetection() {
// Page changes arrive via setHelpPage(), called from showPage()
// Detect first-time user (the first-run guide is for the Tournament Manager, not Analytics)
if (window.NEWTON_APP_MODE !== 'analytics' && !localStorage.getItem('helpSystemSeen')) {
setTimeout(() => {
showHelp('scenarios', 'firstTime');
localStorage.setItem('helpSystemSeen', 'true');
}, 2000);
}
}
/**
* SETUP KEYBOARD SHORTCUTS
*/
function setupHelpKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
// F1 - Show help for current page
if (e.key === 'F1') {
e.preventDefault();
if (helpState.isVisible) {
hideHelp();
} else {
showHelp(helpState.currentPage);
}
}
// ESC - Close help
if (e.key === 'Escape' && helpState.isVisible) {
hideHelp();
}
// Ctrl+H - Toggle help
if (e.ctrlKey && e.key === 'h') {
e.preventDefault();
toggleHelp();
}
});
}
/**
* SHOW HELP
* Display help for specific page and optionally specific section
*/
function showHelp(pageId = 'setup', sectionId = null) {
const modal = document.getElementById('dynamicHelpModal');
if (!modal) return;
helpState.isVisible = true;
helpState.currentPage = pageId;
helpState.currentSection = sectionId;
// Update help content
updateHelpContent(pageId, sectionId);
// Show modal
modal.style.display = 'block';
// Focus for keyboard navigation
modal.focus();
console.log(`📖 Help shown for ${pageId}${sectionId ? ` > ${sectionId}` : ''}`);
}
/**
* HIDE HELP
*/
function hideHelp() {
const modal = document.getElementById('dynamicHelpModal');
if (!modal) return;
modal.style.display = 'none';
helpState.isVisible = false;
console.log('📖 Help hidden');
}
/**
* TOGGLE HELP
*/
function toggleHelp() {
if (helpState.isVisible) {
hideHelp();
} else {
showHelp(helpState.currentPage);
}
}
/**
* UPDATE HELP CONTEXT
* Called when page changes to update help relevance
*/
function updateHelpContext() {
if (!helpState.isVisible) return;
// Update help content for new page
updateHelpContent(helpState.currentPage);
}
/**
* UPDATE HELP CONTENT
* Populate modal with relevant help information
*/
function updateHelpContent(pageId, sectionId = null) {
const helpData = HELP_CONTENT[pageId];
if (!helpData) {
console.warn(`No help content found for page: ${pageId}`);
return;
}
const titleElement = document.getElementById('helpTitle');
const overviewElement = document.getElementById('helpOverview');
const sectionsElement = document.getElementById('helpSections');
// Update title
if (titleElement) {
titleElement.textContent = helpData.title;
}
// Update overview
if (overviewElement) {
overviewElement.innerHTML = `