Skip to content

Instantly share code, notes, and snippets.

@christippett
Last active September 4, 2024 21:13
Show Gist options
  • Save christippett/5097af0ea59c867c4578996350933776 to your computer and use it in GitHub Desktop.
Save christippett/5097af0ea59c867c4578996350933776 to your computer and use it in GitHub Desktop.
Hacker News Stylesheet

Hacker News userstyle

A modern re-styling of the Hacker News front page, including automatic light/dark mode.

Install directly with Stylus

Screenshots

HN 1

HN 2

/* ==UserStyle==
@name Hacker's New CSS
@author Chris Tippett <git@christippett.dev> (https://christippett.dev)
@description A modern retake on the classic Hacker News design.
@version 2024.08.12
@namespace @christippett
@homepageURL https://github.com/christippett
@supportURL https://gist.github.com/christippett/5097af0ea59c867c4578996350933776/
@updateURL https://gist.github.com/christippett/5097af0ea59c867c4578996350933776/raw/hn.user.css
@license MIT
@preprocessor uso
==/UserStyle== */
@-moz-document domain("news.ycombinator.com") {
:root {
color-scheme: light dark;
}
html {
--font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
"Noto Color Emoji";
--primary: #ff6600;
--primary-light: rgb(246, 125, 50);
--primary-alt: #c8c5c5;
--text-color: #4b4b4b;
--text-color-alt: #828282;
--table-bg: #fcfffa;
--body-bg: #f6f6ef;
--border-color: #f6f6ef;
--border-radius: 3px;
--smaller: 0.85em;
--content: 0.96rem;
--magic-num: 0.4rem;
font-size: 14px;
color: var(--text-color);
}
@media (prefers-color-scheme: dark) {
html {
--text-color: #f0f0f0;
--text-color-alt: #c3c3c3;
--primary-alt: #828282;
--border-color: #1b1b1c;
--table-bg: #232323;
--body-bg: #1b1b1c;
}
}
* {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-border-horizontal-spacing: 0;
-webkit-border-vertical-spacing: 0;
}
body {
padding: 20px 40px 50px;
background-color: var(--body-bg);
}
body,
td,
input[type="submit"],
.default,
.admin,
.title,
.subtext,
.yclinks,
.pagetop,
.comhead,
.comment {
font-family: var(--font-family);
}
code {
color: var(--primary-light);
}
@media (max-width: 750px) {
body {
padding: 0;
}
}
body>center {
position: relative;
}
table {
width: 100%;
}
td,
td.default,
.admin td {
font-size: var(--content);
color: var(--text-color);
line-height: 1.4;
}
p,
.default p,
.admin p {
margin-top: var(--smaller);
margin-bottom: var(--smaller);
}
br {
padding: 0;
}
/* hide consecutive <br> tags */
br+br,
span+br:first-of-type {
display: none;
}
font[size] {
font-size: var(--smaller);
}
/* help text on profile & post submission pages */
font[size="2"],
html[op="submit"] form>table tr:last-child td {
font-size: var(--smaller);
color: var(--text-color-alt);
font-style: italic;
}
a,
a:link,
.c00,
.c00 a:link {
color: var(--text-color);
text-underline-offset: 0.12em;
text-decoration-thickness: 1px;
}
form {
padding: 0;
max-width: 700px;
}
input[type="submit"] {
margin-top: var(--magic-num);
}
input[name="q"] {
background: var(--table-bg);
}
textarea,
input,
a.morelink {
max-width: 90%;
color: var(--text-color);
font-size: max(var(--smaller), calc(var(--magic-num) * 2));
background-color: var(--body-bg);
outline: 1px solid var(--border-color);
border: none;
border-radius: var(--border-radius);
box-shadow:
inset 0 0 1px var(--text-color),
2px 2px 4px var(--border-color);
padding: calc(var(--smaller) / 2);
}
textarea+a {
padding: var(--magic-num);
}
/* button style */
input[type="submit"],
select,
a.morelink,
.reply a {
cursor: pointer;
text-transform: capitalize;
}
.reply a:link {
color: var(--text-color-alt);
background-color: color-mix(in srgb, var(--body-bg) 50%, rgba(0, 0, 0, 0));
border-radius: var(--border-radius);
font-size: 0.85em;
padding: 0.3em 0.5em;
transition: background-color 120ms ease;
&:before {
content: "+ ";
display: inline;
}
&:hover {
background-color: var(--body-bg);
}
}
td[style="overflow:hidden"] {
overflow: visible !important;
}
/* --- LOGIN / CREATE ACCOUNT --*/
html:not([op]) body {
background-color: var(--table-bg);
}
html:not([op]) b {
display: inline-block;
margin-top: 1rem;
}
a[href="forgot"] {
display: inline-block;
margin-top: -1rem;
padding: calc(var(--magic-num) * 2);
color: var(--text-color-alt) !important;
font-style: italic;
text-decoration: none;
font-size: var(--smaller);
}
/* --- USER PROFILE --- */
#pagespace+tr>td:empty {
display: none;
}
table td:first-child:not([class])[valign="top"]+td {
padding: 0.25rem;
width: 100%;
}
/* field labels */
table td:first-child:not([class])[valign="top"] {
width: auto;
min-width: 120px;
padding-right: 1rem;
font-size: 0.75rem;
font-weight: 500;
text-transform: uppercase;
}
/* --- TABLE / LAYOUT --- */
#hnmain {
max-width: 900px;
overflow: hidden;
background: var(--table-bg);
border-radius: var(--border-radius);
box-shadow: 0 0 1px var(--primary-alt);
}
/* only add padding for parent table cells */
#hnmain>tbody>tr:not([id])>td:not([bgcolor]) {
padding: 1rem 1.25rem;
width: 100%;
}
/* add left/right padding when viewing user's threads */
#hnmain>tbody>tr.athing.comtr[id]>td {
padding: 0 1.25rem;
}
#hnmain tr:not(.spacer):empty {
display: none;
}
.morespace {
display: none;
}
.morespace+tr td {
padding: var(--magic-num) 0;
}
.spacer {
height: 1rem !important;
}
/* error message shown if not logged in when making comment */
html[op="x"] #hnmain>tbody>tr:last-child td {
color: transparent;
}
/* --- HEADER --- */
td[bgcolor] {
padding: var(--magic-num);
}
#hnmain>tbody>tr:first-child>td:first-child>table td {
height: auto !important;
line-height: 1 !important;
vertical-align: middle;
}
.pagetop {
color: white;
line-height: 1;
font-weight: 500;
font-size: var(--smaller);
padding: 0 var(--magic-num);
}
.pagetop font {
font-weight: bold;
}
.pagetop :is(a, a:visited) {
color: white;
line-height: 1rem;
}
.pagetop a:hover {
text-decoration: underline;
text-decoration-color: white;
}
/* 'hacker news' title */
.hnname {
margin-right: var(--magic-num);
font-size: 1.1rem;
}
.hnname a:hover {
text-decoration: none !important;
}
#me {
font-weight: 600;
font-style: italic;
}
#karma {
font-size: var(--smaller);
line-height: 1rem;
display: inline-flex;
align-items: center;
cursor: grabbing;
}
/* can't remember what this was supposed to hide */
span:not([class])[id]:not(#karma) {
display: none;
}
/* ---- FRONT PAGE ---- */
/* stories from [Month] [Day], [Year] */
#pagespace+tr>td>div {
padding: 0 0 1rem;
margin: 0 0 1rem !important;
border-bottom: 1px solid var(--primary-alt);
font-size: var(--content);
font-weight: 500;
}
#pagespace+tr>td>div>div {
font-style: italic;
font-size: var(--smaller);
margin-top: 0.25rem !important;
}
.hnmore a,
.hnmore a:is(:link, :visited) {
color: var(--text-color-alt);
}
/* --- ARTICLE --- */
.rank {
color: var(--text-color-alt);
font-weight: 500;
}
.title {
font-size: 1rem;
overflow: unset;
}
.titleline {
width: 100%;
font-weight: 500;
display: flex;
gap: 1rem;
justify-content: flex-start;
align-items: baseline;
}
html[op="item"] .titleline>a {
font-size: 1.2rem;
font-weight: 700;
}
/* transparent parantheses around url */
.titleline .sitebit {
flex: 1;
text-align: right;
color: transparent;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: block;
padding: 0 var(--magic-num);
}
.titleline .sitebit a {
font-weight: 300;
pointer-events: auto;
transition: color 220ms ease;
&:not(:active):hover {
color: var(--text-color);
}
}
.titleline:hover .sitebit a {
color: var(--text-color);
}
.subtext,
.subtext a {
display: inline-block;
color: var(--text-color-alt);
font-size: 0.9rem;
padding: 2px 0;
}
.subtext {
cursor: default;
}
.subtext .score {
color: var(--text-color-alt);
font-weight: 500;
border-radius: var(--border-radius);
}
/* ---- COMMENTS ---- */
table.itemlist,
table.fatitem,
table.comment-tree {
position: relative;
}
html:not([op="reply"]) table.fatitem {
padding-bottom: 0.75rem;
margin-bottom: 0.75rem;
border-bottom: 1px solid var(--primary-alt);
}
td.default {
width: 100%;
position: relative;
}
.toptext {
font-size: var(--content);
margin-bottom: -1rem;
}
/* comment: header/author/etc */
.comhead {
font-size: var(--smaller);
color: var(--text-color-alt);
}
a.hnuser {
font-weight: 500;
}
.comment {
margin: 0 0 var(--magic-num);
padding: var(--magic-num);
font-size: inherit;
}
.reply p {
margin-top: var(--smaller);
margin-bottom: var(--smaller);
}
.reply :is(u, a),
.reply a:is(:link, :visited) {
text-decoration: none;
}
.navs {
margin-left: 0.25ch;
display: inline-flex;
align-items: center;
gap: 0.75ch;
}
a.togg {
padding: 0.1em 0.2em;
font-weight: bold;
border-radius: var(--border-radius);
}
/* --- UPVOTE/DOWNVOTE LINKS --- */
.votelinks center:not(:has(font)) {
opacity: 0;
margin-top: 2px;
transition: opacity 120ms ease;
}
tr.athing:hover .votelinks center:has(a.clicky:not(.nosee)) {
opacity: 1;
}
/* self-post indicator */
.votelinks center font {
width: 1em;
display: block;
color: transparent;
position: relative;
}
.votelinks center font:before {
content: "★";
color: var(--primary);
display: inline;
font-size: 0.7em;
position: absolute;
}
/* space between author/links and coment text */
.votelinks>center> :is(font+br, br+img) {
display: none;
}
.admin td {
padding: 0 1rem;
}
.admin td>p:before,
.admin td:before {
content: "⎔ ";
font-size: 1.2em;
margin-right: 0.25em;
}
/* ---- FOOTER ---- */
#hnmain>tbody>tr:last-of-type:has(img) {
position: absolute;
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: center;
transform: translateY(100%);
z-index: 99;
padding: 1rem 0;
}
/* footer: hide orange horizontal bar above footer */
img[src="s.gif"]+table {
display: none;
}
.yclinks {
display: flex;
align-items: center;
justify-content: center;
gap: var(--magic-num);
color: var(--text-color-alt);
margin: var(--magic-num) 0;
padding: var(--magic-num) 1rem;
width: max-content;
border-radius: calc(var(--border-radius) * 2);
}
.yclinks a:link {
color: var(--text-color-alt);
font-size: 0.9rem;
}
.yclinks a:hover {
text-decoration: underline;
text-underline-offset: 2px;
text-decoration-thickness: 1px;
text-decoration-color: var(--primary);
}
form[action="//hn.algolia.com/"] {
color: var(--text-color-alt);
font-size: var(--smaller);
display: flex;
align-items: center;
justify-content: center;
gap: 1ch;
}
/* yc application notice */
center>a[href$="ycombinator.com/apply/"] {
color: var(--text-color-alt);
text-decoration: underline;
text-underline-offset: 2px;
font-weight: 500;
}
}
@pmarreck
Copy link

