From ac006033af367fa6070bf975843570e861e4b9d4 Mon Sep 17 00:00:00 2001 From: "Neon:ryan" Date: Tue, 12 May 2026 11:11:41 -0600 Subject: [PATCH 1/3] =?UTF-8?q?feat(canvas):=20PR=201=20=E2=80=94=20shell?= =?UTF-8?q?=20+=20Insights=20view?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First of three PRs splitting the canvas-upgrade work for review (per Daniel's request). This PR lays down the canvas page shell — shared AppHeader, sidebar wiring, theme integration — and ships the Insights view on its own. Workspace (PR 2) and Documents (PR 3) follow. Includes: - Shared AppHeader component (Home button + view tabs + theme toggle), used here on the canvas page; Chat/Home pages can adopt it incrementally. - Insights view with cards/tasks toggle, per-task status (open / in-progress / completed / abandoned), filter chips, sort, pinning, and source-quote expansion. Stats roll up at the task level per Daniel's feedback. - "Ask follow-up" and "Send to Kanban" actions stash state so PR 2's Kanban widget picks up work transferred from Insights with zero extra wiring. - Welcome tour trimmed to PR 1 scope (Insights only). - CanvasPage.css ships the full design-system tokens up front to avoid a giant CSS diff in PR 2/3; styles for not-yet-rendered components are inert. Sidebar / ChatPage / HomePage are intentionally untouched in this PR — those header-standardization changes can land separately to keep this diff focused. --- phd-advisor-frontend/src/App.js | 10 +- .../src/components/AppHeader.js | 100 + .../src/components/canvas/CanvasIcon.js | 90 + .../components/canvas/CanvasWelcomeTour.js | 87 + .../src/components/canvas/canvasData.js | 124 + .../src/components/canvas/platform.js | 5 + phd-advisor-frontend/src/pages/CanvasPage.js | 1161 ++--- .../src/styles/CanvasPage.css | 3791 ++++++++++++++--- 8 files changed, 4333 insertions(+), 1035 deletions(-) create mode 100644 phd-advisor-frontend/src/components/AppHeader.js create mode 100644 phd-advisor-frontend/src/components/canvas/CanvasIcon.js create mode 100644 phd-advisor-frontend/src/components/canvas/CanvasWelcomeTour.js create mode 100644 phd-advisor-frontend/src/components/canvas/canvasData.js create mode 100644 phd-advisor-frontend/src/components/canvas/platform.js diff --git a/phd-advisor-frontend/src/App.js b/phd-advisor-frontend/src/App.js index 9d03459c..897f05e5 100644 --- a/phd-advisor-frontend/src/App.js +++ b/phd-advisor-frontend/src/App.js @@ -38,7 +38,10 @@ function App() { setCurrentView('auth'); }; - const navigateToCanvas = () => { + const navigateToCanvas = (canvasView) => { + if (['insights', 'workspace', 'deliverables'].includes(canvasView)) { + localStorage.setItem('canvas-view-v2', canvasView); + } setCurrentView('canvas'); }; @@ -74,7 +77,9 @@ function App() {
{currentView === 'home' && ( )} @@ -82,9 +87,10 @@ function App() { )} {currentView === 'canvas' && isAuthenticated && ( - diff --git a/phd-advisor-frontend/src/components/AppHeader.js b/phd-advisor-frontend/src/components/AppHeader.js new file mode 100644 index 00000000..853a487a --- /dev/null +++ b/phd-advisor-frontend/src/components/AppHeader.js @@ -0,0 +1,100 @@ +import React from 'react'; +import { Home, Menu, Users } from 'lucide-react'; +import ThemeToggle from './ThemeToggle'; +import { useAppConfig } from '../contexts/AppConfigContext'; + +/** + * Shared floating header used on every page so the app feels like one surface. + * + * Props: + * currentPage: 'home' | 'chat' | 'canvas' + * onNavigateToHome, onNavigateToChat, onNavigateToCanvas: navigation callbacks + * (onNavigateToCanvas may receive 'insights' | 'workspace' to deep-link a view) + * onMobileMenu?: () => void — when present, shows the mobile menu button + * children?: ReactNode — extra controls slotted between the tabs and the theme toggle + */ +const AppHeader = ({ + currentPage = 'home', + onNavigateToHome, + onNavigateToChat, + onNavigateToCanvas, + onMobileMenu, + children, +}) => { + const { config, resolveIcon } = useAppConfig(); + const BrandIcon = resolveIcon ? resolveIcon('Users') : Users; + + const goToCanvas = (view) => { + if (onNavigateToCanvas) onNavigateToCanvas(view); + }; + + // Accept either 'canvas' (all canvas tabs highlight equally) or a more specific + // 'canvas-' from CanvasPage so only the active one highlights. + const isOnHome = currentPage === 'home'; + const isOnChat = currentPage === 'chat'; + const isOnCanvas = currentPage === 'canvas' || currentPage.startsWith('canvas-'); + const canvasSub = currentPage.startsWith('canvas-') ? currentPage.slice(7) : null; + const tabActive = (sub) => isOnCanvas && (canvasSub === null ? false : canvasSub === sub); + + return ( +
+
+ {onMobileMenu && ( + + )} + +
+
+ +
+
+

{config?.app?.title || 'Advisory'}

+

{config?.app?.subtitle || 'AI-Powered Guidance'}

+
+
+
+ + {/* Hide the view pill bar on the home page — home is a landing page, + not part of the chat ↔ canvas surface. */} + {!isOnHome && ( +
+ + +
+ )} + + {/* Compact mobile dropdown — appears in place of the pill bar at narrow widths */} + {!isOnHome && ( + + )} + +
+ {children} + +
+
+ ); +}; + +export default AppHeader; diff --git a/phd-advisor-frontend/src/components/canvas/CanvasIcon.js b/phd-advisor-frontend/src/components/canvas/CanvasIcon.js new file mode 100644 index 00000000..933b71f9 --- /dev/null +++ b/phd-advisor-frontend/src/components/canvas/CanvasIcon.js @@ -0,0 +1,90 @@ +import React from 'react'; + +const ICONS = { + sparkles: <>, + layout: <>, + insights: <>, + search: <>, + bell: <>, + settings: <>, + plus: , + x: , + check: , + refresh: <>, + trash: <>, + grip: <>, + more: <>, + pin: <>, + link: <>, + message: , + task: <>, + book: <>, + kanban: <>, + timer: <>, + pencil: <>, + calendar: <>, + wallet: <>, + flag: <>, + gavel: <>, + scale: <>, + brain: <>, + alert: <>, + notes: <>, + list: <>, + zap: , + flame: <>, + heart: , + graph: <>, + award: <>, + network: <>, + database: <>, + flask: <>, + shield: , + music: <>, + bullseye: <>, + arrow: <>, + copy: <>, + download: <>, + play: , + pause: <>, + reset: <>, + star: , + shuffle: <>, + expand: <>, + resize: <>, + smile: <>, + send: , + chevron: , + user: <>, + cite: <>, + pinned: <>, + microscope: <>, + sun: <>, + moon: , + back: <>, +}; + +const Icon = ({ name, size = 16, className = '', style }) => { + const paths = ICONS[name]; + if (!paths) return null; + return ( + + ); +}; + +export default Icon; diff --git a/phd-advisor-frontend/src/components/canvas/CanvasWelcomeTour.js b/phd-advisor-frontend/src/components/canvas/CanvasWelcomeTour.js new file mode 100644 index 00000000..83e46ed2 --- /dev/null +++ b/phd-advisor-frontend/src/components/canvas/CanvasWelcomeTour.js @@ -0,0 +1,87 @@ +import React, { useState, useEffect } from 'react'; +import Icon from './CanvasIcon'; + +const TOUR_KEY = 'canvas-tour-seen-v1'; + +const STEPS = [ + { + title: 'Welcome to your Canvas', + icon: 'sparkles', + body: 'AI-summarized highlights from your research conversations live here. Each insight is a discrete task you can mark open, in-progress, completed, or abandoned.', + }, + { + title: 'Filter, sort, pin', + icon: 'layout', + body: 'Use the filter chips to narrow by status, category, or confidence. Pin the most important sections to keep them at the top. The Tasks view flattens everything into a single to-do list.', + }, + { + title: 'Ask follow-up', + icon: 'message', + body: 'Each insight has an "Ask follow-up" action that opens a fresh chat with the relevant context preloaded — useful when a synthesis raises a new question worth digging into.', + }, +]; + +const CanvasWelcomeTour = ({ forceShow = false, onClose }) => { + const [step, setStep] = useState(0); + const [visible, setVisible] = useState(false); + + useEffect(() => { + const seen = localStorage.getItem(TOUR_KEY); + if (forceShow || !seen) setVisible(true); + }, [forceShow]); + + const dismiss = () => { + localStorage.setItem(TOUR_KEY, '1'); + setVisible(false); + if (onClose) onClose(); + }; + + if (!visible) return null; + + const s = STEPS[step]; + const isLast = step === STEPS.length - 1; + + return ( +
{ if (e.target === e.currentTarget) dismiss(); }}> +
e.stopPropagation()}> +
+
+
+
{s.title}
+
Step {step + 1} of {STEPS.length}
+
+ +
+
+

{s.body}

+
+
+
+ {STEPS.map((_, i) => ( + setStep(i)} + style={{ + width: 8, height: 8, borderRadius: '50%', cursor: 'pointer', + background: i === step ? 'var(--canvas-accent)' : 'var(--canvas-surface-3)', + transition: 'background .15s', + }}/> + ))} +
+
+ {step > 0 && ( + + )} + {!isLast && ( + + )} + +
+
+
+
+ ); +}; + +export default CanvasWelcomeTour; diff --git a/phd-advisor-frontend/src/components/canvas/canvasData.js b/phd-advisor-frontend/src/components/canvas/canvasData.js new file mode 100644 index 00000000..aa35c8df --- /dev/null +++ b/phd-advisor-frontend/src/components/canvas/canvasData.js @@ -0,0 +1,124 @@ +// Demo data for a Year-2 PhD student in computational neuroscience. + +export const DEMO_PROJECT = { + title: "Cortical Predictive Coding in Mouse V1", + meta: "Year 2 · PhD · Adv. Dr. Reineke", +}; + +export const INSIGHTS = [ + { + id: 'i-progress', + title: 'Research progress', + icon: 'graph', + category: 'progress', + confidence: 78, + summary: 'Primary recordings from 4 of 6 planned animals are complete. Remaining two scheduled for May 18 and May 25. Analysis pipeline working on existing data; first results draft expected June.', + bullets: [ + 'V1 recordings: 4/6 animals complete (M1–M4)', + 'Pipeline: spike-sorting validated, GLM model converging on M1–M2', + 'Risk: M3 fixation drift suspected; need re-review with adv.', + ], + pinned: true, + sources: 12, + updatedMinutesAgo: 3, + quotes: [ + '"Animal M4 recording finished today, sorting completes tomorrow." — May 6 lab notes', + '"Pipeline is happy with M1, M2; M3 looks drifty." — chat with Reineke advisor', + ], + }, + { + id: 'i-method', + title: 'Methodology', + icon: 'flask', + category: 'theory', + confidence: 64, + summary: 'GLM with spike-history kernel + visual drive is your declared model. You\'ve resisted committing to a specific predictive-coding formulation; this comes up in every advisor meeting.', + bullets: [ + 'Decided: GLM with history kernel + drift-reg covariates', + 'Open: which predictive-coding variant — Rao & Ballard vs. Bastos top-down', + 'Open: how to operationalize "prediction error" from extracellular spikes', + ], + sources: 8, + updatedMinutesAgo: 12, + quotes: [ + '"Need to pick a PC formulation by next 1:1." — meeting notes May 2', + '"Bastos lets you predict laminar profile; Rao&Ballard does not." — methodologist chat', + ], + }, + { + id: 'i-lit', + title: 'Literature review', + icon: 'book', + category: 'literature', + confidence: 71, + summary: 'Strong on canonical predictive coding (Rao & Ballard 1999, Bastos 2012, Keller & Mrsic-Flogel 2018). Thin on recent feedback-circuit anatomy and on counter-evidence — this is showing up as a critique gap.', + bullets: [ + 'Coverage: 47 papers; ~30 well-summarized', + 'Gap: sparse on L5b feedback anatomy (Harris/Shepherd lab)', + 'Gap: no engagement with anti-PC critiques (e.g. Heeger 2017)', + ], + sources: 47, + updatedMinutesAgo: 22, + quotes: [ + '"Have you read Heeger 2017? It changes a lot." — lit-review aide', + '"L5b feedback anatomy is your weak spot." — devil\'s advocate', + ], + }, + { + id: 'i-questions', + title: 'Open research questions', + icon: 'sparkles', + category: 'theory', + confidence: 58, + summary: 'Three live threads. Question 1 (does L2/3 spiking encode prediction error?) is the dissertation core. Q2 and Q3 are scoped to specific aims.', + bullets: [ + 'Q1: Does L2/3 firing during oddball encode prediction error vs. surprise?', + 'Q2: How does this depend on context length (1 vs. 4 vs. 16 trials)?', + 'Q3: Is the signal sharpened by feedback from V2/RSC?', + ], + sources: 6, + updatedMinutesAgo: 38, + quotes: [ + '"Q1 is what the whole dissertation rests on." — methodologist', + '"Q3 is exciting but probably out of scope for the thesis." — Reineke', + ], + }, + { + id: 'i-next', + title: 'Next steps', + icon: 'arrow', + category: 'action', + confidence: 82, + summary: 'Concrete, near-term actions. Two of these have been on the list for 3+ weeks.', + bullets: [ + 'Re-review M3 drift artifact w/ adv. (overdue, 3w)', + 'Draft Aim 2 analysis section (target: May 22)', + 'Read Heeger 2017 + Aitchison & Lengyel 2017', + 'Schedule pilot with M5 (May 18)', + ], + sources: 5, + updatedMinutesAgo: 8, + quotes: [ + '"Aim 2 draft has to land by May 22 or quals slip." — Reineke', + '"M3 review keeps getting punted." — last 3 advisor meetings', + ], + }, + { + id: 'i-blockers', + title: 'Blockers & risks', + icon: 'alert', + category: 'risk', + confidence: 70, + summary: 'One technical, one structural. The structural one is more important and you are deferring it.', + bullets: [ + 'Technical: Drift on M3 — may lose 1 animal of data', + 'Structural: No clear predictive-coding theory commitment yet → hard to define what counts as evidence', + ], + sources: 4, + updatedMinutesAgo: 18, + quotes: [ + '"If M3 is unusable you\'re at n=5 — still publishable but tight." — methodologist', + '"Without a theory commitment you can\'t falsify anything." — devil\'s advocate', + ], + }, +]; diff --git a/phd-advisor-frontend/src/components/canvas/platform.js b/phd-advisor-frontend/src/components/canvas/platform.js new file mode 100644 index 00000000..499766c1 --- /dev/null +++ b/phd-advisor-frontend/src/components/canvas/platform.js @@ -0,0 +1,5 @@ +// Tiny helper so keyboard shortcut hints adapt to the user's OS. +// Detect once at module load — we don't expect platform to change mid-session. +const isMac = typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.platform); +export const MOD = isMac ? '⌘' : 'Ctrl'; +export const MOD_LABEL = isMac ? 'Cmd' : 'Ctrl'; diff --git a/phd-advisor-frontend/src/pages/CanvasPage.js b/phd-advisor-frontend/src/pages/CanvasPage.js index 64e005d7..f5066df1 100644 --- a/phd-advisor-frontend/src/pages/CanvasPage.js +++ b/phd-advisor-frontend/src/pages/CanvasPage.js @@ -1,574 +1,701 @@ -import React, { useState, useEffect } from 'react'; -import { - FileText, - RefreshCw, - Download, - Calendar, - TrendingUp, - Target, - BookOpen, - Lightbulb, - AlertTriangle, - Users, - BarChart3, - Heart, - ArrowLeft, - Printer, - Trash2, - MessageCircle, - ArrowRight -} from 'lucide-react'; +import React, { useState, useEffect, useMemo } from 'react'; +import { HelpCircle } from 'lucide-react'; +import { useTheme } from '../contexts/ThemeContext'; import { useAppConfig } from '../contexts/AppConfigContext'; -import CopyrightNotice from '../components/CopyrightNotice'; -import ConfirmDialog from '../components/ConfirmDialog'; +import Sidebar from '../components/Sidebar'; +import AppHeader from '../components/AppHeader'; +import Icon from '../components/canvas/CanvasIcon'; +import { INSIGHTS } from '../components/canvas/canvasData'; +import CanvasWelcomeTour from '../components/canvas/CanvasWelcomeTour'; +import { MOD } from '../components/canvas/platform'; import '../styles/CanvasPage.css'; -// Section icons mapping -const sectionIcons = { - research_progress: TrendingUp, - methodology: BarChart3, - theoretical_framework: BookOpen, - challenges_obstacles: AlertTriangle, - next_steps: Target, - writing_communication: FileText, - career_development: Users, - literature_review: BookOpen, - data_analysis: BarChart3, - motivation_mindset: Heart -}; +const VIEW_KEY = 'canvas-view-v2'; +const STATES_KEY = 'canvas-states-v2'; -const CanvasSection = ({ section, sectionKey, isExpanded, onToggle }) => { - const IconComponent = sectionIcons[sectionKey] || Lightbulb; - - return ( -
-
onToggle(sectionKey)} - > -
- -
-

{section.title}

-

{section.description}

