diff --git a/index.html b/index.html
index 5148878..4229b37 100644
--- a/index.html
+++ b/index.html
@@ -5,7 +5,7 @@
-
+
@@ -15,7 +15,7 @@
-
+
diff --git a/src/App.css b/src/App.css
index b4e1c36..4242b57 100644
--- a/src/App.css
+++ b/src/App.css
@@ -20,7 +20,7 @@
font-style: italic;
}
.md code {
- font-family: monospace;
+ font-family: var(--font-mono, monospace);
background: var(--color-bg);
border: 1px solid var(--color-border);
border-radius: 3px;
@@ -131,10 +131,14 @@
.dump-title-view {
position: relative;
display: inline-flex;
- align-items: baseline;
+ align-items: center;
gap: 0.4rem;
border-radius: 6px;
max-width: 100%;
+ /* Padding gives the hover box breathing room (incl. right of the pencil);
+ the matching negative margin keeps the title's visual position unchanged. */
+ padding: 0.1rem 0.45rem;
+ margin: -0.1rem -0.45rem;
}
.dump-title-view--editable {
@@ -147,7 +151,9 @@
.dump-title-edit-btn {
font-size: 0.85rem;
- color: var(--color-muted);
+ line-height: 1;
+ flex-shrink: 0;
+ color: var(--color-text-muted);
opacity: 0;
transition: opacity 0.1s;
pointer-events: none;
@@ -164,18 +170,34 @@
gap: 0.5rem;
}
+/* Shared chrome for inline-edit inputs (the editors that live outside `.form`).
+ Each rule below adds only its own size / weight / width deltas. */
+.dump-title-input,
+.profile-email-input,
+.playlist-edit-input,
+.playlist-edit-textarea {
+ border: 2px solid var(--color-border);
+ border-radius: 8px;
+ background: var(--color-bg);
+ color: var(--color-text);
+ font-family: inherit;
+ outline: none;
+ transition: border-color 0.2s;
+}
+
+.dump-title-input:focus,
+.profile-email-input:focus,
+.playlist-edit-input:focus,
+.playlist-edit-textarea:focus {
+ border-color: var(--color-accent);
+}
+
.dump-title-input {
flex: 1;
min-width: 12rem;
padding: 0.4rem 0.6rem;
- border-radius: 8px;
- border: 2px solid var(--color-border);
- background-color: var(--color-bg);
- color: var(--color-text);
font-size: 1.25rem;
font-weight: 700;
- font-family: "Saira", sans-serif;
- outline: none;
}
.dump-title-error {
@@ -294,7 +316,7 @@
background-color: var(--color-bg);
color: var(--color-text);
font-size: 1rem;
- font-family: "Saira", sans-serif;
+ font-family: var(--font-body);
font-optical-sizing: auto;
font-weight: 400;
font-style: normal;
@@ -384,41 +406,12 @@
color: var(--color-text);
}
-/* ── New dump form ── */
-.dump-create-wrapper {
- width: 100%;
- max-width: 520px;
- display: flex;
- flex-direction: column;
- gap: 0.75rem;
- animation: page-enter 0.2s ease both;
-}
-
-.dump-create-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 1rem;
- padding: 0 0.25rem;
-}
-
.dump-create-title {
margin: 0;
font-size: 1.4rem;
font-weight: 700;
}
-.dump-create-form {
- width: 100%;
- display: flex;
- flex-direction: column;
- gap: 1.25rem;
- background: var(--color-surface);
- border-radius: 14px;
- border: 1.5px solid var(--color-border);
- padding: 1.5rem;
-}
-
/* ── Mode toggle — segmented control ── */
.visibility-toggle {
display: flex;
@@ -452,12 +445,6 @@
background: color-mix(in srgb, var(--color-accent) 12%, transparent);
}
-.file-input-info {
- margin: 0.25rem 0 0;
- font-size: 0.85rem;
- opacity: 0.7;
-}
-
/* ── FileDropZone ── */
.fdz-wrapper {
display: flex;
@@ -600,37 +587,6 @@
display: block;
}
-.local-preview-generic {
- display: flex;
- align-items: center;
- gap: 0.75rem;
- padding: 0.85rem 1rem;
- border-radius: 10px;
- background: var(--color-bg);
- border: 2px solid var(--color-border);
-}
-
-.local-preview-icon {
- font-size: 1.5rem;
- flex-shrink: 0;
-}
-
-.local-preview-name {
- font-weight: 600;
- font-size: 0.95rem;
- min-width: 0;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.local-preview-size {
- margin-left: auto;
- font-size: 0.8rem;
- opacity: 0.5;
- flex-shrink: 0;
-}
-
.preview-loading {
margin: 0;
font-size: 0.85rem;
@@ -693,44 +649,6 @@
background: var(--color-accent-hover);
}
-.file-preview-audio-btn {
- display: flex;
- align-items: center;
- gap: 0.75rem;
- padding: 1rem 1.25rem;
- border: 2px solid var(--color-border);
- border-radius: 10px;
- background: var(--color-bg);
- cursor: pointer;
- color: var(--color-text);
- width: 100%;
- font-size: 0.95rem;
- transition: border-color 0.15s;
-}
-
-.file-preview-audio-btn:hover {
- border-color: var(--color-accent);
-}
-
-.file-preview-audio-icon {
- font-size: 1.5rem;
- flex-shrink: 0;
-}
-
-.file-preview-audio-label {
- flex: 1;
- min-width: 0;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- text-align: left;
-}
-
-.file-preview-audio-play {
- flex-shrink: 0;
- opacity: 0.5;
-}
-
/* ── Video player ── */
.video-player {
width: 100%;
@@ -1030,6 +948,14 @@
background: var(--color-soundcloud);
}
+/* Brand badges sit on a saturated brand colour — use the fixed white
+ foreground rather than the accent-tuned `on-accent` of the generic badge. */
+.rich-content-card--youtube .rich-content-badge,
+.rich-content-card--bandcamp .rich-content-badge,
+.rich-content-card--soundcloud .rich-content-badge {
+ color: var(--color-on-brand);
+}
+
/* ── Global persistent player ── */
.global-player {
position: fixed;
@@ -1090,6 +1016,13 @@
min-height: 0;
border-radius: 0 0 8px 8px;
}
+
+/* Inside the global player the shell already draws the border — drop the
+ nested media player's own border so it isn't doubled up. */
+.global-player .audio-player,
+.global-player .video-player {
+ border: none;
+}
.global-player iframe {
width: 100%;
border: none;
@@ -1118,9 +1051,6 @@
body.has-player {
padding-bottom: var(--player-height, 0px);
}
-body.has-player .fab-new {
- bottom: calc(var(--player-height, 0px) + 1rem);
-}
.rich-content-thumbnail-btn {
position: relative;
padding: 0;
@@ -1179,10 +1109,12 @@ body.has-player .fab-new {
min-width: 0;
}
+/* Generic badge (unknown providers) — themed accent. Brand providers override
+ the background below and switch to the fixed white `on-brand` foreground. */
.rich-content-badge {
display: inline-block;
- background: var(--color-youtube);
- color: var(--color-text);
+ background: var(--color-accent);
+ color: var(--color-on-accent);
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.04em;
@@ -1200,15 +1132,25 @@ body.has-player .fab-new {
word-break: break-word;
}
+/* Multi-line truncation boilerplate. Each owner sets its own
+ `-webkit-line-clamp` line count; this group carries the rest. */
+.rich-content-description,
+.dump-card-comment,
+.playlist-card-description,
+.journal-card-title,
+.journal-card--image .journal-card-comment,
+.journal-card-quote-body {
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+
.rich-content-description {
margin: 0;
font-size: 0.85rem;
opacity: 0.75;
- display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
- -webkit-box-orient: vertical;
- overflow: hidden;
}
.rich-content-url {
@@ -1322,24 +1264,30 @@ body.has-player .fab-new {
cursor: default;
}
-.vote-count-clickable {
+/* Clickable counts. Padding + matching negative margin enlarge the click
+ target without shifting layout — the box grows, its visual position holds. */
+.vote-count-clickable,
+.like-count-clickable {
display: inline-block;
cursor: pointer;
- /* Padding+matching negative margin enlarges the click target without
- shifting layout — the box grows but its visual position is unchanged. */
- padding: 0.4rem;
- margin: -0.4rem;
}
-.vote-count-clickable:hover {
+.vote-count-clickable:hover,
+.like-count-clickable:hover {
text-decoration: underline;
}
+.vote-count-clickable {
+ padding: 0.4rem;
+ margin: -0.4rem;
+}
+
/* ── Dump OP line ── */
.dump-op {
display: flex;
align-items: center;
- gap: 0.5rem;
+ flex-wrap: wrap;
+ gap: 0.25rem 0.5rem;
font-size: 0.85rem;
opacity: 0.7;
}
@@ -1348,6 +1296,8 @@ body.has-player .fab-new {
font-weight: 600;
color: var(--color-text);
text-decoration: none;
+ min-width: 0;
+ overflow-wrap: anywhere;
}
.dump-op-link:hover {
@@ -1616,18 +1566,8 @@ body.has-player .fab-new {
width: min(260px, 100%);
padding: 0.35rem 0.6rem;
border-radius: 6px;
- border: 2px solid var(--color-border);
- background-color: var(--color-bg);
- color: var(--color-text);
font-size: 0.85rem;
- font-family: inherit;
line-height: 1.5;
- transition: border-color 0.2s, box-shadow 0.2s;
- outline: none;
-}
-
-.profile-email-input:focus {
- border-color: var(--color-accent);
}
.profile-email-actions {
@@ -1742,7 +1682,7 @@ body.has-player .fab-new {
}
.invite-url {
font-size: 0.75rem;
- font-family: monospace;
+ font-family: var(--font-mono, monospace);
color: var(--color-text-muted);
flex: 1;
overflow: hidden;
@@ -1807,9 +1747,7 @@ body.has-player .fab-new {
color: var(--color-accent);
}
-/* ── Border-tinted buttons ── */
-.btn-border-danger,
-.btn-border-success,
+/* ── Border-tinted button ── */
.btn-border {
display: inline-flex;
align-items: center;
@@ -1825,19 +1763,11 @@ body.has-player .fab-new {
transition: border-color 0.15s, color 0.15s, background 0.15s;
}
-.btn-border-danger:hover,
-.btn-border-danger.active,
.btn-border:hover {
border-color: var(--color-danger);
color: var(--color-danger);
}
-.btn-border-success:hover,
-.btn-border-success.active {
- border-color: var(--color-success);
- color: var(--color-success);
-}
-
/* ── ErrorCard ── */
.error-card-wrap {
display: flex;
@@ -1910,12 +1840,6 @@ body.has-player .fab-new {
width: 100%;
}
-.form-error {
- color: var(--color-danger);
- margin: 0;
- font-size: 0.9rem;
-}
-
/* ── Shared layout ── */
.page-shell {
display: flex;
@@ -1957,45 +1881,6 @@ body.has-player .fab-new {
border-bottom: 2px solid var(--color-border);
}
-/* ── Floating "+ New" button ── */
-.fab-new {
- position: fixed;
- bottom: 2rem;
- right: max(1.5rem, calc((100vw - 860px) / 2 + 1.5rem));
- z-index: 20;
- background: var(--color-accent);
- color: var(--color-on-accent);
- border: none;
- border-radius: 999px;
- padding: 0.6rem 1.4rem;
- font-size: 0.95rem;
- font-weight: 700;
- cursor: pointer;
- box-shadow: 0 4px 20px
- color-mix(in srgb, var(--color-accent) 50%, transparent);
- opacity: 0;
- pointer-events: none;
- transform: translateY(10px) scale(0.94);
- transition:
- opacity 0.2s ease,
- transform 0.2s ease,
- background 0.15s,
- box-shadow 0.15s;
-}
-
-.fab-new--visible {
- opacity: 1;
- pointer-events: auto;
- transform: none;
-}
-
-.fab-new:hover {
- background: var(--color-accent-hover);
- box-shadow: 0 6px 24px
- color-mix(in srgb, var(--color-accent) 60%, transparent);
- transform: translateY(-1px);
-}
-
@media (min-width: 860px) {
.app-header--has-center {
display: grid;
@@ -2066,22 +1951,6 @@ body.has-player .fab-new {
display: none;
}
-.app-header-center:has(.search-bar--expanded) .header-center-slot {
- flex: 1;
- justify-content: center;
-}
-
-/* Expanded: pin center absolutely so it aligns with the feed's visual center */
-@media (min-width: 860px) {
- .app-header-center:has(.search-bar--expanded) {
- position: absolute;
- left: 50%;
- transform: translateX(-50%);
- width: 420px;
- pointer-events: auto;
- }
-}
-
/* As the center column shrinks (viewport narrow, search collapsed),
shed content in order: presence first, then tabs (still in .index-below-header) */
@container (max-width: 460px) {
@@ -2116,18 +1985,38 @@ body.has-player .fab-new {
flex-wrap: nowrap;
}
-/* Normalize all buttons in the header nav to the same height */
-.app-header-nav button {
- padding: 0.35rem 0.85rem;
+/* Normalize every header-nav control to one height (search / notifications /
+ avatar / + new were all different sizes) and centre their contents — flex +
+ line-height:1 keeps text and emoji glyphs vertically centred in the box. */
+.app-header-nav button,
+.app-header-user {
+ height: 2.25rem;
+ box-sizing: border-box;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
font-size: 0.95rem;
font-family: inherit;
- line-height: inherit;
+ line-height: 1;
+}
+
+.app-header-nav button {
+ padding: 0.35rem 0.85rem;
}
.app-header-nav .btn-primary {
padding: 0.35rem 1rem;
}
+/* Icon-only controls are square, so the row reads as a tidy set. */
+.app-header-nav .nav-search-btn,
+.app-header-nav .notification-bell,
+.app-header-nav .user-menu-trigger {
+ width: 2.25rem;
+ padding: 0;
+ justify-content: center;
+}
+
/* Ghost search button — always visible except when the center search bar is shown */
.nav-search-btn {
display: flex;
@@ -2548,6 +2437,26 @@ body.has-player .fab-new {
background: var(--color-danger-hover);
}
+/* ── Ghost button — chromeless icon/toggle action (e.g. global player) ── */
+.btn--ghost {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ background: none;
+ border: none;
+ padding: 0.3rem 0.55rem;
+ border-radius: 6px;
+ color: var(--color-text);
+ font-family: inherit;
+ opacity: 0.7;
+ transition: opacity 0.15s, background 0.15s;
+}
+
+.btn--ghost:hover {
+ opacity: 1;
+ background: color-mix(in srgb, var(--color-text) 8%, transparent);
+}
+
/* ── Ghost icon buttons (shared base) ── */
.modal-close-btn,
.playlist-remove-btn,
@@ -2937,9 +2846,6 @@ body.has-player .fab-new {
opacity: 0.65;
word-break: break-word;
line-height: 1.4;
- display: -webkit-box;
- -webkit-box-orient: vertical;
- overflow: hidden;
}
.dump-card-comment {
@@ -3043,7 +2949,8 @@ body.has-player .fab-new {
.playlist-card-count {
opacity: 0.7;
}
-.playlist-card-owner {
+.playlist-card-owner,
+.playlist-detail-owner {
color: inherit;
text-decoration: none;
font-weight: 600;
@@ -3300,11 +3207,6 @@ body.has-player .fab-new {
gap: 0.1rem;
}
}
-.playlist-detail-owner {
- color: inherit;
- text-decoration: none;
- font-weight: 600;
-}
.playlist-detail-owner:hover {
color: var(--color-accent);
opacity: 1;
@@ -3330,7 +3232,8 @@ body.has-player .fab-new {
transition: opacity 0.15s, border-color 0.15s, color 0.15s;
}
-.playlist-edit-btn:hover {
+.playlist-edit-btn:hover,
+.playlist-cancel-btn:hover {
opacity: 1;
border-color: var(--color-accent);
color: var(--color-accent);
@@ -3338,17 +3241,9 @@ body.has-player .fab-new {
.playlist-edit-input,
.playlist-edit-textarea {
- background: var(--color-bg);
- border: 2px solid var(--color-border);
- border-radius: 8px;
- color: var(--color-text);
+ width: 100%;
font-size: 0.95rem;
padding: 0.4rem 0.65rem;
- font-family: inherit;
- width: 100%;
- box-sizing: border-box;
- outline: none;
- transition: border-color 0.2s;
}
.playlist-edit-textarea {
@@ -3368,11 +3263,6 @@ body.has-player .fab-new {
gap: 0.7rem;
}
-.playlist-edit-input:focus,
-.playlist-edit-textarea:focus {
- border-color: var(--color-accent);
-}
-
.playlist-detail-meta .playlist-edit-toggle {
opacity: 1;
}
@@ -3431,12 +3321,6 @@ body.has-player .fab-new {
white-space: nowrap;
}
-.playlist-cancel-btn:hover {
- opacity: 1;
- border-color: var(--color-accent);
- color: var(--color-accent);
-}
-
.drag-handle {
cursor: grab;
opacity: 0.35;
@@ -3472,20 +3356,6 @@ body.has-player .fab-new {
color: var(--color-accent);
}
-/* ── My Playlists page ── */
-.my-playlists-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 1rem;
-}
-
-.my-playlists-title {
- margin: 0;
- font-size: 1.5rem;
- font-weight: 700;
-}
-
/* ── New playlist toggle (profile page header) ── */
.new-playlist-toggle {
background: none;
@@ -3520,28 +3390,15 @@ body.has-player .fab-new {
color: var(--color-text-muted);
}
-/* ── Comments ── */
-.comment-section {
- margin-top: 2.5rem;
- padding-top: 1.75rem;
- border-top: 2px solid var(--color-border);
-}
-
-.comment-section-title {
- font-size: 0.8rem;
- font-weight: 700;
- color: var(--color-text-muted);
- margin: 0 0 1.25rem;
- text-transform: uppercase;
- letter-spacing: 0.08em;
-}
-
+/* ── Comments / related (shared section chrome) ── */
+.comment-section,
.related-section {
margin-top: 2.5rem;
padding-top: 1.75rem;
border-top: 2px solid var(--color-border);
}
+.comment-section-title,
.related-section-title {
font-size: 0.8rem;
font-weight: 700;
@@ -3672,12 +3529,7 @@ body.has-player .fab-new {
background: color-mix(in srgb, var(--color-text) 6%, transparent);
}
-.comment-delete-btn:hover {
- color: var(--color-danger);
- border-color: color-mix(in srgb, var(--color-danger) 40%, transparent);
- background: color-mix(in srgb, var(--color-danger) 10%, transparent);
-}
-
+.comment-delete-btn:hover,
.comment-action-btn--liked {
color: var(--color-danger);
border-color: color-mix(in srgb, var(--color-danger) 40%, transparent);
@@ -3691,18 +3543,10 @@ body.has-player .fab-new {
}
.like-count-clickable {
- display: inline-block;
- cursor: pointer;
- /* Padding+matching negative margin enlarges the click target without
- shifting layout — the box grows but its visual position is unchanged. */
padding: 0.3rem;
margin: -0.3rem;
}
-.like-count-clickable:hover {
- text-decoration: underline;
-}
-
.comment-replies {
padding-left: max(0.4rem, calc(1.25rem - var(--depth, 0) * 0.1rem));
margin-left: max(0.25rem, calc(1.1rem - var(--depth, 0) * 0.09rem));
@@ -3954,7 +3798,8 @@ body.has-player .fab-new {
transition: background 0.15s, color 0.15s;
}
-.follow-btn:hover {
+.follow-btn:hover,
+.follow-btn--following {
background: var(--color-accent);
color: var(--color-on-accent, #fff);
}
@@ -3964,11 +3809,6 @@ body.has-player .fab-new {
cursor: not-allowed;
}
-.follow-btn--following {
- background: var(--color-accent);
- color: var(--color-on-accent, #fff);
-}
-
.follow-btn--following:hover {
background: transparent;
color: var(--color-accent);
@@ -4056,11 +3896,8 @@ body.has-player .fab-new {
text-decoration: none;
line-height: 1.3;
word-break: break-word;
- display: -webkit-box;
- -webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
- overflow: hidden;
}
.journal-card-title:hover {
@@ -4143,11 +3980,8 @@ body.has-player .fab-new {
line-height: 1.4;
margin: 0;
color: rgba(255, 255, 255, 0.85);
- display: -webkit-box;
- -webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
- overflow: hidden;
}
.journal-card--feature .journal-card-title {
@@ -4171,7 +4005,7 @@ body.has-player .fab-new {
position: absolute;
top: -0.5rem;
right: 0.4rem;
- font-family: Georgia, "Times New Roman", serif;
+ font-family: var(--font-display, Georgia, "Times New Roman", serif);
font-size: 4.5rem;
line-height: 1;
color: var(--color-accent);
@@ -4186,11 +4020,8 @@ body.has-player .fab-new {
font-size: 0.92rem;
line-height: 1.45;
color: var(--color-text);
- display: -webkit-box;
- -webkit-box-orient: vertical;
-webkit-line-clamp: 3;
line-clamp: 3;
- overflow: hidden;
}
.journal-card--wide.journal-card--quote .journal-card-quote-body,
@@ -4706,7 +4537,7 @@ body.has-player .fab-new {
.notif-icon--mention {
font-weight: 700;
- font-family: monospace;
+ font-family: var(--font-mono, monospace);
}
/* ── Tooltip ── */
diff --git a/src/contexts/ThemeContext.ts b/src/contexts/ThemeContext.ts
index c2465aa..c6d9cdf 100644
--- a/src/contexts/ThemeContext.ts
+++ b/src/contexts/ThemeContext.ts
@@ -1,6 +1,6 @@
import { createContext } from "react";
-export type StyleName = "smooth" | "brutalist";
+export type StyleName = "smooth" | "brutalist" | "geocities" | "nyt";
export type ColorScheme = "auto" | "light" | "dark";
export interface ThemeContextValue {
diff --git a/src/locales/en.po b/src/locales/en.po
index 5816c5b..e39887a 100644
--- a/src/locales/en.po
+++ b/src/locales/en.po
@@ -157,7 +157,7 @@ msgstr "Appearance"
msgid "At least {0} characters"
msgstr "At least {0} characters"
-#: src/pages/UserPublicProfile.tsx:1146
+#: src/pages/UserPublicProfile.tsx:1160
msgid "Auto"
msgstr "Auto"
@@ -177,8 +177,8 @@ msgstr "Can't connect to the live updates server. Upvotes and notifications may
#: src/pages/Dump.tsx:357
#: src/pages/DumpEdit.tsx:433
#: src/pages/PlaylistDetail.tsx:908
-#: src/pages/UserPublicProfile.tsx:1482
-#: src/pages/UserPublicProfile.tsx:1552
+#: src/pages/UserPublicProfile.tsx:1496
+#: src/pages/UserPublicProfile.tsx:1566
msgid "Cancel"
msgstr "Cancel"
@@ -212,7 +212,7 @@ msgstr "Checking invite…"
msgid "Close"
msgstr "Close"
-#: src/pages/UserPublicProfile.tsx:1138
+#: src/pages/UserPublicProfile.tsx:1152
msgid "Color scheme"
msgstr "Color scheme"
@@ -263,7 +263,7 @@ msgstr "Creating…"
msgid "Current password"
msgstr "Current password"
-#: src/pages/UserPublicProfile.tsx:1160
+#: src/pages/UserPublicProfile.tsx:1174
msgid "Dark"
msgstr "Dark"
@@ -417,8 +417,8 @@ msgid "Failed to post reply"
msgstr "Failed to post reply"
#: src/pages/PlaylistDetail.tsx:896
-#: src/pages/UserPublicProfile.tsx:1485
-#: src/pages/UserPublicProfile.tsx:1554
+#: src/pages/UserPublicProfile.tsx:1499
+#: src/pages/UserPublicProfile.tsx:1568
msgid "Failed to save"
msgstr "Failed to save"
@@ -538,7 +538,7 @@ msgstr "Invitees"
msgid "Journal"
msgstr "Journal"
-#: src/pages/UserPublicProfile.tsx:1153
+#: src/pages/UserPublicProfile.tsx:1167
msgid "Light"
msgstr "Light"
@@ -644,7 +644,7 @@ msgstr "New"
#: src/components/DumpCreateModal.tsx:292
#: src/pages/UserDumps.tsx:115
-#: src/pages/UserPublicProfile.tsx:1209
+#: src/pages/UserPublicProfile.tsx:1223
msgid "New dump"
msgstr "New dump"
@@ -709,8 +709,8 @@ msgstr "Not following anyone yet."
#: src/pages/Notifications.tsx:374
#: src/pages/UserDumps.tsx:125
-#: src/pages/UserPublicProfile.tsx:1220
-#: src/pages/UserPublicProfile.tsx:1342
+#: src/pages/UserPublicProfile.tsx:1234
+#: src/pages/UserPublicProfile.tsx:1356
#: src/pages/UserUpvoted.tsx:194
msgid "Nothing here yet."
msgstr "Nothing here yet."
@@ -885,8 +885,8 @@ msgstr "Retry"
#: src/pages/Dump.tsx:349
#: src/pages/DumpEdit.tsx:436
#: src/pages/PlaylistDetail.tsx:915
-#: src/pages/UserPublicProfile.tsx:1474
-#: src/pages/UserPublicProfile.tsx:1544
+#: src/pages/UserPublicProfile.tsx:1488
+#: src/pages/UserPublicProfile.tsx:1558
msgid "Save"
msgstr "Save"
@@ -895,8 +895,8 @@ msgstr "Save"
#: src/pages/Dump.tsx:348
#: src/pages/PlaylistDetail.tsx:911
#: src/pages/ResetPassword.tsx:124
-#: src/pages/UserPublicProfile.tsx:1471
-#: src/pages/UserPublicProfile.tsx:1541
+#: src/pages/UserPublicProfile.tsx:1485
+#: src/pages/UserPublicProfile.tsx:1555
msgid "Saving…"
msgstr "Saving…"
@@ -1044,8 +1044,8 @@ msgstr "Users"
#: src/pages/UserPublicProfile.tsx:963
#: src/pages/UserPublicProfile.tsx:1006
#: src/pages/UserPublicProfile.tsx:1048
-#: src/pages/UserPublicProfile.tsx:1241
-#: src/pages/UserPublicProfile.tsx:1372
+#: src/pages/UserPublicProfile.tsx:1255
+#: src/pages/UserPublicProfile.tsx:1386
msgid "View all →"
msgstr "View all →"
@@ -1059,7 +1059,7 @@ msgid "What makes it worth it?"
msgstr "What makes it worth it?"
#: src/pages/UserPublicProfile.tsx:850
-#: src/pages/UserPublicProfile.tsx:1533
+#: src/pages/UserPublicProfile.tsx:1547
msgid "Who am I?"
msgstr "Who am I?"
diff --git a/src/locales/fr.po b/src/locales/fr.po
index 75b5f81..db1a1e1 100644
--- a/src/locales/fr.po
+++ b/src/locales/fr.po
@@ -157,7 +157,7 @@ msgstr "Apparence"
msgid "At least {0} characters"
msgstr "Au moins {0} caractères"
-#: src/pages/UserPublicProfile.tsx:1146
+#: src/pages/UserPublicProfile.tsx:1160
msgid "Auto"
msgstr "Auto"
@@ -177,8 +177,8 @@ msgstr "Impossible de se connecter au serveur de mises à jour en direct. Les vo
#: src/pages/Dump.tsx:357
#: src/pages/DumpEdit.tsx:433
#: src/pages/PlaylistDetail.tsx:908
-#: src/pages/UserPublicProfile.tsx:1482
-#: src/pages/UserPublicProfile.tsx:1552
+#: src/pages/UserPublicProfile.tsx:1496
+#: src/pages/UserPublicProfile.tsx:1566
msgid "Cancel"
msgstr "Annuler"
@@ -212,7 +212,7 @@ msgstr "Vérification de l'invitation…"
msgid "Close"
msgstr "Fermer"
-#: src/pages/UserPublicProfile.tsx:1138
+#: src/pages/UserPublicProfile.tsx:1152
msgid "Color scheme"
msgstr "Thème de couleur"
@@ -263,7 +263,7 @@ msgstr "Création…"
msgid "Current password"
msgstr "Mot de passe actuel"
-#: src/pages/UserPublicProfile.tsx:1160
+#: src/pages/UserPublicProfile.tsx:1174
msgid "Dark"
msgstr "Sombre"
@@ -417,8 +417,8 @@ msgid "Failed to post reply"
msgstr "Impossible de publier la réponse"
#: src/pages/PlaylistDetail.tsx:896
-#: src/pages/UserPublicProfile.tsx:1485
-#: src/pages/UserPublicProfile.tsx:1554
+#: src/pages/UserPublicProfile.tsx:1499
+#: src/pages/UserPublicProfile.tsx:1568
msgid "Failed to save"
msgstr "Enregistrement échoué"
@@ -538,7 +538,7 @@ msgstr "Invités"
msgid "Journal"
msgstr "Journal"
-#: src/pages/UserPublicProfile.tsx:1153
+#: src/pages/UserPublicProfile.tsx:1167
msgid "Light"
msgstr "Clair"
@@ -644,7 +644,7 @@ msgstr "Nouveau"
#: src/components/DumpCreateModal.tsx:292
#: src/pages/UserDumps.tsx:115
-#: src/pages/UserPublicProfile.tsx:1209
+#: src/pages/UserPublicProfile.tsx:1223
msgid "New dump"
msgstr "Nouvelle reco"
@@ -709,8 +709,8 @@ msgstr "Aucun abonnement pour le moment."
#: src/pages/Notifications.tsx:374
#: src/pages/UserDumps.tsx:125
-#: src/pages/UserPublicProfile.tsx:1220
-#: src/pages/UserPublicProfile.tsx:1342
+#: src/pages/UserPublicProfile.tsx:1234
+#: src/pages/UserPublicProfile.tsx:1356
#: src/pages/UserUpvoted.tsx:194
msgid "Nothing here yet."
msgstr "Rien ici pour l'instant."
@@ -885,8 +885,8 @@ msgstr "Réessayer"
#: src/pages/Dump.tsx:349
#: src/pages/DumpEdit.tsx:436
#: src/pages/PlaylistDetail.tsx:915
-#: src/pages/UserPublicProfile.tsx:1474
-#: src/pages/UserPublicProfile.tsx:1544
+#: src/pages/UserPublicProfile.tsx:1488
+#: src/pages/UserPublicProfile.tsx:1558
msgid "Save"
msgstr "Enregistrer"
@@ -895,8 +895,8 @@ msgstr "Enregistrer"
#: src/pages/Dump.tsx:348
#: src/pages/PlaylistDetail.tsx:911
#: src/pages/ResetPassword.tsx:124
-#: src/pages/UserPublicProfile.tsx:1471
-#: src/pages/UserPublicProfile.tsx:1541
+#: src/pages/UserPublicProfile.tsx:1485
+#: src/pages/UserPublicProfile.tsx:1555
msgid "Saving…"
msgstr "Enregistrement…"
@@ -1044,8 +1044,8 @@ msgstr "Utilisateurs"
#: src/pages/UserPublicProfile.tsx:963
#: src/pages/UserPublicProfile.tsx:1006
#: src/pages/UserPublicProfile.tsx:1048
-#: src/pages/UserPublicProfile.tsx:1241
-#: src/pages/UserPublicProfile.tsx:1372
+#: src/pages/UserPublicProfile.tsx:1255
+#: src/pages/UserPublicProfile.tsx:1386
msgid "View all →"
msgstr "Tout voir →"
@@ -1059,7 +1059,7 @@ msgid "What makes it worth it?"
msgstr "Pourquoi on en voudrait ?"
#: src/pages/UserPublicProfile.tsx:850
-#: src/pages/UserPublicProfile.tsx:1533
+#: src/pages/UserPublicProfile.tsx:1547
msgid "Who am I?"
msgstr "Qui suis-je ?"
diff --git a/src/main.tsx b/src/main.tsx
index 6befde4..a89e130 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -8,6 +8,8 @@ import { i18n, loadCatalog } from "./i18n.ts";
import "./index.css";
import "./themes/smooth.css";
import "./themes/brutalist.css";
+import "./themes/geocities.css";
+import "./themes/nyt.css";
await loadCatalog();
diff --git a/src/pages/UserPublicProfile.tsx b/src/pages/UserPublicProfile.tsx
index f458ac1..3bbe6d6 100644
--- a/src/pages/UserPublicProfile.tsx
+++ b/src/pages/UserPublicProfile.tsx
@@ -1131,6 +1131,20 @@ export function UserPublicProfile() {
>
Brutalist
+
+
diff --git a/src/themes/brutalist.css b/src/themes/brutalist.css
index db3feed..46da2f6 100644
--- a/src/themes/brutalist.css
+++ b/src/themes/brutalist.css
@@ -137,7 +137,8 @@
/* ── Typography ──────────────────────────────────────────────────── */
[data-style="brutalist"] {
- font-family: "Space Grotesk", "Saira", sans-serif;
+ --font-body: "Space Grotesk", "Saira", sans-serif;
+ font-family: var(--font-body);
font-weight: 500;
}
@@ -347,11 +348,19 @@
box-shadow: 3px 3px 0 var(--color-border);
}
-/* Neutralise provider brand border-colors so they don't fight the hard shadow */
-[data-style="brutalist"] .rich-content-card--youtube,
-[data-style="brutalist"] .rich-content-card--bandcamp,
+/* Carry the provider brand colour through both the border and the hard
+ offset shadow, so the brand reads instead of being flattened to grey. */
+[data-style="brutalist"] .rich-content-card--youtube {
+ border-color: var(--color-youtube);
+ box-shadow: 3px 3px 0 var(--color-youtube);
+}
+[data-style="brutalist"] .rich-content-card--bandcamp {
+ border-color: var(--color-bandcamp);
+ box-shadow: 3px 3px 0 var(--color-bandcamp);
+}
[data-style="brutalist"] .rich-content-card--soundcloud {
- border-color: var(--color-border);
+ border-color: var(--color-soundcloud);
+ box-shadow: 3px 3px 0 var(--color-soundcloud);
}
[data-style="brutalist"]
@@ -470,16 +479,12 @@
}
/* ── btn-border ──────────────────────────────────────────────────── */
-[data-style="brutalist"] .btn-border,
-[data-style="brutalist"] .btn-border-danger,
-[data-style="brutalist"] .btn-border-success {
+[data-style="brutalist"] .btn-border {
border-radius: 0;
box-shadow: 2px 2px 0 var(--color-border);
}
-[data-style="brutalist"] .btn-border:hover,
-[data-style="brutalist"] .btn-border-danger:hover,
-[data-style="brutalist"] .btn-border-success:hover {
+[data-style="brutalist"] .btn-border:hover {
box-shadow: none;
transform: translate(2px, 2px);
}
diff --git a/src/themes/geocities.css b/src/themes/geocities.css
new file mode 100644
index 0000000..e11093e
--- /dev/null
+++ b/src/themes/geocities.css
@@ -0,0 +1,504 @@
+/* ══════════════════════════════════════════════════════════════════════
+ GeoCities / NeoCities theme — turn-of-the-millennium personal-homepage
+ maximalism: tiled starfield wallpaper, Comic Sans, ridged 3D boxes,
+ clashing neon accents, rainbow dividers, blinking badges.
+
+ Dark scheme → NeoCities "neon on black" revival.
+ Light scheme → classic GeoCities pastel-on-wallpaper.
+ ══════════════════════════════════════════════════════════════════════ */
+
+/* ── Dark variant (default for this theme) ────────────────────────── */
+:root[data-style="geocities"] {
+ --color-text: #f2f0ff;
+ --color-text-secondary: #7fdbff; /* cyan */
+ --color-text-muted: #a99ad6; /* faded violet */
+ --color-on-accent: #000000; /* black on hot magenta */
+
+ --color-bg: #0a0a2e; /* deep indigo night */
+ --color-surface: #1b1b46; /* raised panel */
+
+ --color-border: #00ffff; /* neon cyan */
+ --color-border-subtle: rgba(0, 255, 255, 0.28);
+
+ --color-accent: #ff00ff; /* hot magenta */
+ --color-accent-hover: #ff5cff;
+
+ --color-link: #00ffff; /* classic bright link */
+ --color-link-hover: #ffff00; /* yellow on hover, very 1999 */
+
+ --color-danger: #ff3860;
+ --color-danger-bg: #3a0a1a;
+ --color-danger-hover: #ff6b8a;
+
+ --color-success: #39ff14; /* neon green */
+ --color-success-bg: #0a2e0a;
+ --color-success-hover: #6fff52;
+
+ --color-overlay: rgba(8, 0, 28, 0.72);
+ --color-header-user-bg: rgba(0, 255, 255, 0.1);
+ --color-header-user-bg-hover: rgba(0, 255, 255, 0.2);
+
+ --color-option-bg: #14143a;
+ --color-option-border: #00ffff;
+
+ /* Theme-local helpers */
+ --geo-rainbow: linear-gradient(
+ 90deg,
+ #ff004d,
+ #ff8a00,
+ #ffee00,
+ #00d11a,
+ #00b3ff,
+ #b300ff
+ );
+ /* Starfield: white pinpricks plus the odd coloured star */
+ --geo-pattern:
+ radial-gradient(1px 1px at 20px 30px, #ffffff, transparent),
+ radial-gradient(1px 1px at 130px 80px, #ffffff, transparent),
+ radial-gradient(1.5px 1.5px at 80px 150px, #00ffff, transparent),
+ radial-gradient(1.5px 1.5px at 170px 40px, #ff00ff, transparent),
+ radial-gradient(1px 1px at 50px 190px, #ffee00, transparent);
+ --geo-pattern-size: 200px 200px;
+}
+
+/* ── Light via system preference (auto color-scheme) ──────────────── */
+@media (prefers-color-scheme: light) {
+ :root[data-style="geocities"]:not([data-color-scheme]) {
+ --color-text: #2a1a4e;
+ --color-text-secondary: #aa00aa;
+ --color-text-muted: #7a6a9a;
+ --color-on-accent: #ffffff;
+
+ --color-bg: #fff0f8; /* pale lavender wallpaper */
+ --color-surface: #ffffff;
+
+ --color-border: #d000d0;
+ --color-border-subtle: rgba(208, 0, 208, 0.25);
+
+ --color-accent: #d000d0;
+ --color-accent-hover: #a000a0;
+
+ --color-link: #0000ee; /* unvisited blue */
+ --color-link-hover: #551a8b; /* visited purple */
+
+ --color-danger: #cc0033;
+ --color-danger-bg: #ffe0e8;
+ --color-danger-hover: #990022;
+
+ --color-success: #008800;
+ --color-success-bg: #e0ffe0;
+ --color-success-hover: #006600;
+
+ --color-overlay: rgba(40, 0, 40, 0.4);
+ --color-header-user-bg: rgba(208, 0, 208, 0.08);
+ --color-header-user-bg-hover: rgba(208, 0, 208, 0.16);
+
+ --color-option-bg: #fff0fb;
+ --color-option-border: #d000d0;
+
+ --geo-rainbow: linear-gradient(
+ 90deg,
+ #ff004d,
+ #ff8a00,
+ #ffcc00,
+ #00aa1a,
+ #0077dd,
+ #8800cc
+ );
+ --geo-pattern:
+ radial-gradient(rgba(208, 0, 208, 0.16) 1.5px, transparent 1.5px),
+ radial-gradient(rgba(0, 179, 255, 0.14) 1.5px, transparent 1.5px);
+ --geo-pattern-size: 22px 22px, 22px 22px;
+ }
+}
+
+/* ── Manual dark override ─────────────────────────────────────────── */
+:root[data-style="geocities"][data-color-scheme="dark"] {
+ color-scheme: dark;
+ --color-text: #f2f0ff;
+ --color-text-secondary: #7fdbff;
+ --color-text-muted: #a99ad6;
+ --color-on-accent: #000000;
+ --color-bg: #0a0a2e;
+ --color-surface: #1b1b46;
+ --color-border: #00ffff;
+ --color-border-subtle: rgba(0, 255, 255, 0.28);
+ --color-accent: #ff00ff;
+ --color-accent-hover: #ff5cff;
+ --color-link: #00ffff;
+ --color-link-hover: #ffff00;
+ --color-danger: #ff3860;
+ --color-danger-bg: #3a0a1a;
+ --color-danger-hover: #ff6b8a;
+ --color-success: #39ff14;
+ --color-success-bg: #0a2e0a;
+ --color-success-hover: #6fff52;
+ --color-overlay: rgba(8, 0, 28, 0.72);
+ --color-header-user-bg: rgba(0, 255, 255, 0.1);
+ --color-header-user-bg-hover: rgba(0, 255, 255, 0.2);
+ --color-option-bg: #14143a;
+ --color-option-border: #00ffff;
+ --geo-rainbow: linear-gradient(
+ 90deg,
+ #ff004d,
+ #ff8a00,
+ #ffee00,
+ #00d11a,
+ #00b3ff,
+ #b300ff
+ );
+ --geo-pattern:
+ radial-gradient(1px 1px at 20px 30px, #ffffff, transparent),
+ radial-gradient(1px 1px at 130px 80px, #ffffff, transparent),
+ radial-gradient(1.5px 1.5px at 80px 150px, #00ffff, transparent),
+ radial-gradient(1.5px 1.5px at 170px 40px, #ff00ff, transparent),
+ radial-gradient(1px 1px at 50px 190px, #ffee00, transparent);
+ --geo-pattern-size: 200px 200px;
+}
+
+/* ── Manual light override ────────────────────────────────────────── */
+:root[data-style="geocities"][data-color-scheme="light"] {
+ color-scheme: light;
+ --color-text: #2a1a4e;
+ --color-text-secondary: #aa00aa;
+ --color-text-muted: #7a6a9a;
+ --color-on-accent: #ffffff;
+ --color-bg: #fff0f8;
+ --color-surface: #ffffff;
+ --color-border: #d000d0;
+ --color-border-subtle: rgba(208, 0, 208, 0.25);
+ --color-accent: #d000d0;
+ --color-accent-hover: #a000a0;
+ --color-link: #0000ee;
+ --color-link-hover: #551a8b;
+ --color-danger: #cc0033;
+ --color-danger-bg: #ffe0e8;
+ --color-danger-hover: #990022;
+ --color-success: #008800;
+ --color-success-bg: #e0ffe0;
+ --color-success-hover: #006600;
+ --color-overlay: rgba(40, 0, 40, 0.4);
+ --color-header-user-bg: rgba(208, 0, 208, 0.08);
+ --color-header-user-bg-hover: rgba(208, 0, 208, 0.16);
+ --color-option-bg: #fff0fb;
+ --color-option-border: #d000d0;
+ --geo-rainbow: linear-gradient(
+ 90deg,
+ #ff004d,
+ #ff8a00,
+ #ffcc00,
+ #00aa1a,
+ #0077dd,
+ #8800cc
+ );
+ --geo-pattern:
+ radial-gradient(rgba(208, 0, 208, 0.16) 1.5px, transparent 1.5px),
+ radial-gradient(rgba(0, 179, 255, 0.14) 1.5px, transparent 1.5px);
+ --geo-pattern-size: 22px 22px, 22px 22px;
+}
+
+/* ══════════════════════════════════════════════════════════════════════
+ Structural overrides
+ ══════════════════════════════════════════════════════════════════════ */
+
+/* ── Typography — the one true homepage font ──────────────────────── */
+[data-style="geocities"] {
+ --font-body: "Comic Sans MS", "Comic Sans", "Chalkboard SE", "Comic Neue",
+ "Marker Felt", cursive;
+ font-family: var(--font-body);
+ font-weight: 400;
+}
+
+/* ── Tiled wallpaper, fixed so content scrolls over it ────────────── */
+[data-style="geocities"] body {
+ background-color: var(--color-bg);
+ background-image: var(--geo-pattern);
+ background-size: var(--geo-pattern-size);
+ background-attachment: fixed;
+}
+
+/* ── Loud headings with a soft glow ──────────────────────────────── */
+[data-style="geocities"] h1,
+[data-style="geocities"] .app-header-brand {
+ font-weight: 700;
+ letter-spacing: 0.02em;
+ text-shadow: 0 0 6px color-mix(in srgb, var(--color-accent) 60%, transparent);
+}
+
+/* ── Links — always underlined, bold ─────────────────────────────── */
+[data-style="geocities"] a,
+[data-style="geocities"] .md a {
+ text-decoration: underline;
+ font-weight: 700;
+}
+
+/* …except navigation chrome and avatar links, which aren't prose. */
+[data-style="geocities"] a:has(.avatar-img),
+[data-style="geocities"] a:has(.avatar-initials),
+[data-style="geocities"] .index-presence-avatar,
+[data-style="geocities"] .app-header-brand,
+[data-style="geocities"] .app-header-user,
+[data-style="geocities"] .nav-search-btn,
+[data-style="geocities"] .user-menu-item,
+[data-style="geocities"] .user-list-popover-item {
+ text-decoration: none;
+}
+
+/* ── Blink + marquee-ish keyframes ───────────────────────────────── */
+@keyframes geo-blink {
+ 50% {
+ opacity: 0;
+ }
+}
+
+/* ── Buttons — chunky Win95 outset bevel, press = inset ──────────── */
+[data-style="geocities"] button {
+ border-radius: 0;
+ border: 3px outset var(--color-border);
+ box-shadow: none;
+ font-weight: 700;
+ transition: border-color 0.1s, background 0.12s;
+}
+
+[data-style="geocities"] button:active:not(:disabled) {
+ border-style: inset;
+}
+
+[data-style="geocities"] .btn-primary,
+[data-style="geocities"] .btn-secondary,
+[data-style="geocities"] .btn-danger,
+[data-style="geocities"] .btn-border,
+[data-style="geocities"] .follow-btn,
+[data-style="geocities"] .vote-btn,
+[data-style="geocities"] .feed-sort-btn,
+[data-style="geocities"] .invite-btn,
+[data-style="geocities"] .btn-add-playlist,
+[data-style="geocities"] .load-more-btn,
+[data-style="geocities"] .comment-submit-btn,
+[data-style="geocities"] .comment-cancel-btn,
+[data-style="geocities"] .search-bar-btn {
+ border-radius: 0;
+ border: 3px outset var(--color-border);
+ box-shadow: none;
+}
+
+/* Ghost / icon-only buttons stay flat */
+[data-style="geocities"] .modal-close-btn,
+[data-style="geocities"] .playlist-remove-btn,
+[data-style="geocities"] .playlist-card-delete-btn,
+[data-style="geocities"] .emoji-picker-float [frimousse-emoji],
+[data-style="geocities"] .emoji-picker-close-btn,
+[data-style="geocities"] .new-playlist-toggle,
+[data-style="geocities"] .btn--ghost,
+[data-style="geocities"] .audio-player-btn,
+[data-style="geocities"] .audio-player-vol-btn,
+[data-style="geocities"] .rich-content-thumbnail-btn,
+[data-style="geocities"] .user-menu-trigger,
+[data-style="geocities"] .nav-search-btn,
+[data-style="geocities"] .auth-link-btn,
+[data-style="geocities"] .form-cancel,
+[data-style="geocities"] .form-label-action {
+ border: none;
+ box-shadow: none;
+}
+
+/* ── Segmented controls ──────────────────────────────────────────── */
+[data-style="geocities"] .visibility-toggle {
+ border-radius: 0;
+ border: 3px ridge var(--color-border);
+}
+
+[data-style="geocities"] .visibility-toggle button {
+ border-radius: 0;
+ border: none;
+}
+
+/* ── Cards & panels — ridged "table border" with a soft neon halo ─── */
+[data-style="geocities"] .dump-card,
+[data-style="geocities"] .playlist-card,
+[data-style="geocities"] .dump-post-header,
+[data-style="geocities"] .auth-card,
+[data-style="geocities"] .notification-item,
+[data-style="geocities"] .user-result-item,
+[data-style="geocities"] .journal-card {
+ border-radius: 0;
+ border: 3px ridge var(--color-border);
+ box-shadow: 0 0 9px color-mix(in srgb, var(--color-border) 38%, transparent);
+}
+
+/* Comments nest deeply, so the full frame piled up. Keep the squared retro
+ edge but lighten it: thin flat border, just a whisper of the same halo. */
+[data-style="geocities"] .comment-node-inner,
+[data-style="geocities"] .comment-top-form {
+ border-radius: 0;
+ border: 2px solid var(--color-border-subtle);
+ box-shadow: 0 0 6px var(--color-border-subtle);
+}
+
+[data-style="geocities"] .dump-card:hover,
+[data-style="geocities"] .playlist-card:hover,
+[data-style="geocities"] .user-result-item:hover,
+[data-style="geocities"] .journal-card:hover {
+ border-color: var(--color-accent);
+ box-shadow: 0 0 16px color-mix(in srgb, var(--color-accent) 75%, transparent);
+}
+
+/* ── App header — rainbow divider ────────────────────────────────── */
+[data-style="geocities"] .app-header,
+[data-style="geocities"] .header-search-below {
+ border-bottom: 4px solid;
+ border-image: var(--geo-rainbow) 1;
+}
+
+[data-style="geocities"] .app-header-user {
+ border-radius: 0;
+ border: 3px outset var(--color-border);
+}
+
+/* ── Rainbow dividers — on each element's real edge ──────────────── */
+[data-style="geocities"] .profile-section > h2,
+[data-style="geocities"] .profile-section-header,
+[data-style="geocities"] .profile-tabs-scroller {
+ border-bottom: 3px solid;
+ border-image: var(--geo-rainbow) 1;
+}
+
+[data-style="geocities"] .comment-section,
+[data-style="geocities"] .related-section {
+ border-top: 3px solid;
+ border-image: var(--geo-rainbow) 1;
+}
+
+[data-style="geocities"] .comment-section-title,
+[data-style="geocities"] .related-section-title {
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+}
+
+/* ── Inputs / textareas — sunken inset field ─────────────────────── */
+[data-style="geocities"]
+ input:not([type="range"]):not([type="file"]):not([type="checkbox"]):not(
+ [type="radio"]
+ ),
+[data-style="geocities"] textarea {
+ border-radius: 0 !important;
+ border: 2px inset var(--color-border) !important;
+ background-color: var(--color-bg);
+}
+
+[data-style="geocities"] .search-bar-input {
+ border-radius: 0;
+}
+
+/* ── Modals — heavy ridged frame ─────────────────────────────────── */
+[data-style="geocities"] .modal-card,
+[data-style="geocities"] .confirm-modal,
+[data-style="geocities"] .user-menu-dropdown,
+[data-style="geocities"] .user-list-popover,
+[data-style="geocities"] .mention-dropdown,
+[data-style="geocities"] .emoji-picker-float {
+ border-radius: 0;
+ border: 3px ridge var(--color-border);
+ box-shadow: 0 0 18px color-mix(in srgb, var(--color-border) 45%, transparent);
+}
+
+[data-style="geocities"] .modal-header {
+ border-bottom: 2px solid var(--color-border-subtle);
+}
+
+/* ── Avatars — framed snapshots ──────────────────────────────────── */
+[data-style="geocities"] .avatar-img {
+ border: 2px ridge var(--color-border);
+}
+
+/* ── Rich content cards — keep brand borders, add neon hover glow ── */
+[data-style="geocities"] .rich-content-card {
+ border-radius: 0;
+ border-width: 3px;
+ border-style: ridge;
+ box-shadow: 0 0 9px color-mix(in srgb, var(--color-border) 38%, transparent);
+}
+
+[data-style="geocities"] .rich-content-card:has(.rich-content-body:hover),
+[data-style="geocities"]
+ .rich-content-card--youtube:has(.rich-content-body:hover),
+[data-style="geocities"]
+ .rich-content-card--bandcamp:has(.rich-content-body:hover),
+[data-style="geocities"]
+ .rich-content-card--soundcloud:has(.rich-content-body:hover) {
+ box-shadow: 0 0 10px color-mix(in srgb, var(--color-accent) 70%, transparent);
+}
+
+[data-style="geocities"] .rich-content-badge {
+ border-radius: 0;
+}
+
+/* ── Global player — neon console ────────────────────────────────── */
+[data-style="geocities"] .global-player {
+ border-radius: 0;
+ border: 3px ridge var(--color-accent);
+ box-shadow: 0 0 16px color-mix(in srgb, var(--color-accent) 55%, transparent);
+}
+
+[data-style="geocities"] .global-player-iframe-wrap,
+[data-style="geocities"] .global-player-media-wrap,
+[data-style="geocities"] .global-player .audio-player,
+[data-style="geocities"] .global-player .video-player {
+ border-radius: 0;
+}
+
+[data-style="geocities"] .global-player .audio-player-btn {
+ color: var(--color-on-accent);
+}
+
+[data-style="geocities"] .global-player .audio-player-vol-btn,
+[data-style="geocities"] .global-player .btn--ghost {
+ color: var(--color-text);
+}
+
+/* ── File drop zone ──────────────────────────────────────────────── */
+[data-style="geocities"] .fdz {
+ border-radius: 0;
+ border-style: dashed;
+}
+
+/* ── Pills / badges — squared, and the unread bits blink ─────────── */
+[data-style="geocities"] .notifications-unread-pill,
+[data-style="geocities"] .playlist-badge,
+[data-style="geocities"] .dump-card-private-badge,
+[data-style="geocities"] .notif-icon,
+[data-style="geocities"] .md code,
+[data-style="geocities"] .md pre,
+[data-style="geocities"] .error-card,
+[data-style="geocities"] .feed-tab {
+ border-radius: 0;
+}
+
+[data-style="geocities"] .notification-badge {
+ border-radius: 0;
+ animation: geo-blink 1.1s step-start infinite;
+}
+
+[data-style="geocities"] .notif-dot,
+[data-style="geocities"] .unread-dot {
+ border-radius: 0;
+ animation: geo-blink 1.3s step-start infinite;
+}
+
+/* ── Floating "new" affordance / FAB-style accents ───────────────── */
+[data-style="geocities"] .fdz__upload-icon {
+ opacity: 0.7;
+}
+
+/* ── Casual web-1.0 voice: lowercase everything (code excepted) ──── */
+[data-style="geocities"],
+[data-style="geocities"] * {
+ text-transform: lowercase;
+}
+[data-style="geocities"] input,
+[data-style="geocities"] textarea,
+[data-style="geocities"] .md code,
+[data-style="geocities"] .md pre,
+[data-style="geocities"] .invite-url {
+ text-transform: none;
+}
diff --git a/src/themes/nyt.css b/src/themes/nyt.css
new file mode 100644
index 0000000..a3c9182
--- /dev/null
+++ b/src/themes/nyt.css
@@ -0,0 +1,576 @@
+/* ══════════════════════════════════════════════════════════════════════
+ "Old Grey Lady" theme — New York Times editorial styling.
+ Serif typography, paper-and-ink palette, hairline rules, sharp corners,
+ restrained NYT-blue links, inverted ink-on-paper primary buttons.
+
+ Light scheme → newsprint (white page, black ink).
+ Dark scheme → NYT night mode (near-black page, warm-grey ink).
+ ══════════════════════════════════════════════════════════════════════ */
+
+/* ── Dark variant (default for this theme) ────────────────────────── */
+:root[data-style="nyt"] {
+ --color-text: #e6e6e6;
+ --color-text-secondary: #ababab;
+ --color-text-muted: #8a8a8a;
+ --color-on-accent: #121212;
+
+ --color-bg: #121212; /* NYT night */
+ --color-surface: #1e1e1e;
+
+ --color-border: #3a3a3a;
+ --color-border-subtle: rgba(255, 255, 255, 0.12);
+
+ --color-accent: #ececec; /* monochrome: interactive = paper-on-ink */
+ --color-accent-hover: #ffffff;
+
+ --color-link: #ececec;
+ --color-link-hover: #ffffff;
+
+ --color-danger: #ff6b6b;
+ --color-danger-bg: #2a1414;
+ --color-danger-hover: #ff9a9a;
+
+ --color-success: #6cc070;
+ --color-success-bg: #122a14;
+ --color-success-hover: #95d699;
+
+ --color-overlay: rgba(0, 0, 0, 0.6);
+ --color-header-user-bg: rgba(255, 255, 255, 0.06);
+ --color-header-user-bg-hover: rgba(255, 255, 255, 0.12);
+
+ --color-option-bg: #181818;
+ --color-option-border: #3a3a3a;
+}
+
+/* ── Light via system preference (auto color-scheme) ──────────────── */
+@media (prefers-color-scheme: light) {
+ :root[data-style="nyt"]:not([data-color-scheme]) {
+ --color-text: #121212; /* ink */
+ --color-text-secondary: #5a5a5a;
+ --color-text-muted: #767676; /* NYT timestamp grey */
+
+ --color-on-accent: #ffffff;
+
+ --color-bg: #ffffff; /* white page — content sits directly on it */
+ --color-surface: #ffffff; /* paper */
+
+ --color-border: #e2e2e2; /* hairline rule */
+ --color-border-subtle: rgba(0, 0, 0, 0.08);
+
+ --color-accent: #121212; /* monochrome: interactive = ink-on-paper */
+ --color-accent-hover: #000000;
+
+ --color-link: #1a1a1a;
+ --color-link-hover: #000000;
+
+ --color-danger: #b10000;
+ --color-danger-bg: #fdecec;
+ --color-danger-hover: #8a0000;
+
+ --color-success: #1a7f37;
+ --color-success-bg: #e6f4ea;
+ --color-success-hover: #136a2c;
+
+ --color-overlay: rgba(0, 0, 0, 0.4);
+ --color-header-user-bg: rgba(0, 0, 0, 0.05);
+ --color-header-user-bg-hover: rgba(0, 0, 0, 0.09);
+
+ --color-option-bg: #f2f2f2;
+ --color-option-border: #e2e2e2;
+ }
+}
+
+/* ── Manual dark override ─────────────────────────────────────────── */
+:root[data-style="nyt"][data-color-scheme="dark"] {
+ color-scheme: dark;
+ --color-text: #e6e6e6;
+ --color-text-secondary: #ababab;
+ --color-text-muted: #8a8a8a;
+ --color-on-accent: #121212;
+ --color-bg: #121212;
+ --color-surface: #1e1e1e;
+ --color-border: #3a3a3a;
+ --color-border-subtle: rgba(255, 255, 255, 0.12);
+ --color-accent: #ececec;
+ --color-accent-hover: #ffffff;
+ --color-link: #ececec;
+ --color-link-hover: #ffffff;
+ --color-danger: #ff6b6b;
+ --color-danger-bg: #2a1414;
+ --color-danger-hover: #ff9a9a;
+ --color-success: #6cc070;
+ --color-success-bg: #122a14;
+ --color-success-hover: #95d699;
+ --color-overlay: rgba(0, 0, 0, 0.6);
+ --color-header-user-bg: rgba(255, 255, 255, 0.06);
+ --color-header-user-bg-hover: rgba(255, 255, 255, 0.12);
+ --color-option-bg: #181818;
+ --color-option-border: #3a3a3a;
+}
+
+/* ── Manual light override ────────────────────────────────────────── */
+:root[data-style="nyt"][data-color-scheme="light"] {
+ color-scheme: light;
+ --color-text: #121212;
+ --color-text-secondary: #5a5a5a;
+ --color-text-muted: #767676;
+ --color-on-accent: #ffffff;
+ --color-bg: #ffffff;
+ --color-surface: #ffffff;
+ --color-border: #e2e2e2;
+ --color-border-subtle: rgba(0, 0, 0, 0.08);
+ --color-accent: #121212;
+ --color-accent-hover: #000000;
+ --color-link: #1a1a1a;
+ --color-link-hover: #000000;
+ --color-danger: #b10000;
+ --color-danger-bg: #fdecec;
+ --color-danger-hover: #8a0000;
+ --color-success: #1a7f37;
+ --color-success-bg: #e6f4ea;
+ --color-success-hover: #136a2c;
+ --color-overlay: rgba(0, 0, 0, 0.4);
+ --color-header-user-bg: rgba(0, 0, 0, 0.05);
+ --color-header-user-bg-hover: rgba(0, 0, 0, 0.09);
+ --color-option-bg: #f2f2f2;
+ --color-option-border: #e2e2e2;
+}
+
+/* ══════════════════════════════════════════════════════════════════════
+ Structural overrides
+ ══════════════════════════════════════════════════════════════════════ */
+
+/* ── Typography — one serif everywhere ───────────────────────────── */
+[data-style="nyt"] {
+ --font-body: "Instrument Serif", Georgia, "Times New Roman", serif;
+ --font-display: "Instrument Serif", Georgia, "Times New Roman", serif;
+ font-family: var(--font-body);
+ font-weight: 400;
+ line-height: 1.5;
+}
+
+/* Sharp, rectilinear corners throughout — newsprint has no rounding. */
+[data-style="nyt"] button,
+[data-style="nyt"] .dump-card,
+[data-style="nyt"] .playlist-card,
+[data-style="nyt"] .journal-card,
+[data-style="nyt"] .dump-post-header,
+[data-style="nyt"] .auth-card,
+[data-style="nyt"] .notification-item,
+[data-style="nyt"] .user-result-item,
+[data-style="nyt"] .comment-node-inner,
+[data-style="nyt"] .comment-top-form,
+[data-style="nyt"] .rich-content-card,
+[data-style="nyt"] .rich-content-badge,
+[data-style="nyt"] .modal-card,
+[data-style="nyt"] .confirm-modal,
+[data-style="nyt"] .user-menu-dropdown,
+[data-style="nyt"] .user-list-popover,
+[data-style="nyt"] .global-player,
+[data-style="nyt"] .global-player-media-wrap,
+[data-style="nyt"] .global-player-iframe-wrap,
+[data-style="nyt"] .fdz,
+[data-style="nyt"] .visibility-toggle,
+[data-style="nyt"] .feed-tab,
+[data-style="nyt"] .md code,
+[data-style="nyt"] .md pre,
+[data-style="nyt"]
+ input:not([type="range"]):not([type="file"]):not([type="checkbox"]):not(
+ [type="radio"]
+ ),
+[data-style="nyt"] textarea {
+ border-radius: 0;
+}
+
+/* ── Headlines — display serif ───────────────────────────────────── */
+[data-style="nyt"] h1,
+[data-style="nyt"] .app-header-brand,
+[data-style="nyt"] .dump-title,
+[data-style="nyt"] .dump-card-title,
+[data-style="nyt"] .playlist-card-title,
+[data-style="nyt"] .journal-card-title,
+[data-style="nyt"] .playlist-detail-title,
+[data-style="nyt"] .form-page-title,
+[data-style="nyt"] .auth-card-title,
+[data-style="nyt"] .profile-subpage-title,
+[data-style="nyt"] .notifications-title,
+[data-style="nyt"] .dump-create-title {
+ font-family: var(--font-display);
+ font-weight: 400; /* Instrument Serif is single-weight; size carries it */
+ letter-spacing: 0;
+ line-height: 1.14;
+}
+
+/* A confident broadsheet type scale */
+[data-style="nyt"] .app-header-brand {
+ font-size: 1.45rem;
+ letter-spacing: 0;
+}
+[data-style="nyt"] .dump-title {
+ font-size: 2.1rem;
+}
+[data-style="nyt"] .playlist-detail-title {
+ font-size: 1.9rem;
+}
+[data-style="nyt"] .dump-card-title,
+[data-style="nyt"] .playlist-card-title {
+ font-size: 1.2rem;
+ line-height: 1.2;
+}
+[data-style="nyt"] .journal-card-title {
+ font-size: 1.15rem;
+ line-height: 1.18;
+}
+[data-style="nyt"] .journal-card--feature .journal-card-title {
+ font-size: 1.6rem;
+}
+
+/* Headline links underline on hover (ink stays ink). */
+[data-style="nyt"] .dump-card-inner:hover .dump-card-title,
+[data-style="nyt"] .playlist-card-inner:hover .playlist-card-title,
+[data-style="nyt"] .journal-card-title:hover {
+ color: var(--color-text);
+ text-decoration: underline;
+ text-underline-offset: 2px;
+}
+
+/* ── Section labels — sentence case, no shouty caps ──────────────── */
+[data-style="nyt"] .comment-section-title,
+[data-style="nyt"] .related-section-title,
+[data-style="nyt"] .form-page-eyebrow,
+[data-style="nyt"] .feed-sort-btn,
+[data-style="nyt"] .follow-btn,
+[data-style="nyt"] .playlist-badge,
+[data-style="nyt"] .dump-card-private-badge,
+[data-style="nyt"] .notif-group-label,
+[data-style="nyt"] .dump-card-date,
+[data-style="nyt"] .journal-card-meta {
+ text-transform: none;
+ font-variant-caps: small-caps;
+ font-size: 90%;
+ letter-spacing: 0;
+}
+
+[data-style="nyt"] .comment-section-title,
+[data-style="nyt"] .related-section-title,
+[data-style="nyt"] .form-page-eyebrow {
+ font-weight: 700;
+}
+
+/* ── Lead note — a standfirst with an editorial drop cap ─────────── */
+[data-style="nyt"] .dump-comment {
+ border-left: none;
+ padding: 0;
+ margin: 0.35rem 0 0;
+ opacity: 1;
+ color: var(--color-text-secondary);
+ font-size: 1.2rem;
+ line-height: 1.6;
+}
+[data-style="nyt"] .dump-comment p:first-of-type::first-letter {
+ float: left;
+ font-family: var(--font-display);
+ font-weight: 400;
+ font-size: 3.4rem;
+ line-height: 0.78;
+ padding: 0.12em 0.14em 0 0;
+ color: var(--color-text);
+}
+
+/* ── Generous editorial leading for the lead note; comments stay tight ── */
+[data-style="nyt"] .dump-comment,
+[data-style="nyt"] .dump-comment p {
+ line-height: 1.7;
+}
+
+/* Tight, ruled comment thread — small padding, snug leading, no extra gaps. */
+[data-style="nyt"] .comment-body {
+ line-height: 1.45;
+}
+[data-style="nyt"] .comment-node-inner {
+ padding: 0.5rem 0;
+ gap: 0.6rem;
+}
+[data-style="nyt"] .comment-meta {
+ margin-bottom: 0.1rem;
+}
+[data-style="nyt"] .comment-list,
+[data-style="nyt"] .comment-replies {
+ gap: 0;
+}
+
+/* ── Content isn't boxed — it sits on the page, divided by hairline
+ rules (à la nytimes.com). No surface fill, no card shadow. ─────────── */
+[data-style="nyt"] .dump-card,
+[data-style="nyt"] .playlist-card,
+[data-style="nyt"] .user-result-item,
+[data-style="nyt"] .comment-node-inner {
+ background: transparent;
+ border: none;
+ border-bottom: 1px solid var(--color-border);
+ border-radius: 0;
+ box-shadow: none;
+}
+
+/* The dump detail is a single article, not a list — no dividing rules. */
+[data-style="nyt"] .dump-post-header,
+[data-style="nyt"] .dump-rich-content {
+ background: transparent;
+ border: none;
+ border-radius: 0;
+ box-shadow: none;
+}
+
+/* Notifications keep their left bar (the unread indicator), lose the box. */
+[data-style="nyt"] .notification-item {
+ border: none;
+ border-left: 3px solid transparent;
+ border-bottom: 1px solid var(--color-border);
+ border-radius: 0;
+ box-shadow: none;
+}
+
+/* No box-hover; the headline underline is the affordance. */
+[data-style="nyt"] .dump-card:hover,
+[data-style="nyt"] .playlist-card:hover,
+[data-style="nyt"] .user-result-item:hover {
+ background: transparent;
+ border-color: var(--color-border);
+}
+
+/* Genuine containers (forms, embeds, the compose box) keep a hairline box. */
+[data-style="nyt"] .auth-card,
+[data-style="nyt"] .comment-top-form,
+[data-style="nyt"] .rich-content-card {
+ background: transparent;
+ border: 1px solid var(--color-border);
+ box-shadow: none;
+}
+
+/* Journal view: a section-front grid of stories divided by hairline rules,
+ not boxed cards. Close the gaps and rule each cell on its top & left edge so
+ the rules read as continuous row + column delimiters (à la nytimes.com).
+ The left-most column's rule is masked off (below) so there's no outer frame
+ on the left — CSS grid can't target "first column", hence the mask. */
+[data-style="nyt"] .journal-grid {
+ gap: 0;
+ position: relative;
+}
+[data-style="nyt"] .journal-card {
+ background: transparent;
+ border: none;
+ border-top: 1px solid var(--color-border);
+ border-left: 1px solid var(--color-border);
+ box-shadow: none;
+}
+
+/* Page-coloured strip over the grid's content-left edge, hiding the first
+ column's left rule (left value tracks the grid's padding-left). */
+[data-style="nyt"] .journal-grid::before {
+ content: "";
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 1.25rem;
+ width: 1px;
+ background: var(--color-bg);
+ z-index: 1;
+ pointer-events: none;
+}
+@media (max-width: 460px) {
+ [data-style="nyt"] .journal-grid::before {
+ left: 1rem;
+ }
+}
+[data-style="nyt"] .journal-card:hover {
+ border-color: var(--color-border);
+ transform: none;
+ box-shadow: none;
+}
+
+/* Inset content from the rules so the grid breathes — photos get a margin
+ frame, text stories sit a little further off the lines. */
+[data-style="nyt"] .journal-card--image {
+ padding: 0.55rem;
+}
+[data-style="nyt"] .journal-card--image .journal-card-image {
+ inset: 0.55rem;
+}
+[data-style="nyt"] .journal-card--quote,
+[data-style="nyt"] .journal-card--text {
+ padding: 1.05rem;
+}
+
+/* ── App header — crisp masthead rule ────────────────────────────── */
+[data-style="nyt"] .app-header {
+ border-bottom: 2px solid var(--color-text);
+}
+
+[data-style="nyt"] .header-search-below {
+ border-bottom: 1px solid var(--color-border);
+}
+
+[data-style="nyt"] .app-header-user {
+ border-radius: 0;
+}
+
+/* ── Primary button — inverted ink-on-paper (auto-flips per scheme) ─ */
+[data-style="nyt"] .btn-primary {
+ background: var(--color-text);
+ color: var(--color-bg);
+ border: 1px solid var(--color-text);
+ box-shadow: none;
+}
+
+[data-style="nyt"] .btn-primary:hover:not(:disabled) {
+ background: color-mix(in srgb, var(--color-text) 82%, var(--color-bg));
+ box-shadow: none;
+ transform: none;
+}
+
+/* Secondary / outline buttons — plain hairline box */
+[data-style="nyt"] .btn-secondary,
+[data-style="nyt"] .btn-border,
+[data-style="nyt"] .btn-add-playlist,
+[data-style="nyt"] .vote-btn,
+[data-style="nyt"] .feed-sort-btn,
+[data-style="nyt"] .follow-btn {
+ border-radius: 0;
+}
+
+/* ── Inputs — clean field, ink focus ─────────────────────────────── */
+[data-style="nyt"] .form input:focus,
+[data-style="nyt"] .form textarea:focus,
+[data-style="nyt"] .mention-textarea-wrap textarea:focus {
+ border-color: var(--color-accent);
+ box-shadow: none;
+}
+
+/* ── Modals — paper sheet with a soft editorial shadow ───────────── */
+[data-style="nyt"] .modal-card,
+[data-style="nyt"] .confirm-modal,
+[data-style="nyt"] .user-menu-dropdown,
+[data-style="nyt"] .user-list-popover {
+ border: 1px solid var(--color-border);
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.18);
+}
+
+[data-style="nyt"] .modal-header {
+ border-bottom: 1px solid var(--color-border);
+}
+
+/* ── Rich content cards — keep brand cue, square it, drop the glow ── */
+[data-style="nyt"] .rich-content-card {
+ box-shadow: none;
+}
+
+/* ── Global player — framed strip ────────────────────────────────── */
+[data-style="nyt"] .global-player {
+ border: 1px solid var(--color-border);
+ box-shadow: 0 8px 28px rgba(0, 0, 0, 0.2);
+}
+
+/* ── Drop the reply "tree" rules; nesting reads from indentation alone ── */
+[data-style="nyt"] .comment-replies {
+ border-left: none;
+ margin-top: 0;
+}
+
+/* ══════════════════════════════════════════════════════════════════════
+ Monochrome UI chrome — colour is reserved for meaning (danger, the
+ notification badge, brand-provider cues). Everything else is ink & paper.
+ ══════════════════════════════════════════════════════════════════════ */
+
+/* Feed sort — section-front tabs: an underline, not a filled chip. */
+[data-style="nyt"] .feed-sort-btn {
+ border: none;
+ border-bottom: 2px solid transparent;
+ border-radius: 0;
+ background: none;
+ color: var(--color-text-muted);
+ padding: 0.2rem 0.15rem;
+}
+[data-style="nyt"] .feed-sort-btn:hover {
+ background: none;
+ color: var(--color-text);
+}
+[data-style="nyt"] .feed-sort-btn.active {
+ background: none;
+ color: var(--color-text);
+ border-bottom-color: var(--color-text);
+}
+
+/* Square off the remaining pills & chips. */
+[data-style="nyt"] .playlist-badge,
+[data-style="nyt"] .dump-card-private-badge,
+[data-style="nyt"] .notifications-unread-pill,
+[data-style="nyt"] .notification-badge,
+[data-style="nyt"] .vote-btn,
+[data-style="nyt"] .follow-btn,
+[data-style="nyt"] .btn-add-playlist,
+[data-style="nyt"] .invite-btn {
+ border-radius: 0;
+ border: none;
+}
+
+/* Links are now ink, so give the user-link classes a hover underline
+ to keep them findable without colour. */
+[data-style="nyt"] .dump-op-link:hover,
+[data-style="nyt"] .playlist-card-owner:hover,
+[data-style="nyt"] .playlist-detail-owner:hover,
+[data-style="nyt"] .comment-author:hover {
+ text-decoration: underline;
+ text-underline-offset: 2px;
+}
+
+/* Desaturate decorative UI emoji/icons (the header truck, notification
+ type icons, card glyphs…). Real content — avatars, uploaded thumbnails,
+ media — is left in colour. grayscale() also greys the notif icon tints. */
+[data-style="nyt"] .app-header-brand,
+[data-style="nyt"] .nav-search-btn,
+[data-style="nyt"] .search-bar-btn,
+[data-style="nyt"] .notification-bell-icon,
+[data-style="nyt"] .notif-icon,
+[data-style="nyt"] .notifications-empty-icon,
+[data-style="nyt"] .notifications-title-bell,
+[data-style="nyt"] .dump-card-preview-icon,
+[data-style="nyt"] .playlist-card-icon,
+[data-style="nyt"] .rich-content-compact-icon,
+[data-style="nyt"] .journal-card-glyph,
+[data-style="nyt"] .fdz__file-icon,
+[data-style="nyt"] .empty-state {
+ filter: grayscale(1);
+}
+
+/* ── Tighter, continuous ruled feed (no inter-item gap; the hairline
+ bottom rules read as one column). Even vertical padding per item. ── */
+[data-style="nyt"] .dump-feed {
+ gap: 0;
+}
+[data-style="nyt"] .dump-card-inner,
+[data-style="nyt"] .playlist-card-inner {
+ padding-top: 0.9rem;
+ padding-bottom: 0.9rem;
+}
+
+/* ── Card thumbnail — square & flat box (no pop/shadow), but keep a
+ hover effect adapted to the theme: the image zooms gently inside its
+ fixed, clipped frame, the editorial way. ───────────────────────── */
+[data-style="nyt"] .dump-card-preview,
+[data-style="nyt"] .playlist-card-preview {
+ border-radius: 0;
+ transition: none;
+}
+[data-style="nyt"] .dump-card-inner:hover .dump-card-preview,
+[data-style="nyt"] .playlist-card-inner:hover .playlist-card-preview {
+ transform: none;
+ box-shadow: none;
+}
+[data-style="nyt"] .dump-card-preview img,
+[data-style="nyt"] .playlist-card-preview img {
+ transition: transform 0.3s ease;
+}
+[data-style="nyt"] .dump-card-inner:hover .dump-card-preview img,
+[data-style="nyt"] .playlist-card-inner:hover .playlist-card-preview img {
+ transform: scale(1.09);
+}
diff --git a/src/themes/smooth.css b/src/themes/smooth.css
index e9b2af9..3de0ec7 100644
--- a/src/themes/smooth.css
+++ b/src/themes/smooth.css
@@ -7,7 +7,12 @@
/* ── Dark variant (default) ───────────────────────────────────────── */
:root,
:root[data-style="smooth"] {
- font-family: "Saira", sans-serif;
+ /* Global font defaults — other themes override --font-body; App.css styles
+ consume these variables rather than hard-coding font names. */
+ --font-body: "Saira", sans-serif;
+ --font-mono: monospace;
+
+ font-family: var(--font-body);
font-optical-sizing: auto;
font-weight: 400;
font-style: normal;
@@ -56,13 +61,16 @@
--color-option-bg: #0d2340;
--color-option-border: #001427;
- /* Service brand colors (fixed, not theme-dependent) */
+ /* Service brand colors (fixed, not theme-dependent). `on-brand` is the
+ foreground for text sitting on any of these — also fixed, so the contrast
+ never flips with the theme's text colour. */
--color-youtube: #c00;
--color-youtube-hover: #f00;
--color-bandcamp: #1da0c3;
--color-bandcamp-hover: #25c8f0;
--color-soundcloud: #f50;
--color-soundcloud-hover: #f73;
+ --color-on-brand: #fff;
}
/* ── Light via system preference (auto color-scheme) ──────────────── */