mirror of
https://github.com/svm0n/datadeck.git
synced 2026-07-22 08:31:46 +00:00
A multi-pass mobile session. Each change addresses a specific iOS report, but they hang together because they're all the same shape of bug: the plugin is rendered in WKWebView, which has its own opinions about keyboards, scroll, hover, layer compositing, and table layout. — Kanban card: bottom hover-button row removed. Title-tap opens the expander (already wired); small +/📄 in the title row creates or opens the sidecar .md. One affordance per action; recovers vertical density. — Mobile inline-note edit routes to the expander modal instead of the inline textarea. iOS keyboard pop scrolls focused inputs into view, which yanks the user's y-position. The modal owns its own viewport so the keyboard resizes the modal, not the underlying card list. — Modal keyboard handling. Two stacked fixes after the obvious ones didn't take: 1. visualViewport.height (not dvh, which iOS Obsidian doesn't honour) written into --csv-modal-vh on resize+scroll, used in max-height. 2. On mobile, align-self: flex-start + margin-top: 10px instead of overriding the transform — Obsidian centers via parent flex, not a transform on the modal, so translating the modal kicks it off the left edge. align-self leaves horizontal centering intact and top-anchors the modal so its sticky-bottom footer stays above the keyboard. 3. .modal-content overflow-y: auto + .csv-expander-footer position: sticky bottom: 0 — modal scrolls if content overflows, footer floats above the scroll content and is always reachable. — Mobile toolbar: search input collapses to a 🔍 toggle that overlays the toolbar (position: absolute, not flex displacement — displacement pushed Cards/Kanban/Table off the visible toolbar area and read as 'the view disappeared'). Expanded state holds while the query is non-empty; empty-blur recollapses. Library renamed → Cards, By Genre → Kanban so the labels read correctly for non-library files and the toolbar fits one row. — renderLibrary used to render its own .csv-library-search alongside the toolbar one — both wrote to this.searchQuery, pure duplicate. Removed. — Table lag on iPhone (was the worst report — 'scrolls to notes column and freezes'). Three causes: 1. Per-row requestAnimationFrame for clip detection: 200 rows × N cells × forced reflow each. Batched to one rAF, skipped on touch. 2. td::after gradient pseudo on every td (opacity:0 by default) = N×M compositor pseudos. Scoped to .csv-cell--clipped only; on touch the class is never added, so mobile pays zero. 3. position:relative on every td = N×M stacking contexts. Also scoped to .csv-cell--clipped only. 4. -webkit-line-clamp on the td itself overrode display:table-cell and broke row borders. Moved the clamp onto the inner span so the td stays a normal table-cell. 5. tr:hover gated behind @media (hover: hover) — iOS sticky-hover was leaving the last-touched row tinted and forcing a recomposite on every scroll frame. Also dropped contain: content on tbody tr; CSS containment on table-internal elements is spec-undefined and iOS WebKit was occasionally dropping rows from layout. 6. .csv-table-wrapper gets will-change: scroll-position so iOS promotes it to its own compositor layer. — Mobile table search input debounced by 120ms so each keystroke doesn't trigger a full content re-render. Search-focus also restores window scroll on the next frame — iOS WKWebView auto-scrolls focused inputs into view even with preventScroll, pushing the table below the keyboard. — Kanban notes preview: overflow-wrap: anywhere so a URL or hash in the note doesn't push the card wider than the column. — Build-time timestamp baked into the bundle via esbuild's define; surfaced as 'Built YYYY-MM-DD HH:mm' in the ⋯ menu + a small ⓘ button on desktop. Useful on iPhone where iCloud sync of the deployed bundle can lag and there's no native way to confirm the new code arrived. — Transition durations capped: 0.3s → 0.15s on the habit progress bar, 0.2s → 0.1s elsewhere. UI feels snappier; transitions are compositor- driven so shortening doesn't slow anything down. Tests: 88 plugin + 21 mobile-dashboard, all green. Typecheck clean. Bundle: 300 KB minified (unchanged). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2849 lines
69 KiB
CSS
2849 lines
69 KiB
CSS
/* ─── Root & Theme Variables ──────────────────────────────────────────────── */
|
||
|
||
/* Apple-muted status palette: dialled-darker iOS systemGreen / systemBlue /
|
||
systemRed / systemOrange so they read as confident-but-quiet on both
|
||
light and dark themes, instead of the punchy Google Material set. The
|
||
*-bg companions are the same hue at low opacity for fills. */
|
||
:root {
|
||
--csv-green: #30A14E;
|
||
--csv-green-bg: rgba(52, 199, 89, 0.13);
|
||
--csv-blue: #2E7CE6;
|
||
--csv-blue-bg: rgba(0, 122, 255, 0.13);
|
||
--csv-red: #D5443B;
|
||
--csv-red-bg: rgba(255, 59, 48, 0.12);
|
||
--csv-amber: #C18000;
|
||
--csv-amber-bg: rgba(255, 149, 0, 0.13);
|
||
}
|
||
|
||
.csv-card-view-root {
|
||
--csv-bg: var(--background-primary);
|
||
--csv-surface: var(--background-secondary);
|
||
--csv-surface-2: var(--background-modifier-border);
|
||
--csv-border: var(--background-modifier-border);
|
||
--csv-text: var(--text-normal);
|
||
--csv-text-muted: var(--text-muted);
|
||
--csv-text-faint: var(--text-faint);
|
||
--csv-accent: var(--interactive-accent);
|
||
--csv-accent-hover: var(--interactive-accent-hover);
|
||
--csv-radius: 8px;
|
||
--csv-radius-sm: 5px;
|
||
--csv-shadow: 0 1px 4px rgba(0,0,0,0.08), 0 0 0 1px var(--csv-border);
|
||
--csv-shadow-hover: 0 4px 16px rgba(0,0,0,0.12), 0 0 0 1px var(--csv-accent);
|
||
--csv-font-mono: var(--font-monospace);
|
||
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
overflow: hidden;
|
||
background: var(--csv-bg);
|
||
}
|
||
|
||
/* ─── Toolbar ─────────────────────────────────────────────────────────────── */
|
||
|
||
.csv-toolbar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 10px 16px;
|
||
border-bottom: 1px solid var(--csv-border);
|
||
background: var(--csv-surface);
|
||
flex-shrink: 0;
|
||
gap: 12px;
|
||
flex-wrap: wrap; /* Lets controls drop to a second row on narrow viewports instead of getting clipped off the right edge. */
|
||
row-gap: 8px;
|
||
}
|
||
|
||
.csv-toolbar-title {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: var(--csv-text);
|
||
letter-spacing: 0.01em;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.csv-toolbar-controls {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
flex-wrap: wrap; /* allow controls to wrap onto a second line on narrow viewports */
|
||
row-gap: 8px;
|
||
}
|
||
|
||
/* Overflow ⋯ button is mobile-only — hidden by default, shown via the
|
||
mobile media query below. Secondary buttons (⚙ / 📱 / 💾) flip the
|
||
other direction. Both buttons exist in the DOM at all times so the
|
||
layout responds to window resize / orientation flips without re-render. */
|
||
.csv-cfg-btn-overflow { display: none; }
|
||
/* Search toggle is mobile-only — desktop always shows the input directly. */
|
||
.csv-search-toggle { display: none; }
|
||
|
||
/* Tiny build-info icon (desktop only) — same shape as the cfg buttons but
|
||
icon-tight so it doesn't compete with the primary actions. Click toasts
|
||
the build timestamp; on mobile the ⋯ menu surfaces the same info. */
|
||
.csv-info-btn {
|
||
padding: 4px 8px;
|
||
font-size: 14px;
|
||
opacity: 0.6;
|
||
}
|
||
.csv-info-btn:hover { opacity: 1; }
|
||
|
||
/* Mobile: hide the filename title (already in the tab) and let the search
|
||
bar shrink so the action buttons don't get pushed off-screen. */
|
||
@media (max-width: 600px) {
|
||
.csv-toolbar-title { display: none; }
|
||
.csv-toolbar { padding: 8px 12px; gap: 8px; }
|
||
/* Keep all controls on a single row; if they don't fit, the controls
|
||
row scrolls horizontally rather than wrapping onto a second row. */
|
||
.csv-toolbar-controls {
|
||
gap: 6px;
|
||
flex-wrap: nowrap;
|
||
overflow-x: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
/* hide scrollbar — it's discoverable enough as a fallback */
|
||
scrollbar-width: none;
|
||
}
|
||
.csv-toolbar-controls::-webkit-scrollbar { display: none; }
|
||
/* Prevent the buttons themselves from shrinking — they'd otherwise
|
||
squish to unreadable widths instead of scrolling. */
|
||
.csv-toolbar-controls > * { flex-shrink: 0; }
|
||
/* On mobile the search input is hidden until the user taps the 🔍 toggle.
|
||
When expanded the input OVERLAYS the toolbar (absolute position over
|
||
the controls row) instead of displacing the mode buttons sideways.
|
||
Without the overlay, expanding the search pushes [Cards|Kanban|Table]
|
||
off the visible toolbar area and the user reads that as "the view
|
||
disappeared". With the overlay the buttons stay put underneath and
|
||
reappear instantly when the search closes.
|
||
`!important` defeats the base `.csv-search-wrap { display: flex; }`
|
||
rule that wins on cascade order otherwise. */
|
||
.csv-toolbar { position: relative; }
|
||
.csv-search-toggle { display: inline-flex !important; }
|
||
.csv-search-wrap { display: none !important; }
|
||
.csv-toolbar--search-expanded .csv-search-toggle { display: none !important; }
|
||
.csv-toolbar--search-expanded .csv-search-wrap {
|
||
display: flex !important;
|
||
position: absolute;
|
||
inset: 0 12px 0 12px; /* match the toolbar's horizontal padding */
|
||
z-index: 5;
|
||
background: var(--csv-surface);
|
||
align-items: center;
|
||
}
|
||
/* `!important` defeats the desktop `.csv-search-input:focus { width:220px }`
|
||
rule which otherwise overflows the toolbar at mobile widths. */
|
||
.csv-search-input { width: 100% !important; min-width: 0; max-width: 100%; }
|
||
/* Toolbar buttons go icon-tight on mobile */
|
||
.csv-cfg-btn, .csv-add-btn { padding: 6px 10px; font-size: 12px; }
|
||
.csv-row-count { display: none; }
|
||
/* Collapse the three secondary actions into the ⋯ overflow menu. */
|
||
.csv-cfg-btn-secondary { display: none; }
|
||
.csv-cfg-btn-overflow {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-width: 32px;
|
||
padding: 6px 10px;
|
||
font-size: 16px;
|
||
line-height: 1;
|
||
letter-spacing: 0;
|
||
}
|
||
/* Mode group: slightly tighter so all three modes fit comfortably on
|
||
iPhone widths without wrapping mid-row. */
|
||
.csv-mode-btn { padding: 4px 8px; font-size: 11px; }
|
||
}
|
||
|
||
.csv-row-count {
|
||
font-size: 11px;
|
||
color: var(--csv-text-muted);
|
||
letter-spacing: 0.04em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.csv-mode-group {
|
||
display: flex;
|
||
gap: 2px;
|
||
background: var(--csv-surface-2);
|
||
padding: 2px;
|
||
border-radius: var(--csv-radius-sm);
|
||
}
|
||
|
||
.csv-mode-btn {
|
||
padding: 4px 10px;
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
border: none;
|
||
background: transparent;
|
||
color: var(--csv-text-muted);
|
||
cursor: pointer;
|
||
transition: all 0.15s ease;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.csv-mode-btn:hover {
|
||
color: var(--csv-text);
|
||
background: var(--csv-surface);
|
||
}
|
||
|
||
.csv-mode-btn.active {
|
||
background: var(--csv-bg);
|
||
color: var(--csv-text);
|
||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
/* ─── Search bar ──────────────────────────────────────────────────────────── */
|
||
|
||
.csv-search-wrap {
|
||
display: flex;
|
||
align-items: center;
|
||
position: relative;
|
||
}
|
||
|
||
.csv-search-input {
|
||
padding: 5px 10px;
|
||
border-radius: var(--csv-radius-sm);
|
||
font-size: 13px;
|
||
border: 1px solid var(--background-modifier-border);
|
||
background: var(--background-primary);
|
||
color: var(--csv-text);
|
||
width: 180px;
|
||
outline: none;
|
||
box-sizing: border-box;
|
||
transition: border-color 0.1s, width 0.1s;
|
||
}
|
||
|
||
.csv-search-input:focus {
|
||
border-color: var(--csv-accent);
|
||
width: 220px;
|
||
}
|
||
|
||
.csv-search-input::placeholder {
|
||
color: var(--csv-text-muted);
|
||
}
|
||
|
||
.csv-search-clear {
|
||
position: absolute;
|
||
right: 4px;
|
||
padding: 2px 6px;
|
||
border: none;
|
||
background: transparent;
|
||
color: var(--csv-text-muted);
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
line-height: 1;
|
||
}
|
||
|
||
.csv-search-clear:hover {
|
||
color: var(--csv-text);
|
||
}
|
||
|
||
.csv-search-results {
|
||
padding: 8px 0;
|
||
font-size: 13px;
|
||
color: var(--csv-text-muted);
|
||
}
|
||
|
||
.csv-add-btn {
|
||
padding: 5px 12px;
|
||
border-radius: var(--csv-radius-sm);
|
||
font-size: 12px;
|
||
border: none;
|
||
background: var(--csv-accent);
|
||
color: white;
|
||
cursor: pointer;
|
||
font-weight: 600;
|
||
transition: background 0.15s ease;
|
||
letter-spacing: 0.02em;
|
||
}
|
||
|
||
.csv-add-btn:hover {
|
||
background: var(--csv-accent-hover);
|
||
}
|
||
|
||
/* ─── Content area ────────────────────────────────────────────────────────── */
|
||
|
||
.csv-content-area {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 16px;
|
||
min-height: 0; /* lets the flex child shrink so internal scroll works */
|
||
}
|
||
|
||
/* In kanban mode the columns scroll internally — outer y-scroll would
|
||
stack two scrollbars on the same content. */
|
||
.csv-content-area--no-yscroll {
|
||
overflow-y: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.csv-empty-state {
|
||
color: var(--csv-text-muted);
|
||
font-size: 14px;
|
||
text-align: center;
|
||
padding: 40px 20px;
|
||
}
|
||
|
||
/* Bigger empty state for full-page conditions (no headers / no rows) —
|
||
centered, with a heading + optional CTA button. Quiet by default, so
|
||
the page reads as "nothing here yet" rather than "broken". */
|
||
.csv-empty-state--big {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 80px 24px;
|
||
max-width: 460px;
|
||
margin: 0 auto;
|
||
}
|
||
.csv-empty-state--big h3 {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: var(--csv-text);
|
||
margin: 0;
|
||
}
|
||
.csv-empty-state--big p {
|
||
margin: 0;
|
||
color: var(--csv-text-muted);
|
||
font-size: 13px;
|
||
line-height: 1.5;
|
||
}
|
||
.csv-empty-state-action {
|
||
margin-top: 6px;
|
||
padding: 8px 16px;
|
||
background: var(--csv-accent);
|
||
color: white;
|
||
border: none;
|
||
border-radius: var(--csv-radius-sm);
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
}
|
||
.csv-empty-state-action:hover { background: var(--csv-accent-hover); }
|
||
.csv-empty-state-hint {
|
||
font-size: 11px !important;
|
||
color: var(--csv-text-faint) !important;
|
||
margin-top: 4px !important;
|
||
}
|
||
|
||
/* ─── Grid View ───────────────────────────────────────────────────────────── */
|
||
|
||
.csv-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||
gap: 14px;
|
||
align-items: start;
|
||
}
|
||
|
||
.csv-card {
|
||
background: var(--csv-surface);
|
||
border-radius: var(--csv-radius);
|
||
box-shadow: var(--csv-shadow);
|
||
transition: box-shadow 0.1s ease, transform 0.1s ease;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.csv-card:hover {
|
||
box-shadow: 0 3px 12px rgba(0,0,0,0.1), 0 0 0 1px var(--csv-border);
|
||
}
|
||
|
||
.csv-card.expanded {
|
||
box-shadow: var(--csv-shadow-hover);
|
||
grid-column: span 1;
|
||
}
|
||
|
||
/* Card header */
|
||
.csv-card-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 14px 16px;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
}
|
||
|
||
.csv-card-header:hover {
|
||
background: var(--background-modifier-hover);
|
||
}
|
||
|
||
.csv-card-title-block {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.csv-card-title {
|
||
/* 13px to match .csv-kanban-card-title — same semantic across views. */
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: var(--csv-text);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
line-height: 1.3;
|
||
}
|
||
|
||
.csv-card-subtitle {
|
||
font-size: 12px;
|
||
color: var(--csv-text-muted);
|
||
margin-top: 2px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.csv-expand-btn {
|
||
font-size: 10px;
|
||
color: var(--csv-text-faint);
|
||
cursor: pointer;
|
||
padding: 2px 4px;
|
||
flex-shrink: 0;
|
||
transition: color 0.15s;
|
||
}
|
||
|
||
.csv-expand-btn:hover {
|
||
color: var(--csv-text-muted);
|
||
}
|
||
|
||
/* Status badge */
|
||
.csv-status-badge {
|
||
font-size: 10px;
|
||
font-weight: 600;
|
||
letter-spacing: 0.06em;
|
||
text-transform: uppercase;
|
||
padding: 3px 8px;
|
||
border-radius: 20px;
|
||
flex-shrink: 0;
|
||
background: var(--csv-surface-2);
|
||
color: var(--csv-text-muted);
|
||
}
|
||
|
||
/* Status badge color variants — Apple-muted palette */
|
||
.csv-status-badge.status-read,
|
||
.csv-status-badge.status-done,
|
||
.csv-status-badge.status-watched,
|
||
.csv-status-badge.status-finished,
|
||
.csv-status-badge.status-completed {
|
||
background: var(--csv-green-bg);
|
||
color: var(--csv-green);
|
||
}
|
||
|
||
.csv-status-badge.status-reading,
|
||
.csv-status-badge.status-watching,
|
||
.csv-status-badge.status-in-progress,
|
||
.csv-status-badge.status-ongoing {
|
||
background: var(--csv-blue-bg);
|
||
color: var(--csv-blue);
|
||
}
|
||
|
||
.csv-status-badge.status-to-read,
|
||
.csv-status-badge.status-to-watch,
|
||
.csv-status-badge.status-want,
|
||
.csv-status-badge.status-backlog,
|
||
.csv-status-badge.status-queued {
|
||
background: var(--csv-amber-bg);
|
||
color: var(--csv-amber);
|
||
}
|
||
|
||
.csv-status-badge.status-dropped,
|
||
.csv-status-badge.status-abandoned,
|
||
.csv-status-badge.status-dnf {
|
||
background: var(--csv-red-bg);
|
||
color: var(--csv-red);
|
||
}
|
||
|
||
/* Card body */
|
||
.csv-card-body {
|
||
border-top: 1px solid var(--csv-border);
|
||
padding: 14px 16px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 14px;
|
||
}
|
||
|
||
.csv-card-fields {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
|
||
.csv-field-row {
|
||
display: flex;
|
||
gap: 10px;
|
||
align-items: baseline;
|
||
}
|
||
|
||
.csv-field-label {
|
||
/* Apple convention: row labels (vs section headers) are title case, not
|
||
uppercase. We just title-case at the JS layer (titleCase()) and let the
|
||
value sit in the column with its column heading already conveying scope. */
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
color: var(--csv-text-muted);
|
||
min-width: 80px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.csv-field-value {
|
||
font-size: 13px;
|
||
color: var(--csv-text);
|
||
flex: 1;
|
||
cursor: text;
|
||
border-radius: 3px;
|
||
padding: 1px 4px;
|
||
margin: -1px -4px;
|
||
transition: background 0.15s;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.csv-field-value:hover {
|
||
background: var(--background-modifier-hover);
|
||
}
|
||
|
||
.csv-inline-input {
|
||
width: 100%;
|
||
font-size: 13px;
|
||
background: var(--background-modifier-form-field);
|
||
border: 1px solid var(--csv-accent);
|
||
border-radius: 3px;
|
||
padding: 2px 6px;
|
||
color: var(--csv-text);
|
||
outline: none;
|
||
}
|
||
|
||
/* Notes section */
|
||
.csv-notes-section {
|
||
border-top: 1px solid var(--csv-border);
|
||
padding-top: 12px;
|
||
}
|
||
|
||
.csv-notes-label {
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
letter-spacing: 0.05em;
|
||
text-transform: uppercase;
|
||
color: var(--csv-text-faint);
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.csv-notes-content {
|
||
font-size: 13px;
|
||
color: var(--csv-text);
|
||
cursor: text;
|
||
border-radius: var(--csv-radius-sm);
|
||
padding: 8px;
|
||
background: var(--background-primary);
|
||
border: 1px solid transparent;
|
||
transition: border-color 0.15s;
|
||
min-height: 40px;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.csv-notes-content:hover {
|
||
border-color: var(--csv-border);
|
||
}
|
||
|
||
/* Pass markdown rendered styles through */
|
||
.csv-notes-content p { margin: 0 0 8px; }
|
||
.csv-notes-content p:last-child { margin-bottom: 0; }
|
||
.csv-notes-content ul, .csv-notes-content ol { margin: 4px 0 8px 18px; }
|
||
.csv-notes-content li { margin: 2px 0; }
|
||
.csv-notes-content h1,.csv-notes-content h2,.csv-notes-content h3 { margin: 10px 0 4px; }
|
||
.csv-notes-content code { font-size: 12px; }
|
||
.csv-notes-content blockquote { border-left: 3px solid var(--csv-accent); padding-left: 10px; margin: 6px 0; color: var(--csv-text-muted); }
|
||
|
||
.csv-notes-empty {
|
||
color: var(--csv-text-faint);
|
||
font-size: 13px;
|
||
}
|
||
|
||
.csv-notes-textarea {
|
||
width: 100%;
|
||
min-height: 120px;
|
||
font-size: 13px;
|
||
font-family: var(--csv-font-mono);
|
||
background: var(--background-modifier-form-field);
|
||
border: 1px solid var(--csv-accent);
|
||
border-radius: var(--csv-radius-sm);
|
||
padding: 8px;
|
||
color: var(--csv-text);
|
||
outline: none;
|
||
resize: vertical;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
/* ─── Kanban View ─────────────────────────────────────────────────────────── */
|
||
|
||
.csv-kanban-board {
|
||
display: flex;
|
||
gap: 16px;
|
||
align-items: stretch; /* columns share the board height instead of sizing to content */
|
||
flex: 1;
|
||
min-height: 0;
|
||
overflow-x: auto;
|
||
padding-bottom: 4px;
|
||
/* Snap each column to viewport on touch — stops the awkward "1.5 columns
|
||
half-visible" state on phones. Desktop ignores when there's no horizontal
|
||
overflow. */
|
||
scroll-snap-type: x mandatory;
|
||
scroll-padding-left: 16px;
|
||
}
|
||
.csv-kanban-col {
|
||
scroll-snap-align: start;
|
||
}
|
||
|
||
/* Mobile: one column at a time, snap-scroll between them.
|
||
The desktop 260-300px range looks cramped on a 390px phone — a near-full
|
||
width column reads cleanly, swipe to the next. */
|
||
@media (max-width: 600px) {
|
||
.csv-kanban-board { gap: 12px; }
|
||
.csv-kanban-col {
|
||
min-width: calc(100vw - 60px);
|
||
max-width: calc(100vw - 60px);
|
||
}
|
||
}
|
||
|
||
.csv-kanban-col {
|
||
background: var(--csv-surface);
|
||
border-radius: var(--csv-radius);
|
||
min-width: 260px;
|
||
max-width: 300px;
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
box-shadow: var(--csv-shadow);
|
||
min-height: 0; /* allows .csv-kanban-col-body to actually constrain */
|
||
max-height: 100%;
|
||
}
|
||
|
||
.csv-kanban-col-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 12px 14px 10px;
|
||
border-bottom: 1px solid var(--csv-border);
|
||
}
|
||
|
||
.csv-kanban-col-title {
|
||
/* Section header convention: uppercase + tracked, but muted (not accent) —
|
||
accent-colored headers shout. Muted lets card content do the talking. */
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.06em;
|
||
text-transform: uppercase;
|
||
color: var(--csv-text-muted);
|
||
}
|
||
|
||
.csv-kanban-col-count {
|
||
font-size: 11px;
|
||
color: var(--csv-text-faint);
|
||
background: var(--csv-surface-2);
|
||
padding: 1px 7px;
|
||
border-radius: 10px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.csv-kanban-col-body {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
padding: 10px;
|
||
flex: 1;
|
||
min-height: 0;
|
||
overflow-y: auto; /* internal scroll only — content area no longer y-scrolls in kanban */
|
||
}
|
||
|
||
.csv-kanban-card {
|
||
background: var(--csv-bg);
|
||
border-radius: var(--csv-radius-sm);
|
||
padding: 10px 12px;
|
||
box-shadow: 0 1px 3px rgba(0,0,0,0.07), 0 0 0 1px var(--csv-border);
|
||
cursor: pointer;
|
||
transition: box-shadow 0.15s ease, transform 0.1s ease;
|
||
}
|
||
|
||
.csv-kanban-card:hover {
|
||
box-shadow: 0 3px 10px rgba(0,0,0,0.12), 0 0 0 1px var(--csv-accent);
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
/* Title row holds the title and a small notes-file icon button. Flex so the
|
||
icon hugs the right edge regardless of title length. */
|
||
.csv-kanban-card-title-row {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 6px;
|
||
margin-bottom: 3px;
|
||
}
|
||
|
||
.csv-kanban-card-title {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: var(--csv-text);
|
||
line-height: 1.3;
|
||
flex: 1;
|
||
min-width: 0; /* allow ellipsis inside flex */
|
||
}
|
||
|
||
/* Small notes-file create/open icon — quiet by default, brightens on hover.
|
||
Shown when an .md sidecar exists (📄) or not (+). */
|
||
.csv-kanban-notes-icon {
|
||
flex: 0 0 auto;
|
||
border: none;
|
||
background: transparent;
|
||
color: var(--csv-text-faint);
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
line-height: 1;
|
||
padding: 2px 5px;
|
||
border-radius: 4px;
|
||
opacity: 0.55;
|
||
transition: opacity 0.15s ease, background 0.15s ease, color 0.15s ease;
|
||
}
|
||
.csv-kanban-notes-icon.exists { opacity: 0.9; }
|
||
.csv-kanban-card:hover .csv-kanban-notes-icon { opacity: 1; }
|
||
.csv-kanban-notes-icon:hover {
|
||
color: var(--csv-accent);
|
||
background: var(--background-modifier-hover);
|
||
}
|
||
|
||
.csv-kanban-card-sub {
|
||
font-size: 11px;
|
||
color: var(--csv-text-muted);
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.csv-kanban-card-meta {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 4px;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.csv-kanban-chip {
|
||
font-size: 10px;
|
||
padding: 2px 7px;
|
||
border-radius: 10px;
|
||
background: var(--csv-surface);
|
||
border: 1px solid var(--csv-border);
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.csv-chip-label {
|
||
color: var(--csv-text-faint);
|
||
}
|
||
|
||
.csv-chip-value {
|
||
color: var(--csv-text-muted);
|
||
font-weight: 500;
|
||
}
|
||
|
||
.csv-kanban-notes-indicator {
|
||
font-size: 10px;
|
||
color: var(--csv-text-faint);
|
||
margin-top: 8px;
|
||
letter-spacing: 0.02em;
|
||
}
|
||
|
||
.csv-kanban-add-card {
|
||
margin: 0 12px 12px;
|
||
padding: 7px;
|
||
border: 1px dashed var(--csv-border);
|
||
border-radius: var(--csv-radius-sm);
|
||
background: transparent;
|
||
color: var(--csv-text-faint);
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
width: calc(100% - 24px);
|
||
transition: all 0.15s ease;
|
||
text-align: center;
|
||
}
|
||
|
||
.csv-kanban-add-card:hover {
|
||
border-color: var(--csv-accent);
|
||
color: var(--csv-accent);
|
||
background: var(--background-modifier-hover);
|
||
}
|
||
|
||
/* ─── Table View ──────────────────────────────────────────────────────────── */
|
||
|
||
.csv-table-wrapper {
|
||
overflow-x: auto;
|
||
border-radius: var(--csv-radius);
|
||
box-shadow: var(--csv-shadow);
|
||
/* Hint to the compositor that this is a scroll container — promotes it
|
||
to its own layer on iOS, which smooths scroll on big tables. */
|
||
will-change: scroll-position;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
|
||
.csv-table {
|
||
border-collapse: collapse;
|
||
width: 100%;
|
||
background: var(--csv-surface);
|
||
font-size: 13px;
|
||
}
|
||
|
||
.csv-table th {
|
||
text-align: left;
|
||
padding: 8px 12px;
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.05em;
|
||
text-transform: uppercase;
|
||
color: var(--csv-text-faint);
|
||
background: var(--csv-surface);
|
||
border-bottom: 1px solid var(--csv-border);
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.csv-table td {
|
||
padding: 7px 12px;
|
||
border-bottom: 1px solid var(--csv-border);
|
||
color: var(--csv-text);
|
||
vertical-align: top;
|
||
cursor: text;
|
||
/* Reasonable readable column width; the wrapper x-scrolls if the table
|
||
can't fit. This stops the browser from squeezing every column to ~50px
|
||
when there are many headers. */
|
||
min-width: 160px;
|
||
max-width: 320px;
|
||
/* overflow-wrap (not word-break) keeps words intact and only breaks
|
||
unbreakable strings like URLs. `word-break: break-word` was causing
|
||
long titles to split character-by-character into vertical strips. */
|
||
overflow-wrap: break-word;
|
||
word-break: normal;
|
||
/* Clamp very long content to a few lines — full text is one click away
|
||
via the expander. Without this a single 200-char title makes the row
|
||
30 lines tall and pushes every other row off-screen. */
|
||
max-height: 7.5em;
|
||
overflow: hidden;
|
||
/* position:relative is only needed when the .csv-cell--clipped pseudo
|
||
renders — moving it under that class avoids creating N×M stacking
|
||
contexts for cells that don't need one. */
|
||
}
|
||
.csv-table td.csv-cell--clipped { position: relative; }
|
||
|
||
/* Hover background only on hover-capable devices. iOS sticky-hover
|
||
otherwise leaves the last-touched row tinted and forces a recomposite
|
||
per scroll frame — a major cause of table lag on iPhone. */
|
||
@media (hover: hover) {
|
||
.csv-table tr:hover td { background: var(--background-modifier-hover); }
|
||
.csv-table tr:hover td.csv-cell--clipped::after { background: linear-gradient(to bottom, transparent, var(--background-modifier-hover)); }
|
||
}
|
||
/* Long-content fade-out hint, scoped to cells JS measured as overflowing.
|
||
The pseudo-element previously existed on every td (opacity:0 by default)
|
||
producing N×M compositor pseudo-elements — a material chunk of mobile
|
||
table scroll lag. Now only clipped cells get the pseudo, and clip
|
||
detection is skipped on touch entirely, so mobile pays zero overhead. */
|
||
.csv-table td.csv-cell--clipped::after {
|
||
content: "";
|
||
position: absolute;
|
||
inset: auto 0 0 0;
|
||
height: 14px;
|
||
background: linear-gradient(to bottom, transparent, var(--csv-surface));
|
||
pointer-events: none;
|
||
}
|
||
|
||
.csv-table tr:last-child td {
|
||
border-bottom: none;
|
||
}
|
||
|
||
/* (Previously had `contain: content` on tbody tr for paint isolation, but
|
||
contain on table-internal elements is spec-undefined and iOS WebKit
|
||
sometimes drops rows from layout. Removed — the per-td ::after + position
|
||
pseudo cleanup alone was enough to make scroll smooth.) */
|
||
|
||
/* Notes cell stays a normal table-cell so its row aligns with the others.
|
||
The line-clamp lives on the inner span so it doesn't disrupt table
|
||
layout (display:-webkit-box on the td would override display:table-cell
|
||
and misalign row borders relative to other cells in the same row). */
|
||
.csv-table td.csv-table-notes-cell {
|
||
color: var(--csv-text-muted);
|
||
font-style: italic;
|
||
font-size: 12px;
|
||
min-width: 200px;
|
||
max-width: 320px;
|
||
line-height: 1.4;
|
||
}
|
||
.csv-table td.csv-table-notes-cell > span {
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 3;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.csv-table-action {
|
||
width: 40px;
|
||
text-align: center;
|
||
}
|
||
|
||
.csv-table-del-btn {
|
||
border: none;
|
||
background: transparent;
|
||
color: var(--csv-text-faint);
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
padding: 2px 6px;
|
||
border-radius: 3px;
|
||
opacity: 0;
|
||
transition: opacity 0.15s, color 0.15s;
|
||
}
|
||
|
||
@media (hover: hover) {
|
||
.csv-table tr:hover .csv-table-del-btn { opacity: 1; }
|
||
}
|
||
/* On touch the affordance has to be discoverable without hover, so just
|
||
show it at low opacity always. */
|
||
@media (hover: none) {
|
||
.csv-table-del-btn { opacity: 0.5; }
|
||
}
|
||
|
||
.csv-table-del-btn:hover {
|
||
color: var(--csv-red);
|
||
background: var(--csv-red-bg);
|
||
}
|
||
|
||
/* ─── Notes File Button ───────────────────────────────────────────────────── */
|
||
|
||
.csv-notes-file-bar {
|
||
padding: 10px 0 0;
|
||
}
|
||
|
||
.csv-notes-file-btn {
|
||
width: 100%;
|
||
padding: 8px 12px;
|
||
border-radius: var(--csv-radius-sm);
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: all 0.15s ease;
|
||
text-align: center;
|
||
letter-spacing: 0.02em;
|
||
}
|
||
|
||
.csv-notes-file-btn.new {
|
||
border: 1px dashed var(--csv-border);
|
||
background: transparent;
|
||
color: var(--csv-text-muted);
|
||
}
|
||
|
||
.csv-notes-file-btn.new:hover {
|
||
border-color: var(--csv-accent);
|
||
color: var(--csv-accent);
|
||
background: var(--background-modifier-hover);
|
||
}
|
||
|
||
.csv-notes-file-btn.exists {
|
||
border: 1px solid var(--csv-border);
|
||
background: var(--background-modifier-hover);
|
||
color: var(--csv-text);
|
||
}
|
||
|
||
.csv-notes-file-btn.exists:hover {
|
||
border-color: var(--csv-accent);
|
||
background: rgba(var(--interactive-accent-rgb), 0.08);
|
||
}
|
||
|
||
/* Table notes button */
|
||
.csv-table-notes-btn {
|
||
border: none;
|
||
background: transparent;
|
||
cursor: pointer;
|
||
font-size: 13px;
|
||
padding: 2px 5px;
|
||
border-radius: 3px;
|
||
opacity: 0.5;
|
||
transition: opacity 0.15s;
|
||
margin-right: 4px;
|
||
}
|
||
|
||
@media (hover: hover) {
|
||
.csv-table tr:hover .csv-table-notes-btn { opacity: 1; }
|
||
}
|
||
|
||
.csv-table-notes-btn.exists {
|
||
opacity: 0.8;
|
||
}
|
||
|
||
/* ─── Genre Kanban ────────────────────────────────────────────────────────── */
|
||
|
||
.csv-kanban-genre-board {
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.csv-kanban-status-group {
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.csv-kanban-status-label {
|
||
font-size: 10px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.08em;
|
||
text-transform: uppercase;
|
||
padding: 3px 8px;
|
||
border-radius: 10px;
|
||
margin-bottom: 6px;
|
||
display: inline-block;
|
||
background: var(--csv-surface-2);
|
||
color: var(--csv-text-muted);
|
||
}
|
||
|
||
.csv-kanban-status-label.status-finished,
|
||
.csv-kanban-status-label.status-read,
|
||
.csv-kanban-status-label.status-watched,
|
||
.csv-kanban-status-label.status-seen,
|
||
.csv-kanban-status-label.status-done {
|
||
background: var(--csv-green-bg);
|
||
color: var(--csv-green);
|
||
}
|
||
|
||
.csv-kanban-status-label.status-in-progress,
|
||
.csv-kanban-status-label.status-reading,
|
||
.csv-kanban-status-label.status-watching {
|
||
background: var(--csv-blue-bg);
|
||
color: var(--csv-blue);
|
||
}
|
||
|
||
.csv-kanban-status-label.status-not-started,
|
||
.csv-kanban-status-label.status-to-read,
|
||
.csv-kanban-status-label.status-unwatched,
|
||
.csv-kanban-status-label.status-unread,
|
||
.csv-kanban-status-label.status-no,
|
||
.csv-kanban-status-label.status-todo {
|
||
background: rgba(180,180,180,0.12);
|
||
color: var(--csv-text-faint);
|
||
}
|
||
|
||
/* ─── Resizable columns ───────────────────────────────────────────────────── */
|
||
|
||
.csv-table th {
|
||
position: relative;
|
||
user-select: none;
|
||
}
|
||
|
||
.csv-col-resize-handle {
|
||
position: absolute;
|
||
right: 0;
|
||
top: 0;
|
||
height: 100%;
|
||
width: 6px;
|
||
cursor: col-resize;
|
||
background: transparent;
|
||
transition: background 0.15s;
|
||
z-index: 1;
|
||
}
|
||
|
||
.csv-col-resize-handle:hover,
|
||
.csv-col-resize-handle:active {
|
||
background: var(--csv-accent);
|
||
opacity: 0.5;
|
||
}
|
||
|
||
/* ─── Notes multiline fix ─────────────────────────────────────────────────── */
|
||
|
||
.csv-notes-content {
|
||
/* ensure rendered markdown paragraphs show correctly */
|
||
white-space: normal;
|
||
}
|
||
|
||
.csv-notes-content p {
|
||
margin: 0 0 0.5em;
|
||
}
|
||
|
||
.csv-notes-content p:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.csv-notes-content blockquote {
|
||
margin: 0.4em 0;
|
||
padding: 4px 10px;
|
||
border-left: 3px solid var(--csv-accent);
|
||
color: var(--csv-text-muted);
|
||
font-style: italic;
|
||
}
|
||
|
||
.csv-notes-content blockquote p {
|
||
margin: 0;
|
||
}
|
||
|
||
.csv-notes-textarea {
|
||
min-height: 160px;
|
||
}
|
||
|
||
/* ─── Select Picker ───────────────────────────────────────────────────────── */
|
||
|
||
.csv-select-chip {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 3px 10px;
|
||
border-radius: 12px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
background: var(--background-modifier-hover);
|
||
border: 1px solid var(--csv-border);
|
||
color: var(--csv-text);
|
||
cursor: pointer;
|
||
transition: all 0.15s ease;
|
||
user-select: none;
|
||
max-width: 100%;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.csv-select-chip:hover {
|
||
border-color: var(--csv-accent);
|
||
background: rgba(var(--interactive-accent-rgb, 99,102,241), 0.08);
|
||
}
|
||
|
||
.csv-select-chip.empty {
|
||
color: var(--csv-text-faint);
|
||
background: transparent;
|
||
border-style: dashed;
|
||
}
|
||
|
||
/* Status color tinting for status chips — Apple-muted palette */
|
||
.csv-select-chip.status-chip-finished,
|
||
.csv-select-chip.status-chip-read {
|
||
background: var(--csv-green-bg);
|
||
border-color: rgba(52, 199, 89, 0.3);
|
||
color: var(--csv-green);
|
||
}
|
||
.csv-select-chip.status-chip-in-progress,
|
||
.csv-select-chip.status-chip-reading {
|
||
background: var(--csv-blue-bg);
|
||
border-color: rgba(0, 122, 255, 0.3);
|
||
color: var(--csv-blue);
|
||
}
|
||
.csv-select-chip.status-chip-not-started,
|
||
.csv-select-chip.status-chip-to-read {
|
||
background: rgba(180,180,180,0.1);
|
||
border-color: rgba(180,180,180,0.2);
|
||
color: var(--csv-text-faint);
|
||
}
|
||
|
||
/* In table cells */
|
||
td .csv-select-chip {
|
||
margin: 1px 0;
|
||
}
|
||
|
||
/* Inline select in kanban chips */
|
||
.csv-chip-select {
|
||
cursor: pointer;
|
||
text-decoration: underline dotted;
|
||
text-underline-offset: 2px;
|
||
}
|
||
|
||
/* ─── Picker dropdown ─────────────────────────────────────────────────────── */
|
||
|
||
.csv-select-picker {
|
||
background: var(--background-primary);
|
||
border: 1px solid var(--csv-border);
|
||
border-radius: var(--csv-radius);
|
||
box-shadow: 0 8px 30px rgba(0,0,0,0.2), 0 2px 8px rgba(0,0,0,0.12);
|
||
min-width: 180px;
|
||
max-width: 280px;
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.csv-picker-search {
|
||
padding: 8px 12px;
|
||
border: none;
|
||
border-bottom: 1px solid var(--csv-border);
|
||
background: var(--background-modifier-form-field);
|
||
color: var(--csv-text);
|
||
font-size: 12px;
|
||
outline: none;
|
||
width: 100%;
|
||
}
|
||
|
||
.csv-picker-list {
|
||
max-height: 220px;
|
||
overflow-y: auto;
|
||
padding: 4px;
|
||
}
|
||
|
||
.csv-picker-item {
|
||
padding: 6px 10px;
|
||
border-radius: var(--csv-radius-sm);
|
||
font-size: 12px;
|
||
color: var(--csv-text);
|
||
cursor: pointer;
|
||
transition: background 0.1s;
|
||
}
|
||
|
||
.csv-picker-item:hover,
|
||
.csv-picker-item--hover {
|
||
background: var(--background-modifier-hover);
|
||
}
|
||
|
||
.csv-picker-item.active {
|
||
background: rgba(var(--interactive-accent-rgb, 99,102,241), 0.12);
|
||
color: var(--interactive-accent);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.csv-picker-item.csv-picker-add {
|
||
color: var(--csv-accent);
|
||
font-style: italic;
|
||
}
|
||
|
||
.csv-picker-item.csv-picker-clear {
|
||
color: var(--csv-text-faint);
|
||
font-size: 11px;
|
||
}
|
||
|
||
.csv-picker-empty {
|
||
padding: 8px 10px;
|
||
font-size: 11px;
|
||
color: var(--csv-text-faint);
|
||
font-style: italic;
|
||
}
|
||
|
||
/* field-row alignment for chips */
|
||
.csv-field-row .csv-select-chip {
|
||
margin-top: 1px;
|
||
}
|
||
|
||
/* ─── Notes editing state ─────────────────────────────────────────────────── */
|
||
.csv-notes-content.csv-notes-editing {
|
||
padding: 0;
|
||
border-color: var(--csv-accent);
|
||
background: var(--background-modifier-form-field);
|
||
}
|
||
|
||
/* ─── Kanban inline notes preview ────────────────────────────────────────── */
|
||
|
||
.csv-kanban-notes-preview {
|
||
font-size: 11px;
|
||
color: var(--csv-text-muted);
|
||
font-style: italic;
|
||
margin-top: 6px;
|
||
line-height: 1.4;
|
||
border-left: 2px solid var(--csv-border);
|
||
padding-left: 7px;
|
||
cursor: text; /* hints "click to edit"; matches the textarea that opens */
|
||
border-radius: 2px;
|
||
transition: background 0.12s ease, border-color 0.12s ease;
|
||
/* Unbreakable tokens (URLs, hash strings) would otherwise push the card
|
||
wider than the column, producing per-column horizontal scroll on the
|
||
kanban board. `anywhere` lets them break at any character. */
|
||
overflow-wrap: anywhere;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.csv-kanban-notes-preview:hover {
|
||
background: var(--csv-surface-2);
|
||
border-left-color: var(--csv-accent);
|
||
}
|
||
|
||
/* Empty state doubles as the "+ Add note" affordance — quiet, only visible
|
||
on card hover so it doesn't compete with the title when nothing's there. */
|
||
.csv-kanban-notes-preview--empty {
|
||
font-style: normal;
|
||
color: var(--csv-text-faint);
|
||
border-left-color: transparent;
|
||
opacity: 0;
|
||
transition: opacity 0.15s ease;
|
||
}
|
||
|
||
.csv-kanban-card:hover .csv-kanban-notes-preview--empty {
|
||
opacity: 1;
|
||
}
|
||
|
||
/* Title is now the clickable nav element */
|
||
.csv-kanban-card-title {
|
||
cursor: pointer;
|
||
text-decoration: underline dotted;
|
||
text-underline-offset: 2px;
|
||
}
|
||
|
||
.csv-kanban-card-title:hover {
|
||
color: var(--interactive-accent);
|
||
text-decoration: underline solid;
|
||
}
|
||
|
||
.csv-kanban-notes-editor {
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.csv-kanban-notes-editor .csv-notes-textarea {
|
||
width: 100%;
|
||
min-height: 120px;
|
||
font-size: 12px;
|
||
font-family: var(--font-monospace);
|
||
background: var(--background-modifier-form-field);
|
||
border: 1px solid var(--csv-accent);
|
||
border-radius: var(--csv-radius-sm);
|
||
padding: 8px;
|
||
color: var(--csv-text);
|
||
outline: none;
|
||
resize: vertical;
|
||
line-height: 1.5;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.csv-kanban-edit-btn {
|
||
margin-top: 4px;
|
||
}
|
||
|
||
.csv-kanban-card-title {
|
||
cursor: default;
|
||
text-decoration: none;
|
||
}
|
||
|
||
.csv-kanban-card-title:hover {
|
||
color: var(--csv-text);
|
||
text-decoration: none;
|
||
}
|
||
|
||
/* ─── Add Entry Modal ─────────────────────────────────────────────────────── */
|
||
|
||
.csv-add-modal {
|
||
padding: 8px 4px;
|
||
}
|
||
|
||
.csv-modal-title {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
margin: 0 0 20px;
|
||
color: var(--csv-text);
|
||
}
|
||
|
||
.csv-modal-form {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 14px;
|
||
max-height: 60vh;
|
||
overflow-y: auto;
|
||
padding-right: 4px;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.csv-modal-row {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 5px;
|
||
}
|
||
|
||
.csv-modal-label {
|
||
/* Apple convention: modal form row labels in title case. */
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
color: var(--csv-text-muted);
|
||
}
|
||
|
||
.csv-modal-input {
|
||
padding: 7px 10px;
|
||
border-radius: var(--csv-radius-sm);
|
||
border: 1px solid var(--csv-border);
|
||
background: var(--background-modifier-form-field);
|
||
color: var(--csv-text);
|
||
font-size: 13px;
|
||
outline: none;
|
||
transition: border-color 0.15s;
|
||
}
|
||
|
||
.csv-modal-input:focus {
|
||
border-color: var(--csv-accent);
|
||
}
|
||
|
||
.csv-modal-textarea {
|
||
padding: 8px 10px;
|
||
border-radius: var(--csv-radius-sm);
|
||
border: 1px solid var(--csv-border);
|
||
background: var(--background-modifier-form-field);
|
||
color: var(--csv-text);
|
||
font-size: 13px;
|
||
font-family: var(--font-monospace);
|
||
outline: none;
|
||
resize: vertical;
|
||
min-height: 80px;
|
||
line-height: 1.5;
|
||
transition: border-color 0.15s;
|
||
}
|
||
|
||
.csv-modal-textarea:focus {
|
||
border-color: var(--csv-accent);
|
||
}
|
||
|
||
.csv-modal-select-wrap {
|
||
display: flex;
|
||
}
|
||
|
||
.csv-modal-select-wrap .csv-select-chip {
|
||
min-width: 140px;
|
||
}
|
||
|
||
.csv-modal-btns {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 8px;
|
||
padding-top: 4px;
|
||
border-top: 1px solid var(--csv-border);
|
||
}
|
||
|
||
.csv-modal-cancel {
|
||
padding: 7px 16px;
|
||
border-radius: var(--csv-radius-sm);
|
||
border: 1px solid var(--csv-border);
|
||
background: transparent;
|
||
color: var(--csv-text-muted);
|
||
font-size: 13px;
|
||
cursor: pointer;
|
||
transition: all 0.15s;
|
||
}
|
||
|
||
.csv-modal-cancel:hover {
|
||
background: var(--background-modifier-hover);
|
||
color: var(--csv-text);
|
||
}
|
||
|
||
.csv-modal-submit {
|
||
padding: 7px 20px;
|
||
border-radius: var(--csv-radius-sm);
|
||
border: none;
|
||
background: var(--csv-accent);
|
||
color: white;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: background 0.15s;
|
||
}
|
||
|
||
.csv-modal-submit:hover {
|
||
background: var(--csv-accent-hover);
|
||
}
|
||
|
||
/* ─── Note Expander Modal ─────────────────────────────────────────────────── */
|
||
|
||
.csv-note-expander-modal {
|
||
width: min(780px, 90vw) !important;
|
||
/* Keyboard-aware modal height. The JS in NoteExpanderModal sets
|
||
--csv-modal-vh to visualViewport.height (the screen minus the iOS
|
||
keyboard, when it's open). When that variable is absent the modal
|
||
falls back to dvh, then vh — those work on desktop and on browsers
|
||
without the visualViewport API. The 85% factor leaves a margin
|
||
around the modal so it doesn't butt against the edges. */
|
||
max-height: 85vh !important;
|
||
max-height: 85dvh !important;
|
||
max-height: calc(var(--csv-modal-vh, 100dvh) * 0.85) !important;
|
||
}
|
||
|
||
/* Mobile: dock the modal to the top of the visible viewport instead of
|
||
center-of-window. Obsidian's default centering uses window.innerHeight
|
||
which doesn't shrink when the iOS keyboard appears — so the modal stays
|
||
centered in the no-keyboard viewport and its bottom (footer + textarea)
|
||
ends up behind the keyboard. Obsidian centers via flex on the parent
|
||
container (not a transform on the modal itself), so we keep horizontal
|
||
centering by overriding only align-self and adding a small margin-top.
|
||
max-height is bound to visualViewport.height (written by the JS handler
|
||
into --csv-modal-vh) so the modal always fits above the keyboard. */
|
||
@media (max-width: 600px) {
|
||
.csv-note-expander-modal {
|
||
align-self: flex-start !important;
|
||
margin-top: 10px !important;
|
||
max-height: calc(var(--csv-modal-vh, 100dvh) - 20px) !important;
|
||
}
|
||
}
|
||
|
||
.csv-note-expander-modal .modal-content {
|
||
padding: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
max-height: 85vh;
|
||
max-height: 85dvh;
|
||
max-height: calc(var(--csv-modal-vh, 100dvh) * 0.85);
|
||
/* The modal-content itself scrolls when its body exceeds the keyboard-
|
||
aware max-height. Combined with `position: sticky` on the footer, this
|
||
guarantees the action bar stays reachable no matter what iOS does with
|
||
the keyboard or how long the note is. */
|
||
overflow-y: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
|
||
@media (max-width: 600px) {
|
||
.csv-note-expander-modal .modal-content {
|
||
max-height: calc(var(--csv-modal-vh, 100dvh) - 20px);
|
||
}
|
||
}
|
||
|
||
.csv-note-expander {
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 400px;
|
||
}
|
||
|
||
.csv-expander-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 16px 20px 14px;
|
||
border-bottom: 1px solid var(--csv-border);
|
||
flex-shrink: 0;
|
||
gap: 12px;
|
||
}
|
||
|
||
.csv-expander-title {
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
color: var(--csv-text);
|
||
flex: 1;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.csv-expander-header-btns {
|
||
display: flex;
|
||
gap: 6px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.csv-expander-save-btn,
|
||
.csv-expander-close-btn {
|
||
padding: 5px 12px;
|
||
border-radius: var(--csv-radius-sm);
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: all 0.15s;
|
||
border: 1px solid var(--csv-border);
|
||
}
|
||
|
||
.csv-expander-save-btn {
|
||
background: var(--csv-accent);
|
||
color: white;
|
||
border-color: transparent;
|
||
font-weight: 600;
|
||
}
|
||
.csv-expander-save-btn:hover { background: var(--csv-accent-hover); }
|
||
|
||
.csv-expander-close-btn {
|
||
background: transparent;
|
||
color: var(--csv-text-faint);
|
||
}
|
||
.csv-expander-close-btn:hover { color: var(--csv-text); background: var(--background-modifier-hover); }
|
||
|
||
.csv-expander-rendered {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 24px 28px;
|
||
font-size: 14px;
|
||
line-height: 1.7;
|
||
cursor: text; /* indicates click-to-edit; respects text-selection */
|
||
border-radius: 4px;
|
||
transition: background 0.12s ease;
|
||
}
|
||
|
||
.csv-expander-rendered:hover { background: var(--csv-surface-2); }
|
||
/* Links/buttons inside the rendered note keep their own cursor and don't
|
||
pick up the hover-tint from the editable container. */
|
||
.csv-expander-rendered a,
|
||
.csv-expander-rendered button { cursor: pointer; }
|
||
|
||
.csv-expander-rendered p { margin: 0 0 0.8em; }
|
||
.csv-expander-rendered p:last-child { margin-bottom: 0; }
|
||
.csv-expander-rendered blockquote {
|
||
border-left: 3px solid var(--csv-accent);
|
||
padding: 4px 12px;
|
||
margin: 8px 0;
|
||
color: var(--csv-text-muted);
|
||
font-style: italic;
|
||
}
|
||
.csv-expander-rendered h1,
|
||
.csv-expander-rendered h2,
|
||
.csv-expander-rendered h3 {
|
||
margin: 1em 0 0.4em;
|
||
font-weight: 700;
|
||
}
|
||
.csv-expander-rendered ul,
|
||
.csv-expander-rendered ol { margin: 4px 0 10px 20px; }
|
||
.csv-expander-rendered li { margin: 3px 0; }
|
||
.csv-expander-rendered code {
|
||
font-family: var(--font-monospace);
|
||
font-size: 12px;
|
||
background: var(--background-modifier-hover);
|
||
padding: 1px 5px;
|
||
border-radius: 3px;
|
||
}
|
||
.csv-expander-rendered hr { border: none; border-top: 1px solid var(--csv-border); margin: 16px 0; }
|
||
|
||
.csv-expander-editor {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: 16px 20px;
|
||
}
|
||
|
||
.csv-expander-textarea {
|
||
flex: 1;
|
||
width: 100%;
|
||
min-height: 320px;
|
||
font-family: var(--font-monospace);
|
||
font-size: 13px;
|
||
line-height: 1.6;
|
||
background: var(--background-modifier-form-field);
|
||
border: 1px solid var(--csv-border);
|
||
border-radius: var(--csv-radius-sm);
|
||
color: var(--csv-text);
|
||
padding: 12px;
|
||
outline: none;
|
||
resize: none;
|
||
}
|
||
|
||
/* On mobile the 320px min-height combined with the iOS keyboard pushes
|
||
the modal's footer below the keyboard. Letting the textarea shrink
|
||
gives the flex container the room it needs to keep header + footer
|
||
visible at the same time. */
|
||
@media (max-width: 600px) {
|
||
.csv-expander-textarea { min-height: 120px; }
|
||
.csv-expander-editor { padding: 10px 12px; }
|
||
}
|
||
|
||
.csv-expander-textarea:focus {
|
||
border-color: var(--csv-accent);
|
||
}
|
||
|
||
/* ─── Table notes cell — whole cell opens the expander ───────────────────── */
|
||
|
||
.csv-table-notes-cell {
|
||
cursor: pointer;
|
||
transition: background 0.12s ease;
|
||
}
|
||
|
||
@media (hover: hover) {
|
||
.csv-table tr:hover .csv-table-notes-cell { background: var(--csv-surface-2); }
|
||
}
|
||
|
||
/* Empty-cell placeholder — only visible on row hover so unfilled notes
|
||
don't clutter the table at rest. On touch (no hover) keep it visible
|
||
at low opacity so the user can still discover the affordance. */
|
||
.csv-table-notes-empty {
|
||
color: var(--csv-text-faint);
|
||
opacity: 0;
|
||
transition: opacity 0.15s ease;
|
||
}
|
||
|
||
@media (hover: hover) {
|
||
.csv-table tr:hover .csv-table-notes-empty { opacity: 1; }
|
||
}
|
||
@media (hover: none) {
|
||
.csv-table-notes-empty { opacity: 0.5; }
|
||
}
|
||
|
||
/* ─── Column config button & modal ────────────────────────────────────────── */
|
||
|
||
.csv-cfg-btn {
|
||
padding: 5px 10px;
|
||
border-radius: var(--csv-radius-sm);
|
||
font-size: 12px;
|
||
border: 1px solid var(--csv-border);
|
||
background: transparent;
|
||
color: var(--csv-text-muted);
|
||
cursor: pointer;
|
||
transition: all 0.15s;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.csv-cfg-btn:hover {
|
||
color: var(--csv-text);
|
||
background: var(--background-modifier-hover);
|
||
border-color: var(--csv-accent);
|
||
}
|
||
|
||
.csv-modal-desc {
|
||
font-size: 12px;
|
||
color: var(--csv-text-muted);
|
||
margin: -12px 0 16px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.csv-modal-select {
|
||
padding: 7px 10px;
|
||
border-radius: var(--csv-radius-sm);
|
||
border: 1px solid var(--csv-border);
|
||
background: var(--background-modifier-form-field);
|
||
color: var(--csv-text);
|
||
font-size: 13px;
|
||
outline: none;
|
||
cursor: pointer;
|
||
width: 100%;
|
||
transition: border-color 0.15s;
|
||
}
|
||
|
||
.csv-modal-select:focus {
|
||
border-color: var(--csv-accent);
|
||
}
|
||
|
||
/* Habit columns checkbox grid in config modal */
|
||
.csv-modal-hint {
|
||
font-size: 11px;
|
||
color: var(--csv-text-faint);
|
||
margin: 0 0 8px;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.csv-modal-checkbox-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||
gap: 6px;
|
||
padding: 8px;
|
||
background: var(--background-modifier-form-field);
|
||
border: 1px solid var(--csv-border);
|
||
border-radius: var(--csv-radius-sm);
|
||
max-height: 180px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.csv-modal-checkbox-label {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 6px 8px;
|
||
border-radius: var(--csv-radius-sm);
|
||
font-size: 12px;
|
||
color: var(--csv-text);
|
||
cursor: pointer;
|
||
transition: background 0.15s;
|
||
}
|
||
|
||
.csv-modal-checkbox-label:hover {
|
||
background: var(--background-modifier-hover);
|
||
}
|
||
|
||
.csv-modal-checkbox-label.auto-detected {
|
||
background: rgba(52, 168, 83, 0.08);
|
||
border: 1px solid rgba(52, 168, 83, 0.2);
|
||
}
|
||
|
||
.csv-modal-checkbox-label input[type="checkbox"] {
|
||
margin: 0;
|
||
cursor: pointer;
|
||
}
|
||
|
||
/* ─── Expander fields & layout updates ───────────────────────────────────── */
|
||
|
||
.csv-note-expander-modal .modal-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.csv-note-expander {
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
height: 100%;
|
||
}
|
||
|
||
.csv-expander-fields {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px 16px;
|
||
padding: 12px 20px;
|
||
border-bottom: 1px solid var(--csv-border);
|
||
background: var(--csv-surface);
|
||
}
|
||
|
||
.csv-expander-field-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
min-width: 0;
|
||
}
|
||
|
||
.csv-expander-field-label {
|
||
/* Apple convention: in-row labels in title case (titleCase() is applied
|
||
at the JS layer to the header name). Section headers stay uppercase
|
||
(see .csv-expander-notes-label, .csv-table th, .csv-kanban-col-title). */
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
color: var(--csv-text-muted);
|
||
white-space: nowrap;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.csv-expander-field-value {
|
||
font-size: 12px;
|
||
color: var(--csv-text);
|
||
cursor: text;
|
||
padding: 2px 5px;
|
||
border-radius: 3px;
|
||
border: 1px solid transparent;
|
||
max-width: 220px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
transition: border-color 0.15s, background 0.15s;
|
||
}
|
||
|
||
.csv-expander-field-value:hover {
|
||
border-color: var(--csv-border);
|
||
background: var(--background-modifier-hover);
|
||
}
|
||
|
||
.csv-expander-divider {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 10px 20px 6px;
|
||
border-bottom: 1px solid var(--csv-border);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.csv-expander-notes-label {
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.05em;
|
||
text-transform: uppercase;
|
||
color: var(--csv-text-faint);
|
||
flex: 1;
|
||
}
|
||
|
||
.csv-expander-footer {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 12px 20px;
|
||
border-top: 1px solid var(--csv-border);
|
||
flex-shrink: 0;
|
||
/* Pin the action bar to the bottom of the modal's scroll container so
|
||
the Delete / Cancel / Save buttons stay reachable on iOS even when
|
||
the keyboard pushes content up. Opaque bg so scrolled content doesn't
|
||
bleed through. */
|
||
position: sticky;
|
||
bottom: 0;
|
||
background: var(--background-primary);
|
||
z-index: 2;
|
||
}
|
||
|
||
.csv-expander-footer-right {
|
||
display: flex;
|
||
gap: 8px;
|
||
margin-left: auto;
|
||
}
|
||
|
||
/* Delete is destructive — muted red, ghost-style by default so it doesn't
|
||
compete with Save, then fills on hover so the user knows it'll fire. */
|
||
.csv-expander-delete-btn {
|
||
padding: 8px 14px;
|
||
border: 1px solid rgba(213, 68, 59, 0.4);
|
||
background: transparent;
|
||
color: var(--csv-red);
|
||
border-radius: 8px;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: background 0.15s, color 0.15s, border-color 0.15s;
|
||
}
|
||
|
||
.csv-expander-delete-btn:hover {
|
||
background: var(--csv-red);
|
||
border-color: var(--csv-red);
|
||
color: white;
|
||
}
|
||
|
||
/* Chip truncation in kanban */
|
||
.csv-chip-value {
|
||
max-width: 200px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
display: inline-block;
|
||
vertical-align: middle;
|
||
}
|
||
|
||
/* ─── Dashboard View ───────────────────────────────────────────────────────── */
|
||
|
||
.csv-dashboard {
|
||
padding: 16px 20px;
|
||
}
|
||
|
||
.csv-dash-nav {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
margin-bottom: 32px;
|
||
padding: 0;
|
||
background: transparent;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.csv-dash-nav-btn {
|
||
padding: 8px 14px;
|
||
border: none;
|
||
background: transparent;
|
||
color: var(--csv-text-muted);
|
||
border-radius: var(--csv-radius-sm);
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
transition: all 0.15s;
|
||
}
|
||
|
||
.csv-dash-nav-btn:hover {
|
||
background: var(--background-modifier-hover);
|
||
color: var(--csv-text);
|
||
}
|
||
|
||
.csv-dash-date {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.csv-dash-today-dot {
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 50%;
|
||
background: var(--csv-green);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.csv-dash-date-select {
|
||
padding: 4px 8px;
|
||
border: none;
|
||
background: transparent;
|
||
color: var(--csv-text);
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
outline: none;
|
||
min-width: 100px;
|
||
text-align: center;
|
||
}
|
||
|
||
.csv-dash-today-btn {
|
||
padding: 8px 16px;
|
||
border: none;
|
||
background: transparent;
|
||
color: var(--csv-text-muted);
|
||
border-radius: var(--csv-radius-sm);
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
transition: all 0.15s;
|
||
}
|
||
|
||
.csv-dash-today-btn:hover {
|
||
background: var(--background-modifier-hover);
|
||
color: var(--csv-text);
|
||
}
|
||
|
||
.csv-dash-section-title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: var(--csv-text);
|
||
margin: 0 0 16px;
|
||
}
|
||
|
||
/* Add section for missing dates */
|
||
.csv-dash-add-section {
|
||
padding: 20px 0;
|
||
text-align: center;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.csv-dash-add-section p {
|
||
color: var(--csv-text-muted);
|
||
margin: 0 0 12px;
|
||
}
|
||
|
||
.csv-dash-add-btn {
|
||
padding: 8px 20px;
|
||
border: none;
|
||
background: var(--csv-accent);
|
||
color: white;
|
||
border-radius: var(--csv-radius-sm);
|
||
cursor: pointer;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
transition: background 0.15s;
|
||
}
|
||
|
||
.csv-dash-add-btn:hover {
|
||
background: var(--csv-accent-hover);
|
||
}
|
||
|
||
/* Habits grid */
|
||
.csv-dash-habits {
|
||
padding: 0;
|
||
margin-bottom: 32px;
|
||
}
|
||
|
||
.csv-dash-habits-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||
gap: 10px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.csv-dash-habit {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 10px 14px;
|
||
background: transparent;
|
||
border-radius: var(--csv-radius-sm);
|
||
border: 1px solid var(--csv-border);
|
||
transition: all 0.15s;
|
||
}
|
||
|
||
.csv-dash-habit:hover {
|
||
border-color: var(--csv-text-muted);
|
||
}
|
||
|
||
.csv-dash-habit.checked {
|
||
background: transparent;
|
||
border-color: var(--csv-green);
|
||
}
|
||
|
||
.csv-dash-habit-check {
|
||
border: none;
|
||
background: transparent;
|
||
font-size: 18px;
|
||
cursor: pointer;
|
||
color: var(--csv-text-faint);
|
||
padding: 0;
|
||
line-height: 1;
|
||
transition: color 0.15s, transform 0.1s;
|
||
}
|
||
|
||
.csv-dash-habit.checked .csv-dash-habit-check {
|
||
color: var(--csv-green);
|
||
}
|
||
|
||
.csv-dash-habit-check:hover {
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
.csv-dash-habit-label {
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
color: var(--csv-text);
|
||
}
|
||
|
||
.csv-dash-habits-count {
|
||
font-size: 13px;
|
||
color: var(--csv-text-muted);
|
||
text-align: left;
|
||
margin-top: 12px;
|
||
}
|
||
|
||
.csv-dash-notes-preview {
|
||
margin-top: 12px;
|
||
padding: 10px 12px;
|
||
background: var(--csv-bg);
|
||
border-radius: var(--csv-radius-sm);
|
||
border-left: 3px solid var(--csv-accent);
|
||
font-size: 12px;
|
||
color: var(--csv-text-muted);
|
||
line-height: 1.5;
|
||
}
|
||
|
||
/* Chart section */
|
||
.csv-dash-chart-section {
|
||
padding: 0;
|
||
margin-bottom: 32px;
|
||
}
|
||
|
||
.csv-dash-chart-wrap {
|
||
height: 300px;
|
||
position: relative;
|
||
}
|
||
|
||
.csv-dash-chart {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
/* Stats section */
|
||
.csv-dash-stats-section {
|
||
padding: 0;
|
||
margin-bottom: 32px;
|
||
}
|
||
|
||
.csv-dash-stats-bar {
|
||
font-size: 14px;
|
||
color: var(--csv-text-muted);
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.csv-dash-stats-bar strong {
|
||
color: var(--csv-text);
|
||
font-weight: 700;
|
||
}
|
||
|
||
/* Habit cards */
|
||
.csv-dash-cards-section {
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.csv-dash-cards-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||
gap: 12px;
|
||
}
|
||
|
||
.csv-dash-habit-card {
|
||
padding: 14px 16px;
|
||
background: transparent;
|
||
border: 1px solid var(--csv-border);
|
||
border-radius: var(--csv-radius);
|
||
cursor: pointer;
|
||
transition: all 0.15s;
|
||
}
|
||
|
||
.csv-dash-habit-card:hover {
|
||
border-color: var(--csv-accent);
|
||
background: var(--background-modifier-hover);
|
||
}
|
||
|
||
.csv-dash-habit-card.selected {
|
||
border: 2px solid var(--csv-accent);
|
||
background: rgba(55, 138, 221, 0.05);
|
||
}
|
||
|
||
.csv-dash-habit-card-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.csv-dash-habit-icon {
|
||
font-size: 14px;
|
||
color: var(--csv-text-faint);
|
||
width: 18px;
|
||
text-align: center;
|
||
}
|
||
|
||
.csv-dash-habit-card-name {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: var(--csv-text);
|
||
}
|
||
|
||
.csv-dash-habit-years {
|
||
font-size: 11px;
|
||
color: var(--csv-text-faint);
|
||
margin-bottom: 8px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.csv-dash-habit-card-thisyear {
|
||
font-size: 12px;
|
||
color: var(--csv-text);
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.csv-dash-habit-card-alltime {
|
||
font-size: 11px;
|
||
color: var(--csv-accent);
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.csv-dash-habit-card-last {
|
||
font-size: 11px;
|
||
color: var(--csv-text-faint);
|
||
}
|
||
|
||
.csv-dash-habit-progress {
|
||
margin-top: 10px;
|
||
height: 4px;
|
||
background: var(--csv-surface-2);
|
||
border-radius: 2px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.csv-dash-habit-progress-bar {
|
||
height: 100%;
|
||
background: var(--csv-accent);
|
||
border-radius: 2px;
|
||
transition: width 0.15s ease;
|
||
}
|
||
|
||
/* Per-habit timeline section */
|
||
.csv-dash-timeline-section {
|
||
padding: 20px 0;
|
||
margin-bottom: 32px;
|
||
border-top: 1px solid var(--csv-border);
|
||
}
|
||
|
||
.csv-dash-timeline-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.csv-dash-timeline-header .csv-dash-section-title {
|
||
margin: 0;
|
||
}
|
||
|
||
.csv-dash-timeline-close {
|
||
border: none;
|
||
background: transparent;
|
||
color: var(--csv-text-faint);
|
||
font-size: 16px;
|
||
cursor: pointer;
|
||
padding: 4px 8px;
|
||
border-radius: var(--csv-radius-sm);
|
||
transition: all 0.15s;
|
||
}
|
||
|
||
.csv-dash-timeline-close:hover {
|
||
background: var(--background-modifier-hover);
|
||
color: var(--csv-text);
|
||
}
|
||
|
||
.csv-dash-timeline-grid-wrap {
|
||
overflow-x: auto;
|
||
padding-bottom: 8px;
|
||
}
|
||
|
||
.csv-dash-timeline-months {
|
||
display: flex;
|
||
padding-left: 24px;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.csv-dash-timeline-month-label {
|
||
flex: 1;
|
||
font-size: 11px;
|
||
font-weight: 500;
|
||
color: var(--csv-text-faint);
|
||
text-align: center;
|
||
}
|
||
|
||
.csv-dash-timeline-calendar {
|
||
display: flex;
|
||
gap: 2px;
|
||
}
|
||
|
||
.csv-dash-timeline-day-labels {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
width: 20px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.csv-dash-timeline-day-label {
|
||
height: 10px;
|
||
font-size: 9px;
|
||
color: var(--csv-text-faint);
|
||
text-align: right;
|
||
padding-right: 4px;
|
||
line-height: 10px;
|
||
}
|
||
|
||
.csv-dash-timeline-month-col {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
flex: 1;
|
||
}
|
||
|
||
.csv-dash-timeline-cell {
|
||
height: 10px;
|
||
border-radius: 2px;
|
||
background: var(--csv-border);
|
||
transition: transform 0.1s;
|
||
}
|
||
|
||
.csv-dash-timeline-cell:hover:not(.empty):not(.future) {
|
||
transform: scale(1.5);
|
||
z-index: 1;
|
||
}
|
||
|
||
.csv-dash-timeline-cell.empty {
|
||
background: transparent;
|
||
}
|
||
|
||
.csv-dash-timeline-cell.future {
|
||
background: transparent;
|
||
}
|
||
|
||
.csv-dash-timeline-cell.done {
|
||
background: var(--csv-green);
|
||
}
|
||
|
||
.csv-dash-timeline-cell.missed {
|
||
background: var(--csv-red);
|
||
}
|
||
|
||
.csv-dash-timeline-cell.no-entry {
|
||
background: var(--csv-border);
|
||
opacity: 0.3;
|
||
}
|
||
|
||
.csv-dash-year-select {
|
||
padding: 4px 8px;
|
||
border: none;
|
||
background: transparent;
|
||
color: var(--csv-text-muted);
|
||
font-size: 13px;
|
||
cursor: pointer;
|
||
margin-left: auto;
|
||
margin-right: 12px;
|
||
}
|
||
|
||
.csv-dash-timeline-stats {
|
||
margin-top: 16px;
|
||
padding-top: 12px;
|
||
border-top: 1px solid var(--csv-border);
|
||
font-size: 13px;
|
||
color: var(--csv-text-muted);
|
||
}
|
||
|
||
.csv-dash-timeline-stats strong {
|
||
color: var(--csv-text);
|
||
font-weight: 700;
|
||
}
|
||
|
||
/* ─── Mobile Scrollable Table ─────────────────────────────────────────────── */
|
||
|
||
.csv-mobile-table {
|
||
overflow-x: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
margin: 0 -12px;
|
||
padding: 0 12px;
|
||
}
|
||
|
||
.csv-mobile-table table {
|
||
font-size: 12px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.csv-mobile-table th,
|
||
.csv-mobile-table td {
|
||
padding: 4px 8px !important;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.csv-mobile-table th {
|
||
font-weight: 600;
|
||
text-transform: capitalize;
|
||
}
|
||
|
||
/* ─── Mobile Add Form (csv-add code block) ────────────────────────────────────
|
||
One unified "menu" instead of a stack of disjoint prompts. Default state
|
||
is a discreet pill; tapping it expands a single rounded grouped card
|
||
(iOS-Settings style) with hairline-separated rows and one Add button. */
|
||
|
||
.csv-add-error {
|
||
color: var(--csv-red);
|
||
padding: 8px 12px;
|
||
background: var(--csv-red-bg);
|
||
border-radius: var(--csv-radius-sm);
|
||
font-size: 13px;
|
||
}
|
||
|
||
.csv-add-form {
|
||
padding: 0;
|
||
background: transparent;
|
||
border: none;
|
||
margin: 0 0 16px 0;
|
||
max-width: 520px;
|
||
}
|
||
|
||
/* Collapsed trigger pill */
|
||
.csv-add-trigger {
|
||
padding: 8px 14px;
|
||
border: none;
|
||
background: var(--background-secondary);
|
||
color: var(--text-normal);
|
||
border-radius: 999px;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: background 0.15s, transform 0.05s;
|
||
}
|
||
|
||
.csv-add-trigger:hover { background: var(--background-modifier-hover); }
|
||
.csv-add-trigger:active { transform: scale(0.98); }
|
||
|
||
/* Expanded card */
|
||
.csv-add-card {
|
||
background: var(--background-secondary);
|
||
border-radius: 14px;
|
||
overflow: hidden;
|
||
box-shadow: inset 0 0 0 1px transparent;
|
||
transition: box-shadow 0.15s;
|
||
}
|
||
|
||
/* Update mode (date matches an existing row): subtle accent ring + tinted
|
||
title so the user sees they're editing rather than creating. */
|
||
.csv-add-card.is-updating {
|
||
box-shadow: inset 0 0 0 1px var(--csv-blue, var(--text-accent));
|
||
}
|
||
|
||
.csv-add-card.is-updating .csv-add-card-title {
|
||
color: var(--csv-blue, var(--text-accent));
|
||
}
|
||
|
||
.csv-add-card-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 8px 12px 8px 14px;
|
||
background: transparent;
|
||
}
|
||
|
||
.csv-add-card-title {
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: var(--text-muted);
|
||
letter-spacing: 0.03em;
|
||
}
|
||
|
||
.csv-add-card-close {
|
||
width: 24px;
|
||
height: 24px;
|
||
border: none;
|
||
background: transparent;
|
||
color: var(--text-muted);
|
||
border-radius: 50%;
|
||
font-size: 18px;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
padding: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.csv-add-card-close:hover {
|
||
background: var(--background-modifier-hover);
|
||
color: var(--text-normal);
|
||
}
|
||
|
||
/* Grouped row list with hairline separators */
|
||
.csv-add-rows {
|
||
background: var(--background-primary);
|
||
margin: 0 12px;
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.csv-add-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 7px 12px;
|
||
min-height: 34px;
|
||
border-bottom: 1px solid var(--background-modifier-border);
|
||
}
|
||
|
||
.csv-add-row:last-child { border-bottom: none; }
|
||
|
||
.csv-add-row-label {
|
||
flex: 0 0 auto;
|
||
font-size: 13px;
|
||
color: var(--text-normal);
|
||
min-width: 72px;
|
||
}
|
||
|
||
.csv-add-row-control {
|
||
flex: 1 1 auto;
|
||
border: none;
|
||
background: transparent;
|
||
color: var(--text-normal);
|
||
font-size: 13px;
|
||
text-align: right;
|
||
outline: none;
|
||
padding: 0;
|
||
font-family: inherit;
|
||
}
|
||
|
||
.csv-add-row-control::placeholder {
|
||
color: var(--text-faint);
|
||
}
|
||
|
||
/* Selects need a chevron and right-aligned text */
|
||
select.csv-add-row-control {
|
||
appearance: none;
|
||
-webkit-appearance: none;
|
||
background: transparent
|
||
url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path fill='none' stroke='gray' stroke-width='1.5' d='M1 1l4 4 4-4'/></svg>")
|
||
no-repeat right center;
|
||
padding-right: 14px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
/* Notes row stacks vertically (label above, textarea full width) */
|
||
.csv-add-row-notes {
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
gap: 6px;
|
||
padding: 10px 14px 12px;
|
||
}
|
||
|
||
.csv-add-row-notes .csv-add-row-label {
|
||
font-size: 11px;
|
||
color: var(--text-muted);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.05em;
|
||
}
|
||
|
||
.csv-add-row-textarea {
|
||
width: 100%;
|
||
min-height: 56px;
|
||
border: none;
|
||
background: transparent;
|
||
color: var(--text-normal);
|
||
font-size: 14px;
|
||
resize: vertical;
|
||
outline: none;
|
||
font-family: inherit;
|
||
padding: 0;
|
||
}
|
||
|
||
.csv-add-row-textarea::placeholder { color: var(--text-faint); }
|
||
|
||
/* iOS-style switch for binary toggle rows — sized for the 34px-min row */
|
||
.csv-add-switch {
|
||
position: relative;
|
||
display: inline-block;
|
||
width: 36px;
|
||
height: 22px;
|
||
flex: 0 0 auto;
|
||
margin-left: auto;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.csv-add-switch-input {
|
||
position: absolute;
|
||
opacity: 0;
|
||
width: 0;
|
||
height: 0;
|
||
}
|
||
|
||
.csv-add-switch-track {
|
||
position: absolute;
|
||
inset: 0;
|
||
background: var(--background-modifier-border);
|
||
border-radius: 999px;
|
||
transition: background 0.18s;
|
||
}
|
||
|
||
.csv-add-switch-track::after {
|
||
content: "";
|
||
position: absolute;
|
||
top: 2px;
|
||
left: 2px;
|
||
width: 18px;
|
||
height: 18px;
|
||
background: var(--background-primary);
|
||
border-radius: 50%;
|
||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||
transition: transform 0.18s;
|
||
}
|
||
|
||
.csv-add-switch-input:checked + .csv-add-switch-track {
|
||
background: var(--csv-green, var(--text-accent));
|
||
}
|
||
|
||
.csv-add-switch-input:checked + .csv-add-switch-track::after {
|
||
transform: translateX(14px);
|
||
}
|
||
|
||
/* Submit button — full width inside the card footer */
|
||
.csv-add-submit {
|
||
display: block;
|
||
width: calc(100% - 24px);
|
||
margin: 10px 12px 12px;
|
||
padding: 9px 18px;
|
||
border: none;
|
||
background: var(--text-accent);
|
||
color: white;
|
||
border-radius: 8px;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: opacity 0.15s, transform 0.05s;
|
||
}
|
||
|
||
.csv-add-submit:hover { opacity: 0.92; }
|
||
.csv-add-submit:active { transform: scale(0.99); }
|
||
|
||
.csv-refresh-btn {
|
||
padding: 4px 10px;
|
||
border: none;
|
||
background: transparent;
|
||
border-radius: 6px;
|
||
color: var(--text-faint);
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
transition: all 0.15s;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
|
||
.csv-refresh-btn:hover {
|
||
background: var(--background-secondary);
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
/* ─── Library View ───────────────────────────────────────────────────────────── */
|
||
|
||
.csv-library-filters {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-bottom: 20px;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
}
|
||
|
||
.csv-library-filter-select {
|
||
padding: 6px 12px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--background-modifier-border);
|
||
background: var(--background-primary);
|
||
color: var(--text-normal);
|
||
font-size: 13px;
|
||
cursor: pointer;
|
||
min-width: 100px;
|
||
}
|
||
|
||
.csv-library-filter-select:focus {
|
||
outline: none;
|
||
border-color: var(--text-accent);
|
||
}
|
||
|
||
.csv-library-search {
|
||
padding: 6px 12px;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--background-modifier-border);
|
||
background: var(--background-primary);
|
||
color: var(--text-normal);
|
||
font-size: 13px;
|
||
min-width: 180px;
|
||
}
|
||
|
||
.csv-library-search:focus {
|
||
outline: none;
|
||
border-color: var(--text-accent);
|
||
}
|
||
|
||
.csv-library-result-count {
|
||
font-size: 12px;
|
||
color: var(--text-muted);
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.csv-library-sections {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
.csv-library-section {
|
||
margin: 0;
|
||
}
|
||
|
||
.csv-library-section-header {
|
||
/* Borderless section header: lighter visual rhythm than a chip-in-chip,
|
||
and lets the grid sit closer underneath. Bold text + quiet chevron. */
|
||
list-style: none;
|
||
cursor: pointer;
|
||
padding: 6px 4px;
|
||
font-weight: 600;
|
||
font-size: 13px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
user-select: none;
|
||
color: var(--csv-text);
|
||
transition: color 0.15s;
|
||
}
|
||
|
||
.csv-library-section-header:hover {
|
||
color: var(--text-accent);
|
||
}
|
||
|
||
.csv-library-section-header::-webkit-details-marker {
|
||
display: none;
|
||
}
|
||
|
||
.csv-library-arrow {
|
||
font-size: 11px;
|
||
color: var(--csv-text-faint);
|
||
transition: transform 0.1s;
|
||
display: inline-block;
|
||
line-height: 1;
|
||
}
|
||
|
||
.csv-library-section[open] .csv-library-arrow {
|
||
transform: rotate(90deg);
|
||
}
|
||
|
||
.csv-library-count {
|
||
font-weight: 500;
|
||
font-size: 12px;
|
||
color: var(--csv-text-faint);
|
||
margin-left: auto;
|
||
}
|
||
|
||
.csv-library-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||
gap: 10px;
|
||
padding: 10px 0 4px;
|
||
}
|
||
|
||
.csv-library-card {
|
||
padding: 14px 16px;
|
||
border-radius: 10px;
|
||
background: transparent;
|
||
border: 1px solid var(--background-modifier-border);
|
||
cursor: pointer;
|
||
transition: all 0.15s;
|
||
}
|
||
|
||
.csv-library-card:hover {
|
||
background: var(--background-secondary);
|
||
border-color: var(--background-modifier-border-hover);
|
||
}
|
||
|
||
.csv-library-card-title {
|
||
font-weight: 600;
|
||
font-size: 14px;
|
||
margin-bottom: 4px;
|
||
color: var(--text-normal);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.csv-library-done-dot {
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 50%;
|
||
background: var(--csv-green);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.csv-library-card-meta {
|
||
font-size: 12px;
|
||
color: var(--text-muted);
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.csv-library-card-rating {
|
||
font-size: 12px;
|
||
color: var(--text-muted);
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.csv-library-card-tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 4px;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.csv-library-card-tag {
|
||
font-size: 11px;
|
||
padding: 2px 8px;
|
||
border-radius: 4px;
|
||
border: 1px solid var(--background-modifier-border);
|
||
color: var(--text-muted);
|
||
background: transparent;
|
||
}
|
||
|
||
/* Undo button in delete Notice — small, inline, looks like a link. */
|
||
.csv-notice-undo {
|
||
margin-left: 6px;
|
||
padding: 2px 8px;
|
||
background: transparent;
|
||
border: 1px solid var(--background-modifier-border);
|
||
color: var(--text-normal);
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
}
|
||
.csv-notice-undo:hover { background: var(--background-modifier-hover); }
|
||
.csv-notice-undo[disabled] { opacity: 0.4; cursor: default; }
|
||
|
||
/* ─── Mobile compact density ──────────────────────────────────────────────────
|
||
On phone-width viewports the Library / Kanban / Table cards mirror the
|
||
mobile-md dashboard's compact card style: tighter padding, smaller type,
|
||
2-column Library grid. Pure CSS — no state to persist. If the user wants
|
||
a manual toggle later, layer a `.csv-density-compact` class on the root
|
||
and reuse these rules.
|
||
────────────────────────────────────────────────────────────────────────── */
|
||
@media (max-width: 600px) {
|
||
/* Content area: tighter outer padding so cards have more room to breathe. */
|
||
.csv-content-area { padding: 12px; }
|
||
|
||
/* — Library: 2-col compact grid (matches mobile-md `compact` layout) — */
|
||
.csv-library-grid {
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 8px;
|
||
padding: 6px 0 2px;
|
||
}
|
||
.csv-library-card {
|
||
padding: 10px 12px;
|
||
border-radius: 10px;
|
||
}
|
||
.csv-library-card-title {
|
||
font-size: 13px;
|
||
line-height: 1.3;
|
||
margin-bottom: 3px;
|
||
/* Allow long titles to wrap cleanly inside the narrower card. */
|
||
word-break: break-word;
|
||
}
|
||
.csv-library-card-meta { font-size: 11px; margin-bottom: 4px; }
|
||
.csv-library-card-rating { font-size: 11px; margin-bottom: 4px; letter-spacing: 1px; }
|
||
.csv-library-card-tags { margin-top: 6px; gap: 3px; }
|
||
.csv-library-card-tag {
|
||
font-size: 10px;
|
||
padding: 1px 6px;
|
||
border-radius: 999px;
|
||
}
|
||
.csv-library-filters { gap: 6px; flex-wrap: wrap; }
|
||
.csv-library-filter-select { flex: 1 1 110px; min-width: 0; font-size: 12px; }
|
||
.csv-library-search { flex: 1 1 100%; font-size: 12px; }
|
||
.csv-library-section-header { padding: 6px 4px; font-size: 12px; }
|
||
|
||
/* — Kanban: tighter cards inside each swipe-snap column — */
|
||
.csv-kanban-col-body { padding: 8px; gap: 6px; }
|
||
.csv-kanban-card { padding: 8px 10px; }
|
||
.csv-kanban-card-title { font-size: 13px; }
|
||
.csv-kanban-card-sub { font-size: 11px; margin-bottom: 6px; }
|
||
.csv-kanban-card-meta { gap: 4px; }
|
||
.csv-kanban-chip { font-size: 11px; padding: 1px 6px; }
|
||
/* Inline notes preview reads tighter inside the smaller card. */
|
||
.csv-kanban-notes-preview { font-size: 11px; -webkit-line-clamp: 2; }
|
||
/* Notes icon stays visible on touch (no hover state). */
|
||
.csv-kanban-notes-icon { opacity: 0.9; padding: 4px 8px; }
|
||
|
||
/* — Table: comfortable on phone widths — */
|
||
.csv-table { font-size: 12px; }
|
||
.csv-table th {
|
||
padding: 6px 8px;
|
||
font-size: 10px;
|
||
}
|
||
.csv-table td {
|
||
padding: 6px 8px;
|
||
min-width: 110px;
|
||
max-width: 220px;
|
||
}
|
||
/* Mobile keeps the 3-line clamp (now on the inner span, doesn't disrupt
|
||
table layout). The original "freezes when I scroll to notes" report
|
||
turned out to be position:relative on every td + ::after pseudo on
|
||
every td — both now scoped to clipped cells only, so the clamp on the
|
||
inner span is safe to keep. */
|
||
.csv-table td.csv-table-notes-cell {
|
||
min-width: 140px;
|
||
max-width: 220px;
|
||
font-size: 11px;
|
||
}
|
||
}
|