pmarreck commented Sep 2, 2023

Since I got banned by Reddit across everything thanks to "intelligence-free and thus sometimes accidentally too punitive fingerprinting", I can't even join "tildes" apparently. Is there another way to join?

@pmarreck
Copy link

pmarreck commented Sep 2, 2023

(actually, I applied to tilde.town via SSH. Which is an amazing user filter. LOL)

@christippett
Copy link
Author

Hey, it seems like the Userstyle has stopped working - starting today, at least for me.
Using the Userstyle directly from this gist, fwiw.

What extension are you using to apply the CSS? The gist hasn't been updated for awhile and it's still working fine for me on both Safari and Firefox.

@nervous-inhuman
Copy link

Hey, it seems like the Userstyle has stopped working - starting today, at least for me.
Using the Userstyle directly from this gist, fwiw.

What extension are you using to apply the CSS? The gist hasn't been updated for awhile and it's still working fine for me on both Safari and Firefox.

Stylus with Firefox, oddly enough, it seems to have fixed itself, but prior to that, it just wasn't getting applied at all.

@nervous-inhuman
Copy link

And it's back to not being applied. I'm clueless as to what's happening.

@christippett
Copy link
Author

And it's back to not being applied. I'm clueless as to what's happening.

When you next notice it not working, can you dump the HTML source here so I can take a look? Maybe there's an overly specific CSS selector that's not applying.

