Board: new-card unread indicator (public/js/modules/board.js,
public/js/modules/buckets.js, public/js/modules/state.js,
public/css/style.css):
* state.unseenCardIds (Set) tracks card ids that arrived since the
last visit to the "all" view. Diff runs in renderBoard() against a
module-private knownCardIds snapshot.
* Bug fix: the first renderBoard runs with state.cards = [] (before
the API returns). The previous diff treated every card from the
real load as "new" and pulsed the entire board. Now the diff is
skipped when knownCardIds is null or empty — the first non-empty
render is a silent prime.
* When activeBucket === 'all' and unseenCardIds is non-empty, the set
is drained and every matching board card gets a 3-cycle accent glow
(`.card-pulse-new`, 0.8s × 3, pinned for anim-fast/slow). Works
both when navigating TO all and when a poll arrives while already
on all.
* When the user is on a different bucket: unseenCardIds accumulates
across polls and a red dot (`.bar-unread-dot`) appears on the "all"
tab. Position is top:3px right:3px *inside* the tab to survive
the double overflow clip (`#main-header overflow:hidden` +
`#bucket-bar overflow-x:auto`). renderBar() bakes the dot into the
all-tab template on every render so it survives bar re-renders that
aren't followed by a board render.
* decorateAllUnreadDot() in board.js is a fallback called at the end
of every renderBoard for cases where the diff changed unseenCardIds
but renderBar hasn't run yet.
Relay: readable inbox (relay/inc/routes/messages.php,
relay/ui/app.js, relay/ui/layout.php, relay/ui/style.css):
* New session-based GET /api/messages/inbox — returns up to 200
messages for the logged-in user, decrypted server-side with the
inter-tenant key (no extra leakage — the relay already sees
plaintext during the routing hop). Does NOT mark messages as
fetched; the hyprcrd client is still the canonical receiver.
* loadInbox() now fetches from /api/messages/inbox and renders
actual inbox items: sender (with user-circle icon + accent2),
optional title (bold), body (pre-wrap, max-height 120px), relative
timestamp (formatRelTime: "just now", "2 min ago", "today 14:32",
"3 d ago", or YYYY-MM-DD for older). Unfetched messages show a
small "new" accent badge next to the sender name.
* deleteMessage(id, btn) — confirms, calls existing DELETE
/api/messages/{id} (already session-aware), removes the DOM row,
re-loads the list, toasts "message deleted".
* Auto-poll: startInboxPoll / stopInboxPoll refresh the inbox every
15s while the compose view is active. Stops on view switch.
* Header-status badge: mirrors the real message count (not just the
/api/status count) and toggles the has-unread class + dot so the
tap-to-scroll-to-inbox target stays fresh.
Relay UI polish (relay/ui/style.css):
* .header-status is now a clickable button with tray icon, hover
tint, focus-ring, and a red dot (::after pseudo) when has-unread.
jumpToInbox() scrolls smoothly to #inbox-section and flashes it
with a 1.6s accent box-shadow pulse.
* .inbox-item .from is flex-row with icon + badge-new pill.
* .inbox-item .del gets hover-background for better mobile tappability.
* @keyframes flash-highlight for the scroll-to-inbox animation.
chore: bump version to 0.9.9
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1081 lines
44 KiB
CSS
1081 lines
44 KiB
CSS
body { font-family: var(--t-font-content, 'JetBrains Mono', monospace); }
|
|
.font-pixel { font-family: var(--t-font-ui, var(--t-font-pixel, 'Silkscreen', cursive)) !important; }
|
|
.brand { font-family: 'Silkscreen', cursive !important; }
|
|
body.no-scanlines::after { display: none !important; }
|
|
#dialog-msg { white-space: pre-line; word-break: break-word; }
|
|
|
|
/* ── User-customizable design tokens ── */
|
|
:root {
|
|
--ui-radius-card: 0px;
|
|
--ui-radius-button: 0px;
|
|
--ui-radius-input: 0px;
|
|
--ui-radius-modal: 4px;
|
|
--ui-radius-tag: 2px;
|
|
--ui-radius-image: 4px;
|
|
--ui-border-card: 1px;
|
|
--ui-border-style: solid;
|
|
--ui-bar-height: 3px;
|
|
--ui-pad-card: 16px;
|
|
--ui-gap-board: 0px;
|
|
--ui-shadow-card: none;
|
|
--ui-shadow-modal: 0 25px 50px -12px rgba(0,0,0,0.35);
|
|
--ui-shadow-header: 0 4px 20px -4px rgba(0,0,0,0.3);
|
|
--ui-shadow-footer: 0 -4px 20px -4px rgba(0,0,0,0.3);
|
|
--ui-lift-card: 0px;
|
|
--ui-blur-overlay: 0px;
|
|
}
|
|
|
|
#main-header { box-shadow: var(--ui-shadow-header); }
|
|
#main-footer { box-shadow: var(--ui-shadow-footer); }
|
|
|
|
/* Cards: geometry & shadow driven by design tokens */
|
|
.board-card {
|
|
--card-stack-shadow: 0 0 0 0 transparent;
|
|
border-radius: var(--ui-radius-card);
|
|
border-width: var(--ui-border-card);
|
|
border-style: var(--ui-border-style);
|
|
box-shadow: var(--ui-shadow-card), var(--card-stack-shadow);
|
|
transition: border-color 0.12s, transform 0.15s, box-shadow 0.2s;
|
|
overflow: hidden; /* clip card-bar (and other inner elements) to the rounded corners */
|
|
}
|
|
.board-card.card-group {
|
|
--card-stack-shadow:
|
|
0 3px 0 -2px var(--t-border),
|
|
0 3px 0 0 var(--t-bg),
|
|
0 8px 0 -4px color-mix(in srgb, var(--t-border) 70%, transparent),
|
|
0 6px 0 0 var(--t-bg),
|
|
0 13px 0 -6px color-mix(in srgb, var(--t-border) 30%, transparent);
|
|
}
|
|
.board-card:hover { transform: translateY(calc(0px - var(--ui-lift-card))); }
|
|
.board-card > .p-4 { padding: var(--ui-pad-card); }
|
|
.board-card .card-tags { flex-shrink: 0; min-height: 18px; }
|
|
#board { gap: var(--ui-gap-board); }
|
|
|
|
/* Bordered action buttons */
|
|
button[class*="border-t-border"]:not(.toolbar-btn):not(.settings-tab):not(.bucket-tab):not(.ctx-item):not(.tp-btn),
|
|
button[class*="border-t-accent"]:not(.toolbar-btn):not(.settings-tab):not(.bucket-tab):not(.ctx-item):not(.tp-btn),
|
|
button[class*="border-t-danger"]:not(.toolbar-btn):not(.settings-tab):not(.bucket-tab):not(.ctx-item):not(.tp-btn),
|
|
a[class*="border-t-border"],
|
|
a[class*="border-t-accent"] {
|
|
border-radius: var(--ui-radius-button);
|
|
}
|
|
|
|
/* Inputs / selects / textareas */
|
|
input:not([type="checkbox"]):not([type="radio"]):not([type="range"])[class*="border-t-border"],
|
|
input:not([type="checkbox"]):not([type="radio"]):not([type="range"])[class*="border-t-accent"],
|
|
textarea[class*="border-t-border"],
|
|
textarea[class*="border-t-accent"],
|
|
select[class*="border-t-border"],
|
|
select[class*="border-t-accent"] {
|
|
border-radius: var(--ui-radius-input);
|
|
}
|
|
|
|
/* Tag chips */
|
|
.tag-chip { border-radius: var(--ui-radius-tag); }
|
|
|
|
/* Prose images & card cover */
|
|
.prose img { border-radius: var(--ui-radius-image); }
|
|
.board-card.card-has-cover { border-radius: var(--ui-radius-card); }
|
|
|
|
/* Modal containers (the inner box of each overlay) */
|
|
#modal > div,
|
|
#detail > div,
|
|
#settings-overlay > div,
|
|
#help > div,
|
|
#search > div,
|
|
#tasks > div,
|
|
#calendar > div,
|
|
#gallery > div,
|
|
#dialog > div {
|
|
border-radius: var(--ui-radius-modal);
|
|
box-shadow: var(--ui-shadow-modal);
|
|
}
|
|
|
|
/* Overlay backdrop blur (applies to anything that's already an .overlay) */
|
|
.overlay { backdrop-filter: blur(var(--ui-blur-overlay)); -webkit-backdrop-filter: blur(var(--ui-blur-overlay)); }
|
|
|
|
/* Animation speed (toggled via body class from applyDesign) */
|
|
body.anim-off *, body.anim-off *::before, body.anim-off *::after {
|
|
transition: none !important;
|
|
animation: none !important;
|
|
}
|
|
body.anim-fast *, body.anim-fast *::before, body.anim-fast *::after {
|
|
transition-duration: 50ms !important;
|
|
animation-duration: 100ms !important;
|
|
}
|
|
body.anim-slow *, body.anim-slow *::before, body.anim-slow *::after {
|
|
transition-duration: 400ms !important;
|
|
animation-duration: 600ms !important;
|
|
}
|
|
/* Blink / pulse animations are status indicators with deliberate cadences
|
|
(cursor, overdue bar, unread chat button, new collab bucket 3-pulse).
|
|
Speeding them up makes them useless strobes, so they ignore the global
|
|
anim-* override via higher-specificity rules. */
|
|
body.anim-fast .cursor-blink::after,
|
|
body.anim-slow .cursor-blink::after {
|
|
animation: blink 1s step-end infinite !important;
|
|
animation-duration: 1s !important;
|
|
}
|
|
body.anim-fast .card-bar-pulse,
|
|
body.anim-slow .card-bar-pulse {
|
|
animation: pulse-glow 2s ease-in-out infinite !important;
|
|
animation-duration: 2s !important;
|
|
}
|
|
body.anim-fast .board-card:has(.card-bar-pulse) .card-bar-pulse ~ * .text-t-danger,
|
|
body.anim-slow .board-card:has(.card-bar-pulse) .card-bar-pulse ~ * .text-t-danger {
|
|
animation-duration: 2s !important;
|
|
}
|
|
body.anim-fast .card-spinner,
|
|
body.anim-slow .card-spinner {
|
|
animation: spin 0.8s linear infinite !important;
|
|
animation-duration: 0.8s !important;
|
|
}
|
|
|
|
/* ── Theme preview (settings) ── */
|
|
.theme-preview {
|
|
position: relative; padding: 14px; overflow: hidden;
|
|
background: var(--t-bg); border: 1px solid var(--t-border); border-radius: 3px;
|
|
}
|
|
.theme-preview .tp-scan { position: absolute; inset: 0; pointer-events: none; background: repeating-linear-gradient(0deg, transparent, transparent 2px, var(--t-scanline, rgba(0,0,0,0.04)) 2px, var(--t-scanline, rgba(0,0,0,0.04)) 4px); }
|
|
.theme-preview .tp-row { display: flex; gap: 12px; align-items: stretch; position: relative; z-index: 1; }
|
|
.theme-preview .tp-card {
|
|
flex: 1; min-width: 0;
|
|
background: var(--t-surface);
|
|
border: var(--tp-border-card, 1px) var(--tp-border-style, solid) var(--t-border);
|
|
border-radius: var(--tp-radius-card, 0px);
|
|
box-shadow: var(--tp-shadow-card, none);
|
|
overflow: hidden;
|
|
}
|
|
.theme-preview .tp-card-bar { height: var(--tp-bar-height, 3px); background: var(--t-accent); }
|
|
.theme-preview .tp-card-body { padding: var(--tp-pad-card, 14px); }
|
|
.theme-preview .tp-headline { font-family: var(--tp-font-headline); color: var(--t-bright); font-size: 16px; font-weight: 700; margin-bottom: 4px; }
|
|
.theme-preview .tp-content { font-family: var(--tp-font-content); color: var(--t-text); font-size: 11px; line-height: 1.55; margin-bottom: 8px; }
|
|
.theme-preview .tp-meta { display: flex; gap: 6px; align-items: center; }
|
|
.theme-preview .tp-tag {
|
|
font-family: var(--tp-font-ui); font-size: 9px;
|
|
color: var(--t-accent); background: color-mix(in srgb, var(--t-accent) 12%, transparent);
|
|
border: 1px solid color-mix(in srgb, var(--t-accent) 25%, transparent);
|
|
border-radius: var(--tp-radius-tag, 2px); padding: 1px 6px;
|
|
}
|
|
.theme-preview .tp-ui { font-family: var(--tp-font-ui); font-size: 9px; color: var(--t-dim); text-transform: uppercase; letter-spacing: 0.05em; }
|
|
.theme-preview .tp-side { display: flex; flex-direction: column; gap: 6px; min-width: 96px; }
|
|
.theme-preview .tp-btn {
|
|
font-family: var(--tp-font-ui); font-size: 10px;
|
|
color: var(--t-accent); border: 1px solid var(--t-accent);
|
|
padding: 4px 10px; border-radius: var(--tp-radius-button, 0px);
|
|
background: transparent; cursor: pointer;
|
|
}
|
|
.theme-preview .tp-input {
|
|
font-family: var(--tp-font-ui); font-size: 10px;
|
|
color: var(--t-text); background: var(--t-bg);
|
|
border: 1px solid var(--t-border);
|
|
padding: 4px 8px; border-radius: var(--tp-radius-input, 0px);
|
|
outline: none; width: 100%;
|
|
}
|
|
|
|
/* Range slider styling for design panel */
|
|
.design-slider {
|
|
flex: 1; height: 4px;
|
|
-webkit-appearance: none; appearance: none;
|
|
background: var(--t-border); border-radius: 2px; outline: none; cursor: pointer;
|
|
}
|
|
.design-slider::-webkit-slider-thumb {
|
|
-webkit-appearance: none; appearance: none;
|
|
width: 12px; height: 12px; border-radius: 50%;
|
|
background: var(--t-accent); cursor: pointer; border: none;
|
|
}
|
|
.design-slider::-moz-range-thumb {
|
|
width: 12px; height: 12px; border-radius: 50%;
|
|
background: var(--t-accent); cursor: pointer; border: none;
|
|
}
|
|
.design-row { display: flex; align-items: center; gap: 10px; padding: 2px 0; }
|
|
.design-row > label { flex: 0 0 88px; font-size: 11px; color: var(--t-muted); }
|
|
.design-row > select { width: 110px; }
|
|
.design-row > .design-val { font-family: var(--t-font-pixel); font-size: 9px; color: var(--t-dim); width: 36px; text-align: right; flex-shrink: 0; }
|
|
.design-section-label { font-family: var(--t-font-pixel); font-size: 9px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--t-dim); margin: 12px 0 4px; }
|
|
.design-section-label:first-child { margin-top: 0; }
|
|
#design-advanced > summary { list-style: none; }
|
|
#design-advanced > summary::-webkit-details-marker { display: none; }
|
|
#design-advanced > summary:hover { color: var(--t-bright); }
|
|
#design-advanced .design-advanced-caret { transition: transform 0.15s; display: inline-block; }
|
|
#design-advanced[open] .design-advanced-caret { transform: rotate(90deg); }
|
|
|
|
/* Settings peek: hide only the dim background so the user can see the real UI behind, while the settings popup stays open and interactive */
|
|
body.settings-peeking #settings-overlay {
|
|
background: transparent !important;
|
|
pointer-events: none !important;
|
|
backdrop-filter: none !important;
|
|
-webkit-backdrop-filter: none !important;
|
|
}
|
|
body.settings-peeking #settings-overlay > div { pointer-events: auto; }
|
|
|
|
/* Toolbar toggles */
|
|
.toolbar-toggle {
|
|
display: flex; align-items: center; gap: 4px;
|
|
padding: 3px 8px; font-size: 10px; font-family: var(--t-font-pixel, 'Silkscreen', cursive);
|
|
color: var(--t-dim); border-radius: 3px; border: none; background: none;
|
|
cursor: pointer; transition: color 0.15s, background 0.15s; flex-shrink: 0; margin-left: 4px;
|
|
}
|
|
.toolbar-toggle:hover { color: var(--t-accent); background: var(--t-accent10, rgba(56,189,248,0.08)); }
|
|
.toolbar-toggle.active { color: var(--t-accent); background: var(--t-accent10, rgba(56,189,248,0.12)); }
|
|
|
|
/* Calendar */
|
|
.cal-header { display: grid; grid-template-columns: repeat(7, 1fr); border-bottom: 3px solid var(--t-border); flex-shrink: 0; }
|
|
.cal-day-label { padding: 6px 8px; font-family: var(--t-font-pixel, 'Silkscreen', cursive); font-size: 9px; color: var(--t-dim); text-align: center; text-transform: uppercase; letter-spacing: 0.05em; }
|
|
.cal-body { display: grid; grid-template-columns: repeat(7, 1fr); grid-template-rows: repeat(6, 1fr); flex: 1; min-height: 0; }
|
|
.cal-cell { border-right: 3px solid var(--t-border); border-bottom: 3px solid var(--t-border); padding: 4px 6px; overflow: hidden; cursor: default; transition: background 0.1s; position: relative; min-height: 0; display: flex; flex-direction: column; }
|
|
.cal-cell:nth-child(7n) { border-right: none; }
|
|
.cal-cell:hover { background: var(--t-raised); }
|
|
.cal-cell-out { opacity: 0.2; }
|
|
.cal-cell-today { background: color-mix(in srgb, var(--t-accent) 8%, transparent); }
|
|
.cal-cell-today:hover { background: color-mix(in srgb, var(--t-accent) 14%, transparent); }
|
|
.cal-cell-num { font-family: var(--t-font-pixel, 'Silkscreen', cursive); font-size: 10px; color: var(--t-muted); margin-bottom: 3px; }
|
|
.cal-cell-items { display: flex; flex-direction: column; gap: 2px; overflow-y: auto; flex: 1; min-height: 0; }
|
|
.cal-item { font-size: 10px; color: var(--t-text); padding: 2px 5px; border-left: 3px solid var(--t-accent); background: transparent; cursor: pointer; display: flex; align-items: center; gap: 4px; white-space: nowrap; overflow: hidden; flex-shrink: 0; }
|
|
.cal-item:hover { text-decoration: underline dotted; text-underline-offset: 2px; color: var(--t-bright); }
|
|
.cal-item-journal { opacity: 0.8; }
|
|
.cal-todo { font-family: var(--t-font-pixel, 'Silkscreen', cursive); font-size: 8px; color: var(--t-accent); flex-shrink: 0; }
|
|
|
|
/* Datepicker */
|
|
.dp-header { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; }
|
|
.dp-header button { background: none; border: 1px solid var(--t-border); color: var(--t-muted); cursor: pointer; padding: 2px 8px; border-radius: 3px; transition: color 0.1s, border-color 0.1s; }
|
|
.dp-header button:hover { color: var(--t-accent); border-color: var(--t-accent); }
|
|
.dp-title { flex: 1; text-align: center; font-family: var(--t-font-pixel); font-size: 10px; color: var(--t-bright); }
|
|
.dp-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
|
|
.dp-day-label { text-align: center; font-family: var(--t-font-pixel); font-size: 8px; color: var(--t-dim); padding: 2px 0; text-transform: uppercase; }
|
|
.dp-day { text-align: center; font-size: 11px; color: var(--t-text); padding: 5px 0; cursor: pointer; border-radius: 3px; transition: background 0.1s, color 0.1s; }
|
|
.dp-day:hover { background: var(--t-raised); color: var(--t-bright); }
|
|
.dp-day.dp-out { color: var(--t-dim); opacity: 0.4; }
|
|
.dp-day.dp-today { color: var(--t-accent); font-weight: bold; }
|
|
.dp-day.dp-selected { background: var(--t-accent); color: var(--t-bg); }
|
|
|
|
/* Scrollbars */
|
|
* {
|
|
scrollbar-width: thin;
|
|
scrollbar-color: var(--t-border) transparent;
|
|
}
|
|
*::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
*::-webkit-scrollbar-track { background: transparent; }
|
|
*::-webkit-scrollbar-thumb {
|
|
background: var(--t-border);
|
|
border-radius: 3px;
|
|
}
|
|
*::-webkit-scrollbar-thumb:hover { background: var(--t-dim); }
|
|
*::-webkit-scrollbar-corner { background: transparent; }
|
|
|
|
/* Scanlines */
|
|
body::after {
|
|
content: '';
|
|
position: fixed;
|
|
inset: 0;
|
|
pointer-events: none;
|
|
z-index: 9999;
|
|
background: repeating-linear-gradient(0deg, transparent, transparent 2px, var(--t-scanline, rgba(0,0,0,0.04)) 2px, var(--t-scanline, rgba(0,0,0,0.04)) 4px);
|
|
}
|
|
|
|
/* Blinking cursor */
|
|
.cursor-blink::after { content: '_'; animation: blink 1s step-end infinite; }
|
|
@keyframes blink { 50% { opacity: 0; } }
|
|
|
|
/* Line clamp */
|
|
.line-clamp-2, .line-clamp-3, .line-clamp-5 { display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; }
|
|
.line-clamp-2 { -webkit-line-clamp: 2; }
|
|
.line-clamp-3 { -webkit-line-clamp: 3; }
|
|
.line-clamp-5 { -webkit-line-clamp: 5; }
|
|
|
|
/* Superview */
|
|
/* Single-column list view (toggled via #superview-toggle). Each row is
|
|
a flex line with: color dot · optional icon · title (flex-grows) ·
|
|
tag chips · bucket · date. Title ellipsizes when narrow. */
|
|
.superview-grid { padding: 1rem 1.5rem 6rem; max-width: 1100px; margin: 0 auto; display: flex; flex-direction: column; gap: 1px; }
|
|
.superview-item {
|
|
padding: 8px 12px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
cursor: pointer;
|
|
transition: background 0.1s, color 0.1s;
|
|
border-bottom: 1px solid color-mix(in srgb, var(--t-border) 50%, transparent);
|
|
}
|
|
.superview-item:hover { background: color-mix(in srgb, var(--t-raised) 60%, transparent); color: var(--t-bright); }
|
|
.superview-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
|
.superview-icon { font-size: 14px; flex-shrink: 0; }
|
|
.superview-title {
|
|
font-size: 14px;
|
|
color: var(--t-text);
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
flex: 1 1 0;
|
|
min-width: 0;
|
|
}
|
|
.superview-item:hover .superview-title { color: var(--t-bright); }
|
|
.superview-tags { display: flex; gap: 4px; flex-shrink: 0; }
|
|
.superview-tag {
|
|
font-family: var(--t-font-pixel, 'Silkscreen', cursive);
|
|
font-size: 10px;
|
|
color: var(--t-accent);
|
|
background: color-mix(in srgb, var(--t-accent) 12%, transparent);
|
|
padding: 1px 6px;
|
|
border-radius: var(--ui-radius-tag, 2px);
|
|
white-space: nowrap;
|
|
}
|
|
.superview-tag-more { color: var(--t-dim); background: transparent; }
|
|
.superview-bucket {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
font-family: var(--t-font-pixel, 'Silkscreen', cursive);
|
|
font-size: 10px;
|
|
color: var(--t-muted);
|
|
flex-shrink: 0;
|
|
}
|
|
.superview-bucket i { font-size: 12px; }
|
|
.superview-date {
|
|
font-family: var(--t-font-pixel, 'Silkscreen', cursive);
|
|
font-size: 10px;
|
|
color: var(--t-dim);
|
|
flex-shrink: 0;
|
|
min-width: 80px;
|
|
text-align: right;
|
|
}
|
|
|
|
.board-card.preview-active { border-color: var(--t-accent) !important; box-shadow: 0 0 0 1px var(--t-accent), 0 0 12px -2px color-mix(in srgb, var(--t-accent) 30%, transparent); }
|
|
|
|
/* Upcoming banner */
|
|
.upcoming-item { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; font-size: 11px; border-radius: 3px; border: 1px solid var(--t-border); cursor: pointer; transition: border-color 0.12s; }
|
|
.upcoming-item:hover { border-color: var(--t-accent); }
|
|
.upcoming-dismiss { background: none; border: none; color: var(--t-dim); cursor: pointer; padding: 2px; transition: color 0.12s; }
|
|
.upcoming-dismiss:hover { color: var(--t-danger); }
|
|
|
|
/* Toggle switch */
|
|
.toggle-row { display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 3px 0; }
|
|
.toggle-row:hover .toggle-label { color: var(--t-bright); }
|
|
.toggle-label { flex: 1; font-size: 12px; color: var(--t-muted); user-select: none; transition: color 0.1s; }
|
|
.toggle-switch { position: relative; width: 32px; height: 18px; border-radius: 9px; background: var(--t-border); flex-shrink: 0; transition: background 0.2s; }
|
|
.toggle-switch::after { content: ''; position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%; background: var(--t-bg); transition: left 0.2s; }
|
|
.toggle-switch.on { background: var(--t-accent); }
|
|
.toggle-switch.on::after { left: 16px; }
|
|
#settings-panel-behavior select { width: 120px; }
|
|
|
|
/* Settings */
|
|
.settings-tab { display: flex; align-items: center; gap: 8px; padding: 6px 16px; font-size: 11px; color: var(--t-muted); cursor: pointer; border: none; background: none; width: 100%; text-align: left; transition: color 0.1s, background 0.1s; border-left: 2px solid transparent; }
|
|
.settings-tab:hover { color: var(--t-bright); background: var(--t-raised); }
|
|
.settings-tab.active { color: var(--t-accent); border-left-color: var(--t-accent); background: color-mix(in srgb, var(--t-accent) 8%, transparent); }
|
|
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
@keyframes pulse-glow { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
|
|
.card-bar-pulse { animation: pulse-glow 2s ease-in-out infinite; }
|
|
.board-card:has(.card-bar-pulse) { border-color: var(--t-danger); }
|
|
.due-pill { display: inline-flex; align-items: center; gap: 3px; padding: 0 5px; border-radius: 8px; font-size: 9px; line-height: 16px; color: var(--t-bg); font-family: var(--t-font-pixel); }
|
|
.card-done-badge { position: absolute; bottom: 0; right: 0; width: 0; height: 0; border-style: solid; border-width: 0 0 24px 24px; border-color: transparent transparent #4ade80 transparent; z-index: 1; }
|
|
.card-done-badge i { position: absolute; bottom: -22px; right: 1px; font-size: 10px; color: #000; }
|
|
.board-card:has(.card-bar-pulse) .card-bar-pulse ~ * .text-t-danger { animation: pulse-glow 2s ease-in-out infinite; }
|
|
.card-spinner { animation: spin 0.8s linear infinite; }
|
|
|
|
/* Cards */
|
|
.card-bar { image-rendering: pixelated; height: var(--ui-bar-height, 3px); }
|
|
.board-card > .card-bar { border-top-left-radius: var(--ui-radius-card); border-top-right-radius: var(--ui-radius-card); }
|
|
.board-card { min-height: var(--card-min-h, 180px); }
|
|
.card-layer { position: relative; z-index: 1; }
|
|
.card-has-cover {
|
|
background-size: cover;
|
|
background-position: center;
|
|
background-repeat: no-repeat;
|
|
overflow: hidden;
|
|
}
|
|
.card-has-cover h3,
|
|
.card-has-cover p,
|
|
.card-has-cover .font-pixel,
|
|
.card-has-cover i {
|
|
text-shadow: 0 0 8px var(--t-bg), 0 0 16px var(--t-bg), 0 1px 3px var(--t-bg);
|
|
}
|
|
.card-group.card-has-cover {
|
|
overflow: visible;
|
|
}
|
|
.card-cover-overlay {
|
|
position: absolute;
|
|
inset: 0;
|
|
background: var(--t-cover-gradient, linear-gradient(180deg, rgba(15, 23, 42, 0.22) 0%, rgba(15, 23, 42, 0.7) 62%, rgba(15, 23, 42, 0.86) 100%));
|
|
pointer-events: none;
|
|
z-index: 0;
|
|
}
|
|
|
|
/* Group stacking effect — implemented via box-shadow on .board-card.card-group above
|
|
(pseudo-elements would be clipped by the card's overflow:hidden). */
|
|
.card-group { position: relative; }
|
|
.card-group-badge {
|
|
position: absolute;
|
|
top: 6px;
|
|
right: 6px;
|
|
min-width: 20px;
|
|
height: 20px;
|
|
padding: 0 6px;
|
|
border-radius: 999px;
|
|
box-sizing: border-box;
|
|
white-space: nowrap;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 10px;
|
|
font-weight: 700;
|
|
z-index: 5;
|
|
}
|
|
|
|
/* Group sidebar */
|
|
.sidebar-card { border-left-width: 2px; }
|
|
|
|
/* Drop-on-card group indicator */
|
|
.card-drop-group {
|
|
outline: 2px solid var(--t-accent2) !important;
|
|
outline-offset: -2px;
|
|
box-shadow: 0 0 20px -4px var(--t-accent2) !important;
|
|
}
|
|
.card-thumb {
|
|
position: absolute;
|
|
top: 12px;
|
|
right: 12px;
|
|
width: 44px;
|
|
height: 44px;
|
|
border-radius: 50%;
|
|
background-size: cover;
|
|
background-position: center;
|
|
border: 2px solid var(--t-border);
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
|
z-index: 2;
|
|
transform-origin: center center;
|
|
transition: width 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), height 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), margin 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), border-color 0.2s ease, box-shadow 0.25s ease;
|
|
}
|
|
.card-thumb:hover {
|
|
width: 80px;
|
|
height: 80px;
|
|
margin-top: -18px;
|
|
margin-right: -18px;
|
|
border-color: var(--t-muted);
|
|
box-shadow: 0 4px 16px rgba(0,0,0,0.4);
|
|
z-index: 10;
|
|
}
|
|
.card-glow:hover { box-shadow: var(--ui-shadow-card), var(--card-stack-shadow), 0 0 16px -3px var(--glow-color, rgba(56, 189, 248, 0.15)); }
|
|
.card-selected { outline: 2px solid var(--t-accent) !important; outline-offset: -2px; }
|
|
.card-select-check {
|
|
position: absolute; top: 6px; left: 6px; width: 18px; height: 18px;
|
|
border-radius: 50%; background: var(--t-accent); z-index: 6;
|
|
display: flex; align-items: center; justify-content: center;
|
|
pointer-events: none;
|
|
}
|
|
.card-select-check::after {
|
|
content: ''; width: 5px; height: 9px;
|
|
border: solid var(--t-bg); border-width: 0 2px 2px 0;
|
|
transform: rotate(45deg); margin-top: -2px;
|
|
}
|
|
.card-dragging { opacity: 0.15; transform: scale(0.95); }
|
|
.box-select {
|
|
position: absolute; border: 1px solid var(--t-accent); background: rgba(56, 189, 248, 0.08);
|
|
pointer-events: none; z-index: 10;
|
|
}
|
|
|
|
/* Card drop cursor — single absolutely-positioned vertical line that
|
|
lives inside #board (which is `position: relative`) and gets
|
|
repositioned by the dragover handlers in `board.js`. Replaces the
|
|
old `.card-drop-before::before` / `.card-drop-after::after` pseudo-
|
|
elements so the indicator is a real DOM node in the gap between cards
|
|
instead of a sliver glued to one card. The "before first card"
|
|
case works automatically: the first card's left-25% zone positions
|
|
the cursor at `card.offsetLeft - 6`, which sits in #board's left
|
|
padding and stays visible. */
|
|
#card-drop-cursor {
|
|
position: absolute;
|
|
width: 4px;
|
|
background: var(--t-accent);
|
|
box-shadow: 0 0 12px var(--t-accent), 0 0 4px var(--t-accent);
|
|
border-radius: 2px;
|
|
pointer-events: none;
|
|
z-index: 10;
|
|
display: none;
|
|
}
|
|
|
|
/* Group fan preview */
|
|
.group-fan {
|
|
position: fixed;
|
|
z-index: 50;
|
|
padding: 8px;
|
|
background: var(--t-bg);
|
|
border: 1px solid var(--t-border);
|
|
border-radius: 6px;
|
|
box-shadow: 0 8px 30px -6px rgba(0,0,0,0.4);
|
|
pointer-events: auto;
|
|
animation: fanIn 0.25s ease;
|
|
max-width: min(720px, 95vw);
|
|
}
|
|
@keyframes fanIn {
|
|
from { opacity: 0; transform: translateY(6px) scale(0.95); }
|
|
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
}
|
|
.group-fan-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(var(--fan-cols, 6), 104px);
|
|
gap: 5px;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
.group-fan-grid::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0; bottom: 0; right: 0;
|
|
width: 0;
|
|
background: linear-gradient(to right, transparent, var(--t-bg));
|
|
pointer-events: none;
|
|
transition: width 0.2s;
|
|
}
|
|
.group-fan-grid.has-more::after { width: 40px; }
|
|
.group-fan-card {
|
|
width: 104px;
|
|
background: var(--t-surface);
|
|
border: 1px solid var(--t-border);
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
overflow: hidden;
|
|
transition: border-color 0.12s, transform 0.12s;
|
|
}
|
|
.group-fan-card:hover {
|
|
border-color: var(--t-muted);
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.25);
|
|
}
|
|
.group-fan-bar { height: 2px; }
|
|
.group-fan-title {
|
|
padding: 5px 6px;
|
|
font-size: 9px;
|
|
line-height: 1.3;
|
|
color: var(--t-bright);
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
}
|
|
.group-fan-thumb {
|
|
height: 44px;
|
|
background-size: cover;
|
|
background-position: center;
|
|
}
|
|
|
|
/* Board card animation */
|
|
#board > div {
|
|
transition: transform 0.2s ease, opacity 0.2s ease;
|
|
}
|
|
|
|
/* Split panes */
|
|
.split-pick {
|
|
padding: 6px 12px;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-size: 11px;
|
|
color: var(--t-text);
|
|
transition: background 0.1s;
|
|
}
|
|
.split-pick:hover, .split-pick.active { background: var(--t-raised); }
|
|
.split-pick .sp-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
|
|
|
|
/* Zen mode */
|
|
html.detail-zen-open,
|
|
body.detail-zen-open { overflow: hidden; }
|
|
#detail.zen > div { inset: 0 !important; border: none !important; border-radius: 0; overflow-y: auto; }
|
|
#detail.zen #detail-sidebar,
|
|
#detail.zen #split-right { display: none !important; }
|
|
#detail.zen .card-bar { display: none; }
|
|
#detail.zen #detail-scroll {
|
|
margin: 0 auto;
|
|
padding: 3rem 2rem;
|
|
overflow: visible !important;
|
|
}
|
|
#detail.zen #detail-scroll #detail-title { font-size: 1.6rem; margin-bottom: 1.5rem; }
|
|
#detail.zen #detail-scroll #detail-body { font-size: 1rem; line-height: 1.85; }
|
|
#detail.zen #detail-scroll #detail-meta { display: none; }
|
|
#detail.zen #detail-backlinks { display: none; }
|
|
#detail.zen #detail-controls {
|
|
position: fixed;
|
|
top: 0; left: 0; right: 0;
|
|
z-index: 120;
|
|
border: none;
|
|
background: transparent;
|
|
justify-content: flex-end;
|
|
padding: 1rem;
|
|
opacity: 0.15;
|
|
transition: opacity 0.2s;
|
|
}
|
|
#detail.zen #detail-controls:hover { opacity: 1; }
|
|
#detail.zen #split-right-btn { display: none; }
|
|
|
|
/* Zen resize handles */
|
|
|
|
/* Detail close morph */
|
|
.detail-close-frame {
|
|
z-index: 130;
|
|
pointer-events: none;
|
|
border: 3px solid var(--t-accent2);
|
|
border-radius: 10px;
|
|
background: transparent;
|
|
opacity: 0.95;
|
|
box-sizing: border-box;
|
|
will-change: left, top, width, height, opacity, border-radius;
|
|
transition: left 0.34s cubic-bezier(0.22, 1, 0.36, 1), top 0.34s cubic-bezier(0.22, 1, 0.36, 1), width 0.34s cubic-bezier(0.22, 1, 0.36, 1), height 0.34s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.34s ease, border-radius 0.34s ease;
|
|
}
|
|
.card-morph-target {
|
|
animation: cardMorphTarget 0.34s ease;
|
|
}
|
|
@keyframes cardMorphTarget {
|
|
0% { transform: scale(1); box-shadow: 0 0 0 0 transparent; }
|
|
45% { transform: scale(1.015); box-shadow: 0 0 0 2px var(--t-accent2); }
|
|
100% { transform: scale(1); box-shadow: 0 0 0 0 transparent; }
|
|
}
|
|
|
|
/* Undo hint */
|
|
#undo-hint { opacity: 0; transition: opacity 0.3s, transform 0.3s; pointer-events: none; border-radius: 4px; }
|
|
|
|
/* Overlays */
|
|
.overlay { transition: opacity 0.15s; }
|
|
.overlay:not(.open) { opacity: 0; pointer-events: none; }
|
|
.overlay.open { opacity: 1; }
|
|
|
|
/* Detail image fullscreen */
|
|
body.detail-image-fs-open { overflow: hidden; }
|
|
#detail-body img { cursor: zoom-in; }
|
|
.detail-image-fs-overlay {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 170;
|
|
display: none;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 24px;
|
|
background: rgba(2, 6, 23, 0.94);
|
|
}
|
|
.detail-image-fs-overlay.open { display: flex; }
|
|
.detail-image-fs-stage {
|
|
position: relative;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
.detail-image-fs-img {
|
|
max-width: min(96vw, 1900px);
|
|
max-height: 94vh;
|
|
object-fit: contain;
|
|
border: 1px solid var(--t-border);
|
|
box-shadow: 0 16px 60px rgba(0, 0, 0, 0.55);
|
|
cursor: grab;
|
|
user-select: none;
|
|
-webkit-user-drag: none;
|
|
will-change: transform;
|
|
}
|
|
.detail-image-fs-overlay.dragging .detail-image-fs-img { cursor: grabbing; }
|
|
.detail-image-fs-close {
|
|
position: fixed;
|
|
top: 12px;
|
|
right: 12px;
|
|
z-index: 171;
|
|
width: 34px;
|
|
height: 34px;
|
|
display: grid;
|
|
place-items: center;
|
|
padding: 0;
|
|
border: none;
|
|
background: transparent;
|
|
color: #fff;
|
|
font-family: inherit;
|
|
cursor: pointer;
|
|
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.7);
|
|
transition: color 0.12s, transform 0.12s;
|
|
}
|
|
.detail-image-fs-close:hover {
|
|
color: var(--t-accent);
|
|
transform: scale(1.06);
|
|
}
|
|
|
|
/* Theme picker */
|
|
.theme-picker { position: relative; }
|
|
.theme-menu { display: none; position: absolute; bottom: calc(100% + 8px); right: 0; z-index: 200; }
|
|
.theme-picker.open .theme-menu { display: block; }
|
|
|
|
/* Buckets */
|
|
.bucket-tab.active, .bucket-tab.active:hover { background: var(--t-accent); color: var(--t-bg); border-color: var(--t-accent); }
|
|
.bucket-tab.drag-over {
|
|
background: var(--t-accent) !important;
|
|
color: var(--t-bg) !important;
|
|
border-color: var(--t-accent) !important;
|
|
transform: scale(1.05);
|
|
transition: all 0.15s ease;
|
|
}
|
|
.bucket-tab.drag-over i,
|
|
.bucket-tab.drag-over span { color: inherit !important; }
|
|
.bucket-add.drag-over {
|
|
background: var(--t-accent) !important;
|
|
color: var(--t-bg) !important;
|
|
transform: scale(1.05);
|
|
transition: all 0.15s ease;
|
|
}
|
|
.bucket-add.drag-over i,
|
|
.bucket-add.drag-over span { color: inherit !important; }
|
|
/* Bucket reorder drop cursor — a single absolutely-positioned vertical
|
|
line that lives inside #bucket-bar and gets repositioned by the
|
|
dragover handler in `public/js/modules/buckets.js`. Showing the cursor
|
|
*between* tiles makes the insert point unambiguous next to the
|
|
super-bucket nest gesture (center 50% → nest, edges 25% → reorder). */
|
|
#bucket-drop-cursor {
|
|
position: absolute;
|
|
top: 4px;
|
|
bottom: 4px;
|
|
width: 3px;
|
|
background: var(--t-accent);
|
|
box-shadow: 0 0 6px color-mix(in srgb, var(--t-accent) 70%, transparent);
|
|
border-radius: 1px;
|
|
pointer-events: none;
|
|
z-index: 4;
|
|
display: none;
|
|
}
|
|
/* Drop-into-center highlight: target tile gets an outline that says
|
|
"release here to nest". Distinct from the insertion cursor so the
|
|
user can tell reorder vs. nest at a glance. */
|
|
.bucket-tab.bucket-nest-target {
|
|
outline: 2px dashed var(--t-accent) !important;
|
|
outline-offset: 1px;
|
|
background: color-mix(in srgb, var(--t-accent) 12%, transparent) !important;
|
|
}
|
|
|
|
/* Unread dot on the fixed `all` bucket-tab — appears when new cards
|
|
arrive while the user is viewing a different bucket. Cleared the
|
|
moment the user navigates back to all (renderBoard drains the
|
|
unseen set + pulses the cards). The dot sits *inside* the tab's
|
|
top-right corner because the parent #main-header has overflow:
|
|
hidden — anything positioned outside the tab gets clipped twice
|
|
(overflow-x-auto on bucket-bar + overflow:hidden on header). */
|
|
.bucket-tab.has-unread { position: relative; }
|
|
.bar-unread-dot {
|
|
position: absolute; top: 3px; right: 3px;
|
|
width: 7px; height: 7px; border-radius: 50%;
|
|
background: var(--t-danger);
|
|
box-shadow: 0 0 6px color-mix(in srgb, var(--t-danger) 75%, transparent);
|
|
pointer-events: none;
|
|
z-index: 1;
|
|
}
|
|
|
|
/* "Look at me" pulse on cards that just arrived. Triggered by
|
|
board.js when the all view is shown with unseen cards in the set —
|
|
either because new cards arrived while the user was already on
|
|
all, or because the user just navigated to all and the unseen ids
|
|
accumulated since the last visit. 3 short accent flashes, then
|
|
the class is stripped automatically by setTimeout. */
|
|
@keyframes card-pulse-new {
|
|
0%, 100% {
|
|
box-shadow: var(--ui-shadow-card, none),
|
|
0 0 0 0 color-mix(in srgb, var(--t-accent) 80%, transparent);
|
|
}
|
|
50% {
|
|
box-shadow: var(--ui-shadow-card, none),
|
|
0 0 18px 4px color-mix(in srgb, var(--t-accent) 75%, transparent),
|
|
inset 0 0 0 2px color-mix(in srgb, var(--t-accent) 70%, transparent);
|
|
}
|
|
}
|
|
.board-card.card-pulse-new {
|
|
animation: card-pulse-new 0.8s ease-in-out 3;
|
|
z-index: 5;
|
|
}
|
|
/* Pin the cadence so anim-fast/anim-slow can't strobe it */
|
|
body.anim-fast .board-card.card-pulse-new,
|
|
body.anim-slow .board-card.card-pulse-new {
|
|
animation: card-pulse-new 0.8s ease-in-out 3 !important;
|
|
animation-duration: 0.8s !important;
|
|
}
|
|
|
|
/* Super-bucket dropdown popover. Positioned via inline styles by
|
|
openSuperDropdown() in buckets.js. Lives on document.body (not
|
|
inside #bucket-bar) so it can escape the bar's overflow:auto. */
|
|
#bucket-super-popover {
|
|
z-index: 220;
|
|
background: var(--t-surface);
|
|
border: 1px solid var(--t-border);
|
|
border-radius: var(--ui-radius-modal);
|
|
box-shadow: var(--ui-shadow-modal, 0 10px 30px rgba(0,0,0,0.4));
|
|
padding: 4px;
|
|
min-width: 180px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
}
|
|
|
|
/* Tag chips */
|
|
.tag-chip {
|
|
display: inline-flex; align-items: center; padding: 1px 6px;
|
|
font-size: 9px; font-family: var(--t-font-pixel, 'Silkscreen', cursive);
|
|
color: var(--t-accent); background: color-mix(in srgb, var(--t-accent) 12%, transparent);
|
|
border: 1px solid color-mix(in srgb, var(--t-accent) 25%, transparent);
|
|
border-radius: 2px; cursor: pointer; line-height: 1.4;
|
|
transition: background 0.12s, border-color 0.12s;
|
|
}
|
|
.tag-seg { cursor: pointer; border-radius: 1px; padding: 0 1px; }
|
|
.tag-seg:hover { text-decoration: underline; color: var(--t-bright); }
|
|
.tag-sep { color: var(--t-dim); margin: 0 0.5px; pointer-events: none; }
|
|
.tag-chip-more { color: var(--t-dim); background: transparent; border-color: var(--t-border); cursor: default; }
|
|
#tag-bar { border-top: 1px dashed color-mix(in srgb, var(--t-accent) 30%, transparent); }
|
|
|
|
/* Toolbar */
|
|
.toolbar-btn { color: var(--t-muted); padding: 4px 6px; border-radius: 3px; transition: color 0.1s, background 0.1s; cursor: pointer; background: none; border: none; font-family: inherit; }
|
|
.toolbar-btn:hover { color: var(--t-bright); background: var(--t-raised); }
|
|
|
|
/* Editor */
|
|
#editor-body { tab-size: 4; resize: none; }
|
|
#editor-body:focus { outline: none; }
|
|
#editor-gutter::-webkit-scrollbar { display: none; }
|
|
#editor-gutter > div { padding-right: 2px; box-sizing: border-box; }
|
|
.gutter-active { color: var(--t-accent) !important; font-weight: 700; background: var(--t-raised); }
|
|
|
|
/* Search */
|
|
#search { backdrop-filter: blur(4px); }
|
|
#search-input { caret-color: var(--t-accent); }
|
|
#search-input:focus { outline: none; }
|
|
.search-hit { scroll-margin: 8px; }
|
|
.search-hit.active { background: var(--t-raised); }
|
|
.search-hit mark, #search-preview-content mark { background: none; color: var(--t-accent); font-weight: 700; }
|
|
|
|
/* Context menu */
|
|
#ctx-menu .ctx-item, #bucket-ctx .ctx-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 6px 12px;
|
|
font-size: 11px;
|
|
color: var(--t-text);
|
|
cursor: pointer;
|
|
transition: background 0.1s;
|
|
width: 100%;
|
|
background: none;
|
|
border: none;
|
|
font-family: inherit;
|
|
text-align: left;
|
|
}
|
|
#ctx-menu .ctx-item:hover, #bucket-ctx .ctx-item:hover { background: var(--t-raised); }
|
|
#ctx-menu .ctx-item.danger, #bucket-ctx .ctx-item.danger { color: var(--t-danger); }
|
|
#ctx-menu .ctx-item.danger:hover, #bucket-ctx .ctx-item.danger:hover { background: var(--t-danger); color: var(--t-bg); }
|
|
|
|
/* Card links */
|
|
.card-link { color: var(--t-accent2); cursor: pointer; border-bottom: 1px dashed var(--t-accent2); transition: color 0.1s; }
|
|
.card-link:hover { color: var(--t-accent); border-color: var(--t-accent); }
|
|
.card-link.broken { color: var(--t-danger); border-color: var(--t-danger); opacity: 0.6; }
|
|
|
|
/* Autocomplete */
|
|
#link-ac .ac-item, #tag-ac .ac-item, #snippet-ac .ac-item { padding: 6px 12px; cursor: pointer; transition: background 0.1s; }
|
|
#link-ac .ac-item:hover, #link-ac .ac-item.active, #tag-ac .ac-item:hover, #tag-ac .ac-item.active, #snippet-ac .ac-item:hover, #snippet-ac .ac-item.active { background: var(--t-raised); }
|
|
#link-ac .ac-item .ac-title, #tag-ac .ac-item .ac-title, #snippet-ac .ac-item .ac-title { font-size: 12px; color: var(--t-bright); }
|
|
#snippet-ac .ac-item { display: flex; align-items: center; gap: 4px; }
|
|
#link-ac .ac-item .ac-bucket { font-size: 9px; color: var(--t-dim); margin-left: 6px; }
|
|
|
|
/* Prose (markdown) */
|
|
.prose { line-height: 1.75; }
|
|
.prose h1, .prose h2, .prose h3, .prose h4 { color: var(--t-bright); font-family: var(--t-font-headline, var(--t-font-pixel, 'Silkscreen', cursive)); margin: 1.5em 0 0.6em; font-weight: 700; }
|
|
.prose h1 { font-size: 1.8em; padding-bottom: 0.3em; border-bottom: 1px solid var(--t-border); }
|
|
.prose h2 { font-size: 1.4em; }
|
|
.prose h3 { font-size: 1.15em; }
|
|
.prose h4 { font-size: 1em; color: var(--t-muted); }
|
|
.prose p { margin: 0.75em 0; min-height: 1.2em; }
|
|
.prose a { color: var(--t-accent); text-decoration: underline; text-underline-offset: 2px; }
|
|
.prose strong { color: var(--t-bright); font-weight: 700; }
|
|
.prose em { font-style: italic; }
|
|
.prose code { background: var(--t-bg); border: 1px solid var(--t-border); padding: 0.15em 0.4em; border-radius: 3px; font-size: 0.88em; }
|
|
.prose pre { background: var(--t-bg); border: 1px solid var(--t-border); padding: 0.8em 1em; overflow-x: auto; margin: 0.8em 0; border-radius: 3px; position: relative; }
|
|
.prose pre code { background: none; border: none; padding: 0; font-size: 0.85em; }
|
|
.prose pre .code-copy {
|
|
position: absolute; top: 4px; right: 4px;
|
|
background: var(--t-surface); border: 1px solid var(--t-border);
|
|
color: var(--t-dim); cursor: pointer;
|
|
width: 26px; height: 26px; border-radius: 3px;
|
|
display: flex; align-items: center; justify-content: center;
|
|
opacity: 0; transition: opacity 0.15s, color 0.15s;
|
|
font-size: 13px; padding: 0;
|
|
}
|
|
.prose pre:hover .code-copy { opacity: 1; }
|
|
.prose pre .code-copy:hover { color: var(--t-accent); border-color: var(--t-accent); }
|
|
.prose pre .code-copy.copied { color: var(--t-accent); opacity: 1; }
|
|
|
|
/* Lists — proper bullets */
|
|
.prose ul { list-style: disc; padding-left: 1.6em; margin: 0.75em 0; }
|
|
.prose ul ul { list-style: circle; }
|
|
.prose ul ul ul { list-style: square; }
|
|
.prose ol { list-style: decimal; padding-left: 1.6em; margin: 0.75em 0; }
|
|
.prose li { margin: 0.35em 0; padding-left: 0.3em; }
|
|
.prose li::marker { color: var(--t-accent); }
|
|
.prose li > ul, .prose li > ol { margin: 0.2em 0; }
|
|
|
|
/* Tree mode for lists — works on .prose.tree (global) or .tree-list (per-list) */
|
|
.prose.tree ul, .prose .tree-list, .prose .tree-list ul { list-style: none; padding-left: 0; }
|
|
.prose.tree ul ul, .prose .tree-list ul { padding-left: 1.4em; }
|
|
.prose.tree li, .prose .tree-list li {
|
|
position: relative;
|
|
padding-left: 1.5em;
|
|
margin: 0.15em 0;
|
|
}
|
|
.prose.tree li::before, .prose .tree-list li::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: 0.25em;
|
|
top: 0;
|
|
bottom: 0;
|
|
width: 2px;
|
|
background: var(--t-dim);
|
|
border-radius: 1px;
|
|
}
|
|
.prose.tree li::after, .prose .tree-list li::after {
|
|
content: '';
|
|
position: absolute;
|
|
left: 0.25em;
|
|
top: 0.7em;
|
|
width: 1em;
|
|
height: 2px;
|
|
background: var(--t-dim);
|
|
border-radius: 1px;
|
|
}
|
|
.prose.tree li:last-child::before, .prose .tree-list li:last-child::before {
|
|
bottom: calc(100% - 0.7em);
|
|
}
|
|
.prose.tree li::marker, .prose .tree-list li::marker { color: transparent; font-size: 0; }
|
|
|
|
/* Explicit bullet-list keeps bullets even when global tree is on */
|
|
.prose.tree .bullet-list, .prose.tree .bullet-list ul { list-style: disc; padding-left: 1.6em; }
|
|
.prose.tree .bullet-list li { padding-left: 0.3em; }
|
|
.prose.tree .bullet-list li::before, .prose.tree .bullet-list li::after { display: none; }
|
|
.prose.tree .bullet-list li::marker { color: var(--t-accent); font-size: inherit; }
|
|
.prose blockquote { border-left: 3px solid var(--t-accent); padding-left: 1em; margin: 0.8em 0; color: var(--t-muted); }
|
|
.prose hr { border: none; border-top: 1px solid var(--t-border); margin: 1.2em 0; }
|
|
/* Todo component */
|
|
.prose ul:has(.todo-item), .prose ol:has(.todo-item) { padding-left: 1.5em; }
|
|
.todo-item {
|
|
list-style: none !important;
|
|
margin: 0 !important;
|
|
margin-left: -1.5em !important;
|
|
display: flex !important;
|
|
align-items: flex-start;
|
|
gap: 10px;
|
|
min-height: 28px;
|
|
padding: 5px 0;
|
|
}
|
|
.todo-item { cursor: pointer; }
|
|
.todo-item:hover { background: color-mix(in srgb, var(--t-raised) 50%, transparent); }
|
|
.todo-item.todo-done .todo-text { color: var(--t-dim); text-decoration: line-through; }
|
|
.todo-text { flex: 1; min-width: 0; line-height: 1.5; user-select: none; }
|
|
.todo-cb {
|
|
width: 18px; height: 18px; min-width: 18px;
|
|
transform: translateY(-9px);
|
|
border: 2px solid var(--t-border); border-radius: 3px;
|
|
background: transparent; cursor: pointer;
|
|
display: flex; align-items: center; justify-content: center;
|
|
padding: 0; font-size: 0;
|
|
transition: border-color 0.12s, background 0.12s;
|
|
}
|
|
.todo-cb:hover { border-color: var(--t-accent); }
|
|
.todo-cb.checked { background: var(--t-accent); border-color: var(--t-accent); }
|
|
.todo-cb i { font-size: 12px; transition: opacity 0.12s; }
|
|
.todo-cb:not(.checked) i { opacity: 0; color: var(--t-accent); }
|
|
.todo-cb:not(.checked):hover i { opacity: 0.5; }
|
|
.todo-cb.checked i { opacity: 1; color: var(--t-bg); }
|
|
.prose input[type="checkbox"] { display: none; }
|
|
|
|
/* Todo progress bar */
|
|
.todo-progress { display: flex; flex: 1; gap: 3px; }
|
|
.todo-seg { flex: 1; height: 5px; border-radius: 3px; transition: background 0.2s; }
|
|
|
|
.prose audio {
|
|
width: 100%;
|
|
max-width: 400px;
|
|
margin: 0.8em 0;
|
|
border-radius: 6px;
|
|
}
|
|
.prose img {
|
|
max-width: 100%;
|
|
height: auto;
|
|
border-radius: 4px;
|
|
border: 1px solid var(--t-border);
|
|
margin: 0.8em 0;
|
|
cursor: nwse-resize;
|
|
}
|
|
.prose img.resizing { outline: 2px solid var(--t-accent); }
|
|
|
|
/* Preview panes: ensure the prose content never overflows horizontally so
|
|
images can resolve their `max-width: 100%` against a constrained container.
|
|
Without this, images with explicit width attrs (e.g. <img width="2000">)
|
|
push the content box wider than the flex pane. */
|
|
#editor-preview,
|
|
#board-preview-body {
|
|
overflow-x: hidden;
|
|
min-width: 0;
|
|
}
|
|
#preview-content,
|
|
#board-preview-body > * {
|
|
max-width: 100%;
|
|
min-width: 0;
|
|
}
|
|
#editor-preview .prose img,
|
|
#board-preview-body.prose img,
|
|
#board-preview-body .prose img {
|
|
max-width: 100%;
|
|
width: auto;
|
|
height: auto;
|
|
}
|
|
.prose table { border-collapse: collapse; width: 100%; margin: 0.8em 0; }
|
|
.prose th, .prose td { border: 1px solid var(--t-border); padding: 0.4em 0.8em; text-align: left; }
|
|
.prose th { background: var(--t-raised); color: var(--t-bright); font-weight: 600; }
|
|
|
|
/* Markdown gallery */
|
|
.md-gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 6px; margin: 0.8em 0; }
|
|
.md-gallery-item { aspect-ratio: 1; overflow: hidden; border-radius: 4px; border: 1px solid var(--t-border); cursor: pointer; }
|
|
.md-gallery-item img { width: 100% !important; height: 100% !important; object-fit: cover !important; display: block; max-width: none !important; margin: 0 !important; border: none !important; border-radius: 0 !important; cursor: pointer !important; }
|
|
.md-gallery-item:hover img { transform: scale(1.05); }
|
|
|
|
/* Image fullscreen prev/next */
|
|
.detail-image-fs-nav { position: fixed; top: 50%; transform: translateY(-50%); z-index: 172; width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,0.5); border: 1px solid rgba(255,255,255,0.15); color: rgba(255,255,255,0.7); font-size: 20px; cursor: pointer; border-radius: 50%; transition: background 0.15s, color 0.15s; }
|
|
.detail-image-fs-nav:hover { background: rgba(0,0,0,0.8); color: #fff; }
|
|
.detail-image-fs-prev { left: 16px; }
|
|
.detail-image-fs-next { right: 16px; }
|
|
.detail-image-fs-counter { position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%); z-index: 172; font-family: var(--font-pixel, monospace); font-size: 10px; color: rgba(255,255,255,0.5); }
|
|
|
|
/* Temporary reading aid in open detail view (not persisted) */
|
|
#detail-body .detail-reading-resize {
|
|
position: relative;
|
|
padding-bottom: 1rem;
|
|
min-height: 1.8em;
|
|
}
|
|
#detail-body .detail-reading-resize:hover,
|
|
#detail-body .detail-reading-resize.resizing {
|
|
outline: 1px dashed var(--t-border);
|
|
outline-offset: 1px;
|
|
}
|
|
#detail-body .detail-font-dragline {
|
|
position: absolute;
|
|
left: 0.4rem;
|
|
right: 0.4rem;
|
|
bottom: 0.2rem;
|
|
height: 10px;
|
|
padding: 0;
|
|
border: none;
|
|
background: transparent;
|
|
cursor: ns-resize;
|
|
opacity: 0;
|
|
transition: opacity 0.12s ease;
|
|
}
|
|
#detail-body .detail-font-dragline::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: 0;
|
|
right: 0;
|
|
top: 4px;
|
|
border-top: 1px dashed var(--t-border);
|
|
transition: border-color 0.12s ease;
|
|
}
|
|
#detail-body .detail-reading-resize:hover .detail-font-dragline,
|
|
#detail-body .detail-reading-resize.resizing .detail-font-dragline {
|
|
opacity: 1;
|
|
}
|
|
#detail-body .detail-font-dragline:hover::before,
|
|
#detail-body .detail-reading-resize.resizing .detail-font-dragline::before {
|
|
border-color: var(--t-accent);
|
|
}
|