:root { --bg-primary: #1a1a1a; --bg-secondary: #242424; --bg-tertiary: #2e2e2e; --bg-hover: #353535; --bg-active: #3a3a3a; --text-primary: #e0e0e0; --text-secondary: #a0a0a0; --text-muted: #666666; --border: #333333; --border-subtle: #2a2a2a; --accent-blue: #4a9eff; --accent-green: #4ade80; --accent-purple: #a78bfa; --accent-amber: #fbbf24; --accent-cyan: #22d3ee; --accent-orange: #fb923c; --accent-gray: #9ca3af; --accent-brown: #a8896c; --status-draft: #666666; --status-active: #4ade80; --status-completed: #4a9eff; --status-archived: #444444; --topbar-height: 44px; --sidebar-width: 260px; --detail-panel-width: 300px; --content-max-width: 960px; --font-ui: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; --font-content: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; --font-mono: 'SF Mono', 'Fira Code', 'Consolas', monospace; --space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px; --space-5: 20px; --space-6: 24px; --space-8: 32px; --space-10: 40px; --radius: 6px; --radius-sm: 4px; --radius-lg: 8px; } * { margin: 0; padding: 0; box-sizing: border-box; } html, body { height: 100%; font-family: var(--font-ui); font-size: 14px; line-height: 1.5; color: var(--text-primary); background: var(--bg-primary); -webkit-font-smoothing: antialiased; } a { color: var(--accent-blue); text-decoration: none; } a:hover { text-decoration: underline; } button { font-family: inherit; font-size: inherit; cursor: pointer; border: none; background: none; color: inherit; } input, textarea, select { font-family: inherit; font-size: inherit; color: var(--text-primary); background: var(--bg-tertiary); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-2) var(--space-3); outline: none; } input:focus, textarea:focus { border-color: var(--accent-blue); } /* Scrollbar */ ::-webkit-scrollbar { width: 6px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; } /* Prose styles for rendered markdown */ .prose { font-family: var(--font-content); font-size: 15px; line-height: 1.7; color: var(--text-primary); max-width: var(--content-max-width); } .prose h1 { font-size: 1.8em; margin: 1.2em 0 0.6em; font-weight: 700; } .prose h2 { font-size: 1.4em; margin: 1em 0 0.5em; font-weight: 600; } .prose h3 { font-size: 1.15em; margin: 0.8em 0 0.4em; font-weight: 600; } .prose p { margin: 0.6em 0; } .prose ul, .prose ol { margin: 0.6em 0; padding-left: 1.5em; } .prose li { margin: 0.2em 0; } .prose li input[type="checkbox"] { margin-right: 0.4em; } .prose pre { background: var(--bg-tertiary); padding: var(--space-4); border-radius: var(--radius); overflow-x: auto; margin: 0.8em 0; font-family: var(--font-mono); font-size: 0.9em; line-height: 1.5; } .prose code { background: var(--bg-tertiary); padding: 0.15em 0.4em; border-radius: var(--radius-sm); font-family: var(--font-mono); font-size: 0.9em; } .prose pre code { background: none; padding: 0; } .prose blockquote { border-left: 3px solid var(--border); padding-left: var(--space-4); margin: 0.8em 0; color: var(--text-secondary); } .prose table { border-collapse: collapse; width: 100%; margin: 0.8em 0; } .prose th, .prose td { border: 1px solid var(--border); padding: var(--space-2) var(--space-3); text-align: left; } .prose th { background: var(--bg-secondary); font-weight: 600; } .prose hr { border: none; border-top: 1px solid var(--border); margin: 1.5em 0; } .prose img { max-width: 100%; border-radius: var(--radius); } .prose a.doc-link { background: var(--bg-tertiary); padding: 0.1em 0.5em; border-radius: var(--radius-sm); color: var(--accent-blue); font-weight: 500; } .prose a.doc-link:hover { background: var(--bg-hover); text-decoration: none; } .prose .doc-link.broken { color: var(--accent-orange); opacity: 0.7; text-decoration: line-through; } /* External links in rendered markdown */ .prose .external-link { color: var(--accent-blue); text-decoration: none; } .prose .external-link:hover { text-decoration: underline; } .prose .external-icon { font-size: 0.75em; opacity: 0.6; vertical-align: super; } /* Editor link styles — autolinked URLs and wiki-links */ .prose a.editor-link { color: var(--accent-blue); text-decoration: none; border-bottom: 1px solid color-mix(in srgb, var(--accent-blue) 30%, transparent); transition: border-color 0.15s; } .prose a.editor-link:hover { border-bottom-color: var(--accent-blue); } /* Attachment images — both inside the editor and in rendered markdown. Lazy-loaded via the node's renderHTML/NodeView; styles below set a sane default size so the editor doesn't reflow once thumbnails arrive. */ .attachment-image { max-width: 100%; height: auto; border-radius: var(--radius); cursor: zoom-in; display: block; margin: 0.4em 0; } .attachment-image:hover { box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent-blue) 35%, transparent); } /* Wrapper appended by the AttachmentImage NodeView to host the selection-time rotate toolbar (TASK-879). Position relative so the toolbar can pin to the top-right corner of the image. */ .attachment-image-wrapper { position: relative; display: inline-block; max-width: 100%; } .attachment-image-wrapper.attachment-image-selected .attachment-image { box-shadow: 0 0 0 2px var(--accent-blue); } /* Rotate toolbar — appears when the AttachmentImage node is selected. Pinned top-right with absolute positioning so it doesn't shift the image's layout when toggling visibility. */ .attachment-image-toolbar { position: absolute; top: 8px; right: 8px; display: inline-flex; gap: 2px; padding: 4px; background: rgba(20, 20, 20, 0.85); border-radius: var(--radius); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4); z-index: 5; -webkit-user-select: none; user-select: none; } .attachment-image-toolbar-hidden { display: none; } .attachment-image-toolbar-btn { width: 28px; height: 28px; display: inline-flex; align-items: center; justify-content: center; padding: 0; border: none; background: transparent; color: #fff; font-size: 18px; line-height: 1; border-radius: 4px; cursor: pointer; transition: background 0.15s, opacity 0.15s; font-family: inherit; } .attachment-image-toolbar-btn:hover:not(:disabled) { background: rgba(255, 255, 255, 0.15); } .attachment-image-toolbar-btn:disabled { opacity: 0.4; cursor: not-allowed; } /* Attachment file chips — Notion-style chip rendering for non-image attachments. Used by both the editor's AttachmentChip node and the read-only markdown render path (TASK-874). Styles target the same class names from both surfaces so the visual matches edge to edge. */ .file-chip { display: inline-flex; align-items: center; gap: 0.4em; max-width: 100%; padding: 4px 10px; margin: 0 1px; background: var(--bg-tertiary); border: 1px solid var(--border-subtle); border-radius: 6px; color: var(--text-primary); text-decoration: none; font-size: 0.92em; line-height: 1.3; vertical-align: baseline; transition: background 0.15s, border-color 0.15s; cursor: pointer; } .file-chip:hover { background: var(--bg-hover); border-color: color-mix(in srgb, var(--accent-blue) 35%, var(--border)); text-decoration: none; } .file-chip-icon { flex-shrink: 0; font-size: 1.05em; line-height: 1; } .file-chip-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 38ch; font-weight: 500; } .file-chip-size { flex-shrink: 0; color: var(--text-muted); font-size: 0.88em; } .file-chip-size:empty { display: none; } /* Editor selection styling — when the chip is the ProseMirror selection (atom node selected for delete/replace), give it the same selection treatment ProseMirror gives other inline nodes. */ .ProseMirror-selectednode.file-chip, .file-chip.ProseMirror-selectednode { outline: 2px solid var(--accent-blue); outline-offset: 1px; } /* Upload-in-progress placeholder — rendered as a ProseMirror widget decoration by the AttachmentUpload extension. Dashed border + faded color so it visually reads as "not yet committed content" while the network round-trip resolves. */ .attachment-upload-placeholder { display: inline-flex; align-items: center; gap: 0.4em; padding: 4px 10px; margin: 0 1px; background: var(--bg-tertiary); border: 1px dashed color-mix(in srgb, var(--accent-blue) 50%, var(--border)); border-radius: 6px; color: var(--text-secondary); font-size: 0.92em; line-height: 1.3; vertical-align: baseline; } .attachment-upload-spinner { width: 12px; height: 12px; border: 2px solid color-mix(in srgb, var(--accent-blue) 35%, transparent); border-top-color: var(--accent-blue); border-radius: 50%; animation: attachment-upload-spin 0.7s linear infinite; flex-shrink: 0; } @keyframes attachment-upload-spin { to { transform: rotate(360deg); } } .attachment-upload-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 30ch; } /* Crop modal (TASK-880). Appended to document.body so styles must be global. Uses the native element with a flex layout so the image fills available space while header/footer stay fixed. */ dialog.attachment-crop-modal { border: none; background: transparent; padding: 0; max-width: 100vw; max-height: 100vh; color: #fff; overflow: visible; } dialog.attachment-crop-modal::backdrop { background: rgba(0, 0, 0, 0.85); backdrop-filter: blur(2px); } .attachment-crop-modal-shell { display: flex; flex-direction: column; width: min(95vw, 1200px); height: min(92vh, 900px); background: var(--bg-secondary); color: var(--text-primary); border-radius: var(--radius); overflow: hidden; box-shadow: 0 12px 40px rgba(0, 0, 0, 0.55); } .attachment-crop-modal-header { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--border); background: var(--bg-secondary); } .attachment-crop-modal-title { font-weight: 600; font-size: 0.95em; } .attachment-crop-modal-aspects { display: inline-flex; gap: 4px; background: var(--bg-tertiary); padding: 2px; border-radius: var(--radius); } .attachment-crop-modal-aspect-btn { padding: 4px 12px; border: none; background: transparent; color: var(--text-secondary); border-radius: 4px; font-size: 0.85em; cursor: pointer; font-family: inherit; transition: background 0.15s, color 0.15s; } .attachment-crop-modal-aspect-btn:hover { background: var(--bg-hover); color: var(--text-primary); } .attachment-crop-modal-aspect-active, .attachment-crop-modal-aspect-active:hover { background: var(--accent-blue); color: #fff; } .attachment-crop-modal-stage { flex: 1; min-height: 0; position: relative; overflow: hidden; background: rgba(0, 0, 0, 0.4); display: flex; align-items: center; justify-content: center; -webkit-user-select: none; user-select: none; } .attachment-crop-modal-img { max-width: 100%; max-height: 100%; display: block; -webkit-user-drag: none; pointer-events: none; } .attachment-crop-modal-rect { position: absolute; box-sizing: border-box; border: 1px solid #fff; box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.45); cursor: move; touch-action: none; } .attachment-crop-modal-handle { position: absolute; width: 12px; height: 12px; background: var(--accent-blue); border: 2px solid #fff; border-radius: 50%; touch-action: none; } .attachment-crop-modal-handle-nw { top: -7px; left: -7px; cursor: nwse-resize; } .attachment-crop-modal-handle-ne { top: -7px; right: -7px; cursor: nesw-resize; } .attachment-crop-modal-handle-sw { bottom: -7px; left: -7px; cursor: nesw-resize; } .attachment-crop-modal-handle-se { bottom: -7px; right: -7px; cursor: nwse-resize; } .attachment-crop-modal-footer { display: flex; justify-content: flex-end; gap: var(--space-2); padding: var(--space-3) var(--space-4); border-top: 1px solid var(--border); background: var(--bg-secondary); } .attachment-crop-modal-cancel, .attachment-crop-modal-apply { padding: 6px 16px; border: 1px solid var(--border); background: var(--bg-tertiary); color: var(--text-primary); border-radius: var(--radius); font-family: inherit; font-size: 0.92em; cursor: pointer; transition: background 0.15s, border-color 0.15s; } .attachment-crop-modal-cancel:hover { background: var(--bg-hover); } .attachment-crop-modal-primary { background: var(--accent-blue); color: #fff; border-color: var(--accent-blue); } .attachment-crop-modal-primary:hover { filter: brightness(1.1); } /* Lightbox dialog — appended to document.body when an attachment image is clicked, so styles must be global rather than scoped to Editor.svelte. Uses the native element; the ::backdrop pseudo handles the overlay shade. The dialog content fills the viewport with a centered image and a single close button. */ dialog.attachment-image-lightbox { border: none; background: transparent; padding: 0; max-width: 100vw; max-height: 100vh; overflow: visible; color: #fff; } dialog.attachment-image-lightbox::backdrop { background: rgba(0, 0, 0, 0.85); backdrop-filter: blur(2px); } dialog.attachment-image-lightbox .attachment-image-lightbox-img { max-width: 95vw; max-height: 95vh; display: block; margin: 0 auto; box-shadow: 0 12px 40px rgba(0, 0, 0, 0.55); cursor: zoom-out; border-radius: var(--radius); } dialog.attachment-image-lightbox .attachment-image-lightbox-close { position: fixed; top: 16px; right: 16px; width: 40px; height: 40px; border-radius: 50%; border: none; background: rgba(0, 0, 0, 0.6); color: #fff; font-size: 28px; line-height: 1; cursor: pointer; display: flex; align-items: center; justify-content: center; z-index: 1; } dialog.attachment-image-lightbox .attachment-image-lightbox-close:hover { background: rgba(0, 0, 0, 0.85); } /* Light mode */ [data-theme="light"] { --bg-primary: #ffffff; --bg-secondary: #f7f7f8; --bg-tertiary: #eeeff1; --bg-hover: #e5e6e8; --bg-active: #dddee0; --text-primary: #1a1a1a; --text-secondary: #555555; --text-muted: #999999; --border: #e0e0e0; --border-subtle: #eaeaea; --accent-blue: #2563eb; --accent-green: #16a34a; --accent-purple: #7c3aed; --accent-amber: #d97706; --accent-cyan: #0891b2; --accent-orange: #ea580c; --accent-gray: #6b7280; --accent-brown: #92400e; --status-draft: #999999; --status-active: #16a34a; --status-completed: #2563eb; --status-archived: #bbbbbb; } @media (prefers-color-scheme: light) { :root:not([data-theme="dark"]) { --bg-primary: #ffffff; --bg-secondary: #f7f7f8; --bg-tertiary: #eeeff1; --bg-hover: #e5e6e8; --bg-active: #dddee0; --text-primary: #1a1a1a; --text-secondary: #555555; --text-muted: #999999; --border: #e0e0e0; --border-subtle: #eaeaea; --accent-blue: #2563eb; --accent-green: #16a34a; --accent-purple: #7c3aed; --accent-amber: #d97706; --accent-cyan: #0891b2; --accent-orange: #ea580c; --accent-gray: #6b7280; --accent-brown: #92400e; --status-draft: #999999; --status-active: #16a34a; --status-completed: #2563eb; --status-archived: #bbbbbb; } } /* Responsive */ @media (max-width: 768px) { :root { --sidebar-width: 280px; } } /* ============================================================================ Print styles (PLAN-620 / TASK-621). Tune Ctrl/Cmd+P output so users can save any Pad page as a clean PDF. This file handles LAYOUT-LEVEL concerns: - hide the workspace chrome (sidebar, top bar, modals, floating UI) - force a light color palette regardless of the active theme - unlock the fixed 100vh / overflow:hidden app shell so content flows across pages in the print preview Item-level formatting (title, properties block, markdown body, header / footer rules, child-item checklist) is layered on top in tasks 622–624. ============================================================================ */ @media print { /* Force a light color palette for print regardless of the active theme. We only need to pin the color variables; spacing / radii stay. */ :root, :root[data-theme="dark"], :root[data-theme="light"] { --bg-primary: #ffffff; --bg-secondary: #ffffff; --bg-tertiary: #f5f5f5; --bg-hover: transparent; --bg-active: transparent; --text-primary: #000000; --text-secondary: #222222; --text-muted: #555555; --border: #cccccc; --border-subtle: #dddddd; } html, body { height: auto; background: #ffffff !important; color: #000000 !important; font-size: 11pt; } /* Unlock the screen shell — it uses height: 100vh / overflow: hidden to keep the sidebar + main column pinned. In print we need the content to flow naturally across pages. */ .app-layout, .app-shell { display: block !important; height: auto !important; max-height: none !important; overflow: visible !important; } .main-content { flex: none !important; width: 100% !important; min-width: 0 !important; overflow: visible !important; } /* Hide the workspace chrome. */ .sidebar, .topbar, .sidebar-expand-btn, .topbar-expand-btn, .mobile-header, .hamburger, .toast-container, .overlay, .palette, .modal, .modal-backdrop { display: none !important; } /* Modals / dialogs / tooltips. Most KeyboardShortcuts-style overlays carry role="dialog" on the backdrop. */ [role="dialog"], [role="tooltip"], [role="menu"] { display: none !important; } /* Opt-in: any element tagged with data-print-hide disappears in print. Components can use this to strip interactive affordances (edit buttons, status pickers, hover cards, etc.) without needing a new CSS rule. */ [data-print-hide] { display: none !important; } /* Strip decorative shadows, gradients, and background images. */ * { box-shadow: none !important; text-shadow: none !important; background-image: none !important; } /* Page margins + page number (PLAN-620 / BUG-625). - Top/bottom margins carve out space for the rendered header / footer (TASK-623). Previously declared in +page.svelte, but Svelte scoped-style timing meant the rule didn't always win over this block, causing the header strip to overlap the title on page 1. Single source of truth here. - The page number lives in `@bottom-right` because `counter(page)` evaluated inside a fixed element's ::after pseudo captures its initial value (0) and never increments per page in Chromium. Margin-box counters evaluate per page correctly. */ @page { /* Top margin is generous-but-plain (no reserved header strip): the page-1 document header lives in normal flow via `.title-row` + `.meta-info`, so subsequent pages start at this margin with no overlap risk. Bottom margin reserves space for the fixed footer + the `@bottom-right` page number. See BUG-626 for why the repeating header was dropped. */ margin: 0.6in 0.6in 1in 0.6in; @bottom-right { content: "Page " counter(page); font-family: var(--font-ui); font-size: 9pt; color: #555; } } /* ----------------------------------------------------------------- TASK-622 — rendered markdown (.prose) tuned for print. ----------------------------------------------------------------- */ .prose { font-size: 11pt; line-height: 1.55; color: #000 !important; max-width: none; } .prose h1, .prose h2, .prose h3, .prose h4, .prose h5, .prose h6 { color: #000 !important; page-break-after: avoid; break-after: avoid-page; page-break-inside: avoid; break-inside: avoid-page; } .prose h1 { font-size: 18pt; } .prose h2 { font-size: 14pt; } .prose h3 { font-size: 12pt; } .prose p, .prose li { orphans: 3; widows: 3; } /* Links: keep the colored style AND append the URL inline so a printed page still carries the reference. Skip internal wiki-links (which resolve to the printed document's siblings) and fragment-only links. Note: rich-editor links are rendered via Tiptap's SafeLink extension using `data-href` instead of `href`, so we target both. */ .prose a[href]::after { content: " (" attr(href) ")"; font-size: 0.85em; color: #555; word-break: break-all; } .prose a[data-href]::after { content: " (" attr(data-href) ")"; font-size: 0.85em; color: #555; word-break: break-all; } .prose a[href^="#"]::after, .prose a[data-href^="#"]::after, .prose a.doc-link::after, .prose a.editor-link[href^="/"]::after, .prose a.editor-link[data-href^="/"]::after { content: ""; } /* Code blocks: visible, readable in light palette, don't orphan across pages when we can help it. */ .prose pre { background: #f5f5f5 !important; border: 1px solid #ddd !important; color: #000 !important; font-size: 9.5pt; page-break-inside: avoid; break-inside: avoid; } .prose code { background: #f5f5f5 !important; color: #000 !important; } /* Images: full page width max, don't split across pages. */ .prose img { max-width: 100% !important; page-break-inside: avoid; break-inside: avoid; } /* Tables: visible borders, contained to page width. */ .prose table { page-break-inside: avoid; break-inside: avoid; border: 1px solid #ccc !important; } .prose th, .prose td { border: 1px solid #ccc !important; color: #000 !important; } .prose th { background: #f5f5f5 !important; } .prose blockquote { border-left: 3px solid #666 !important; color: #333 !important; } /* Editor overlays (bubble menu, link popover, slash menu, mobile toolbar, table toolbar, editor-toolbar) — never print these. */ .bubble-menu, .link-popover, .slash-menu, .mobile-toolbar, .table-toolbar, .toolbar, .editor-toolbar, .editor-link-popover, .editor-bubble-menu { display: none !important; } /* Editor wrapper: in print the content is read-only — remove any interactive cursor, borders, or focus styling. */ .editor-wrapper, .editor-content { border: none !important; background: transparent !important; padding: 0 !important; outline: none !important; } /* ----------------------------------------------------------------- TASK-622 — Properties panel form widgets in print. These rules live here (not in the item detail page's scoped style block) because the widgets are rendered by the FieldEditor child component — Svelte's scoped selectors don't cross component boundaries. Strip interactive styling so the selected value reads as plain text; the `.toggle` checkbox button is exempted and rendered as a visible outlined box with a check mark when on. ----------------------------------------------------------------- */ .field-value select, .field-value input, .field-value textarea, .field-value button:not(.toggle) { appearance: none !important; -webkit-appearance: none !important; background: transparent !important; border: none !important; padding: 0 !important; margin: 0 !important; color: #000 !important; font: inherit !important; cursor: default !important; pointer-events: none; box-shadow: none !important; } /* Custom select controls (FieldEditor) render a dropdown chevron as an inline SVG (`.select-chevron`) inside the button — `appearance: none` on the button doesn't affect SVG children, so the `∨` caret leaks into print. Same story for the expanded dropdown panel (`.select-dropdown`) if one happens to be open at print time. BUG-625. */ .field-value .select-chevron, .field-value .select-dropdown, .select-chevron, .select-dropdown { display: none !important; } /* Checkbox field (`.toggle`): visible outlined box; on-state gets a check mark overlay so the on/off signal survives in print. */ .field-value .toggle { appearance: none !important; -webkit-appearance: none !important; display: inline-block !important; width: 11pt !important; height: 11pt !important; padding: 0 !important; margin: 0 !important; background: #fff !important; border: 1px solid #000 !important; border-radius: 2pt !important; position: relative; cursor: default !important; pointer-events: none; box-shadow: none !important; vertical-align: -1pt; } .field-value .toggle-knob { display: none !important; } .field-value .toggle.on::after { content: "✓"; position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: #000; font-size: 10pt; font-weight: 700; line-height: 1; } }