Skip to content

Commit 57eff6e

Browse files
feat: Add double elimination bracket visualization (#26)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent aeda1b8 commit 57eff6e

File tree

1 file changed

+264
-0
lines changed

1 file changed

+264
-0
lines changed

demo/index.html

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,125 @@
354354
color: #c8102e;
355355
}
356356

357+
/* Double Elimination Styles */
358+
.double-elim-container {
359+
display: flex;
360+
flex-direction: column;
361+
gap: 4rem;
362+
margin-top: 2rem;
363+
position: relative;
364+
}
365+
366+
.bracket-section {
367+
position: relative;
368+
background: #0d1117;
369+
border: 2px solid #30363d;
370+
border-radius: 8px;
371+
padding: 2rem;
372+
overflow-x: auto;
373+
overflow-y: visible;
374+
}
375+
376+
.bracket-title {
377+
color: #ffffff;
378+
font-size: 1.5rem;
379+
font-weight: 800;
380+
text-transform: uppercase;
381+
letter-spacing: 1px;
382+
margin-bottom: 1.5rem;
383+
padding-bottom: 0.75rem;
384+
border-bottom: 3px solid #c8102e;
385+
display: inline-block;
386+
width: 100%;
387+
}
388+
389+
.winners-bracket {
390+
border-color: #2ea043;
391+
box-shadow: 0 0 20px rgba(46, 160, 67, 0.1);
392+
}
393+
394+
.winners-bracket .bracket-title {
395+
border-bottom-color: #2ea043;
396+
}
397+
398+
.losers-bracket {
399+
border-color: #f85149;
400+
box-shadow: 0 0 20px rgba(248, 81, 73, 0.1);
401+
}
402+
403+
.losers-bracket .bracket-title {
404+
border-bottom-color: #f85149;
405+
}
406+
407+
.grand-finals {
408+
max-width: 900px;
409+
margin: 0 auto;
410+
background: linear-gradient(180deg, #1a1d29 0%, #0d1117 100%);
411+
border-color: #ffd700;
412+
border-width: 3px;
413+
box-shadow: 0 0 40px rgba(255, 215, 0, 0.3), inset 0 0 60px rgba(255, 215, 0, 0.05);
414+
}
415+
416+
.grand-finals .bracket-title {
417+
border-bottom-color: #ffd700;
418+
text-align: center;
419+
font-size: 1.8rem;
420+
}
421+
422+
.bracket-wrapper-inner {
423+
background: transparent;
424+
border-radius: 6px;
425+
padding: 1rem;
426+
min-height: 400px;
427+
position: relative;
428+
}
429+
430+
/* Visual connectors between brackets */
431+
.bracket-section::before,
432+
.bracket-section::after {
433+
content: '';
434+
position: absolute;
435+
width: 3px;
436+
z-index: 5;
437+
pointer-events: none;
438+
}
439+
440+
.winners-bracket::after {
441+
bottom: -4rem;
442+
left: 50%;
443+
height: 4rem;
444+
transform: translateX(-50%);
445+
background: linear-gradient(180deg, #2ea043 0%, rgba(46, 160, 67, 0.3) 50%, transparent 100%);
446+
box-shadow: 0 0 10px rgba(46, 160, 67, 0.4);
447+
}
448+
449+
.losers-bracket::before {
450+
top: -4rem;
451+
left: 50%;
452+
height: 4rem;
453+
transform: translateX(-50%);
454+
background: linear-gradient(180deg, transparent 0%, rgba(248, 81, 73, 0.3) 50%, #f85149 100%);
455+
box-shadow: 0 0 10px rgba(248, 81, 73, 0.4);
456+
}
457+
458+
.losers-bracket::after {
459+
bottom: -4rem;
460+
left: 50%;
461+
height: 4rem;
462+
transform: translateX(-50%);
463+
background: linear-gradient(180deg, #f85149 0%, rgba(248, 81, 73, 0.3) 50%, transparent 100%);
464+
box-shadow: 0 0 10px rgba(248, 81, 73, 0.4);
465+
}
466+
467+
.grand-finals::before {
468+
top: -4rem;
469+
left: 50%;
470+
height: 4rem;
471+
transform: translateX(-50%);
472+
background: linear-gradient(180deg, transparent 0%, rgba(255, 215, 0, 0.3) 50%, #ffd700 100%);
473+
box-shadow: 0 0 15px rgba(255, 215, 0, 0.5);
474+
}
475+
357476
@media (max-width: 768px) {
358477
h1 {
359478
font-size: 2.5rem;
@@ -374,6 +493,18 @@
374493
button {
375494
width: 100%;
376495
}
496+
497+
.double-elim-container {
498+
gap: 2rem;
499+
}
500+
501+
.bracket-section {
502+
padding: 1.5rem;
503+
}
504+
505+
.bracket-title {
506+
font-size: 1.2rem;
507+
}
377508
}
378509
</style>
379510
</head>
@@ -517,6 +648,39 @@ <h2>🎯 Complete Interactive Tournament</h2>
517648
<div class="log-entry log-info">Click "Start New Tournament" to begin!</div>
518649
</div>
519650
</div>
651+
652+
<!-- Feature 5: Double Elimination Bracket -->
653+
<div class="demo-section">
654+
<h2>🏆 Double Elimination Bracket</h2>
655+
<p class="demo-description">
656+
A complete double elimination tournament visualization. Teams start in the Winners Bracket (top).
657+
Losers drop to the Losers Bracket (bottom). The Grand Finals pits the Winners Bracket champion
658+
against the Losers Bracket champion - the Winners Bracket champion must lose twice to be eliminated.
659+
</p>
660+
661+
<div class="double-elim-container">
662+
<div class="bracket-section winners-bracket">
663+
<h3 class="bracket-title">Winners Bracket</h3>
664+
<div class="bracket-wrapper-inner">
665+
<div id="winners-bracket"></div>
666+
</div>
667+
</div>
668+
669+
<div class="bracket-section losers-bracket">
670+
<h3 class="bracket-title">Losers Bracket</h3>
671+
<div class="bracket-wrapper-inner">
672+
<div id="losers-bracket"></div>
673+
</div>
674+
</div>
675+
676+
<div class="bracket-section grand-finals">
677+
<h3 class="bracket-title">Grand Finals</h3>
678+
<div class="bracket-wrapper-inner">
679+
<div id="grand-finals-bracket"></div>
680+
</div>
681+
</div>
682+
</div>
683+
</div>
520684
</div>
521685

522686
<footer>
@@ -985,6 +1149,106 @@ <h2>🎯 Complete Interactive Tournament</h2>
9851149

9861150
addLog('complete-log', '\\n' + report, 'success');
9871151
});
1152+
1153+
// ========================================================================
1154+
// Demo 5: Double Elimination Bracket
1155+
// ========================================================================
1156+
1157+
// 8-team double elimination tournament data
1158+
const doubleElimTeams = [
1159+
{ name: 'Warriors', id: 'warriors', seed: 1 },
1160+
{ name: 'Lakers', id: 'lakers', seed: 2 },
1161+
{ name: 'Celtics', id: 'celtics', seed: 3 },
1162+
{ name: 'Heat', id: 'heat', seed: 4 },
1163+
{ name: 'Bucks', id: 'bucks', seed: 5 },
1164+
{ name: 'Suns', id: 'suns', seed: 6 },
1165+
{ name: 'Nets', id: 'nets', seed: 7 },
1166+
{ name: 'Nuggets', id: 'nuggets', seed: 8 }
1167+
];
1168+
1169+
// Winners Bracket (8 teams -> 4 -> 2 -> 1 champion)
1170+
const winnersBracketData = [
1171+
// Round 1: Quarterfinals (4 matches)
1172+
[
1173+
[{ name: 'Warriors', id: 'warriors', seed: 1, score: 105 }, { name: 'Nuggets', id: 'nuggets', seed: 8, score: 92 }],
1174+
[{ name: 'Lakers', id: 'lakers', seed: 2, score: 98 }, { name: 'Nets', id: 'nets', seed: 7, score: 110 }],
1175+
[{ name: 'Celtics', id: 'celtics', seed: 3, score: 108 }, { name: 'Suns', id: 'suns', seed: 6, score: 102 }],
1176+
[{ name: 'Heat', id: 'heat', seed: 4, score: 95 }, { name: 'Bucks', id: 'bucks', seed: 5, score: 112 }]
1177+
],
1178+
// Round 2: Semifinals (2 matches)
1179+
[
1180+
[{ name: 'Warriors', id: 'warriors', seed: 1, score: 118 }, { name: 'Nets', id: 'nets', seed: 7, score: 105 }],
1181+
[{ name: 'Celtics', id: 'celtics', seed: 3, score: 115 }, { name: 'Bucks', id: 'bucks', seed: 5, score: 108 }]
1182+
],
1183+
// Round 3: Finals (1 match)
1184+
[
1185+
[{ name: 'Warriors', id: 'warriors', seed: 1, score: 120 }, { name: 'Celtics', id: 'celtics', seed: 3, score: 115 }]
1186+
],
1187+
// Round 4: Champion
1188+
[
1189+
[{ name: 'Warriors', id: 'warriors', seed: 1 }]
1190+
]
1191+
];
1192+
1193+
// Losers Bracket (losers from winners bracket feed in)
1194+
// Structure: Round 1 losers play each other, then winners play Round 2 losers, etc.
1195+
const losersBracketData = [
1196+
// Round 1: First round losers (4 teams -> 2)
1197+
[
1198+
[{ name: 'Nuggets', id: 'nuggets', seed: 8, score: 88 }, { name: 'Lakers', id: 'lakers', seed: 2, score: 95 }],
1199+
[{ name: 'Suns', id: 'suns', seed: 6, score: 102 }, { name: 'Heat', id: 'heat', seed: 4, score: 98 }]
1200+
],
1201+
// Round 2: Losers from Winners Round 2 + Winners from Losers Round 1 (4 teams -> 2)
1202+
[
1203+
[{ name: 'Lakers', id: 'lakers', seed: 2, score: 92 }, { name: 'Nets', id: 'nets', seed: 7, score: 105 }],
1204+
[{ name: 'Suns', id: 'suns', seed: 6, score: 98 }, { name: 'Bucks', id: 'bucks', seed: 5, score: 110 }]
1205+
],
1206+
// Round 3: Semifinals (2 teams -> 1)
1207+
[
1208+
[{ name: 'Nets', id: 'nets', seed: 7, score: 108 }, { name: 'Bucks', id: 'bucks', seed: 5, score: 112 }]
1209+
],
1210+
// Round 4: Finals (loser plays winner from previous round)
1211+
[
1212+
[{ name: 'Nets', id: 'nets', seed: 7, score: 115 }, { name: 'Celtics', id: 'celtics', seed: 3, score: 118 }]
1213+
],
1214+
// Round 5: Losers Bracket Champion
1215+
[
1216+
[{ name: 'Celtics', id: 'celtics', seed: 3 }]
1217+
]
1218+
];
1219+
1220+
// Grand Finals: Winners Bracket Champion vs Losers Bracket Champion
1221+
const grandFinalsData = [
1222+
// Grand Finals Match 1
1223+
[
1224+
[{ name: 'Warriors', id: 'warriors', seed: 1, score: 125 }, { name: 'Celtics', id: 'celtics', seed: 3, score: 120 }]
1225+
],
1226+
// Grand Finals Match 2 (if needed - Warriors already won, so this is just for display)
1227+
[
1228+
[{ name: 'Warriors', id: 'warriors', seed: 1 }]
1229+
]
1230+
];
1231+
1232+
// Initialize Winners Bracket
1233+
const winnersBracket = new Gracket('#winners-bracket', {
1234+
src: winnersBracketData,
1235+
roundLabels: ['Quarterfinals', 'Semifinals', 'Finals', 'Champion'],
1236+
canvasLineColor: '#2ea043'
1237+
});
1238+
1239+
// Initialize Losers Bracket
1240+
const losersBracket = new Gracket('#losers-bracket', {
1241+
src: losersBracketData,
1242+
roundLabels: ['Round 1', 'Round 2', 'Semifinals', 'Finals', 'Champion'],
1243+
canvasLineColor: '#f85149'
1244+
});
1245+
1246+
// Initialize Grand Finals
1247+
const grandFinalsBracket = new Gracket('#grand-finals-bracket', {
1248+
src: grandFinalsData,
1249+
roundLabels: ['Grand Finals', 'Champion'],
1250+
canvasLineColor: '#ffd700'
1251+
});
9881252
</script>
9891253
</body>
9901254
</html>

0 commit comments

Comments
 (0)