small ui quirks
This commit is contained in:
@@ -42,7 +42,7 @@ Havenllo (Haven + Trello) is a lightweight, single-user, no-authentication kanba
|
|||||||
- `web/src/markdown.ts`: safe CommonMark renderer using `commonmark` plus DOMPurify, and plain-text extraction for card summaries.
|
- `web/src/markdown.ts`: safe CommonMark renderer using `commonmark` plus DOMPurify, and plain-text extraction for card summaries.
|
||||||
- `web/src/App.tsx`: board loading, optimistic mutations, selected-card state, and component wiring.
|
- `web/src/App.tsx`: board loading, optimistic mutations, selected-card state, and component wiring.
|
||||||
- `web/src/components/Header.tsx`: Havenllo operations-board brand, local-ready state, refresh, and list-management toggle.
|
- `web/src/components/Header.tsx`: Havenllo operations-board brand, local-ready state, refresh, and list-management toggle.
|
||||||
- `web/src/components/BoardCanvas.tsx`: control-room board intro, live/edit badge, derived summary metrics, command-deck guidance, centered-or-scrollable rail, list-management add control, and board-level Pointer Events drag lifecycle. It detects whole-column targets, insertion indices, pointer cancellation, and Escape cancellation.
|
- `web/src/components/BoardCanvas.tsx`: centered-or-scrollable workflow rail, list-management add control, task count, and board-level Pointer Events drag lifecycle. It detects whole-column targets, insertion indices, pointer cancellation, and Escape cancellation.
|
||||||
- `web/src/components/ListColumn.tsx`: stage marker/kicker, column header/count, drop target and insertion marker, inline card creation, and management-only rename/delete controls.
|
- `web/src/components/ListColumn.tsx`: stage marker/kicker, column header/count, drop target and insertion marker, inline card creation, and management-only rename/delete controls.
|
||||||
- `web/src/components/CardItem.tsx`: focusable card tile with a dedicated drag grip, current-list status chip, plain-text Markdown excerpt, and open-card affordance. Clicking outside the grip opens details.
|
- `web/src/components/CardItem.tsx`: focusable card tile with a dedicated drag grip, current-list status chip, plain-text Markdown excerpt, and open-card affordance. Clicking outside the grip opens details.
|
||||||
- `web/src/components/CardDetailModal.tsx`: accessible card dialog with title editing, current-list status, native move-to-list selector, CommonMark Write/Preview description editor, timestamps, and delete confirmation.
|
- `web/src/components/CardDetailModal.tsx`: accessible card dialog with title editing, current-list status, native move-to-list selector, CommonMark Write/Preview description editor, timestamps, and delete confirmation.
|
||||||
@@ -106,7 +106,7 @@ Errors use `{ "error": { "code", "message" } }`. Success uses `200`, `201`, or `
|
|||||||
- Start a pointer drag from a card's grip. The entire destination column accepts it; the nearest card midpoint controls the insertion marker and fractional position. Mouse and touch both work. Pointer cancel or Escape aborts without a request. Drops update optimistically and roll back with a toast on failure.
|
- Start a pointer drag from a card's grip. The entire destination column accepts it; the nearest card midpoint controls the insertion marker and fractional position. Mouse and touch both work. Pointer cancel or Escape aborts without a request. Drops update optimistically and roll back with a toast on failure.
|
||||||
- The card dialog also has a native Move to list select, which appends the card through the existing `PATCH /api/cards/{id}` behavior and provides a keyboard-friendly alternative.
|
- The card dialog also has a native Move to list select, which appends the card through the existing `PATCH /api/cards/{id}` behavior and provides a keyboard-friendly alternative.
|
||||||
- Status is derived solely from the card's current list. Tiles show the list-name chip; the dialog shows the same read-only status. `Done` has no special backend meaning.
|
- Status is derived solely from the card's current list. Tiles show the list-name chip; the dialog shows the same read-only status. `Done` has no special backend meaning.
|
||||||
- Descriptions remain raw strings in the API/database. The dialog writes CommonMark source and previews safe, sanitized CommonMark; card tiles show a compact plain-text rendering.
|
- Descriptions remain raw strings in the API/database. The dialog opens in a safe, sanitized CommonMark Preview tab and switches to Write mode for editing; card tiles show a compact plain-text rendering.
|
||||||
- The Edit toggle exposes list add/rename/delete controls. Card add, edit, delete, movement, and detail viewing remain available outside list-management mode.
|
- The Edit toggle exposes list add/rename/delete controls. Card add, edit, delete, movement, and detail viewing remain available outside list-management mode.
|
||||||
- Short rails are horizontally centered on wide screens. Overflowing rails begin at the first column and use responsive horizontal scrolling and mobile scroll snapping.
|
- Short rails are horizontally centered on wide screens. Overflowing rails begin at the first column and use responsive horizontal scrolling and mobile scroll snapping.
|
||||||
|
|
||||||
|
|||||||
@@ -44,8 +44,6 @@ export function BoardCanvas(props: BoardCanvasProps) {
|
|||||||
const dragRef = useRef<ActiveCardDrag | null>(null);
|
const dragRef = useRef<ActiveCardDrag | null>(null);
|
||||||
const cleanupDragRef = useRef<() => void>(() => undefined);
|
const cleanupDragRef = useRef<() => void>(() => undefined);
|
||||||
const totalCards = props.lists.reduce((total, list) => total + list.cards.length, 0);
|
const totalCards = props.lists.reduce((total, list) => total + list.cards.length, 0);
|
||||||
const activeCards = props.lists[0]?.cards.length ?? 0;
|
|
||||||
const filledLists = props.lists.filter((list) => list.cards.length > 0).length;
|
|
||||||
|
|
||||||
useEffect(() => () => cleanupDragRef.current(), []);
|
useEffect(() => () => cleanupDragRef.current(), []);
|
||||||
|
|
||||||
@@ -135,40 +133,6 @@ export function BoardCanvas(props: BoardCanvasProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section class="board-area" aria-label="Haven task board">
|
<section class="board-area" aria-label="Haven task board">
|
||||||
<div class="board-intro">
|
|
||||||
<div class="board-title-block">
|
|
||||||
<div class="eyebrow-row">
|
|
||||||
<p class="eyebrow">Workspace / Haven homelab</p>
|
|
||||||
<span class="board-live-badge"><i aria-hidden="true"></i>{props.managingLists ? "Edit mode" : "Live board"}</span>
|
|
||||||
</div>
|
|
||||||
<h1>Haven <span>control room</span></h1>
|
|
||||||
<p class="board-description">A clear runway for the work that keeps your little corner of the internet humming.</p>
|
|
||||||
</div>
|
|
||||||
<div class="board-command-card">
|
|
||||||
<span class="command-card-label">Command deck</span>
|
|
||||||
<p class="board-hint">
|
|
||||||
{props.managingLists ? "Shape the workflow: rename, remove, or add stages." : "Grab a handle to move a task, or open one for the full story."}
|
|
||||||
</p>
|
|
||||||
<span class="command-card-key"><kbd>SPACE</kbd> to open a card</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="board-summary" aria-label="Board summary">
|
|
||||||
<div class="board-metric board-metric-primary">
|
|
||||||
<span class="metric-label">Tasks in orbit</span>
|
|
||||||
<strong>{totalCards}</strong>
|
|
||||||
<span class="metric-note">across the board</span>
|
|
||||||
</div>
|
|
||||||
<div class="board-metric">
|
|
||||||
<span class="metric-label">Workflow stages</span>
|
|
||||||
<strong>{props.lists.length}</strong>
|
|
||||||
<span class="metric-note">{filledLists} with activity</span>
|
|
||||||
</div>
|
|
||||||
<div class="board-metric">
|
|
||||||
<span class="metric-label">Front of queue</span>
|
|
||||||
<strong>{activeCards}</strong>
|
|
||||||
<span class="metric-note">in the first stage</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{props.lists.length ? (
|
{props.lists.length ? (
|
||||||
<div class={"board-rail " + (props.managingLists ? "is-managing" : "")}>
|
<div class={"board-rail " + (props.managingLists ? "is-managing" : "")}>
|
||||||
<div class="rail-header"><span>Workflow rail</span><i aria-hidden="true"></i><span>{totalCards} {totalCards === 1 ? "task" : "tasks"} in flight</span></div>
|
<div class="rail-header"><span>Workflow rail</span><i aria-hidden="true"></i><span>{totalCards} {totalCards === 1 ? "task" : "tasks"} in flight</span></div>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ function formatTimestamp(value: string): string {
|
|||||||
export function CardDetailModal({ card, listName, lists, onClose, onEdit, onDelete }: CardDetailModalProps) {
|
export function CardDetailModal({ card, listName, lists, onClose, onEdit, onDelete }: CardDetailModalProps) {
|
||||||
const [title, setTitle] = useState(card.title);
|
const [title, setTitle] = useState(card.title);
|
||||||
const [description, setDescription] = useState(card.description);
|
const [description, setDescription] = useState(card.description);
|
||||||
const [descriptionMode, setDescriptionMode] = useState<"write" | "preview">("write");
|
const [descriptionMode, setDescriptionMode] = useState<"write" | "preview">("preview");
|
||||||
const [savingTitle, setSavingTitle] = useState(false);
|
const [savingTitle, setSavingTitle] = useState(false);
|
||||||
const [savingDescription, setSavingDescription] = useState(false);
|
const [savingDescription, setSavingDescription] = useState(false);
|
||||||
const [savingList, setSavingList] = useState(false);
|
const [savingList, setSavingList] = useState(false);
|
||||||
@@ -41,7 +41,7 @@ export function CardDetailModal({ card, listName, lists, onClose, onEdit, onDele
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTitle(card.title);
|
setTitle(card.title);
|
||||||
setDescription(card.description);
|
setDescription(card.description);
|
||||||
setDescriptionMode("write");
|
setDescriptionMode("preview");
|
||||||
}, [card.id]);
|
}, [card.id]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -195,11 +195,10 @@ export function CardDetailModal({ card, listName, lists, onClose, onEdit, onDele
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="detail-description" aria-labelledby="detail-description-heading">
|
<section class="detail-description" aria-label="Card description">
|
||||||
<div class="detail-section-heading">
|
<div class="detail-section-heading">
|
||||||
<div>
|
<div>
|
||||||
<p class="eyebrow">Details</p>
|
<p class="eyebrow">Details</p>
|
||||||
<h2 id="detail-description-heading">Description</h2>
|
|
||||||
</div>
|
</div>
|
||||||
{descriptionChanged ? <span class="unsaved-chip">Unsaved changes</span> : null}
|
{descriptionChanged ? <span class="unsaved-chip">Unsaved changes</span> : null}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -157,24 +157,6 @@ button:disabled { cursor: not-allowed; opacity: 0.55; }
|
|||||||
|
|
||||||
.board-area { padding: clamp(26px, 4.2vw, 58px) clamp(18px, 4vw, 56px) 34px; }
|
.board-area { padding: clamp(26px, 4.2vw, 58px) clamp(18px, 4vw, 56px) 34px; }
|
||||||
|
|
||||||
.board-intro {
|
|
||||||
max-width: 1530px;
|
|
||||||
margin: 0 auto 26px;
|
|
||||||
display: flex;
|
|
||||||
align-items: end;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.board-intro h1 {
|
|
||||||
margin: 5px 0 0;
|
|
||||||
font-size: clamp(1.72rem, 3vw, 2.45rem);
|
|
||||||
letter-spacing: -0.055em;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.board-hint { max-width: 335px; margin: 0 2px 2px 0; color: var(--muted); font-size: 0.89rem; line-height: 1.5; text-align: right; }
|
|
||||||
|
|
||||||
.board-rail {
|
.board-rail {
|
||||||
width: min(100%, 1530px);
|
width: min(100%, 1530px);
|
||||||
min-height: 320px;
|
min-height: 320px;
|
||||||
@@ -497,24 +479,23 @@ textarea { min-height: 96px; resize: vertical; }
|
|||||||
.detail-title-input:focus { padding-right: 8px; padding-left: 8px; border-color: var(--teal); background: rgba(7, 14, 27, 0.48); }
|
.detail-title-input:focus { padding-right: 8px; padding-left: 8px; border-color: var(--teal); background: rgba(7, 14, 27, 0.48); }
|
||||||
|
|
||||||
.detail-properties {
|
.detail-properties {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
align-items: stretch;
|
||||||
|
flex-wrap: nowrap;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin: 18px 0 26px 27px;
|
margin: 18px 0 26px 27px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-property { min-height: 66px; display: grid; align-content: center; gap: 5px; padding: 10px 12px; border: 1px solid var(--border); border-radius: 12px; background: rgba(5, 12, 26, 0.22); }
|
.detail-property { min-width: 0; min-height: 66px; flex: 1 1 0; display: grid; align-content: center; gap: 5px; padding: 10px 12px; border: 1px solid var(--border); border-radius: 12px; background: rgba(5, 12, 26, 0.22); }
|
||||||
.detail-property > span { color: var(--faint); font-size: 0.69rem; font-weight: 750; letter-spacing: 0.08em; text-transform: uppercase; }
|
.detail-property > span { color: var(--faint); font-size: 0.69rem; font-weight: 750; letter-spacing: 0.08em; text-transform: uppercase; }
|
||||||
.detail-property strong { overflow: hidden; color: var(--ink); font-size: 0.87rem; text-overflow: ellipsis; white-space: nowrap; }
|
.detail-property strong { overflow: hidden; color: var(--ink); font-size: 0.87rem; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
|
||||||
.detail-status { display: flex; align-content: initial; align-items: center; justify-content: space-between; gap: 8px; }
|
.detail-status { display: flex; align-content: initial; align-items: center; justify-content: space-between; gap: 8px; }
|
||||||
.detail-status .status-pill { max-width: 190px; color: var(--teal); border-color: rgba(101, 218, 203, 0.42); background: var(--teal-soft); }
|
.detail-status .status-pill { max-width: 190px; color: var(--teal); border-color: rgba(101, 218, 203, 0.42); background: var(--teal-soft); }
|
||||||
.detail-move { grid-column: 1 / -1; }
|
|
||||||
.detail-move select { min-height: 38px; padding-top: 7px; padding-bottom: 7px; font-size: 0.84rem; }
|
.detail-move select { min-height: 38px; padding-top: 7px; padding-bottom: 7px; font-size: 0.84rem; }
|
||||||
|
|
||||||
.detail-description { margin-left: 27px; }
|
.detail-description { margin-left: 27px; }
|
||||||
.detail-section-heading { justify-content: space-between; gap: 12px; margin-bottom: 10px; }
|
.detail-section-heading { justify-content: space-between; gap: 12px; margin-bottom: 10px; }
|
||||||
.detail-section-heading h2 { margin: 3px 0 0; color: var(--ink); font-size: 1.03rem; letter-spacing: -0.02em; }
|
|
||||||
|
|
||||||
.unsaved-chip { padding: 4px 8px; border: 1px solid rgba(145, 164, 255, 0.34); border-radius: 999px; color: #cbd4ff; font-size: 0.69rem; font-weight: 720; background: rgba(145, 164, 255, 0.11); }
|
.unsaved-chip { padding: 4px 8px; border: 1px solid rgba(145, 164, 255, 0.34); border-radius: 999px; color: #cbd4ff; font-size: 0.69rem; font-weight: 720; background: rgba(145, 164, 255, 0.11); }
|
||||||
.detail-description textarea { min-height: 150px; }
|
.detail-description textarea { min-height: 150px; }
|
||||||
@@ -608,8 +589,6 @@ textarea { min-height: 96px; resize: vertical; }
|
|||||||
.topbar-board { display: none; }
|
.topbar-board { display: none; }
|
||||||
.header-edit-button { min-width: 44px; padding: 8px 10px; }
|
.header-edit-button { min-width: 44px; padding: 8px 10px; }
|
||||||
.board-area { padding: 25px 16px 26px; overflow: hidden; }
|
.board-area { padding: 25px 16px 26px; overflow: hidden; }
|
||||||
.board-intro { align-items: flex-start; margin-bottom: 19px; }
|
|
||||||
.board-hint { display: none; }
|
|
||||||
.board-rail { width: calc(100% + 16px); margin-right: -16px; padding-right: 16px; scroll-snap-type: x proximity; }
|
.board-rail { width: calc(100% + 16px); margin-right: -16px; padding-right: 16px; scroll-snap-type: x proximity; }
|
||||||
.board-list-group { justify-content: flex-start; }
|
.board-list-group { justify-content: flex-start; }
|
||||||
.list-column, .add-list-column { scroll-snap-align: start; }
|
.list-column, .add-list-column { scroll-snap-align: start; }
|
||||||
@@ -617,7 +596,7 @@ textarea { min-height: 96px; resize: vertical; }
|
|||||||
.detail-backdrop, .dialog-backdrop { padding: 10px; }
|
.detail-backdrop, .dialog-backdrop { padding: 10px; }
|
||||||
.detail-header { padding: 13px 13px 0; }
|
.detail-header { padding: 13px 13px 0; }
|
||||||
.detail-content { padding: 6px 18px 20px; }
|
.detail-content { padding: 6px 18px 20px; }
|
||||||
.detail-properties { grid-template-columns: 1fr; margin-left: 0; }
|
.detail-properties { margin-left: 0; }
|
||||||
.detail-description, .detail-footer { margin-left: 0; }
|
.detail-description, .detail-footer { margin-left: 0; }
|
||||||
.detail-footer { align-items: flex-start; flex-direction: column; gap: 12px; }
|
.detail-footer { align-items: flex-start; flex-direction: column; gap: 12px; }
|
||||||
.detail-delete { padding-left: 0; }
|
.detail-delete { padding-left: 0; }
|
||||||
@@ -723,7 +702,7 @@ button:focus-visible, input:focus-visible, textarea:focus-visible, select:focus-
|
|||||||
.topbar-board { gap: 3px; padding-left: 22px; border-left-color: rgba(198, 216, 203, 0.2); }
|
.topbar-board { gap: 3px; padding-left: 22px; border-left-color: rgba(198, 216, 203, 0.2); }
|
||||||
.topbar-board strong { display: flex; align-items: center; gap: 8px; font-size: 0.88rem; }
|
.topbar-board strong { display: flex; align-items: center; gap: 8px; font-size: 0.88rem; }
|
||||||
.local-state { display: inline-flex; align-items: center; gap: 5px; color: var(--faint); font-size: 0.63rem; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; }
|
.local-state { display: inline-flex; align-items: center; gap: 5px; color: var(--faint); font-size: 0.63rem; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; }
|
||||||
.local-state i, .status-dot, .board-live-badge i { width: 6px; height: 6px; display: inline-block; border-radius: 50%; background: var(--signal); box-shadow: 0 0 0 4px rgba(215, 255, 99, 0.1), 0 0 13px rgba(215, 255, 99, 0.9); }
|
.local-state i, .status-dot { width: 6px; height: 6px; display: inline-block; border-radius: 50%; background: var(--signal); box-shadow: 0 0 0 4px rgba(215, 255, 99, 0.1), 0 0 13px rgba(215, 255, 99, 0.9); }
|
||||||
.topbar-signal { display: inline-flex; align-items: center; gap: 9px; margin-left: auto; color: var(--faint); font-size: 0.68rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; }
|
.topbar-signal { display: inline-flex; align-items: center; gap: 9px; margin-left: auto; color: var(--faint); font-size: 0.68rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; }
|
||||||
.topbar-signal .status-dot { width: 7px; height: 7px; }
|
.topbar-signal .status-dot { width: 7px; height: 7px; }
|
||||||
.topbar-actions { margin-left: 2px; }
|
.topbar-actions { margin-left: 2px; }
|
||||||
@@ -737,30 +716,6 @@ button:focus-visible, input:focus-visible, textarea:focus-visible, select:focus-
|
|||||||
.icon-button:hover:not(:disabled) { color: var(--signal); border-color: rgba(215, 255, 99, 0.42); background: var(--signal-soft); transform: translateY(-1px); }
|
.icon-button:hover:not(:disabled) { color: var(--signal); border-color: rgba(215, 255, 99, 0.42); background: var(--signal-soft); transform: translateY(-1px); }
|
||||||
|
|
||||||
.board-area { max-width: 1660px; padding: 56px clamp(20px, 4.5vw, 72px) 76px; }
|
.board-area { max-width: 1660px; padding: 56px clamp(20px, 4.5vw, 72px) 76px; }
|
||||||
.board-intro { display: grid; grid-template-columns: minmax(0, 1fr) minmax(245px, 335px); align-items: end; gap: 36px; margin-bottom: 24px; }
|
|
||||||
.board-title-block { min-width: 0; }
|
|
||||||
.eyebrow-row { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; margin-bottom: 14px; }
|
|
||||||
.board-live-badge { display: inline-flex; align-items: center; gap: 7px; min-height: 25px; padding: 4px 9px; border: 1px solid rgba(215, 255, 99, 0.23); border-radius: 999px; color: var(--signal); font-size: 0.6rem; font-weight: 850; letter-spacing: 0.11em; text-transform: uppercase; background: rgba(215, 255, 99, 0.06); }
|
|
||||||
.board-live-badge i { width: 5px; height: 5px; }
|
|
||||||
.board-intro h1 { margin: 0; color: var(--ink); font-size: clamp(2.5rem, 5.3vw, 5.35rem); font-weight: 850; letter-spacing: -0.075em; line-height: 0.94; }
|
|
||||||
.board-intro h1 span { color: var(--muted); font-weight: 450; }
|
|
||||||
.board-description { max-width: 590px; margin: 20px 0 0; color: var(--muted); font-size: 1rem; line-height: 1.55; }
|
|
||||||
.board-command-card { position: relative; padding: 17px 18px 16px; border: 1px solid rgba(198, 216, 203, 0.15); border-radius: 16px 16px 4px 16px; background: linear-gradient(145deg, rgba(23, 30, 40, 0.94), rgba(12, 16, 23, 0.87)); box-shadow: var(--shadow); }
|
|
||||||
.board-command-card::before { position: absolute; top: 0; right: 20px; left: 20px; height: 1px; background: linear-gradient(90deg, transparent, var(--signal), transparent); content: ""; opacity: 0.8; }
|
|
||||||
.command-card-label { display: block; color: var(--signal); font-size: 0.61rem; font-weight: 850; letter-spacing: 0.16em; text-transform: uppercase; }
|
|
||||||
.board-hint { max-width: none; margin: 10px 0 14px; color: var(--ink); font-size: 0.82rem; line-height: 1.5; text-align: left; }
|
|
||||||
.command-card-key { color: var(--faint); font-size: 0.66rem; }
|
|
||||||
kbd { padding: 3px 5px; border: 1px solid rgba(198, 216, 203, 0.18); border-bottom-color: rgba(198, 216, 203, 0.35); border-radius: 4px; color: var(--muted); font-size: 0.58rem; font-weight: 850; letter-spacing: 0.08em; background: rgba(0, 0, 0, 0.25); }
|
|
||||||
|
|
||||||
.board-summary { display: grid; grid-template-columns: 1.3fr 1fr 1fr; gap: 10px; margin-bottom: 38px; }
|
|
||||||
.board-metric { position: relative; min-height: 93px; display: grid; align-content: center; gap: 3px; padding: 14px 17px; overflow: hidden; border: 1px solid rgba(198, 216, 203, 0.12); border-radius: 15px; background: rgba(16, 21, 29, 0.63); }
|
|
||||||
.board-metric::after { position: absolute; right: -18px; bottom: -32px; width: 95px; height: 95px; border: 1px solid rgba(198, 216, 203, 0.08); border-radius: 50%; content: ""; }
|
|
||||||
.board-metric-primary { border-color: rgba(215, 255, 99, 0.28); background: linear-gradient(135deg, rgba(215, 255, 99, 0.11), rgba(16, 21, 29, 0.66) 56%); }
|
|
||||||
.metric-label { color: var(--muted); font-size: 0.63rem; font-weight: 850; letter-spacing: 0.1em; text-transform: uppercase; }
|
|
||||||
.board-metric strong { color: var(--ink); font-size: 2rem; font-weight: 760; letter-spacing: -0.06em; line-height: 1; }
|
|
||||||
.board-metric-primary strong { color: var(--signal); }
|
|
||||||
.metric-note { color: var(--faint); font-size: 0.66rem; }
|
|
||||||
|
|
||||||
.board-rail { width: min(100%, 1560px); min-height: 320px; padding: 0 2px 34px; scrollbar-color: rgba(215, 255, 99, 0.3) transparent; }
|
.board-rail { width: min(100%, 1560px); min-height: 320px; padding: 0 2px 34px; scrollbar-color: rgba(215, 255, 99, 0.3) transparent; }
|
||||||
.rail-header { display: flex; align-items: center; gap: 11px; margin: 0 2px 13px; color: var(--faint); font-size: 0.62rem; font-weight: 850; letter-spacing: 0.14em; text-transform: uppercase; }
|
.rail-header { display: flex; align-items: center; gap: 11px; margin: 0 2px 13px; color: var(--faint); font-size: 0.62rem; font-weight: 850; letter-spacing: 0.14em; text-transform: uppercase; }
|
||||||
.rail-header i { height: 1px; flex: 1; background: linear-gradient(90deg, rgba(215, 255, 99, 0.34), transparent); }
|
.rail-header i { height: 1px; flex: 1; background: linear-gradient(90deg, rgba(215, 255, 99, 0.34), transparent); }
|
||||||
@@ -854,7 +809,6 @@ input:focus, textarea:focus, select:focus { border-color: var(--signal); backgro
|
|||||||
.detail-property > span { color: var(--faint); font-size: 0.61rem; letter-spacing: 0.13em; }
|
.detail-property > span { color: var(--faint); font-size: 0.61rem; letter-spacing: 0.13em; }
|
||||||
.detail-status .status-pill { color: var(--signal); border-color: rgba(215, 255, 99, 0.34); background: var(--signal-soft); }
|
.detail-status .status-pill { color: var(--signal); border-color: rgba(215, 255, 99, 0.34); background: var(--signal-soft); }
|
||||||
.detail-description { margin-top: 4px; }
|
.detail-description { margin-top: 4px; }
|
||||||
.detail-section-heading h2 { font-size: 1.12rem; }
|
|
||||||
.markdown-tabs button.is-active { color: var(--signal); border-color: rgba(215, 255, 99, 0.34); background: var(--signal-soft); }
|
.markdown-tabs button.is-active { color: var(--signal); border-color: rgba(215, 255, 99, 0.34); background: var(--signal-soft); }
|
||||||
.detail-description textarea, .markdown-preview { border-color: rgba(198, 216, 203, 0.2); border-radius: 13px; background: rgba(5, 8, 13, 0.45); }
|
.detail-description textarea, .markdown-preview { border-color: rgba(198, 216, 203, 0.2); border-radius: 13px; background: rgba(5, 8, 13, 0.45); }
|
||||||
.markdown-preview blockquote { border-left-color: var(--signal); }
|
.markdown-preview blockquote { border-left-color: var(--signal); }
|
||||||
@@ -868,8 +822,6 @@ input:focus, textarea:focus, select:focus { border-color: var(--signal); backgro
|
|||||||
|
|
||||||
@media (max-width: 850px) {
|
@media (max-width: 850px) {
|
||||||
.topbar-signal { display: none; }
|
.topbar-signal { display: none; }
|
||||||
.board-intro { grid-template-columns: minmax(0, 1fr) minmax(220px, 0.65fr); gap: 22px; }
|
|
||||||
.board-intro h1 { font-size: clamp(2.55rem, 7vw, 4.4rem); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 700px) {
|
@media (max-width: 700px) {
|
||||||
@@ -877,14 +829,6 @@ input:focus, textarea:focus, select:focus { border-color: var(--signal); backgro
|
|||||||
.topbar-board { display: none; }
|
.topbar-board { display: none; }
|
||||||
.topbar-actions { gap: 6px; }
|
.topbar-actions { gap: 6px; }
|
||||||
.board-area { padding: 38px 16px 48px; }
|
.board-area { padding: 38px 16px 48px; }
|
||||||
.board-intro { display: block; margin-bottom: 20px; }
|
|
||||||
.board-intro h1 { font-size: clamp(2.75rem, 13vw, 4.5rem); }
|
|
||||||
.board-description { margin-top: 15px; font-size: 0.9rem; }
|
|
||||||
.board-command-card { margin-top: 24px; }
|
|
||||||
.board-summary { grid-template-columns: repeat(3, minmax(126px, 1fr)); gap: 8px; margin: 0 -2px 28px; overflow-x: auto; }
|
|
||||||
.board-metric { min-height: 88px; padding: 12px; }
|
|
||||||
.board-metric strong { font-size: 1.7rem; }
|
|
||||||
.metric-label { font-size: 0.56rem; }
|
|
||||||
.board-rail { width: calc(100% + 16px); margin-right: -16px; padding-right: 16px; }
|
.board-rail { width: calc(100% + 16px); margin-right: -16px; padding-right: 16px; }
|
||||||
.rail-header { margin-right: 16px; }
|
.rail-header { margin-right: 16px; }
|
||||||
.board-list-group { justify-content: flex-start; }
|
.board-list-group { justify-content: flex-start; }
|
||||||
@@ -898,7 +842,6 @@ input:focus, textarea:focus, select:focus { border-color: var(--signal); backgro
|
|||||||
.brand-wordmark small { display: none; }
|
.brand-wordmark small { display: none; }
|
||||||
.header-edit-button { min-width: 40px; padding: 8px 10px; font-size: 0; }
|
.header-edit-button { min-width: 40px; padding: 8px 10px; font-size: 0; }
|
||||||
.header-edit-button span { font-size: 1rem; }
|
.header-edit-button span { font-size: 1rem; }
|
||||||
.board-summary { margin-right: -16px; padding-right: 16px; }
|
|
||||||
.list-column, .add-list-column { width: calc(100vw - 48px); flex-basis: calc(100vw - 48px); }
|
.list-column, .add-list-column { width: calc(100vw - 48px); flex-basis: calc(100vw - 48px); }
|
||||||
.detail-timestamps { display: grid; gap: 9px; }
|
.detail-timestamps { display: grid; gap: 9px; }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user