aeb5c18ca3
Two pieces:
1) Multi-tenant onboarding via scripts/add-digest-user.sh
Shared miniflux + per-user digest stack. Onboarding a teammate
takes one command (plus a one-time sudo for dir creation):
scripts/add-digest-user.sh <username>
What the script does:
- Reads miniflux admin creds from ana-docker
- Allocates next free port (scans existing digest-*/.env)
- Generates a random password (or accepts one as 2nd arg)
- Creates the miniflux user via the admin API
- Materializes a per-user .env at /opt/docker/compose/digest-<user>/
(inherits NEWS_DIGEST_TAG from the canonical stack so all
tenants run the same image)
- Brings up `docker compose -p digest-<user> up -d`
- Seeds default world/local feeds in the new user's miniflux
- Triggers a first digest run
compose.yaml now uses ${DIGEST_PROJECT:-news-digest} to namespace
container_name + homepage labels. Default keeps backward-compat
for the singleton install — existing stacks unaffected.
2) Masthead overlap on phone widths
Desktop CSS pinned .masthead-edition to grid-row 1, which collided
with .masthead-brand once the mobile media query collapsed both
to grid-column 1. Result: "MORNING EDITION" badge stacked on top
of the "DAILY DIGEST" hero. Reset grid-row to `auto` for all
three masthead children in the ≤720 px breakpoint so they
auto-flow vertically.
1047 lines
27 KiB
CSS
1047 lines
27 KiB
CSS
/* -------------------------------------------------------------------------
|
||
news-digest — daily intel briefing.
|
||
|
||
Aesthetic: operations-center chrome (Australis cool-mono palette,
|
||
JetBrains Mono UPPERCASE eyebrows, mdi-glyph anchor) wrapping
|
||
editorial-serif news content (Fraunces). Two type families that
|
||
wouldn't normally meet, intentionally combined: the chrome says
|
||
"filed at 0800 from the bridge"; the headlines say "this is news,
|
||
read it like news."
|
||
|
||
Type:
|
||
Display (headlines): Fraunces — variable serif w/ optical sizes
|
||
Body (summaries): Inter — same as Australis, optimized for screens
|
||
Mono (chrome): JetBrains Mono — eyebrows / metadata / chips
|
||
|
||
Color: pure Australis Ice + Sea + Aurora. Score chips lean
|
||
--aus-blue (info); release tags --aus-green; drama --aus-red
|
||
(sparingly). No warm accents — Australis is cool-only by design.
|
||
------------------------------------------------------------------------- */
|
||
|
||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Inter:wght@400;500;600;700&family=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600;9..144,700;9..144,800&display=swap');
|
||
|
||
:root {
|
||
/* Australis palette — vendored subset */
|
||
--aus-black: #222531;
|
||
--aus-white: #a9bcc3;
|
||
--aus-bright-white: #cce7ec;
|
||
--aus-bright-black: #373b46;
|
||
--aus-dark-30: #414751;
|
||
--aus-dark-40: #565f69;
|
||
--aus-dark-50: #6e7882;
|
||
--aus-dark-60: #86929d;
|
||
--aus-bright-70: #9daeb6;
|
||
--aus-bright-80: #b3cbcf;
|
||
--aus-blue: #6388d8;
|
||
--aus-bright-blue: #a4c4ff;
|
||
--aus-cyan: #00b1a8;
|
||
--aus-bright-cyan: #42dcd1;
|
||
--aus-green: #16b866;
|
||
--aus-bright-green: #51e08a;
|
||
--aus-yellow: #e1c631;
|
||
--aus-bright-yellow:#ffe14e;
|
||
--aus-red: #ff491a;
|
||
--aus-magenta: #9d78ff;
|
||
|
||
--bg: var(--aus-black);
|
||
--bg-elev: var(--aus-bright-black);
|
||
--surface: #2a2e3a;
|
||
--surface-hi: var(--aus-dark-30);
|
||
--rule: var(--aus-dark-30);
|
||
--rule-bright: var(--aus-dark-40);
|
||
--fg: var(--aus-bright-white);
|
||
--fg-dim: var(--aus-bright-70);
|
||
--fg-faint: var(--aus-dark-50);
|
||
--accent: var(--aus-bright-cyan);
|
||
--accent-dim: var(--aus-cyan);
|
||
|
||
/* Type families */
|
||
--font-display: "Fraunces", "Times New Roman", Georgia, serif;
|
||
--font-sans: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
|
||
--font-mono: "JetBrains Mono", ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;
|
||
|
||
/* Scale + chrome */
|
||
--pad-x: 24px;
|
||
--content-w: 880px;
|
||
}
|
||
|
||
*, *::before, *::after { box-sizing: border-box; }
|
||
[hidden] { display: none !important; }
|
||
|
||
html, body {
|
||
margin: 0;
|
||
background: var(--bg);
|
||
color: var(--fg);
|
||
font: 16px/1.55 var(--font-sans);
|
||
font-feature-settings: "ss01" 1;
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
|
||
a { color: inherit; text-decoration: none; }
|
||
a:hover { color: var(--accent); }
|
||
|
||
/* -------------------------------------------------------------------------
|
||
masthead — full-width banner; the "I am a news edition" statement
|
||
------------------------------------------------------------------------- */
|
||
|
||
.masthead {
|
||
border-bottom: 1px solid var(--rule);
|
||
background: var(--bg-elev);
|
||
}
|
||
|
||
.masthead-inner {
|
||
max-width: var(--content-w);
|
||
margin: 0 auto;
|
||
padding: 36px var(--pad-x) 24px;
|
||
display: grid;
|
||
grid-template-columns: auto 1fr;
|
||
grid-template-rows: auto auto;
|
||
column-gap: 32px;
|
||
row-gap: 16px;
|
||
align-items: end;
|
||
}
|
||
|
||
.masthead-brand {
|
||
grid-column: 1 / 2;
|
||
grid-row: 1 / 2;
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 10px;
|
||
font-family: var(--font-mono);
|
||
/* Brand wraps an <a href="index.html"> so the hero is clickable to
|
||
the latest edition from any archived page. Strip default link
|
||
styling so the visual is unchanged. */
|
||
color: inherit;
|
||
text-decoration: none;
|
||
transition: opacity 0.15s ease;
|
||
}
|
||
.masthead-brand:hover {
|
||
opacity: 0.85;
|
||
}
|
||
.masthead-brand:focus-visible {
|
||
outline: 2px solid var(--accent);
|
||
outline-offset: 4px;
|
||
border-radius: 2px;
|
||
}
|
||
.brand-glyph {
|
||
font-size: 32px;
|
||
line-height: 1;
|
||
color: var(--accent);
|
||
text-shadow: 0 0 12px rgba(66, 220, 209, .35);
|
||
}
|
||
.brand-mark {
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
letter-spacing: .18em;
|
||
color: var(--fg);
|
||
}
|
||
|
||
.masthead-edition {
|
||
grid-column: 2 / 3;
|
||
grid-row: 1 / 2;
|
||
display: flex;
|
||
align-items: baseline;
|
||
justify-content: flex-end;
|
||
gap: 18px;
|
||
font-family: var(--font-mono);
|
||
}
|
||
.archive-link {
|
||
color: var(--fg-dim);
|
||
font-size: 11px;
|
||
letter-spacing: .14em;
|
||
border-bottom: 1px dotted var(--fg-faint);
|
||
padding-bottom: 1px;
|
||
}
|
||
.archive-link:hover { color: var(--accent); border-color: var(--accent-dim); }
|
||
.edition-num {
|
||
color: var(--fg-faint);
|
||
font-size: 11px;
|
||
letter-spacing: .14em;
|
||
}
|
||
.edition-stamp {
|
||
/* THE statement piece — large mono in Australis yellow.
|
||
"This is the morning/evening brief, filed officially." */
|
||
font-family: var(--font-mono);
|
||
font-size: 22px;
|
||
font-weight: 700;
|
||
letter-spacing: .14em;
|
||
color: var(--aus-yellow);
|
||
padding: 4px 10px;
|
||
border: 1px solid var(--aus-yellow);
|
||
border-radius: 2px;
|
||
}
|
||
|
||
.masthead-meta {
|
||
grid-column: 1 / 3;
|
||
grid-row: 2 / 3;
|
||
display: grid;
|
||
grid-template-columns: repeat(3, auto);
|
||
gap: 4px 32px;
|
||
align-items: baseline;
|
||
font-family: var(--font-mono);
|
||
padding-top: 16px;
|
||
border-top: 1px solid var(--rule);
|
||
}
|
||
.meta-row {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 10px;
|
||
font-size: 11px;
|
||
letter-spacing: .08em;
|
||
}
|
||
.meta-label {
|
||
color: var(--fg-faint);
|
||
text-transform: uppercase;
|
||
letter-spacing: .14em;
|
||
}
|
||
.meta-value {
|
||
color: var(--fg);
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
/* The aurora glow rule — sanctioned Australis gradient, used once. */
|
||
.aurora-rule {
|
||
height: 2px;
|
||
background: linear-gradient(90deg,
|
||
transparent 0%,
|
||
var(--aus-bright-blue) 25%,
|
||
var(--aus-bright-cyan) 50%,
|
||
var(--aus-bright-green) 75%,
|
||
transparent 100%);
|
||
opacity: .65;
|
||
}
|
||
|
||
/* -------------------------------------------------------------------------
|
||
jump nav
|
||
------------------------------------------------------------------------- */
|
||
|
||
.jumpnav {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 5;
|
||
background: rgba(34, 37, 49, 0.92);
|
||
backdrop-filter: blur(8px);
|
||
-webkit-backdrop-filter: blur(8px);
|
||
border-bottom: 1px solid var(--rule);
|
||
}
|
||
.jumpnav ol {
|
||
list-style: none;
|
||
margin: 0;
|
||
padding: 12px var(--pad-x);
|
||
max-width: var(--content-w);
|
||
margin-inline: auto;
|
||
display: flex;
|
||
gap: 28px;
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
letter-spacing: .14em;
|
||
text-transform: uppercase;
|
||
}
|
||
.jumpnav a {
|
||
color: var(--fg-dim);
|
||
display: inline-flex;
|
||
align-items: baseline;
|
||
gap: 8px;
|
||
}
|
||
.jumpnav a:hover { color: var(--fg); }
|
||
.jump-num { color: var(--fg-faint); font-size: 10px; }
|
||
|
||
/* -------------------------------------------------------------------------
|
||
main brief
|
||
------------------------------------------------------------------------- */
|
||
|
||
.brief {
|
||
max-width: var(--content-w);
|
||
margin: 0 auto;
|
||
padding: 32px var(--pad-x) 64px;
|
||
}
|
||
|
||
.desk + .desk { margin-top: 64px; }
|
||
|
||
.desk-head {
|
||
display: grid;
|
||
/* num | title | count | collapse — explicit columns so the
|
||
collapse button doesn't auto-flow into the desk-sub row. */
|
||
grid-template-columns: auto 1fr auto auto;
|
||
align-items: baseline;
|
||
column-gap: 16px;
|
||
padding-bottom: 14px;
|
||
margin-bottom: 24px;
|
||
border-bottom: 1px solid var(--rule-bright);
|
||
}
|
||
.desk-num {
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
letter-spacing: .14em;
|
||
color: var(--aus-yellow);
|
||
padding-top: 4px; /* aligns visually with the serif baseline */
|
||
}
|
||
.desk-title {
|
||
font-family: var(--font-display);
|
||
font-size: 38px;
|
||
font-weight: 700;
|
||
letter-spacing: -0.015em;
|
||
margin: 0;
|
||
color: var(--fg);
|
||
font-variation-settings: "opsz" 144;
|
||
}
|
||
.desk-sub {
|
||
/* Sub-line hugs the title baseline */
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
letter-spacing: .08em;
|
||
color: var(--fg-faint);
|
||
text-transform: uppercase;
|
||
grid-column: 2 / 3;
|
||
align-self: end;
|
||
padding-bottom: 6px;
|
||
padding-left: 12px;
|
||
}
|
||
.desk-count {
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
font-variant-numeric: tabular-nums;
|
||
letter-spacing: .08em;
|
||
color: var(--fg-dim);
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
/* Per-desk collapse toggle. Sits on the right edge of the desk header,
|
||
clicking collapses the body to a single-line summary. State is
|
||
persisted in localStorage by section id (#world, #local, etc), so
|
||
each device remembers per-user preference. */
|
||
.desk-collapse {
|
||
grid-column: 4 / 5;
|
||
grid-row: 1 / 2;
|
||
align-self: center;
|
||
background: transparent;
|
||
border: 1px solid transparent;
|
||
border-radius: 4px;
|
||
padding: 4px 8px;
|
||
font-family: var(--font-mono);
|
||
font-size: 14px;
|
||
line-height: 1;
|
||
color: var(--fg-dim);
|
||
cursor: pointer;
|
||
transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease, transform 0.18s ease;
|
||
}
|
||
.desk-collapse:hover,
|
||
.desk-collapse:focus-visible {
|
||
color: var(--accent);
|
||
border-color: var(--rule-bright);
|
||
background: var(--bg-elev);
|
||
outline: none;
|
||
}
|
||
.desk.is-collapsed .desk-collapse {
|
||
transform: rotate(-90deg);
|
||
}
|
||
/* When collapsed, hide everything inside the desk EXCEPT the header.
|
||
The .desk-head is direct child; .source / .headlines-list etc are
|
||
the rest of the body. */
|
||
.desk.is-collapsed > :not(.desk-head) {
|
||
display: none;
|
||
}
|
||
/* Tighten the header bottom-border in collapsed state — no items below
|
||
means the heavy underline looks weird floating alone. */
|
||
.desk.is-collapsed .desk-head {
|
||
margin-bottom: 0;
|
||
padding-bottom: 8px;
|
||
border-bottom: 1px dashed var(--rule);
|
||
}
|
||
|
||
/* -------------------------------------------------------------------------
|
||
per-source block
|
||
------------------------------------------------------------------------- */
|
||
|
||
.source + .source { margin-top: 36px; }
|
||
|
||
.source-head {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 14px;
|
||
margin-bottom: 14px;
|
||
}
|
||
.source-name {
|
||
font-family: var(--font-mono);
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
letter-spacing: .12em;
|
||
text-transform: uppercase;
|
||
color: var(--accent);
|
||
border-bottom: 1px dashed var(--accent-dim);
|
||
padding-bottom: 1px;
|
||
}
|
||
.source-name:hover { color: var(--aus-bright-cyan); }
|
||
.source-count {
|
||
font-family: var(--font-mono);
|
||
font-size: 10px;
|
||
letter-spacing: .14em;
|
||
color: var(--fg-faint);
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
/* -------------------------------------------------------------------------
|
||
item — the news card
|
||
------------------------------------------------------------------------- */
|
||
|
||
.items {
|
||
list-style: none;
|
||
margin: 0;
|
||
padding: 0;
|
||
display: grid;
|
||
gap: 24px;
|
||
}
|
||
|
||
.item {
|
||
display: grid;
|
||
grid-template-columns: 96px 1fr;
|
||
column-gap: 24px;
|
||
padding-bottom: 24px;
|
||
border-bottom: 1px solid var(--rule);
|
||
}
|
||
.item:last-child {
|
||
border-bottom: 0;
|
||
padding-bottom: 0;
|
||
}
|
||
|
||
.item-rail {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-end;
|
||
gap: 6px;
|
||
padding-top: 6px;
|
||
}
|
||
|
||
.chip {
|
||
font-family: var(--font-mono);
|
||
font-size: 10.5px;
|
||
font-weight: 600;
|
||
letter-spacing: .06em;
|
||
padding: 2px 8px;
|
||
border: 1px solid currentColor;
|
||
border-radius: 2px;
|
||
white-space: nowrap;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
.chip-score { color: var(--aus-blue); }
|
||
.chip-comments { color: var(--fg-dim); border-color: var(--rule-bright); }
|
||
.chip-tag {
|
||
font-size: 9.5px;
|
||
letter-spacing: .14em;
|
||
text-transform: uppercase;
|
||
}
|
||
.chip-tag-news { color: var(--aus-bright-blue); }
|
||
.chip-tag-tutorial { color: var(--aus-bright-cyan); }
|
||
.chip-tag-release { color: var(--aus-bright-green); }
|
||
.chip-tag-discussion{ color: var(--fg-dim); }
|
||
.chip-tag-question { color: var(--aus-yellow); }
|
||
.chip-tag-showcase { color: var(--aus-magenta); }
|
||
.chip-tag-drama { color: var(--aus-red); }
|
||
.chip-tag-meme { color: var(--fg-faint); }
|
||
|
||
.item-body {
|
||
min-width: 0;
|
||
}
|
||
|
||
.item-title {
|
||
margin: 0 0 8px;
|
||
font-family: var(--font-display);
|
||
font-size: 22px;
|
||
line-height: 1.2;
|
||
font-weight: 600;
|
||
letter-spacing: -0.01em;
|
||
color: var(--fg);
|
||
font-variation-settings: "opsz" 36;
|
||
}
|
||
.item-title a { color: inherit; }
|
||
.item-title a:hover { color: var(--accent); }
|
||
|
||
.item-tldr {
|
||
margin: 0 0 12px;
|
||
font-size: 15px;
|
||
line-height: 1.55;
|
||
color: var(--fg-dim);
|
||
}
|
||
|
||
.item-foot {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
gap: 16px;
|
||
font-family: var(--font-mono);
|
||
font-size: 10.5px;
|
||
letter-spacing: .06em;
|
||
color: var(--fg-faint);
|
||
}
|
||
/* Action chips — explicit "go here" buttons. Reddit items get TWO of
|
||
these when the post links externally: SOURCE (the linked content)
|
||
and REDDIT THREAD (the discussion). Non-Reddit items get just SOURCE. */
|
||
.item-actions {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
.action {
|
||
display: inline-flex;
|
||
align-items: baseline;
|
||
gap: 6px;
|
||
padding: 4px 10px;
|
||
border: 1px solid var(--rule-bright);
|
||
border-radius: 2px;
|
||
font-family: var(--font-mono);
|
||
font-size: 10.5px;
|
||
font-weight: 600;
|
||
letter-spacing: .08em;
|
||
text-transform: uppercase;
|
||
color: var(--fg-dim);
|
||
transition: border-color 120ms, color 120ms, background 120ms;
|
||
}
|
||
.action-arrow {
|
||
color: var(--accent-dim);
|
||
font-size: 11px;
|
||
}
|
||
.action-label { color: var(--fg); }
|
||
.action-domain {
|
||
color: var(--fg-dim);
|
||
font-weight: 500;
|
||
letter-spacing: .04em;
|
||
text-transform: lowercase;
|
||
}
|
||
.action:hover {
|
||
border-color: var(--accent);
|
||
background: rgba(66, 220, 209, 0.08);
|
||
}
|
||
.action:hover .action-arrow,
|
||
.action:hover .action-label,
|
||
.action:hover .action-domain { color: var(--accent); }
|
||
|
||
.action-source { /* default styling above is "source" */ }
|
||
.action-thread .action-arrow { color: var(--aus-bright-blue); }
|
||
.action-thread:hover { border-color: var(--aus-bright-blue); background: rgba(164, 196, 255, 0.08); }
|
||
.action-thread:hover .action-arrow,
|
||
.action-thread:hover .action-label { color: var(--aus-bright-blue); }
|
||
|
||
.item-meta-row {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 8px;
|
||
margin-left: auto; /* pushes timestamp to the right */
|
||
}
|
||
.item-meta { font-variant-numeric: tabular-nums; color: var(--fg-faint); }
|
||
.sep { color: var(--rule-bright); }
|
||
|
||
/* Visual de-emphasize purely meme/skip items if any slip through. */
|
||
.item[data-tag="meme"] { opacity: .58; }
|
||
.item[data-tag="other"] { opacity: .85; }
|
||
|
||
/* -------------------------------------------------------------------------
|
||
headlines — dense list (world / local).
|
||
Different rhythm from .item cards: one row per story, source on the
|
||
right, title-led. Volume-driven sections where the deliverable is
|
||
"what happened" not "what someone said about what happened".
|
||
------------------------------------------------------------------------- */
|
||
|
||
.headlines-list {
|
||
list-style: none;
|
||
margin: 0;
|
||
padding: 0;
|
||
display: grid;
|
||
gap: 0;
|
||
}
|
||
|
||
.headline {
|
||
position: relative; /* anchors the absolute-positioned × button */
|
||
display: grid;
|
||
grid-template-columns: 1fr auto auto;
|
||
grid-template-rows: auto auto;
|
||
align-items: baseline;
|
||
column-gap: 16px;
|
||
row-gap: 4px;
|
||
padding: 14px 0;
|
||
border-bottom: 1px solid var(--rule);
|
||
}
|
||
.headline-title { grid-column: 1 / 2; grid-row: 1; }
|
||
.headline-source { grid-column: 2 / 3; grid-row: 1; }
|
||
.headline-time { grid-column: 3 / 4; grid-row: 1; }
|
||
.headline-tldr {
|
||
grid-column: 1 / 4;
|
||
grid-row: 2;
|
||
margin: 4px 0 2px;
|
||
font-family: var(--font-sans);
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
color: var(--fg-dim);
|
||
}
|
||
.headline:last-child {
|
||
border-bottom: 0;
|
||
}
|
||
|
||
.headline-title {
|
||
margin: 0;
|
||
font-family: var(--font-display);
|
||
font-size: 17px;
|
||
line-height: 1.35;
|
||
font-weight: 500;
|
||
letter-spacing: -0.005em;
|
||
color: var(--fg);
|
||
font-variation-settings: "opsz" 24;
|
||
/* Title takes the leftmost column; allow it to wrap on narrow screens. */
|
||
min-width: 0;
|
||
}
|
||
.headline-title a { color: inherit; }
|
||
.headline-title a:hover { color: var(--accent); }
|
||
|
||
.headline-source {
|
||
font-family: var(--font-mono);
|
||
font-size: 10px;
|
||
font-weight: 600;
|
||
letter-spacing: .12em;
|
||
color: var(--fg-faint);
|
||
text-transform: uppercase;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.headline-time {
|
||
font-family: var(--font-mono);
|
||
font-size: 10.5px;
|
||
color: var(--fg-faint);
|
||
font-variant-numeric: tabular-nums;
|
||
min-width: 32px;
|
||
text-align: right;
|
||
}
|
||
|
||
/* Hidden state — same mechanism as .item, applies to .headline too. */
|
||
.headline.is-hidden { display: none; }
|
||
|
||
/* -------------------------------------------------------------------------
|
||
archive page — list of all editions
|
||
------------------------------------------------------------------------- */
|
||
|
||
.archive-list {
|
||
list-style: none;
|
||
margin: 0;
|
||
padding: 0;
|
||
display: grid;
|
||
gap: 0;
|
||
border: 1px solid var(--rule);
|
||
border-radius: 2px;
|
||
overflow: hidden;
|
||
}
|
||
.archive-item + .archive-item { border-top: 1px solid var(--rule); }
|
||
|
||
.archive-row {
|
||
display: grid;
|
||
grid-template-columns: 1fr auto auto;
|
||
align-items: center;
|
||
gap: 24px;
|
||
padding: 16px 20px;
|
||
background: var(--surface);
|
||
transition: background 120ms;
|
||
}
|
||
.archive-row:hover {
|
||
background: var(--surface-hi);
|
||
color: var(--fg);
|
||
}
|
||
|
||
.archive-date {
|
||
font-family: var(--font-display);
|
||
font-size: 17px;
|
||
font-weight: 500;
|
||
letter-spacing: -0.005em;
|
||
color: var(--fg);
|
||
font-variation-settings: "opsz" 36;
|
||
}
|
||
|
||
.archive-edition {
|
||
font-family: var(--font-mono);
|
||
font-size: 10.5px;
|
||
font-weight: 700;
|
||
letter-spacing: .14em;
|
||
padding: 3px 9px;
|
||
border: 1px solid currentColor;
|
||
border-radius: 2px;
|
||
}
|
||
.archive-edition-am { color: var(--aus-yellow); }
|
||
.archive-edition-pm { color: var(--aus-bright-cyan); }
|
||
|
||
.archive-arrow {
|
||
font-family: var(--font-mono);
|
||
color: var(--fg-faint);
|
||
font-size: 14px;
|
||
}
|
||
.archive-row:hover .archive-arrow { color: var(--accent); }
|
||
|
||
/* -------------------------------------------------------------------------
|
||
empty state + colophon
|
||
------------------------------------------------------------------------- */
|
||
|
||
.desk.empty {
|
||
text-align: center;
|
||
padding: 72px 24px;
|
||
border: 1px dashed var(--rule);
|
||
border-radius: 6px;
|
||
}
|
||
.empty-msg {
|
||
font-family: var(--font-mono);
|
||
font-size: 13px;
|
||
color: var(--fg-faint);
|
||
line-height: 1.6;
|
||
}
|
||
.empty-msg code {
|
||
color: var(--fg-dim);
|
||
font-family: inherit;
|
||
background: var(--surface);
|
||
padding: 2px 6px;
|
||
border-radius: 2px;
|
||
}
|
||
|
||
.colophon {
|
||
border-top: 1px solid var(--rule);
|
||
background: var(--bg-elev);
|
||
padding: 24px var(--pad-x);
|
||
margin-top: 64px;
|
||
}
|
||
.colophon-inner {
|
||
max-width: var(--content-w);
|
||
margin: 0 auto;
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 20px;
|
||
font-family: var(--font-mono);
|
||
font-size: 10.5px;
|
||
letter-spacing: .08em;
|
||
}
|
||
.colophon-block {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
.colophon-block .meta-label { color: var(--fg-faint); font-size: 10px; }
|
||
.colophon-block .meta-value { color: var(--fg-dim); font-size: 11px; }
|
||
|
||
/* -------------------------------------------------------------------------
|
||
per-item × button + hidden tray
|
||
------------------------------------------------------------------------- */
|
||
|
||
/* The .item layout is a 2-column grid; the × button absolute-positions
|
||
over its top-right corner so it doesn't disturb the rail/body grid. */
|
||
.item {
|
||
position: relative;
|
||
}
|
||
|
||
.item-hide {
|
||
position: absolute;
|
||
top: -2px;
|
||
right: -4px;
|
||
width: 22px;
|
||
height: 22px;
|
||
padding: 0;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: transparent;
|
||
border: 1px solid transparent;
|
||
border-radius: 4px;
|
||
color: var(--fg-faint);
|
||
font-family: var(--font-sans);
|
||
font-size: 18px;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
opacity: 0;
|
||
transition: opacity 0.15s ease, color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
|
||
}
|
||
.item:hover .item-hide,
|
||
.item:focus-within .item-hide,
|
||
.item-hide:focus-visible {
|
||
opacity: 1;
|
||
}
|
||
.item-hide:hover,
|
||
.item-hide:focus-visible {
|
||
color: var(--aus-red);
|
||
border-color: var(--rule-bright);
|
||
background: var(--bg-elev);
|
||
outline: none;
|
||
}
|
||
|
||
/* Hidden state — display:none rather than animate, since we re-render
|
||
pre-paint from the API set on every page load. Keeping it simple. */
|
||
.item.is-hidden {
|
||
display: none;
|
||
}
|
||
|
||
/* When every item in a .source (or every source in a .desk) is hidden,
|
||
collapse the whole header out — no point rendering "r/homelab (0)".
|
||
app.js applies .is-empty when the visible-count drops to zero. */
|
||
.source.is-empty,
|
||
.desk.is-empty {
|
||
display: none;
|
||
}
|
||
|
||
.hidden-tray {
|
||
margin-top: 24px;
|
||
padding: 14px 18px;
|
||
background: var(--bg-elev);
|
||
border: 1px solid var(--rule);
|
||
border-radius: 6px;
|
||
}
|
||
.hidden-tray-head {
|
||
margin: 0;
|
||
}
|
||
.hidden-tray-toggle {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 4px 6px;
|
||
margin-left: -6px;
|
||
background: transparent;
|
||
border: none;
|
||
color: var(--fg-dim);
|
||
font-family: var(--font-sans);
|
||
font-size: 11px;
|
||
letter-spacing: 0.14em;
|
||
text-transform: uppercase;
|
||
cursor: pointer;
|
||
border-radius: 4px;
|
||
transition: color 0.15s ease, background 0.15s ease;
|
||
}
|
||
.hidden-tray-toggle:hover,
|
||
.hidden-tray-toggle:focus-visible {
|
||
color: var(--fg);
|
||
background: var(--surface);
|
||
outline: none;
|
||
}
|
||
.tray-glyph {
|
||
display: inline-block;
|
||
font-size: 11px;
|
||
transition: transform 0.18s ease;
|
||
}
|
||
.hidden-tray.is-expanded .tray-glyph {
|
||
transform: rotate(180deg);
|
||
}
|
||
.tray-count {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-width: 22px;
|
||
padding: 0 6px;
|
||
background: var(--surface);
|
||
color: var(--fg);
|
||
border-radius: 10px;
|
||
font-family: var(--font-mono);
|
||
font-size: 11px;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
.hidden-tray-list {
|
||
margin: 12px 0 0;
|
||
padding: 0;
|
||
list-style: none;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
.hidden-tray-item {
|
||
display: grid;
|
||
grid-template-columns: 1fr auto;
|
||
align-items: baseline;
|
||
gap: 12px;
|
||
padding: 6px 8px;
|
||
border-radius: 4px;
|
||
transition: background 0.12s ease;
|
||
}
|
||
.hidden-tray-item:hover {
|
||
background: var(--surface);
|
||
}
|
||
.hidden-tray-title {
|
||
font-family: var(--font-display);
|
||
font-weight: 400;
|
||
font-size: 14px;
|
||
color: var(--fg-dim);
|
||
line-height: 1.35;
|
||
/* Truncate with ellipsis on long titles so the row stays one line. */
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.hidden-tray-restore {
|
||
padding: 2px 8px;
|
||
background: transparent;
|
||
border: 1px solid var(--rule-bright);
|
||
border-radius: 4px;
|
||
color: var(--fg-dim);
|
||
font-family: var(--font-sans);
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
|
||
}
|
||
.hidden-tray-restore:hover,
|
||
.hidden-tray-restore:focus-visible {
|
||
color: var(--accent);
|
||
border-color: var(--accent);
|
||
background: var(--bg);
|
||
outline: none;
|
||
}
|
||
|
||
/* -------------------------------------------------------------------------
|
||
responsive — two breakpoints:
|
||
≤ 720 px : tablet / narrow desktop. Soften the layout but still
|
||
room for two-column-ish.
|
||
≤ 480 px : true phone. Strip the rail, hide section numbers, tighten
|
||
everything for a single-thumb scroll.
|
||
------------------------------------------------------------------------- */
|
||
|
||
@media (max-width: 720px) {
|
||
:root { --pad-x: 16px; }
|
||
.masthead-inner {
|
||
grid-template-columns: 1fr;
|
||
grid-template-rows: auto auto auto;
|
||
padding: 24px var(--pad-x) 16px;
|
||
row-gap: 12px;
|
||
}
|
||
/* Desktop pins brand+edition both to grid-row 1, which collapses
|
||
them on top of each other once we go single-column. Reset row
|
||
placement so they auto-flow vertically. */
|
||
.masthead-brand,
|
||
.masthead-edition,
|
||
.masthead-meta {
|
||
grid-column: 1 / 2;
|
||
grid-row: auto;
|
||
}
|
||
.masthead-edition { justify-content: flex-start; flex-wrap: wrap; gap: 12px; }
|
||
.edition-stamp { font-size: 16px; padding: 3px 8px; }
|
||
.masthead-meta {
|
||
grid-template-columns: 1fr;
|
||
gap: 6px;
|
||
padding-top: 12px;
|
||
}
|
||
.meta-row { font-size: 10.5px; flex-wrap: wrap; }
|
||
|
||
.jumpnav ol { gap: 18px; padding: 10px var(--pad-x); }
|
||
|
||
/* Desk header: num / title row 1, sub spans below, count + collapse
|
||
pushed to row 1 right-side. */
|
||
.desk-head {
|
||
grid-template-columns: auto 1fr auto;
|
||
row-gap: 6px;
|
||
}
|
||
.desk-num { padding-top: 2px; }
|
||
.desk-title { font-size: 26px; }
|
||
.desk-sub {
|
||
grid-column: 1 / 4;
|
||
padding-left: 0;
|
||
padding-bottom: 0;
|
||
align-self: start;
|
||
}
|
||
.desk-count { font-size: 10px; align-self: center; }
|
||
.desk-collapse {
|
||
grid-column: 3 / 4;
|
||
grid-row: 1 / 2;
|
||
margin-left: 4px;
|
||
/* Bigger touch target for thumbs. */
|
||
padding: 6px 10px;
|
||
font-size: 16px;
|
||
}
|
||
|
||
/* Items — keep the rail but compact it. */
|
||
.item { grid-template-columns: 60px 1fr; column-gap: 12px; }
|
||
.item-rail { padding-top: 4px; gap: 4px; }
|
||
.chip { font-size: 9.5px; padding: 1px 6px; }
|
||
.item-title { font-size: 18px; }
|
||
.item-tldr { font-size: 14px; }
|
||
|
||
.colophon-inner { grid-template-columns: 1fr; }
|
||
|
||
/* On touch screens there's no hover, so the × always shows.
|
||
Slightly bigger tap target too (44×44 minimum is the WCAG ask;
|
||
28 is the visible bullseye, padded to ~44 effective via the
|
||
parent .item gutter). */
|
||
.item-hide { opacity: 1; width: 30px; height: 30px; font-size: 20px; }
|
||
.hidden-tray { padding: 12px 14px; }
|
||
|
||
/* Headlines: stack title / source / time / tldr vertically. */
|
||
.headline {
|
||
grid-template-columns: 1fr;
|
||
gap: 4px;
|
||
padding: 12px 0;
|
||
}
|
||
.headline-title,
|
||
.headline-source,
|
||
.headline-time,
|
||
.headline-tldr {
|
||
grid-column: 1 / 2;
|
||
grid-row: auto;
|
||
}
|
||
.headline-time { text-align: left; }
|
||
.headline-tldr { font-size: 14px; }
|
||
}
|
||
|
||
@media (max-width: 480px) {
|
||
:root { --pad-x: 14px; }
|
||
|
||
/* Masthead: shrink the brand strip, drop the edition number badge. */
|
||
.masthead-inner { padding: 18px var(--pad-x) 12px; }
|
||
.brand-glyph { font-size: 26px; }
|
||
.brand-mark { font-size: 12px; letter-spacing: .14em; }
|
||
.edition-num { display: none; }
|
||
.archive-link { font-size: 10px; }
|
||
|
||
.jumpnav ol {
|
||
gap: 14px;
|
||
font-size: 10.5px;
|
||
overflow-x: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
flex-wrap: nowrap;
|
||
white-space: nowrap;
|
||
}
|
||
.jumpnav { -webkit-overflow-scrolling: touch; }
|
||
|
||
/* Phone: drop the section number badge AND the rail. Chips for
|
||
reddit cards float into the body inline (the "▲ 234" stays
|
||
useful but the column itself is wasteful at this width). */
|
||
.desk-num { display: none; }
|
||
.desk-head { grid-template-columns: 1fr auto; column-gap: 10px; }
|
||
.desk-title { font-size: 22px; }
|
||
.desk-sub { grid-column: 1 / 3; }
|
||
.desk-count { display: none; }
|
||
.desk-collapse { grid-column: 2 / 3; padding: 8px 12px; }
|
||
|
||
/* Strip the rail entirely; chips go inline above the title. Keeps
|
||
score/comment context but reclaims the 60px gutter for content. */
|
||
.item {
|
||
grid-template-columns: 1fr;
|
||
column-gap: 0;
|
||
row-gap: 6px;
|
||
padding-bottom: 18px;
|
||
}
|
||
.item-rail {
|
||
flex-direction: row;
|
||
align-items: center;
|
||
align-self: start;
|
||
gap: 6px;
|
||
padding-top: 0;
|
||
}
|
||
.chip { font-size: 9px; padding: 1px 5px; }
|
||
.item-title { font-size: 17px; line-height: 1.3; }
|
||
.item-tldr { font-size: 13.5px; }
|
||
.item-foot { gap: 10px; font-size: 10px; }
|
||
.action { padding: 4px 8px; font-size: 10px; }
|
||
.item-meta-row { margin-left: 0; flex-basis: 100%; }
|
||
|
||
.colophon { padding: 18px var(--pad-x); margin-top: 32px; }
|
||
.colophon-block .meta-value { font-size: 10.5px; word-break: break-word; }
|
||
|
||
.brief { padding: 20px var(--pad-x) 40px; }
|
||
.desk + .desk { margin-top: 40px; }
|
||
}
|
||
|
||
/* Belt-and-suspenders: never let a long URL or unbroken token push
|
||
horizontal overflow. */
|
||
@media (max-width: 720px) {
|
||
body { overflow-x: hidden; }
|
||
.item-title, .item-tldr, .headline-title, .headline-tldr {
|
||
overflow-wrap: anywhere;
|
||
word-break: break-word;
|
||
}
|
||
}
|