@nervous-inhuman
Copy link

And it's back to not being applied. I'm clueless as to what's happening.

When you next notice it not working, can you dump the HTML source here so I can take a look? Maybe there's an overly specific CSS selector that's not applying.

Yeah, I probably should've started with that, sorry.

Here it is: https://gist.github.com/nervous-inhuman/1e1f470ab7a108a8e4f759bcb77391ca

@christippett
Copy link
Author

christippett commented Sep 29, 2023

FYI I've been playing around with the style, mostly around the layout of items and the upvote/downvote links. I'll dogfeed it for a week to ensure I haven't completely broken things before I publish it. Maybe it'll fix things for a few of you – who knows!

@nervous-inhuman I loaded your HTML gist and the page rendered just fine with the current CSS. It perhaps suggests there's something else interfering with the style on your end?

@gingerbeardman
Copy link

gingerbeardman commented May 23, 2024

Seems broken for me as of today, all paragraphs are very narrow and stack horizontally.

Before

image

Quick Fix

  td.default div:first-child {
    display: inline-block !important;
    height: inherit !important;
  }

After

Screen shot 2024-05-23 at 21 45 21

@christippett
Copy link
Author

Oh yikes, yes that's awful. I've removed the td.default div:first-child rule altogether, the HTML has obviously been updated and it no longer targets whatever it was it was originally supposed to! Hopefully the update propagates quickly to a stylesheet extension near you...

