Make the bars have colors on the repos page

0dfdda04380fc9f3a49687b47a9f2d232e5195e1

Tucker McKnight <tmcknight@instructure.com> | Wed Jul 22 2026

Make the bars have colors on the repos page

Adds a new, optional parameter to the repo configuration:
defaultTemplateConfiguration.colors.languageGraph. This should be
an array of hex color strings (or anything else that can be used as
a `background-color: <here>;` value in CSS).

Also includes some style and spacing tweaks for the new header,
especially at small widths.
js_templates/commits.ts:30
Before
29
30
31
32
33
34
            href: nav.rssFeed()
          }, [
            m('img', {
              src: `${nav.rootPath()}/frontend/img/rss-icon.svg`,
              style: "width: 11px; margin-right: 5px;"
            }),
            'RSS',
After
29
30
31
32
33
34
            href: nav.rssFeed()
          }, [
            m('img', {
              src: `${nav.rootPath()}frontend/img/rss-icon.svg`,
              style: "width: 11px; margin-right: 5px;"
            }),
            'RSS',
js_templates/common/commonPage.ts:4
Before
3
4
5
6

7
export default async (template, reposConfiguration, eleventyConfig) => {
  return async (data) => {
    const opts = await template(reposConfiguration, eleventyConfig, data)
    
⁣
    return render([
      m.trust('<!doctype html>'),
After
3
4
5
6
7
8
export default async (template, reposConfiguration, eleventyConfig) => {
  return async (data) => {
    const opts = await template(reposConfiguration, eleventyConfig, data)
    const rootPath = opts.rootPath == '/' ? '' : opts.rootPath
    
    return render([
      m.trust('<!doctype html>'),
js_templates/common/commonPage.ts:12
Before
11
12
13
14
15
16
17
18
          m('meta', {charset: "utf-8"}),
          m('meta', {name: "viewport", content: "width=device-width, initial-scale=1"}),
          m('title', opts.pageTitle),
          m('link', {rel: "stylesheet", href: `${opts.rootPath}/css/design-board.css`}),
          opts.additionalHeadContent || null,
          m('script', {src: `${opts.rootPath}/frontend/top.js`}),
        ]),
        m('body', [
          m('div', {class: "container-fluid container-lg"}, [
After
11
12
13
14
15
16
17
18
          m('meta', {charset: "utf-8"}),
          m('meta', {name: "viewport", content: "width=device-width, initial-scale=1"}),
          m('title', opts.pageTitle),
          m('link', {rel: "stylesheet", href: `${rootPath}/css/design-board.css`}),
          opts.additionalHeadContent || null,
          m('script', {src: `${rootPath}/frontend/top.js`}),
        ]),
        m('body', [
          m('div', {class: "container-fluid container-lg"}, [
js_templates/common/commonPage.ts:61
Before
60
61
62
63
64
65
            integrity: "sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI",
            crossorigin: "anonymous"
          }),
          m('script', {src: `${opts.rootPath}/frontend/main-frontend.bundle.js`})
        ])
      ])
    ])
After
60
61
62
63
64
65
            integrity: "sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI",
            crossorigin: "anonymous"
          }),
          m('script', {src: `${rootPath}/frontend/main-frontend.bundle.js`})
        ])
      ])
    ])
