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
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 2m52s
This commit is contained in:
390
src/App.css
390
src/App.css
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user