This page looks best with JavaScript enabled

Tournament brackets (part 1)

 ·  ☕ 27 min read

The 2020 LCS Summer Playoffs bracket

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

An empty 4-team single elimination bracket
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:

  • z-down
  • z-up

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:spans 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:

A 4SE bracket mocked up in Excel

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 divs 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 divs 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:

Diagram showing the 5 border areas

Their corresponding CSS references:

  1. ::before {border-top}
  2. ::after {border-top}
  3. ::before {border-bottom}
  4. ::after {border-bottom}
  5. ::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:

Diagram of a Z-connector, showing the 5 areas around it

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.

Diagram of an L-connector, showing the 5 areas around it

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:

Part of a 64SE bracket

The quarterfinals of that 64SE bracket

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);
}
Share on

river
WRITTEN BY
River
River is a developer most at home in MediaWiki and known for building Leaguepedia. She likes cats.

What's on this Page