js_templates/helpers/nav.ts:42
Before
41
42
43
44
45
46
        return "/"
      }
      else {
        return reposPath
      }
    },
    repoHomePath: () => {
After
41
42
43
44
45
46
        return "/"
      }
      else {
        return reposPath + '/'
      }
    },
    repoHomePath: () => {
js_templates/repo.ts:64
Before
63
64
65
66

67
  const largestPercent = Math.max(...topLanguagePercentages.map(tuple => tuple[1]), otherLanguagePercent)

  const readmeContent = await renderContentIfAvailable(await getReadMe(repo.name, ref.name), ref.name)

⁣
  const pageContent = [
    m('div', {class: "row"}, [
After
63
64
65
66
67
68
  const largestPercent = Math.max(...topLanguagePercentages.map(tuple => tuple[1]), otherLanguagePercent)

  const readmeContent = await renderContentIfAvailable(await getReadMe(repo.name, ref.name), ref.name)
  const colors = reposConfig.defaultTemplate?.colors?.languageGraph || []

  const pageContent = [
    m('div', {class: "row"}, [
js_templates/repo.ts:80
Before
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
            ]),
            m('div', {class: "col-12 col-lg-6 d-flex flex-column justify-content-around"}, [
              m('div', {class: 'row'}, [
                m('div', {class: "col-6"}, [
                  m('div', {class: "row"}, [
                    m('div', {class: "col"}, [
                      m('div', {class: "text-white d-inline-block"}, [
                        m('div', {class: "d-flex align-items-center"}, [
                          m('div', {class: "fs-5 fw-light"}, `Latest commit in ${ref.name}`),
                          m('a', {href: nav.rssFeed(), class: "ps-1 p-0 btn badge shadow-none"}, [
                            m('img', {
⁣
                              src: `${nav.rootPath()}/frontend/img/rss-icon.svg`,
                              style: "width: 11px; margin-right: 5px;"
                            }),
                            'RSS',
                          ]),
                        ]),
                        m('div', {class: "font-monospace"}, [
                          `${latestCommit.date.toDateString().split(' ').slice(1).join(' ')} `,
                          m('a', {href: nav.commit(latestCommit.hash), class: "fw-bold link-info"}, latestCommit.hash.substr(0, 6))
                        ]),
                      ])
                    ])
                  ]),
                  m('div', {class: "row mt-2"}, [
                    m('div', {class: "col"}, [
                      m('div', {class: "text-white mb-2 d-inline-block"}, [
                        m('div', {class: "fs-5 fw-light"}, 'Latest commit overall'),
                        m('div', {class: "font-monospace"}, [
                          `${mostRecentCommitOverall.commit.date.toDateString().split(' ').slice(1).join(' ')} `,
                          m('a', {
                            href: nav.commit(mostRecentCommitOverall.commit.hash),
                            class: "link-info fw-bold"
                          }, mostRecentCommitOverall.commit.hash.substr(0, 6))
                        ]),
                        m('span', {class: "fw-light"}, [
                          'in branch ',
                          m('a', {
                            href: nav.commits(1, {
After
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
            ]),
            m('div', {class: "col-12 col-lg-6 d-flex flex-column justify-content-around"}, [
              m('div', {class: 'row'}, [
                m('div', {class: "col-12 col-sm-6"}, [
                  m('div', {class: "row"}, [
                    m('div', {class: "col-6 col-lg-12 pb-0 pb-lg-2"}, [
                      m('div', {class: "text-white d-inline-block"}, [
                        m('div', {class: "latest-commit fs-5 fw-light"}, [
                          m('span', {class: 'me-1'}, `Latest in ${ref.name}`),
                          m('a', {href: nav.rssFeed(), class: "fw-light p-0 btn badge shadow-none"}, [
                            m('img', {
                              class: 'mb-1 me-1',
                              src: `${nav.rootPath()}frontend/img/rss-icon.svg`,
                              style: "width: 11px;"
                            }),
                            'RSS',
                          ]),
                        ]),
                        m('div', {class: "small font-monospace"}, [
                          `${latestCommit.date.toDateString().split(' ').slice(1).join(' ')} `,
                          m('a', {href: nav.commit(latestCommit.hash), class: "fw-bold link-info"}, latestCommit.hash.substr(0, 6))
                        ]),
                      ])
⁣
                    ]),
⁣
                    m('div', {class: "col-6 col-lg-12 pb-3 pb-lg-0"}, [
                      m('div', {class: "text-white d-inline-block"}, [
                        m('div', {class: "latest-commit fs-5 fw-light"}, 'Latest overall'),
                        m('div', {class: "small font-monospace"}, [
                          `${mostRecentCommitOverall.commit.date.toDateString().split(' ').slice(1).join(' ')} `,
                          m('a', {
                            href: nav.commit(mostRecentCommitOverall.commit.hash),
                            class: "link-info fw-bold"
                          }, mostRecentCommitOverall.commit.hash.substr(0, 6))
                        ]),
                        m('span', {class: "fs-6 fw-light"}, [
                          'in branch ',
                          m('a', {
                            href: nav.commits(1, {
js_templates/repo.ts:126
Before
125
126
127
128
129
130
131
132
133
134
135
136
137
138




139
140
141



142
143
144
                    ])
                  ])
                ]),
                m('div', {class: "col-6 d-flex"}, [
                  m('div', {class: "d-flex flex-column"}, [
                    topLanguagePercentages.map((percentTuple) => {
                      return m('div', {class: 'me-1 d-flex justify-content-end align-items-end small text-white font-monospace flex-grow-1'}, percentTuple[0])
                    }),
                    otherLanguagePercent > 0
                    ? m('div', {class: 'me-1 d-flex justify-content-end align-items-end small text-white font-monospace flex-grow-1'}, 'other')
                    : null
                  ]),
                  m('div', {class: "flex-grow-1 d-flex flex-column"}, [
                    topLanguagePercentages.map((percentTuple) => {
⁣
⁣
⁣
⁣
                      return m('div', {class: 'language-col flex-grow-1', style: `width: ${percentTuple[1] / largestPercent * 100}%;`})
                    }),
                    otherLanguagePercent > 0
⁣
⁣
⁣
                      ? m('div', {class: 'language-col flex-grow-1', style: `width: ${otherLanguagePercent / largestPercent * 100}%;`})
                      : null
                  ])
                ]),
After
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
                    ])
                  ])
                ]),
                m('div', {class: "col-12 col-sm-6 d-flex"}, [
                  m('div', {class: "d-flex flex-column"}, [
                    topLanguagePercentages.map((percentTuple) => {
                      return m('div', {class: 'language-name me-1 d-flex justify-content-end align-items-end small text-white font-monospace flex-grow-1'}, percentTuple[0])
                    }),
                    otherLanguagePercent > 0
                    ? m('div', {class: 'language-name me-1 d-flex justify-content-end align-items-end small text-white font-monospace flex-grow-1'}, 'other')
                    : null
                  ]),
                  m('div', {class: "flex-grow-1 d-flex flex-column"}, [
                    topLanguagePercentages.map((percentTuple, index) => {
                      const color = colors[index % (colors.length - 1)]
                      return m('div', {
                        class: 'language-col flex-grow-1',
                        style: `width: ${percentTuple[1] / largestPercent * 100}%; background-color: ${color};`
                      })
                    }),
                    otherLanguagePercent > 0
                      ? m('div', {
                        class: 'language-col flex-grow-1',
                        style: `width: ${otherLanguagePercent / largestPercent * 100}%; background-color: ${colors[colors.length - 1]};`
                      })
                      : null
                  ])
                ]),
schemas/ReposConfiguration.json:158
Before
157
158
159












160
161
            "allRepositoriesPageTitle": {
              "type": "string"
            },
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
            "enabled": {
              "default": true,
              "description": "Whether or not the default template should be used. If `true`, will generate the type of HTML pages seen at https://tuckerm.us/repos.",
After
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
            "allRepositoriesPageTitle": {
              "type": "string"
            },
            "colors": {
              "additionalProperties": false,
              "properties": {
                "languageGraph": {
                  "items": {
                    "type": "string"
                  },
                  "type": "array"
                }
              },
              "type": "object"
            },
            "enabled": {
              "default": true,
              "description": "Whether or not the default template should be used. If `true`, will generate the type of HTML pages seen at https://tuckerm.us/repos.",
scss/design-board.scss:150
Before
149
150
151



152
153
154
155



156
157
.language-percentages {
  font-size: 0;
}
⁣
⁣
⁣
.language-col {
  background: black;
  display: flex;
  align-items: end;
⁣
⁣
⁣
}
.language-percent {
  display: inline-block;
After
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
.language-percentages {
  font-size: 0;
}
.latest-commit {
  line-height: normal;
}
.language-col {
  background: black;
  display: flex;
  align-items: end;
  max-height: 2rem;
  margin-top: 1px;
  margin-bottom: 1px;
}
.language-percent {
  display: inline-block;
scss/design-board.scss:165
Before
164
165
166
167
168
169
170
  background-color: $magenta;
}
.language-name {
  display: inline-block;
  font-size: 1rem;
}

.rel-dropdown[data-selected=branch] .tag {
After
164
165
166

167
168
169
  background-color: $magenta;
}
.language-name {
⁣
  max-height: 2rem;
}

.rel-dropdown[data-selected=branch] .tag {
src/configTypes.ts:56
Before
55
56
57



58
59
    */
    enabled?: boolean,
    allRepositoriesPageTitle?: string,
⁣
⁣
⁣
  },
}
After
55
56
57
58
59
60
61
62
    */
    enabled?: boolean,
    allRepositoriesPageTitle?: string,
    colors?: {
      languageGraph?: Array<string>,
    },
  },
}