-
-
-
- {section.insights.length} insights -
- ▼ -
-
-
- - {isExpanded && ( -
- {section.insights.length === 0 ? ( -
- -

No insights yet. Keep chatting with your advisors to build this section!

-
- ) : ( -
- {section.insights.map((insight, index) => ( -
-
- {insight.content} -
-
- {insight.source_persona} - - {Math.round(insight.confidence_score * 100)}% confidence - -
-
- ))} -
- )} -
- )} -
- ); +// ============================================================================ +// Insights view — AI-synthesized highlights with stats bar, filters, and +// click-to-expand source quotes. +// ============================================================================ +const INSIGHT_CATEGORIES = [ + { id: 'all', label: 'All' }, + { id: 'open', label: 'Open' }, + { id: 'in-progress', label: 'In progress' }, + { id: 'completed', label: 'Completed' }, + { id: 'abandoned', label: 'Abandoned' }, + { id: 'pinned', label: 'Pinned' }, + { id: 'high', label: 'High confidence' }, + { id: 'progress', label: 'Progress' }, + { id: 'theory', label: 'Theory' }, + { id: 'literature', label: 'Literature' }, + { id: 'action', label: 'Actions' }, + { id: 'risk', label: 'Risks' }, +]; +const CATEGORY_TINT = { + progress: 'rgba(16, 185, 129, 0.12)', + theory: 'rgba(99, 102, 241, 0.12)', + literature: 'rgba(245, 158, 11, 0.12)', + action: 'rgba(59, 130, 246, 0.12)', + risk: 'rgba(220, 38, 38, 0.12)', +}; +const CATEGORY_FG = { + progress: '#10B981', + theory: '#818CF8', + literature: '#F59E0B', + action: '#3B82F6', + risk: '#DC2626', }; +const confidenceTier = (c) => c >= 75 ? 'high' : c >= 60 ? 'med' : 'low'; + +// Task statuses live on each individual bullet within an insight, not on the +// whole card — Daniel's feedback: "each card is a discrete task, not a set of tasks". +const TASK_STATUSES = [ + { id: 'open', label: 'Open', color: 'var(--canvas-text-3)', icon: 'sparkles' }, + { id: 'in-progress', label: 'In progress', color: '#3B82F6', icon: 'graph' }, + { id: 'completed', label: 'Completed', color: '#10B981', icon: 'check' }, + { id: 'abandoned', label: 'Abandoned', color: 'var(--canvas-text-4)', icon: 'x' }, +]; +const TASK_STATUS_KEY = 'canvas-task-status-v1'; +const taskKey = (insId, idx) => `${insId}::${idx}`; -const CanvasPage = ({ user, authToken, onNavigateToChat, onSignOut }) => { - const { config } = useAppConfig(); - const appName = config?.app_settings?.app_name || 'Advisory Panel'; - const [canvasData, setCanvasData] = useState(null); - const [isLoading, setIsLoading] = useState(true); - const [isUpdating, setIsUpdating] = useState(false); - const [expandedSections, setExpandedSections] = useState({}); - const [stats, setStats] = useState({}); - const [isPrintView, setIsPrintView] = useState(false); - const [isProcessingFirstTime, setIsProcessingFirstTime] = useState(false); - const [isRefreshing, setIsRefreshing] = useState(false); - const [showClearConfirm, setShowClearConfirm] = useState(false); - const [isClearing, setIsClearing] = useState(false); +function InsightsView({ widgetStates, setWidgetStates, onNavigateToChat }) { + const [pinned, setPinned] = useState(() => new Set(INSIGHTS.filter(i => i.pinned).map(i => i.id))); + const [taskStatuses, setTaskStatuses] = useState(() => { + try { return JSON.parse(localStorage.getItem(TASK_STATUS_KEY) || '{}'); } catch { return {}; } + }); + const [filter, setFilter] = useState('all'); + const [sortBy, setSortBy] = useState('confidence'); + const [expanded, setExpanded] = useState(new Set()); + const [refreshing, setRefreshing] = useState(false); + const [openStatusMenu, setOpenStatusMenu] = useState(null); + // 'cards' = current cards-of-tasks layout, 'tasks' = flat task list per Daniel's + // "Sections in sidebar, Tasks in the main view" suggestion. + const [viewMode, setViewMode] = useState(() => localStorage.getItem('canvas-insights-view') || 'cards'); + useEffect(() => { localStorage.setItem('canvas-insights-view', viewMode); }, [viewMode]); useEffect(() => { - let pollInterval = null; - - const initializeCanvas = async () => { - await fetchCanvas(); - await fetchStats(); - await triggerAutoUpdate(); - - setTimeout(() => { - checkForEmptyCanvasWithChats(); - }, 2000); - }; - - initializeCanvas(); - - // Cleanup on unmount - return () => { - if (pollInterval) { - clearInterval(pollInterval); - } - }; - }, []); + localStorage.setItem(TASK_STATUS_KEY, JSON.stringify(taskStatuses)); + }, [taskStatuses]); - const fetchCanvas = async () => { - try { - const response = await fetch(`${process.env.REACT_APP_API_URL}/api/phd-canvas`, { - headers: { - 'Authorization': `Bearer ${authToken}`, - 'Content-Type': 'application/json' - } - }); - - if (response.ok) { - const data = await response.json(); - setCanvasData(data); - - // Auto-expand sections with insights - const sectionsToExpand = {}; - Object.entries(data.sections).forEach(([key, section]) => { - if (section.insights.length > 0) { - sectionsToExpand[key] = true; - } - }); - setExpandedSections(sectionsToExpand); - } else { - console.error('Failed to fetch canvas'); - } - } catch (error) { - console.error('Error fetching canvas:', error); - } finally { - setIsLoading(false); - } + const taskStatusOf = (insId, idx) => taskStatuses[taskKey(insId, idx)] || 'open'; + const setTaskStatus = (insId, idx, status) => { + setTaskStatuses(prev => ({ ...prev, [taskKey(insId, idx)]: status })); + const lbl = TASK_STATUSES.find(s => s.id === status)?.label || status; + window.dispatchEvent(new CustomEvent('canvas-toast', { detail: { msg: `Task marked ${lbl}`, kind: 'success' } })); }; - const fetchStats = async () => { - try { - const response = await fetch(`${process.env.REACT_APP_API_URL}/api/phd-canvas/stats`, { - headers: { - 'Authorization': `Bearer ${authToken}`, - 'Content-Type': 'application/json' - } - }); - - if (response.ok) { - const data = await response.json(); - setStats(data); - } - } catch (error) { - console.error('Error fetching stats:', error); - } + // Roll up to a card-level status: completed if all tasks done, abandoned if all abandoned, + // in-progress if any are in-progress, otherwise open. + const insightRollup = (ins) => { + const states = ins.bullets.map((_, idx) => taskStatusOf(ins.id, idx)); + const total = states.length; + if (total === 0) return { state: 'open', done: 0, total: 0, pct: 0 }; + const done = states.filter(s => s === 'completed').length; + const inProg = states.filter(s => s === 'in-progress').length; + const abandoned = states.filter(s => s === 'abandoned').length; + let state = 'open'; + if (done === total) state = 'completed'; + else if (abandoned === total) state = 'abandoned'; + else if (inProg > 0 || done > 0) state = 'in-progress'; + return { state, done, total, inProg, abandoned, pct: Math.round((done / total) * 100) }; }; - // Check if user has chats but empty canvas - const checkForEmptyCanvasWithChats = async () => { - try { - const isEmpty = !canvasData || canvasData.total_insights === 0; - - if (isEmpty) { - const response = await fetch(`${process.env.REACT_APP_API_URL}/api/chat-sessions/count`, { - headers: { - 'Authorization': `Bearer ${authToken}`, - 'Content-Type': 'application/json' - } - }); - - if (response.ok) { - const { count } = await response.json(); - if (count > 0) { - console.log(`User has ${count} chats but empty canvas. Triggering full refresh.`); - await handleFullRefresh(); - } - } else { - console.error('Failed to fetch chat sessions count:', response.status); - // Don't trigger refresh if count fails - return; - } - } - } catch (error) { - console.error('Error checking for empty canvas with chats:', error); - // Stop the polling if there's an error - return; - } + const togglePin = (id) => { + setPinned(prev => { + const n = new Set(prev); + if (n.has(id)) n.delete(id); else n.add(id); + return n; + }); }; - - const triggerAutoUpdate = async () => { - try { - const response = await fetch(`${process.env.REACT_APP_API_URL}/api/phd-canvas/auto-update`, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${authToken}`, - 'Content-Type': 'application/json' - } - }); - - if (response.ok) { - const result = await response.json(); - - // If this is a first-time canvas update, show appropriate message - if (result.type === 'full_update') { - console.log('First-time canvas detected. Processing all your chats...'); - - // Show loading state - setIsProcessingFirstTime(true); - setIsUpdating(true); - - // Poll for updates every 10 seconds for up to 3 minutes - let attempts = 0; - const maxAttempts = 18; // 3 minutes / 10 seconds - - const pollForUpdates = setInterval(async () => { - attempts++; - - try { - await fetchCanvas(); - - // If canvas now has insights, stop polling - if (canvasData && canvasData.total_insights > 0) { - clearInterval(pollForUpdates); - setIsUpdating(false); - setIsProcessingFirstTime(false); - console.log('Canvas successfully populated with insights!'); - } - - // Stop polling after max attempts - if (attempts >= maxAttempts) { - clearInterval(pollForUpdates); - setIsUpdating(false); - setIsProcessingFirstTime(false); - } - } catch (error) { - console.error('Error polling for updates:', error); - } - }, 10000); - } - } - } catch (error) { - console.error('Error triggering auto-update:', error); - } + const toggleExpand = (id) => { + setExpanded(prev => { + const n = new Set(prev); + if (n.has(id)) n.delete(id); else n.add(id); + return n; + }); }; - const handleClearCanvas = async () => { - setIsClearing(true); - try { - const response = await fetch(`${process.env.REACT_APP_API_URL}/api/phd-canvas`, { - method: 'DELETE', - headers: { - 'Authorization': `Bearer ${authToken}`, - 'Content-Type': 'application/json' - } - }); - if (response.ok) { - setCanvasData(null); - await fetchCanvas(); - } - } catch (error) { - console.error('Error clearing canvas:', error); - } finally { - setIsClearing(false); - setShowClearConfirm(false); - } + const insightToTaskTitle = (ins) => { + const plain = (ins.bullets[0] || ins.summary || ins.title).replace(/<[^>]+>/g, ''); + return plain.length > 80 ? plain.slice(0, 77) + '…' : plain; }; - const handleRefreshCanvas = async () => { - // Prevent multiple simultaneous refresh requests - if (isRefreshing || isUpdating) { - console.log('Refresh already in progress, ignoring duplicate request'); - return; - } - - setIsRefreshing(true); - setIsUpdating(true); - - try { - const response = await fetch(`${process.env.REACT_APP_API_URL}/api/phd-canvas/refresh`, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${authToken}`, - 'Content-Type': 'application/json' - } - }); - - if (response.ok) { - const result = await response.json(); - console.log('Full refresh initiated:', result); - - // Poll for updates - setTimeout(() => { - fetchCanvas(); - fetchStats(); - }, 5000); - - setTimeout(() => { - setIsUpdating(false); - setIsRefreshing(false); - }, 10000); - } - } catch (error) { - console.error('Error refreshing canvas:', error); - setIsUpdating(false); - setIsRefreshing(false); - } + // Pre-stages a kanban card in shared widget state so that when the user adds a + // Kanban widget (next PR) the work transferred from Insights is already there. + const sendToKanban = (ins) => { + if (!setWidgetStates) return; + const kanban = widgetStates.kanban || { cards: [] }; + const card = { + id: 'k' + Date.now(), + col: 'todo', + title: insightToTaskTitle(ins), + priority: 'med', + meta: `from Insights · ${ins.title}`, + }; + setWidgetStates(s => ({ ...s, kanban: { ...kanban, cards: [...(kanban.cards || []), card] } })); + window.dispatchEvent(new CustomEvent('canvas-toast', { detail: { msg: 'Sent to Kanban (To Do)', kind: 'success' } })); }; - const handleFullRefresh = async () => { - // Prevent multiple simultaneous refresh requests - if (isRefreshing || isUpdating) { - console.log('Refresh already in progress, ignoring duplicate request'); - return; - } - - setIsRefreshing(true); - setIsUpdating(true); - - try { - const response = await fetch(`${process.env.REACT_APP_API_URL}/api/phd-canvas/refresh`, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${authToken}`, - 'Content-Type': 'application/json' - } - }); - - if (response.ok) { - const result = await response.json(); - console.log('Full refresh initiated:', result); - - // Poll for updates - setTimeout(() => { - fetchCanvas(); - fetchStats(); - }, 5000); - - setTimeout(() => { - setIsUpdating(false); - setIsRefreshing(false); - }, 10000); - } - } catch (error) { - console.error('Error refreshing canvas:', error); - setIsUpdating(false); - setIsRefreshing(false); - } + // TODO(LLM): real refresh hits the orchestrator and re-synthesizes insights. + const handleRefresh = () => { + setRefreshing(true); + setTimeout(() => setRefreshing(false), 900); + window.dispatchEvent(new CustomEvent('canvas-toast', { detail: { msg: 'Refreshing insights… (stub)', kind: 'success' } })); }; - const toggleSection = (sectionKey) => { - setExpandedSections(prev => ({ - ...prev, - [sectionKey]: !prev[sectionKey] + // "Ask follow-up": stash a draft prompt + insight context that the chat page + // can pick up on next navigation. Backend hookup TODO: include source-conversation IDs. + const askFollowUp = (ins) => { + const plain = (ins.bullets[0] || ins.summary || '').replace(/<[^>]+>/g, ''); + const prompt = `Follow up on the insight "${ins.title}": ${plain}`; + try { + localStorage.setItem('canvas-chat-handoff', JSON.stringify({ + at: Date.now(), + prompt, + insightId: ins.id, + insightTitle: ins.title, + })); + } catch { /* ignore */ } + window.dispatchEvent(new CustomEvent('canvas-toast', { + detail: { msg: `Follow-up drafted: "${ins.title}" — opening chat`, kind: 'success' }, })); + // Use the existing navigation prop if present; falls back to the global event. + if (onNavigateToChat) onNavigateToChat(); }; - const handlePrint = () => { - setIsPrintView(true); - setTimeout(() => { - window.print(); - setIsPrintView(false); - }, 100); - }; + const filtered = useMemo(() => { + let r = INSIGHTS; + if (filter === 'pinned') r = r.filter(i => pinned.has(i.id)); + else if (filter === 'high') r = r.filter(i => i.confidence >= 75); + else if (['open', 'in-progress', 'completed', 'abandoned'].includes(filter)) r = r.filter(i => insightRollup(i).state === filter); + else if (filter !== 'all') r = r.filter(i => i.category === filter); + if (sortBy === 'confidence') r = [...r].sort((a, b) => b.confidence - a.confidence); + if (sortBy === 'recent') r = [...r].sort((a, b) => (a.updatedMinutesAgo || 0) - (b.updatedMinutesAgo || 0)); + if (sortBy === 'progress') r = [...r].sort((a, b) => insightRollup(b).pct - insightRollup(a).pct); + return r; + }, [filter, sortBy, pinned, taskStatuses]); // eslint-disable-line react-hooks/exhaustive-deps - const formatDate = (dateString) => { - if (!dateString) return 'Never'; - return new Date(dateString).toLocaleDateString(); - }; + // Aggregate stats over individual TASKS, not insights (Daniel's framing) + const stats = useMemo(() => { + const allTasks = INSIGHTS.flatMap(i => i.bullets.map((_, idx) => taskStatusOf(i.id, idx))); + const taskTotal = allTasks.length; + const completed = allTasks.filter(s => s === 'completed').length; + const inProgress = allTasks.filter(s => s === 'in-progress').length; + const abandoned = allTasks.filter(s => s === 'abandoned').length; + const open = taskTotal - completed - inProgress - abandoned; + const totalSources = INSIGHTS.reduce((s, i) => s + (i.sources || 0), 0); + const avgConf = Math.round(INSIGHTS.reduce((s, i) => s + i.confidence, 0) / INSIGHTS.length); + return { + sections: INSIGHTS.length, taskTotal, completed, inProgress, abandoned, open, + totalSources, avgConf, pinnedCount: pinned.size, + }; + }, [pinned, taskStatuses]); // eslint-disable-line react-hooks/exhaustive-deps + + const lastUpdated = Math.min(...INSIGHTS.map(i => i.updatedMinutesAgo || 0)); - if (isLoading) { + // Empty state — defensive (current data is hardcoded but a real backend could send []) + if (INSIGHTS.length === 0) { return ( -
-
-

Loading your {appName} Canvas...

-
+ <> +
+
+

Insights

+
AI-synthesized from your research conversations.
+
+
+
+ +
No insights yet
+
Have a conversation with your advisors and insights will appear here.
+
+ ); } - // Sort sections by priority and insights count - const sortedSections = Object.entries(canvasData?.sections || {}) - .sort(([, a], [, b]) => { - // First by priority (lower number = higher priority) - if (a.priority !== b.priority) { - return a.priority - b.priority; - } - // Then by insights count (more insights first) - return b.insights.length - a.insights.length; - }); - return ( -
- {/* Header */} -
-
- - -
- - - + <> +
+
+

Insights

+
AI-synthesized from your research conversations.
+
+
- + {/* Stats bar */} +
+
+ {stats.completed}/{stats.taskTotal} + tasks done +
+
+
+ + +
+ + {stats.completed} done · {stats.inProgress} in progress · {stats.open} open + {stats.abandoned > 0 && ` · ${stats.abandoned} abandoned`} +
- -
-

- - {appName} Canvas -

-

Your research progress at a glance

