diff --git a/project-context.md b/project-context.md index c395191..5a0405f 100644 --- a/project-context.md +++ b/project-context.md @@ -41,14 +41,14 @@ Havenllo (Haven + Trello) is a lightweight, single-user, no-authentication kanba - `web/src/drag.ts`: `movePosition`, which calculates a fractional position from a target index after removing the dragged card. - `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/components/Header.tsx`: brand, board label, refresh, and list-management toggle. -- `web/src/components/BoardCanvas.tsx`: board intro, 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/ListColumn.tsx`: 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, and plain-text Markdown excerpt. Clicking outside the grip opens details. +- `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/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/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/ConfirmDialog.tsx`: accessible destructive-action confirmation. - `web/src/components/Toast.tsx`: auto-dismissing non-blocking feedback. -- `web/src/styles.css`: dark Haven theme, workflow-specific teal/indigo/gold accents, centered short rails, horizontal mobile scrolling, pointer drop feedback, Markdown typography, focus states, 44px touch targets, and reduced-motion support. +- `web/src/styles.css`: dark night-shift control-room theme with graphite surfaces, acid-lime/aurora accents, subtle grid/orbit decoration, stage-specific columns, centered short rails, horizontal mobile scrolling, pointer drop feedback, Markdown typography, focus states, 44px touch targets, and reduced-motion support. ### Embedding bridge diff --git a/web/index.html b/web/index.html index 0541824..6e7c3ae 100644 --- a/web/index.html +++ b/web/index.html @@ -3,7 +3,7 @@ - + Havenllo @@ -12,4 +12,3 @@ - diff --git a/web/src/components/BoardCanvas.tsx b/web/src/components/BoardCanvas.tsx index 641dad9..c956882 100644 --- a/web/src/components/BoardCanvas.tsx +++ b/web/src/components/BoardCanvas.tsx @@ -43,6 +43,9 @@ export function BoardCanvas(props: BoardCanvasProps) { const [drag, setDrag] = useState(null); const dragRef = useRef(null); const cleanupDragRef = useRef<() => void>(() => undefined); + 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(), []); @@ -133,22 +136,49 @@ export function BoardCanvas(props: BoardCanvasProps) { return (
-
-

One calm place for your homelab work

-

Haven board

+
+
+

Workspace / Haven homelab

+ {props.managingLists ? "Edit mode" : "Live board"} +
+

Haven control room

+

A clear runway for the work that keeps your little corner of the internet humming.

+
+
+ Command deck +

+ {props.managingLists ? "Shape the workflow: rename, remove, or add stages." : "Grab a handle to move a task, or open one for the full story."} +

+ SPACE to open a card +
+
+
+
+ Tasks in orbit + {totalCards} + across the board +
+
+ Workflow stages + {props.lists.length} + {filledLists} with activity +
+
+ Front of queue + {activeCards} + in the first stage
-

- {props.managingLists ? "Manage your workflow: rename, remove, or add lists." : "Drag tasks between lists, or open one for the full story."} -

{props.lists.length ? (
+
Workflow rail{totalCards} {totalCards === 1 ? "task" : "tasks"} in flight
{props.lists.map((list, index) => ( ) : ( )}
: null} diff --git a/web/src/components/CardItem.tsx b/web/src/components/CardItem.tsx index f56a34c..1d6a6ab 100644 --- a/web/src/components/CardItem.tsx +++ b/web/src/components/CardItem.tsx @@ -47,7 +47,10 @@ export function CardItem({ card, listName, accent, dragging, onOpen, onDragStart

{card.title}

{listName} - {preview ?

{preview}

: null} +
+ {preview ?

{preview}

:

No notes attached yet.

} + +
); } diff --git a/web/src/components/Header.tsx b/web/src/components/Header.tsx index 5289b04..679b727 100644 --- a/web/src/components/Header.tsx +++ b/web/src/components/Header.tsx @@ -9,12 +9,19 @@ export function Header({ loading, onReload, managingLists, onToggleManagingLists return (
- - havenllo + + + havenllo + operations board +
Haven homelab - My board + My board local +
+
+ + Ready for lift-off
)} diff --git a/web/src/styles.css b/web/src/styles.css index b3f25f9..730d436 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -636,3 +636,275 @@ textarea { min-height: 96px; resize: vertical; } @media (prefers-reduced-motion: reduce) { *, *::before, *::after { scroll-behavior: auto !important; transition: none !important; animation: none !important; } } + +/* Night-shift control room visual system */ +:root { + font-family: "Avenir Next", Avenir, Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + background: #07090e; + color: #f1f5ed; + --canvas: #07090e; + --canvas-glow: #121b1c; + --surface: #10151d; + --surface-raised: #171e28; + --surface-hover: #202a35; + --ink: #f1f5ed; + --muted: #a0aca5; + --faint: #6b7772; + --border: rgba(198, 216, 203, 0.14); + --border-strong: rgba(215, 255, 99, 0.58); + --teal: #6df7d0; + --teal-soft: rgba(109, 247, 208, 0.12); + --signal: #d7ff63; + --signal-soft: rgba(215, 255, 99, 0.12); + --indigo: #a992ff; + --danger: #ff8c78; + --danger-soft: rgba(255, 140, 120, 0.12); + --shadow: 0 24px 70px rgba(0, 0, 0, 0.34); + --shadow-lifted: 0 30px 72px rgba(0, 0, 0, 0.5); +} + +body { + background-color: var(--canvas); + background-image: + radial-gradient(circle at 78% -12%, rgba(169, 146, 255, 0.2), transparent 32rem), + radial-gradient(circle at 4% 26%, rgba(109, 247, 208, 0.1), transparent 29rem), + linear-gradient(rgba(215, 255, 99, 0.035) 1px, transparent 1px), + linear-gradient(90deg, rgba(215, 255, 99, 0.035) 1px, transparent 1px), + var(--canvas); + background-size: auto, auto, 48px 48px, 48px 48px, auto; + background-attachment: fixed; +} + +.app-shell { position: relative; min-height: 100vh; overflow: hidden; } +.app-shell::before { + position: absolute; + top: 90px; + right: -160px; + width: 380px; + height: 380px; + border: 1px solid rgba(215, 255, 99, 0.12); + border-radius: 50%; + box-shadow: 0 0 0 22px rgba(215, 255, 99, 0.025), 0 0 0 44px rgba(215, 255, 99, 0.018); + content: ""; + pointer-events: none; +} + +button:focus-visible, input:focus-visible, textarea:focus-visible, select:focus-visible, [tabindex="-1"]:focus-visible { + outline: 2px solid var(--signal); + outline-offset: 4px; +} + +.topbar { + min-height: 80px; + padding: 14px clamp(20px, 4.5vw, 72px); + border-bottom-color: rgba(198, 216, 203, 0.13); + background: rgba(7, 9, 14, 0.77); + box-shadow: 0 1px 0 rgba(215, 255, 99, 0.03), 0 10px 40px rgba(0, 0, 0, 0.12); + backdrop-filter: blur(20px) saturate(130%); +} + +.brand { gap: 12px; font-size: 1.25rem; letter-spacing: -0.06em; } +.brand-mark { + position: relative; + width: 38px; + height: 38px; + overflow: hidden; + border: 1px solid rgba(241, 245, 237, 0.18); + border-radius: 13px 13px 13px 4px; + color: #0a0d10; + background: var(--signal); + box-shadow: 7px 7px 0 rgba(109, 247, 208, 0.16), 0 0 24px rgba(215, 255, 99, 0.15); +} +.brand-mark::after { position: absolute; inset: -20% 48% 48% -20%; border-radius: 50%; background: rgba(255, 255, 255, 0.62); content: ""; } +.brand-mark span { position: relative; z-index: 1; font-size: 1.08rem; font-weight: 950; } +.brand-wordmark { display: grid; gap: 1px; } +.brand-wordmark small { color: var(--faint); font-size: 0.53rem; font-weight: 800; letter-spacing: 0.13em; text-transform: uppercase; } + +.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; } +.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); } +.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-actions { margin-left: 2px; } +.eyebrow { color: var(--signal); font-size: 0.64rem; letter-spacing: 0.16em; } + +.header-edit-button, .icon-button { border-color: rgba(198, 216, 203, 0.16); } +.header-edit-button { min-height: 40px; padding: 8px 14px; border-radius: 10px; color: var(--muted); background: rgba(16, 21, 29, 0.72); } +.header-edit-button:hover, .header-edit-button.is-active { color: var(--signal); border-color: rgba(215, 255, 99, 0.48); background: var(--signal-soft); } +.header-edit-button.is-active { box-shadow: inset 0 0 0 1px rgba(215, 255, 99, 0.08), 0 0 22px rgba(215, 255, 99, 0.07); } +.icon-button { color: var(--muted); background: rgba(16, 21, 29, 0.72); } +.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-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; } +.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 span:last-child { letter-spacing: 0.06em; } +.board-list-group { gap: 14px; } + +.list-column, .add-list-column { width: min(340px, calc(100vw - 48px)); flex-basis: 340px; border-color: rgba(198, 216, 203, 0.13); border-radius: 22px; background: linear-gradient(155deg, rgba(20, 27, 36, 0.95), rgba(11, 15, 21, 0.96)); box-shadow: 0 18px 45px rgba(0, 0, 0, 0.26); } +.list-column { position: relative; overflow: hidden; padding: 17px; transition: border-color 180ms ease, box-shadow 180ms ease, transform 180ms ease; } +.list-column::before { position: absolute; top: 0; right: 16px; left: 16px; height: 2px; border-radius: 0 0 4px 4px; background: linear-gradient(90deg, transparent, var(--list-accent), transparent); content: ""; opacity: 0.85; } +.list-column::after { position: absolute; right: -45px; bottom: -48px; width: 150px; height: 150px; border: 1px solid color-mix(in srgb, var(--list-accent) 18%, transparent); border-radius: 50%; content: ""; pointer-events: none; } +.list-column > * { position: relative; z-index: 1; } +.list-column:hover { border-color: color-mix(in srgb, var(--list-accent) 44%, rgba(198, 216, 203, 0.18)); box-shadow: 0 24px 62px rgba(0, 0, 0, 0.38); transform: translateY(-2px); } +.list-column.is-managing { border-color: color-mix(in srgb, var(--signal) 47%, transparent); box-shadow: 0 0 0 1px rgba(215, 255, 99, 0.08), 0 24px 62px rgba(0, 0, 0, 0.32); } +.list-column.is-drop-target { border-color: var(--list-accent); box-shadow: 0 0 0 3px var(--list-accent-soft), 0 24px 62px rgba(0, 0, 0, 0.4); transform: translateY(-3px); } +.list-column[data-accent="1"] { --list-accent: var(--indigo); --list-accent-soft: rgba(169, 146, 255, 0.14); } +.list-column[data-accent="2"] { --list-accent: var(--danger); --list-accent-soft: rgba(255, 140, 120, 0.14); } + +.list-header { gap: 12px; padding: 1px 0 15px; } +.list-heading { min-width: 0; flex: 1; gap: 10px; } +.list-marker { width: 10px; height: 10px; flex: 0 0 auto; border: 2px solid var(--list-accent); border-radius: 50%; box-shadow: 0 0 0 4px var(--list-accent-soft), 0 0 14px var(--list-accent); } +.list-title-block { min-width: 0; display: grid; gap: 2px; } +.list-kicker { color: var(--list-accent); font-size: 0.58rem; font-weight: 850; letter-spacing: 0.14em; text-transform: uppercase; } +.list-name, .list-name-text { font-size: 1.05rem; letter-spacing: -0.03em; } +.list-name:hover { color: var(--signal); } +.list-heading input { width: 200px; padding: 5px 7px; } +.count-badge { min-width: 28px; padding: 5px 8px; border: 1px solid color-mix(in srgb, var(--list-accent) 25%, transparent); color: var(--list-accent); font-size: 0.7rem; font-weight: 850; background: var(--list-accent-soft); } +.list-delete { width: 32px; min-height: 32px; } +.list-delete:hover:not(:disabled) { color: var(--danger); border-color: rgba(255, 140, 120, 0.44); background: var(--danger-soft); } + +.cards-stack { min-height: 72px; } +.card-slot + .card-slot { margin-top: 10px; } +.card-insertion-marker { height: 4px; margin: 6px 4px; background: var(--list-accent); box-shadow: 0 0 0 4px var(--list-accent-soft), 0 0 17px var(--list-accent); } +.card { position: relative; overflow: hidden; padding: 14px 14px 13px; border-color: rgba(198, 216, 203, 0.13); border-radius: 16px; background: linear-gradient(145deg, rgba(29, 38, 49, 0.96), rgba(16, 22, 30, 0.98)); box-shadow: 0 8px 20px rgba(0, 0, 0, 0.18); transition: transform 150ms ease, border-color 150ms ease, box-shadow 150ms ease, background 150ms ease, opacity 150ms ease; } +.card::before { position: absolute; top: 14px; bottom: 14px; left: 0; width: 2px; border-radius: 0 4px 4px 0; background: var(--list-accent, var(--teal)); content: ""; opacity: 0.72; } +.card:hover { border-color: color-mix(in srgb, var(--list-accent, var(--teal)) 48%, rgba(198, 216, 203, 0.16)); background: linear-gradient(145deg, rgba(37, 48, 61, 0.98), rgba(17, 24, 33, 0.99)); box-shadow: var(--shadow-lifted); transform: translateY(-3px) rotate(-0.25deg); } +.card:focus-visible { outline-color: var(--signal); } +.card.is-dragging { opacity: 0.38; transform: scale(0.975) rotate(1deg); } +.card-topline { gap: 8px; } +.card-grip { width: 24px; min-height: 24px; border-color: transparent; border-radius: 8px; } +.card-grip:hover, .card-grip:focus-visible { border-color: var(--signal); background: var(--signal-soft); } +.card-grip i { width: 3px; height: 3px; background: var(--faint); } +.card-title { font-size: 0.93rem; font-weight: 760; letter-spacing: -0.02em; line-height: 1.38; } +.card-status { max-width: 96px; min-height: 25px; padding: 5px 8px; font-size: 0.63rem; font-weight: 820; } +.card-bottomline { display: flex; align-items: flex-end; gap: 8px; margin-top: 10px; padding-left: 32px; } +.card-preview { flex: 1; min-width: 0; margin: 0; color: var(--muted); font-size: 0.75rem; line-height: 1.42; -webkit-line-clamp: 2; } +.card-preview-empty { color: var(--faint); font-style: italic; } +.card-open { flex: 0 0 auto; color: var(--faint); font-size: 0.57rem; font-weight: 850; letter-spacing: 0.08em; text-transform: uppercase; opacity: 0; transform: translateX(-4px); transition: opacity 140ms ease, transform 140ms ease, color 140ms ease; } +.card:hover .card-open, .card:focus-within .card-open { color: var(--signal); opacity: 1; transform: translateX(0); } +.card-open b { font-size: 0.82rem; font-weight: 500; } + +input, textarea, select { border-color: rgba(198, 216, 203, 0.2); border-radius: 11px; color: var(--ink); background: rgba(5, 8, 13, 0.72); } +input:hover, textarea:hover, select:hover { border-color: rgba(198, 216, 203, 0.38); } +input:focus, textarea:focus, select:focus { border-color: var(--signal); background: rgba(5, 8, 13, 0.9); box-shadow: 0 0 0 3px var(--signal-soft); } +.button { border-radius: 10px; font-size: 0.77rem; font-weight: 820; letter-spacing: 0.01em; } +.button-primary { color: #0b100a; background: var(--signal); box-shadow: 0 8px 22px rgba(215, 255, 99, 0.16); } +.button-primary:hover:not(:disabled) { background: #e5ff96; box-shadow: 0 12px 28px rgba(215, 255, 99, 0.28); } +.button-secondary { border-color: var(--border); color: var(--ink); background: var(--surface-hover); } +.button-danger { color: #1a0b09; background: var(--danger); } +.button-quiet { color: var(--muted); } +.button-quiet:hover:not(:disabled) { color: var(--ink); background: rgba(198, 216, 203, 0.08); } +.text-danger { color: var(--danger); } + +.quick-add { padding-top: 12px; } +.add-card-button, .add-list-button { min-height: 45px; border-radius: 11px; color: var(--faint); font-size: 0.77rem; font-weight: 780; } +.add-card-button { margin-top: 11px; } +.add-card-button:hover, .add-list-button:hover { border-color: color-mix(in srgb, var(--signal) 52%, transparent); color: var(--signal); background: var(--signal-soft); } +.add-card-button span, .add-list-button span { color: var(--signal); font-size: 1.15rem; } +.add-list-column { min-height: 90px; padding: 10px; border-color: rgba(198, 216, 203, 0.2); background: rgba(16, 21, 29, 0.42); } +.add-list-button { min-height: 68px; } +.list-empty { margin: 18px 6px; color: var(--faint); font-size: 0.74rem; } +.list-empty.is-dropping { color: var(--list-accent); } + +.empty-board, .loading-card, .fatal-state { max-width: 600px; margin: 86px auto; padding: 34px; border-color: rgba(198, 216, 203, 0.16); border-radius: 22px; background: linear-gradient(145deg, rgba(23, 30, 40, 0.96), rgba(12, 16, 23, 0.96)); box-shadow: var(--shadow); } +.empty-board h2, .fatal-state h1 { font-size: 1.35rem; } +.empty-state-mark { width: 46px; height: 46px; border-radius: 15px; color: #0b100a; background: var(--signal); box-shadow: 0 0 26px rgba(215, 255, 99, 0.2); } +.loading-card::after { background: linear-gradient(90deg, transparent, var(--signal), transparent); } + +.detail-backdrop, .dialog-backdrop { background: rgba(3, 5, 8, 0.78); backdrop-filter: blur(11px) saturate(120%); } +.card-detail { position: relative; width: min(100%, 820px); border-color: rgba(198, 216, 203, 0.2); border-radius: 24px; background: linear-gradient(150deg, #19222c, #0f151d 72%); box-shadow: 0 35px 110px rgba(0, 0, 0, 0.68); } +.card-detail::before { position: absolute; top: 0; right: 22px; left: 22px; height: 2px; border-radius: 0 0 4px 4px; background: linear-gradient(90deg, var(--signal), var(--teal), var(--indigo)); content: ""; } +.detail-header { padding: 20px 22px 0; } +.detail-breadcrumb { font-size: 0.68rem; letter-spacing: 0.05em; text-transform: uppercase; } +.detail-breadcrumb strong { color: var(--signal); } +.detail-close { width: 36px; min-height: 36px; border-radius: 10px; } +.detail-content { padding-top: 12px; } +.detail-grip { color: var(--signal); } +.detail-title-input { font-size: clamp(1.65rem, 3.6vw, 2.35rem); font-weight: 800; } +.detail-title-input:focus { border-color: var(--signal); box-shadow: 0 0 0 3px var(--signal-soft); } +.detail-properties { gap: 12px; margin-top: 20px; } +.detail-property { min-height: 72px; border-color: rgba(198, 216, 203, 0.15); border-radius: 14px; background: rgba(5, 8, 13, 0.3); } +.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-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); } +.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 code { color: #efffb7; background: var(--signal-soft); } +.markdown-preview a { color: var(--teal); } +.detail-footer { border-top-color: rgba(198, 216, 203, 0.14); } +.confirm-dialog { border-color: rgba(198, 216, 203, 0.2); border-radius: 18px; background: #18222d; } +.toast { border-color: rgba(198, 216, 203, 0.16); border-left-color: var(--signal); border-radius: 13px; background: #17212a; } +.toast-error { border-left-color: var(--danger); } +.toast-info { border-left-color: var(--indigo); } + +@media (max-width: 850px) { + .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) { + .topbar { min-height: 70px; padding: 11px 16px; } + .topbar-board { display: none; } + .topbar-actions { gap: 6px; } + .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; } + .rail-header { margin-right: 16px; } + .board-list-group { justify-content: flex-start; } + .list-column, .add-list-column { scroll-snap-align: start; } + .card-detail { max-height: calc(100vh - 20px); border-radius: 20px; } +} + +@media (max-width: 500px) { + .brand { font-size: 1.15rem; } + .brand-mark { width: 34px; height: 34px; border-radius: 11px 11px 11px 3px; } + .brand-wordmark small { display: none; } + .header-edit-button { min-width: 40px; padding: 8px 10px; font-size: 0; } + .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); } + .detail-timestamps { display: grid; gap: 9px; } +} + +@media (prefers-reduced-motion: reduce) { + .app-shell::before { display: none; } + .card-open { opacity: 1; transform: none; } + *, *::before, *::after { transition: none !important; animation: none !important; } +}