v3: added site-wide categories, added admin category management, various visual fixes
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 2m52s

This commit is contained in:
khannurien
2026-06-28 16:23:44 +00:00
parent c8c7b05c25
commit fae25f3e6c
44 changed files with 2144 additions and 399 deletions

View File

@@ -1570,12 +1570,36 @@ body.has-fab .page-content {
}
/* ── Profile (own) page ── */
.profile-username-row {
display: flex;
align-items: center;
gap: 0.5rem;
flex-wrap: wrap;
}
.profile-username {
margin: 0;
font-size: 1.25rem;
font-weight: 600;
}
/* Role badge — elevated roles only (see RoleChip) */
.profile-role-chip {
padding: 0.1rem 0.55rem;
border-radius: 999px;
background: var(--color-accent);
color: var(--color-on-accent);
font-size: 0.72rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.04em;
white-space: nowrap;
}
.profile-role-chip--moderator {
background: var(--color-success);
}
.profile-header .btn-border {
margin-top: 0.5rem;
}
@@ -3840,30 +3864,46 @@ body.has-fab .page-content {
padding-bottom: 0;
}
/* Child list: indented, left edge aligns with parent avatar center */
/* Child list: indented so connector lines have room on the left.
The vertical line sits at the list's left:0, centered under the parent
avatar (avatar center 12px - 1px half-line = 11px). */
.invite-tree-node > .invite-tree {
position: relative;
margin-left: 11px; /* center 2px line on 24px avatar: 12px - 1px */
margin-left: 11px;
padding-left: calc(12px + 0.5rem);
}
/* Vertical connector: starts below parent avatar, stops at last child avatar center */
.invite-tree-node > .invite-tree::before {
content: "";
position: absolute;
left: 0;
top: 0;
bottom: 12px;
width: 2px;
background: var(--color-border-subtle);
/* Connectors are owned per child node (not by the list) so they always anchor
to that child's own avatar center, regardless of how deep its subtree is.
--avatar-center = node top padding (0.2rem) + half the 24px avatar (12px). */
.invite-tree-node > .invite-tree > .invite-tree-node {
--avatar-center: calc(0.2rem + 12px);
}
/* Horizontal connector: from vertical line to each child's avatar left edge */
/* Vertical segment: drops from the top of the child node to its avatar center,
joining the line coming down from the parent / previous sibling. */
.invite-tree-node > .invite-tree > .invite-tree-node::before {
content: "";
position: absolute;
left: calc(-12px - 0.5rem);
top: calc(0.2rem + 11px); /* centered on 24px avatar */
top: 0;
height: var(--avatar-center);
width: 2px;
background: var(--color-border-subtle);
}
/* Non-last children extend the vertical segment through their full height
(including any subtree) so it reaches the next sibling. */
.invite-tree-node > .invite-tree > .invite-tree-node:not(:last-child)::before {
height: auto;
bottom: 0;
}
/* Horizontal connector: from the vertical line to the child's avatar. */
.invite-tree-node > .invite-tree > .invite-tree-node::after {
content: "";
position: absolute;
left: calc(-12px - 0.5rem);
top: calc(var(--avatar-center) - 1px); /* straddle center with the 2px bar */
width: calc(12px + 0.5rem);
height: 2px;
background: var(--color-border-subtle);
@@ -4836,3 +4876,325 @@ body.has-fab .page-content {
font-weight: 600;
opacity: 0.7;
}
/* ── Categories ─────────────────────────────────────────────────────────── */
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
border: 0;
}
/* Scope switcher — the leading item of the feed-sort tab row. The trigger
reuses .feed-sort-btn so it shares the tabs' exact pill metrics; a thin
divider sets it apart from the sort tabs that follow. */
.category-switcher {
position: relative;
display: inline-flex;
align-items: center;
gap: 0.4rem;
flex-shrink: 0;
}
.category-switcher-btn {
display: inline-flex;
align-items: center;
gap: 0.4rem;
}
.category-switcher-btn:hover,
.category-switcher-btn--open {
border-color: var(--color-accent);
color: var(--color-text);
}
.category-switcher-label {
max-width: 10rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Caret tracks the open state. */
.category-switcher-caret {
width: 0;
height: 0;
border-left: 0.26rem solid transparent;
border-right: 0.26rem solid transparent;
border-top: 0.3rem solid currentColor;
transition: transform 0.15s ease;
}
.category-switcher-btn--open .category-switcher-caret {
transform: rotate(180deg);
}
/* Vertical rule separating the scope selector from the sort tabs. */
.feed-sort-sep {
width: 2px;
height: 1.05rem;
background: var(--color-border);
border-radius: 1px;
flex-shrink: 0;
}
/* Themed popover (fixed-positioned so the row's overflow can't clip it). */
.category-switcher-menu {
position: fixed;
z-index: 200;
min-width: 11rem;
max-height: 60vh;
overflow-y: auto;
margin: 0;
padding: 0.35rem;
list-style: none;
display: flex;
flex-direction: column;
gap: 0.15rem;
background: var(--color-surface);
border: 2px solid var(--color-border-subtle);
border-radius: 10px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
}
.category-switcher-option {
display: block;
width: 100%;
text-align: left;
padding: 0.45rem 0.75rem;
border: none;
border-radius: 7px;
background: transparent;
color: var(--color-text);
font-size: 0.9rem;
font-weight: 600;
white-space: nowrap;
cursor: pointer;
transition: background 0.12s;
}
.category-switcher-option:hover {
background: var(--color-header-user-bg-hover);
}
.category-switcher-option--active {
background: var(--color-accent);
color: var(--color-on-accent);
}
/* Toggle chips used in the dump create/edit forms */
.category-select {
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
}
.category-chip-toggle {
padding: 0.25rem 0.7rem;
border: 1px solid var(--color-border);
border-radius: 999px;
background: var(--color-bg);
color: var(--color-text-secondary);
font-size: 0.85rem;
cursor: pointer;
}
.category-chip-toggle:hover {
border-color: var(--color-accent);
}
.category-chip-toggle--active {
background: var(--color-accent);
border-color: var(--color-accent);
color: var(--color-on-accent);
}
/* Read-only category chips on dump cards */
.dump-card-categories {
display: flex;
flex-wrap: wrap;
gap: 0.3rem;
margin-top: 0.35rem;
}
.dump-card-category-chip {
padding: 0.1rem 0.5rem;
border-radius: 999px;
background: var(--color-header-user-bg);
color: var(--color-text-secondary);
text-decoration: none;
font-size: 0.75rem;
}
.dump-card-category-chip:hover {
background: var(--color-accent);
color: var(--color-on-accent);
}
/* Admin category management — chips that expand into an inline editor */
.category-chips {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: 0.5rem;
}
.category-chip {
display: inline-flex;
align-items: center;
padding: 0.35rem 0.85rem;
border: 1px solid var(--color-border);
border-radius: 999px;
background: var(--color-bg);
color: var(--color-text);
font-size: 0.9rem;
font-family: inherit;
line-height: 1.4;
cursor: pointer;
transition: border-color 0.15s, background 0.15s, color 0.15s, transform 0.1s;
}
.category-chip:hover:not(:disabled) {
border-color: var(--color-accent);
color: var(--color-accent);
}
.category-chip:active:not(:disabled) {
transform: scale(0.96);
}
.category-chip:disabled {
opacity: 0.4;
cursor: default;
}
/* Inherits the base chip's font-size/padding so its height matches exactly;
only the glyph weight/color set it apart. */
.category-chip--add {
font-weight: 700;
color: var(--color-text-secondary);
}
.category-chip--add:hover:not(:disabled) {
background: var(--color-accent);
border-color: var(--color-accent);
color: var(--color-on-accent);
}
/* Inline editor: the expanded form of a chip */
.category-editor-wrap {
display: flex;
flex-direction: column;
gap: 0.35rem;
min-width: 0;
}
.category-editor {
display: inline-flex;
align-items: center;
gap: 0.3rem;
padding: 0.2rem 0.3rem 0.2rem 0.7rem;
border: 1px solid var(--color-accent);
border-radius: 999px;
background: var(--color-bg);
box-shadow: 0 0 0 3px
color-mix(in srgb, var(--color-accent) 16%, transparent);
transform-origin: left center;
animation: category-editor-in 0.18s ease-out;
}
@keyframes category-editor-in {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
.category-editor-input {
min-width: 0;
border: none;
background: transparent;
color: var(--color-text);
font-size: 0.9rem;
font-family: inherit;
padding: 0.2rem 0;
outline: none;
}
.category-editor-input--name {
width: 8.5rem;
font-weight: 600;
}
.category-editor-input--slug {
width: 6.5rem;
font-family: var(--font-mono, monospace);
color: var(--color-text-secondary);
}
.category-editor-sep {
color: var(--color-text-secondary);
opacity: 0.5;
}
.category-editor-actions {
display: inline-flex;
align-items: center;
gap: 0.1rem;
}
.category-editor-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.75rem;
height: 1.75rem;
padding: 0;
border: none;
border-radius: 999px;
background: transparent;
color: var(--color-text-secondary);
font-size: 0.8rem;
line-height: 1;
cursor: pointer;
transition: background 0.15s, color 0.15s, filter 0.15s;
}
.category-editor-btn:hover:not(:disabled) {
background: var(--color-header-user-bg);
}
.category-editor-btn:disabled {
opacity: 0.4;
cursor: default;
}
.category-editor-btn--save {
background: var(--color-accent);
color: var(--color-on-accent);
}
.category-editor-btn--save:hover:not(:disabled) {
filter: brightness(1.1);
}
.category-editor-btn--save:disabled {
background: var(--color-border);
color: var(--color-text-secondary);
opacity: 1;
}
.category-editor-btn--delete:hover:not(:disabled) {
background: color-mix(in srgb, var(--color-danger, #c0392b) 18%, transparent);
color: var(--color-danger, #c0392b);
}