From 96c41f01da27e444bac582e7655465d199379bd8 Mon Sep 17 00:00:00 2001 From: khannurien Date: Sat, 27 Jun 2026 14:28:33 +0000 Subject: [PATCH] v3: tracked down many visual inconsistencies, added two new themes --- index.html | 4 +- src/App.css | 445 ++++++++---------------- src/contexts/ThemeContext.ts | 2 +- src/locales/en.po | 36 +- src/locales/fr.po | 36 +- src/main.tsx | 2 + src/pages/UserPublicProfile.tsx | 14 + src/themes/brutalist.css | 27 +- src/themes/geocities.css | 504 ++++++++++++++++++++++++++++ src/themes/nyt.css | 576 ++++++++++++++++++++++++++++++++ src/themes/smooth.css | 12 +- 11 files changed, 1299 insertions(+), 359 deletions(-) create mode 100644 src/themes/geocities.css create mode 100644 src/themes/nyt.css 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) ──────────────── */