@pmarreck
Copy link

Thanks @christippett ! I noticed the same the other day; turned it off assuming (correctly) that it'll get fixed. Appreciate this HN restyle!

@christippett
Copy link
Author

Chuffed you guys are still enjoying it. Apparently this is the most downloaded userstyle for Hacker News too!

image

@pmarreck
Copy link

Just noticed that if I try to style a code block by indenting it 4 spaces, it won't monospace it as it's supposed to...

@christippett
Copy link
Author

@pmarreck leave it with me, I'll look into it.

While we're posting bugs: there's an issue with the column widths becoming unwieldy if the first item on a page happens to be a sponsored post (ie. a YC company job ad). These items don't allow for up/down votes which throws out the alignment for the rest of the table. I've noticed this mostly when viewing page 2+ onwards.

@christippett
Copy link
Author

@pmarreck Just pushed an update. Code blocks should be back to being in monospace... I was too overzealous with my use of * { font-family: ... } 🫡

I also ended up removing most of the styles used customise the up/down vote icons in this update... it was giving me too much grief and the various corner-cases and workarounds needed to make them work weren't worth it.

Hopefully I haven't messed up anything too bad in the process!

@gingerbeardman
Copy link

Looking good!

@pmarreck
Copy link

Looks great over here, thanks @christippett !

