:root { --bg: #f6f6f7; --card: #ffffff; --ink: #16181c; --ink-dim: #5b6470; --line: #e2e4e8; --accent-ink: #ffffff; --radius: 14px; color-scheme: light dark; } @media (prefers-color-scheme: dark) { :root { --bg: #0d0e11; --card: #16181d; --ink: #e9ebee; --ink-dim: #949cab; --line: #262a31; } } * { box-sizing: border-box; } body { margin: 0; padding: max(12px, env(safe-area-inset-top)) 12px calc(24px + env(safe-area-inset-bottom)); background: var(--bg); color: var(--ink); font: 16px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; -webkit-text-size-adjust: 100%; } main { max-width: 680px; margin: 0 auto; } .post { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; } /* ---------- header ---------- */ .post__head { display: flex; align-items: center; gap: 12px; padding: 12px 14px; border-bottom: 1px solid var(--line); } .badge { display: inline-flex; align-items: center; gap: 8px; flex: none; } .badge__mark { display: grid; place-items: center; width: 26px; height: 26px; border-radius: 8px; background: var(--accent); color: var(--accent-ink); font-size: 13px; font-weight: 700; line-height: 1; } .badge__label { font-size: 13px; font-weight: 600; letter-spacing: 0.01em; } .who { display: flex; align-items: center; gap: 8px; min-width: 0; margin-left: auto; } .who__avatar { width: 28px; height: 28px; border-radius: 50%; flex: none; } .who__names { display: flex; flex-direction: column; min-width: 0; text-align: right; } .who__name { font-size: 13px; font-weight: 600; } .who__name, .who__handle { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .who__handle { font-size: 12px; color: var(--ink-dim); } /* ---------- text ---------- */ .text { padding: 14px; font-size: 16px; white-space: normal; overflow-wrap: anywhere; } .text a { color: inherit; text-decoration-color: var(--ink-dim); text-underline-offset: 2px; } /* ---------- media ---------- */ /* Card-coloured, not black: whatever space the media does not fill should read as part of the page rather than as a border around it. */ .media { border-block: 1px solid var(--line); background: var(--card); } .post__head + .media, .text + .media { border-top: 1px solid var(--line); } .media__bar { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 12px; background: var(--card); border-bottom: 1px solid var(--line); } .media__count { font-size: 12px; color: var(--ink-dim); font-variant-numeric: tabular-nums; } .media__views { display: inline-flex; border: 1px solid var(--line); border-radius: 999px; overflow: hidden; } .media__view { appearance: none; border: 0; background: transparent; color: var(--ink-dim); font: inherit; font-size: 12px; padding: 5px 12px; cursor: pointer; } .media__view[aria-pressed="true"] { background: var(--ink); color: var(--card); } /* Swipe is the default, and needs no JavaScript to work. */ .media__rail { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; } .media__rail::-webkit-scrollbar { display: none; } .item { flex: 0 0 100%; scroll-snap-align: center; scroll-snap-stop: always; margin: 0; display: grid; place-items: center; position: relative; min-width: 0; min-height: 0; } /* Sized by its own proportions rather than stretched to the column: a portrait video filling the width is mostly empty space either side of it. The cap keeps any single item within the window. */ .item img, .item video { display: block; width: auto; height: auto; max-width: 100%; max-height: 70vh; max-height: 70dvh; object-fit: contain; background: transparent; } .item__note { position: absolute; inset-inline: 0; bottom: 0; padding: 4px 8px; font-size: 11px; color: #cfd4dc; background: rgba(0, 0, 0, 0.55); } .media[data-view="grid"] .media__rail { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 2px; overflow: visible; } .media[data-view="grid"] .item { aspect-ratio: 1 / 1; cursor: zoom-in; overflow: hidden; } /* * Filled absolutely rather than with height: 100%. The cell's height comes * from its aspect-ratio, which a percentage height will not resolve against, * and `aspect-ratio: auto` means "use the image's own" rather than "ignore * ratios" -- between them a portrait thumbnail ends up twice the height of * its cell. */ .media[data-view="grid"] .item img, .media[data-view="grid"] .item video { position: absolute; inset: 0; width: 100%; height: 100%; max-width: none; max-height: none; min-height: 0; object-fit: cover; aspect-ratio: auto !important; } .media[data-view="grid"] .item__note { display: none; } /* ---------- footer ---------- */ .post__foot { padding: 14px; display: flex; flex-direction: column; gap: 12px; } .post__foot time { font-size: 12px; color: var(--ink-dim); } .original { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; } .copy, .original__open { appearance: none; font: inherit; font-size: 14px; padding: 8px 14px; border-radius: 999px; cursor: pointer; text-decoration: none; border: 1px solid var(--line); } .copy { background: var(--ink); color: var(--card); border-color: var(--ink); } .copy[data-copied] { background: #1a7f4b; border-color: #1a7f4b; } .original__open { background: transparent; color: var(--ink); } .original__url { flex-basis: 100%; margin: 0; } .original__url code { font-size: 12px; color: var(--ink-dim); overflow-wrap: anywhere; user-select: all; } /* ---------- error and index ---------- */ .error__heading { font-size: 18px; margin: 14px; } .error__detail { margin: 0 14px 14px; color: var(--ink-dim); font-size: 14px; } .post--error .post__head:empty { display: none; } .post--index { padding: 18px; } .index__title { margin: 0; font-size: 20px; } .index__lede { color: var(--ink-dim); font-size: 14px; } .index__table { width: 100%; border-collapse: collapse; font-size: 13px; } .index__table th { text-align: left; color: var(--ink-dim); font-weight: 600; padding: 6px 8px 6px 0; } .index__table td { padding: 8px 8px 8px 0; border-top: 1px solid var(--line); vertical-align: middle; } .index__table code { overflow-wrap: anywhere; } /* ---------- verification puzzle ---------- */ .challenge__lede { margin: 14px; font-size: 14px; color: var(--ink-dim); } .challenge__stage { position: relative; display: grid; place-items: center; padding: 8px; background: #000; border-block: 1px solid var(--line); min-height: 160px; } .challenge__stage img { max-width: 100%; /* The puzzle is a drag target, so the browser must not claim the gesture for scrolling or image-dragging. */ touch-action: none; user-select: none; -webkit-user-drag: none; cursor: grab; } .challenge__stage img:active { cursor: grabbing; } .challenge__status { position: absolute; bottom: 8px; margin: 0; padding: 4px 10px; border-radius: 999px; font-size: 12px; color: #e9ebee; background: rgba(0, 0, 0, 0.65); } /* ---------- threads ---------- */ .segment + .segment { border-top: 1px solid var(--line); } /* A chain reads as one column of posts, so the media in each needs its own top edge only when text sits above it. */ .segment .text + .media { border-top: 1px solid var(--line); } .post[data-segments] .segment { position: relative; } .segment__mark { margin: 0; padding: 8px 14px 0; font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--ink-dim); } .post[data-segments] .segment[data-anchor="true"] { /* The link may point at the middle of a thread, so say which one it was without hiding the rest. */ background: color-mix(in srgb, var(--accent, #888) 7%, transparent); } /* ---------- fitting the window ---------- */ /* * On a large screen a single post should be readable without scrolling to * find it -- a video especially, which you can full-screen if you want it * bigger. * * The cap is the window less the chrome around the media: header, caption and * footer come to roughly 270-350px on the posts this serves. Deriving it * instead of subtracting a constant is the obvious thing to try and does not * work: the card is sized by its contents, so a percentage height inside it * resolves to auto and clamps nothing. * * A thread is a column of several posts and cannot fit whatever we do, so it * keeps the looser cap above and scrolls. */ @media (min-width: 700px) and (min-height: 560px) { .post:not([data-segments]) .item img, .post:not([data-segments]) .item video { max-height: calc(100vh - 350px); max-height: calc(100dvh - 350px); /* Below that the media would be too small to be worth showing; better to let a very long caption scroll. */ min-height: 220px; } } /* ---------- a post with a headline ---------- */ /* Reddit is the one platform where the post has a title and the body is optional, so the headline has to carry on its own. */ .text__title { margin: 0 0 8px; font-size: 19px; line-height: 1.3; font-weight: 650; } .text__title:only-child { margin-bottom: 0; } /* ---------- comments ---------- */ .thread { margin-top: 12px; background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); } .thread__head { display: flex; align-items: baseline; gap: 10px; padding: 12px 14px; border-bottom: 1px solid var(--line); } .thread__title { margin: 0; font-size: 15px; font-weight: 650; } .thread__count { font-size: 12px; color: var(--ink-dim); } .thread__toggle { appearance: none; margin-left: auto; border: 1px solid var(--line); background: transparent; color: var(--ink-dim); font: inherit; font-size: 12px; padding: 4px 12px; border-radius: 999px; cursor: pointer; } .thread__list { padding: 6px 14px 14px; } .c { margin-top: 10px; } /* The whole byline is the hit target for folding, so it wants to look like one thing rather than a row of separate labels. */ .c__head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; font-size: 12px; color: var(--ink-dim); cursor: pointer; list-style: none; padding: 2px 0; user-select: none; } .c__head::-webkit-details-marker { display: none; } .c__head::before { content: "–"; display: inline-grid; place-items: center; width: 15px; height: 15px; border: 1px solid var(--line); border-radius: 4px; font-size: 11px; line-height: 1; color: var(--ink-dim); flex: none; } .c:not([open]) > .c__head::before { content: "+"; } .c__author { font-weight: 600; color: var(--ink); } .c__hidden { font-variant-numeric: tabular-nums; } .c[open] > .c__head .c__hidden { display: none; } .c__mark { padding: 1px 5px; border-radius: 4px; font-size: 10px; font-weight: 700; letter-spacing: 0.03em; color: var(--accent-ink); background: var(--ink-dim); } .c__mark--op { background: #ff4500; } .c__mark--mod, .c__mark--admin { background: #1a7f4b; } .c__body { font-size: 15px; overflow-wrap: anywhere; } .c__body > :first-child { margin-top: 4px; } .c__body > :last-child { margin-bottom: 0; } .c__body p { margin: 0 0 8px; } .c__body a { color: inherit; text-decoration-color: var(--ink-dim); text-underline-offset: 2px; } .c__body ul, .c__body ol { margin: 0 0 8px; padding-left: 20px; } .c__body blockquote { margin: 0 0 8px; padding-left: 10px; border-left: 3px solid var(--line); color: var(--ink-dim); } .c__body pre { margin: 0 0 8px; padding: 8px 10px; border-radius: 8px; background: color-mix(in srgb, var(--ink) 6%, transparent); overflow-x: auto; } .c__body code { font-size: 13px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; } .c__body pre code { font-size: 12px; } /* The rule down the left is what tells you where a subtree ends, which is the thing a wall of replies otherwise makes you count indents for. */ .c__replies { margin: 6px 0 0 7px; padding-left: 11px; border-left: 1px solid var(--line); } .c__more { margin: 8px 0 0; font-size: 12px; color: var(--ink-dim); } .thread__more { margin: 0; padding: 12px 14px; border-top: 1px solid var(--line); font-size: 12px; color: var(--ink-dim); } @media (max-width: 520px) { .c__replies { margin-left: 4px; padding-left: 7px; } } /* ---------- a quoted post ---------- */ /* * A post inside a post. Bordered and inset rather than merely indented, * because the whole job of this block is to be unmistakably someone else's * — an indent alone reads as a continuation of the words above it. */ .quote { margin: 0 14px 14px; border: 1px solid var(--line); border-radius: 10px; overflow: hidden; } .text + .quote, .media + .quote { margin-top: 14px; } .quote__who { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 6px; padding: 10px 12px 0; font-size: 13px; min-width: 0; } .quote__avatar { width: 20px; height: 20px; border-radius: 50%; flex: none; align-self: center; } .quote__name { font-weight: 600; } .quote__handle, .quote__who time { color: var(--ink-dim); font-size: 12px; } .quote__text { padding: 6px 12px 10px; font-size: 15px; overflow-wrap: anywhere; } .quote__text a { color: inherit; text-decoration-color: var(--ink-dim); text-underline-offset: 2px; } /* Its own media sits flush in the block, the way the post's own does in the card, so the two read as the same kind of thing at different sizes. */ .quote .media { border-bottom: 0; } .quote__text + .media, .quote__who + .media { border-top: 1px solid var(--line); } .quote__open { display: block; padding: 9px 12px; border-top: 1px solid var(--line); font-size: 12px; color: var(--ink-dim); text-decoration: none; } /* * Deliberately smaller than the post's own media, and after the window-fitting * rule above so it wins on source order at equal specificity. A quote is * context for the post, and context that fills the screen has stopped being * context. */ .post .quote .item img, .post .quote .item video { max-height: 40vh; max-height: 40dvh; min-height: 0; } /* * A post that quotes another has two things to show, so its own media gives * up the window-filling cap above. Left at full height the quote starts below * the fold, which is the same as not having it — the whole reason it is here * is that neither half reads without the other. */ .segment[data-quoted] .item img, .segment[data-quoted] .item video { max-height: 45vh; max-height: 45dvh; min-height: 0; }