Open pub-trivia.app and paste this into the console:
getComputedStyle(document.documentElement).getPropertyValue("--chart-1")
// "220 70% 50%"
That is a chart colour. We have five of them, declared three times over, and there is not a single chart anywhere in the product. Nothing reads any of those variables. The only other occurrence of the word "chart" in the whole repository is inside a Christmas quiz question about the 1952 singles chart.
If you would rather not take the console's word for it, the declarations are in the stylesheet the CDN is serving right now:
curl -s https://pub-trivia.app/ \
| grep -oE '/_next/static/[^"?]+\.css' | head -1 \
| sed 's|^|https://pub-trivia.app|' | xargs curl -s \
| grep -o -- '--chart-[0-9]' | wc -l
# 15
The whole file is 117 lines of source and 85,513 bytes compiled, 15,378 over the wire with brotli. This is an audit of all of it, because a stylesheet is the one file in a project nobody ever reads top to bottom.
Both Tailwind configuration systems, at the same time
The file opens with two lines that are not supposed to appear together:
@import "tailwindcss";
@config "../tailwind.config.ts";
The first is Tailwind 4, where configuration lives in CSS. The second points at an 80-line JavaScript config object in the Tailwind 3 style: content globs, darkMode: "class", a theme.extend.colors map, keyframes, and plugins: [require("tailwindcss-animate")].
@config exists exactly for this, so this is not broken. It is a supported bridge for a project that was generated against v3 and upgraded. But the result is that answering "where is the border radius defined" means knowing that the scale is in the JS file, the variable it reads is in the CSS file, and the font families are in a third place, a @theme block between them:
@theme {
--font-family-sans: var(--font-geist), system-ui, sans-serif;
--font-family-mono: var(--font-geist-mono), monospace;
}
Three configuration surfaces for one design system, each of them correct. The thing I would change is not the bridge, it is that nothing in the project says the bridge is there. A new file wanting a new colour has a one in three chance of putting it somewhere the other two places disagree with.
There is also a require() call sitting in a .ts file inside a package declaring "type": "module". Tailwind loads that config through a loader that handles it, and Node would not. It works, and it is one of those lines that will cost somebody twenty minutes the first time they try to import the config from anywhere else.
Three palettes, one of which is a copy of another
The file carries three blocks of custom properties: :root, .dark and .light. Twenty five declarations in the first, twenty four in each of the others.
Diff :root against .light and the difference is one line. .light is :root, verbatim, minus --radius, plus color-scheme: light. Same hues, same lightness values, same order. The duplication is in the shipped bytes too:
# .light{--background:0 0% 100%;--foreground:222.2 84% 4.9%;--card:0 0% 100%; …
The reason it exists is next-themes with attribute="class": the theme name becomes a class on <html>, so a theme called light needs a .light selector to match, and :root on its own will not answer to it. The reason it is a copy rather than a reference is that nobody wrote .light { } as an empty block and trusted inheritance, which would have been enough, since :root is already the light palette.
What makes this more than tidying is that the two palettes are not the same design. The light values are the stock shadcn slate scale, with hues like 222.2 84% 4.9% and 210 40% 96.1%. The dark values are pure neutral greys, 0 0% 9% and 0 0% 16%, hand-picked for this product. Only one of those two was ever art-directed, and it is not the one that got written down twice.
The palette nobody can see
Which palette does the public actually get? The root layout mounts the provider like this:
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem disableTransitionOnChange>
and the <head> hard-codes the browser chrome colour:
<meta name="theme-color" content="#171717" />
So a first-time visitor with nothing in localStorage gets class="dark" on <html>, which you can confirm on any page of the site:
document.documentElement.className // "dark"
localStorage.getItem("theme") // null
enableSystem is on, so the system option exists, but the default is dark rather than system, and the only control that can change it is a theme toggle that lives in the dashboard header. The dashboard is behind a login. Which means the light palette, the one duplicated in full across two selectors and shipped to every visitor in a stylesheet they download, is reachable only after signing up.
And if a signed-in user does switch to light, theme-color is still #171717. The page goes white, the browser chrome and the PWA splash stay near-black, because that meta tag is a static string in the layout and knows nothing about the class on <html>.
The toggle itself is honest about the one thing it cannot do:
if (!mounted) {
return <Button variant="ghost" size="icon" aria-label="Toggle theme" disabled />
}
The server has no idea what theme the client will resolve to, so rendering the sun or the moon before mount is a coin flip and a hydration mismatch. A disabled button of the right size holds the space without claiming anything.
The last four lines are the most aggressive thing in the file
* {
scrollbar-width: none;
}
*::-webkit-scrollbar {
display: none;
}
Not a container. Not a specific overflow panel. Every element on every page, public and signed-in, with the comment "Hide all scrollbars".
On the marketing site this is nearly invisible and arguably a win, because those pages are a single column you scroll with a wheel or a thumb. In the app it is a real decision with a real cost: any panel that scrolls now scrolls with no indication that it can, and no indication of how far through it you are. Keyboard and wheel still work, the affordance is gone, and on a desktop browser the affordance is most of how people know the panel is scrollable at all.
It is also the kind of rule that is extremely hard to undo later, because by the time you notice, several layouts quietly depend on the scrollbar taking up no width.
The whole audit in one line
Fifteen chart colours nothing paints with, twenty four duplicated declarations, three places to configure one design system, a light theme that needs an account, and a scrollbar rule with a blast radius of every element on the site. None of it is a bug. All of it is what a stylesheet turns into when every individual change is reasonable and nobody reads the file end to end.
Both palettes are live if you want to compare them: the dark one is on every page, and the light one is in the bundle you just downloaded, waiting for a class it cannot get.
Top comments (0)