@gingerbeardman
Copy link

gingerbeardman commented Jul 30, 2024

One request, comments that have had "-" clicked on them, marked as class .nosee are displayed very close to the following comment.

image

It would be great if the space could return underneath those. This seems to do it for me:

td.default > div:nth-child(1) {
  margin-bottom: 20px !important;
}

Screen shot 2024-07-30 at 23 25 26

@christippett
Copy link
Author

Good call. I'll make the change later today.

I also only just now made the connection between your account here and on HN... I commented on your macOS automation article completely oblivious that it was you. Thank you for not making it weird and picking my comment to take a screenshot of ;-)

@gingerbeardman
Copy link

OK, this is better:

td.nosee + td > div:first-child {
  margin-bottom: 10px !important;
}

@christippett
Copy link
Author

Sorry for the delay! Rather than piling on more styles, I removed the style I'd added that removed the original margin between comments.

@christippett
Copy link
Author

Unfortunately the diff is masked by whitespace changes (opps), what I changed was to remove .default br:has(+ div.comment) from the following section:

/* space between author/links and coment text */
.default br:has(+ div.comment),
.votelinks>center> :is(font+br, br+img) {
	display: none;
}

@gingerbeardman
Copy link

All good!

@pmarreck
Copy link

pmarreck commented Aug 22, 2024

I can't believe I'm saying this but I feel like CSS needs some kind of unit-testing framework lol

If only because a ton of the possible declarative changes you can make could result in regressions in other content that wasn't considered

It's possible that an LLM could help here because then you could assert on abstract notions like "make sure these voting controls in this one view are visible, but only when the related content is rolled over, and shaped like up/down triangles"

Just thinking aloud because I could probably build something like this if it was useful (and because I'm looking for work...)

@christippett
Copy link
Author

I've been giving this some thought too. What I think would be useful is to configure Playwright to take a bunch of screenshots of different pages which could then be manually reviewed before publishing a release. Each screenshot could have a side-by-side before/after view that would hopefully surface any obvious regressions or visual quirks...

@pmarreck
Copy link

That seems like a reasonable middle ground and would certainly automate most of the checking while also not forgetting anything!

@christippett
Copy link
Author

christippett commented Aug 23, 2024

The challenge is making sure that all possible states are accounted for. You'd probably want to test against static HTML to be as deterministic as possible, but the risk is you miss some minute change to the live site that messes everything up. Not forgetting that different users have varying levels of functionality available to them depending on their karma.

And then you start wondering if it's all a big over-engineered footgun and the easiest option is to just wait until someone leaves a comment here complaining about something not working 😉

PS. @pmarreck I see you're into EDM. I'm about to make my way to go see LCD Soundsystem 🤘

@pmarreck
Copy link

pmarreck commented Aug 26, 2024

@christippett DUDE I AM SO JEALOUS. James Murphy is the MAN! Truly a creative inspiration. Ha, enjoy!!
Ever see this? https://www.youtube.com/watch?v=Zj9Sv1JpmPs
I just left this comment on it: "If 'edging' was a song" 😂

@christippett
Copy link
Author

I'm going to play around with the upvote buttons again.. I miss the visual feedback of which posts/comments I've upvoted (the default hides the upvote arrows once voted). I've also unlocked downvote functionality for my account (501 karma), so I can easily see/test this particular use-case without relying on someone else's HTML dump!

@christippett
Copy link
Author

I've also started using this style on mobile which has surfaced a bunch of new issues (e.g. horizontal scroll at default zoom). I'd like to address these too as part of a future update.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment