v4: the header's controls are line icons, and the avatar leads them — 🔍, ✕, 💬 and 🔔 were four emoji in four palettes beside the avatar and the "+", none of them the theme's. They are now stroked icons (NavIcons.tsx, which the search bar's submit and ChatFab share) in the text colour, their bodies washed with the accent; the wash deepens on hover, and fills while something is unread, with a small dot in place of the red count (the count stays in the button's label and the tab's title, and ChatFab keeps its own). Themes set the line through --nav-icon-stroke, -linecap, -linejoin and -tint-strength: Brutalist heavy and square-cut, Times hairline in ink (its grayscale filter no longer needed on them), GeoCities fat and soaked. The search toggle was a ghost button with its own hover; it now shares the chat button's and the bell's rule, background and borders in every theme. The avatar sat among the icons, its hover box larger than theirs and 1.5px higher (a block .user-menu left a line-box gap under the inline-flex trigger): it now leads the row, where the name and Playlists links stand on wide screens, so the icons run unbroken to the "+"; it is 32px, and hover rings it with the accent instead of a box. 669 frontend tests pass, and tsc, eslint and deno fmt are clean; checked in screenshots in all six styles, light and dark, at 390px, at rest and unread, and Smooth and Brutalist at 1280px.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
104
src/App.css
104
src/App.css
@@ -1775,7 +1775,7 @@ body.has-fab .page-content {
|
||||
top 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.chat-fab-icon {
|
||||
.chat-fab .chat-fab-icon {
|
||||
width: 1.6rem;
|
||||
height: 1.6rem;
|
||||
}
|
||||
@@ -3075,23 +3075,38 @@ body.has-player .chat-fab {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Ghost search button — opens the search (in the center column, or the row
|
||||
under the header on small viewports) and turns into its ✕ */
|
||||
.nav-search-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.35rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
font-size: var(--text-base);
|
||||
cursor: pointer;
|
||||
border-radius: var(--radius-md);
|
||||
/* Line icons (NavIcons.tsx), stroked in the text colour. */
|
||||
.nav-icon {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
flex-shrink: 0;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-width: var(--nav-icon-stroke);
|
||||
stroke-linecap: var(--nav-icon-linecap);
|
||||
stroke-linejoin: var(--nav-icon-linejoin);
|
||||
}
|
||||
|
||||
.nav-search-btn:hover {
|
||||
background: var(--color-surface);
|
||||
/* Two-tone in the header and its search: the icons' bodies take a wash of
|
||||
the accent, which deepens on hover and fills while something is unread.
|
||||
The floating chat button sits on the accent, so its bubble stays hollow. */
|
||||
.app-header .nav-icon-tint,
|
||||
.search-bar-btn .nav-icon-tint {
|
||||
fill: color-mix(in srgb, var(--color-accent) var(--nav-icon-tint-strength),
|
||||
transparent);
|
||||
transition: fill var(--dur-base);
|
||||
}
|
||||
|
||||
.nav-search-btn:hover,
|
||||
.chat-button:hover,
|
||||
.notification-bell:hover,
|
||||
.search-bar-btn:hover {
|
||||
--nav-icon-tint-strength: 45%;
|
||||
}
|
||||
|
||||
.chat-button--unread,
|
||||
.notification-bell--unread {
|
||||
--nav-icon-tint-strength: 100%;
|
||||
}
|
||||
|
||||
/* Text links — visible only at wide viewports */
|
||||
@@ -3137,8 +3152,11 @@ body.has-player .chat-fab {
|
||||
}
|
||||
|
||||
/* ── User menu (compact nav) ── */
|
||||
/* A flex box, so the inline-flex trigger leaves no line-box gap under it
|
||||
that would sit it higher than the buttons beside it. */
|
||||
.user-menu {
|
||||
position: relative;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.user-menu-trigger {
|
||||
@@ -3152,8 +3170,15 @@ body.has-player .chat-fab {
|
||||
transition: background var(--dur-base);
|
||||
}
|
||||
|
||||
.user-menu-trigger:hover {
|
||||
background: var(--color-header-user-bg-hover);
|
||||
/* The avatar fills its button, so a hover background would read as a box
|
||||
bigger than the icons' ones: an accent ring shows instead. */
|
||||
.user-menu-trigger > * {
|
||||
transition: box-shadow var(--dur-base);
|
||||
}
|
||||
|
||||
.user-menu-trigger:hover > *,
|
||||
.user-menu-trigger[aria-expanded="true"] > * {
|
||||
box-shadow: 0 0 0 2px var(--color-accent);
|
||||
}
|
||||
|
||||
.user-menu-dropdown {
|
||||
@@ -6484,24 +6509,28 @@ body.has-player .chat-fab {
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Header icon buttons ──
|
||||
Search (which opens the search, in the center column or the row under the
|
||||
header on small viewports, and turns into its ✕), chat and the bell. */
|
||||
.nav-search-btn,
|
||||
.chat-button,
|
||||
.notification-bell {
|
||||
position: relative;
|
||||
background: var(--color-header-user-bg);
|
||||
color: inherit;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: var(--text-md);
|
||||
font-weight: 600;
|
||||
padding: 0.35rem 0.85rem;
|
||||
border-radius: var(--radius-lg);
|
||||
transition: background var(--dur-base);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
.nav-search-btn:hover,
|
||||
.chat-button:hover,
|
||||
.notification-bell:hover {
|
||||
background: var(--color-header-user-bg-hover);
|
||||
}
|
||||
.notification-bell-icon {
|
||||
display: inline-block;
|
||||
transform-origin: 50% 10%;
|
||||
}
|
||||
.notification-bell--ringing .notification-bell-icon {
|
||||
@@ -6527,25 +6556,16 @@ body.has-player .chat-fab {
|
||||
box-shadow: 0 0 0 2px var(--color-bg);
|
||||
}
|
||||
|
||||
/* ── Header chat button ── */
|
||||
.chat-button {
|
||||
position: relative;
|
||||
background: var(--color-header-user-bg);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: var(--text-md);
|
||||
font-weight: 600;
|
||||
padding: 0.35rem 0.85rem;
|
||||
border-radius: var(--radius-lg);
|
||||
transition: background var(--dur-base);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
.chat-button:hover {
|
||||
background: var(--color-header-user-bg-hover);
|
||||
}
|
||||
.chat-button-icon {
|
||||
display: inline-block;
|
||||
/* The header's buttons fill their icon while something is unread, so a dot
|
||||
is enough; the count is in their label. */
|
||||
.notification-badge--dot {
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
min-width: 0;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
padding: 0;
|
||||
background: var(--color-accent);
|
||||
}
|
||||
|
||||
/* ── Notifications page ── */
|
||||
@@ -7067,6 +7087,8 @@ body.has-player .chat-fab {
|
||||
}
|
||||
|
||||
.search-bar-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.3rem 0.55rem;
|
||||
background: transparent;
|
||||
border: var(--border-width) solid var(--color-border-subtle);
|
||||
|
||||
@@ -15,6 +15,7 @@ import { NotificationBell } from "./NotificationBell.tsx";
|
||||
import { ChatButton } from "./ChatButton.tsx";
|
||||
import { UserMenu } from "./UserMenu.tsx";
|
||||
import { SearchBar } from "./SearchBar.tsx";
|
||||
import { IconClose, IconSearch } from "./NavIcons.tsx";
|
||||
import { SITE_EMOJI, SITE_NAME } from "../../hooks/shared/useDocumentTitle.ts";
|
||||
import { searchPath, userPath } from "../../../shared/paths.ts";
|
||||
|
||||
@@ -156,6 +157,32 @@ export function AppHeader(
|
||||
</div>
|
||||
|
||||
<nav className="app-header-nav">
|
||||
{
|
||||
/* The user's own links lead the row, so the icons after them run
|
||||
unbroken to the "+". CSS shows the text links or the avatar's
|
||||
menu, by width. */
|
||||
}
|
||||
{user && (
|
||||
<>
|
||||
<div className="nav-links">
|
||||
<Link
|
||||
to={userPath(user.username)}
|
||||
className="app-header-user"
|
||||
>
|
||||
{user.username}
|
||||
</Link>
|
||||
<Link
|
||||
to={userPath(user.username, "playlists")}
|
||||
className="app-header-user"
|
||||
>
|
||||
<Trans>Playlists</Trans>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="nav-compact">
|
||||
<UserMenu user={user} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
ref={searchToggleRef}
|
||||
type="button"
|
||||
@@ -164,31 +191,11 @@ export function AppHeader(
|
||||
aria-expanded={searchExpanded}
|
||||
onClick={handleSearchToggle}
|
||||
>
|
||||
{searchExpanded ? "✕" : "🔍"}
|
||||
{searchExpanded ? <IconClose /> : <IconSearch />}
|
||||
</button>
|
||||
{user
|
||||
? (
|
||||
<>
|
||||
<div className="nav-links">
|
||||
<Link
|
||||
to={userPath(user.username)}
|
||||
className="app-header-user"
|
||||
>
|
||||
{user.username}
|
||||
</Link>
|
||||
<Link
|
||||
to={userPath(user.username, "playlists")}
|
||||
className="app-header-user"
|
||||
>
|
||||
<Trans>Playlists</Trans>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* CSS shows either the links above or this menu, by width. */}
|
||||
<div className="nav-compact">
|
||||
<UserMenu user={user} />
|
||||
</div>
|
||||
|
||||
<ChatButton />
|
||||
<NotificationBell />
|
||||
<button
|
||||
@@ -205,11 +212,9 @@ export function AppHeader(
|
||||
</>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<button type="button" onClick={() => navigate("/login")}>
|
||||
<Trans>Log in</Trans>
|
||||
</button>
|
||||
</>
|
||||
<button type="button" onClick={() => navigate("/login")}>
|
||||
<Trans>Log in</Trans>
|
||||
</button>
|
||||
)}
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
@@ -7,10 +7,12 @@
|
||||
import { t } from "@lingui/core/macro";
|
||||
import { useWSChat } from "../../hooks/ws/useWS.ts";
|
||||
import { useChat } from "../../hooks/chat/useChat.ts";
|
||||
import { IconChat } from "./NavIcons.tsx";
|
||||
|
||||
/**
|
||||
* Renders the button that opens the chat, with the socket's count of unread
|
||||
* messages. `ChatFab` does the same once the header scrolls away.
|
||||
* Renders the button that opens the chat, filled with the accent and dotted
|
||||
* while the socket counts unread messages; the count itself is in its label.
|
||||
* `ChatFab` takes over, with the count, once the header scrolls away.
|
||||
*/
|
||||
export function ChatButton(): React.JSX.Element {
|
||||
const { unreadChatCount } = useWSChat();
|
||||
@@ -19,17 +21,17 @@ export function ChatButton(): React.JSX.Element {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="chat-button"
|
||||
className={`chat-button${
|
||||
unreadChatCount > 0 ? " chat-button--unread" : ""
|
||||
}`}
|
||||
onClick={openChat}
|
||||
aria-label={unreadChatCount > 0
|
||||
? t`Chat (${unreadChatCount} unread)`
|
||||
: t`Chat`}
|
||||
>
|
||||
<span className="chat-button-icon">💬</span>
|
||||
<IconChat />
|
||||
{unreadChatCount > 0 && (
|
||||
<span className="notification-badge">
|
||||
{unreadChatCount > 99 ? "99+" : unreadChatCount}
|
||||
</span>
|
||||
<span className="notification-badge notification-badge--dot" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -10,6 +10,7 @@ import { t } from "@lingui/core/macro";
|
||||
import { useAuth } from "../../hooks/auth/useAuth.ts";
|
||||
import { useWSChat } from "../../hooks/ws/useWS.ts";
|
||||
import { useChat } from "../../hooks/chat/useChat.ts";
|
||||
import { IconChat } from "./NavIcons.tsx";
|
||||
|
||||
/**
|
||||
* Renders the chat button that floats above `DumpFab` once the header, and its
|
||||
@@ -56,18 +57,7 @@ export function ChatFab(): React.JSX.Element | null {
|
||||
tabIndex={headerHidden ? 0 : -1}
|
||||
onClick={openChat}
|
||||
>
|
||||
<svg
|
||||
className="chat-fab-icon"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M21 11.5a8.38 8.38 0 0 1-8.5 8.5 8.5 8.5 0 0 1-3.8-.9L3 21l1.9-5.7a8.5 8.5 0 0 1-.9-3.8A8.38 8.38 0 0 1 12.5 3a8.38 8.38 0 0 1 8.5 8.5z" />
|
||||
</svg>
|
||||
<IconChat className="chat-fab-icon" />
|
||||
{unreadChatCount > 0 && (
|
||||
<span className="notification-badge">
|
||||
{unreadChatCount > 99 ? "99+" : unreadChatCount}
|
||||
|
||||
65
src/components/layout/NavIcons.tsx
Normal file
65
src/components/layout/NavIcons.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Draws the line icons of the header's controls (search, its ✕, chat and the
|
||||
* bell) and of `ChatFab`. They stroke in the text colour; the shapes marked
|
||||
* `nav-icon-tint` also fill with `--nav-icon-tint`, which the header sets to a
|
||||
* wash of the accent and the floating button leaves unset. Themes set the
|
||||
* line's weight and ends with `--nav-icon-stroke` and `--nav-icon-linecap`.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
/** Passed to every icon, so a caller can add its own class (the bell rings). */
|
||||
export interface NavIconProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/** Wraps an icon's shapes in the shared 24-unit frame. */
|
||||
function NavIcon(
|
||||
{ className, children }: NavIconProps & { children: React.ReactNode },
|
||||
): React.JSX.Element {
|
||||
return (
|
||||
<svg
|
||||
className={className ? `nav-icon ${className}` : "nav-icon"}
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{children}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
/** Renders a magnifier, for the header's search toggle and the search bar. */
|
||||
export const IconSearch = (props: NavIconProps): React.JSX.Element => (
|
||||
<NavIcon {...props}>
|
||||
<circle className="nav-icon-tint" cx="10.5" cy="10.5" r="6.5" />
|
||||
<path d="M15.5 15.5 20.5 20.5" />
|
||||
</NavIcon>
|
||||
);
|
||||
|
||||
/** Renders the ✕ the search toggle turns into while the search is open. */
|
||||
export const IconClose = (props: NavIconProps): React.JSX.Element => (
|
||||
<NavIcon {...props}>
|
||||
<path d="M6 6 18 18M18 6 6 18" />
|
||||
</NavIcon>
|
||||
);
|
||||
|
||||
/** Renders a speech bubble, for `ChatButton` and `ChatFab`. */
|
||||
export const IconChat = (props: NavIconProps): React.JSX.Element => (
|
||||
<NavIcon {...props}>
|
||||
<path
|
||||
className="nav-icon-tint"
|
||||
d="M20.5 11.5a8 8 0 0 1-8 8 8.1 8.1 0 0 1-3.6-.85L3.5 20.5l1.85-5.4a8.1 8.1 0 0 1-.85-3.6 8 8 0 0 1 8-8 8 8 0 0 1 8 8z"
|
||||
/>
|
||||
</NavIcon>
|
||||
);
|
||||
|
||||
/** Renders a bell, for `NotificationBell`. */
|
||||
export const IconBell = (props: NavIconProps): React.JSX.Element => (
|
||||
<NavIcon {...props}>
|
||||
<path
|
||||
className="nav-icon-tint"
|
||||
d="M6 9.5a6 6 0 0 1 12 0c0 5.5 2.5 7.5 2.5 7.5h-17S6 15 6 9.5z"
|
||||
/>
|
||||
<path d="M10.25 20.5a2 2 0 0 0 3.5 0" />
|
||||
</NavIcon>
|
||||
);
|
||||
@@ -8,10 +8,13 @@ import { useEffect, useRef, useState } from "react";
|
||||
import { useNavigate } from "react-router";
|
||||
import { t } from "@lingui/core/macro";
|
||||
import { useWS } from "../../hooks/ws/useWS.ts";
|
||||
import { IconBell } from "./NavIcons.tsx";
|
||||
|
||||
/**
|
||||
* Renders the bell that leads to `/notifications`, with the socket's count of
|
||||
* unread notifications. It rings when the socket pushes a new one.
|
||||
* Renders the bell that leads to `/notifications`. While the socket counts
|
||||
* unread notifications it fills with the accent and wears a dot; the count
|
||||
* itself is in its label and the tab's title. It rings when the socket pushes
|
||||
* a new one.
|
||||
*/
|
||||
export function NotificationBell(): React.JSX.Element {
|
||||
const { unreadNotificationCount, lastNotification } = useWS();
|
||||
@@ -46,18 +49,16 @@ export function NotificationBell(): React.JSX.Element {
|
||||
<button
|
||||
type="button"
|
||||
className={`notification-bell${
|
||||
ringing ? " notification-bell--ringing" : ""
|
||||
}`}
|
||||
unreadNotificationCount > 0 ? " notification-bell--unread" : ""
|
||||
}${ringing ? " notification-bell--ringing" : ""}`}
|
||||
onClick={() => navigate("/notifications")}
|
||||
aria-label={unreadNotificationCount > 0
|
||||
? t`Notifications (${unreadNotificationCount} unread)`
|
||||
: t`Notifications`}
|
||||
>
|
||||
<span className="notification-bell-icon">🔔</span>
|
||||
<IconBell className="notification-bell-icon" />
|
||||
{unreadNotificationCount > 0 && (
|
||||
<span className="notification-badge">
|
||||
{unreadNotificationCount > 99 ? "99+" : unreadNotificationCount}
|
||||
</span>
|
||||
<span className="notification-badge notification-badge--dot" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { t } from "@lingui/core/macro";
|
||||
import { IconSearch } from "./NavIcons.tsx";
|
||||
|
||||
/**
|
||||
* Passed to {@link SearchBar} by `AppHeader`, which holds the query and decides
|
||||
@@ -88,7 +89,7 @@ export function SearchBar(
|
||||
aria-label={t`Submit search`}
|
||||
tabIndex={expanded ? 0 : -1}
|
||||
>
|
||||
🔍
|
||||
<IconSearch />
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -59,7 +59,7 @@ export function UserMenu({ user }: UserMenuProps): React.JSX.Element {
|
||||
userId={user.id}
|
||||
username={user.username}
|
||||
hasAvatar={!!user.avatarMime}
|
||||
size={28}
|
||||
size={32}
|
||||
/>
|
||||
</button>
|
||||
{open && (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
import { screen, waitFor } from "@testing-library/react";
|
||||
import { screen } from "@testing-library/react";
|
||||
import { userEvent } from "@testing-library/user-event";
|
||||
import { ChatButton } from "../../../components/layout/ChatButton.tsx";
|
||||
import { ChatContext } from "../../../contexts/chat/ChatContext.ts";
|
||||
@@ -17,8 +17,9 @@ describe("ChatButton", () => {
|
||||
</ChatContext.Provider>,
|
||||
{ ws: { unreadChatCount: 4 } },
|
||||
);
|
||||
await user.click(screen.getByRole("button", { name: "Chat (4 unread)" }));
|
||||
const chat = screen.getByRole("button", { name: "Chat (4 unread)" });
|
||||
expect(chat).toHaveClass("chat-button--unread");
|
||||
await user.click(chat);
|
||||
expect(openChat).toHaveBeenCalled();
|
||||
await waitFor(() => expect(screen.getByText("4")).toBeInTheDocument());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import { currentPath, renderWithContexts } from "../../render.tsx";
|
||||
import { defaultWSValue, WSContext } from "../../../contexts/ws/WSContext.ts";
|
||||
|
||||
describe("NotificationBell", () => {
|
||||
test("the bell shows the unread count and goes to notifications", async () => {
|
||||
test("the bell shows unread notifications and goes to them", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithContexts(<NotificationBell />, {
|
||||
ws: { unreadNotificationCount: 120 },
|
||||
@@ -15,7 +15,7 @@ describe("NotificationBell", () => {
|
||||
const bell = screen.getByRole("button", {
|
||||
name: "Notifications (120 unread)",
|
||||
});
|
||||
expect(bell).toHaveTextContent("99+");
|
||||
expect(bell).toHaveClass("notification-bell--unread");
|
||||
await user.click(bell);
|
||||
expect(currentPath()).toBe("/notifications");
|
||||
});
|
||||
|
||||
@@ -45,6 +45,10 @@
|
||||
--radius-lg: 0;
|
||||
--radius-xl: 0;
|
||||
--radius-full: 0;
|
||||
/* Heavy, square-cut icon lines, like the borders. */
|
||||
--nav-icon-stroke: 2.5;
|
||||
--nav-icon-linecap: square;
|
||||
--nav-icon-linejoin: miter;
|
||||
font-family: var(--font-body);
|
||||
font-weight: 500;
|
||||
}
|
||||
@@ -102,7 +106,6 @@
|
||||
[data-style="brutalist"] .audio-player-vol-btn,
|
||||
[data-style="brutalist"] .rich-content-thumbnail-btn,
|
||||
[data-style="brutalist"] .user-menu-trigger,
|
||||
[data-style="brutalist"] .nav-search-btn,
|
||||
[data-style="brutalist"] .auth-link-btn,
|
||||
[data-style="brutalist"] .form-cancel,
|
||||
[data-style="brutalist"] .form-label-action {
|
||||
@@ -112,8 +115,6 @@
|
||||
|
||||
[data-style="brutalist"] .user-menu-trigger:hover:not(:disabled),
|
||||
[data-style="brutalist"] .user-menu-trigger:active,
|
||||
[data-style="brutalist"] .nav-search-btn:hover:not(:disabled),
|
||||
[data-style="brutalist"] .nav-search-btn:active,
|
||||
[data-style="brutalist"] .auth-link-btn:hover:not(:disabled),
|
||||
[data-style="brutalist"] .auth-link-btn:active,
|
||||
[data-style="brutalist"] .form-cancel:hover:not(:disabled),
|
||||
|
||||
@@ -101,6 +101,9 @@
|
||||
"Marker Felt", cursive;
|
||||
font-family: var(--font-body);
|
||||
font-weight: 400;
|
||||
/* Fat icons, soaked in the accent. */
|
||||
--nav-icon-stroke: 2.25;
|
||||
--nav-icon-tint-strength: 55%;
|
||||
}
|
||||
|
||||
/* ── Tiled wallpaper, fixed so content scrolls over it ────────────── */
|
||||
@@ -132,7 +135,6 @@
|
||||
[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;
|
||||
@@ -215,7 +217,6 @@
|
||||
[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,
|
||||
|
||||
@@ -69,6 +69,8 @@
|
||||
smeared faux bold or spindly faux small caps give the typeface away.
|
||||
Hierarchy comes from size, italics and capitals instead. */
|
||||
font-synthesis: none;
|
||||
/* Hairline icons, drawn in ink like the rules. */
|
||||
--nav-icon-stroke: 1.25;
|
||||
}
|
||||
|
||||
/* This theme squares everything off — opt the FAB into a square too (it keeps
|
||||
@@ -472,13 +474,9 @@
|
||||
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"] .page-title-icon,
|
||||
[data-style="nyt"] .chat-button-icon,
|
||||
[data-style="nyt"] .dump-card-preview-icon,
|
||||
[data-style="nyt"] .playlist-card-icon,
|
||||
[data-style="nyt"] .rich-content-compact-icon,
|
||||
|
||||
@@ -138,6 +138,12 @@
|
||||
--tab-active-fg: var(--color-on-accent);
|
||||
--tab-text-transform: uppercase;
|
||||
--tab-tracking: 0.04em;
|
||||
/* The header's line icons (NavIcons.tsx): their weight and ends, and how
|
||||
much of the accent washes their bodies at rest. */
|
||||
--nav-icon-stroke: 1.75;
|
||||
--nav-icon-linecap: round;
|
||||
--nav-icon-linejoin: round;
|
||||
--nav-icon-tint-strength: 22%;
|
||||
--btn-primary-shadow: 0 2px 8px
|
||||
color-mix(in srgb, var(--color-accent) 40%, transparent);
|
||||
--btn-primary-shadow-hover: 0 4px 14px
|
||||
|
||||
Reference in New Issue
Block a user