Recently I was asked to write up how our tournament brackets on Leaguepedia were constructed. This was a topic I eventually planned to do a blog post on anyway, so here we go! Our tournament brackets are one of the most collaboratively-created features on the entire wiki - Ema designed all of the CSS & HTML for them, I wrote the Lua code, and several volunteers from among the Leaguepedia wiki staff contributed “bracket definitions” - the Lua syntax files that define the layout for the HTML generation to create the layouts that you see all over the wiki.
This post is also a collaboration - Ema wrote an indepth section about how the really cool CSS works. And it’s part 1 - this post will cover everything about the HTML/CSS implementation, as well as the original Lua code, but I’ll save talking about the current state of Module:Bracket and its Cargo support, as well as the (Python) rollout of these brackets, for a later post.
(Note: All code in this post was originally written for Leaguepedia and is licensed under CC BY-SA 3.0. Unless otherwise stated, all CSS was written by Ema and all non-CSS code was written by me.)
Overview
The general concept of the brackets is to use a CSS grid layout with ::before
and ::after
pseudoelements to construct all of the connectors (though the super long horizontal connectors that exist in losers’ brackets are actual grid elements). The grid elements themselves consist of two types of alternating columns: content columns and line columns. The lines are empty space (not to be confused with “spacers,” which go in between the content in content columns) that have room for the connector pseudoelements to do their thing, and the content columns contain the actual content (as well as the long connector lines).
In addition to this, there’s some random magic to do with the grid row height, occasional text that has to display, when teams can get reseeded, dark-and-light-mode support, other CSS variable goodness, grid-template-rows
, grid-template-columns
, team highlighting, toggling to hide the first N
rounds, and, of course, Cargo support.
But mostly this is just CSS grid and ::before
and ::after
pseudoelements.
Implementation requirements
There are several requirements of our implementation that increased the difficulty of design.
Line height
One challenge of any implementation of brackets for us, grid or otherwise, is line height. Not every implementation has to consider this; some sources can assume that teams with very long names get curtailed with text-overflow:ellipsis;
or just never have names that are too long or something, but we were adamant that in open qualifiers, teams with randomly extremely long names would WRAP and be displayed in their entirety.
Flexibility
We have to be able to display literally any type of bracket. Entities that don’t need to do this can get away with things like individually-crafted images (though of course these aren’t responsive). We cannot.
Ability for anyone to design new brackets
The design should be easily editable / implementable by anyone. It’s really, really easy for someone to create a new bracket definition once they’re familiar with the syntax. It takes some trial and error to get it right, sure, but there’s no deep confusion involved.
HTML
Let’s look at the HTML generated by the wikitext {{Bracket|4SE}}
(via Lua code that we’ll get to later):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
<div class="bracket-grid" style="grid-template-rows:repeat(14,var(--grid-row-height));grid-template-columns:0 minmax(12em, 12em) 3em minmax(12em, 12em)">
<div class="bracket-grid-header round1">
<div class="bracket-header-content">Round 1</div>
</div>
<div class="bracket-spacer round1"></div>
<div class="bracket-team round1 teamhighlight teamhighlighter">
<div class="bracket-team-name"></div>
<div class="bracket-team-points"></div>
</div>
<div class="bracket-team round1 teamhighlight teamhighlighter">
<div class="bracket-team-name"></div>
<div class="bracket-team-points"></div>
</div>
<div class="bracket-spacer round1"></div>
<div class="bracket-spacer round1"></div>
<div class="bracket-team round1 teamhighlight teamhighlighter">
<div class="bracket-team-name"></div>
<div class="bracket-team-points"></div>
</div>
<div class="bracket-team round1 teamhighlight teamhighlighter">
<div class="bracket-team-name"></div>
<div class="bracket-team-points"></div>
</div>
<div class="bracket-spacer round1"></div>
<div class="bracket-line round1" style="grid-row:span 5;"></div>
<div class="bracket-line z-down round1" style="grid-row:span 2;"></div>
<div class="bracket-line round1" style="grid-row:span 2;"></div>
<div class="bracket-line z-up round1" style="grid-row:span 2;"></div>
<div class="bracket-grid-header round2">
<div class="bracket-header-content">Round 2</div>
</div>
<div class="bracket-spacer round2" style="grid-row:span 3;"></div>
<div class="bracket-spacer round2"></div>
<div class="bracket-team round2 teamhighlight teamhighlighter">
<div class="bracket-team-name"></div>
<div class="bracket-team-points"></div>
</div>
<div class="bracket-team round2 teamhighlight teamhighlighter">
<div class="bracket-team-name"></div>
<div class="bracket-team-points"></div>
</div>
<div class="bracket-spacer round2"></div>
</div>
|
This is a lot of HTML, but I wanted to make sure we had different kinds of connector elements in the example, and to do that we needed…a lot of HTML.
The container of course is <div class="bracket-grid"></div>
wrapping the entire thing. Then we have some divs that are like bracket-spacer
or bracket-team
and are round1
. The team
ones contain content, while the spacer
ones don’t. That goes on for a while.
After these divs, we have some more round1
stuff, but they are bracket-line
(remember from the introduction, these are the “lines” divs now). They have some inline styles that give them grid-row
(aka height) spans. Two of them have classes that print pseudoelements too:
The pseudoelement-printing classes are the “cool” part.
Following this round1
stuff, we have some round2
stuff. It’s pretty similar to the round1
stuff, except there’s no bracket-line
stuff after it.
And then that’s it, we’re done!
CSS
Grid layout CSS
First of all, because I’m sure you’re wondering, the round1
, round2
, etc CSS is for actually the most boring implementation you can possibly imagine:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
.round1 {
grid-column: 2;
}
.round2 {
grid-column: 4;
}
.round3 {
grid-column: 6;
}
/* snip */
.bracket-line.round0 {
grid-column: 1;
}
.bracket-line.round1 {
grid-column: 3;
}
.bracket-line.round2 {
grid-column: 5;
}
/* snip */
|
That’s seriously it. It goes up to round9
. Could we have done some ridiculous magic with calc
and CSS variables? Uh…maybe. Should we have? Definitely not. This is clear, intuitive, straightforward, easy, obvious, lots of good things. It’s not DRY but who cares? We implemented it years ago and haven’t had to add a row since. And if we did, we’d just add like 3 rows all at once and not have to touch it for another several years. There was a possibility of adding some extra shit with CSS variables to implement toggling, see the section later on about toggling, for now at least that’s not happening.
(Note, while the columns are set by the class, the rows are set automatically by the HTML being generated in order, see the grid-auto-flow: column;
in the next block of code. And of course we noticed the inline styles of the grid-row:span
s for the lines classes setting their “height.”)
Now, the grid definition itself:
1
2
3
4
5
6
7
8
|
.bracket-grid {
display: grid;
grid-auto-flow: column;
font-size: 90%;
overflow: auto;
line-height: 1;
max-width:100%;
}
|
Dark & light mode CSS
In the bracket CSS we have:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
.bracket-grid-header,
.bracket-team {
border: 1px solid var(--interface-border);
}
.bracket-grid-header {
border: 1px solid var(--interface-border);
background: var(--interface-background);
}
.bracket-team-points {
background: var(--interface-background);
border-left: 1px solid var(--interface-border);
}
.bracket-line, .bracket-spacer {
--bracket-line-color: var(--body-text-color);
}
|
(Most of these rules are abbreviated, and the full rules will be shown below.)
And then in our global (aka esports-wiki-wide) variables list we have:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
.skin-hydra, .skin-minerva, .skin-fandommobile, .skin-fandomdesktop {
--body-text-color:#222222;
/* snip */
--interface-background:#f8f9fa;
--interface-background-rgb:248, 249, 250;
--interface-border:#a2a9b1;
}
.skin-hydradark {
--body-text-color: #E0E0CD;
/* snip */
--interface-background: #181818;
--interface-background-rgb:24, 24, 24;
--interface-border: #C9C9BE;
}
|
So everything for our brackets auto-works in both dark & light mode.
(In fact our entire skin is set up like this, with CSS variables behind every single color, everywhere, and one day I’m going to write a blog post about it - there’s some incredibly complex stuff that goes way beyond this, like where we need to compute HSL values in different color wheels for both light & dark mode and set inline variable definitions so that the stylesheet can use the right color. CSS variables rock.)
Pseudoelements - the text ones
1
2
3
4
5
6
7
|
.bracket-line.reseed-reseeding::after {
content: "Reseeding";
}
.bracket-line.reseed-selection::after {
content: "Selection";
}
|
I think this is the only part of the CSS that I wrote, and arguably a better approach would be something like:
1
2
3
|
.bracket-line.reseed::after {
content: attr(data-reseed-text);
}
|
Then the value of data-reseed-text
can be looked up in an i18n file and properly localized etc. I actually considered rewriting this code while writing this blog post but ultimately decided against it because it’s MUCH easier to deal only with classes in the HTML generation Lua code - currently there’s no support whatsoever for attributes on parts of the bracket, and I don’t really feel it’s worth complicating the code for something this minor. It is a possibility though for anyone adapting this implementation.
Pseudoelements - the interesting ones
Ema guest-wrote this section!
One of the biggest challenges of the brackets was the question of the connecting lines between rounds. Some things that these lines needed to do to make this efficient:
- needed to be able to connect seamlessly to either the edge or the middle of a team cell in the next round
- needed to be flexible enough to allow for a variety of connection configurations and line shapes
- ideally would allow for a variety of color choices
During initial brainstorming on this project I had actually mocked this up in a spreadsheet to try and figure out the neatest way to get the lines to connect up properly. This isn’t the original spreadsheet, but it looked something like this:
This is also pretty close to what the brackets were built like before this project. What specifically caused problems for the connecting lines was the fact that HTML tables are constructed row-by-row, while the connecting lines serve an entire purpose to connect cells vertically across columns. To make a single change uniformly across just one column in a HTML table requires basically infinite changes to replicate the change across all rows.
This is where both CSS grid and the bracket-line pseudoelements come in.
Grid allows us to rotate the axis around which the bracket grid is generated, instead allowing us to in fact construct everything on the column axis. In the grid definition above, this is prompted by grid-auto-flow: column;
which sorts the long list of div
s inside down columns first, rather than rows, so all we need to do is tell it which type of thing goes into which column and the grid spec does all the work for us.
Okay - not all the work just yet.
Pseudoelements
In the spreadsheet example above, you can see that in order to draw the connecting line down the center of the bracket-line column using the cell borders, it actually required two columns. To convert that into the grid, we could have taken the extra ridiculous route and actually added two columns for every line column, but the HTML bloat involved in that and the chance of the columns getting out of sync would have been a nightmare.
Instead, we can just take the one column we have and split it in half down the middle.
Rather than adding two more div
s inside each div
, and having to add classes on both so that we don’t have to use some unfortunate combination of :nth-child
or adjacent sibling combinators to tell which div was on the left versus on the right… We can use the convenient, built-in, already differentiated selectors available inside of (nearly) every HTML element: pseudoelements.
Every bracket-line div uses ::before
and/or ::after
to give us two spaces to work with to manipulate the borders, so the following is the style applied to all of them to give us the basic bare minimum style:
1
2
3
4
5
6
7
8
|
.bracket-line::after, .bracket-line::before, .bracket-spacer.horizontal::before {
width: calc(50% + 2px);
height: calc(100% + 2px);
box-sizing: border-box;
display: inline-block;
margin: -1px;
border: 0 solid var(--bracket-line-color);
}
|
This splits each bracket-line div down the middle by making each pseudoelement half the width, adds a baseline border color so that we don’t have to keep repeating the variable later, and the +/- 1px in the width, height, and margin ensures that there are no tiny gaps between the lines and the teams. From there, we break the lines down by shape, so that we can assign them different border configurations.
Border configurations
For each bracket-line div we have 5 possible border areas that we can use to create the connecting lines we want:
Their corresponding CSS references:
::before
{border-top
}
::after
{border-top
}
::before
{border-bottom
}
::after
{border-bottom
}
::before
{border-right
} AND ::after
{border-left
}
By selectively coloring or hiding certain border areas, the line can connect in different ways.
For an example, lets look at a “Z” connector, the most common type of connector in the average bracket. Here is a visual representation of what we need to show/hide:
And now here is the code:
1
2
3
4
5
6
7
8
9
10
11
12
|
.bracket-line.z-down::before {
content: "";
border-width: 2px 2px 0 0;
border-bottom: 2px solid transparent;
border-radius: 0 2px 0 0;
}
.bracket-line.z-down::after {
content: "";
border-width: 0 0 2px 2px;
border-top: 2px solid transparent;
border-radius: 0 0 0 2px;
}
|
The reason we save the content parameter for here, rather than the basic structure above, is because not all connector configurations require both pseudoelements to be generated. Leaving off the content parameter on the unneded pseudoelement will prevent it from generating, cutting down on a little bit of bloat. Of course, for a Z connector, we do need both.
On to the borders: This is a lot of shorthand, but the border shorthand follows the top-right-bottom-left order, so we effectively have the following for the border-width:
1
2
3
4
5
6
7
8
9
10
11
12
|
.bracket-line.z-down::before {
border-top-width: 2px;
border-right-width: 2px;
border-bottom-width: 0px;
border-left-width: 0px;
}
.bracket-line.z-down::after {
border-top-width: 0px;
border-right-width: 0px;
border-bottom-width: 2px;
border-left-width: 2px;
}
|
Which corresponds to enabling border areas 1, 4, and 5 as displayed in the diagram above.
To prevent the border-width of the top and bottom borders from pushing one pseudoelement up and the other down, we have to counterbalance them with a transparent 2px border on the other vertical direction.
A small border radius on the bent corner of each pseudoelement helps clean up the final look.
One more example: The L connector, which is an example of a connector that only uses one pseudoelement.
As both 1 and 3 are hidden, and 5 can be drawn by either pseudoelement, we don’t even need to enable the ::before
. This is the entire code for the L:
1
2
3
4
5
6
|
.bracket-line.l-down::after {
content: "";
border-width: 0 0 2px 2px;
border-radius: 0 0 0 2px;
float: right;
}
|
The only new thing here is the float property, which ensures that even though there is no ::before
element on the left, the ::after
remains flush with the right side of the div.
CSS - conclusion
At this point we have a roughly functional static HTML & CSS setup. There’s a lot of rules I’ve left out to “prettify” the design, but they can be left up to your particular needs. To see our specifics, you can check out our full stylesheet at MediaWiki:Gadget-brackets.css, and I will also include it at the end of this post.
However, it’s wildly impractical to do anything with these brackets unless we have an HTML generator as well as a compact “markup language” for defining the individual bracket layouts. I built both of these things in Lua, and I will go over them both in the next section.
HTML Generation (Lua)
I like to say that Ema did all of the design, and then I made all of the wiki editors do all of the implementation, and all I did was write a little bit of code to glue them together. Over time, the bracket module has certainly gotten more complex, but the initial version that I built really was a very simple product that did not do very much work at all.
Syntax generation
Let’s look at the Lua syntax generation file to create that 4SE bracket whose HTML we saw above:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
return {
{
matches = {
title = 'Round 1',
{ display = 'match' },
{ display = 'match' }
},
},
{
lines = {
{ above = 3, class = 'z-down', height = 2 },
{ above = 2, class = 'z-up', height = 2 }
},
matches = {
title = 'Round 2',
{ display = 'match', above = 3 },
{ display = 'match', argtoshow = 'third', label = '3rd place match' }
}
}
}
|
You can see those z-down
and z-up
connectors we talked about before. And the syntax definition is doing all of the hard work of positioning stuff. So indeed the Lua does not need to do very much work at all.
Original Lua code
The first version of the Lua module would:
- Read in the template args and parse the human input into a table based on the determined syntax
RxMy_paramname
with an optional _1
or _2
at the end for team (you can read more about the arg syntax on the template documentation page)
- Determine the
grid-template-columns
inline CSS value (actually this was a bug; it needs to also determine the grid-template-rows
value, but we didn’t realize this for a few months)
- Iterate through the bracket definition, printing first the lines and then the matches for each column
I’ll paste in the code in a bit, but first I want to talk about one last thing….
Toggles
I was adamant that we would support toggles in some form or another. That is, you had to be able to collapse a 64 bracket to just the quarterfinals:
Now, one way to do this COULD have been like so:
1
2
3
4
5
6
7
8
9
10
11
|
:root {
--bracket-offset:0;
}
.round1 {
grid-column: calc(2 + var(--bracket-offset));
}
.round2 {
grid-column: calc(4 + var(--bracket-offset));
}
|
When you click on the toggler, the --bracket-offset
variable is set to a negative number, all of the .round
classes before it are set to visibility:hidden;
, and everything shifts to the left. The PROBLEM, though, is that we ALSO need VERTICAL collapsing. And vertical collapsing doesn’t come for free. In fact…if you figure out literally any way to do vertical collapsing, tell me, because, we straight-up couldn’t think of ANYTHING. So, here’s how collapsing is done in the definition of the 64SE bracket:
1
2
3
4
5
|
togglers = {
{ bracket = require('Module:Bracket/32SE') },
{ bracket = require('Module:Bracket/16SE') },
{ bracket = require('Module:Bracket/8SE') },
}
|
In other words, we require the definition to contain a sub-definition of what every collapsed view looks like.
Toggles weren’t included in the original MVP of my brackets, so that’s why this discussion is included separately here.
Lua code
Here is the full Lua code from November 2018:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
|
local util_table = require('Module:TableUtil')
local util_args = require('Module:ArgsUtil')
local bracket_wiki = require('Module:Bracket/Wiki') -- wiki localization per game
local h = {}
function h.processArgs(tpl_args)
-- format tpl_args
local args = {}
for k, v in pairs(tpl_args) do
if k:find('R%d+M%d+_.*_') then
local match, val, team = k:match('(R%d+M%d+)_(.*)_(%d+)')
if not args[match] then
args[match] = { team1 = {}, team2 = {} }
end
args[match]['team' .. team][val] = v
elseif k:find('R%d+M%d+_.*') then
local match, val = k:match('(R%d+M%d+)_(.*)')
if not args[match] then
args[match] = { team1 = {}, team2 = {} }
end
args[match][val] = v
else
args[k] = v
end
end
return args
end
function h.printBracket(args, settings)
local tbl = mw.html.create('div')
:addClass('bracket-grid')
:css({
['grid-template-columns'] = h.gtcSetting(settings, args.roundwidth)
})
for round, col in ipairs(settings) do
h.addLinesColumn(tbl, col.lines, round, not args.notitle)
h.addMatchesColumn(tbl, args, col.matches, round, not args.notitle)
end
return tbl
end
function h.gtcSetting(settings, roundwidth)
local len = #settings
local firstCol = settings[1].lines and next(settings[1].lines)
return ('%s repeat(%s, %s 3em)'):format(
firstCol and '3em' or '0',
(len * 2 - 1),
roundwidth or '16em'
)
end
function h.addLinesColumn(tbl, lineData, r, addtitle)
local roundname = 'round' .. (r - 1)
if not lineData then
return
end
for m, row in ipairs(lineData) do
if m == 1 and addtitle then
h.addBracketLine(tbl, roundname, row, 2)
else
h.addBracketLine(tbl, roundname, row, 0)
end
end
return
end
function h.addBracketLine(tbl, roundname, linerow, extra)
if linerow.above + extra > 0 then
tbl:tag('div')
:addClass('bracket-line')
:addClass(roundname)
:cssText(('grid-row:span %s;'):format(linerow.above + extra))
end
tbl:tag('div')
:addClass('bracket-line')
:addClass(linerow.class)
:addClass(roundname)
:cssText(('grid-row:span %s;'):format(linerow.height))
return
end
function h.addMatchesColumn(tbl, args, data, r, addtitle)
local roundname = 'round' .. r
if addtitle then
h.makeTitle(tbl, roundname, args['R' .. r .. '_title'] or data.title or '')
end
for m, row in ipairs(data) do
local game = args[('R%sM%s'):format(r, m)] or { team1 = {}, team2 = {} }
if row.above then
h.addSpacer(tbl, roundname, row.above)
end
if row.display == 'match' then
h.makeMatch(tbl, game, roundname, row.label)
elseif row.display == 'hline' then
h.makeHorizontalCell(tbl, roundname)
end
end
return
end
function h.makeTitle(tbl, roundname, text)
tbl:tag('div')
:addClass('bracket-grid-header')
:addClass(roundname)
:tag('div')
:addClass('bracket-header-content')
:wikitext(text)
return
end
function h.makeHorizontalCell(tbl, roundname)
tbl:tag('div')
:addClass('bracket-spacer')
:addClass('horizontal')
:addClass(roundname)
return
end
function h.makeMatch(tbl, game, roundname, label)
if game.label then label = game.label end
h.addSpacer(tbl, roundname, nil, label)
h.makeTeam(tbl, roundname, game.team1, game.winner == '1')
h.makeTeam(tbl, roundname, game.team2, game.winner == '2')
h.addSpacer(tbl, roundname)
return
end
function h.addSpacer(tbl, roundname, n, label)
local div = tbl:tag('div')
:addClass('bracket-spacer')
:addClass(roundname)
:wikitext(label)
if n then
div:cssText(('grid-row:span %s;'):format(n))
end
return
end
function h.makeTeam(tbl, roundname, data, isWinner)
local line = tbl:tag('div')
:addClass('bracket-team')
:addClass(roundname)
if isWinner then
line:addClass('bracket-winner')
end
local team = line:tag('div')
:addClass('bracket-team-name')
if data.free then
team:wikitext(data.free)
else
bracket_wiki.teamDisplay(team, data)
end
line:tag('div')
:addClass('bracket-team-points')
:wikitext(data.score or '')
return
end
local p = {}
function p.main(frame)
local tpl_args = util_args.merge(true)
-- use require instead of loadData so that we can use next() and #
local settings = require('Module:Bracket/'.. tpl_args.bracket)
local args = h.processArgs(tpl_args)
return h.printBracket(args, settings)
end
return p
|
The module Bracket/Wiki
gives me some polymorphism across wikis without actually having object-oriented code, since at the time I did not yet have Module:LuaClassSystem to work with. I’m actually still using this approach, although now that I much more standardly use LuaClassSystem for wiki-specific code, I may refactor just for uniformity.
Anyway, here’s the original version of Module:Bracket/Wiki
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
local m_region = require('Module:Region')
local m_team = require('Module:Team')
local p = {}
function p.teamDisplay(tbl, data, teamstyle)
if data.region then
tbl:wikitext(m_region.onlyimage(data.region))
end
if data.team then
tbl:wikitext(m_team[teamstyle or 'rightmediumlinked'](data.team))
end
return
end
return p
|
All told, the entire thing is under 200 lines of Lua, not bad!
Finally, here is the full CSS code.
Full CSS code
I will give two versions of the CSS, the original from December 2018 and the current version. Not everything in the current version is supported by the shown Lua code. Of course you can also see Gadget-brackets.css for the latest revision, included changes made after the time of publication.
December 2018 revision
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
|
.bracket-grid {
display: grid;
grid-auto-rows: minmax(0.5em, 1fr);
grid-auto-flow: column;
font-size: 90%;
overflow: auto;
line-height: 1;
}
.bracket-line,
.bracket-spacer {
text-align: center;
height: 100%;
}
.bracket-team,
.bracket-grid-header {
grid-row: span 2;
display: table;
border: 1px solid var(--interface-border);
border-radius: 2px;
height: 100%;
box-sizing: border-box;
}
.bracket-grid-header{
background: var(--interface-background);
text-align: center;
}
.bracket-team-name,
.bracket-team-points,
.bracket-header-content {
display: table-cell;
vertical-align: middle;
}
.bracket-team-name {
padding: 1px 2px;
min-width: 6em;
max-width: 0;
word-wrap: break-word;
}
.bracket-team-points {
background: var(--interface-background);
width: 2em;
min-width: 2em;
text-align: center;
border-left: 1px solid var(--interface-border);
}
.bracket-line.reseed {
position: relative;
}
.bracket-line::after,
.bracket-line::before,
.bracket-spacer.horizontal::before {
width: calc(50% + 2px);
height: calc(100% + 2px);
box-sizing: border-box;
display: inline-block;
margin: -1px;
border: 0 solid var(--body-text-color);
}
.bracket-line.horizontal::before,
.bracket-spacer.horizontal::before {
content: "";
border-width: 0 0 2px 0;
width: calc(100% + 2px);
}
.bracket-line.t-down::after {
content: "";
border-width: 2px 0 2px 2px;
border-radius: 0 0 0 2px;
}
.bracket-line.t-down::before {
content: "";
border-width: 2px 2px 0 0;
border-bottom: 2px solid transparent;
}
.bracket-line.t-up::after {
content: "";
border-width: 0 0 2px 2px;
border-radius: 2px 0 0 0;
}
.bracket-line.t-up::before {
content: "";
border-width: 2px 2px 2px 0;
border-top: 2px solid transparent;
}
.bracket-line.z-down::after {
content: "";
border-width: 0 0 2px 2px;
border-top: 2px solid transparent;
border-radius: 0 0 0 2px;
}
.bracket-line.z-down::before {
content: "";
border-width: 2px 2px 0 0;
border-bottom: 2px solid transparent;
border-radius: 0 2px 0 0;
}
.bracket-line.z-up::after {
content: "";
border-width: 2px 0 0 2px;
border-bottom: 2px solid transparent;
border-radius: 2px 0 0 0;
}
.bracket-line.z-up::before {
content: "";
border-width: 0 2px 2px 0;
border-top: 2px solid transparent;
border-radius: 0 0 2px 0;
}
.bracket-line.reseed::after {
content: "Reseeding";
border-width: 2px 0 2px 2px;
writing-mode: vertical-lr;
position: absolute;
font-size: 90%;
text-align: center;
}
.bracket-line.reseed::before {
content: "";
border-width: 2px 2px 2px 0;
}
.bracket-line.l-down::after {
content: "";
border-width: 0 0 2px 2px;
border-radius: 0 0 0 2px;
float: right;
}
.bracket-line.l-up::after {
content: "";
border-width: 2px 0 0 2px;
border-radius: 2px 0 0 0;
float: right;
}
.bracket-winner {
font-weight:bold;
}
.round1 {
grid-column: 2;
}
.round2 {
grid-column: 4;
}
.round3 {
grid-column: 6;
}
.round4 {
grid-column: 8;
}
.round5 {
grid-column: 10;
}
.round6 {
grid-column: 12;
}
.round7 {
grid-column: 14;
}
.round8 {
grid-column: 16;
}
.round9 {
grid-column: 18;
}
.round10 {
grid-column: 20;
}
.bracket-line.round0 {
grid-column: 1;
}
.bracket-line.round1 {
grid-column: 3;
}
.bracket-line.round2 {
grid-column: 5;
}
.bracket-line.round3 {
grid-column: 7;
}
.bracket-line.round4 {
grid-column: 9;
}
.bracket-line.round5 {
grid-column: 11;
}
.bracket-line.round6 {
grid-column: 13;
}
.bracket-line.round7 {
grid-column: 15;
}
.bracket-line.round8 {
grid-column: 17;
}
.bracket-line.round9 {
grid-column: 19;
}
.bracket-extrainfo {
float:right;
display:inline-block;
}
|
Current revision
Very little has changed, and it’s mostly just additions, but I will still paste the entire file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
|
:root {
--grid-row-height: 1fr;
}
.frontpage-content {
--grid-row-height: calc(1em + 1px);
}
.bracket-grid {
display: grid;
grid-auto-flow: column;
font-size: 90%;
overflow: auto;
line-height: 1;
max-width:100%;
}
.bracket-line,
.bracket-spacer {
text-align: center;
height: 100%;
}
.bracket-grid-header,
.bracket-team {
grid-row: span 2;
display: table;
border: 1px solid var(--interface-border);
border-radius: 2px;
height: 100%;
width: 100%;
box-sizing: border-box;
}
.bracket-grid-header {
border: 1px solid var(--interface-border);
background: var(--interface-background);
text-align: center;
}
.bracket-header-content,
.bracket-team-name,
.bracket-team-points {
display: table-cell;
vertical-align: middle;
}
.bracket-header-content {
position:relative;
}
.bracket-toggler {
position:absolute;
right:5px;
top: 50%;
margin-top: -0.5em;
}
.bracket-team-name {
padding: 3px;
min-width: 4em;
max-width: 0;
word-wrap: break-word;
}
.bracket-team-points {
background: var(--interface-background);
width: 2em;
min-width: 2em;
text-align: center;
border-left: 1px solid var(--interface-border);
padding-left:0;
padding-right:0;
}
.bracket-line.reseed {
position: relative;
}
.bracket-line::after,
.bracket-line::before,
.bracket-spacer.horizontal::before {
width: calc(50% + 2px);
height: calc(100% + 2px);
box-sizing: border-box;
display: inline-block;
margin: -1px;
border: 0 solid var(--bracket-line-color);
}
.bracket-line, .bracket-spacer {
--bracket-line-color: var(--body-text-color);
}
.bracket-line.loser-advance {
--bracket-line-color: #f00;
}
.bracket-line.reseed {
--bracket-line-color: var(--interface-border);
}
.bracket-line.horizontal::before,
.bracket-spacer.horizontal::before {
content: "";
border-width: 0 0 2px 0;
width: calc(100% + 2px);
}
.bracket-line.t-down::after {
content: "";
border-width: 2px 0 2px 2px;
border-radius: 0 0 0 2px;
border-color: var(--bracket-line-color) #f00 #f00;
}
.bracket-line.t-down::before {
content: "";
border-width: 2px 2px 0 0;
border-bottom: 2px solid transparent;
border-color: var(--bracket-line-color) #f00 transparent;
}
.bracket-line.t-up::after {
content: "";
border-width: 0 0 2px 2px;
border-radius: 2px 0 0 0;
}
.bracket-line.t-up::before {
content: "";
border-width: 2px 2px 2px 0;
border-top: 2px solid transparent;
}
.bracket-line.z-down::after {
content: "";
border-width: 0 0 2px 2px;
border-top: 2px solid transparent;
border-radius: 0 0 0 2px;
}
.bracket-line.z-down::before {
content: "";
border-width: 2px 2px 0 0;
border-bottom: 2px solid transparent;
border-radius: 0 2px 0 0;
}
.bracket-line.z-up::after {
content: "";
border-width: 2px 0 0 2px;
border-bottom: 2px solid transparent;
border-radius: 2px 0 0 0;
}
.bracket-line.z-up::before {
content: "";
border-width: 0 2px 2px 0;
border-top: 2px solid transparent;
border-radius: 0 0 2px 0;
}
.bracket-line.reseed::after {
border-width: 2px 0 2px 2px;
writing-mode: vertical-lr;
position: absolute;
right: 0;
top: 0;
font-size: 90%;
text-align: center;
}
.bracket-line.reseed::before {
content: "";
float: left;
border-width: 2px 1px 2px 0;
}
.bracket-line.reseed-reseeding::after {
content: "Reseeding";
}
.bracket-line.reseed-selection::after {
content: "Selection";
}
.bracket-line.l-down::after {
content: "";
border-width: 0 0 2px 2px;
border-radius: 0 0 0 2px;
float: right;
}
.bracket-line.l-up::after {
content: "";
border-width: 2px 0 0 2px;
border-radius: 2px 0 0 0;
float: right;
}
.bracket-winner {
font-weight: bold;
}
.round1 {
grid-column: 2;
}
.round2 {
grid-column: 4;
}
.round3 {
grid-column: 6;
}
.round4 {
grid-column: 8;
}
.round5 {
grid-column: 10;
}
.round6 {
grid-column: 12;
}
.round7 {
grid-column: 14;
}
.round8 {
grid-column: 16;
}
.round9 {
grid-column: 18;
}
.round10 {
grid-column: 20;
}
.bracket-line.round0 {
grid-column: 1;
}
.bracket-line.round1 {
grid-column: 3;
}
.bracket-line.round2 {
grid-column: 5;
}
.bracket-line.round3 {
grid-column: 7;
}
.bracket-line.round4 {
grid-column: 9;
}
.bracket-line.round5 {
grid-column: 11;
}
.bracket-line.round6 {
grid-column: 13;
}
.bracket-line.round7 {
grid-column: 15;
}
.bracket-line.round8 {
grid-column: 17;
}
.bracket-line.round9 {
grid-column: 19;
}
.bracket-extrainfo {
float: right;
display: inline-block;
}
.qualified.bracket-winner,
.qualified.bracket-winner .bracket-team-points {
background-color: var(--color-up);
}
.both-qualified,
.both-qualified .bracket-team-points {
background-color: var(--color-up);
}
.bracket-hidden {
display: none!important;
}
.bracket-bye .bracket-team-name {
font-style: italic;
background-color: var(--table-header-background);
}
.bracket-bye .bracket-team-points {
background-color: var(--table-header-background);
}
.bracket-score-winner {
font-weight:bold;
}
.bracket-score-loser {
font-weight:normal;
}
.extended-series .bracket-team-points {
background-color: var(--color-extended-series);
}
.bracket-team-points.bracket-team-bestof {
width:2.5em;
color:var(--color-bracket-bestof);
}
|