+
+ {stats.sections} + sections
+
+ {stats.avgConf}% + avg confidence +
+ + + + updated {lastUpdated} min ago + +
- {/* Stats Bar */} -
-
- {canvasData?.total_insights || 0} - Total Insights -
-
- - {Object.keys(canvasData?.sections || {}).filter(key => - canvasData.sections[key].insights.length > 0 - ).length} - - Active Sections + {/* View toggle: Cards (sections with their tasks) vs Tasks (flat list) */} +
+ + +
+ + {/* Filter + sort */} +
+
+ {INSIGHT_CATEGORIES.map(c => { + const count = + c.id === 'all' ? INSIGHTS.length : + c.id === 'pinned' ? pinned.size : + c.id === 'high' ? INSIGHTS.filter(i => i.confidence >= 75).length : + ['open', 'in-progress', 'completed', 'abandoned'].includes(c.id) ? INSIGHTS.filter(i => insightRollup(i).state === c.id).length : + INSIGHTS.filter(i => i.category === c.id).length; + if (count === 0 && c.id !== 'all') return null; + return ( + + ); + })}
-
- - {formatDate(canvasData?.last_updated)} + +
+ + {/* Pinned strip — only when there are pins and we're not already filtering by pinned */} + {pinned.size > 0 && filter !== 'pinned' && ( +
+ + Pinned - Last Updated + {INSIGHTS.filter(i => pinned.has(i.id)).map(ins => ( + + ))}
-
+ )} - {/* Canvas Content */} -
- {sortedSections.length === 0 ? ( -
- -

Your Canvas is Empty

- {isUpdating || isProcessingFirstTime ? ( -
-

- {isProcessingFirstTime - ? 'Processing your chat history to populate insights...' - : 'Updating canvas with latest insights...' - } -

-
- -
-

- This may take a few minutes for extensive chat history. -

-
- ) : ( -
-

Start chatting with your AI advisors to populate your {appName} Canvas with insights!

-
+ {/* TASKS view — flat list of every bullet across insights */} + {viewMode === 'tasks' && (() => { + const allTasks = filtered.flatMap(ins => ins.bullets.map((b, idx) => ({ + ins, idx, text: b, status: taskStatusOf(ins.id, idx), + }))); + const statusOrder = { open: 0, 'in-progress': 1, completed: 2, abandoned: 3 }; + const sorted = [...allTasks].sort((a, b) => statusOrder[a.status] - statusOrder[b.status]); + if (sorted.length === 0) { + return ( +
+ +
No tasks match this filter
+ +
+ ); + } + return ( +
+ {sorted.map(({ ins, idx, text, status }) => { + const meta = TASK_STATUSES.find(s => s.id === status); + const menuKey = `tv::${ins.id}::${idx}`; + const menuOpen = openStatusMenu === menuKey; + return ( +
- + +
+ + {menuOpen && ( +
setOpenStatusMenu(null)}> + {TASK_STATUSES.map(s => ( + + ))} +
+ )}
-
- )} + ); + })}
- ) : ( -
- {sortedSections.map(([sectionKey, section]) => ( - - ))} -
- )} -
+ ); + })()} - {/* Copyright Footer */} -
- -
+ {/* CARDS view (default) */} + {viewMode === 'cards' && (filtered.length === 0 ? ( +
+ +
No insights match this filter
+ +
+ ) : ( +
+ {filtered.map(ins => { + const isExpanded = expanded.has(ins.id); + const isPinned = pinned.has(ins.id); + const tier = confidenceTier(ins.confidence); + const tint = CATEGORY_TINT[ins.category] || 'var(--canvas-surface-2)'; + const fg = CATEGORY_FG[ins.category] || 'var(--canvas-accent)'; + const rollup = insightRollup(ins); + return ( +
+
+
+ +
+
{ins.title}
+ +
- {/* Print Footer */} - {isPrintView && ( -
-

Generated by {appName} - {new Date().toLocaleDateString()}

-

Student: {user?.email} | Total Insights: {canvasData?.total_insights || 0}

+ {/* Per-card progress (rolls up the bullet tasks) */} + {rollup.total > 0 && ( +
+
+ {rollup.done}/{rollup.total} tasks + {rollup.state === 'completed' && ✓ Resolved} + {rollup.state === 'abandoned' && Abandoned} + {rollup.state === 'in-progress' && In progress} +
+
+ + {rollup.inProg > 0 && } + {rollup.abandoned > 0 && } +
+
+ )} + +
+
{ins.summary}
+
    + {ins.bullets.map((b, idx) => { + const status = taskStatusOf(ins.id, idx); + const meta = TASK_STATUSES.find(s => s.id === status); + const menuOpen = openStatusMenu === `${ins.id}::${idx}`; + return ( +
  • + + + {menuOpen && ( +
    setOpenStatusMenu(null)}> + {TASK_STATUSES.map(s => ( + + ))} +
    + )} +
  • + ); + })} +
+
+ + {/* Detail panel — quotes from sources, only when expanded */} + {isExpanded && ins.quotes && ( +
+
Source quotes · {ins.sources} {ins.sources === 1 ? 'source' : 'sources'}
+ {ins.quotes.map((q, i) => ( +
{q}
+ ))} +
+ )} + +
+ + {ins.sources} + + updated {ins.updatedMinutesAgo}m ago + +
+ +
+ + + + +
+
+ ); + })}
- )} + ))} + + ); +} - setShowClearConfirm(false)} +// Small SVG ring used for the confidence indicator +function ConfidenceRing({ value }) { + const r = 14; + const c = 2 * Math.PI * r; + const dash = c * (1 - value / 100); + const tier = confidenceTier(value); + const color = tier === 'high' ? '#10B981' : tier === 'med' ? '#F59E0B' : '#DC2626'; + return ( +
+ + + + + {value} +
+ ); +} + +function ToastStack() { + const [toasts, setToasts] = useState([]); + useEffect(() => { + const handler = (e) => { + const id = Date.now() + Math.random(); + setToasts(t => [...t, { id, ...e.detail }]); + setTimeout(() => setToasts(t => t.filter(x => x.id !== id)), 3500); + }; + window.addEventListener('canvas-toast', handler); + return () => window.removeEventListener('canvas-toast', handler); + }, []); + return ( +
+ {toasts.map(t => ( +
+ + {t.msg} +
+ ))} +
+ ); +} + + +const CanvasPage = ({ user, authToken, onNavigateToHome, onNavigateToChat, onSignOut }) => { + const { theme } = useTheme(); + useAppConfig(); + // Insights is the only view available in this PR. Workspace + Documents land + // in follow-up PRs but the storage key + tab-switching plumbing stay so those + // PRs can extend without touching this file's structure. + const [view, setView] = useState(() => { + const saved = localStorage.getItem(VIEW_KEY); + return saved === 'insights' ? saved : 'insights'; + }); + const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false); + const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); + const [tourForceShow, setTourForceShow] = useState(0); + + // Shared widget-state store. Empty in this PR (no widgets yet); kept so the + // Insights "Send to Kanban" can pre-stage work for the next PR's Kanban widget. + const [widgetStates, setWidgetStates] = useState(() => { + try { + const saved = localStorage.getItem(STATES_KEY); + return saved ? JSON.parse(saved) : {}; + } catch { return {}; } + }); + useEffect(() => { localStorage.setItem(STATES_KEY, JSON.stringify(widgetStates)); }, [widgetStates]); + useEffect(() => { localStorage.setItem(VIEW_KEY, view); }, [view]); + + // Apply canvas theme attribute on body for scoped styling + useEffect(() => { + document.body.dataset.canvasTheme = theme; + return () => { delete document.body.dataset.canvasTheme; }; + }, [theme]); + + // Critic widgets dispatch `canvas-open-in-chat` when the user wants real LLM history. + useEffect(() => { + const handler = () => onNavigateToChat && onNavigateToChat(); + window.addEventListener('canvas-open-in-chat', handler); + return () => window.removeEventListener('canvas-open-in-chat', handler); + }, [onNavigateToChat]); + + // ? opens the welcome tour for help (matches the icon in the topbar). + useEffect(() => { + const k = (e) => { + if (e.key === '?' && !['INPUT', 'TEXTAREA'].includes(e.target.tagName)) { + e.preventDefault(); + setTourForceShow(n => n + 1); + } + }; + window.addEventListener('keydown', k); + return () => window.removeEventListener('keydown', k); + }, []); + + // Highlight a section when picked from the sidebar + const flashScrollTo = (selector) => { + const el = document.querySelector(selector); + if (!el) return; + el.scrollIntoView({ block: 'center', behavior: 'smooth' }); + el.style.boxShadow = '0 0 0 2px var(--canvas-accent), 0 0 24px var(--canvas-accent-glow)'; + setTimeout(() => { el.style.boxShadow = ''; }, 1400); + }; + + // Insights: list of sections — Daniel's feedback said sidebar should show sections here + const insightSections = useMemo(() => { + let taskMap = {}; + try { taskMap = JSON.parse(localStorage.getItem(TASK_STATUS_KEY) || '{}'); } catch { /* ignore */ } + return INSIGHTS.map(ins => { + const states = ins.bullets.map((_, idx) => taskMap[taskKey(ins.id, idx)] || 'open'); + const done = states.filter(s => s === 'completed').length; + return { + id: ins.id, + name: ins.title, + icon: ins.icon, + category: ins.category, + confidence: ins.confidence, + taskCount: ins.bullets.length, + doneCount: done, + onClick: () => flashScrollTo(`#insight-${ins.id}`), + }; + }); + }, [view, widgetStates]); // eslint-disable-line react-hooks/exhaustive-deps + + return ( +
+ {}} + onSelectSession={(id) => onNavigateToChat && onNavigateToChat(id)} + onNewChat={() => onNavigateToChat && onNavigateToChat()} + pageContext="canvas" + canvasSubview={view} + insightSections={insightSections} /> +
+
+ setView(v || 'insights')} + onMobileMenu={() => setIsMobileMenuOpen(true)} + > + + +
+ {view === 'insights' && } +
+
+
+ + 0}/> +
); }; -export default CanvasPage; \ No newline at end of file +// Subtle floating hint bar showing the help shortcut. +// Auto-hides on small screens and after the first 12s, until the user hovers. +function ShortcutHint() { + const [visible, setVisible] = useState(true); + useEffect(() => { + const t = setTimeout(() => setVisible(false), 12000); + return () => clearTimeout(t); + }, []); + return ( +
setVisible(true)} + onMouseLeave={() => setVisible(false)} + > + ? help + {MOD}K commands · coming soon +
+ ); +} + +export default CanvasPage; diff --git a/phd-advisor-frontend/src/styles/CanvasPage.css b/phd-advisor-frontend/src/styles/CanvasPage.css index 1f1c196f..42e1d100 100644 --- a/phd-advisor-frontend/src/styles/CanvasPage.css +++ b/phd-advisor-frontend/src/styles/CanvasPage.css @@ -1,708 +1,3467 @@ -/* CanvasPage.css - PhD Canvas Page Styling */ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap'); -.canvas-page { - min-height: 100vh; +/* Layout: app sidebar + canvas main area (matches chat-page-with-sidebar pattern) */ +.canvas-page-with-sidebar { + display: flex; + height: 100vh; + overflow: hidden; background: var(--bg-gradient); - color: var(--text-primary); - padding: 2rem; -} - -.canvas-page.print-view { - background: white; - color: black; - padding: 1rem; } - -/* Header Section */ -.canvas-header { +.canvas-main-area { + flex: 1; + margin-left: 300px; + height: 100vh; + transition: margin-left 0.3s ease; + overflow: hidden; display: flex; flex-direction: column; - gap: 1.25rem; - margin-bottom: 2rem; - background: var(--bg-primary); - padding: 1.5rem; - border-radius: 16px; - border: 1px solid var(--border-primary); - box-shadow: var(--shadow-lg); } - -.canvas-header-top { +.canvas-main-area.sidebar-collapsed { margin-left: 70px; } +.canvas-app-shell { + flex: 1; display: flex; - align-items: center; - justify-content: space-between; - gap: 1rem; + flex-direction: column; + min-height: 0; + overflow: hidden; +} +@media (max-width: 768px) { + .canvas-main-area, .canvas-main-area.sidebar-collapsed { margin-left: 0; } +} + +/* PhD Canvas — color palette matched to the rest of the app + (Tailwind gray + indigo accent + violet "wedge" accent) */ +.canvas-page-with-sidebar { + --canvas-bg: #111827; + --canvas-bg-2: #0f172a; + --canvas-surface: #1F2937; + --canvas-surface-2: #374151; + --canvas-surface-3: #4B5563; + --canvas-border: #374151; + --canvas-border-2: #4B5563; + --canvas-text: #F9FAFB; + --canvas-text-2: #D1D5DB; + --canvas-text-3: #9CA3AF; + --canvas-text-4: #6B7280; + --canvas-accent: #818CF8; + --canvas-accent-dim: #4F46E5; + --canvas-accent-glow: rgba(129, 140, 248, 0.20); + --canvas-critic: #A78BFA; + --canvas-critic-dim: #7C3AED; + --canvas-critic-glow: rgba(167, 139, 250, 0.20); + --canvas-warn: #F59E0B; + --canvas-danger: #DC2626; + --canvas-ok: #10B981; + --canvas-shadow: 0 8px 24px rgba(0,0,0,0.4), 0 2px 6px rgba(0,0,0,0.3); + --canvas-shadow-lg: 0 24px 60px rgba(0,0,0,0.55), 0 6px 16px rgba(0,0,0,0.4); + --canvas-r-sm: 6px; + --canvas-r-md: 10px; + --canvas-r-lg: 14px; + --canvas-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; + --canvas-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + color: var(--canvas-text); + font-family: var(--canvas-sans); + font-size: 14px; + -webkit-font-smoothing: antialiased; +} + +/* Light theme — matches the app's white + Tailwind-gray palette */ +.canvas-page-with-sidebar[data-canvas-theme="light"] { + --canvas-bg: #F9FAFB; /* gray-50 */ + --canvas-bg-2: #FFFFFF; + --canvas-surface: #FFFFFF; + --canvas-surface-2: #F3F4F6; /* gray-100 */ + --canvas-surface-3: #E5E7EB; /* gray-200 */ + --canvas-border: #E5E7EB; /* gray-200 */ + --canvas-border-2: #D1D5DB; /* gray-300 */ + --canvas-text: #111827; /* gray-900 */ + --canvas-text-2: #374151; /* gray-700 */ + --canvas-text-3: #6B7280; /* gray-500 */ + --canvas-text-4: #9CA3AF; /* gray-400 */ + --canvas-accent: #6366F1; /* indigo-500 (matches accent-primary) */ + --canvas-accent-dim: #C7D2FE; + --canvas-accent-glow: rgba(99, 102, 241, 0.15); + --canvas-critic: #8B5CF6; /* violet-500 (matches accent-secondary) */ + --canvas-critic-glow: rgba(139, 92, 246, 0.12); + --canvas-shadow: 0 4px 14px rgba(20, 30, 50, 0.08); + --canvas-shadow-lg: 0 18px 48px rgba(20, 30, 50, 0.16); +} + +.canvas-page-with-sidebar *, .canvas-modal-backdrop *, .toast-stack * { box-sizing: border-box; } + +/* Standardized motion tokens */ +.canvas-page-with-sidebar, .canvas-modal-backdrop { + --canvas-ease: cubic-bezier(0.2, 0, 0, 1); + --canvas-fast: 120ms var(--canvas-ease); + --canvas-base: 180ms var(--canvas-ease); + --canvas-slow: 280ms var(--canvas-ease); +} + +/* Widgets fade + slight rise on mount */ +@keyframes canvas-widget-in { + from { opacity: 0; transform: translateY(4px); } + to { opacity: 1; transform: none; } +} +.canvas-page-with-sidebar .widget { + animation: canvas-widget-in 240ms var(--canvas-ease); +} + +/* Smooth content view transitions */ +@keyframes canvas-view-in { + from { opacity: 0; transform: translateY(2px); } + to { opacity: 1; transform: none; } +} +.canvas-content > * { + animation: canvas-view-in 220ms var(--canvas-ease); +} + +/* Improved focus rings — accent halo, no harsh outline */ +.canvas-page-with-sidebar :focus-visible, +.canvas-modal-backdrop :focus-visible { + outline: none; + box-shadow: 0 0 0 3px var(--canvas-accent-glow); + border-radius: 4px; +} +.canvas-page-with-sidebar input:focus-visible, +.canvas-page-with-sidebar textarea:focus-visible, +.canvas-modal-backdrop input:focus-visible, +.canvas-modal-backdrop textarea:focus-visible { + outline: none; + border-color: var(--canvas-accent); + box-shadow: 0 0 0 3px var(--canvas-accent-glow); +} + +/* Respect users who don't want motion */ +@media (prefers-reduced-motion: reduce) { + .canvas-page-with-sidebar .widget, + .canvas-content > * { animation: none; } +} + +.canvas-page-with-sidebar button { + font-family: inherit; + cursor: pointer; + border: none; + background: none; + color: inherit; } -.print-view .canvas-header { - background: var(--bg-secondary); - border: 1px solid var(--border-secondary); +.canvas-page-with-sidebar input, .canvas-page-with-sidebar textarea, .canvas-page-with-sidebar select, +.canvas-modal-backdrop input, .canvas-modal-backdrop textarea, .canvas-modal-backdrop select { + font-family: inherit; + color: inherit; } -.header-left { - display: flex; +.canvas-page-with-sidebar ::selection { background: var(--canvas-accent-glow); color: var(--canvas-text); } + +/* Floating header — matches the .floating-header pattern used on ChatPage */ +.canvas-page-with-sidebar .floating-header { + position: sticky; + top: 0; + z-index: 100; + background: rgba(31, 41, 55, 0.95); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border-bottom: 1px solid var(--canvas-border); + padding: 12px 24px; + display: grid; + grid-template-columns: 1fr auto 1fr; align-items: center; - gap: 1rem; + gap: 16px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); + flex-shrink: 0; +} +.canvas-page-with-sidebar .floating-header .header-right { justify-self: end; } +.canvas-page-with-sidebar[data-canvas-theme="light"] .floating-header { + background: rgba(255, 255, 255, 0.95); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); } -.back-button { +.canvas-page-with-sidebar .header-left { display: flex; align-items: center; - gap: 0.5rem; - padding: 10px 16px; - background: var(--bg-secondary); - border: 1px solid var(--border-primary); - border-radius: 12px; - color: var(--text-primary); - cursor: pointer; - transition: all 0.2s ease; - font-size: 14px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); + gap: 16px; } - -.back-button:hover { - background: var(--bg-tertiary); - border-color: var(--accent-primary); - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +.canvas-page-with-sidebar .header-right { + display: flex; + align-items: center; + gap: 8px; } - -.print-view .back-button { +.canvas-page-with-sidebar .mobile-menu-button { display: none; + width: 40px; + height: 40px; + border-radius: 10px; + border: none; + background: var(--canvas-surface-2); + color: var(--canvas-text-2); + align-items: center; + justify-content: center; + cursor: pointer; } - -.canvas-title-section { +@media (max-width: 768px) { + .canvas-page-with-sidebar .mobile-menu-button { display: flex; } +} +.canvas-page-with-sidebar .header-brand { display: flex; - flex-direction: column; + align-items: center; + gap: 12px; } - -.canvas-title { +.canvas-page-with-sidebar .brand-icon { + width: 40px; + height: 40px; + background: linear-gradient(135deg, var(--canvas-accent), var(--canvas-critic)); + border-radius: 10px; display: flex; align-items: center; - gap: 0.75rem; - font-size: 2rem; + justify-content: center; + color: #fff; +} +.canvas-page-with-sidebar .brand-text h1 { + font-size: 20px; font-weight: 700; + color: var(--canvas-text); margin: 0; - margin-bottom: 0.25rem; - color: var(--text-primary); -} - -.canvas-title-icon { - color: var(--feature-icon-color); + line-height: 1.15; + letter-spacing: -0.01em; +} +.canvas-page-with-sidebar .brand-text p { + font-size: 13px; + color: var(--canvas-text-3); + margin: 2px 0 0; + line-height: 1; +} +.canvas-page-with-sidebar .theme-toggle .theme-icon { display: block; } + +/* When the tabs sit in ChatPage's flex .floating-header, absolutely-position + them so they center regardless of header-left/header-right widths. */ +.floating-header { position: sticky; } +.floating-header > .chat-view-tabs { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); +} +.canvas-tabs-mobile { display: none; } +@media (max-width: 900px) { + .floating-header > .chat-view-tabs { display: none; } + .canvas-tabs-mobile { + display: inline-block; + background: var(--canvas-surface, var(--bg-tertiary, #f3f4f6)); + border: 1px solid var(--canvas-border, var(--border-primary, #e5e7eb)); + color: var(--canvas-text, var(--text-primary, #111827)); + font-family: inherit; + font-size: 13px; + padding: 6px 10px; + border-radius: 7px; + } } -.canvas-subtitle { - margin: 0; - color: var(--text-secondary); - font-size: 1rem; +/* Shared view-tabs pill bar — used on both Canvas and Chat pages. + Uses canvas vars first, falls back to app global vars on Chat. */ +.canvas-tabs { + display: flex; + gap: 2px; + background: var(--canvas-surface, var(--bg-tertiary, #f3f4f6)); + padding: 3px; + border-radius: 8px; + border: 1px solid var(--canvas-border, var(--border-primary, #e5e7eb)); + justify-self: center; + cursor: pointer; } - -.header-actions { +.canvas-tabs .tab { + padding: 6px 14px; + font-size: 13px; + color: var(--canvas-text-2, var(--text-secondary, #6b7280)); + border-radius: 6px; display: flex; align-items: center; - gap: 0.75rem; + gap: 7px; + font-weight: 500; + border: none; + background: transparent; + font-family: inherit; + cursor: pointer; + transition: background .12s, color .12s; +} +.canvas-tabs .tab:hover { color: var(--canvas-text, var(--text-primary, #111827)); } +.canvas-tabs .tab.active { + background: var(--canvas-surface-3, var(--bg-primary, #fff)); + color: var(--canvas-text, var(--text-primary, #111827)); + box-shadow: inset 0 0 0 1px var(--canvas-border-2, var(--border-secondary, #d1d5db)); +} +.canvas-tabs .tab .badge { + font-family: var(--canvas-mono); + font-size: 10px; + background: var(--canvas-accent-glow); + color: var(--canvas-accent); + padding: 1px 5px; + border-radius: 3px; + letter-spacing: 0.02em; +} + +.canvas-page-with-sidebar .topbar-spacer { flex: 1; } + +.canvas-page-with-sidebar .icon-btn, +.canvas-modal-backdrop .icon-btn { + width: 32px; + height: 32px; + display: grid; + place-items: center; + border-radius: 7px; + color: var(--canvas-text-2); + transition: background .12s, color .12s; + font-family: inherit; + cursor: pointer; + border: none; + background: none; } +.canvas-page-with-sidebar .icon-btn:hover, +.canvas-modal-backdrop .icon-btn:hover { background: var(--canvas-surface); color: var(--canvas-text); } -.canvas-powered-by { +.canvas-page-with-sidebar .btn, +.canvas-modal-backdrop .btn { display: inline-flex; align-items: center; - gap: 4px; - color: var(--text-tertiary); - font-size: 12px; - text-decoration: none; - transition: opacity 0.2s ease; + gap: 7px; + padding: 7px 12px; + border-radius: 7px; + font-size: 13px; + font-weight: 500; + background: var(--canvas-surface-2); + border: 1px solid var(--canvas-border-2); + color: var(--canvas-text); + transition: all .12s; + font-family: inherit; + cursor: pointer; } - -.canvas-powered-by:hover { - opacity: 0.8; +.canvas-page-with-sidebar .btn:hover, +.canvas-modal-backdrop .btn:hover { background: var(--canvas-surface-3); border-color: var(--canvas-border-2); } +.canvas-page-with-sidebar .btn-primary, +.canvas-modal-backdrop .btn-primary { + background: var(--canvas-accent); + color: #FFFFFF; + border-color: var(--canvas-accent); + font-weight: 600; } - -.canvas-powered-by-logo { - height: 1em; - width: auto; - vertical-align: middle; +.canvas-page-with-sidebar .btn-primary:hover, +.canvas-modal-backdrop .btn-primary:hover { background: var(--canvas-accent-dim); border-color: var(--canvas-accent-dim); filter: brightness(1.05); box-shadow: 0 0 0 3px var(--canvas-accent-glow); } +.canvas-page-with-sidebar .btn-critic, +.canvas-modal-backdrop .btn-critic { + background: var(--canvas-critic); + color: #FFFFFF; + border-color: var(--canvas-critic); + font-weight: 600; } +.canvas-page-with-sidebar .btn-critic:hover, +.canvas-modal-backdrop .btn-critic:hover { filter: brightness(1.08); box-shadow: 0 0 0 3px var(--canvas-critic-glow); } +.canvas-page-with-sidebar .btn-ghost, +.canvas-modal-backdrop .btn-ghost { background: transparent; border-color: transparent; color: var(--canvas-text-2); } +.canvas-page-with-sidebar .btn-ghost:hover, +.canvas-modal-backdrop .btn-ghost:hover { background: var(--canvas-surface); color: var(--canvas-text); border-color: transparent; } +.canvas-page-with-sidebar .btn-danger:hover, +.canvas-modal-backdrop .btn-danger:hover { background: rgba(240,106,106,0.12); border-color: rgba(240,106,106,0.4); color: var(--canvas-danger); } -.print-view .header-actions { - display: none; +.canvas-page-with-sidebar .btn:disabled, +.canvas-modal-backdrop .btn:disabled { opacity: 0.5; cursor: not-allowed; } + +/* Content */ +.canvas-content { + padding: 24px 28px 80px; + max-width: 100%; + flex: 1; + overflow-y: auto; + background: var(--canvas-bg); } +.canvas-page-with-sidebar[data-canvas-theme="light"] .canvas-content { background: var(--canvas-bg); } -/* Icon-only buttons in canvas header */ -.canvas-icon-btn { +.canvas-page-with-sidebar .page-header { + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: 24px; + margin-bottom: 22px; +} +.canvas-page-with-sidebar .page-title { font-size: 28px; font-weight: 700; letter-spacing: -0.02em; margin: 0; color: var(--canvas-text); } +.canvas-page-with-sidebar .page-sub { color: var(--canvas-text-3); font-size: 13px; margin-top: 6px; } +.canvas-page-with-sidebar .page-meta { + font-family: var(--canvas-mono); + font-size: 11px; + color: var(--canvas-text-3); display: flex; align-items: center; - justify-content: center; - width: 40px; - height: 40px; - padding: 0; - background: var(--bg-secondary); - border: 1px solid var(--border-primary); - border-radius: 12px; - color: var(--text-primary); - cursor: pointer; - transition: background 0.18s ease, border-color 0.18s ease, color 0.18s ease, transform 0.12s ease, box-shadow 0.18s ease; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); + gap: 8px; } - -.canvas-icon-btn svg { - width: 18px; - height: 18px; +.canvas-page-with-sidebar .dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--canvas-ok); + box-shadow: 0 0 8px var(--canvas-ok); } -.canvas-icon-btn:hover:not(:disabled) { - background: var(--bg-tertiary); - border-color: var(--accent-primary); - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +/* ----- Insights view ----- */ +.canvas-page-with-sidebar .insights-stats { + display: flex; + gap: 18px; + align-items: center; + padding: 14px 18px; + background: var(--canvas-surface); + border: 1px solid var(--canvas-border); + border-radius: 10px; + margin-bottom: 14px; + flex-wrap: wrap; } - -.canvas-icon-btn:disabled { - opacity: 0.5; - cursor: not-allowed; - transform: none; +.canvas-page-with-sidebar[data-canvas-theme="light"] .insights-stats { + background: #ffffff; + border-color: rgba(15, 15, 15, 0.06); } - -/* Clear-canvas variant — destructive */ -.clear-canvas-btn { - color: #ef4444; - border-color: rgba(239, 68, 68, 0.35); +.canvas-page-with-sidebar .insights-stat { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 1px; + min-width: 70px; } - -.clear-canvas-btn:hover:not(:disabled) { - background: #ef4444; - color: #ffffff; - border-color: #ef4444; - box-shadow: 0 4px 12px -2px rgba(239, 68, 68, 0.45); +.canvas-page-with-sidebar .insights-stat-value { + font-family: var(--canvas-mono); + font-size: 22px; + font-weight: 700; + color: var(--canvas-text); + letter-spacing: -0.02em; + line-height: 1; +} +.canvas-page-with-sidebar .insights-stat-label { + font-size: 11px; + color: var(--canvas-text-3); + text-transform: uppercase; + letter-spacing: 0.06em; + font-weight: 500; } - -[data-theme="dark"] .clear-canvas-btn { - color: #f87171; - border-color: rgba(248, 113, 113, 0.4); +.canvas-page-with-sidebar .insights-stat-update { + font-size: 11px; + color: var(--canvas-text-3); + font-family: var(--canvas-mono); + display: inline-flex; + align-items: center; + gap: 6px; } -[data-theme="dark"] .clear-canvas-btn:hover:not(:disabled) { - background: #ef4444; - color: #ffffff; - border-color: #ef4444; +.canvas-page-with-sidebar .insights-filters { + display: flex; + gap: 12px; + align-items: center; + margin-bottom: 14px; + flex-wrap: wrap; } -.action-button { +.canvas-page-with-sidebar .insights-pinned-strip { display: flex; align-items: center; - gap: 0.5rem; - padding: 10px 16px; - background: var(--bg-secondary); - border: 1px solid var(--border-primary); - border-radius: 12px; - color: var(--text-primary); + gap: 8px; + padding: 10px 14px; + margin-bottom: 14px; + background: var(--canvas-accent-glow); + border: 1px solid rgba(99, 102, 241, 0.18); + border-radius: 8px; + flex-wrap: wrap; +} +.canvas-page-with-sidebar .insights-pinned-pill { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 4px 10px; + background: var(--canvas-surface); + border: 1px solid var(--canvas-border); + border-radius: 999px; + font-family: inherit; + font-size: 12px; + color: var(--canvas-text); cursor: pointer; - transition: all 0.2s ease; - font-size: 14px; - font-weight: 500; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); + transition: all .12s; } - -.action-button:hover { - background: var(--bg-tertiary); - border-color: var(--accent-primary); - transform: translateY(-1px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +.canvas-page-with-sidebar .insights-pinned-pill:hover { + border-color: var(--canvas-accent); + color: var(--canvas-accent); } -.action-button:disabled { - opacity: 0.6; - cursor: not-allowed; - transform: none; +/* Cards vs Tasks view toggle on the Insights view */ +.canvas-page-with-sidebar .insights-view-toggle { + display: inline-flex; + align-items: center; + gap: 2px; + margin-bottom: 12px; + background: var(--canvas-surface); + border: 1px solid var(--canvas-border); + border-radius: 7px; + padding: 3px; +} +.canvas-page-with-sidebar .insights-view-toggle button { + background: transparent; + border: none; + padding: 5px 12px; + border-radius: 5px; + font-family: inherit; + font-size: 12px; + color: var(--canvas-text-3); + display: inline-flex; + align-items: center; + gap: 6px; + cursor: pointer; + transition: background .12s, color .12s; } - -.action-icon.spinning { - animation: spin 1s linear infinite; +.canvas-page-with-sidebar .insights-view-toggle button:hover { color: var(--canvas-text); } +.canvas-page-with-sidebar .insights-view-toggle button.active { + background: var(--canvas-surface-3); + color: var(--canvas-text); + box-shadow: inset 0 0 0 1px var(--canvas-border-2); } -@keyframes spin { - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } +/* Flat task list view */ +.canvas-page-with-sidebar .insights-tasks-list { + display: flex; + flex-direction: column; + gap: 4px; } - -/* Stats Section */ -.canvas-stats { +.canvas-page-with-sidebar .insights-task-row { display: flex; - gap: 2rem; - margin-bottom: 2rem; - justify-content: center; + align-items: flex-start; + gap: 10px; + padding: 10px 12px; + background: var(--canvas-surface); + border: 1px solid var(--canvas-border); + border-radius: 7px; + position: relative; + transition: background .12s, border-color .12s; +} +.canvas-page-with-sidebar .insights-task-row:hover { + border-color: var(--canvas-border-2); +} +.canvas-page-with-sidebar .insights-task-row.task-completed { opacity: 0.7; } +.canvas-page-with-sidebar .insights-task-row.task-abandoned { opacity: 0.5; } +.canvas-page-with-sidebar .insights-task-row.task-completed:hover, +.canvas-page-with-sidebar .insights-task-row.task-abandoned:hover { opacity: 0.95; } +.canvas-page-with-sidebar .insights-task-row .insight-task-check { margin-top: 4px; } +.canvas-page-with-sidebar .insights-task-body { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 4px; } - -.stat-item { - text-align: center; - background: var(--bg-primary); - padding: 1.5rem; - border-radius: 16px; - border: 1px solid var(--border-primary); - box-shadow: var(--shadow-md); - min-width: 140px; - transition: all 0.3s ease; +.canvas-page-with-sidebar .insights-task-section { + background: transparent; + border: none; + color: var(--canvas-text-3); + font-family: var(--canvas-mono); + font-size: 10.5px; + text-transform: uppercase; + letter-spacing: 0.06em; + padding: 0; + display: inline-flex; + align-items: center; + gap: 5px; + cursor: pointer; + align-self: flex-start; } - -.stat-item:hover { - transform: translateY(-2px); - box-shadow: var(--shadow-xl); +.canvas-page-with-sidebar .insights-task-section:hover { color: var(--canvas-accent); } +.canvas-page-with-sidebar .insights-task-text { + font-size: 13px; + color: var(--canvas-text); + line-height: 1.5; } - -.print-view .stat-item { - background: var(--bg-secondary); - border: 1px solid var(--border-secondary); +.canvas-page-with-sidebar .insights-task-row.task-completed .insights-task-text, +.canvas-page-with-sidebar .insights-task-row.task-abandoned .insights-task-text { + text-decoration: line-through; + color: var(--canvas-text-3); } +.canvas-page-with-sidebar .insights-task-text strong { color: var(--canvas-text); } -.stat-number { - display: block; - font-size: 2rem; +/* Per-card progress bar — replaces the old single status pill */ +.canvas-page-with-sidebar .insight-progress { + display: flex; + flex-direction: column; + gap: 4px; + padding: 0 0 4px; +} +.canvas-page-with-sidebar .insight-progress-meta { + display: flex; + align-items: center; + gap: 8px; + font-family: var(--canvas-mono); + font-size: 10.5px; + color: var(--canvas-text-3); +} +.canvas-page-with-sidebar .insight-progress-count { font-weight: 600; } +.canvas-page-with-sidebar .insight-progress-badge { + font-family: var(--canvas-mono); + font-size: 9px; + text-transform: uppercase; + letter-spacing: 0.08em; font-weight: 700; - color: var(--accent-primary); - margin-bottom: 0.5rem; + padding: 1px 6px; + border-radius: 3px; } +.canvas-page-with-sidebar .insight-progress-badge.done { background: rgba(16,185,129,0.15); color: #10B981; } +.canvas-page-with-sidebar .insight-progress-badge.inprog { background: rgba(59,130,246,0.15); color: #3B82F6; } +.canvas-page-with-sidebar .insight-progress-badge.abandoned { background: var(--canvas-surface-2); color: var(--canvas-text-4); } -.print-view .stat-number { - color: var(--text-secondary); +.canvas-page-with-sidebar .insight-progress-bar { + height: 5px; + background: var(--canvas-surface-2); + border-radius: 3px; + overflow: hidden; + display: flex; } - -.stat-label { - font-size: 0.9rem; - color: var(--text-secondary); +.canvas-page-with-sidebar .insight-progress-bar > i { + display: block; + height: 100%; + transition: width .25s; } +.canvas-page-with-sidebar .insight-progress-bar .ip-completed { background: #10B981; } +.canvas-page-with-sidebar .insight-progress-bar .ip-inprogress { background: #3B82F6; } +.canvas-page-with-sidebar .insight-progress-bar .ip-abandoned { background: var(--canvas-text-4); } -/* Content Section */ -.canvas-content { - max-width: 1200px; - margin: 0 auto; +/* Per-bullet tasks inside the body */ +.canvas-page-with-sidebar .insight-tasks { + list-style: none; + padding: 0; + margin: 6px 0 0; + display: flex; + flex-direction: column; + gap: 6px; } - -.empty-canvas { - text-align: center; - padding: 4rem 2rem; - color: var(--text-secondary); - max-width: 600px; - margin: 0 auto; +.canvas-page-with-sidebar .insight-task { + display: flex; + align-items: flex-start; + gap: 8px; + padding: 2px 0; + font-size: 13px; + color: var(--canvas-text-2); + line-height: 1.5; + position: relative; } - -.empty-canvas-icon { - width: 4rem; - height: 4rem; - margin-bottom: 1rem; - color: var(--text-tertiary); +.canvas-page-with-sidebar .insight-task.task-completed .insight-task-text { + text-decoration: line-through; + color: var(--canvas-text-3); + opacity: 0.7; } - -.empty-canvas h2 { - margin-bottom: 1rem; - color: var(--text-primary); +.canvas-page-with-sidebar .insight-task.task-abandoned .insight-task-text { + text-decoration: line-through; + color: var(--canvas-text-4); + opacity: 0.5; } - -.empty-canvas p { - margin-bottom: 1.5rem; - line-height: 1.6; +.canvas-page-with-sidebar .insight-task.task-in-progress .insight-task-text { + color: var(--canvas-text); + font-weight: 500; } - -.start-chatting-button { - padding: 16px 32px; - background: var(--accent-gradient); - color: white; - border: none; - border-radius: 12px; - font-size: 1.1rem; - font-weight: 600; +.canvas-page-with-sidebar .insight-task-text { flex: 1; } +.canvas-page-with-sidebar .insight-task-text strong { color: var(--canvas-text); } +.canvas-page-with-sidebar .insight-task-check { + flex-shrink: 0; + width: 18px; + height: 18px; + border-radius: 4px; + border: 1.5px solid currentColor; + background: transparent; cursor: pointer; - transition: all 0.3s ease; - box-shadow: var(--shadow-md); + display: grid; + place-items: center; + margin-top: 2px; + transition: background .12s; } - -.start-chatting-button:hover { - transform: translateY(-2px); - box-shadow: var(--shadow-xl); +.canvas-page-with-sidebar .insight-task-check:hover { + background: var(--canvas-surface-2); } - -/* Sections */ -.sections-container { - display: flex; - flex-direction: column; - gap: 1.5rem; +.canvas-page-with-sidebar .insight-task.task-completed .insight-task-check { + background: #10B981; + color: #fff; } - -.canvas-section { - background: var(--bg-primary); - border-radius: 16px; - border: 1px solid var(--border-primary); - box-shadow: var(--shadow-md); - overflow: hidden; - transition: all 0.3s ease; +.canvas-page-with-sidebar .insight-task.task-abandoned .insight-task-check { + background: var(--canvas-text-4); + color: #fff; } - -.canvas-section:hover { - transform: translateY(-2px); - box-shadow: var(--shadow-xl); +.canvas-page-with-sidebar .task-check-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: currentColor; } -.print-view .canvas-section { - background: white; - border: 1px solid var(--border-secondary); - color: black; - page-break-inside: avoid; - margin-bottom: 1rem; +/* Sidebar row for a fully-done section gets struck through */ +.sidebar .csm-row-done .csm-row-label { + text-decoration: line-through; + color: var(--text-tertiary, #9CA3AF); +} +.sidebar .csm-row-done .csm-row-meta { + color: #10B981; + font-weight: 600; } -.section-header { - display: flex; - justify-content: space-between; +/* Insight status pill + menu */ +.canvas-page-with-sidebar .insight-status-wrap { + position: relative; +} +.canvas-page-with-sidebar .insight-status-pill { + display: inline-flex; align-items: center; - padding: 1.5rem; + gap: 5px; + padding: 3px 9px; + border-radius: 999px; + border: 1px solid transparent; + background: transparent; + font-family: inherit; + font-size: 10.5px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; cursor: pointer; - transition: background-color 0.2s ease; + transition: background .12s; } - -.section-header:hover { - background: var(--bg-secondary); +.canvas-page-with-sidebar .insight-status-pill:hover { + background: var(--canvas-surface-2); } - -.print-view .section-header:hover { - background: var(--bg-tertiary); +.canvas-page-with-sidebar .insight-status-menu { + position: absolute; + top: calc(100% + 4px); + right: 0; + z-index: 5; + background: var(--canvas-surface); + border: 1px solid var(--canvas-border-2); + border-radius: 7px; + box-shadow: var(--canvas-shadow-lg); + padding: 4px; + min-width: 150px; + display: flex; + flex-direction: column; } - -.section-header-content { +.canvas-page-with-sidebar .insight-status-menu button { display: flex; align-items: center; - gap: 1rem; - flex: 1; + gap: 8px; + background: transparent; + border: none; + padding: 6px 10px; + border-radius: 4px; + font-family: inherit; + font-size: 12px; + color: var(--canvas-text); + cursor: pointer; + text-align: left; } - -.section-icon { - width: 2rem; - height: 2rem; - color: var(--feature-icon-color); +.canvas-page-with-sidebar .insight-status-menu button:hover { + background: var(--canvas-surface-2); } - -.print-view .section-icon { - color: var(--text-secondary); +.canvas-page-with-sidebar .insight-status-menu button.active { + background: var(--canvas-accent-glow); + color: var(--canvas-accent); + font-weight: 600; } -.section-titles { - flex: 1; +/* Card-level status mutes the body when completed/abandoned */ +.canvas-page-with-sidebar .insight.status-completed .insight-body, +.canvas-page-with-sidebar .insight.status-completed .insight-foot { + opacity: 0.65; } - -.section-title { - margin: 0 0 0.25rem 0; - font-size: 1.3rem; - font-weight: 600; - color: var(--text-primary); +.canvas-page-with-sidebar .insight.status-abandoned { + opacity: 0.5; } +.canvas-page-with-sidebar .insight.status-abandoned:hover { opacity: 0.85; } -.section-description { - margin: 0; - color: var(--text-secondary); - font-size: 0.9rem; +/* Progress bar in stats — segmented for status mix */ +.canvas-page-with-sidebar .insights-stat-progress { + flex: 1; + min-width: 180px; + max-width: 280px; + gap: 4px; } - -.section-meta { +.canvas-page-with-sidebar .insights-progress-bar { + height: 6px; + width: 100%; + background: var(--canvas-surface-2); + border-radius: 3px; + overflow: hidden; display: flex; - align-items: center; - gap: 1rem; - font-size: 0.9rem; } - -.insight-count { - background: var(--feature-bg); - color: var(--accent-primary); - padding: 0.25rem 0.75rem; - border-radius: 20px; - font-weight: 500; +.canvas-page-with-sidebar .insights-progress-bar > i { + display: block; + height: 100%; + transition: width .3s; +} +.canvas-page-with-sidebar .insights-progress-bar .ip-completed { background: #10B981; } +.canvas-page-with-sidebar .insights-progress-bar .ip-inprogress { background: #3B82F6; } +.canvas-page-with-sidebar .insights-progress-bar .ip-abandoned { background: var(--canvas-text-4); } + +/* Confidence ring */ +.canvas-page-with-sidebar .confidence-ring { + position: relative; + width: 32px; + height: 32px; + display: grid; + place-items: center; + flex-shrink: 0; } - -[data-theme="dark"] .insight-count { - background: rgba(255, 255, 255, 0.12); - color: #ffffff; +.canvas-page-with-sidebar .confidence-ring span { + position: absolute; + font-family: var(--canvas-mono); + font-size: 9.5px; + font-weight: 700; } -.print-view .insight-count { - background: var(--bg-tertiary); - color: var(--text-secondary); +/* Insight card refinements */ +.canvas-page-with-sidebar .insight { + transition: border-color .15s, transform .15s, box-shadow .15s; } - -.expand-arrow { - transition: transform 0.3s ease; - font-size: 0.8rem; - color: var(--text-secondary); +.canvas-page-with-sidebar .insight:hover { + transform: translateY(-1px); + box-shadow: 0 6px 18px rgba(0,0,0,0.08); } - -.expand-arrow.expanded { - transform: rotate(180deg); +.canvas-page-with-sidebar .insight.is-pinned { + border-color: var(--canvas-accent); + background: var(--canvas-surface); + box-shadow: 0 0 0 1px var(--canvas-accent-glow); } - -.section-content { - padding: 0 1.5rem 1.5rem 1.5rem; - border-top: 1px solid var(--border-primary); +.canvas-page-with-sidebar[data-canvas-theme="light"] .insight.is-pinned { + background: #ffffff; } -.print-view .section-content { - border-top: 1px solid var(--border-secondary); +/* Insight footer (source count + updated time) */ +.canvas-page-with-sidebar .insight-foot { + display: flex; + justify-content: space-between; + align-items: center; + padding: 6px 0 0; + font-size: 11px; + color: var(--canvas-text-3); } - -.empty-section { - text-align: center; - padding: 2rem; - color: var(--text-tertiary); +.canvas-page-with-sidebar .insight-foot-meta { + display: inline-flex; + align-items: center; + gap: 6px; + font-family: var(--canvas-mono); +} +.canvas-page-with-sidebar .insight-dot { + width: 3px; + height: 3px; + border-radius: 50%; + background: var(--canvas-text-4); } -.empty-icon { - width: 2rem; - height: 2rem; - margin-bottom: 1rem; +/* Expanded source-quotes panel */ +.canvas-page-with-sidebar .insight-detail { + margin-top: 8px; + padding: 10px 12px; + background: var(--canvas-bg-2); + border-left: 2px solid var(--canvas-accent); + border-radius: 0 6px 6px 0; + display: flex; + flex-direction: column; + gap: 6px; + animation: canvas-view-in 180ms var(--canvas-ease); +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .insight-detail { + background: rgba(99, 102, 241, 0.04); +} +.canvas-page-with-sidebar .insight-detail-head { + font-size: 10.5px; + font-family: var(--canvas-mono); + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--canvas-accent); + font-weight: 600; +} +.canvas-page-with-sidebar .insight-quote { + font-size: 12.5px; + color: var(--canvas-text-2); + line-height: 1.5; + font-style: italic; } /* Insights */ -.insights-grid { +.canvas-page-with-sidebar .insight-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - gap: 1rem; - margin-top: 1rem; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 14px; } - -.insight-card { - background: var(--bg-secondary); - border-radius: 12px; - padding: 1.25rem; - border: 1px solid var(--border-primary); - transition: all 0.3s ease; +@media (max-width: 1100px) { + .canvas-page-with-sidebar .insight-grid { grid-template-columns: 1fr; } } -.insight-card:hover { - background: var(--bg-tertiary); - transform: translateY(-1px); - box-shadow: var(--shadow-sm); +.canvas-page-with-sidebar .insight { + background: var(--canvas-surface); + border: 1px solid var(--canvas-border); + border-radius: var(--canvas-r-md); + padding: 16px 18px; + display: flex; + flex-direction: column; + gap: 10px; + transition: border-color .15s; } +.canvas-page-with-sidebar .insight:hover { border-color: var(--canvas-border-2); } +.canvas-page-with-sidebar[data-canvas-theme="light"] .insight { box-shadow: 0 1px 2px rgba(20,30,50,0.03); } -.print-view .insight-card { - background: var(--bg-tertiary); - border: 1px solid var(--border-secondary); - color: black; - break-inside: avoid; +.canvas-page-with-sidebar .insight-head { + display: flex; + align-items: center; + gap: 10px; } - -.insight-content { - margin-bottom: 1rem; - line-height: 1.5; - font-size: 0.95rem; - color: var(--text-primary); +.canvas-page-with-sidebar .insight-icon { + width: 28px; + height: 28px; + border-radius: 7px; + background: var(--canvas-surface-2); + display: grid; + place-items: center; + color: var(--canvas-accent); } - -.empty-canvas-actions { +.canvas-page-with-sidebar .insight-title { font-weight: 600; font-size: 14px; flex: 1; color: var(--canvas-text); } +.canvas-page-with-sidebar .confidence { display: flex; - gap: 12px; - justify-content: center; align-items: center; - flex-wrap: wrap; - margin-top: 1.75rem; + gap: 6px; + font-family: var(--canvas-mono); + font-size: 11px; + color: var(--canvas-text-3); +} +.canvas-page-with-sidebar .conf-bar { + width: 36px; + height: 4px; + background: var(--canvas-surface-3); + border-radius: 2px; + overflow: hidden; +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .conf-bar { background: #e7ebf2; } +.canvas-page-with-sidebar .conf-bar > i { + display: block; + height: 100%; + background: var(--canvas-accent); + border-radius: 2px; +} + +.canvas-page-with-sidebar .insight-body { color: var(--canvas-text-2); font-size: 13px; } +.canvas-page-with-sidebar .insight-body ul { margin: 6px 0 0; padding-left: 18px; } +.canvas-page-with-sidebar .insight-body li { margin-bottom: 4px; } +.canvas-page-with-sidebar .insight-body li::marker { color: var(--canvas-text-4); } +.canvas-page-with-sidebar .insight-body strong { color: var(--canvas-text); font-weight: 600; } + +.canvas-page-with-sidebar .insight-actions { + display: flex; + gap: 4px; + flex-wrap: wrap; + padding-top: 10px; + border-top: 1px dashed var(--canvas-border); + margin-top: 4px; +} +.canvas-page-with-sidebar .chip, +.canvas-modal-backdrop .chip { + font-size: 11px; + padding: 4px 9px; + border-radius: 5px; + background: var(--canvas-surface-2); + color: var(--canvas-text-2); + display: inline-flex; + align-items: center; + gap: 5px; + transition: background .12s, color .12s; + border: none; + cursor: pointer; +} +.canvas-page-with-sidebar .chip:hover { background: var(--canvas-surface-3); color: var(--canvas-text); } +.canvas-page-with-sidebar .chip.pinned { background: var(--canvas-accent-glow); color: var(--canvas-accent); } + +/* Workspace grid */ +.canvas-page-with-sidebar .workspace { + display: grid; + grid-template-columns: repeat(6, minmax(0, 1fr)); + gap: 18px; + align-items: start; +} +/* Notion-inspired widget treatment: hairline borders, generous padding, + no hard chrome — the content is the page, the frame is barely there. */ +.canvas-page-with-sidebar .widget { + background: var(--canvas-surface); + border: 1px solid transparent; + border-radius: 6px; + display: flex; + flex-direction: column; + min-height: 200px; + position: relative; + overflow: hidden; + transition: background .15s, border-color .15s, box-shadow .15s; +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .widget { + background: #FFFFFF; + border-color: rgba(15, 15, 15, 0.06); +} +.canvas-page-with-sidebar .widget.size-S { grid-column: span 2; } +.canvas-page-with-sidebar .widget.size-M { grid-column: span 3; } +.canvas-page-with-sidebar .widget.size-L { grid-column: span 6; } +.canvas-page-with-sidebar .widget.dragging { opacity: 0.4; } +.canvas-page-with-sidebar .widget.drag-over { + box-shadow: 0 0 0 2px var(--canvas-accent), 0 4px 14px var(--canvas-accent-glow); +} +.canvas-page-with-sidebar .widget:hover { + background: var(--canvas-surface-2); +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .widget:hover { + background: rgba(0, 0, 0, 0.015); + border-color: rgba(15, 15, 15, 0.08); +} +.canvas-page-with-sidebar .widget.critic { border-color: rgba(167, 139, 250, 0.18); } +.canvas-page-with-sidebar[data-canvas-theme="light"] .widget.critic { border-color: rgba(139, 92, 246, 0.18); } +.canvas-page-with-sidebar .widget.critic:hover { border-color: var(--canvas-critic); } + +/* Preserve 3-S / 2-M / 1-L per row at every desktop width. + On phones (≤768px) just stack everything full-width for readability. */ +@media (max-width: 768px) { + .canvas-page-with-sidebar .workspace { grid-template-columns: 1fr; } + .canvas-page-with-sidebar .widget.size-S, + .canvas-page-with-sidebar .widget.size-M, + .canvas-page-with-sidebar .widget.size-L { grid-column: 1 / -1; } +} + +/* Notion-style header: drag grip is hover-only, title is plain text. */ +.canvas-page-with-sidebar .widget-head { + display: flex; + align-items: center; + gap: 8px; + padding: 14px 16px 8px; + cursor: grab; +} +.canvas-page-with-sidebar .widget-head:active { cursor: grabbing; } +.canvas-page-with-sidebar .widget-head .drag-grip { + color: var(--canvas-text-4); + display: grid; + place-items: center; + width: 14px; + opacity: 0; + transition: opacity .15s; +} +.canvas-page-with-sidebar .widget:hover .widget-head .drag-grip { opacity: 0.6; } +.canvas-page-with-sidebar .widget-icon { + width: 18px; + height: 18px; + display: grid; + place-items: center; + color: var(--canvas-text-3); +} +.canvas-page-with-sidebar .widget.critic .widget-icon { color: var(--canvas-critic); } +.canvas-page-with-sidebar .widget-title { + font-weight: 600; + font-size: 13.5px; + flex: 1; + letter-spacing: -0.005em; + color: var(--canvas-text); +} +.canvas-page-with-sidebar .widget-tag, +.canvas-modal-backdrop .widget-tag { + font-size: 9.5px; + font-family: var(--canvas-mono); + text-transform: uppercase; + letter-spacing: 0.08em; + padding: 2px 6px; + border-radius: 3px; + background: var(--canvas-critic-glow); + color: var(--canvas-critic); + font-weight: 600; +} +.canvas-modal-backdrop .widget-tag-enhanced { + background: rgba(16, 185, 129, 0.15); + color: #10B981; +} +.canvas-modal-backdrop .widget-tag-chat { + background: rgba(59, 130, 246, 0.15); + color: #3B82F6; +} +.canvas-page-with-sidebar .widget-actions { display: flex; gap: 1px; opacity: 0; transition: opacity .12s; } +.canvas-page-with-sidebar .widget:hover .widget-actions { opacity: 1; } +.canvas-page-with-sidebar .widget-actions .icon-btn { width: 24px; height: 24px; } +.canvas-page-with-sidebar .widget-actions .icon-btn svg { width: 14px; height: 14px; } +.canvas-page-with-sidebar .size-pill { + font-family: var(--canvas-mono); + font-size: 10px; + background: var(--canvas-surface-2); + color: var(--canvas-text-3); + padding: 2px 6px; + border-radius: 4px; + cursor: pointer; + user-select: none; +} +.canvas-page-with-sidebar .size-pill:hover { background: var(--canvas-surface-3); color: var(--canvas-text); } + +.canvas-page-with-sidebar .widget-body { + padding: 4px 16px 18px; + flex: 1; + display: flex; + flex-direction: column; + gap: 12px; + min-height: 0; +} + +/* Preset picker (shown above the empty workspace) */ +.canvas-page-with-sidebar .canvas-presets { + margin-bottom: 18px; +} +.canvas-page-with-sidebar .canvas-presets-head { + margin-bottom: 10px; +} +.canvas-page-with-sidebar .canvas-presets-title { + font-size: 16px; + font-weight: 600; + color: var(--canvas-text); +} +.canvas-page-with-sidebar .canvas-presets-sub { + font-size: 12.5px; + color: var(--canvas-text-3); + margin-top: 2px; +} +.canvas-page-with-sidebar .canvas-presets-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + gap: 10px; +} +.canvas-page-with-sidebar .canvas-preset-card { + background: transparent; + border: 1px solid rgba(15, 15, 15, 0.10); + border-radius: 8px; + padding: 16px; + display: flex; + gap: 12px; + text-align: left; + cursor: pointer; + transition: background .15s, border-color .15s; + font-family: inherit; + color: var(--canvas-text); +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .canvas-preset-card { + border-color: rgba(15, 15, 15, 0.12); +} +.canvas-page-with-sidebar .canvas-preset-card:hover { + background: var(--canvas-surface-2); + border-color: rgba(15, 15, 15, 0.15); +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .canvas-preset-card:hover { + background: rgba(0, 0, 0, 0.025); +} +.canvas-page-with-sidebar .canvas-preset-icon { + width: 32px; + height: 32px; + border-radius: 6px; + background: var(--canvas-surface-2); + color: var(--canvas-text-2); + display: grid; + place-items: center; + flex-shrink: 0; + font-size: 16px; +} +.canvas-page-with-sidebar .canvas-preset-content { flex: 1; min-width: 0; } +.canvas-page-with-sidebar .canvas-preset-name { font-size: 13.5px; font-weight: 600; color: var(--canvas-text); } +.canvas-page-with-sidebar .canvas-preset-desc { font-size: 11.5px; color: var(--canvas-text-3); margin-top: 3px; line-height: 1.45; } +.canvas-page-with-sidebar .canvas-preset-meta { + font-family: var(--canvas-mono); + font-size: 10px; + color: var(--canvas-text-4); + margin-top: 6px; + text-transform: uppercase; + letter-spacing: 0.06em; +} + +.canvas-page-with-sidebar .empty-cell { + border: 1.5px dashed var(--canvas-border-2); + border-radius: var(--canvas-r-md); + padding: 28px 24px; + text-align: center; + color: var(--canvas-text-3); + font-size: 13px; + grid-column: span 6; + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + background: rgba(255,255,255,0.01); +} + +/* Inputs / forms (scoped) */ +.canvas-page-with-sidebar .input, .canvas-page-with-sidebar .textarea, .canvas-page-with-sidebar .select, +.canvas-modal-backdrop .input, .canvas-modal-backdrop .textarea, .canvas-modal-backdrop .select { + width: 100%; + background: var(--canvas-bg-2); + border: 1px solid var(--canvas-border-2); + border-radius: 7px; + padding: 8px 10px; + font-size: 13px; + color: var(--canvas-text); + transition: border-color .12s, box-shadow .12s; +} +.canvas-page-with-sidebar .input:focus, .canvas-page-with-sidebar .textarea:focus, .canvas-page-with-sidebar .select:focus, +.canvas-modal-backdrop .input:focus, .canvas-modal-backdrop .textarea:focus, .canvas-modal-backdrop .select:focus { + outline: none; + border-color: var(--canvas-accent); + box-shadow: 0 0 0 3px var(--canvas-accent-glow); +} +.canvas-page-with-sidebar .textarea, .canvas-modal-backdrop .textarea { resize: vertical; min-height: 80px; font-family: inherit; line-height: 1.5; } +.canvas-page-with-sidebar .label, .canvas-modal-backdrop .label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--canvas-text-3); font-weight: 600; } + +.canvas-page-with-sidebar[data-canvas-theme="light"] .input, +.canvas-page-with-sidebar[data-canvas-theme="light"] .textarea, +.canvas-page-with-sidebar[data-canvas-theme="light"] .select, +body[data-canvas-theme="light"] .canvas-modal-backdrop .input, +body[data-canvas-theme="light"] .canvas-modal-backdrop .textarea, +body[data-canvas-theme="light"] .canvas-modal-backdrop .select { + background: #fff; +} + +/* Modal backdrop — rendered at the body level, so it gets its own theme vars */ +.canvas-modal-backdrop { + position: fixed; + inset: 0; + background: rgba(17, 24, 39, 0.7); + backdrop-filter: blur(6px); + z-index: 100; + display: grid; + place-items: center; + padding: 40px 20px; + animation: canvas-fade-in .15s ease; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + + --canvas-bg: #111827; + --canvas-bg-2: #0f172a; + --canvas-surface: #1F2937; + --canvas-surface-2: #374151; + --canvas-surface-3: #4B5563; + --canvas-border: #374151; + --canvas-border-2: #4B5563; + --canvas-text: #F9FAFB; + --canvas-text-2: #D1D5DB; + --canvas-text-3: #9CA3AF; + --canvas-text-4: #6B7280; + --canvas-accent: #818CF8; + --canvas-accent-dim: #4F46E5; + --canvas-accent-glow: rgba(129, 140, 248, 0.20); + --canvas-critic: #A78BFA; + --canvas-critic-glow: rgba(167, 139, 250, 0.20); + --canvas-warn: #F59E0B; + --canvas-danger: #DC2626; + --canvas-ok: #10B981; + --canvas-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; + --canvas-shadow-lg: 0 24px 60px rgba(0,0,0,0.55), 0 6px 16px rgba(0,0,0,0.4); + color: var(--canvas-text); +} +body[data-canvas-theme="light"] .canvas-modal-backdrop { + background: rgba(17, 24, 39, 0.35); + --canvas-bg: #F9FAFB; + --canvas-bg-2: #FFFFFF; + --canvas-surface: #FFFFFF; + --canvas-surface-2: #F3F4F6; + --canvas-surface-3: #E5E7EB; + --canvas-border: #E5E7EB; + --canvas-border-2: #D1D5DB; + --canvas-text: #111827; + --canvas-text-2: #374151; + --canvas-text-3: #6B7280; + --canvas-text-4: #9CA3AF; + --canvas-accent: #6366F1; + --canvas-accent-dim: #C7D2FE; + --canvas-accent-glow: rgba(99, 102, 241, 0.15); + --canvas-critic: #8B5CF6; + --canvas-critic-glow: rgba(139, 92, 246, 0.12); + --canvas-shadow-lg: 0 18px 48px rgba(20, 30, 50, 0.16); +} +@keyframes canvas-fade-in { from { opacity: 0 } to { opacity: 1 } } + +.canvas-modal { + background: var(--canvas-surface); + border: 1px solid var(--canvas-border-2); + border-radius: 14px; + width: 100%; + max-width: 560px; + max-height: 88vh; + overflow: hidden; + display: flex; + flex-direction: column; + box-shadow: var(--canvas-shadow-lg); + animation: canvas-scale-in .18s cubic-bezier(.22,.9,.3,1); +} +.canvas-modal.wide { max-width: 760px; } +.canvas-modal.huge { max-width: 920px; } +.canvas-modal.canvas-tour { max-width: 480px; } +@keyframes canvas-scale-in { from { opacity: 0; transform: translateY(8px) scale(0.98) } to { opacity: 1; transform: none } } + +.canvas-modal .modal-head { + padding: 18px 22px 14px; + border-bottom: 1px solid var(--canvas-border); + display: flex; + align-items: center; + gap: 12px; +} +.canvas-modal .modal-title { font-weight: 600; font-size: 15px; flex: 1; letter-spacing: -0.005em; color: var(--canvas-text); } +.canvas-modal .modal-sub { color: var(--canvas-text-3); font-size: 12px; margin-top: 2px; } +.canvas-modal .modal-body { padding: 18px 22px; overflow-y: auto; flex: 1; } +.canvas-modal .modal-foot { + padding: 14px 22px; + border-top: 1px solid var(--canvas-border); + display: flex; + justify-content: flex-end; + gap: 8px; + background: var(--canvas-bg-2); +} + +.canvas-modal .modal-icon { + width: 36px; + height: 36px; + border-radius: 9px; + background: var(--canvas-accent-glow); + color: var(--canvas-accent); + display: grid; + place-items: center; +} +.canvas-modal .modal-icon.critic { background: var(--canvas-critic-glow); color: var(--canvas-critic); } + +.canvas-modal-backdrop .form-grid { display: grid; gap: 12px; } +.canvas-modal-backdrop .form-grid.two { grid-template-columns: 1fr 1fr; } +.canvas-modal-backdrop .form-row { display: flex; flex-direction: column; gap: 6px; } + +/* Widget palette */ +.canvas-modal-backdrop .palette-search { padding: 4px 0 16px; position: sticky; top: 0; background: var(--canvas-surface); z-index: 2; } +.canvas-modal-backdrop .palette-cats { + display: flex; + gap: 6px; + flex-wrap: wrap; + margin-bottom: 14px; +} +.canvas-modal-backdrop .palette-cat { + font-size: 12px; + padding: 5px 10px; + border-radius: 999px; + background: var(--canvas-surface-2); + color: var(--canvas-text-2); + border: 1px solid var(--canvas-border); + cursor: pointer; + font-family: inherit; +} +.canvas-modal-backdrop .palette-cat:hover { color: var(--canvas-text); } +.canvas-modal-backdrop .palette-cat.active { background: var(--canvas-accent-glow); color: var(--canvas-accent); border-color: var(--canvas-accent); } +.canvas-modal-backdrop .palette-cat.critic.active { background: var(--canvas-critic-glow); color: var(--canvas-critic); border-color: var(--canvas-critic); } +.canvas-modal-backdrop .palette-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 8px; +} +.canvas-modal-backdrop .palette-item { + background: var(--canvas-surface-2); + border: 1px solid var(--canvas-border); + border-radius: 9px; + padding: 12px; + display: flex; + gap: 10px; + align-items: flex-start; + cursor: pointer; + text-align: left; + transition: all .12s; + font-family: inherit; + color: var(--canvas-text); +} +.canvas-modal-backdrop .palette-item:hover { + background: var(--canvas-surface-3); + border-color: var(--canvas-border-2); + transform: translateY(-1px); +} +.canvas-modal-backdrop .palette-item.added { opacity: 0.4; cursor: default; } +.canvas-modal-backdrop .palette-item.added:hover { transform: none; } +.canvas-modal-backdrop .palette-item.critic { border-left: 2px solid var(--canvas-critic); } +.canvas-modal-backdrop .palette-item .pi-icon { + width: 30px; + height: 30px; + border-radius: 7px; + background: var(--canvas-surface-3); + display: grid; + place-items: center; + color: var(--canvas-accent); + flex-shrink: 0; +} +.canvas-modal-backdrop .palette-item.critic .pi-icon { color: var(--canvas-critic); } +.canvas-modal-backdrop .palette-item .pi-content { flex: 1; min-width: 0; } +.canvas-modal-backdrop .palette-item .pi-title { font-size: 13px; font-weight: 600; display: flex; align-items: center; gap: 6px; color: var(--canvas-text); } +.canvas-modal-backdrop .palette-item .pi-desc { font-size: 11.5px; color: var(--canvas-text-3); margin-top: 2px; line-height: 1.4; } +.canvas-modal-backdrop .palette-item .pi-added { + font-size: 10px; + font-family: var(--canvas-mono); + color: var(--canvas-accent); + background: var(--canvas-accent-glow); + padding: 2px 6px; + border-radius: 3px; + text-transform: uppercase; + letter-spacing: 0.08em; +} + +/* Bibliography widget */ +.canvas-page-with-sidebar .bib-list { display: flex; flex-direction: column; gap: 6px; max-height: 320px; overflow-y: auto; } +.canvas-page-with-sidebar .bib-cite { color: var(--canvas-text); font-weight: 500; line-height: 1.4; } +.canvas-page-with-sidebar .bib-meta { font-family: var(--canvas-mono); font-size: 10px; color: var(--canvas-text-3); display: flex; gap: 8px; } +.canvas-page-with-sidebar .bib-meta .key { color: var(--canvas-accent); } + +.canvas-page-with-sidebar .format-tabs { display: flex; gap: 2px; background: var(--canvas-surface-2); padding: 2px; border-radius: 6px; } +.canvas-page-with-sidebar .format-tab { + font-family: var(--canvas-mono); + font-size: 10px; + padding: 3px 7px; + border-radius: 4px; + color: var(--canvas-text-3); + letter-spacing: 0.05em; + cursor: pointer; +} +.canvas-page-with-sidebar .format-tab.active { background: var(--canvas-surface-3); color: var(--canvas-text); } + +/* Kanban widget */ +.canvas-page-with-sidebar .kanban { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 8px; + flex: 1; + min-height: 0; +} +.canvas-page-with-sidebar .kan-col { + background: var(--canvas-bg-2); + border-radius: 7px; + padding: 8px; + display: flex; + flex-direction: column; + gap: 6px; + min-width: 0; + border: 1px solid var(--canvas-border); +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .kan-col { background: #f3f5f9; } +.canvas-page-with-sidebar .kan-col.drag-target { background: var(--canvas-accent-glow); border-color: var(--canvas-accent); } +.canvas-page-with-sidebar .kan-col-head { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--canvas-text-3); + display: flex; + justify-content: space-between; + font-weight: 600; + padding: 0 2px 4px; +} +.canvas-page-with-sidebar .kan-col-head .count { font-family: var(--canvas-mono); } +.canvas-page-with-sidebar .kan-card { + background: var(--canvas-surface-2); + border: 1px solid var(--canvas-border-2); + border-radius: 6px; + padding: 7px 9px; + font-size: 12px; + cursor: grab; + line-height: 1.35; + position: relative; + color: var(--canvas-text); +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .kan-card { background: #fff; box-shadow: 0 1px 2px rgba(20,30,50,0.04); } +.canvas-page-with-sidebar .kan-card:active { cursor: grabbing; } +.canvas-page-with-sidebar .kan-card:hover { background: var(--canvas-surface-3); } +.canvas-page-with-sidebar .kan-card.dragging { opacity: 0.4; } +.canvas-page-with-sidebar .kan-card .priority-bar { + position: absolute; + left: 0; top: 6px; bottom: 6px; + width: 2px; + border-radius: 2px; +} +.canvas-page-with-sidebar .kan-card.high .priority-bar { background: var(--canvas-danger); } +.canvas-page-with-sidebar .kan-card.med .priority-bar { background: var(--canvas-warn); } +.canvas-page-with-sidebar .kan-card.low .priority-bar { background: var(--canvas-text-4); } +.canvas-page-with-sidebar .kan-card .kan-title { padding-left: 6px; } +.canvas-page-with-sidebar .kan-card .kan-meta { + font-family: var(--canvas-mono); + font-size: 9.5px; + color: var(--canvas-text-3); + margin-top: 4px; + padding-left: 6px; + display: flex; + gap: 6px; +} + +.canvas-page-with-sidebar .add-tiny { + font-size: 11px; + color: var(--canvas-text-3); + padding: 4px 6px; + border-radius: 4px; + text-align: left; + border: 1px dashed transparent; + cursor: pointer; + background: none; + font-family: inherit; +} +.canvas-page-with-sidebar .add-tiny:hover { color: var(--canvas-accent); border-color: var(--canvas-border); background: var(--canvas-surface); } + +/* Pomodoro */ +.canvas-page-with-sidebar .pomo { + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + padding: 6px 0; + flex: 1; + justify-content: center; +} +.canvas-page-with-sidebar .pomo-ring { + width: 130px; + height: 130px; + position: relative; +} +.canvas-page-with-sidebar .pomo-ring svg { transform: rotate(-90deg); } +.canvas-page-with-sidebar .pomo-ring .track { stroke: var(--canvas-surface-3); } +.canvas-page-with-sidebar[data-canvas-theme="light"] .pomo-ring .track { stroke: #e7ebf2; } +.canvas-page-with-sidebar .pomo-ring .fill { + stroke: var(--canvas-accent); + stroke-linecap: round; + filter: drop-shadow(0 0 6px var(--canvas-accent-glow)); + transition: stroke-dashoffset .4s ease; +} +.canvas-page-with-sidebar .pomo-ring.break .fill { stroke: var(--canvas-ok); filter: drop-shadow(0 0 6px rgba(16,185,129,0.4)); } +.canvas-page-with-sidebar .pomo-time { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-family: var(--canvas-mono); +} +.canvas-page-with-sidebar .pomo-time .t { font-size: 26px; font-weight: 600; color: var(--canvas-text); letter-spacing: -0.02em; } +.canvas-page-with-sidebar .pomo-time .l { font-size: 10px; color: var(--canvas-text-3); text-transform: uppercase; letter-spacing: 0.08em; margin-top: 2px; } +.canvas-page-with-sidebar .pomo-controls { display: flex; gap: 6px; } +.canvas-page-with-sidebar .pomo-stats { + font-family: var(--canvas-mono); + font-size: 10px; + color: var(--canvas-text-3); + display: flex; + gap: 14px; +} +.canvas-page-with-sidebar .pomo-stats b { color: var(--canvas-accent); font-weight: 500; } + +/* Writing tracker */ +.canvas-page-with-sidebar .write-stats { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 8px; +} +.canvas-page-with-sidebar .stat { + background: var(--canvas-bg-2); + border: 1px solid var(--canvas-border); + border-radius: 7px; + padding: 9px 11px; +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .stat { background: #fff; } +.canvas-page-with-sidebar .stat .stat-label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--canvas-text-3); font-weight: 600; } +.canvas-page-with-sidebar .stat .stat-value { font-family: var(--canvas-mono); font-size: 18px; font-weight: 600; color: var(--canvas-text); margin-top: 3px; letter-spacing: -0.02em; } +.canvas-page-with-sidebar .stat .stat-sub { font-size: 10.5px; color: var(--canvas-text-3); margin-top: 2px; } +.canvas-page-with-sidebar .stat .stat-value.accent { color: var(--canvas-accent); } + +.canvas-page-with-sidebar .spark { width: 100%; height: 56px; } +.canvas-page-with-sidebar .spark .area { fill: var(--canvas-accent-glow); } +.canvas-page-with-sidebar .spark .line { stroke: var(--canvas-accent); fill: none; stroke-width: 1.5; } +.canvas-page-with-sidebar .spark .dot-today { fill: var(--canvas-accent); } +.canvas-page-with-sidebar .spark .grid { stroke: var(--canvas-border); stroke-dasharray: 2 3; } + +.canvas-page-with-sidebar .progress { + height: 6px; + background: var(--canvas-surface-2); + border-radius: 3px; + overflow: hidden; +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .progress { background: #e7ebf2; } +.canvas-page-with-sidebar .progress > i { + display: block; + height: 100%; + background: var(--canvas-accent); + border-radius: 3px; + transition: width .3s; +} + +/* Deadlines */ +.canvas-page-with-sidebar .dl-list { display: flex; flex-direction: column; gap: 6px; } +.canvas-page-with-sidebar .dl-row { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 10px; + background: var(--canvas-bg-2); + border: 1px solid var(--canvas-border); + border-radius: 7px; +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .dl-row { background: #fff; } +.canvas-page-with-sidebar .dl-row:hover { border-color: var(--canvas-border-2); } +.canvas-page-with-sidebar .dl-day { + font-family: var(--canvas-mono); + font-size: 11px; + font-weight: 600; + width: 56px; + text-align: center; + padding: 6px 0; + border-radius: 5px; + background: var(--canvas-surface-2); + flex-shrink: 0; + line-height: 1.1; + color: var(--canvas-text-2); +} +.canvas-page-with-sidebar .dl-day.urgent { background: rgba(220,38,38,0.15); color: var(--canvas-danger); } +.canvas-page-with-sidebar .dl-day.warn { background: rgba(245,158,11,0.15); color: var(--canvas-warn); } +.canvas-page-with-sidebar .dl-day .num { font-size: 16px; display: block; } +.canvas-page-with-sidebar .dl-day .lbl { font-size: 9px; text-transform: uppercase; letter-spacing: 0.06em; } +.canvas-page-with-sidebar .dl-info { flex: 1; min-width: 0; } +.canvas-page-with-sidebar .dl-title { font-size: 12.5px; font-weight: 500; color: var(--canvas-text); } +.canvas-page-with-sidebar .dl-sub { font-family: var(--canvas-mono); font-size: 10px; color: var(--canvas-text-3); margin-top: 2px; } + +/* Budget */ +.canvas-page-with-sidebar .budget-bar { + height: 8px; + background: var(--canvas-surface-2); + border-radius: 4px; + overflow: hidden; + display: flex; + margin: 6px 0 12px; +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .budget-bar { background: #e7ebf2; } +.canvas-page-with-sidebar .budget-bar > i { display: block; height: 100%; } +.canvas-page-with-sidebar .budget-list { display: flex; flex-direction: column; gap: 6px; } +.canvas-page-with-sidebar .budget-row { + display: grid; + grid-template-columns: 12px 1fr auto auto; + gap: 10px; + align-items: center; + font-size: 12px; + padding: 5px 0; + color: var(--canvas-text); +} +.canvas-page-with-sidebar .budget-row .swatch { width: 8px; height: 8px; border-radius: 2px; } +.canvas-page-with-sidebar .budget-row .amt { font-family: var(--canvas-mono); color: var(--canvas-text-2); } +.canvas-page-with-sidebar .budget-row .pct { font-family: var(--canvas-mono); color: var(--canvas-text-3); font-size: 10.5px; width: 36px; text-align: right; } + +/* Reviewer 2 widget */ +.canvas-page-with-sidebar .critic-prompt { + font-size: 12px; + color: var(--canvas-text-2); + background: var(--canvas-bg-2); + border: 1px dashed var(--canvas-critic-glow); + border-radius: 7px; + padding: 10px 12px; + font-style: italic; + line-height: 1.5; +} +.canvas-page-with-sidebar .critic-meter { + display: flex; + align-items: center; + gap: 8px; + font-family: var(--canvas-mono); + font-size: 10.5px; + color: var(--canvas-text-3); +} +.canvas-page-with-sidebar .critic-meter .bar { + flex: 1; + height: 4px; + background: var(--canvas-surface-2); + border-radius: 2px; + position: relative; + overflow: hidden; +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .critic-meter .bar { background: #e7ebf2; } +.canvas-page-with-sidebar .critic-meter .bar > i { + position: absolute; + left: 0; top: 0; bottom: 0; + background: linear-gradient(90deg, var(--canvas-ok), var(--canvas-warn) 50%, var(--canvas-critic)); + border-radius: 2px; +} + +.canvas-page-with-sidebar .review, +.canvas-modal-backdrop .review { + background: var(--canvas-bg-2); + border-left: 3px solid var(--canvas-critic); + padding: 10px 12px; + border-radius: 0 6px 6px 0; + font-size: 12.5px; + line-height: 1.55; + color: var(--canvas-text); +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .review, +body[data-canvas-theme="light"] .canvas-modal-backdrop .review { background: #fff; } +.canvas-page-with-sidebar .review .review-tag, +.canvas-modal-backdrop .review .review-tag { + font-family: var(--canvas-mono); + font-size: 10px; + color: var(--canvas-critic); + text-transform: uppercase; + letter-spacing: 0.08em; + font-weight: 600; + margin-bottom: 4px; + display: block; +} + +/* Devil's Advocate */ +.canvas-page-with-sidebar .devil-list, +.canvas-modal-backdrop .devil-list { display: flex; flex-direction: column; gap: 6px; } +.canvas-page-with-sidebar .devil-item, +.canvas-modal-backdrop .devil-item { + background: var(--canvas-bg-2); + border: 1px solid var(--canvas-border); + border-radius: 7px; + padding: 9px 11px; + font-size: 12px; + position: relative; + padding-left: 30px; + color: var(--canvas-text); +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .devil-item, +body[data-canvas-theme="light"] .canvas-modal-backdrop .devil-item { background: #fff; } +.canvas-page-with-sidebar .devil-item::before, +.canvas-modal-backdrop .devil-item::before { + content: '◆'; + position: absolute; + left: 11px; + top: 9px; + color: var(--canvas-critic); + font-size: 10px; +} +.canvas-page-with-sidebar .devil-item .lbl, +.canvas-modal-backdrop .devil-item .lbl { + font-family: var(--canvas-mono); + font-size: 9.5px; + color: var(--canvas-critic); + text-transform: uppercase; + letter-spacing: 0.08em; + font-weight: 600; + margin-bottom: 3px; +} + +/* Scope realism */ +.canvas-page-with-sidebar .realism { display: flex; flex-direction: column; gap: 10px; } +.canvas-page-with-sidebar .realism-verdict { + background: var(--canvas-bg-2); + border: 1px solid var(--canvas-critic-glow); + border-radius: 9px; + padding: 12px 14px; +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .realism-verdict { background: #fff; } +.canvas-page-with-sidebar .verdict-head { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 8px; +} +.canvas-page-with-sidebar .verdict-score { + font-family: var(--canvas-mono); + font-size: 22px; + font-weight: 600; + color: var(--canvas-critic); + letter-spacing: -0.02em; +} +.canvas-page-with-sidebar .verdict-label { + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--canvas-text-2); + font-weight: 600; +} +.canvas-page-with-sidebar .realism-row, +.canvas-modal-backdrop .realism-row { + display: flex; + align-items: center; + gap: 10px; + font-size: 12px; + padding: 5px 0; +} +.canvas-page-with-sidebar .realism-row .label-cell, +.canvas-modal-backdrop .realism-row .label-cell { width: 100px; color: var(--canvas-text-2); font-size: 11.5px; } +.canvas-page-with-sidebar .realism-row .gauge, +.canvas-modal-backdrop .realism-row .gauge { + flex: 1; + height: 4px; + background: var(--canvas-surface-2); + border-radius: 2px; + overflow: hidden; +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .gauge, +body[data-canvas-theme="light"] .canvas-modal-backdrop .gauge, +body[data-canvas-theme="light"] .canvas-modal-backdrop .critic-meter .bar { background: #e7ebf2; } +.canvas-page-with-sidebar .realism-row .gauge i, +.canvas-modal-backdrop .realism-row .gauge i { display: block; height: 100%; background: var(--canvas-critic); border-radius: 2px; } +.canvas-page-with-sidebar .realism-row .val, +.canvas-modal-backdrop .realism-row .val { font-family: var(--canvas-mono); font-size: 10.5px; color: var(--canvas-text-3); width: 30px; text-align: right; } + +/* Notes / generic row */ +.canvas-page-with-sidebar .note-list { display: flex; flex-direction: column; gap: 6px; max-height: 280px; overflow-y: auto; } +.canvas-page-with-sidebar .note-row { + background: var(--canvas-bg-2); + border: 1px solid var(--canvas-border); + border-radius: 7px; + padding: 8px 10px; + display: flex; + flex-direction: column; + gap: 4px; + position: relative; +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .note-row { background: #fff; } +.canvas-page-with-sidebar .note-row:hover { border-color: var(--canvas-border-2); } +.canvas-page-with-sidebar .note-row .note-text { font-size: 12.5px; color: var(--canvas-text); line-height: 1.45; word-break: break-word; } +.canvas-page-with-sidebar .note-row .note-meta { font-family: var(--canvas-mono); font-size: 9.5px; color: var(--canvas-text-3); display: flex; gap: 8px; } +.canvas-page-with-sidebar .note-row .row-actions, +.canvas-page-with-sidebar .dl-row .row-actions { position: absolute; top: 6px; right: 6px; display: flex; gap: 1px; opacity: 0; transition: opacity .12s; } +.canvas-page-with-sidebar .note-row:hover .row-actions, +.canvas-page-with-sidebar .dl-row:hover .row-actions { opacity: 1; } +.canvas-page-with-sidebar .row-actions .icon-btn { width: 22px; height: 22px; } +.canvas-page-with-sidebar .row-actions .icon-btn svg { width: 12px; height: 12px; } +.canvas-page-with-sidebar .tag-pill { display: inline-block; font-family: var(--canvas-mono); font-size: 9.5px; padding: 1px 6px; border-radius: 3px; background: var(--canvas-accent-glow); color: var(--canvas-accent); text-transform: uppercase; letter-spacing: 0.06em; } + +/* ----- Deliverables view ----- */ + +/* Paper / document mode — three-column layout that collapses on narrow screens */ +.canvas-page-with-sidebar .deliverable-grid { + display: grid; + grid-template-columns: 180px minmax(0, 1fr) 240px; + gap: 16px; + align-items: start; +} +.canvas-page-with-sidebar .deliverable-nav { + display: flex; + flex-direction: column; + gap: 4px; + position: sticky; + top: 0; +} + +/* Slides mode — thumbs on left, big canvas in middle, panel on right */ +.canvas-page-with-sidebar .deliverable-slides-grid { + display: grid; + grid-template-columns: 160px minmax(0, 1fr) 240px; + gap: 16px; + align-items: start; +} +@media (max-width: 1280px) { + .canvas-page-with-sidebar .deliverable-slides-grid { grid-template-columns: 140px minmax(0, 1fr); } + .canvas-page-with-sidebar .deliverable-slides-grid > .deliverable-insertables { display: none; } +} +@media (max-width: 768px) { + .canvas-page-with-sidebar .deliverable-slides-grid { grid-template-columns: 1fr; } + .canvas-page-with-sidebar .slide-thumbs { display: none; } +} + +/* Slide thumbnails column */ +.canvas-page-with-sidebar .slide-thumbs { + display: flex; + flex-direction: column; + gap: 8px; + position: sticky; + top: 0; + max-height: calc(100vh - 100px); + overflow-y: auto; + padding-right: 4px; +} +.canvas-page-with-sidebar .slide-thumb { + display: flex; + align-items: stretch; + gap: 6px; + padding: 0; + background: transparent; + border: none; + cursor: pointer; + font-family: inherit; + text-align: left; +} +.canvas-page-with-sidebar .slide-thumb-num { + width: 18px; + font-family: var(--canvas-mono); + font-size: 10px; + color: var(--canvas-text-3); + padding-top: 4px; + flex-shrink: 0; + text-align: right; +} +.canvas-page-with-sidebar .slide-thumb-canvas { + flex: 1; + background: #fff; + border: 1px solid var(--canvas-border-2); + border-radius: 4px; + aspect-ratio: 16 / 9; + padding: 5px 6px; + overflow: hidden; + color: #111; + display: flex; + flex-direction: column; + gap: 3px; + transition: all .15s; +} +.canvas-page-with-sidebar .slide-thumb:hover .slide-thumb-canvas { + border-color: var(--canvas-accent); + transform: translateY(-1px); +} +.canvas-page-with-sidebar .slide-thumb.active .slide-thumb-canvas { + border: 2px solid var(--canvas-accent); + box-shadow: 0 0 0 2px var(--canvas-accent-glow); + padding: 4px 5px; +} +.canvas-page-with-sidebar .slide-thumb.active .slide-thumb-num { color: var(--canvas-accent); font-weight: 700; } +.canvas-page-with-sidebar .slide-thumb-title { + font-size: 8px; + font-weight: 700; + color: #1a1a1a; + letter-spacing: -0.01em; + line-height: 1.1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.canvas-page-with-sidebar .slide-thumb-body { + font-size: 6.5px; + color: #555; + line-height: 1.25; + overflow: hidden; + flex: 1; +} + +/* Big slide canvas */ +.canvas-page-with-sidebar .slide-canvas-wrap { + background: var(--canvas-surface-2); + border: 1px solid var(--canvas-border); + border-radius: 10px; + padding: 18px; + display: flex; + align-items: center; + justify-content: center; +} +.canvas-page-with-sidebar .slide-canvas { + position: relative; + width: 100%; + max-width: 720px; + aspect-ratio: 16 / 9; + background: #fff; + border-radius: 6px; + box-shadow: 0 12px 32px rgba(0,0,0,0.18), 0 4px 10px rgba(0,0,0,0.10); + padding: 36px 44px; + display: flex; + flex-direction: column; + gap: 16px; + color: #111; + overflow: hidden; +} +.canvas-page-with-sidebar .slide-canvas::before { + content: ''; + position: absolute; + top: 0; left: 0; right: 0; + height: 4px; + background: linear-gradient(90deg, var(--canvas-accent), var(--canvas-critic)); +} +.canvas-page-with-sidebar .slide-canvas-title { + font-size: 26px; + font-weight: 700; + letter-spacing: -0.02em; + color: #0f172a; + line-height: 1.15; +} +.canvas-page-with-sidebar .slide-canvas-body { + flex: 1; + font-size: 15px; + color: #1f2937; + line-height: 1.55; + overflow: hidden; +} +.canvas-page-with-sidebar .slide-canvas-body ul { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: 8px; +} +.canvas-page-with-sidebar .slide-canvas-body ul li { + position: relative; + padding-left: 22px; +} +.canvas-page-with-sidebar .slide-canvas-body ul li::before { + content: ''; + position: absolute; + left: 0; + top: 9px; + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--canvas-accent); +} +.canvas-page-with-sidebar .slide-paragraph { + font-size: 17px; + line-height: 1.6; +} +.canvas-page-with-sidebar .slide-placeholder { + color: #9ca3af; + font-style: italic; + font-size: 14px; +} +.canvas-page-with-sidebar .slide-canvas-footer { + position: absolute; + bottom: 14px; + right: 18px; + font-family: var(--canvas-mono); + font-size: 10px; + color: #9ca3af; +} + +/* Section-check pill (used in both modes) */ +.canvas-page-with-sidebar .check-pill { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 11px; + padding: 3px 9px; + border-radius: 999px; + background: var(--canvas-surface-2); + color: var(--canvas-text-3); + font-family: var(--canvas-sans); +} +.canvas-page-with-sidebar .check-pill[data-passed="true"] { + background: rgba(16, 185, 129, 0.15); + color: #10B981; +} + +/* Document-page preview (Google Docs feel) */ +.canvas-page-with-sidebar .doc-page { + background: #ffffff; + color: #1a1a1a; + padding: 56px 72px; + border-radius: 4px; + box-shadow: 0 4px 14px rgba(0,0,0,0.10), 0 1px 4px rgba(0,0,0,0.06); + font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; + max-width: 800px; + margin: 0 auto; + width: 100%; +} +.canvas-page-with-sidebar .doc-title { + font-size: 28px; + font-weight: 700; + margin: 0 0 24px; + letter-spacing: -0.02em; + color: #0f172a; +} +.canvas-page-with-sidebar .doc-h2 { + font-size: 18px; + font-weight: 700; + margin: 24px 0 8px; + color: #0f172a; +} +.canvas-page-with-sidebar .doc-body { font-size: 14px; line-height: 1.7; color: #1f2937; } +.canvas-page-with-sidebar .doc-body p { margin: 0 0 12px; } +.canvas-page-with-sidebar .doc-body strong { color: #0f172a; } +.canvas-page-with-sidebar .doc-section { margin-bottom: 8px; } + +/* Paper-page preview (Overleaf feel — serif, two-column body) */ +.canvas-page-with-sidebar .paper-page { + background: #fdfbf7; + color: #1a1a1a; + padding: 64px 72px; + border-radius: 4px; + box-shadow: 0 4px 14px rgba(0,0,0,0.10), 0 1px 4px rgba(0,0,0,0.06); + font-family: 'Georgia', 'Times New Roman', serif; + max-width: 820px; + margin: 0 auto; + width: 100%; +} +.canvas-page-with-sidebar .paper-page-head { + text-align: center; + border-bottom: 1px solid #d1d5db; + padding-bottom: 16px; + margin-bottom: 22px; +} +.canvas-page-with-sidebar .paper-title { + font-size: 22px; + font-weight: 700; + letter-spacing: -0.01em; + color: #0f172a; +} +.canvas-page-with-sidebar .paper-byline { + font-size: 12px; + color: #6b7280; + font-style: italic; + margin-top: 6px; +} +.canvas-page-with-sidebar .paper-h2 { + font-size: 14px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.08em; + color: #0f172a; + margin: 22px 0 8px; +} +.canvas-page-with-sidebar .paper-body { font-size: 13px; line-height: 1.65; color: #1f2937; text-align: justify; } +.canvas-page-with-sidebar .paper-body p { margin: 0 0 10px; text-indent: 1.5em; } +.canvas-page-with-sidebar .paper-body p:first-child { text-indent: 0; } +.canvas-page-with-sidebar .paper-empty { color: #9ca3af; font-style: italic; font-size: 12px; } +.canvas-page-with-sidebar .paper-section { margin-bottom: 4px; } + +/* ----- Canvas-mode app sidebar menu (Insights / Workspace / Deliverables) ----- */ +/* These rules live in the *app* sidebar (not the canvas-page-with-sidebar scope) + because they apply to .sidebar from components.css when the user is on canvas. */ +.canvas-sidebar-menu { + flex: 1; + overflow-y: auto; + padding: 8px 8px 16px; + display: flex; + flex-direction: column; + gap: 4px; +} +.canvas-sidebar-menu .no-sessions { + padding: 18px 12px; + font-size: 12px; + color: var(--text-tertiary, #9CA3AF); + text-align: center; +} + +/* --- Workspace: widget groups --- */ +.canvas-sidebar-menu .csm-group { display: flex; flex-direction: column; gap: 1px; margin-bottom: 4px; } +.canvas-sidebar-menu .csm-group-head { + display: flex; + align-items: center; + gap: 6px; + padding: 6px 8px; + background: transparent; + border: none; + border-radius: 5px; + font-family: inherit; + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.06em; + font-weight: 600; + color: var(--text-tertiary, #9CA3AF); + cursor: pointer; +} +.canvas-sidebar-menu .csm-group-head:hover { background: var(--bg-secondary, #f3f4f6); color: var(--text-secondary, #6b7280); } +.dark .canvas-sidebar-menu .csm-group-head:hover { background: var(--bg-secondary-dark, #374151); } +.canvas-sidebar-menu .csm-group-name { flex: 1; text-align: left; } +.canvas-sidebar-menu .csm-group-count { + font-family: 'JetBrains Mono', monospace; + font-size: 10px; + color: var(--text-tertiary, #9CA3AF); +} +.canvas-sidebar-menu .csm-group-body { display: flex; flex-direction: column; gap: 1px; padding: 2px 0 6px 8px; } + +/* --- Deliverables: project blocks --- */ +.canvas-sidebar-menu .csm-project { + display: flex; + flex-direction: column; + gap: 1px; + border-radius: 6px; + margin-bottom: 4px; +} +.canvas-sidebar-menu .csm-project.active { background: rgba(99, 102, 241, 0.06); } +.dark .canvas-sidebar-menu .csm-project.active { background: rgba(129, 140, 248, 0.10); } +.canvas-sidebar-menu .csm-project-head { + display: flex; + align-items: center; + gap: 7px; + padding: 8px 10px; + background: transparent; + border: none; + border-radius: 5px; + font-family: inherit; + font-size: 13px; + font-weight: 500; + color: var(--text-primary, #111827); + cursor: pointer; + text-align: left; +} +.canvas-sidebar-menu .csm-project-head:hover { background: var(--bg-secondary, #f3f4f6); } +.dark .canvas-sidebar-menu .csm-project-head { color: var(--text-primary-dark, #f9fafb); } +.dark .canvas-sidebar-menu .csm-project-head:hover { background: var(--bg-secondary-dark, #374151); } +.canvas-sidebar-menu .csm-project.active .csm-project-name { color: var(--accent-primary, #6366F1); font-weight: 600; } +.canvas-sidebar-menu .csm-project-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.canvas-sidebar-menu .csm-versions { + display: inline-flex; + align-items: center; + gap: 3px; + font-family: 'JetBrains Mono', monospace; + font-size: 10px; + color: var(--text-tertiary, #9CA3AF); + padding: 1px 5px; + border-radius: 3px; + background: var(--bg-secondary, #f3f4f6); +} +.dark .canvas-sidebar-menu .csm-versions { background: var(--bg-secondary-dark, #374151); } +.canvas-sidebar-menu .csm-project-body { + padding: 2px 0 8px 22px; + display: flex; + flex-direction: column; + gap: 1px; +} + +/* --- Shared row (used in groups and project bodies) --- */ +.canvas-sidebar-menu .csm-row { + display: flex; + align-items: center; + gap: 8px; + padding: 5px 10px 5px 6px; + background: transparent; + border: none; + border-radius: 4px; + font-family: inherit; + font-size: 12.5px; + color: var(--text-secondary, #6b7280); + text-align: left; + cursor: pointer; +} +.canvas-sidebar-menu .csm-row:hover { + background: var(--bg-secondary, #f3f4f6); + color: var(--text-primary, #111827); +} +.dark .canvas-sidebar-menu .csm-row { color: var(--text-secondary-dark, #9ca3af); } +.dark .canvas-sidebar-menu .csm-row:hover { background: var(--bg-secondary-dark, #374151); color: var(--text-primary-dark, #f9fafb); } +.canvas-sidebar-menu .csm-row.critic { color: #8B5CF6; } +.dark .canvas-sidebar-menu .csm-row.critic { color: #A78BFA; } +.canvas-sidebar-menu .csm-row-bullet { + width: 4px; + height: 4px; + border-radius: 50%; + background: currentColor; + opacity: 0.45; + flex-shrink: 0; + margin-left: 4px; +} +.canvas-sidebar-menu .csm-row-icon { font-size: 11px; opacity: 0.85; } +.canvas-sidebar-menu .csm-row-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.canvas-sidebar-menu .csm-row-meta { + font-family: 'JetBrains Mono', monospace; + font-size: 9.5px; + color: var(--text-tertiary, #9CA3AF); +} +.canvas-sidebar-menu .csm-row-foot { + display: flex; + align-items: center; + gap: 6px; + padding: 3px 8px; + font-family: 'JetBrains Mono', monospace; + font-size: 9.5px; + color: var(--text-tertiary, #9CA3AF); + cursor: default; +} +.canvas-sidebar-menu .csm-row-foot:hover { background: transparent; color: var(--text-tertiary, #9CA3AF); } + +/* Chevron rotates open/closed */ +.canvas-sidebar-menu .csm-chevron { + transition: transform 0.15s ease; + color: var(--text-tertiary, #9CA3AF); +} +.canvas-sidebar-menu .csm-chevron.open { transform: rotate(90deg); } + +/* ----- Deliverable project tabs (Overleaf-style multi-draft) ----- */ +.canvas-page-with-sidebar .deliverable-projects { + display: flex; + gap: 4px; + flex-wrap: wrap; + align-items: center; + margin-bottom: 14px; + padding-bottom: 12px; + border-bottom: 1px solid var(--canvas-border); +} +.canvas-page-with-sidebar .deliverable-project-tab { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 11px; + border-radius: 6px; + background: transparent; + border: 1px solid transparent; + color: var(--canvas-text-2); + font-family: inherit; + font-size: 12.5px; + cursor: pointer; + transition: background .12s, color .12s, border-color .12s; +} +.canvas-page-with-sidebar .deliverable-project-tab:hover { + background: var(--canvas-surface-2); + color: var(--canvas-text); +} +.canvas-page-with-sidebar .deliverable-project-tab.active { + background: var(--canvas-surface-2); + border-color: var(--canvas-border); + color: var(--canvas-text); + font-weight: 600; +} +.canvas-page-with-sidebar .deliverable-new-project { position: relative; } +.canvas-page-with-sidebar .deliverable-new-project summary { + list-style: none; + cursor: pointer; + color: var(--canvas-text-3); +} +.canvas-page-with-sidebar .deliverable-new-project summary::-webkit-details-marker { display: none; } +.canvas-page-with-sidebar .deliverable-new-project[open] summary { background: var(--canvas-surface-2); color: var(--canvas-text); } + +/* Auto-save indicator next to the project title */ +.canvas-page-with-sidebar .save-indicator { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 11.5px; + color: var(--canvas-text-3); + font-family: var(--canvas-mono); + white-space: nowrap; + padding: 4px 8px; + border-radius: 999px; + background: var(--canvas-surface-2); + transition: background .2s, color .2s; +} +.canvas-page-with-sidebar .save-indicator-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--canvas-ok); + box-shadow: 0 0 6px rgba(16, 185, 129, 0.4); + transition: background .2s; +} +.canvas-page-with-sidebar .save-indicator.saving { + background: var(--canvas-accent-glow); + color: var(--canvas-accent); +} +.canvas-page-with-sidebar .save-indicator.saving .save-indicator-dot { + background: var(--canvas-accent); + animation: save-pulse 0.6s ease infinite; +} +@keyframes save-pulse { + 0%, 100% { opacity: 1; transform: scale(1); } + 50% { opacity: 0.4; transform: scale(0.85); } +} + +/* Editable project title — looks like the page-title but is an input */ +.canvas-page-with-sidebar .page-title-editable { + background: transparent; + border: none; + outline: none; + width: 100%; + font-family: inherit; + font-size: 28px; + font-weight: 700; + letter-spacing: -0.02em; + margin: 0; + padding: 2px 0; + color: var(--canvas-text); +} +.canvas-page-with-sidebar .page-title-editable:focus { + background: rgba(255, 255, 255, 0.03); + border-radius: 4px; + padding: 2px 6px; + margin: 0 -6px; +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .page-title-editable:focus { + background: rgba(0, 0, 0, 0.025); +} + +/* Version history dropdown panel */ +.canvas-page-with-sidebar .deliverable-history { + background: var(--canvas-surface); + border: 1px solid var(--canvas-border); + border-radius: 8px; + margin-bottom: 16px; + padding: 6px; + max-height: 320px; + overflow-y: auto; +} +.canvas-page-with-sidebar .deliverable-history-head { + display: flex; + align-items: center; + justify-content: space-between; + padding: 4px 8px 8px; + font-size: 11px; + font-family: var(--canvas-mono); + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--canvas-text-3); + border-bottom: 1px solid var(--canvas-border); + margin-bottom: 4px; +} +.canvas-page-with-sidebar .deliverable-history-row { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + padding: 6px 8px; + background: transparent; + border: none; + border-radius: 4px; + color: var(--canvas-text-2); + font-family: inherit; + font-size: 12px; + cursor: pointer; +} +.canvas-page-with-sidebar .deliverable-history-row:hover { + background: var(--canvas-surface-2); + color: var(--canvas-text); } -.empty-canvas-btn { - display: inline-flex; +/* Slash menu (block-insert popover) */ +.canvas-page-with-sidebar .slash-menu { + position: absolute; + bottom: calc(100% + 4px); + left: 0; + z-index: 30; + background: var(--canvas-surface); + border: 1px solid var(--canvas-border-2); + border-radius: 8px; + box-shadow: var(--canvas-shadow-lg); + padding: 4px; + min-width: 240px; + max-height: 280px; + overflow-y: auto; + display: flex; + flex-direction: column; +} +.canvas-page-with-sidebar .slash-menu-head { + font-size: 10px; + font-family: var(--canvas-mono); + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--canvas-text-4); + padding: 6px 8px 4px; +} +.canvas-page-with-sidebar .slash-menu button { + display: flex; align-items: center; - justify-content: center; - gap: 10px; - height: 46px; - padding: 0 22px; - border-radius: 12px; - font-size: 15px; - font-weight: 600; - letter-spacing: -0.005em; + gap: 8px; + background: transparent; + border: none; + padding: 6px 8px; + border-radius: 4px; + font-family: inherit; + font-size: 12.5px; + color: var(--canvas-text); + text-align: left; cursor: pointer; - transition: transform 0.18s cubic-bezier(0.4, 0, 0.2, 1), - box-shadow 0.22s ease, - background 0.18s ease, - border-color 0.18s ease, - color 0.18s ease; - border: 1px solid transparent; } - -.empty-canvas-btn .empty-canvas-btn-arrow { - transition: transform 0.18s ease; +.canvas-page-with-sidebar .slash-menu button:hover, +.canvas-page-with-sidebar .slash-menu button.active { + background: var(--canvas-surface-2); } - -.empty-canvas-btn:hover .empty-canvas-btn-arrow { - transform: translateX(3px); +.canvas-page-with-sidebar .slash-menu-kind { + font-family: var(--canvas-mono); + font-size: 9.5px; + color: var(--canvas-text-4); + text-transform: uppercase; + letter-spacing: 0.06em; } -.empty-canvas-btn:active { - transform: translateY(1px); +/* Poster mode — 4-quadrant grid, banners on top and bottom */ +.canvas-page-with-sidebar .poster-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 14px; + background: var(--canvas-surface); + border: 1px solid var(--canvas-border); + border-radius: 10px; + padding: 18px; +} +.canvas-page-with-sidebar .poster-banner { grid-column: 1 / -1; } +.canvas-page-with-sidebar .poster-panel { + background: var(--canvas-bg-2); + border: 1px solid var(--canvas-border); + border-radius: 8px; + padding: 14px; + display: flex; + flex-direction: column; + gap: 8px; } - -/* Primary — Start Chatting */ -.empty-canvas-btn.primary { - background: var(--accent-gradient); - color: #ffffff; - box-shadow: 0 6px 18px -6px rgba(99, 102, 241, 0.55), - inset 0 1px 0 rgba(255, 255, 255, 0.18); +.canvas-page-with-sidebar[data-canvas-theme="light"] .poster-panel { + background: #ffffff; + border-color: rgba(15, 15, 15, 0.08); } - -.empty-canvas-btn.primary:hover { - transform: translateY(-2px); - box-shadow: 0 12px 28px -8px rgba(99, 102, 241, 0.7), - inset 0 1px 0 rgba(255, 255, 255, 0.25); +.canvas-page-with-sidebar .poster-panel-head { + font-size: 13px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--canvas-accent); + border-bottom: 2px solid var(--canvas-accent-glow); + padding-bottom: 6px; +} +.canvas-page-with-sidebar .poster-panel textarea { + flex: 1; + min-height: 120px; + background: transparent; + border: none; + outline: none; + resize: none; + font-family: inherit; + font-size: 13px; + line-height: 1.5; + color: var(--canvas-text); + padding: 0; +} +@media (max-width: 768px) { + .canvas-page-with-sidebar .poster-grid { grid-template-columns: 1fr; } } -/* Secondary — Process Existing Chats */ -.empty-canvas-btn.secondary { - background: var(--bg-secondary); - color: var(--text-primary); - border-color: var(--border-primary); +/* ----- Rich editor block (click-to-edit + floating toolbar + LaTeX math) ----- */ +.canvas-page-with-sidebar .rich-block { + position: relative; + margin-top: 4px; } -.empty-canvas-btn.secondary:hover { - background: var(--bg-tertiary); - border-color: var(--accent-primary); - color: var(--accent-primary); - transform: translateY(-2px); - box-shadow: 0 6px 16px -6px rgba(0, 0, 0, 0.15); +.canvas-page-with-sidebar .rich-toolbar { + position: absolute; + bottom: calc(100% + 6px); + left: 0; + display: flex; + flex-wrap: wrap; + gap: 1px; + padding: 4px; + background: var(--canvas-surface); + border: 1px solid var(--canvas-border-2); + border-radius: 8px; + box-shadow: var(--canvas-shadow-lg); + z-index: 20; + animation: canvas-view-in 140ms var(--canvas-ease); +} +.canvas-page-with-sidebar .rich-toolbar button { + background: transparent; + border: none; + width: 30px; + height: 28px; + border-radius: 4px; + display: grid; + place-items: center; + color: var(--canvas-text-2); + font-family: inherit; + font-size: 13px; + cursor: pointer; + transition: background .12s, color .12s; +} +.canvas-page-with-sidebar .rich-toolbar button:hover { + background: var(--canvas-surface-2); + color: var(--canvas-text); +} + +/* Rendered block (idle state — looks like real document text) */ +.canvas-page-with-sidebar .rich-rendered { + cursor: text; + padding: 4px 0; + border-radius: 4px; + transition: background .12s; + outline: none; +} +.canvas-page-with-sidebar .rich-rendered:hover { + background: rgba(255, 255, 255, 0.02); +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .rich-rendered:hover { + background: rgba(0, 0, 0, 0.02); +} +.canvas-page-with-sidebar .rich-rendered:focus-visible { + background: rgba(0, 0, 0, 0.025); + box-shadow: 0 0 0 2px var(--canvas-accent-glow); +} +.canvas-page-with-sidebar .rich-placeholder { + color: var(--canvas-text-4); + font-style: italic; +} + +/* Inside the rendered block: typography matching the page */ +.canvas-page-with-sidebar .rich-rendered > * { margin: 0; } +.canvas-page-with-sidebar .rich-rendered > * + * { margin-top: 12px; } +.canvas-page-with-sidebar .rich-rendered p { + font-size: 16px; + line-height: 1.65; + color: var(--canvas-text); +} +.canvas-page-with-sidebar .rich-rendered.serif p { + font-family: 'Georgia', 'Times New Roman', serif; + font-size: 14.5px; + line-height: 1.7; + text-align: justify; +} +.canvas-page-with-sidebar .rich-rendered h1, +.canvas-page-with-sidebar .rich-rendered h2, +.canvas-page-with-sidebar .rich-rendered h3, +.canvas-page-with-sidebar .rich-rendered h4 { + font-weight: 700; + letter-spacing: -0.01em; + color: var(--canvas-text); + margin-top: 16px; +} +.canvas-page-with-sidebar .rich-rendered h1 { font-size: 24px; } +.canvas-page-with-sidebar .rich-rendered h2 { font-size: 19px; } +.canvas-page-with-sidebar .rich-rendered h3 { font-size: 16px; } +.canvas-page-with-sidebar .rich-rendered ul, +.canvas-page-with-sidebar .rich-rendered ol { + padding-left: 22px; + font-size: 16px; + line-height: 1.65; + color: var(--canvas-text); +} +.canvas-page-with-sidebar .rich-rendered.serif ul, +.canvas-page-with-sidebar .rich-rendered.serif ol { + font-family: 'Georgia', serif; + font-size: 14.5px; +} +.canvas-page-with-sidebar .rich-rendered li { margin-bottom: 4px; } +.canvas-page-with-sidebar .rich-rendered blockquote { + border-left: 3px solid var(--canvas-accent); + padding: 4px 14px; + color: var(--canvas-text-2); + margin-left: 0; + background: rgba(99, 102, 241, 0.04); + border-radius: 0 4px 4px 0; +} +.canvas-page-with-sidebar .rich-rendered code { + background: var(--canvas-surface-2); + padding: 1px 5px; + border-radius: 3px; + font-family: var(--canvas-mono); + font-size: 13px; +} +.canvas-page-with-sidebar .rich-rendered pre { + background: var(--canvas-bg-2); + border: 1px solid var(--canvas-border); + border-radius: 6px; + padding: 12px; + overflow-x: auto; + font-family: var(--canvas-mono); + font-size: 13px; +} +.canvas-page-with-sidebar .rich-rendered pre code { background: none; padding: 0; } +.canvas-page-with-sidebar .rich-rendered a { + color: var(--canvas-accent); + text-decoration: underline; + text-underline-offset: 2px; +} +.canvas-page-with-sidebar .rich-rendered strong { color: var(--canvas-text); font-weight: 700; } +.canvas-page-with-sidebar .rich-rendered em { color: var(--canvas-text); } +.canvas-page-with-sidebar .rich-rendered img { + max-width: 100%; + height: auto; + border-radius: 4px; + margin: 8px 0; + display: block; } -[data-theme="dark"] .empty-canvas-btn.secondary { - background: rgba(255, 255, 255, 0.05); - border-color: rgba(255, 255, 255, 0.12); - color: var(--text-primary); +/* KaTeX math overrides — match document text size */ +.canvas-page-with-sidebar .rich-rendered .katex { + font-size: 1.05em; + color: var(--canvas-text); +} +.canvas-page-with-sidebar .rich-rendered .katex-display { + margin: 16px 0; + padding: 8px 12px; + background: var(--canvas-bg-2); + border-radius: 6px; + overflow-x: auto; +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .rich-rendered .katex-display { + background: rgba(0, 0, 0, 0.025); } -[data-theme="dark"] .empty-canvas-btn.secondary:hover { - background: rgba(255, 255, 255, 0.1); - border-color: var(--accent-primary); - color: #ffffff; +/* ----- Real LaTeX editor (CodeMirror + LaTeX.js) ----- */ +.canvas-page-with-sidebar .paper-editor-toggle { + display: inline-flex; + align-items: center; + gap: 4px; + background: var(--canvas-surface); + border: 1px solid var(--canvas-border); + border-radius: 7px; + padding: 3px; + margin-bottom: 14px; +} +.canvas-page-with-sidebar .paper-editor-toggle-label { + font-size: 10.5px; + color: var(--canvas-text-4); + font-family: var(--canvas-mono); + text-transform: uppercase; + letter-spacing: 0.06em; + padding: 0 8px 0 6px; +} +.canvas-page-with-sidebar .paper-editor-toggle button { + background: transparent; + border: none; + padding: 4px 11px; + border-radius: 5px; + font-family: inherit; + font-size: 12px; + color: var(--canvas-text-3); + cursor: pointer; + transition: background .12s, color .12s; +} +.canvas-page-with-sidebar .paper-editor-toggle button:hover { color: var(--canvas-text); } +.canvas-page-with-sidebar .paper-editor-toggle button.active { + background: var(--canvas-surface-3); + color: var(--canvas-text); + box-shadow: inset 0 0 0 1px var(--canvas-border-2); } -.inline-loading-spinner { +.canvas-page-with-sidebar .latex-editor { + display: flex; + flex-direction: column; + background: var(--canvas-surface); + border: 1px solid var(--canvas-border); + border-radius: 8px; + overflow: hidden; +} +.canvas-page-with-sidebar .latex-editor-toolbar { display: flex; - justify-content: center; align-items: center; - width: 100%; - margin: 1.25rem 0; + gap: 8px; + padding: 8px 12px; + border-bottom: 1px solid var(--canvas-border); + background: var(--canvas-bg-2); } +.canvas-page-with-sidebar .latex-editor-mode { + display: inline-flex; + align-items: center; + gap: 2px; + background: var(--canvas-surface); + border: 1px solid var(--canvas-border); + border-radius: 5px; + padding: 2px; +} +.canvas-page-with-sidebar .latex-editor-mode button { + background: transparent; + border: none; + padding: 3px 10px; + border-radius: 3px; + font-family: inherit; + font-size: 11.5px; + color: var(--canvas-text-3); + display: inline-flex; + align-items: center; + gap: 5px; + cursor: pointer; +} +.canvas-page-with-sidebar .latex-editor-mode button.active { + background: var(--canvas-surface-3); + color: var(--canvas-text); +} +.canvas-page-with-sidebar .latex-editor-mode button:hover { color: var(--canvas-text); } -.inline-loading-spinner .spinning { - animation: spin 2s linear infinite; - width: 2rem; - height: 2rem; - color: var(--accent-primary); +.canvas-page-with-sidebar .latex-editor-panes { + display: grid; + min-height: 520px; + height: 70vh; + max-height: 800px; } +.canvas-page-with-sidebar .latex-editor-panes.mode-split { grid-template-columns: 1fr 1fr; } +.canvas-page-with-sidebar .latex-editor-panes.mode-source, +.canvas-page-with-sidebar .latex-editor-panes.mode-preview { grid-template-columns: 1fr; } -@keyframes spin { - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } +.canvas-page-with-sidebar .latex-source-pane { + border-right: 1px solid var(--canvas-border); + overflow: hidden; +} +.canvas-page-with-sidebar .latex-source-pane .cm-editor { + height: 100%; + font-size: 13px; +} +.canvas-page-with-sidebar .latex-source-pane .cm-scroller { + font-family: var(--canvas-mono); +} +.canvas-page-with-sidebar .latex-editor-panes.mode-preview .latex-source-pane, +.canvas-page-with-sidebar .latex-editor-panes.mode-source ~ .latex-preview-pane { + display: none; } -.insight-footer { +.canvas-page-with-sidebar .latex-preview-pane { + background: #fdfbf7; + color: #1a1a1a; + overflow-y: auto; + padding: 40px 56px; + font-family: 'Georgia', 'Times New Roman', serif; + position: relative; +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .latex-preview-pane { background: #fdfbf7; } +.canvas-page-with-sidebar .latex-preview-body { + max-width: 720px; + margin: 0 auto; +} +.canvas-page-with-sidebar .latex-preview-body h1, +.canvas-page-with-sidebar .latex-preview-body h2, +.canvas-page-with-sidebar .latex-preview-body h3 { + font-family: 'Georgia', serif; + color: #0f172a; +} +.canvas-page-with-sidebar .latex-preview-body p { + font-size: 14.5px; + line-height: 1.65; + color: #1f2937; + text-align: justify; +} +.canvas-page-with-sidebar .latex-preview-error { display: flex; - justify-content: space-between; - font-size: 0.8rem; - color: var(--text-tertiary); + gap: 10px; + padding: 10px 14px; + margin: 0 auto 14px; + max-width: 720px; + background: rgba(220, 38, 38, 0.08); + border-left: 3px solid var(--canvas-danger); + border-radius: 4px; + color: var(--canvas-danger); } -.insight-source { - font-weight: 500; - color: var(--accent-primary); +@media (max-width: 900px) { + .canvas-page-with-sidebar .latex-editor-panes.mode-split { grid-template-columns: 1fr; } + .canvas-page-with-sidebar .latex-editor-panes.mode-split .latex-source-pane { border-right: none; border-bottom: 1px solid var(--canvas-border); } } -.print-view .insight-source { - color: var(--text-secondary); +/* ----- Notion-style single-surface deliverable editor ----- */ +.canvas-page-with-sidebar .notion-deliverable-grid { + display: grid; + grid-template-columns: 200px minmax(0, 1fr) 240px; + gap: 28px; + align-items: start; +} +@media (max-width: 1280px) { + .canvas-page-with-sidebar .notion-deliverable-grid { grid-template-columns: 200px minmax(0, 1fr); } + .canvas-page-with-sidebar .notion-deliverable-grid > .deliverable-insertables { display: none; } +} +@media (max-width: 768px) { + .canvas-page-with-sidebar .notion-deliverable-grid { grid-template-columns: 1fr; } + .canvas-page-with-sidebar .notion-toc { display: none; } } -/* Loading State */ -.canvas-loading { +/* Subtle TOC */ +.canvas-page-with-sidebar .notion-toc { display: flex; flex-direction: column; + gap: 1px; + position: sticky; + top: 0; + padding: 4px 0; +} +.canvas-page-with-sidebar .notion-toc-label { + font-size: 11px; + color: var(--canvas-text-4); + font-weight: 500; + padding: 4px 6px 8px; + letter-spacing: 0; + text-transform: none; +} +.canvas-page-with-sidebar .notion-toc-link { + display: flex; align-items: center; + gap: 8px; + background: transparent; + border: none; + padding: 5px 8px; + border-radius: 4px; + cursor: pointer; + font-family: inherit; + font-size: 13px; + color: var(--canvas-text-2); + text-align: left; + transition: background .12s, color .12s; +} +.canvas-page-with-sidebar .notion-toc-link:hover { + background: rgba(255, 255, 255, 0.04); + color: var(--canvas-text); +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .notion-toc-link:hover { + background: rgba(0, 0, 0, 0.04); +} +.canvas-page-with-sidebar .notion-toc-link.active { + background: rgba(255, 255, 255, 0.06); + color: var(--canvas-text); + font-weight: 500; +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .notion-toc-link.active { + background: rgba(0, 0, 0, 0.05); +} +.canvas-page-with-sidebar .notion-toc-link-text { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.canvas-page-with-sidebar .notion-toc-link-count { + font-family: var(--canvas-mono); + font-size: 10px; + color: var(--canvas-text-4); +} + +/* The page itself */ +.canvas-page-with-sidebar .notion-page-wrap { + display: flex; justify-content: center; - height: 100vh; - background: var(--bg-gradient); - color: var(--text-primary); +} +.canvas-page-with-sidebar .notion-page { + background: var(--canvas-surface); + width: 100%; + max-width: 760px; + padding: 60px 80px; + border-radius: 4px; + color: var(--canvas-text); + font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .notion-page { + background: #ffffff; + border: 1px solid rgba(15, 15, 15, 0.06); + box-shadow: 0 1px 3px rgba(0,0,0,0.04); +} +.canvas-page-with-sidebar .notion-page.serif { + font-family: 'Georgia', 'Times New Roman', serif; +} +.canvas-page-with-sidebar .notion-page-wrap.paper .notion-page { + background: #fdfbf7; +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .notion-page-wrap.paper .notion-page { + background: #fdfbf7; +} +.canvas-page-with-sidebar .notion-page-title { + font-size: 40px; + font-weight: 700; + margin: 0 0 6px; + letter-spacing: -0.025em; + line-height: 1.15; + color: var(--canvas-text); +} +.canvas-page-with-sidebar .notion-page-meta { + font-size: 12.5px; + color: var(--canvas-text-3); + margin: 0 0 36px; } -.loading-spinner { - width: 3rem; - height: 3rem; - border: 3px solid var(--border-primary); - border-top: 3px solid var(--accent-primary); - border-radius: 50%; - animation: spin 1s linear infinite; - margin-bottom: 1rem; +.canvas-page-with-sidebar .notion-block { + margin: 24px 0; + position: relative; +} +.canvas-page-with-sidebar .notion-h2 { + font-size: 24px; + font-weight: 700; + letter-spacing: -0.015em; + margin: 0 0 8px; + color: var(--canvas-text); +} +.canvas-page-with-sidebar .notion-h2.serif { + font-size: 16px; + text-transform: uppercase; + letter-spacing: 0.06em; + font-weight: 700; +} +.canvas-page-with-sidebar .notion-hint { + font-size: 13.5px; + color: var(--canvas-text-4); + font-style: italic; + margin: 4px 0 6px; } -/* Print Styles */ -.print-footer { - position: fixed; - bottom: 0; - left: 0; +/* The textarea, styled as Notion body text */ +.canvas-page-with-sidebar .notion-text { + display: block; + width: 100%; + background: transparent; + border: none; + outline: none; + resize: none; + padding: 4px 0; + margin: 0; + font-family: inherit; + font-size: 16px; + line-height: 1.6; + color: var(--canvas-text); + overflow: hidden; + min-height: 28px; +} +.canvas-page-with-sidebar .notion-text.serif { + font-family: 'Georgia', 'Times New Roman', serif; + font-size: 14.5px; + line-height: 1.7; +} +.canvas-page-with-sidebar .notion-text::placeholder { + color: var(--canvas-text-4); +} +.canvas-page-with-sidebar .notion-text:focus { + background: rgba(0, 0, 0, 0.02); + border-radius: 3px; + padding: 4px 6px; + margin: 0 -6px; +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .notion-text:focus { + background: rgba(0, 0, 0, 0.025); +} + +/* Inline meta (check pills + word count) sits below the block */ +.canvas-page-with-sidebar .notion-block-meta { + margin-top: 10px; + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +/* Notion-style callout box for AI suggestions */ +.canvas-page-with-sidebar .notion-callout { + margin-top: 12px; + background: rgba(99, 102, 241, 0.06); + border-radius: 4px; + padding: 14px 16px; + display: flex; + gap: 10px; + font-size: 13.5px; + line-height: 1.55; + color: var(--canvas-text); +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .notion-callout { + background: rgba(99, 102, 241, 0.07); +} +.canvas-page-with-sidebar .notion-callout > svg { + color: var(--canvas-accent); + flex-shrink: 0; + margin-top: 2px; +} +.canvas-page-with-sidebar .notion-callout-label { + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--canvas-accent); + font-weight: 600; + margin-bottom: 2px; +} + +/* Export dropdown — uses native
for zero-state-management */ +.canvas-page-with-sidebar .canvas-export-menu summary { + list-style: none; +} +.canvas-page-with-sidebar .canvas-export-menu summary::-webkit-details-marker { + display: none; +} +.canvas-page-with-sidebar .canvas-export-menu[open] summary { + filter: brightness(1.05); +} +.canvas-page-with-sidebar .canvas-export-menu-list { + position: absolute; + top: calc(100% + 4px); right: 0; - background: white; - padding: 1rem; - text-align: center; - font-size: 0.8rem; - color: var(--text-secondary); - border-top: 1px solid var(--border-primary); + background: var(--canvas-surface); + border: 1px solid var(--canvas-border-2); + border-radius: 6px; + box-shadow: var(--canvas-shadow-lg); + padding: 4px; + min-width: 180px; + display: flex; + flex-direction: column; + z-index: 30; +} +.canvas-page-with-sidebar .canvas-export-menu-list button { + text-align: left; + background: transparent; + border: none; + padding: 7px 10px; + border-radius: 4px; + font-size: 13px; + cursor: pointer; + color: var(--canvas-text); +} +.canvas-page-with-sidebar .canvas-export-menu-list button:hover { + background: rgba(255, 255, 255, 0.05); +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .canvas-export-menu-list button:hover { + background: rgba(0, 0, 0, 0.05); } -@media print { - .canvas-page { - background: white !important; - color: black !important; - padding: 0.5rem !important; - } +/* Old paper/doc preview styles can stay below for the slides bottom-info case */ - .back-button, - .header-actions { - display: none !important; - } +/* Responsive breakpoints for paper/document mode */ +@media (max-width: 1280px) { + .canvas-page-with-sidebar .deliverable-grid { grid-template-columns: 160px minmax(0, 1fr); } + .canvas-page-with-sidebar .deliverable-grid > .deliverable-insertables { display: none; } +} +@media (max-width: 768px) { + .canvas-page-with-sidebar .deliverable-grid { grid-template-columns: 1fr; } + .canvas-page-with-sidebar .doc-page, + .canvas-page-with-sidebar .paper-page { padding: 32px 24px; } +} - .canvas-section { - page-break-inside: avoid; - margin-bottom: 1rem; - } +/* Insertable row in the right rail of Deliverables */ +.canvas-page-with-sidebar .canvas-insert-row { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 8px; + background: transparent; + border: none; + border-radius: 4px; + text-align: left; + cursor: pointer; + font-family: inherit; + color: var(--canvas-text); + transition: background .12s; +} +.canvas-page-with-sidebar .canvas-insert-row:hover { + background: rgba(255, 255, 255, 0.05); +} +.canvas-page-with-sidebar[data-canvas-theme="light"] .canvas-insert-row:hover { + background: rgba(0, 0, 0, 0.04); +} - .insight-card { - break-inside: avoid; - } +/* PhD Journey milestone rows */ +.canvas-page-with-sidebar .phd-milestone { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 8px 4px; + border-radius: 6px; + cursor: pointer; + transition: background .12s; +} +.canvas-page-with-sidebar .phd-milestone:hover { background: var(--canvas-surface-2); } +.canvas-page-with-sidebar .phd-milestone-check { + flex-shrink: 0; + width: 18px; + height: 18px; + border-radius: 4px; + border: 1.5px solid var(--canvas-text-4); + background: transparent; + cursor: pointer; + display: grid; + place-items: center; + margin-top: 2px; + color: #fff; + transition: background .12s, border-color .12s; +} +.canvas-page-with-sidebar .phd-milestone.milestone-in-progress .phd-milestone-check { + background: #3B82F6; + border-color: #3B82F6; +} +.canvas-page-with-sidebar .phd-milestone.milestone-completed .phd-milestone-check { + background: #10B981; + border-color: #10B981; +} +.canvas-page-with-sidebar .phd-milestone-body { flex: 1; min-width: 0; } +.canvas-page-with-sidebar .phd-milestone-label { + font-size: 13px; + color: var(--canvas-text); + font-weight: 500; + line-height: 1.4; +} +.canvas-page-with-sidebar .phd-milestone.milestone-completed .phd-milestone-label { + text-decoration: line-through; + color: var(--canvas-text-3); +} +.canvas-page-with-sidebar .phd-milestone-hint { + font-size: 11.5px; + color: var(--canvas-text-4); + font-style: italic; + margin-top: 2px; +} +.canvas-page-with-sidebar .phd-milestone-note { + font-size: 11.5px; + color: var(--canvas-text-2); + margin-top: 2px; } -/* Mobile Responsive */ -@media (max-width: 768px) { - .canvas-page { - padding: 1rem; - } +/* PhD Resources link rows */ +.canvas-page-with-sidebar .phd-resource-link { + display: flex; + flex-direction: column; + gap: 1px; + padding: 6px 8px; + background: transparent; + border-radius: 5px; + text-decoration: none; + color: var(--canvas-text); + transition: background .12s; +} +.canvas-page-with-sidebar .phd-resource-link:hover { + background: var(--canvas-surface-2); +} +.canvas-page-with-sidebar .phd-resource-name { + font-size: 12.5px; + font-weight: 600; + color: var(--canvas-accent); +} +.canvas-page-with-sidebar .phd-resource-desc { + font-size: 11px; + color: var(--canvas-text-3); +} - .canvas-header { - flex-direction: column; - gap: 1rem; - align-items: stretch; - } +/* "Coming soon" stub widget — roadmap-preview card, not a dead end */ +.canvas-page-with-sidebar .widget-stub { + flex: 1; + display: flex; + flex-direction: column; + gap: 6px; + padding: 18px 4px 8px; +} +.canvas-page-with-sidebar .widget-stub-icon { + width: 32px; + height: 32px; + border-radius: 7px; + background: var(--canvas-surface-2); + color: var(--canvas-text-3); + display: grid; + place-items: center; +} +.canvas-page-with-sidebar .widget-stub-tag { + font-size: 9.5px; + font-family: var(--canvas-mono); + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--canvas-text-4); + font-weight: 600; + margin-top: 8px; +} +.canvas-page-with-sidebar .widget-stub-title { + font-size: 14px; + font-weight: 600; + color: var(--canvas-text); +} +.canvas-page-with-sidebar .widget-stub-desc { + font-size: 12px; + color: var(--canvas-text-3); + line-height: 1.5; +} +.canvas-page-with-sidebar .widget-stub-plan { + margin: 8px 0 0; + padding-left: 18px; + font-size: 12px; + color: var(--canvas-text-3); + line-height: 1.6; +} +.canvas-page-with-sidebar .widget-stub-plan li::marker { color: var(--canvas-accent); } - .header-left { - justify-content: center; - } +/* Shared empty-state block inside widgets (Notion-style: airy, single CTA) */ +.canvas-page-with-sidebar .widget-empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 6px; + padding: 28px 16px; + text-align: center; + color: var(--canvas-text-3); + flex: 1; +} +.canvas-page-with-sidebar .widget-empty-icon { + width: 36px; + height: 36px; + border-radius: 8px; + background: var(--canvas-surface-2); + color: var(--canvas-text-3); + display: grid; + place-items: center; + margin-bottom: 4px; +} +.canvas-page-with-sidebar .widget-empty-title { + font-size: 13.5px; + font-weight: 600; + color: var(--canvas-text-2); +} +.canvas-page-with-sidebar .widget-empty-hint { + font-size: 12px; + color: var(--canvas-text-3); + max-width: 240px; + line-height: 1.5; +} +.canvas-page-with-sidebar .widget-empty-action { + margin-top: 8px; +} - .canvas-stats { - flex-direction: column; - gap: 1rem; - } +/* Cross-widget drop overlay — shows over the target widget body during drag */ +.canvas-page-with-sidebar .canvas-drop-overlay { + position: absolute; + inset: -4px; + background: var(--canvas-accent-glow); + border: 2px dashed var(--canvas-accent); + border-radius: 9px; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + font-size: 13px; + font-weight: 600; + color: var(--canvas-accent); + z-index: 5; + pointer-events: none; +} +.canvas-page-with-sidebar .canvas-drop-active { outline: none; } + +/* Compact markdown rendering inside note rows */ +.canvas-page-with-sidebar .canvas-md p { margin: 0 0 4px; } +.canvas-page-with-sidebar .canvas-md p:last-child { margin-bottom: 0; } +.canvas-page-with-sidebar .canvas-md h1, +.canvas-page-with-sidebar .canvas-md h2, +.canvas-page-with-sidebar .canvas-md h3 { font-size: 13px; margin: 4px 0 2px; font-weight: 600; } +.canvas-page-with-sidebar .canvas-md ul, +.canvas-page-with-sidebar .canvas-md ol { margin: 4px 0; padding-left: 18px; } +.canvas-page-with-sidebar .canvas-md li { margin-bottom: 2px; } +.canvas-page-with-sidebar .canvas-md code { + background: var(--canvas-surface-2); + padding: 1px 5px; + border-radius: 3px; + font-family: var(--canvas-mono); + font-size: 11.5px; +} +.canvas-page-with-sidebar .canvas-md pre { + background: var(--canvas-bg-2); + border: 1px solid var(--canvas-border); + border-radius: 5px; + padding: 8px 10px; + overflow-x: auto; + font-family: var(--canvas-mono); + font-size: 11.5px; + margin: 4px 0; +} +.canvas-page-with-sidebar .canvas-md pre code { background: none; padding: 0; } +.canvas-page-with-sidebar .canvas-md a { color: var(--canvas-accent); text-decoration: underline; text-underline-offset: 2px; } +.canvas-page-with-sidebar .canvas-md strong { color: var(--canvas-text); font-weight: 600; } +.canvas-page-with-sidebar .canvas-md blockquote { + border-left: 2px solid var(--canvas-accent); + margin: 4px 0; + padding: 2px 10px; + color: var(--canvas-text-2); +} + +/* Edit-in-place */ +.canvas-page-with-sidebar .inline-input { + background: transparent; + border: 1px solid var(--canvas-accent); + border-radius: 4px; + padding: 2px 5px; + font-size: inherit; + color: var(--canvas-text); + font-family: inherit; + width: 100%; + outline: none; +} - .insights-grid { - grid-template-columns: 1fr; +/* Print: just the Notion page itself, no chrome */ +@media print { + .canvas-sidebar, + .canvas-page-with-sidebar .floating-header, + .canvas-page-with-sidebar .canvas-tabs, + .canvas-page-with-sidebar .notion-toc, + .canvas-page-with-sidebar .deliverable-insertables, + .canvas-page-with-sidebar .canvas-shortcut-hint, + .canvas-page-with-sidebar .page-header > div:last-child, + .toast-stack, + .canvas-modal-backdrop { + display: none !important; } - - .canvas-title { - font-size: 1.5rem; + .canvas-page-with-sidebar, + .canvas-main-area, + .canvas-app-shell, + .canvas-content, + .canvas-page-with-sidebar .notion-deliverable-grid, + .canvas-page-with-sidebar .notion-page-wrap { + display: block !important; + overflow: visible !important; + margin: 0 !important; + padding: 0 !important; + background: #ffffff !important; + color: #000 !important; } - - .stat-item { - min-width: auto; + .canvas-page-with-sidebar .notion-page { + background: #ffffff !important; + border: none !important; + box-shadow: none !important; + padding: 0 !important; + max-width: none !important; } + .canvas-page-with-sidebar .notion-page-title { font-size: 28px; color: #000 !important; } + .canvas-page-with-sidebar .notion-h2 { color: #000 !important; } + .canvas-page-with-sidebar .notion-text { color: #000 !important; padding: 0 !important; margin: 0 !important; } + .canvas-page-with-sidebar .check-pill, + .canvas-page-with-sidebar .notion-callout { display: none !important; } } -/* Canvas Copyright Footer */ -.canvas-copyright-footer { - padding: 24px; +/* Floating shortcut hint — subtle, hides itself, hover-revealed */ +.canvas-shortcut-hint { + position: fixed; + bottom: 16px; + right: 20px; + z-index: 50; + display: flex; + gap: 14px; + padding: 8px 14px; + border-radius: 999px; + background: var(--canvas-surface); + border: 1px solid var(--canvas-border); + box-shadow: var(--canvas-shadow); + font-size: 11.5px; + color: var(--canvas-text-3); + font-family: 'Inter', sans-serif; + opacity: 0; + transform: translateY(8px); + transition: opacity 0.25s var(--canvas-ease), transform 0.25s var(--canvas-ease); + pointer-events: none; +} +.canvas-shortcut-hint.visible { + opacity: 1; + transform: none; + pointer-events: auto; +} +.canvas-shortcut-hint:not(.visible):hover { + opacity: 0.6; + transform: none; + pointer-events: auto; +} +.canvas-shortcut-hint kbd { + display: inline-block; + font-family: var(--canvas-mono); + font-size: 10px; + padding: 1px 5px; + border-radius: 3px; + background: var(--canvas-surface-2); + border: 1px solid var(--canvas-border-2); + color: var(--canvas-text-2); + margin-right: 2px; + min-width: 14px; text-align: center; - border-top: 1px solid var(--border-light, #e5e7eb); - margin-top: 32px; -} \ No newline at end of file +} +@media (max-width: 768px) { + .canvas-shortcut-hint { display: none; } +} + +/* Toast */ +.toast-stack { + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + z-index: 1000; + display: flex; + flex-direction: column; + gap: 8px; + pointer-events: none; +} +.toast-stack .toast { + background: #374151; + border: 1px solid #4B5563; + border-radius: 8px; + padding: 10px 14px; + font-size: 13px; + display: flex; + align-items: center; + gap: 10px; + box-shadow: 0 8px 24px rgba(0,0,0,0.4), 0 2px 6px rgba(0,0,0,0.3); + animation: canvas-toast-in .25s cubic-bezier(.22,.9,.3,1); + pointer-events: auto; + color: #F9FAFB; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; +} +body[data-canvas-theme="light"] .toast-stack .toast { + background: #FFFFFF; + color: #111827; + border-color: #D1D5DB; + box-shadow: 0 18px 48px rgba(20, 30, 50, 0.16); +} +.toast-stack .toast.success { border-left: 3px solid #10B981; } +.toast-stack .toast.critic { border-left: 3px solid #A78BFA; } +.toast-stack .toast.danger { border-left: 3px solid #DC2626; } +@keyframes canvas-toast-in { from { opacity: 0; transform: translateY(8px) } to { opacity: 1; transform: none } } + +/* Spinner */ +.canvas-page-with-sidebar .spinner, +.canvas-modal-backdrop .spinner { + width: 16px; + height: 16px; + border: 2px solid var(--canvas-surface-3); + border-top-color: var(--canvas-accent); + border-radius: 50%; + animation: canvas-spin .8s linear infinite; +} +.canvas-page-with-sidebar .spinner.critic, +.canvas-modal-backdrop .spinner.critic { border-top-color: var(--canvas-critic); } +@keyframes canvas-spin { to { transform: rotate(360deg) } } + +/* Scrollbar */ +.canvas-page-with-sidebar *::-webkit-scrollbar, +.canvas-modal-backdrop *::-webkit-scrollbar { width: 8px; height: 8px; } +.canvas-page-with-sidebar *::-webkit-scrollbar-track, +.canvas-modal-backdrop *::-webkit-scrollbar-track { background: transparent; } +.canvas-page-with-sidebar *::-webkit-scrollbar-thumb, +.canvas-modal-backdrop *::-webkit-scrollbar-thumb { background: var(--canvas-border-2); border-radius: 4px; } +.canvas-page-with-sidebar *::-webkit-scrollbar-thumb:hover, +.canvas-modal-backdrop *::-webkit-scrollbar-thumb:hover { background: var(--canvas-text-3); } + From 03f55f696ff05cc897feb10dbda4c7048b2d3d81 Mon Sep 17 00:00:00 2001 From: "Neon:ryan" Date: Tue, 12 May 2026 11:23:37 -0600 Subject: [PATCH 2/3] =?UTF-8?q?feat(canvas):=20PR=202=20=E2=80=94=20Worksp?= =?UTF-8?q?ace=20view=20+=2020+=20widgets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second of three PRs splitting the canvas-upgrade work. Stacks on PR 1 (canvas/InsightsUpdate) and adds the Workspace view with the full widget system. Documents (LaTeX editor + templates) follows in PR 3. Includes: - WorkspaceView with drag-and-drop reordering, S/M/L size cycling, preset starting points, and persistent layout in localStorage. - 20+ widgets: Bibliography, Kanban, Pomodoro, Writing tracker, Deadlines, Budget, Reading Queue, Notes, Habits, Goals, Meetings, Outline, Highlights, LaTeX, Calendar, Documenter, Activity, PhD Journey, PhD Resources. - Three "anti-yes-man" critic widgets — Reviewer 2, Devil's Advocate, Scope Realism — tuned to push back on the user's thinking rather than validate it (Daniel's framing). - Modal system: add-citation, add-task, add-deadline, log-words, confirm-remove, reading-paper, budget-item, note, habit, goal, meeting, palette, command palette, global search. - Cmd/Ctrl+K command palette and Cmd/Ctrl+/ global content search across all widget state. - Workspace tab in shared AppHeader. Documents view + LaTeX drafting lands in PR 3. --- .../components/canvas/CanvasCriticWidgets.js | 430 +++++ .../src/components/canvas/CanvasModals.js | 1096 +++++++++++ .../components/canvas/CanvasWelcomeTour.js | 18 +- .../src/components/canvas/CanvasWidgets.js | 1663 +++++++++++++++++ .../src/components/canvas/canvasData.js | 178 ++ phd-advisor-frontend/src/pages/CanvasPage.js | 417 ++++- 6 files changed, 3773 insertions(+), 29 deletions(-) create mode 100644 phd-advisor-frontend/src/components/canvas/CanvasCriticWidgets.js create mode 100644 phd-advisor-frontend/src/components/canvas/CanvasModals.js create mode 100644 phd-advisor-frontend/src/components/canvas/CanvasWidgets.js diff --git a/phd-advisor-frontend/src/components/canvas/CanvasCriticWidgets.js b/phd-advisor-frontend/src/components/canvas/CanvasCriticWidgets.js new file mode 100644 index 00000000..0f947963 --- /dev/null +++ b/phd-advisor-frontend/src/components/canvas/CanvasCriticWidgets.js @@ -0,0 +1,430 @@ +import React, { useState } from 'react'; +import Icon from './CanvasIcon'; + +const fireToast = (msg, kind = 'success') => + window.dispatchEvent(new CustomEvent('canvas-toast', { detail: { msg, kind } })); + +// Critic widgets are scripted in canvas; the *real* critique should happen in +// the main chat so message history lives in one place (per Daniel's review). +// "Open in chat" stashes a draft prompt + persona hint then asks CanvasPage to +// navigate to chat — the chat page can read `canvas-chat-handoff` from localStorage. +const handoffToChat = (persona, prompt, context = {}) => { + try { + localStorage.setItem('canvas-chat-handoff', JSON.stringify({ + at: Date.now(), persona, prompt, ...context, + })); + } catch { /* ignore */ } + window.dispatchEvent(new CustomEvent('canvas-open-in-chat', { detail: { persona, prompt } })); + fireToast(`Opening ${persona} in chat — full history will be there.`); +}; + +// ---------- Reviewer 2 widget ---------- +export function Reviewer2Widget({ state, setState, openModal }) { + return ( + <> +
+ "Paste a draft paragraph, abstract, or section. I'll respond as the most uncharitable but technically competent reviewer your work will ever see." +
+ {state.lastReview ? ( +
+ Last critique · {state.lastReview.severity}/10 severity +
Major: {state.lastReview.major}
+
+{state.lastReview.minorCount} minor issues, {state.lastReview.suggestionCount} suggestions
+
+ ) : ( +
+ No drafts reviewed yet. +
+ )} +
+ tone +
+ harsh +
+
+ + +
+ + ); +} + +// ---------- Devil's Advocate widget ---------- +export function DevilsAdvocateWidget({ state, setState, openModal }) { + return ( + <> +
Your claim
+
"{state.claim}"
+
+ Strongest counters · {state.counters.length} +
+
+ {state.counters.slice(0, 3).map((c, i) => ( +
+
{c.lbl}
+
{c.text}
+
+ ))} +
+
+ + +
+ + ); +} + +// ---------- Scope realism widget ---------- +export function ScopeRealismWidget({ state, openModal }) { + return ( +
+
+
+
{state.score.toFixed(1)}
+
+
Feasibility
+
{state.label}
+
+
+
+ target: {state.target} +
+
+
+ {state.factors.map(f => ( +
+ {f.label} + + {f.val} +
+ ))} +
+
+ + +
+
+ ); +} + +// =================================================================== +// Critic modals +// =================================================================== + +const REVIEW_TEMPLATES = [ + { + severity: 8, + major: 'The hypothesis is presented before its operationalization. You write that L2/3 spiking "encodes" prediction error without specifying what spike-pattern feature you will measure (rate? latency? variance?), what range of values would count as "encoding," or what would falsify the claim.', + minor: [ + 'Sample size of n=4 animals is described as "preliminary" without a power calculation or a stopping rule.', + 'GLM with history kernel is presented as the analysis but no mention of how its outputs map to the proposed predictive-coding interpretation.', + 'No engagement with adaptation as a confound — the obvious alternative explanation for any oddball-driven decrease in firing.', + 'Reference to "predictive coding" is loose. Rao & Ballard, Bastos, and Friston make different commitments. Which one are you testing?', + ], + suggestions: [ + 'Add a single sentence specifying the measurable signature you predict, with directionality.', + 'Either control for arousal/pupil or acknowledge it as a limit upfront.', + 'Add a stopping rule and target effect size before scaling beyond n=4.', + ], + }, + { + severity: 7, + major: 'You claim the GLM analyses are "consistent with" the hypothesis. This phrase is doing too much work. Consistency with PE encoding is also consistency with at least three alternative explanations (adaptation, arousal, attention). Without a positive test that PE encoding predicts but the alternatives do not, "consistent with" is unfalsifiable.', + minor: [ + 'Mouse V1 is justified by convention rather than by what makes it the right model for this question.', + 'No statement of what would change your mind.', + 'Figure-free abstract for an empirical claim is a red flag for reviewers.', + ], + suggestions: [ + 'Replace "consistent with" with a specific signed prediction the data either matches or doesn\'t.', + 'List 1-2 results that, if observed, would refute the hypothesis.', + ], + }, + { + severity: 9, + major: 'This reads like an introduction, not an abstract. There is no result. There is no number. The strongest claim is that your "preliminary analyses are consistent" with your hypothesis — which is the lowest possible bar in empirical neuroscience. If the actual finding is interesting, lead with the finding, not with the framing.', + minor: [ + 'Word "preliminary" appears three times in three sentences. Cut two.', + '"Oddball stimulus paradigm" is jargon-without-citation; one sentence of definition or one citation, not zero.', + 'No mention of layer-specificity, despite L2/3 being the core claim.', + ], + suggestions: [ + 'Lead sentence: "We find that " — even if the effect is small, name it.', + 'Cut "we hypothesize that" entirely. Hypotheses go in the intro of the paper, not the abstract.', + ], + }, +]; + +export function ReviewerModal({ data, onClose }) { + const [draft, setDraft] = useState(data.initial || ''); + const [running, setRunning] = useState(false); + const [review, setReview] = useState(null); + + const run = () => { + if (!draft.trim()) return; + setRunning(true); + setReview(null); + setTimeout(() => { + const idx = (draft.length + draft.split(' ').length) % REVIEW_TEMPLATES.length; + const r = REVIEW_TEMPLATES[idx]; + setReview(r); + setRunning(false); + }, 900); + }; + + const accept = () => { + if (!review) return; + data.onComplete({ + draft, + severity: review.severity, + major: review.major, + minorCount: review.minor.length, + suggestionCount: review.suggestions.length, + }); + fireToast('Critique saved · severity ' + review.severity + '/10', 'critic'); + onClose(); + }; + + return ( +
e.stopPropagation()}> +
+
+
+
Reviewer 2 Simulator
+
Paste a draft. Get the harshest competent peer review you'll ever read — before a real reviewer does.
+
+ +
+
+
+ +