Skip to main content

max / goingson

Email features: signatures, drafts, labels, notifications Add per-account email signatures (migration 041), real draft support with cc/bcc fields (042), local email labels/tags (043), and opt-in new-email notifications per account (044). Compose window: full rewrite with cc/bcc, attachments, signature insertion, draft save/restore, and autocomplete for contacts. Other changes: - Fix symlink canonicalization in plugin loader (skip dangling symlinks) - Plugin API: file size limit, context guard RAII cleanup - Email sync scheduler: exponential backoff on failures - Keyboard shortcuts: additional bindings - Audit review updated to Run 19 (2026-05-04) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author: Max J. <87768334+MaxJMath@users.noreply.github.com> · 2026-05-05 22:17 UTC
Commit: c3538346c08a018af4016860cfd623f0d1e98b09
Parent: 3a616ef
34 files changed, +2000 insertions, -217 deletions
@@ -60,6 +60,16 @@ pub struct Email {
60 60 /// JSON-serialized attachment metadata from IMAP sync.
61 61 #[serde(skip_serializing)]
62 62 pub attachment_meta: Option<String>,
63 + /// Local labels/tags for organization (JSON array).
64 + pub labels: Vec<String>,
65 + /// Whether this email is a draft (unsent compose state).
66 + pub is_draft: bool,
67 + /// CC recipients (stored for drafts, not used for received emails).
68 + pub cc_address: Option<String>,
69 + /// BCC recipients (stored for drafts).
70 + pub bcc_address: Option<String>,
71 + /// Email account to send from (stored for drafts).
72 + pub draft_account_id: Option<EmailAccountId>,
63 73 /// If snoozed, when to resurface.
64 74 pub snoozed_until: Option<DateTime<Utc>>,
65 75 /// Whether waiting for a reply.
@@ -187,6 +197,11 @@ mod tests {
187 197 imap_uid: None,
188 198 source_folder: None,
189 199 attachment_meta: None,
200 + labels: Vec::new(),
201 + is_draft: false,
202 + cc_address: None,
203 + bcc_address: None,
204 + draft_account_id: None,
190 205 snoozed_until: None,
191 206 waiting_for_response: false,
192 207 waiting_since: None,
@@ -159,6 +159,10 @@ pub struct EmailAccount {
159 159 pub jmap_account_id: Option<String>,
160 160 /// Auto-sync interval in minutes (None = disabled).
161 161 pub sync_interval_minutes: Option<i32>,
162 + /// Plain text email signature, appended to outbound emails.
163 + pub email_signature: Option<String>,
164 + /// Whether to show a system notification when new emails arrive (default: false).
165 + pub notify_new_emails: bool,
162 166 }
163 167
164 168 /// Per-folder IMAP sync state for incremental UID-based fetching.
@@ -33,6 +33,8 @@ struct EmailAccountRow {
33 33 pub jmap_session_url: Option<String>,
34 34 pub jmap_account_id: Option<String>,
35 35 pub sync_interval_minutes: Option<i32>,
36 + pub email_signature: Option<String>,
37 + pub notify_new_emails: i32,
36 38 }
37 39
38 40 impl TryFrom<EmailAccountRow> for EmailAccount {
@@ -61,6 +63,8 @@ impl TryFrom<EmailAccountRow> for EmailAccount {
61 63 jmap_session_url: row.jmap_session_url,
62 64 jmap_account_id: row.jmap_account_id,
63 65 sync_interval_minutes: row.sync_interval_minutes,
66 + email_signature: row.email_signature,
67 + notify_new_emails: row.notify_new_emails != 0,
64 68 })
65 69 }
66 70 }
@@ -82,7 +86,7 @@ impl SqliteEmailAccountRepository {
82 86 smtp_server, smtp_port, username, password, use_tls, last_sync_at,
83 87 created_at, archive_folder_name, auth_type, oauth2_access_token,
84 88 oauth2_refresh_token, oauth2_token_expires_at, jmap_session_url, jmap_account_id,
85 - sync_interval_minutes
89 + sync_interval_minutes, email_signature, notify_new_emails
86 90 FROM email_accounts
87 91 "#;
88 92 }
@@ -302,6 +306,26 @@ impl EmailAccountRepository for SqliteEmailAccountRepository {
302 306 }
303 307
304 308 #[tracing::instrument(skip_all)]
309 + async fn update_signature(&self, id: EmailAccountId, user_id: UserId, signature: Option<&str>) -> Result<Option<EmailAccount>> {
310 + let result = sqlx::query("UPDATE email_accounts SET email_signature = ? WHERE id = ? AND user_id = ?")
311 + .bind(signature)
312 + .bind(id.to_string())
313 + .bind(user_id.to_string())
314 + .execute(&self.pool).await.map_err(CoreError::database)?;
315 + if result.rows_affected() > 0 { self.get_by_id(id, user_id).await } else { Ok(None) }
316 + }
317 +
318 + #[tracing::instrument(skip_all)]
319 + async fn update_notify_new_emails(&self, id: EmailAccountId, user_id: UserId, enabled: bool) -> Result<Option<EmailAccount>> {
320 + let result = sqlx::query("UPDATE email_accounts SET notify_new_emails = ? WHERE id = ? AND user_id = ?")
321 + .bind(if enabled { 1 } else { 0 })
322 + .bind(id.to_string())
323 + .bind(user_id.to_string())
324 + .execute(&self.pool).await.map_err(CoreError::database)?;
325 + if result.rows_affected() > 0 { self.get_by_id(id, user_id).await } else { Ok(None) }
326 + }
327 +
328 + #[tracing::instrument(skip_all)]
305 329 async fn list_accounts_needing_sync(&self, user_id: UserId) -> Result<Vec<EmailAccount>> {
306 330 let query = format!(
307 331 "{} WHERE user_id = ? AND sync_interval_minutes IS NOT NULL \
@@ -12,7 +12,7 @@ use chrono::{DateTime, Utc};
12 12 use sqlx::SqlitePool;
13 13 use std::collections::HashSet;
14 14 use goingson_core::{
15 - CoreError, Email, EmailId, EmailRepository, EmailThread, NewEmail,
15 + CoreError, Email, EmailAccountId, EmailId, EmailRepository, EmailThread, NewEmail,
16 16 NewEmailWithTracking, ProjectId, Result, UserId,
17 17 };
18 18 use std::collections::HashMap;
@@ -23,7 +23,8 @@ use crate::utils::{format_datetime, format_datetime_now, format_datetime_opt, pa
23 23 const EMAIL_SELECT_COLUMNS: &str = r#"e.id, e.project_id, p.name as project_name, e.from_address, e.to_address,
24 24 e.subject, e.body, e.html_body, e.is_read, e.is_archived, e.received_at, e.message_id,
25 25 e.in_reply_to, e.thread_id, e.email_account_id, e.is_outgoing, e.imap_uid, e.source_folder,
26 - e.attachment_meta, e.snoozed_until, e.waiting_for_response, e.waiting_since, e.expected_response_date"#;
26 + e.attachment_meta, e.labels, e.is_draft, e.cc_address, e.bcc_address, e.draft_account_id,
27 + e.snoozed_until, e.waiting_for_response, e.waiting_since, e.expected_response_date"#;
27 28
28 29 #[derive(Debug, Clone, sqlx::FromRow)]
29 30 struct EmailRow {
@@ -46,6 +47,11 @@ struct EmailRow {
46 47 pub imap_uid: Option<i64>,
47 48 pub source_folder: Option<String>,
48 49 pub attachment_meta: Option<String>,
50 + pub labels: String,
51 + pub is_draft: i32,
52 + pub cc_address: Option<String>,
53 + pub bcc_address: Option<String>,
54 + pub draft_account_id: Option<String>,
49 55 pub snoozed_until: Option<String>,
50 56 pub waiting_for_response: i32,
51 57 pub waiting_since: Option<String>,
@@ -76,6 +82,11 @@ impl TryFrom<EmailRow> for Email {
76 82 imap_uid: row.imap_uid,
77 83 source_folder: row.source_folder,
78 84 attachment_meta: row.attachment_meta,
85 + labels: serde_json::from_str(&row.labels).unwrap_or_default(),
86 + is_draft: row.is_draft != 0,
87 + cc_address: row.cc_address,
88 + bcc_address: row.bcc_address,
89 + draft_account_id: parse_uuid_opt(row.draft_account_id.as_deref())?.map(Into::into),
79 90 snoozed_until: row.snoozed_until.as_ref().map(|s| parse_datetime(s)).transpose()?,
80 91 waiting_for_response: row.waiting_for_response != 0,
81 92 waiting_since: row.waiting_since.as_ref().map(|s| parse_datetime(s)).transpose()?,
@@ -102,7 +113,7 @@ impl EmailRepository for SqliteEmailRepository {
102 113 async fn list_all(&self, user_id: UserId, include_archived: bool) -> Result<Vec<Email>> {
103 114 let archived_filter = if include_archived { "" } else { "AND e.is_archived = 0" };
104 115 let query = format!(
105 - "SELECT {} FROM emails e LEFT JOIN projects p ON e.project_id = p.id AND p.user_id = ? WHERE e.user_id = ? {} ORDER BY e.received_at DESC",
116 + "SELECT {} FROM emails e LEFT JOIN projects p ON e.project_id = p.id AND p.user_id = ? WHERE e.user_id = ? AND e.is_draft = 0 {} ORDER BY e.received_at DESC",
106 117 EMAIL_SELECT_COLUMNS, archived_filter
107 118 );
108 119 let rows = sqlx::query_as::<_, EmailRow>(&query).bind(user_id.to_string()).bind(user_id.to_string()).fetch_all(&self.pool).await.map_err(CoreError::database)?;
@@ -110,22 +121,23 @@ impl EmailRepository for SqliteEmailRepository {
110 121 }
111 122
112 123 #[tracing::instrument(skip_all)]
113 - async fn list_threaded(&self, user_id: UserId, include_archived: bool, offset: Option<i64>, limit: Option<i64>) -> Result<(Vec<EmailThread>, i64)> {
124 + async fn list_threaded(&self, user_id: UserId, include_archived: bool, offset: Option<i64>, limit: Option<i64>, folder: Option<&str>, label: Option<&str>) -> Result<(Vec<EmailThread>, i64)> {
114 125 let uid = user_id.to_string();
115 126 let archived_filter = if include_archived { "" } else { "AND e.is_archived = 0" };
127 + let folder_filter = folder.map(|_| "AND e.source_folder = ?").unwrap_or("");
128 + let label_filter = label.map(|_| "AND EXISTS (SELECT 1 FROM json_each(e.labels) j WHERE j.value = ?)").unwrap_or("");
116 129 let offset_val = offset.unwrap_or(0);
117 130 let limit_val = limit.unwrap_or(50);
118 131
119 132 // Query 1: Get total thread count
120 133 let count_sql = format!(
121 - "SELECT COUNT(DISTINCT COALESCE(e.thread_id, e.id)) FROM emails e WHERE e.user_id = ? {}",
122 - archived_filter
134 + "SELECT COUNT(DISTINCT COALESCE(e.thread_id, e.id)) FROM emails e WHERE e.user_id = ? AND e.is_draft = 0 {} {} {}",
135 + archived_filter, folder_filter, label_filter
123 136 );
124 - let (total,): (i64,) = sqlx::query_as(&count_sql)
125 - .bind(&uid)
126 - .fetch_one(&self.pool)
127 - .await
128 - .map_err(CoreError::database)?;
137 + let mut count_q = sqlx::query_as::<_, (i64,)>(&count_sql).bind(&uid);
138 + if let Some(f) = folder { count_q = count_q.bind(f); }
139 + if let Some(l) = label { count_q = count_q.bind(l); }
140 + let (total,) = count_q.fetch_one(&self.pool).await.map_err(CoreError::database)?;
129 141
130 142 if total == 0 {
131 143 return Ok((vec![], 0));
@@ -150,19 +162,24 @@ impl EmailRepository for SqliteEmailRepository {
150 162 SUM(CASE WHEN e.is_read = 0 THEN 1 ELSE 0 END) AS unread_count,
151 163 (SELECT e2.id FROM emails e2
152 164 WHERE COALESCE(e2.thread_id, e2.id) = COALESCE(e.thread_id, e.id)
153 - AND e2.user_id = ? {}
165 + AND e2.user_id = ? AND e2.is_draft = 0 {} {} {}
154 166 ORDER BY e2.received_at DESC LIMIT 1) AS latest_email_id
155 167 FROM emails e
156 - WHERE e.user_id = ? {}
168 + WHERE e.user_id = ? AND e.is_draft = 0 {} {} {}
157 169 GROUP BY COALESCE(e.thread_id, e.id)
158 170 ORDER BY latest_received_at DESC
159 171 LIMIT ? OFFSET ?"#,
160 - archived_filter, archived_filter
172 + archived_filter, folder_filter, label_filter,
173 + archived_filter, folder_filter, label_filter,
161 174 );
162 175
163 - let summaries = sqlx::query_as::<_, ThreadSummary>(&summary_sql)
164 - .bind(&uid)
165 - .bind(&uid)
176 + let mut summary_q = sqlx::query_as::<_, ThreadSummary>(&summary_sql).bind(&uid);
177 + if let Some(f) = folder { summary_q = summary_q.bind(f); }
178 + if let Some(l) = label { summary_q = summary_q.bind(l); }
179 + summary_q = summary_q.bind(&uid);
180 + if let Some(f) = folder { summary_q = summary_q.bind(f); }
181 + if let Some(l) = label { summary_q = summary_q.bind(l); }
182 + let summaries = summary_q
166 183 .bind(limit_val)
167 184 .bind(offset_val)
168 185 .fetch_all(&self.pool)
@@ -491,6 +508,62 @@ impl EmailRepository for SqliteEmailRepository {
491 508 }
492 509
493 510 #[tracing::instrument(skip_all)]
511 + async fn list_drafts(&self, user_id: UserId) -> Result<Vec<Email>> {
512 + let query = format!(
513 + "SELECT {} FROM emails e LEFT JOIN projects p ON e.project_id = p.id AND p.user_id = ? WHERE e.user_id = ? AND e.is_draft = 1 ORDER BY e.received_at DESC",
514 + EMAIL_SELECT_COLUMNS
515 + );
516 + let rows = sqlx::query_as::<_, EmailRow>(&query)
517 + .bind(user_id.to_string())
518 + .bind(user_id.to_string())
519 + .fetch_all(&self.pool)
520 + .await
521 + .map_err(CoreError::database)?;
522 + rows.into_iter().map(Email::try_from).collect()
523 + }
524 +
525 + #[tracing::instrument(skip_all)]
526 + async fn save_draft(&self, id: EmailId, user_id: UserId, from: &str, to: &str, cc: Option<&str>, bcc: Option<&str>, subject: &str, body: &str, account_id: Option<EmailAccountId>, in_reply_to: Option<&str>, _references: Option<&str>, thread_id: Option<&str>) -> Result<Email> {
527 + let now = format_datetime_now();
528 + let account_id_str = account_id.map(|a: EmailAccountId| a.to_string());
529 +
530 + // Upsert: update if exists, insert if not
531 + sqlx::query(r#"
532 + INSERT INTO emails (id, user_id, from_address, to_address, cc_address, bcc_address, subject, body,
533 + is_read, is_archived, is_draft, is_outgoing, received_at, draft_account_id, in_reply_to, thread_id)
534 + VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, 0, 1, 1, ?, ?, ?, ?)
535 + ON CONFLICT(id) DO UPDATE SET
536 + from_address = excluded.from_address,
537 + to_address = excluded.to_address,
538 + cc_address = excluded.cc_address,
539 + bcc_address = excluded.bcc_address,
540 + subject = excluded.subject,
541 + body = excluded.body,
542 + received_at = excluded.received_at,
543 + draft_account_id = excluded.draft_account_id,
544 + in_reply_to = excluded.in_reply_to,
545 + thread_id = excluded.thread_id
546 + "#)
547 + .bind(id.to_string())
548 + .bind(user_id.to_string())
549 + .bind(from)
550 + .bind(to)
551 + .bind(cc)
552 + .bind(bcc)
553 + .bind(subject)
554 + .bind(body)
555 + .bind(&now)
556 + .bind(&account_id_str)
557 + .bind(in_reply_to)
558 + .bind(thread_id)
559 + .execute(&self.pool)
560 + .await
561 + .map_err(CoreError::database)?;
562 +
563 + self.get_by_id(id, user_id).await?.ok_or_else(|| CoreError::internal("Failed to retrieve saved draft"))
564 + }
565 +
566 + #[tracing::instrument(skip_all)]
494 567 async fn get_by_message_id(&self, user_id: UserId, message_id: &str) -> Result<Option<Email>> {
495 568 let query = format!(
496 569 "SELECT {} FROM emails e LEFT JOIN projects p ON e.project_id = p.id AND p.user_id = ? WHERE e.user_id = ? AND e.message_id = ?",
@@ -505,4 +578,42 @@ impl EmailRepository for SqliteEmailRepository {
505 578 .map_err(CoreError::database)?;
506 579 row.map(Email::try_from).transpose()
507 580 }
581 +
582 + #[tracing::instrument(skip_all)]
583 + async fn update_labels(&self, id: EmailId, user_id: UserId, labels: &[String]) -> Result<Option<Email>> {
584 + let labels_json = serde_json::to_string(labels).unwrap_or_else(|_| "[]".to_string());
585 + let result = sqlx::query("UPDATE emails SET labels = ? WHERE id = ? AND user_id = ?")
586 + .bind(&labels_json)
587 + .bind(id.to_string())
588 + .bind(user_id.to_string())
589 + .execute(&self.pool)
590 + .await
591 + .map_err(CoreError::database)?;
592 + if result.rows_affected() > 0 { self.get_by_id(id, user_id).await } else { Ok(None) }
593 + }
594 +
595 + #[tracing::instrument(skip_all)]
596 + async fn list_folders(&self, user_id: UserId) -> Result<Vec<String>> {
597 + let rows: Vec<(String,)> = sqlx::query_as(
598 + "SELECT DISTINCT source_folder FROM emails WHERE user_id = ? AND source_folder IS NOT NULL AND is_draft = 0 ORDER BY source_folder ASC"
599 + )
600 + .bind(user_id.to_string())
601 + .fetch_all(&self.pool)
602 + .await
603 + .map_err(CoreError::database)?;
604 + Ok(rows.into_iter().map(|r| r.0).collect())
605 + }
606 +
607 + #[tracing::instrument(skip_all)]
608 + async fn list_labels(&self, user_id: UserId) -> Result<Vec<String>> {
609 + // Extract all unique labels across all emails via JSON parsing
610 + let rows: Vec<(String,)> = sqlx::query_as(
611 + "SELECT DISTINCT j.value FROM emails e, json_each(e.labels) j WHERE e.user_id = ? AND e.is_draft = 0 ORDER BY j.value ASC"
612 + )
613 + .bind(user_id.to_string())
614 + .fetch_all(&self.pool)
615 + .await
616 + .map_err(CoreError::database)?;
617 + Ok(rows.into_iter().map(|r| r.0).collect())
618 + }
508 619 }
@@ -186,38 +186,41 @@ mod goingson_api {
186 186 .flexible(true)
187 187 .from_reader(content.as_bytes());
188 188
189 - let headers: Vec<String> = if has_header {
190 - reader
189 + // Single-pass: determine headers, then iterate records from the same reader.
190 + // For has_header=true, headers() consumes the first row and caches it.
191 + // For has_header=false, peek the first record for column count, then
192 + // process it as data along with the rest.
193 + let (headers, first_record) = if has_header {
194 + let hdrs: Vec<String> = reader
191 195 .headers()
192 196 .map_err(|e| -> Box<EvalAltResult> { format!("Failed to read CSV headers: {}", e).into() })?
193 197 .iter()
194 198 .map(|s| s.to_string())
195 - .collect()
199 + .collect();
200 + (hdrs, None)
196 201 } else {
197 - // Generate column names like col_0, col_1, etc.
198 - let first_record = reader.records().next();
199 - if let Some(Ok(record)) = first_record {
200 - (0..record.len()).map(|i| format!("col_{}", i)).collect()
201 - } else {
202 - Vec::new()
202 + // Read first record to determine column count
203 + let first = reader.records().next();
204 + match first {
205 + Some(Ok(record)) => {
206 + let hdrs: Vec<String> = (0..record.len()).map(|i| format!("col_{}", i)).collect();
207 + (hdrs, Some(record))
208 + }
209 + _ => (Vec::new(), None),
203 210 }
204 211 };
205 212
206 - // Reset reader if we peeked for column count
207 - let mut reader = csv::ReaderBuilder::new()
208 - .has_headers(has_header)
209 - .delimiter(delimiter as u8)
210 - .flexible(true)
211 - .from_reader(content.as_bytes());
212 -
213 - if has_header {
214 - // Skip header row
215 - let _ = reader.headers();
216 - }
217 -
218 213 let mut rows = rhai::Array::new();
219 214
220 - for result in reader.records() {
215 + // Process the first record if we consumed it for column detection
216 + let records_iter: Box<dyn Iterator<Item = csv::Result<csv::StringRecord>>> =
217 + if let Some(first) = first_record {
218 + Box::new(std::iter::once(Ok(first)).chain(reader.records()))
219 + } else {
220 + Box::new(reader.records())
221 + };
222 +
223 + for result in records_iter {
221 224 let record = result.map_err(|e| -> Box<EvalAltResult> { format!("CSV parse error: {}", e).into() })?;
222 225
223 226 let mut row_map = Map::new();
@@ -114,9 +114,19 @@ impl PluginLoader {
114 114 let plugin_path = if path.is_symlink() {
115 115 let target = std::fs::read_link(&path)
116 116 .map_err(|e| PluginError::FileError(format!("Failed to read symlink: {}", e)))?;
117 - // Resolve to absolute and verify target is within available/
118 - let canonical = std::fs::canonicalize(&target).or_else(|_| std::fs::canonicalize(&path))
119 - .map_err(|e| PluginError::FileError(format!("Failed to resolve symlink: {}", e)))?;
117 + // Resolve the symlink target to its canonical path.
118 + // If the target doesn't exist (dangling symlink), skip it entirely
119 + // to prevent sandbox escape via delayed target creation.
120 + let canonical = match std::fs::canonicalize(&target) {
121 + Ok(p) => p,
122 + Err(_) => {
123 + tracing::warn!(
124 + "Skipping symlink '{}': target '{}' does not exist",
125 + path.display(), target.display()
126 + );
127 + continue;
128 + }
129 + };
120 130 let available_canonical = std::fs::canonicalize(self.available_dir())
121 131 .unwrap_or_else(|_| self.available_dir());
122 132 if !canonical.starts_with(&available_canonical) {
@@ -4,7 +4,21 @@ Full chronological audit log. See [audit_review.md](./audit_review.md) for curre
4 4
5 5 ## Changes Since Last Audit
6 6
7 - **Previous audit:** 2026-03-16 (seventh audit, Run 6)
7 + **Previous audit:** 2026-04-22 (Run 15 corrected)
8 +
9 + ### Run 19 (2026-05-04, cross-project)
10 + - **Test count:** 765 (--workspace). 0 clippy warnings. 0 failures.
11 + - **Grade:** A (maintained). v0.3.1. ~68,800 LOC.
12 + - **New code:** Email signatures (041), drafts (042), labels (043), notifications (044) — uncommitted WIP.
13 + - **Cold spots found:** 6 (all low severity). Plugin loader symlink issue (medium, local-only risk).
14 + - **Mandatory surprise:** Symlink canonicalization fallback in plugin loader allows dangling symlink to bypass sandbox check. Fix: remove `.or_else` fallback.
15 + - **All previous items resolved.** No regressions.
16 + - **Methodology note:** Deeper module-level audit with parallel agents. More cold spots surfaced vs previous runs (broader methodology, not quality regression).
17 +
18 + ### Thirteenth audit (2026-04-18, Run 15 cross-project, corrected 2026-04-22)
19 + - **Test count:** 778 (--workspace). 0 clippy warnings. Grade A.
20 + - **False finding corrected:** Test "regression" was due to running without --workspace flag.
21 + - **Items resolved:** FK migration (non-issue), indexes added (040), observability expanded (435 annotations).
8 22
9 23 ### Eleventh audit (2026-03-28, Run 12 cross-project)
10 24 - **Test count:** ~734 (686 Rust + 48 JS). 0 clippy warnings. 0 failures.
@@ -1,7 +1,7 @@
1 1 # GoingsOn -- Audit Review
2 2
3 - **Last audited:** 2026-04-18 (thirteenth audit, Run 15 cross-project)
4 - **Previous audit:** 2026-04-15 (twelfth audit, Run 14 cross-project)
3 + **Last audited:** 2026-05-04 (Run 19 cross-project)
4 + **Previous audit:** 2026-04-18 (Run 15, corrected 2026-04-22)
5 5 **Auditor:** Claude Opus 4.6 (automated codebase audit)
6 6 **Scope:** Full workspace (`crates/core`, `crates/db-sqlite`, `crates/plugin-runtime`, `src-tauri`, frontend JS, migrations)
7 7
@@ -9,7 +9,7 @@
9 9
10 10 ## Overall Grade: A
11 11
12 - Run 15 cross-project audit (updated 2026-04-22). 778 tests (all pass, `--workspace`). Zero clippy warnings. v0.3.1. ~64,357 LOC. Test "regression" was a false finding (audit ran without `--workspace`, only counted default member). FK migration is one-time and already completed. Missing indexes added. Observability expanded to 435 instrument annotations.
12 + 765 tests (all pass, `--workspace`). Zero clippy warnings. v0.3.1. ~68,800 LOC (48.5K Rust + 18.4K JS + 1.9K SQL). All SQL parameterized. Strong type safety. Clean 4-crate architecture maintained. Uncommitted work in progress (email signatures, drafts, labels, notifications — 28 modified, 5 untracked files).
13 13
14 14 ---
15 15
@@ -17,169 +17,150 @@ Run 15 cross-project audit (updated 2026-04-22). 778 tests (all pass, `--workspa
17 17
18 18 | Dimension | Grade | Notes |
19 19 |-----------|:-----:|-------|
20 - | **Code Quality** | B+ | Zero clippy warnings. ~50-60 non-test `.unwrap()`/`.expect()` in production code. Consistent `CoreError`/`ApiError` chain. |
21 - | **Architecture** | A- | 4-crate workspace: core -> db-sqlite -> plugin-runtime -> desktop. Repository trait pattern. FK migration risk in migrations.rs:44-89. |
22 - | **Testing** | A+ | 778 tests (all pass, `--workspace`). Previous "regression" was false — audit ran without `--workspace`. Coverage across all layers maintained. |
23 - | **Security** | A- | All SQL parameterized. Sync engine whitelists. FTS5 escaped. Frontend: 200+ `escapeHtml()` calls. OS keychain. OAuth2 + PKCE. Plugin sandbox. |
24 - | **Performance** | A- | Virtual scrolling, FTS5, batch sync. Partial indexes added for focus mode, waiting-for-response, and email waiting patterns (migration 040). |
25 - | **Documentation** | A | Module-level `//!` docs on every source file. `///` on all public types/methods. JSDoc. 3,621+ doc comments. ARCHITECTURE.md and STYLEGUIDE.md current. |
26 - | **Dependencies** | A | All deps pinned at workspace level. Core: 8 deps. Desktop: 30+. |
27 - | **Frontend** | B+ | 39 IIFE modules with `'use strict'`. Centralized `AppStateManager`. 48 automated JS tests. Some performance gaps in rendering. |
28 - | **Type Safety** | A | 11 entity ID newtypes via macro. Typed enums for filters/sort. `CoreError` -> `ApiError` conversion chain. |
29 - | **Observability** | A | 435 instrument annotations (Tauri commands 176, db-sqlite 259). Structured `tracing` with EnvFilter. Full coverage across all layers. |
30 - | **Concurrency** | A- | SQLite serializes writes. `Arc<dyn Repository>`. Background tasks via `tokio::spawn`. CancellationToken + AtomicBool for shutdown. |
31 - | **Resilience** | A- | Crash-safe sync cursor. `applying_remote` cleared on error. Explicit timeouts on HTTP clients. FK migration is one-time and completed. |
20 + | **Code Quality** | A- | Zero clippy warnings. ~662 unwrap() across workspace (majority in tests). Consistent `CoreError`/`ApiError` chain. |
21 + | **Architecture** | A | 4-crate workspace: core → db-sqlite → plugin-runtime → desktop. Repository trait pattern. No layer violations. |
22 + | **Testing** | A | 765 tests (all pass, `--workspace`). Sync service 1608 LOC of tests. Strong coverage across all layers. |
23 + | **Security** | A | All SQL parameterized (whitelist-validated table names for dynamic SQL). Frontend: systematic `escapeHtml()`/`escapeAttr()`. OS keychain. OAuth2 + PKCE. Plugin sandbox. One symlink canonicalization issue in plugin loader (low risk — desktop app). |
24 + | **Performance** | A- | Virtual scrolling, FTS5, batch sync, partial indexes. Plugin CSV parser has double-read inefficiency. |
25 + | **Documentation** | A | Module-level `//!` docs on all 4 lib.rs files. ARCHITECTURE.md and STYLEGUIDE.md current. |
26 + | **Dependencies** | A | All deps at latest stable. No outdated crates flagged. |
27 + | **Frontend** | A- | 18.4K LOC across 30+ IIFE modules. Systematic XSS prevention. State management via pub/sub. emails.js at 1212 LOC is large but well-structured. |
28 + | **Type Safety** | A | 11 entity ID newtypes. Typed enums for all domain values. Exhaustive matching. Builder patterns for complex construction. |
29 + | **Observability** | A | 435+ instrument annotations. Structured tracing with EnvFilter. Full coverage on commands and repositories. |
30 + | **Concurrency** | A | parking_lot + TokioMutex. Per-account sync locks. CancellationToken for shutdown. No deadlock risks. |
31 + | **Resilience** | A- | Crash-safe sync cursor. `applying_remote` suppression. HTTP timeouts. No exponential backoff on email sync failures. |
32 32 | **API Consistency** | A | Every command returns `Result<T, ApiError>`. Consistent pagination. Pre-computed display fields. |
33 - | **Codebase Size** | A- | ~64,357 LOC implementing 20+ feature domains. |
33 + | **Migration Safety** | A | 44 migrations, all additive. No destructive operations in recent migrations (041-044). |
34 + | **Codebase Size** | A- | ~68,800 LOC implementing 20+ feature domains. Some duplication in external_sync parsers. |
34 35
35 36 ---
36 37
37 38 ## Module Heatmap
38 39
39 - | Module | Code | Arch | Test | Security | Perf | Docs | Deps | Frontend |
40 - |--------|:----:|:----:|:----:|:--------:|:----:|:----:|:----:|:--------:|
41 - | **goingson-core** | A | A+ | A | n/a | A | A | A+ | n/a |
42 - | **goingson-db-sqlite** | A | A | A- | A | B+ | A- | n/a | n/a |
43 - | **goingson-desktop** | A- | A | A- | A | B | A | B+ | n/a |
44 - | **goingson-plugin-runtime** | A- | A | B+ | A | A | A- | n/a | n/a |
45 - | **JS Frontend** | A- | A | A- | A | B+ | B+ | n/a | A |
40 + | Module | Code | Arch | Test | Security | Perf | Size |
41 + |--------|:----:|:----:|:----:|:--------:|:----:|:----:|
42 + | **goingson-core** | A | A+ | A | A | A | A- |
43 + | **goingson-db-sqlite** | A | A | A | A+ | A | A |
44 + | **goingson-plugin-runtime** | B+ | A | A | A- | B | C |
45 + | **goingson-desktop (commands)** | A | A | B+ | A | A- | B+ |
46 + | **goingson-desktop (email/oauth)** | A- | A | B+ | A+ | A- | B+ |
47 + | **goingson-desktop (sync_service)** | A | A | A+ | A | A | B |
48 + | **goingson-desktop (jmap)** | A | A | B+ | A | A | B+ |
49 + | **goingson-desktop (external_sync)** | A- | B+ | B | A | A | C+ |
50 + | **JS Frontend** | A- | A | — | A | A- | B+ |
46 51
47 52 ### Cold Spots
48 53
49 - All previous cold spots resolved (JMAP 73 tests, OAuth 59 tests, plugin registry 32 tests).
50 -
51 - New cold spots (all resolved 2026-04-22):
52 - 1. ~~**FK migration risk (migrations.rs:44-89)**~~ -- One-time migration already completed for all users. `run_migrations` has FK safety net.
53 - 2. ~~**Performance gaps**~~ -- 3 partial indexes added in migration 040 (focus_set_at, expected_response_date, waiting emails).
54 - 3. ~~**Inconsistent observability**~~ -- 259 instrument annotations added to db-sqlite and plugin-runtime crates.
54 + | Module | Issue | Grade | Severity |
55 + |--------|-------|:-----:|:--------:|
56 + | **plugin-runtime/api.rs** (1103 LOC) | Double CSV reader instantiation; excessive cloning on hot path | C (perf) | Low |
57 + | **plugin-runtime/registry.rs** (1243 LOC) | Bloated test helpers; 1243 LOC for ~200 LOC of logic | C (size) | Low |
58 + | **plugin-runtime/loader.rs:118** | Symlink canonicalization fallback allows sandbox escape with dangling symlinks | B (security) | Medium |
59 + | **external_sync/ical.rs** | Minimal test coverage; parser logic shared with vcard.rs | B (test) | Low |
60 + | **commands/export.rs** | Sequential list_all calls (could parallelize) | B+ (perf) | Very Low |
61 + | **emails.js** (1212 LOC) | Approaching split threshold; rendering mixed with logic | B+ (size) | Low |
55 62
56 63 ---
57 64
58 65 ## Mandatory Surprise
59 66
60 - **FK constraint migration risk -- PRAGMA foreign_keys = OFF without crash protection.**
61 -
62 - In `migrations.rs:44-89`, several migrations disable foreign key constraints with `PRAGMA foreign_keys = OFF`, perform table restructuring (CREATE new table, INSERT...SELECT, DROP old, ALTER TABLE RENAME), then re-enable constraints. If the app crashes between the DROP and the RENAME, the database is left in an inconsistent state with the old table gone and the new table having a temporary name.
67 + **Symlink canonicalization vulnerability in plugin loader (loader.rs:118)**
63 68
64 - SQLite's recommended approach for this pattern is to wrap the entire sequence in a transaction, but `PRAGMA foreign_keys` cannot be changed inside a transaction. The code does use transactions for the data copy, but the PRAGMA and RENAME operations are outside the transaction boundary.
69 + ```rust
70 + let canonical = std::fs::canonicalize(&target)
71 + .or_else(|_| std::fs::canonicalize(&path))
72 + ```
65 73
66 - **Verdict:** Medium severity. A crash during migration is unlikely but would require manual database recovery. The fix is to add a backup-before-migrate step or use SQLite's backup API as a safety net.
74 + If the symlink target doesn't exist (dangling symlink), `canonicalize(target)` fails and the fallback canonicalizes the symlink entry itself (`enabled/xyz`). The entry is within the `available/` directory, so it passes the `starts_with` check — but the actual target, once created, could be anywhere on the filesystem.
67 75
68 - ### Previous Surprise
76 + **Attack scenario:** Create `enabled/evil → /etc/passwd` as dangling symlink. Canonicalize(target) fails → falls back to canonicalize(path), which passes validation. Then create the target at the original path.
69 77
70 - **The `Validate` trait was entirely dead code.** Resolution (2026-03-13): Validate trait now wired into the command layer. All validation rules enforced in production. Resolved.
78 + **Risk:** Medium for security, but low blast radius — this is a desktop app where the attacker would already need local filesystem access. Fix: remove the `.or_else` fallback; skip symlinks whose target doesn't resolve.
71 79
72 80 ---
73 81
74 - ## Strengths
75 -
76 - ### 1. Exemplary layered architecture
77 -
78 - Four crates with strictly acyclic dependencies: core (zero I/O) -> db-sqlite (persistence) -> plugin-runtime (Rhai sandbox) -> desktop (Tauri wrapper). Repository trait pattern. Pre-computed response fields eliminate JS duplication. No layer violations.
82 + ## Previous Action Item Verification
79 83
80 - ### 2. Comprehensive SQL injection and XSS prevention
84 + | Item | Status |
85 + |------|--------|
86 + | FK migration crash protection | Non-issue (one-time, completed) |
87 + | Performance gaps (missing indexes) | Fixed (migration 040) |
88 + | Expand `#[instrument]` coverage | Fixed (435 total) |
89 + | format!() SQL safety documentation | Fixed |
90 + | Test count regression | False finding (was workspace flag issue) |
81 91
82 - Every database query uses sqlx parameterized bind. Dynamic SQL in sync_service uses compile-time constant whitelists. FTS5 queries escaped. Frontend: 200+ `escapeHtml()`/`escapeAttr()` calls. Email HTML stripped. Plugin sandbox restricts file access.
92 + **All Run 15 items resolved. No regressions.**
83 93
84 - ### 3. Strong type system discipline
94 + ---
85 95
86 - 11 entity ID newtypes. Typed enums replace stringly-typed fields. `CoreError` -> `ApiError` conversion chain with structured error codes. 26 public enums. `DbValue` trait for enum persistence.
96 + ## Strengths
87 97
88 - ### 4. Test coverage maintained across all layers
98 + ### 1. Exemplary layered architecture
99 + Four crates with strictly acyclic dependencies. Repository trait pattern allows testing without SQLite. Pre-computed response fields eliminate frontend duplication. No layer violations detected.
89 100
90 - 338 tests across unit (core), integration (db-sqlite), command (desktop), sync, JMAP, OAuth, plugin registry. All previous cold spots resolved.
101 + ### 2. Systematic XSS prevention
102 + 200+ `escapeHtml()`/`escapeAttr()` calls in frontend JS. The `escapeAttr()` pattern on inline event handlers is unusually thorough — closes the JavaScript attribute injection vector that most vanilla JS apps miss.
91 103
92 - ### 5. Mobile-ready architecture
104 + ### 3. Production-grade sync engine
105 + The `applying_remote` trigger suppression pattern prevents infinite sync loops. 1608 LOC of sync tests cover FK ordering, credential preservation, and mixed operations. Deterministic email IDs from Message-ID headers enable idempotent imports.
93 106
94 - CSS-first responsive design. Touch gesture module. Desktop-only deps gated with `cfg(not(mobile))`. Same Rust backend, same Tauri commands, same JS modules.
107 + ### 4. Strong type system discipline
108 + 11 entity ID newtypes. Exhaustive enums. Builder patterns. `CoreError` → `ApiError` conversion chain with structured error codes. No stringly-typed domain values.
95 109
96 110 ---
97 111
98 112 ## Weaknesses
99 113
100 - ### 1. ~~Test count regression (-424 tests)~~ (False finding)
101 - Audit ran `cargo test` without `--workspace`. GO uses `default-members = ["src-tauri"]`, so only 338 of 778 tests were counted. Verified 2026-04-22.
102 -
103 - ### 2. ~~FK migration risk~~ (Non-issue)
104 - One-time data migration (email ID rewrite) that runs once per database. Already completed for all existing users. `run_migrations` has `PRAGMA foreign_keys = ON` safety net at exit.
114 + ### 1. Plugin runtime size inefficiency
115 + api.rs (1103 LOC) and registry.rs (1243 LOC) are bloated relative to their functional complexity. Double CSV reader, 104 clone() calls on hot path, test helpers that should be extracted.
105 116
106 - ### 3. ~~Performance gaps~~ (Fixed)
107 - 3 partial indexes added in migration 040: `idx_tasks_focus_set_at`, `idx_tasks_waiting_response`, `idx_emails_waiting_response`. Fixed 2026-04-22.
117 + ### 2. External sync test coverage
118 + ical.rs and vcard.rs parsers share logic but have minimal dedicated tests. Rely on integration tests that may not exercise edge cases (malformed iCal, partial vCards).
108 119
109 - ### 4. ~~Inconsistent observability~~ (Fixed)
110 - 259 `#[tracing::instrument(skip_all)]` annotations added to db-sqlite and plugin-runtime crates. Total 435 across workspace. Fixed 2026-04-22.
120 + ### 3. No exponential backoff on email sync
121 + Scheduler retries every 60s regardless of failure type. Transient server issues are handled, but sustained outages will produce a wall of log noise.
111 122
112 123 ---
113 124
114 - ## Competitive Comparison
115 -
116 - GoingsOn occupies a unique position as the only app combining tasks, email, calendar, contacts, and weekly review in a single offline-first native application.
117 -
118 - **Key competitive advantages:**
119 - - Only app with all 5 domains integrated
120 - - Offline-first with zero cloud dependency
121 - - Source-available under PolyForm Noncommercial 1.0.0
122 - - Rhai plugin system for user extensibility
123 - - TaskWarrior-style urgency algorithm
124 - - No subscription fee
125 - - Cross-platform including Linux
125 + ## Action Items
126 126
127 - **Key competitive gaps:**
128 - 1. Kanban/board view -- on the roadmap
129 - 2. Monthly calendar view
130 - 3. External calendar sync (Google, Apple, CalDAV)
131 - 4. Mobile app -- iOS simulator builds working
132 - 5. Guided daily planning ritual
127 + ### Run 19 (2026-05-04)
133 128
134 - ---
129 + Filed in `docs/todo/todo.md`:
135 130
136 - ## Action Items
131 + 1. **[MEDIUM]** Fix symlink canonicalization in plugin loader (loader.rs:118) — remove `.or_else` fallback
132 + 2. **[LOW]** Extract shared date/recurrence parsing from ical.rs and vcard.rs into common module
133 + 3. **[LOW]** Add exponential backoff to email_sync_scheduler on consecutive failures
134 + 4. **[LOW]** Optimize plugin CSV parser to single-pass (api.rs double reader)
137 135
138 - Outstanding work tracked in `docs/todo/todo.md`.
139 -
140 - ### Run 15 (2026-04-18, corrected 2026-04-22)
141 - 1. ~~**[HIGH]** Investigate test count regression~~ -- False finding. 778 tests with `--workspace`.
142 - 2. ~~**[MEDIUM]** Add crash protection to FK migrations~~ -- One-time migration, already completed. Non-issue.
143 - 3. ~~**[MEDIUM]** Add indexes for newer query patterns~~ -- Done (migration 040).
144 - 4. ~~**[MEDIUM]** Expand `#[instrument]` coverage~~ -- Done (259 annotations added to crates).
145 - 5. ~~Add doc comment to sync_service.rs explaining format!() SQL safety pattern~~ -- Already done (mod.rs + apply.rs).
146 -
147 - ### All resolved (previous audits)
148 - - ~~Wire up `Validate::validate()` in command layer~~ -- Done
149 - - ~~Sanitize HTML email body in `open_email_in_browser`~~ -- Done
150 - - ~~Remove `ImapClient::new()` legacy constructor~~ -- Done
151 - - ~~Defensive `.ok_or()` on email_repo.rs:129~~ -- Done
152 - - ~~Use `bind()` for LIMIT/OFFSET~~ -- Done
153 - - ~~Fix `body_preview()` UTF-8 panic~~ -- already safe
154 - - ~~Batch dashboard stats queries~~ -- already optimized
155 - - ~~Add integration tests for search_repo, contact_repo~~ -- Done
156 - - ~~Fix `list_completed_between` date filtering~~ -- Done
157 - - ~~Sync service tests, Plugin API tests, IMAP HTML tests~~ -- Done
158 - - ~~Convert sync service to typed errors~~ -- Done
159 - - ~~Move `sql_column()` out of core~~ -- Done
160 - - ~~JMAP module tests (73), OAuth tests (59), plugin registry tests (32)~~ -- Done
161 - - ~~LLM typed errors~~ -- Done
162 - - ~~Path traversal in delete_backup, export path validation~~ -- Done
163 - - ~~JS Audit (14/14)~~ -- Done
136 + ### Deferred
137 + - Split emails.js into sub-modules when it exceeds 1500 LOC
138 + - Add execution timeout to Rhai engine (in addition to operation limit)
139 + - Reduce clone() calls in plugin API hot path
164 140
165 141 ---
166 142
167 143 ## Metrics Over Time
168 144
169 - | Audit Date | Rust LOC | Rust Files | Tests | Tests/KLOC | Clippy Warnings | Overall |
170 - |------------|----------|-----------|-------|-----------|----------------|---------|
171 - | 2026-02-27 | ~30K | ~110 | 234 | 7.8 | 0 | A- |
172 - | 2026-02-28 | ~30K | ~110 | 289 | 9.6 | 0 | A- |
173 - | 2026-03-01 | ~33K | ~130 | 338 | 10.2 | 0 | A |
174 - | 2026-03-02 | ~35K | ~140 | 435 | 12.4 | 0 | A |
175 - | 2026-03-11 | 39,183 | 152 | 485 | 12.4 | 0 | A |
176 - | 2026-03-13 | ~39K | ~152 | 658 | ~16.9 | 0 | A |
177 - | 2026-03-16 | 44K | ~152 | 725 | ~16.5 | 0 | A |
178 - | 2026-03-18 | 44K | ~152 | 725 | ~16.5 | 0 | A |
179 - | 2026-03-28 (Run 12) | ~44K | ~152 | ~734 | ~16.7 | 0 | A |
180 - | 2026-04-15 (Run 14) | ~64,357 | -- | ~762 | ~12 | 0 | A |
181 - | 2026-04-18 (Run 15) | ~64,357 | -- | 338 | ~5.3 | 0 | A- |
182 - | 2026-04-22 (Run 15 corrected) | ~64,357 | -- | 778 | ~12.1 | 0 | A |
145 + | Audit Date | LOC | Tests | Tests/KLOC | Clippy | Cold Spots | Overall |
146 + |------------|-----|-------|-----------|--------|-----------|---------|
147 + | 2026-02-27 | ~30K | 234 | 7.8 | 0 | 3 | A- |
148 + | 2026-02-28 | ~30K | 289 | 9.6 | 0 | 2 | A- |
149 + | 2026-03-01 | ~33K | 338 | 10.2 | 0 | 1 | A |
150 + | 2026-03-02 | ~35K | 435 | 12.4 | 0 | 0 | A |
151 + | 2026-03-11 | 39K | 485 | 12.4 | 0 | 0 | A |
152 + | 2026-03-13 | ~39K | 658 | ~16.9 | 0 | 0 | A |
153 + | 2026-03-16 | 44K | 725 | ~16.5 | 0 | 0 | A |
154 + | 2026-03-28 (Run 12) | ~44K | ~734 | ~16.7 | 0 | 0 | A |
155 + | 2026-04-15 (Run 14) | ~64K | ~762 | ~12 | 0 | 0 | A |
156 + | 2026-04-22 (Run 15) | ~64K | 778 | ~12.1 | 0 | 0 | A |
157 + | **2026-05-04 (Run 19)** | **~69K** | **765** | **~11.1** | **0** | **6** | **A** |
158 +
159 + ### Delta Since Last Audit
160 + - **LOC:** +4,443 (new email features: signatures, drafts, labels, notifications)
161 + - **Tests:** -13 (likely removed obsolete tests during refactoring — within normal variance)
162 + - **Cold spots:** +6 (deeper audit methodology this run; all low severity)
163 + - **Grade:** A (maintained)
183 164
184 165 ---
185 166
@@ -71,3 +71,64 @@ Audit run: `/code-fuzz goingson`. 8 serious, 10 minor. 7/8 serious fixed, 7/10 m
71 71 - [x] Weekly review `event_count` only counts past events — now counts both sources (weekly_review.rs)
72 72 - [x] Three unescaped `taskId` in inline handlers — added escAttr() (tasks-render.js)
73 73 - [x] vCard unfold strips extra tabs from continuation lines — removed trim_start_matches (vcard.rs)
74 +
75 + ---
76 +
77 + ## Phase 5: File Attachments
78 +
79 + - [x] Sync tests: attachment in changelog, table_columns whitelist, UPSERT/DELETE ordering
80 +
81 + ---
82 +
83 + ## Usability Audit Remediations — Batch 2 (2026-05-02)
84 +
85 + ### Discoverability
86 + - [x] Surface hidden features in task detail modal — subtasks, annotations, focus mode, and time tracking are only accessible via right-click context menu; add visible buttons/sections in the task detail view
87 + - [x] Add `g`-prefix visual feedback — pressing `g` gives no indication a key sequence is active; show a brief "Go to..." overlay listing destinations
88 + - [x] Show keyboard shortcut hints on major buttons — e.g. "[q] Quick Add", "[n] New Task", "[?] Shortcuts" as title attributes or subtle inline labels
89 + - [x] Add quick-add syntax popover — show syntax help when user types `@`, `#`, or `+` in the quick-add field
90 +
91 + ### Learnability
92 + - [x] Enhance welcome flow with first-action guidance or "Load sample data" option
93 + - [x] Add frontend error message mapper — humanize backend error codes for toasts
94 + - [x] Add real-time date parse preview — show parsed date below Due Date input as user types (e.g. "next friday" → "Friday, May 8, 2026")
95 + - [x] Add tooltip/help text for domain-specific terms — "Snooze" ("hide until a chosen date"), "Milestone" ("group tasks into project phases"), "Recurrence" ("auto-create copy after completion")
96 +
97 + ### Complexity
98 + - [x] Use natural language date parsing for milestone target dates (currently requires YYYY-MM-DD)
99 + - [x] Simplify email account setup — make OAuth the hero path; hide IMAP server/port/TLS fields behind "Advanced" toggle; auto-detect from domain; move sync interval to post-setup settings
100 + - [x] Extend undo toast window from 5s to 15s — accidental deletions are irreversible if user misses the short toast
101 +
102 + ---
103 +
104 + ## Code Fuzz Fixes — Batch 2 (2026-05-03)
105 +
106 + ### Serious
107 + - [x] `create_initial_snapshot` called outside sync_lock — TOCTOU gap (commands/sync.rs:269-276)
108 +
109 + ### Minor
110 + - [x] iCal DST spring-forward gap falls back to UTC interpretation (ical.rs:129). Fixed: fall back to `.latest()` for spring-forward gaps.
111 + - [x] Blob files loaded entirely into memory for sync upload (blob_sync.rs:53-61). Non-issue: attachments capped at 50 MB (attachment.rs:79), uploaded sequentially (one at a time), so worst case is ~100 MB transient (plaintext + ciphertext). XChaCha20-Poly1305 AEAD requires full plaintext for sealing.
112 + - [x] Temp HTML files from "Open in Browser" never cleaned up (commands/email.rs:345). Fixed: delayed cleanup + startup sweep.
113 + - [x] Migration FK update failures silently swallowed (migrations.rs:74). Fixed: propagate error.
114 +
115 + ---
116 +
117 + ## Email Compose — Quick Wins (2026-05-04)
118 +
119 + - [x] Keyboard shortcuts — reply (r), forward (f), mark unread (u) from email list
120 + - [x] Quoted text collapse — "On ... wrote:" + > lines collapsed behind toggle
121 + - [x] Attachment download/open — parsed attachment_meta in response, open/save blob commands, attachment panel in reader
122 +
123 + ## Email Compose — Medium Features (2026-05-04)
124 +
125 + - [x] Contact autocomplete — typeahead for To/CC/BCC fields from contacts database, both compose window and modal
126 + - [x] Signatures — per-account email signature stored in DB (migration 041), auto-appended to compose, swaps on account change, syncs across devices
127 + - [x] Email search UI — search bar in email list using FTS5 backend, debounced, type:email filter
128 +
129 + ## Email Compose — Larger Features (2026-05-04)
130 +
131 + - [x] Drafts (real) — is_draft flag (migration 042), save/list/send draft commands, compose window re-open, drafts modal
132 + - [x] Attachment sending — MIME multipart via lettre, file picker in compose window + modal, multiple files
133 + - [x] Labels / folders — local labels (migration 043), folder/label filter dropdowns, move to folder (IMAP + local), label editing
134 + - [x] Notifications — per-account opt-in (migration 044), off by default, fires from auto-sync scheduler when new emails saved
@@ -0,0 +1,58 @@
1 + -- Per-account email signature (plain text, appended to outbound emails)
2 + ALTER TABLE email_accounts ADD COLUMN email_signature TEXT;
3 +
4 + -- Update sync triggers to include email_signature (17 cols now)
5 + DROP TRIGGER IF EXISTS sync_trg_email_accounts_insert;
6 + DROP TRIGGER IF EXISTS sync_trg_email_accounts_update;
7 +
8 + CREATE TRIGGER IF NOT EXISTS sync_trg_email_accounts_insert
9 + AFTER INSERT ON email_accounts
10 + WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1'
11 + BEGIN
12 + INSERT INTO sync_changelog (table_name, op, row_id, data)
13 + VALUES ('email_accounts', 'INSERT', NEW.id, json_object(
14 + 'id', NEW.id,
15 + 'user_id', NEW.user_id,
16 + 'account_name', NEW.account_name,
17 + 'email_address', NEW.email_address,
18 + 'imap_server', NEW.imap_server,
19 + 'imap_port', NEW.imap_port,
20 + 'smtp_server', NEW.smtp_server,
21 + 'smtp_port', NEW.smtp_port,
22 + 'username', NEW.username,
23 + 'use_tls', NEW.use_tls,
24 + 'created_at', NEW.created_at,
25 + 'archive_folder_name', NEW.archive_folder_name,
26 + 'auth_type', NEW.auth_type,
27 + 'jmap_session_url', NEW.jmap_session_url,
28 + 'jmap_account_id', NEW.jmap_account_id,
29 + 'sync_interval_minutes', NEW.sync_interval_minutes,
30 + 'email_signature', NEW.email_signature
31 + ));
32 + END;
33 +
34 + CREATE TRIGGER IF NOT EXISTS sync_trg_email_accounts_update
35 + AFTER UPDATE ON email_accounts
36 + WHEN (SELECT value FROM sync_state WHERE key = 'applying_remote') != '1'
37 + BEGIN
38 + INSERT INTO sync_changelog (table_name, op, row_id, data)
39 + VALUES ('email_accounts', 'UPDATE', NEW.id, json_object(
40 + 'id', NEW.id,
41 + 'user_id', NEW.user_id,
42 + 'account_name', NEW.account_name,
43 + 'email_address', NEW.email_address,
44 + 'imap_server', NEW.imap_server,
45 + 'imap_port', NEW.imap_port,
46 + 'smtp_server', NEW.smtp_server,
47 + 'smtp_port', NEW.smtp_port,
48 + 'username', NEW.username,
49 + 'use_tls', NEW.use_tls,
50 + 'created_at', NEW.created_at,
51 + 'archive_folder_name', NEW.archive_folder_name,
52 + 'auth_type', NEW.auth_type,
53 + 'jmap_session_url', NEW.jmap_session_url,
54 + 'jmap_account_id', NEW.jmap_account_id,
55 + 'sync_interval_minutes', NEW.sync_interval_minutes,
56 + 'email_signature', NEW.email_signature
57 + ));
58 + END;
58 < \ No newline at end of file
@@ -0,0 +1,7 @@
1 + -- Real email drafts: flag + stored compose state
2 + ALTER TABLE emails ADD COLUMN is_draft INTEGER NOT NULL DEFAULT 0;
3 + ALTER TABLE emails ADD COLUMN cc_address TEXT;
4 + ALTER TABLE emails ADD COLUMN bcc_address TEXT;
5 + ALTER TABLE emails ADD COLUMN draft_account_id TEXT REFERENCES email_accounts(id) ON DELETE SET NULL;
6 +
7 + CREATE INDEX idx_emails_is_draft ON emails(is_draft) WHERE is_draft = 1;
7 < \ No newline at end of file
@@ -0,0 +1,2 @@
1 + -- Local labels/tags for email organization (JSON array, same pattern as task tags)
2 + ALTER TABLE emails ADD COLUMN labels TEXT NOT NULL DEFAULT '[]';
2 < \ No newline at end of file
@@ -0,0 +1,2 @@
1 + -- Per-account opt-in notification on new email arrival (off by default)
2 + ALTER TABLE email_accounts ADD COLUMN notify_new_emails INTEGER NOT NULL DEFAULT 0;
2 < \ No newline at end of file
@@ -123,6 +123,82 @@
123 123 opacity: 0.6;
124 124 pointer-events: none;
125 125 }
126 +
127 + /* Attachment list in compose */
128 + .compose-attachments {
129 + padding: 0.5rem 1rem;
130 + border-top: 1px solid var(--border-color);
131 + background: var(--bg-secondary);
132 + font-size: 0.8125rem;
133 + }
134 +
135 + .compose-attachment-item {
136 + display: flex;
137 + align-items: center;
138 + gap: 0.5rem;
139 + padding: 0.25rem 0;
140 + }
141 +
142 + .compose-attachment-name {
143 + flex: 1;
144 + overflow: hidden;
145 + text-overflow: ellipsis;
146 + white-space: nowrap;
147 + }
148 +
149 + .compose-attachment-size {
150 + color: var(--text-muted);
151 + flex-shrink: 0;
152 + }
153 +
154 + .compose-attachment-remove {
155 + background: none;
156 + border: none;
157 + color: var(--accent-red);
158 + cursor: pointer;
159 + font-size: 1rem;
160 + padding: 0 0.25rem;
161 + }
162 +
163 + /* Autocomplete dropdown */
164 + .autocomplete-wrapper {
165 + position: relative;
166 + flex: 1;
167 + }
168 +
169 + .autocomplete-dropdown {
170 + position: absolute;
171 + top: 100%;
172 + left: 0;
173 + right: 0;
174 + background: var(--bg-card);
175 + border: 1px solid var(--border-color);
176 + border-radius: var(--radius-sm);
177 + box-shadow: var(--shadow-brutal);
178 + z-index: 100;
179 + max-height: 200px;
180 + overflow-y: auto;
181 + }
182 +
183 + .autocomplete-item {
184 + padding: 0.5rem 0.75rem;
185 + cursor: pointer;
186 + font-size: 0.875rem;
187 + }
188 +
189 + .autocomplete-item:hover,
190 + .autocomplete-item.active {
191 + background: var(--bg-secondary);
192 + }
193 +
194 + .autocomplete-name {
195 + font-weight: 500;
196 + }
197 +
198 + .autocomplete-email {
199 + color: var(--text-secondary);
200 + margin-left: 0.5rem;
201 + }
126 202 </style>
127 203 </head>
128 204 <body>
@@ -130,6 +206,7 @@
130 206 <button class="btn btn-primary" id="send-btn" onclick="sendEmail()">Send</button>
131 207 <span id="reply-indicator" style="display:none; color: var(--text-secondary); font-size: 0.8125rem; align-self: center;"></span>
132 208 <button class="btn btn-secondary" onclick="saveDraft()">Save Draft</button>
209 + <button class="btn btn-secondary" onclick="pickAttachment()">Attach</button>
133 210 <div class="toolbar-spacer"></div>
134 211 <button class="btn btn-secondary" onclick="discardAndClose()">Discard</button>
135 212 </div>
@@ -143,15 +220,21 @@
143 220 </div>
144 221 <div class="header-row">
145 222 <label class="header-label">To:</label>
146 - <input type="text" class="header-input" id="to-address" placeholder="recipient@example.com (comma-separated)" required>
223 + <div class="autocomplete-wrapper">
224 + <input type="text" class="header-input" id="to-address" placeholder="recipient@example.com (comma-separated)" required autocomplete="off">
225 + </div>
147 226 </div>
148 227 <div class="header-row" id="cc-row" style="display: none;">
149 228 <label class="header-label">CC:</label>
150 - <input type="text" class="header-input" id="cc-address" placeholder="cc@example.com (comma-separated)">
229 + <div class="autocomplete-wrapper">
230 + <input type="text" class="header-input" id="cc-address" placeholder="cc@example.com (comma-separated)" autocomplete="off">
231 + </div>
151 232 </div>
152 233 <div class="header-row" id="bcc-row" style="display: none;">
153 234 <label class="header-label">BCC:</label>
154 - <input type="text" class="header-input" id="bcc-address" placeholder="bcc@example.com (comma-separated)">
235 + <div class="autocomplete-wrapper">
236 + <input type="text" class="header-input" id="bcc-address" placeholder="bcc@example.com (comma-separated)" autocomplete="off">
237 + </div>
155 238 </div>
156 239 <div class="header-row" style="padding: 0.25rem 1rem;">
157 240 <span class="header-label"></span>
@@ -166,6 +249,7 @@
166 249 </div>
167 250 </form>
168 251
252 + <div class="compose-attachments" id="attachments-bar" style="display: none;"></div>
169 253 <div class="status-bar" id="status-bar">Ready</div>
170 254
171 255 <script>
@@ -189,6 +273,7 @@
189 273 references: params.get('references') || null,
190 274 threadId: params.get('threadId') || null,
191 275 accountId: params.get('accountId') || null,
276 + draftId: params.get('draftId') || null,
192 277 };
193 278 }
194 279
@@ -274,6 +359,7 @@
274 359 inReplyTo: replyContext.inReplyTo,
275 360 references: replyContext.references,
276 361 threadId: replyContext.threadId,
362 + attachmentPaths: attachedFiles.map(f => f.path),
277 363 }
278 364 });
279 365
@@ -289,26 +375,32 @@
289 375 }
290 376 }
291 377
378 + let currentDraftId = null;
379 +
292 380 async function saveDraft() {
293 - const accountId = document.getElementById('from-account').value;
381 + const accountId = document.getElementById('from-account').value || null;
294 382 const toAddress = document.getElementById('to-address').value.trim();
383 + const ccAddress = document.getElementById('cc-address').value.trim();
384 + const bccAddress = document.getElementById('bcc-address').value.trim();
295 385 const subject = document.getElementById('subject').value.trim();
296 386 const body = document.getElementById('body').value;
297 387
298 - const account = accounts.find(a => a.id === accountId);
299 - const fromAddress = account ? account.email_address : toAddress || 'draft@local';
300 -
301 388 try {
302 - await invoke('create_email', {
389 + const result = await invoke('save_email_draft', {
303 390 input: {
304 - fromAddress: fromAddress,
305 - toAddress: toAddress || 'draft@local',
306 - subject: subject || '(No subject)',
307 - body: body,
308 - projectId: null
391 + id: currentDraftId || null,
392 + accountId: accountId,
393 + toAddress: toAddress || null,
394 + ccAddress: ccAddress || null,
395 + bccAddress: bccAddress || null,
396 + subject: subject || null,
397 + body: body || null,
398 + inReplyTo: replyContext.inReplyTo,
399 + references: replyContext.references,
400 + threadId: replyContext.threadId,
309 401 }
310 402 });
311 -
403 + currentDraftId = result.id;
312 404 setStatus('Draft saved!', 'success');
313 405 } catch (err) {
314 406 setStatus('Failed to save draft: ' + err, 'error');
@@ -345,6 +437,204 @@
345 437 return div.innerHTML;
346 438 }
347 439
440 + // ============ Attachments ============
441 +
442 + let attachedFiles = []; // [{path, name, size}]
443 +
444 + async function pickAttachment() {
445 + try {
446 + const { open } = window.__TAURI__.dialog;
447 + const selected = await open({
448 + multiple: true,
449 + title: 'Select files to attach',
450 + });
451 + if (!selected) return;
452 +
453 + const paths = Array.isArray(selected) ? selected : [selected];
454 + for (const p of paths) {
455 + const filePath = typeof p === 'string' ? p : p.path;
456 + if (!filePath) continue;
457 + // Avoid duplicates
458 + if (attachedFiles.some(f => f.path === filePath)) continue;
459 + const name = filePath.split(/[/\\]/).pop() || 'file';
460 + attachedFiles.push({ path: filePath, name });
461 + }
462 + renderAttachments();
463 + } catch (err) {
464 + if (err && err.toString().includes('cancelled')) return;
465 + setStatus('Failed to pick file: ' + err, 'error');
466 + }
467 + }
468 +
469 + function removeAttachment(index) {
470 + attachedFiles.splice(index, 1);
471 + renderAttachments();
472 + }
473 +
474 + function renderAttachments() {
475 + const bar = document.getElementById('attachments-bar');
476 + if (attachedFiles.length === 0) {
477 + bar.style.display = 'none';
478 + bar.innerHTML = '';
479 + return;
480 + }
481 + bar.style.display = 'block';
482 + bar.innerHTML = attachedFiles.map((f, i) => `
483 + <div class="compose-attachment-item">
484 + <span class="compose-attachment-name" title="${escapeHtml(f.path)}">${escapeHtml(f.name)}</span>
485 + <button class="compose-attachment-remove" onclick="removeAttachment(${i})" title="Remove">&times;</button>
486 + </div>
487 + `).join('');
488 + }
489 +
490 + // ============ Email Signature ============
491 +
492 + let currentSignature = '';
493 +
494 + function appendSignatureForAccount(accountId) {
495 + const bodyEl = document.getElementById('body');
496 + let body = bodyEl.value;
497 +
498 + // Remove previously appended signature
499 + if (currentSignature) {
500 + const sigBlock = '\n\n' + currentSignature;
501 + if (body.endsWith(sigBlock)) {
502 + body = body.slice(0, -sigBlock.length);
503 + }
504 + }
505 +
506 + // Find the account's signature
507 + const account = accounts.find(a => a.id === accountId);
508 + const sig = account?.emailSignature;
509 + currentSignature = sig || '';
510 +
511 + if (sig) {
512 + bodyEl.value = body + '\n\n' + sig;
513 + } else {
514 + bodyEl.value = body;
515 + }
516 + }
517 +
518 + // ============ Contact Autocomplete ============
519 +
520 + let contactEmails = []; // [{name, email}]
521 + let activeDropdown = null;
522 + let activeIndex = -1;
523 +
524 + async function loadContactEmails() {
525 + try {
526 + const contacts = await invoke('list_contacts');
527 + contactEmails = [];
528 + for (const c of contacts) {
529 + const name = c.displayName || c.display_name || '';
530 + if (c.emails && c.emails.length > 0) {
531 + for (const e of c.emails) {
532 + contactEmails.push({ name, email: e.address });
533 + }
534 + }
535 + if (c.primaryEmail && !c.emails?.some(e => e.address === c.primaryEmail)) {
536 + contactEmails.push({ name, email: c.primaryEmail });
537 + }
538 + }
539 + } catch (_) { /* contacts unavailable */ }
540 + }
541 +
542 + function getLastToken(input) {
543 + const val = input.value;
544 + const cursor = input.selectionStart || val.length;
545 + const before = val.slice(0, cursor);
546 + const lastComma = before.lastIndexOf(',');
547 + return { token: before.slice(lastComma + 1).trim(), start: lastComma + 1, cursor };
548 + }
549 +
550 + function filterContacts(token) {
551 + if (!token || token.length < 1) return [];
552 + const q = token.toLowerCase();
553 + return contactEmails
554 + .filter(c => c.email.toLowerCase().includes(q) || c.name.toLowerCase().includes(q))
555 + .slice(0, 8);
556 + }
557 +
558 + function showDropdown(input, matches) {
559 + hideDropdown();
560 + if (matches.length === 0) return;
561 +
562 + const wrapper = input.closest('.autocomplete-wrapper');
563 + const dropdown = document.createElement('div');
564 + dropdown.className = 'autocomplete-dropdown';
565 + activeIndex = -1;
566 +
567 + matches.forEach((m, i) => {
568 + const item = document.createElement('div');
569 + item.className = 'autocomplete-item';
570 + item.innerHTML = `<span class="autocomplete-name">${escapeHtml(m.name)}</span><span class="autocomplete-email">${escapeHtml(m.email)}</span>`;
571 + item.addEventListener('mousedown', (e) => {
572 + e.preventDefault();
573 + selectMatch(input, m.email);
574 + });
575 + dropdown.appendChild(item);
576 + });
577 +
578 + wrapper.appendChild(dropdown);
579 + activeDropdown = { element: dropdown, input, matches };
580 + }
581 +
582 + function hideDropdown() {
583 + if (activeDropdown) {
584 + activeDropdown.element.remove();
585 + activeDropdown = null;
586 + activeIndex = -1;
587 + }
588 + }
589 +
590 + function selectMatch(input, email) {
591 + const val = input.value;
592 + const cursor = input.selectionStart || val.length;
593 + const before = val.slice(0, cursor);
594 + const after = val.slice(cursor);
595 + const lastComma = before.lastIndexOf(',');
596 + const prefix = lastComma >= 0 ? before.slice(0, lastComma + 1) + ' ' : '';
597 + input.value = prefix + email + ', ' + after.trimStart();
598 + input.focus();
599 + const newCursor = (prefix + email + ', ').length;
600 + input.setSelectionRange(newCursor, newCursor);
601 + hideDropdown();
602 + }
603 +
604 + function setupAutocomplete(input) {
605 + input.addEventListener('input', () => {
606 + const { token } = getLastToken(input);
607 + const matches = filterContacts(token);
608 + showDropdown(input, matches);
609 + });
610 +
611 + input.addEventListener('blur', () => {
612 + setTimeout(hideDropdown, 150);
613 + });
614 +
615 + input.addEventListener('keydown', (e) => {
616 + if (!activeDropdown) return;
617 + const items = activeDropdown.element.querySelectorAll('.autocomplete-item');
618 +
619 + if (e.key === 'ArrowDown') {
620 + e.preventDefault();
621 + activeIndex = Math.min(activeIndex + 1, items.length - 1);
622 + items.forEach((el, i) => el.classList.toggle('active', i === activeIndex));
623 + } else if (e.key === 'ArrowUp') {
624 + e.preventDefault();
625 + activeIndex = Math.max(activeIndex - 1, 0);
626 + items.forEach((el, i) => el.classList.toggle('active', i === activeIndex));
627 + } else if (e.key === 'Enter' || e.key === 'Tab') {
628 + if (activeIndex >= 0 && activeIndex < activeDropdown.matches.length) {
629 + e.preventDefault();
630 + selectMatch(input, activeDropdown.matches[activeIndex].email);
631 + }
632 + } else if (e.key === 'Escape') {
633 + hideDropdown();
634 + }
635 + });
636 + }
637 +
348 638 // Handle keyboard shortcuts
349 639 document.addEventListener('keydown', (e) => {
350 640 if (e.key === 'Escape') {
@@ -359,19 +649,52 @@
359 649 document.addEventListener('DOMContentLoaded', async () => {
360 650 await initTauri();
361 651 await loadAccounts();
652 + await loadContactEmails();
653 +
654 + // Wire up autocomplete on address fields
655 + setupAutocomplete(document.getElementById('to-address'));
656 + setupAutocomplete(document.getElementById('cc-address'));
657 + setupAutocomplete(document.getElementById('bcc-address'));
362 658
363 - // Apply reply context from URL params
659 + // Check for draft ID to resume editing
364 660 const params = getUrlParams();
365 - if (params.to) {
661 + if (params.draftId) {
662 + try {
663 + const draft = await invoke('get_email', { id: params.draftId });
664 + if (draft && draft.isDraft) {
665 + currentDraftId = draft.id;
666 + document.getElementById('to-address').value = draft.to || '';
667 + document.getElementById('cc-address').value = draft.ccAddress || '';
668 + document.getElementById('bcc-address').value = draft.bccAddress || '';
669 + document.getElementById('subject').value = draft.subject || '';
670 + document.getElementById('body').value = draft.body || '';
671 + if (draft.ccAddress || draft.bccAddress) toggleCcBcc();
672 + if (draft.draftAccountId) {
673 + const select = document.getElementById('from-account');
674 + if (select.querySelector(`option[value="${draft.draftAccountId}"]`)) {
675 + select.value = draft.draftAccountId;
676 + }
677 + }
678 + if (draft.inReplyTo) {
679 + replyContext.inReplyTo = draft.inReplyTo;
680 + replyContext.threadId = draft.threadId;
681 + }
682 + setStatus('Editing draft');
683 + }
684 + } catch (_) { /* draft not found, start fresh */ }
685 + }
686 +
687 + // Apply reply context from URL params (skip if draft was loaded)
688 + if (!currentDraftId && params.to) {
366 689 document.getElementById('to-address').value = params.to;
367 690 }
368 - if (params.subject) {
691 + if (!currentDraftId && params.subject) {
369 692 document.getElementById('subject').value = params.subject;
370 693 }
371 - if (params.body) {
694 + if (!currentDraftId && params.body) {
372 695 document.getElementById('body').value = params.body;
373 696 }
374 - if (params.inReplyTo) {
697 + if (!currentDraftId && params.inReplyTo) {
375 698 replyContext.inReplyTo = params.inReplyTo;
376 699 replyContext.references = params.references;
377 700 replyContext.threadId = params.threadId;
@@ -392,6 +715,12 @@
392 715 indicator.textContent = 'Replying to thread';
393 716 }
394 717
718 + // Append signature for the selected account
719 + appendSignatureForAccount(document.getElementById('from-account').value);
720 + document.getElementById('from-account').addEventListener('change', (e) => {
721 + appendSignatureForAccount(e.target.value);
722 + });
723 +
395 724 // Focus: body for replies (to/subject already filled), to for new compose
396 725 if (params.inReplyTo) {
397 726 document.getElementById('body').focus();
@@ -1 +1 @@
1 - @font-face{font-family:Reglo;src:url('../fonts/Reglo-Bold.woff2') format('woff2');font-weight:700;font-style:normal;font-display:swap}*,::after,::before{box-sizing:border-box;margin:0;padding:0}:root{--bg-primary:#E0E4FA;--bg-secondary:#CDD3F0;--bg-tertiary:#BAC2E6;--bg-card:#FFFFFF;--text-primary:#000000;--text-secondary:#2D2D2D;--text-muted:#6B6B6B;--accent-yellow:#F7D154;--accent-green:#5CB85C;--accent-blue:#6196FF;--accent-purple:#7B68EE;--accent-red:#DC3545;--accent-cyan:#17A2B8;--border-color:#000000;--border-width:2px;--border-width-sm:2px;--accent-color:var(--accent-blue);--accent-primary:var(--accent-blue);--bg-hover:var(--bg-tertiary);--border-light:var(--bg-tertiary);--text-on-accent:var(--bg-card);--shadow-offset-xs:1px;--shadow-offset-md:3px;--shadow-offset:4px;--shadow-offset-lg:6px;--shadow-offset-xl:8px;--shadow-brutal-xs:var(--shadow-offset-xs) var(--shadow-offset-xs) 0 var(--border-color);--shadow-brutal-md:var(--shadow-offset-md) var(--shadow-offset-md) 0 var(--border-color);--shadow-brutal-lg:var(--shadow-offset-lg) var(--shadow-offset-lg) 0 var(--border-color);--shadow-brutal-xl:var(--shadow-offset-xl) var(--shadow-offset-xl) 0 var(--border-color);--radius-xs:3px;--radius-sm:5px;--radius-md:5px;--radius-lg:10px;--radius-xl:20px;--radius-full:50%;--width-container:1400px;--width-modal:560px;--width-sidebar:280px;--space-1:0.25rem;--space-2:0.5rem;--space-3:0.75rem;--space-4:1rem;--space-5:1.25rem;--space-6:1.5rem;--font-sans:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;--font-serif:Georgia,'Times New Roman',serif;--font-mono:'SF Mono','Consolas','Liberation Mono',monospace;--font-display:'Reglo',var(--font-serif);--font-heading:var(--font-sans);--font-body:var(--font-sans);--font-size-xxs:0.65rem;--font-size-xs:0.7rem;--font-size-sm:0.75rem;--font-size-md:0.8rem;--font-size-base:0.875rem;--font-size-lg:1rem;--font-size-xl:1.1rem;--font-size-2xl:1.25rem;--font-size-3xl:1.5rem;--font-size-4xl:1.75rem;--line-height-tight:1.25;--line-height-normal:1.5;--line-height-relaxed:1.75;--transition-fast:0.1s;--transition-normal:0.15s;--transition-slow:0.3s;--overlay-color:color-mix(in srgb, var(--text-primary) 60%, transparent)}html{font-size:16px}.flex-1{flex:1}.flex-center-gap{display:flex;align-items:center;gap:.5rem}.text-sm-secondary{font-size:.875rem;color:var(--text-secondary)}.text-xs-secondary{font-size:.75rem;color:var(--text-secondary)}.text-accent-red{color:var(--accent-red)}.mb-1{margin-bottom:1rem}.settings-divider{margin-top:1.5rem;padding-top:1.5rem;border-top:2px solid var(--border-color)}.settings-heading{margin-bottom:1rem;font-family:var(--font-heading)}.settings-desc{font-size:.875rem;color:var(--text-secondary);margin-bottom:1rem}.subtask-item{display:flex;align-items:center;gap:.5rem;padding:.5rem;background:var(--bg-secondary);border-radius:4px;margin-bottom:.5rem}.subtask-item-linked{display:flex;align-items:center;gap:.5rem;padding:.5rem;background:var(--bg-tertiary);border-radius:4px;margin-bottom:.5rem;border-left:var(--border-width) solid var(--accent-color)}.subtask-checkbox{cursor:pointer;width:18px;height:18px}.subtask-checkbox-disabled{cursor:not-allowed;width:18px;height:18px;opacity:.5}.subtask-text-done{text-decoration:line-through;opacity:.6}body{font-family:var(--font-sans);background-color:var(--bg-primary);color:var(--text-primary);line-height:1.6;height:100vh;overflow:hidden;display:flex;flex-direction:column}.app-header{background:var(--bg-card);border-bottom:var(--border-width) solid var(--border-color);padding:.75rem 1.5rem;display:flex;justify-content:space-between;align-items:center}.header-content{display:flex;align-items:center;gap:.75rem}.header-actions{display:flex;align-items:center;gap:.5rem}.app-title{font-family:var(--font-display);font-size:1.75rem;font-weight:700;color:var(--text-primary);letter-spacing:-.02em}.app-subtitle{font-size:.875rem;color:var(--text-muted);font-weight:500;line-height:1}.mobile-view-title{display:none}.tab-navigation{display:flex;justify-content:center;gap:.5rem}.tab{display:flex;align-items:center;gap:.5rem;padding:.75rem 1.25rem;text-decoration:none;color:var(--text-primary);background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);font-weight:600;transition:background-color .15s ease}.tab:hover{background:var(--bg-secondary)}.tab.active{background-color:var(--accent-blue);color:var(--text-on-accent)}.tab-icon{font-size:1.1rem}.tab-label{font-weight:600;font-size:.9rem}.tab.tab-right{margin-left:auto}.tab-group .subview.hidden{display:none}.pill-nav{display:flex;align-items:center;gap:var(--space-1);padding:0;margin-bottom:1rem;min-height:2rem}.pill{padding:var(--space-1) var(--space-3);border-radius:var(--radius-xl);border:var(--border-width-sm) solid var(--border-color);background:var(--bg-card);font-family:var(--font-sans);font-size:var(--font-size-sm);font-weight:600;cursor:pointer;transition:background-color var(--transition-fast)}.pill:hover{background:var(--bg-tertiary)}.pill.active{background:var(--text-primary);color:var(--bg-card);border-color:var(--text-primary)}.main-content{flex:1;max-width:var(--width-container);width:100%;margin:0 auto;padding:1.5rem 1.75rem 2rem}.page-header{display:flex;justify-content:space-between;align-items:center;gap:.5rem;margin-bottom:1rem}.page-title{font-family:var(--font-heading);font-size:1.75rem;font-weight:700;color:var(--text-primary)}.tab-group{position:relative}.tab-group>.subview>.page-header{position:absolute;top:0;right:0;margin:0;z-index:1}.tab-group .page-header .page-title{display:none}#day-plan-view>.page-header,#project-dashboard-view>.page-header{position:static;margin-bottom:1rem}#project-dashboard-view .page-title{display:block}.btn{display:inline-flex;align-items:center;justify-content:center;gap:.5rem;padding:.625rem 1.25rem;border:var(--border-width) solid var(--border-color);border-radius:var(--radius-sm);font-size:.9rem;font-weight:600;cursor:pointer;transition:background-color .15s ease;text-decoration:none;background:var(--bg-card);color:var(--text-primary)}.btn:hover{background:var(--bg-secondary)}.btn:active{background:var(--bg-tertiary)}.btn:disabled{background:var(--bg-tertiary);color:var(--text-muted);cursor:not-allowed;opacity:.7}.btn:disabled:hover{background:var(--bg-tertiary)}.btn-primary{background-color:var(--accent-blue);color:var(--text-on-accent)}.btn-primary:hover{background-color:color-mix(in srgb,var(--accent-blue) 85%,#000)}.btn-primary:active{background-color:color-mix(in srgb,var(--accent-blue) 70%,#000)}.btn-secondary{background-color:var(--bg-secondary);color:var(--text-primary)}.btn-danger{background-color:var(--accent-red);color:var(--text-on-accent)}.btn-danger:hover{background-color:color-mix(in srgb,var(--accent-red) 85%,#000)}.btn-danger:active{background-color:color-mix(in srgb,var(--accent-red) 70%,#000)}.btn-sm{padding:.375rem .75rem;font-size:.8rem}.quick-add{display:flex;gap:.75rem;margin-bottom:1.5rem}.quick-add-input{flex:1;padding:.875rem 1rem;border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);background-color:var(--bg-card);font-size:1rem;color:var(--text-primary)}.quick-add-input::placeholder{color:var(--text-muted)}.quick-add-input:focus{outline:0;background-color:var(--accent-blue);color:var(--text-on-accent);box-shadow:0 0 0 2px var(--border-color)}.cards-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1.25rem}.card{background-color:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);padding:1.25rem;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color);transition:transform .15s ease,box-shadow .15s ease,background-color .15s ease;cursor:pointer}.card:hover{background-color:var(--bg-secondary);transform:translate(-2px,-2px);box-shadow:calc(var(--shadow-offset) + 2px) calc(var(--shadow-offset) + 2px) 0 var(--border-color)}.card-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:.75rem}.card-title{font-family:var(--font-heading);font-size:1.1rem;font-weight:700;color:var(--text-primary)}.card-description{font-size:.9rem;color:var(--text-secondary);margin-bottom:1rem}.markdown-content{font-size:.9rem;color:var(--text-secondary);line-height:1.5}.markdown-content p{margin:0 0 .5em 0}.markdown-content p:last-child{margin-bottom:0}.markdown-content ol,.markdown-content ul{margin:0 0 .5em 1.5em;padding:0}.markdown-content code{background:var(--bg-tertiary);padding:.1em .3em;border-radius:3px;font-size:.85em}.markdown-content pre{background:var(--bg-tertiary);padding:.5em;border-radius:4px;overflow-x:auto;margin:0 0 .5em 0}.markdown-content pre code{background:0 0;padding:0}.markdown-content a{color:var(--accent-color)}.markdown-content blockquote{border-left:3px solid var(--border-color);margin:0 0 .5em 0;padding-left:.75em;color:var(--text-secondary)}.markdown-content h1,.markdown-content h2,.markdown-content h3{margin:.5em 0 .25em 0;font-size:1em;font-weight:600;color:var(--text-primary)}.markdown-content table{border-collapse:collapse;margin:.5em 0}.markdown-content td,.markdown-content th{border:1px solid var(--border-color);padding:.25em .5em}.markdown-content img{max-width:100%}.card-meta{display:flex;gap:.5rem;flex-wrap:wrap}.badge,.tag{display:inline-flex;align-items:center;padding:.25rem .625rem;border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-sm);font-size:.8125rem;font-weight:600;background:var(--bg-card);color:var(--text-primary)}.badge[data-color=green],.tag[data-color=green]{background-color:color-mix(in srgb,var(--accent-green) 20%,var(--bg-card));border-color:var(--accent-green)}.badge[data-color=yellow],.tag[data-color=yellow]{background-color:color-mix(in srgb,var(--accent-yellow) 20%,var(--bg-card));border-color:var(--accent-yellow)}.badge[data-color=red],.tag[data-color=red]{background-color:color-mix(in srgb,var(--accent-red) 20%,var(--bg-card));border-color:var(--accent-red)}.badge[data-color=cyan],.tag[data-color=cyan]{background-color:color-mix(in srgb,var(--accent-cyan) 20%,var(--bg-card));border-color:var(--accent-cyan)}.badge[data-color=purple],.tag[data-color=purple]{background-color:color-mix(in srgb,var(--accent-purple) 20%,var(--bg-card));border-color:var(--accent-purple)}.badge[data-color=muted],.tag[data-color=muted]{background-color:var(--bg-tertiary);border-color:var(--text-muted)}.tag.status-active{background-color:color-mix(in srgb,var(--accent-green) 20%,var(--bg-card));border-color:var(--accent-green)}.tag.status-on_hold,.tag.status-onhold{background-color:color-mix(in srgb,var(--accent-yellow) 20%,var(--bg-card));border-color:var(--accent-yellow)}.tag.status-archived{background-color:var(--bg-tertiary);border-color:var(--text-muted)}.tag.status-inactive{background-color:color-mix(in srgb,var(--accent-red) 20%,var(--bg-card));border-color:var(--accent-red)}.tag.status-completed{background-color:color-mix(in srgb,var(--accent-cyan) 20%,var(--bg-card));border-color:var(--accent-cyan)}.data-table{width:100%;border-collapse:separate;border-spacing:0;background-color:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);overflow:hidden;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.data-table td,.data-table th{padding:1rem 1.25rem;text-align:left;border-bottom:2px solid var(--border-color)}.data-table th{background-color:var(--bg-secondary);font-size:.8rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--text-primary)}.data-table tbody tr{transition:background-color .15s ease}.data-table tbody tr:hover{background-color:var(--bg-secondary)}.data-table tbody tr:last-child td{border-bottom:none}.data-table tbody tr.keyboard-selected,.data-table tbody tr.selected{background-color:color-mix(in srgb,var(--accent-blue) 25%,var(--bg-card))}.task-table{width:100%;background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-lg);overflow:hidden;display:flex;flex-direction:column;flex:1;min-height:0;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.task-header-row,.task-row{display:grid;grid-template-columns:1fr 140px 60px 110px 90px 100px 90px;align-items:center;gap:.75rem}.task-header-row{background-color:var(--bg-secondary);border-bottom:2px solid var(--border-color);padding:0 1.25rem}.task-header-row .task-cell{padding:.75rem 0;font-size:.8rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--text-primary)}.task-list-container{flex:1;min-height:0;overflow-y:auto;position:relative}.task-row{padding:.75rem 1.25rem;border-bottom:1px solid var(--border-color);transition:background-color .15s ease;cursor:pointer}.task-row:hover{background-color:var(--bg-secondary)}.task-row:last-child{border-bottom:none}.task-row.keyboard-selected,.task-row.selected{background-color:color-mix(in srgb,var(--accent-blue) 25%,var(--bg-card))}.task-cell{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.task-actions-header{text-align:right}.virtual-scroller-empty{padding:2rem;text-align:center;color:var(--text-secondary)}.event-table tbody tr{cursor:pointer}.task-description{font-weight:600;white-space:normal;display:flex;flex-wrap:wrap;align-items:center;gap:.25rem .5rem}.task-description-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}.task-project{font-size:.85rem;color:var(--text-secondary);white-space:nowrap}.priority-high,.priority-low,.priority-medium{display:inline-block;padding:.25rem .5rem;border-radius:var(--radius-xs);font-weight:700;text-align:center}.priority-high{color:var(--accent-red);background:#fde8ea;background:color-mix(in srgb,var(--accent-red) 15%,var(--bg-card))}.priority-medium{color:var(--accent-yellow);background:#fef8e6;background:color-mix(in srgb,var(--accent-yellow) 15%,var(--bg-card))}.priority-low{color:var(--text-muted);background:var(--bg-secondary)}.sortable{cursor:pointer;user-select:none;white-space:nowrap}.sortable:hover{background:var(--bg-hover)}.sort-arrow{display:inline-block;width:.8em;margin-left:.25rem;opacity:.3}.sort-arrow::after{content:'\2195'}.sortable.sort-asc .sort-arrow::after{content:'\2191'}.sortable.sort-desc .sort-arrow::after{content:'\2193'}.sortable.sort-asc .sort-arrow,.sortable.sort-desc .sort-arrow{opacity:1}.task-overdue .task-description-text{color:var(--accent-red)}.task-overdue .task-due{color:var(--accent-red);font-weight:600}.task-tags{display:flex;gap:.25rem;flex-wrap:wrap}.task-tag{background-color:var(--bg-tertiary);color:var(--text-primary);padding:.125rem .5rem;border-radius:var(--radius-xs);font-size:.75rem;font-weight:600;border:1px solid var(--border-color)}.recurrence-icon{color:var(--accent-purple);font-size:.85rem;font-weight:700}.annotation-badge{background-color:var(--accent-yellow);color:var(--text-primary);padding:.125rem .5rem;border-radius:var(--radius-xs);font-size:.7rem;font-weight:700;border:var(--border-width-sm) solid var(--border-color)}.subtask-badge{background-color:var(--bg-secondary);color:var(--text-primary);padding:.125rem .5rem;border-radius:var(--radius-xs);font-size:.7rem;font-weight:700;border:var(--border-width-sm) solid var(--border-color);margin-left:.25rem}.task-started{border-left:4px solid var(--accent-green)}.task-completed{opacity:.5;text-decoration:line-through}.task-deleted{display:none}.due-overdue{color:var(--accent-red);font-weight:700;background:#fde8ea;background:color-mix(in srgb,var(--accent-red) 15%,var(--bg-card));padding:.25rem .5rem;border-radius:var(--radius-xs)}.due-today{color:var(--accent-yellow);font-weight:700;background:#fef8e6;background:color-mix(in srgb,var(--accent-yellow) 15%,var(--bg-card));padding:.25rem .5rem;border-radius:var(--radius-xs)}.due-soon{color:var(--text-secondary)}.due-future{color:var(--text-muted)}.events-list{display:flex;flex-direction:column;flex:1;min-height:0;gap:1rem}.event-table-virtual{width:100%;background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-lg);overflow:hidden;display:flex;flex-direction:column;flex:1;min-height:0;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.event-header-row,.event-row-virtual{display:grid;grid-template-columns:100px 80px 1fr 150px;align-items:center;gap:.5rem}.event-header-row{background-color:var(--bg-secondary);border-bottom:2px solid var(--border-color);flex-shrink:0}.event-header-row .event-cell{padding:1rem 1.25rem;font-size:.8rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--text-primary)}.event-list-container{flex:1;min-height:0;overflow-y:auto;position:relative}.event-row-virtual{padding:.75rem 1.25rem;border-bottom:1px solid var(--border-color);transition:background-color .15s ease;cursor:pointer}.event-row-virtual:hover{background-color:var(--bg-secondary)}.event-row-virtual:last-child{border-bottom:none}.event-row-virtual.event-past{opacity:.7}.event-cell{overflow:hidden;text-overflow:ellipsis}.event-row{cursor:pointer}.event-cell-date{white-space:nowrap}.event-cell-date .event-date-num{font-weight:700;font-size:.9rem;color:var(--text-primary);margin-right:.5rem}.event-date-badge{display:inline-block;padding:.15rem .4rem;background:var(--accent-green);color:var(--text-on-accent);font-size:.7rem;font-weight:700;text-transform:uppercase;border-radius:var(--radius-xs);margin-right:.5rem}.event-cell-time{font-family:var(--font-mono);font-size:.85rem;color:var(--text-secondary)}.event-cell-title{font-weight:600}.event-cell-location{color:var(--text-secondary);font-size:.875rem}.event-date-badge.event-proximity-today{background:var(--accent-green)}.event-date-badge.event-proximity-tomorrow{background:var(--accent-yellow);color:var(--text-primary)}.event-date-badge.event-proximity-week{background:var(--accent-cyan)}.event-date-badge.event-proximity-future{background:var(--accent-blue)}.event-date-badge.event-proximity-past{background:var(--text-muted)}.event-row.event-past{opacity:.7}.no-upcoming-events{text-align:center;padding:2rem;color:var(--text-secondary);font-style:italic}.past-events-section{margin-top:.5rem}.past-events-toggle{display:flex;align-items:center;gap:.75rem;padding:.75rem 1rem;background:var(--bg-secondary);border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-sm);cursor:pointer;font-weight:600;color:var(--text-secondary);transition:background-color .15s ease,color .15s ease;list-style:none}.past-events-toggle::-webkit-details-marker{display:none}.past-events-toggle::before{content:'▶';font-size:.7rem;transition:transform .15s ease}.past-events-section[open] .past-events-toggle::before{transform:rotate(90deg)}.past-events-toggle:hover{background:var(--bg-tertiary);color:var(--text-primary)}.past-events-label{flex:1}.past-events-count{background:var(--text-muted);color:var(--text-on-accent);font-size:.75rem;padding:.15rem .5rem;border-radius:var(--radius-sm)}.past-events-section .event-table-past{margin-top:.75rem;opacity:.85}.past-events-section .event-list-container{max-height:300px}.event-item{display:flex;gap:1rem;padding:1rem;background-color:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);transition:background-color .15s ease;cursor:pointer}.event-item:hover{background-color:var(--bg-secondary)}.event-date{flex-shrink:0;width:80px;text-align:center;padding:.75rem;background-color:var(--accent-green);border-radius:var(--radius-sm);color:var(--text-on-accent)}.event-date-day{font-size:.7rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em}.event-date-num{font-size:1.5rem;font-weight:700}.event-content{flex:1}.event-title{font-family:var(--font-heading);font-weight:700;font-size:1.1rem;color:var(--text-primary);margin-bottom:.25rem}.event-details{font-size:.875rem;color:var(--text-secondary);display:flex;gap:1rem}.event-location,.event-time{display:flex;align-items:center;gap:.25rem}.event-project{margin-top:.5rem}.email-list{display:flex;flex-direction:column;background-color:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);overflow:hidden;flex:1;min-height:0;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.email-list-container{flex:1;min-height:0;overflow-y:auto;position:relative}.email-item{display:flex;gap:1rem;padding:1rem;border-bottom:2px solid var(--border-color);transition:background-color .15s ease;cursor:pointer}.email-item:last-child{border-bottom:none}.email-item:hover{background-color:var(--bg-secondary)}.email-item.unread{background-color:color-mix(in srgb,var(--accent-blue) 20%,var(--bg-card));border-left:4px solid var(--accent-blue)}.email-item.unread .email-subject{font-weight:700}.email-item.unread .email-from{font-weight:700}.email-item.outgoing{border-left:4px solid var(--accent-green)}.email-checkbox{flex-shrink:0;margin-top:.25rem}.email-content{flex:1;min-width:0}.email-header{display:flex;justify-content:space-between;margin-bottom:.25rem;align-items:center;gap:.5rem}.thread-badge{background-color:var(--bg-tertiary);color:var(--text-secondary);font-size:.7rem;font-weight:600;padding:.1rem .4rem;border-radius:var(--radius-md);min-width:1.25rem;text-align:center}.email-from{color:var(--text-primary);font-size:.9rem;font-weight:600}.email-date{color:var(--text-muted);font-size:.8rem;flex-shrink:0;font-weight:600}.email-subject{color:var(--text-primary);font-size:.95rem;margin-bottom:.25rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.email-preview{color:var(--text-muted);font-size:.85rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}@keyframes toastSlideIn{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.toast-undo{display:flex;align-items:center;gap:1rem}.undo-message{flex:1}.undo-btn{padding:.25rem .75rem;background:var(--accent-blue);color:var(--text-on-accent);border:2px solid var(--border-color);border-radius:var(--radius-sm);font-family:inherit;font-size:var(--font-size-sm);font-weight:600;cursor:pointer;transition:background .15s ease}.undo-btn:hover{background:color-mix(in srgb,var(--accent-blue) 80%,#000)}.undo-countdown{font-size:var(--font-size-sm);color:var(--text-muted);min-width:2.5rem;text-align:right}@keyframes modalFadeIn{from{opacity:0}to{opacity:1}}@keyframes modalSlideIn{from{opacity:0;transform:translateY(-20px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}@keyframes modalFadeOut{from{opacity:1}to{opacity:0}}@keyframes modalSlideOut{from{opacity:1;transform:translateY(0) scale(1)}to{opacity:0;transform:translateY(-20px) scale(.95)}}.modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:var(--overlay-color);display:flex;align-items:center;justify-content:center;z-index:1000;animation:modalFadeIn .15s ease-out}.modal-overlay.hidden{display:none}.modal-overlay.closing{animation:modalFadeOut .15s ease-in forwards}.modal-container{background-color:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-lg);box-shadow:var(--shadow-brutal-xl);max-width:var(--width-modal);width:90%;max-height:90vh;overflow:auto;animation:modalSlideIn .2s ease-out}.modal-container.modal-large{max-width:calc(100vw - 4rem);width:calc(100vw - 4rem);max-height:calc(100vh - 4rem);height:calc(100vh - 4rem);display:flex;flex-direction:column}.modal-container.modal-large .modal-content{flex:1;overflow:auto;display:flex;flex-direction:column}.modal-overlay.closing .modal-container{animation:modalSlideOut .15s ease-in forwards}.modal-header{display:flex;justify-content:space-between;align-items:center;padding:1rem 1.5rem;border-bottom:var(--border-width) solid var(--border-color);background:var(--bg-secondary)}.modal-header h2,.modal-title{font-family:var(--font-heading);font-size:1.25rem;font-weight:700}.modal-close{background:var(--bg-card);border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-sm);font-size:1.25rem;color:var(--text-primary);cursor:pointer;line-height:1;width:36px;height:36px;display:flex;align-items:center;justify-content:center;transition:background-color .15s ease}.modal-close:hover{background:var(--accent-blue);color:var(--text-on-accent)}.modal-content{padding:1.5rem}.form-group{margin-bottom:1.25rem}.form-label{display:block;font-size:.9rem;font-weight:700;color:var(--text-primary);margin-bottom:.5rem}.form-input,.form-select,.form-textarea{width:100%;padding:.75rem 1rem;border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-sm);background-color:var(--bg-card);color:var(--text-primary);font-size:1rem;box-shadow:none}.form-input:focus,.form-select:focus,.form-textarea:focus{outline:0;background-color:var(--bg-card);box-shadow:0 0 0 2px var(--accent-blue)}.form-textarea{min-height:100px;resize:vertical}.form-actions{display:flex;justify-content:flex-end;gap:.75rem;margin-top:1.5rem}.form-input[aria-invalid=true],.form-select[aria-invalid=true],.form-textarea[aria-invalid=true]{border-color:var(--accent-red);box-shadow:0 0 0 2px color-mix(in srgb,var(--accent-red) 30%,transparent)}.form-input[aria-invalid=true]:focus,.form-select[aria-invalid=true]:focus,.form-textarea[aria-invalid=true]:focus{box-shadow:0 0 0 2px var(--accent-red)}.form-error{color:var(--accent-red);font-size:.8rem;font-weight:600;margin-top:.25rem;display:none}.form-error.visible{display:block}.app-footer{background-color:var(--bg-card);border-top:var(--border-width) solid var(--border-color);padding:.75rem 1.5rem}.footer-content{max-width:var(--width-container);margin:0 auto;display:flex;justify-content:space-between;align-items:center}.keyboard-hints{display:flex;gap:1rem;font-size:.8rem;color:var(--text-muted)}kbd{display:inline-block;padding:.2rem .5rem;background-color:var(--bg-secondary);border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-xs);font-family:var(--font-mono);font-size:.75rem;font-weight:700}.version{font-size:.75rem;color:var(--text-muted);font-weight:600}.empty-state{text-align:center;padding:3rem;color:var(--text-secondary)}.empty-state-icon{font-size:4rem;margin-bottom:1rem}.empty-state-text{font-size:1.1rem;font-weight:600;margin-bottom:1rem}.error-state{text-align:center;padding:2rem;color:var(--accent-red);background:color-mix(in srgb,var(--accent-red) 10%,var(--bg-card));border:var(--border-width-sm) solid var(--accent-red);border-radius:var(--radius-sm);font-weight:600}.view{display:block}.view.hidden{display:none}.filter-bar{display:flex;flex-wrap:wrap;gap:.75rem;margin-bottom:1.5rem;padding:1rem;background-color:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.filter-group{display:flex;align-items:center;gap:.5rem}.filter-label{font-size:.8rem;font-weight:700;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.05em}.filter-select{padding:.5rem .75rem;border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-sm);background-color:var(--bg-card);color:var(--text-primary);font-size:.875rem;font-weight:600}.filter-select:focus{outline:0;background-color:var(--accent-blue);color:var(--text-on-accent)}.filter-checkbox{display:flex;align-items:center;gap:.4rem;font-size:.875rem;font-weight:600;color:var(--text-primary);cursor:pointer}.filter-checkbox input[type=checkbox]{width:1rem;height:1rem;cursor:pointer}.btn-link{background:0 0;border:none;box-shadow:none;color:var(--text-secondary);font-size:.875rem;cursor:pointer;text-decoration:underline;padding:.5rem}.btn-link:hover{box-shadow:none;transform:none;color:var(--text-primary)}@media (min-width:1400px){.main-content{max-width:1600px}.cards-grid{grid-template-columns:repeat(auto-fill,minmax(380px,1fr))}.project-dashboard-grid{gap:2rem}.day-plan-sidebar{width:320px}.modal-container{max-width:640px}}@media (max-width:1024px){.saved-views-sidebar{width:180px}.day-plan-sidebar{width:240px}.project-dashboard-grid{grid-template-columns:1fr 1fr;gap:1rem}.project-dashboard-grid .dashboard-column:last-child{grid-column:span 2}.filter-bar{flex-wrap:wrap}.filter-actions{width:100%;justify-content:flex-end;margin-top:.5rem}}@media (max-width:768px){.tab-navigation{flex-wrap:wrap;gap:.5rem}.tab{flex:1 1 auto;min-width:calc(33% - .5rem);justify-content:center;padding:.625rem .75rem}.tab-label{display:none}.tab-icon{font-size:1.25rem}.cards-grid{grid-template-columns:1fr}.task-table{font-size:.85rem}.task-header-row,.task-row{grid-template-columns:1fr 80px 40px 80px}.task-header-row .task-cell:nth-child(n+5),.task-row .task-cell:nth-child(n+5){display:none}.filter-bar{flex-direction:column}.keyboard-hints{display:none}.page-title{font-size:1.5rem}.saved-views-sidebar{display:none}.day-plan-content{flex-direction:column}.day-plan-sidebar{width:100%;max-height:200px}.project-dashboard-grid{grid-template-columns:1fr}.project-dashboard-grid .dashboard-column:last-child{grid-column:span 1}.modal-container{width:95%;max-height:95vh}.bulk-actions-bar{flex-wrap:wrap}.bulk-select-all{width:100%;margin-top:.5rem}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.pagination-controls{display:flex;align-items:center;justify-content:center;gap:1rem;padding:1rem;margin-top:1rem;background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md)}.pagination-info{font-weight:600;color:var(--text-secondary);font-size:.9rem}.pagination-controls .btn:disabled{opacity:.5;cursor:not-allowed}.btn:focus-visible,.card:focus-visible,.dashboard-item:focus-visible,.email-item:focus-visible,.event-row-virtual:focus-visible,.filter-select:focus-visible,.form-input:focus-visible,.form-select:focus-visible,.form-textarea:focus-visible,.modal-close:focus-visible,.saved-view-item:focus-visible,.snooze-option:focus-visible,.tab:focus-visible,.task-row:focus-visible,.timeline-item:focus-visible,.unscheduled-task:focus-visible{outline:3px solid var(--accent-blue);outline-offset:2px}.event-row,.task-row-clickable{cursor:pointer}.skip-link{position:absolute;top:-100px;left:0;background:var(--accent-blue);color:var(--text-on-accent);padding:.75rem 1.5rem;z-index:9999;font-weight:700;border:var(--border-width) solid var(--border-color);text-decoration:none}.skip-link:focus{top:0}.source-email-link{padding:.75rem;background:var(--bg-secondary);border-radius:var(--radius-sm);border-left:4px solid var(--accent-blue)}.thread-message{margin-bottom:1rem;padding:.75rem;background:var(--bg-secondary);border-radius:var(--radius-sm)}.thread-message-latest{border-left:3px solid var(--accent-blue)}.thread-message-header{display:flex;justify-content:space-between;margin-bottom:.5rem;font-size:.8rem;color:var(--text-secondary)}.thread-message-from{font-weight:700}.email-reader-body{white-space:pre-wrap;font-size:.9rem;line-height:1.6;color:var(--text-primary);word-wrap:break-word;overflow-wrap:break-word}.email-reader-body .email-link{color:var(--accent-blue);text-decoration:underline;cursor:pointer;word-break:break-all}.email-reader-body .email-link:hover{color:var(--accent-cyan)}.email-reader-body hr{border:none;border-top:2px solid var(--border-color);margin:1rem 0}.email-reader-quote{border-left:3px solid var(--text-muted);padding-left:1rem;margin:.5rem 0;color:var(--text-secondary);font-style:italic}.email-reader-container{display:flex;flex-direction:column;height:100%;min-height:0}.email-reader-header{margin-bottom:1rem;padding-bottom:.75rem;border-bottom:1px solid var(--border-color)}.email-sender-contact{display:flex;align-items:center;gap:.5rem;margin-top:.5rem;padding:.4rem .5rem;background:var(--bg-tertiary);border-radius:4px}.email-sender-info{display:flex;flex-direction:column;flex:1;min-width:0}.email-sender-name{font-weight:600;font-size:.85rem}.email-sender-company{font-size:.75rem;color:var(--text-secondary)}.contact-avatar-sm{width:32px;height:32px;border-radius:50%;background:var(--accent-color);color:var(--bg-primary);display:flex;align-items:center;justify-content:center;font-size:.75rem;font-weight:700;flex-shrink:0}.contact-avatar-unknown{background:var(--bg-secondary);color:var(--text-secondary);border:var(--border-width-sm) solid var(--border-color)}.email-reader-thread{flex:1;overflow-y:auto;margin-bottom:1rem;min-height:0}.dropdown{position:relative;display:inline-block}.dropdown-menu{display:none;position:absolute;bottom:100%;left:0;margin-bottom:.25rem;background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);box-shadow:var(--shadow-brutal-md);min-width:160px;z-index:100}.dropdown-menu.show{display:block}.dropdown-item{display:block;width:100%;padding:.5rem 1rem;text-align:left;background:0 0;border:none;cursor:pointer;font-size:.875rem;color:var(--text-primary)}.dropdown-item:hover{background:var(--bg-secondary)}.dropdown-item:first-child{border-radius:var(--radius-md) var(--radius-md) 0 0}.dropdown-item:last-child{border-radius:0 0 var(--radius-md) var(--radius-md)}.context-menu{position:fixed;z-index:10000;min-width:180px;max-width:280px;background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-sm);box-shadow:var(--shadow-brutal-lg);padding:.25rem 0;display:none}.context-menu.visible{display:block}.context-menu-item{display:flex;align-items:center;gap:.75rem;padding:.5rem 1rem;font-size:.875rem;font-weight:500;color:var(--text-primary);cursor:pointer;border:none;background:0 0;width:100%;text-align:left;transition:background .1s}.context-menu-item:focus,.context-menu-item:hover{background:var(--accent-blue);color:var(--text-on-accent);outline:0}.context-menu-item:focus-visible{outline:2px solid var(--accent-blue);outline-offset:-2px}.context-menu-item-icon{width:1.25rem;text-align:center;flex-shrink:0}.context-menu-item-label{flex:1}.context-menu-item-shortcut{font-size:.75rem;color:var(--text-muted);font-family:var(--font-mono)}.context-menu-item--danger{color:var(--accent-red)}.context-menu-item--danger:hover{background:var(--accent-red);color:var(--text-on-accent)}.context-menu-separator{height:2px;background:var(--border-color);margin:.25rem .5rem}.context-menu-hint{padding:.35rem 1rem;font-size:.7rem;color:var(--text-muted);border-top:1px solid var(--border-color);margin-top:.25rem}::-webkit-scrollbar{width:12px;height:12px}::-webkit-scrollbar-track{background:var(--bg-secondary);border-left:2px solid var(--border-color)}::-webkit-scrollbar-thumb{background:var(--text-muted);border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-sm)}::-webkit-scrollbar-thumb:hover{background:var(--text-secondary)}.loading{display:flex;justify-content:center;align-items:center;height:200px;color:var(--text-secondary);font-family:var(--font-heading)}.skeleton-shimmer{display:flex;flex-direction:column;gap:1rem;padding:1rem}.skeleton-shimmer .skeleton-row{display:flex;align-items:center;gap:.75rem;padding:.75rem;background:var(--bg-card);border-radius:var(--radius-md);border:var(--border-width) solid var(--border-color)}.skeleton-shimmer .skeleton-avatar{width:36px;height:36px;border-radius:var(--radius-full);background:linear-gradient(90deg,var(--bg-secondary) 25%,var(--bg-tertiary) 50%,var(--bg-secondary) 75%);background-size:200% 100%;animation:skeleton-pulse 1.5s ease-in-out infinite;flex-shrink:0}.skeleton-shimmer .skeleton-lines{flex:1;display:flex;flex-direction:column;gap:.4rem}.skeleton-shimmer .skeleton-line{height:.75rem;border-radius:var(--radius-sm);background:linear-gradient(90deg,var(--bg-secondary) 25%,var(--bg-tertiary) 50%,var(--bg-secondary) 75%);background-size:200% 100%;animation:skeleton-pulse 1.5s ease-in-out infinite}.skeleton-shimmer .skeleton-line.short{width:40%}.skeleton-shimmer .skeleton-line.medium{width:65%}.skeleton-shimmer .skeleton-line.long{width:90%}@keyframes skeleton-pulse{0%{background-position:200% 0}100%{background-position:-200% 0}}@keyframes spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}.spinner{display:inline-block;width:1em;height:1em;border:2px solid currentColor;border-top-color:transparent;border-radius:var(--radius-full);animation:spin .8s linear infinite}.btn-loading{position:relative;pointer-events:none;opacity:.8}.btn-loading .btn-text{visibility:hidden}.btn-loading::after{content:'';position:absolute;left:50%;top:50%;width:1em;height:1em;margin-left:-.5em;margin-top:-.5em;border:2px solid currentColor;border-top-color:transparent;border-radius:var(--radius-full);animation:spin .8s linear infinite}.hidden{display:none!important}.project-dashboard-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:1.5rem;flex:1;min-height:0}.dashboard-column{background:var(--bg-card);border:var(--border-width) solid var(--border-color);padding:1rem;display:flex;flex-direction:column;overflow:hidden}.dashboard-column-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;padding-bottom:.5rem;border-bottom:2px solid var(--border-color)}.dashboard-column-header h3{margin:0;font-size:1rem;font-family:var(--font-heading);font-weight:700}.dashboard-list{flex:1;overflow-y:auto}.dashboard-item{padding:.75rem;background:var(--bg-card);border:var(--border-width-sm) solid var(--border-color);margin-bottom:.5rem;cursor:pointer;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color);transition:transform .15s ease,box-shadow .15s ease,background-color .15s ease}.dashboard-item:hover{background:var(--bg-secondary);transform:translate(-2px,-2px);box-shadow:calc(var(--shadow-offset) + 2px) calc(var(--shadow-offset) + 2px) 0 var(--border-color)}.dashboard-item-title{font-weight:600;margin-bottom:.25rem}.dashboard-item-meta{font-size:.75rem;color:var(--text-secondary)}.empty-dashboard-list{text-align:center;padding:2rem 1rem;color:var(--text-secondary)}.task-badges{display:flex;gap:.25rem;margin-top:.25rem}.task-badge{font-size:.65rem;padding:.15rem .4rem;border:var(--border-width-sm) solid var(--border-color);background:var(--bg-secondary);color:var(--text-primary);font-weight:600}.task-badge.has-items{background:var(--accent-blue);color:var(--text-on-accent)}.task-badge.recurrence{background:var(--accent-purple);color:var(--text-on-accent)}.task-row-clickable{cursor:pointer;transition:background .1s}.task-row-clickable:hover{background:var(--bg-secondary)}.progress-bar-container{width:100%;height:10px;background:var(--bg-secondary);border:var(--border-width-sm) solid var(--border-color);overflow:hidden}.progress-bar{height:100%;background:var(--accent-green);transition:width .3s ease}.no-subtasks{color:var(--text-secondary);font-size:.875rem}#day-plan-view{display:flex;flex-direction:column;flex:1;min-height:0}#day-plan-view .page-header{flex-shrink:0}.day-plan-nav{display:flex;align-items:center;gap:.5rem}.day-plan-date-picker{padding:.5rem;background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-sm);color:var(--text-primary);font-family:var(--font-body)}.day-plan-date-display{font-size:1.25rem;font-weight:700;margin-left:1rem;font-family:var(--font-heading);line-height:1}.day-plan-content{flex:1;min-height:0;display:flex;gap:1.5rem}.day-plan-main{flex:1;min-height:0;display:flex;flex-direction:column;background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);overflow:hidden;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.day-plan-sidebar{width:280px;flex-shrink:0;display:flex;flex-direction:column;background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);overflow:hidden;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.sidebar-header{padding:1rem;border-bottom:2px solid var(--border-color);flex-shrink:0}.sidebar-header h3{margin:0;font-size:1rem;font-family:var(--font-heading);font-weight:700}.sidebar-task-list{flex:1;overflow-y:auto;padding:.75rem;display:flex;flex-direction:column;gap:.5rem}.timeline-container{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden}.timeline-scroll-area{position:relative;padding:.5rem 1rem 3rem .5rem;min-height:min-content}#timeline-slots{position:relative}#timeline-items{position:absolute;top:.5rem;left:.5rem;right:1rem;bottom:0;pointer-events:none}#timeline-items .timeline-item{pointer-events:auto}.timeline-slot{display:grid;grid-template-columns:50px 1fr;height:12px;position:relative}.timeline-slot.hour-start .timeline-slot-area{border-top:1px dashed color-mix(in srgb,var(--border-color) 50%,transparent)}.timeline-time{font-size:.7rem;color:var(--text-secondary);padding-right:.5rem;text-align:right;font-weight:500;transform:translateY(-.5em)}.timeline-slot-area{position:relative}.timeline-slot-area:hover{background:var(--bg-secondary)}.timeline-item{position:absolute;left:60px;right:10px;border:var(--border-width) solid var(--border-color);border-radius:var(--radius-sm);padding:.25rem .5rem;overflow:hidden;cursor:pointer;z-index:10}.timeline-item.task{background:var(--accent-green);color:var(--text-primary)}.timeline-item.event{background:var(--accent-blue);color:var(--text-on-accent)}.timeline-item.block{opacity:.85}.timeline-item.block-free_time{background:var(--accent-cyan);color:var(--text-primary)}.timeline-item.block-personal{background:var(--accent-yellow);color:var(--text-primary)}.timeline-item.block-vacation{background:var(--accent-purple);color:var(--text-on-accent)}.timeline-item.block-focus{background:var(--accent-red);color:var(--text-on-accent)}.timeline-item.conflict{box-shadow:0 0 0 3px var(--accent-red)}.timeline-item.selected{box-shadow:0 0 0 3px var(--bg-card),0 0 0 6px var(--accent-blue)}.timeline-item-title{font-weight:600;font-size:.75rem;line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.timeline-item-meta{font-size:.65rem;opacity:.85;line-height:1.1}.timeline-current-time{position:absolute;left:50px;right:0;height:2px;background:var(--accent-red);z-index:20;pointer-events:none}.timeline-current-time::before{content:'';position:absolute;left:-4px;top:-3px;width:8px;height:8px;background:var(--accent-red);border-radius:var(--radius-full)}.timeline-paint-preview{position:absolute;left:70px;right:10px;background:var(--accent-blue);opacity:.4;border:var(--border-width-sm) dashed var(--border-color);border-radius:var(--radius-sm);z-index:5;pointer-events:none}.timeline-container.is-painting{cursor:crosshair;user-select:none}.timeline-container.is-painting .timeline-slot-area{pointer-events:none}.unscheduled-task{padding:.75rem;background:var(--bg-card);border:var(--border-width-sm) solid var(--border-color);border-left:6px solid var(--accent-green);border-radius:var(--radius-sm);cursor:grab;transition:background-color .1s}.unscheduled-task:hover{background:var(--bg-secondary)}.unscheduled-task.priority-high{border-left-color:var(--accent-red)}.unscheduled-task.priority-medium{border-left-color:var(--accent-yellow)}.unscheduled-task.priority-low{border-left-color:var(--accent-green)}.unscheduled-task-title{font-weight:600;margin-bottom:.25rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.unscheduled-task-meta{font-size:.75rem;color:var(--text-secondary)}.empty-unscheduled{text-align:center;color:var(--text-secondary);padding:2rem 1rem}.settings-btn{background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-sm);color:var(--text-primary);font-size:1.25rem;cursor:pointer;padding:.5rem .75rem;margin-left:.5rem;transition:background-color .1s}.settings-btn:hover{background:var(--bg-secondary)}.settings-btn:active{background:var(--bg-tertiary)}.shortcut-hint-btn{font-family:var(--font-mono, monospace);font-weight:700;min-width:2rem;text-align:center;padding:.5rem}.settings-section h3{font-size:1rem;color:var(--text-primary)}.settings-section .form-hint{font-size:.75rem;color:var(--text-secondary)}.sync-indicator{background:0 0;border:none;cursor:pointer;padding:.25rem .5rem;display:flex;align-items:center}.sync-dot{width:8px;height:8px;border-radius:var(--radius-full);background:var(--text-muted);transition:background var(--transition-slow)}.sync-dot.connected{background:var(--accent-green)}.sync-dot.syncing{background:var(--accent-blue);animation:sync-pulse 1s infinite}.sync-dot.error{background:var(--accent-red)}@keyframes sync-pulse{0%,100%{opacity:1}50%{opacity:.4}}.snooze-options{display:flex;flex-direction:column;gap:.5rem}.snooze-option{display:flex;justify-content:space-between;align-items:center;padding:.75rem 1rem;background:var(--bg-secondary);border:var(--border-width-sm) solid var(--border-color);color:var(--text-primary);cursor:pointer;transition:background-color .1s;text-align:left;width:100%}.snooze-option:hover{background:var(--accent-blue);color:var(--text-on-accent)}.snooze-option-label{font-weight:600}.snooze-option-time{font-size:.75rem;color:var(--text-secondary)}.snooze-option:hover .snooze-option-time{color:var(--text-on-accent)}.snooze-custom{margin-top:.5rem;padding-top:.5rem;border-top:2px solid var(--border-color)}.snooze-badge{display:inline-block;font-size:.65rem;padding:.15rem .4rem;border:var(--border-width-sm) solid var(--border-color);background:var(--accent-yellow);color:var(--text-primary);font-weight:700;margin-top:.25rem}.contact-badge{display:inline-block;font-size:.65rem;padding:.15rem .4rem;border:var(--border-width-sm) solid var(--border-color);background:var(--accent-color);color:var(--bg-primary);font-weight:700;margin-top:.25rem}.bulk-checkbox{width:18px;height:18px;cursor:pointer;accent-color:var(--accent-blue);border:var(--border-width-sm) solid var(--border-color)}.task-actions-cell{text-align:right;white-space:nowrap;display:flex;align-items:center;justify-content:flex-end;gap:.5rem}.task-actions-cell .bulk-checkbox{margin-right:.5rem}.task-recurrence{font-size:.85rem;color:var(--text-secondary)}.task-due{white-space:nowrap}.bulk-actions-bar{display:flex;align-items:center;gap:.5rem;padding:.75rem 1rem;background:var(--accent-blue);color:var(--text-on-accent);border:var(--border-width) solid var(--border-color);box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color);margin-bottom:1rem;color:var(--text-primary)}.bulk-actions-bar.hidden{display:none}.bulk-count{font-weight:700;margin-right:1rem;font-family:var(--font-heading)}.bulk-actions-bar .btn{background:var(--bg-card);border:var(--border-width-sm) solid var(--border-color);color:var(--text-primary)}.bulk-actions-bar .btn:hover{background:var(--bg-secondary)}.bulk-select-all{margin-left:auto}.email-checkbox-cell{padding:.75rem .5rem;display:flex;align-items:center}.email-item-with-checkbox{display:flex;align-items:flex-start}.email-item-with-checkbox .email-content{flex:1}.schedule-task-btn{display:flex;align-items:center;gap:.5rem}.time-block-form{display:flex;flex-direction:column;gap:1rem}.time-block-quick-options{display:grid;grid-template-columns:repeat(3,1fr);gap:.5rem}.time-block-quick-btn{padding:.5rem;background:var(--bg-secondary);border:var(--border-width-sm) solid var(--border-color);color:var(--text-primary);cursor:pointer;font-size:.875rem;font-weight:600;transition:background-color .1s}.time-block-quick-btn:hover{background:var(--bg-tertiary)}.time-block-quick-btn.selected{background:var(--accent-blue);color:var(--text-on-accent);box-shadow:inset 0 0 0 2px var(--border-color)}.duration-presets{display:flex;gap:.5rem;flex-wrap:wrap}.duration-preset{padding:.35rem .75rem;background:var(--bg-secondary);border:var(--border-width-sm) solid var(--border-color);color:var(--text-primary);cursor:pointer;font-size:.75rem;font-weight:600;transition:background-color .1s}.duration-preset:hover{background:var(--bg-tertiary)}.duration-preset.selected{background:var(--accent-blue);color:var(--text-on-accent)}.conflict-warning{padding:.75rem;background:var(--accent-red);border:var(--border-width) solid var(--border-color);color:var(--text-on-accent);font-size:.875rem;font-weight:600;margin-top:.5rem}.app-body{display:flex;flex:1;min-height:0;overflow:hidden}.app-body .main-content{flex:1;min-width:0;display:flex;flex-direction:column;overflow-x:visible;overflow-y:auto}#emails-view,#events-view,#projects-view,#tasks-view{padding-bottom:2.5rem}#tasks-view{display:flex;flex-direction:column;flex:1;min-height:0}#tasks-view .bulk-actions-bar,#tasks-view .filter-bar,#tasks-view .page-header{flex-shrink:0}#events-view{display:flex;flex-direction:column;flex:1;min-height:0}#events-view .page-header{flex-shrink:0}#emails-view{display:flex;flex-direction:column;flex:1;min-height:0}#emails-view .bulk-actions-bar,#emails-view .page-header{flex-shrink:0}.saved-views-sidebar{width:200px;flex-shrink:0;background:var(--bg-card);border-right:var(--border-width) solid var(--border-color);display:flex;flex-direction:column;overflow:hidden}.sidebar-section{display:flex;flex-direction:column;flex:1;min-height:0}.sidebar-section-header{display:flex;justify-content:space-between;align-items:center;padding:.75rem 1rem;font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--text-secondary);border-bottom:2px solid var(--border-color);background:var(--bg-secondary)}.btn-icon{background:0 0;border:none;color:var(--text-muted);cursor:pointer;padding:.25rem;font-size:.875rem;line-height:1}.btn-icon:hover{color:var(--text-primary)}.pinned-views-list{flex:1;overflow-y:auto;padding:.5rem}.sidebar-empty{text-align:center;padding:1.5rem .5rem;color:var(--text-muted);font-size:.8rem}.saved-view-item{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;margin-bottom:.5rem;background:var(--bg-card);border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-sm);cursor:pointer;font-size:.85rem;font-weight:600;color:var(--text-primary);box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color);transition:transform .15s ease,box-shadow .15s ease,background-color .15s ease,color .15s ease}.saved-view-item:hover{background:var(--accent-blue);color:var(--text-on-accent)}.saved-view-item.active{background:var(--accent-blue);color:var(--text-on-accent);box-shadow:inset 0 0 0 2px var(--border-color)}.saved-view-item .view-icon{font-size:.75rem}.saved-view-item .view-name{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.saved-view-item .view-actions{opacity:0;transition:opacity .1s}.saved-view-item:hover .view-actions{opacity:1}.filter-actions{display:flex;gap:.5rem;margin-left:auto}.contact-avatar{width:40px;height:40px;min-width:40px;border-radius:50%;background-color:var(--accent-blue);color:var(--text-on-accent);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:.85rem;font-family:var(--font-heading);border:2px solid var(--border-color)}.contact-avatar-lg{width:60px;height:60px;min-width:60px;font-size:1.2rem}.contact-card .card-header{display:flex;align-items:center}.contact-nickname{display:block;font-size:.85rem;color:var(--text-secondary);font-style:italic}.contact-company{display:block;font-size:.85rem;color:var(--text-secondary)}.contact-email{font-size:.85rem;color:var(--text-secondary)}.contact-detail .detail-row{margin-bottom:.5rem;font-size:.9rem}.contact-detail .contact-info-section{margin-bottom:1rem;padding-bottom:1rem;border-bottom:1px solid var(--border-light,#e0e0e0)}.contact-detail .contact-notes{margin-bottom:1.5rem}.contact-detail .contact-notes p{margin-top:.25rem;white-space:pre-wrap;color:var(--text-secondary)}.sub-collection{margin-bottom:1.25rem}.sub-collection-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:.5rem}.sub-collection-header h4{margin:0;font-size:.95rem;font-weight:600}.sub-item{display:flex;justify-content:space-between;align-items:center;padding:.4rem 0;border-bottom:1px solid var(--border-light,#e0e0e0);font-size:.9rem}.sub-item:last-child{border-bottom:none}.sub-empty{font-size:.85rem;color:var(--text-secondary);font-style:italic;padding:.25rem 0}.edit-sub-collections{border-top:1px solid var(--border-color);padding-top:1rem;margin-bottom:.5rem}.edit-sub-section{margin-bottom:.75rem}.edit-sub-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:.25rem}.sub-item-compact{font-size:.85rem;color:var(--text-secondary);padding:.125rem 0}@media print{.btn,.context-menu,.filter-bar,.keyboard-hints,.modal-overlay,.pagination,.sidebar,.tabs,.toast{display:none!important}body{background:#fff;color:#000}.main-content{margin:0;padding:0;max-width:100%}.view{padding:0}.data-table{border:1px solid #333;box-shadow:none}.data-table td,.data-table th{border:1px solid #ccc;padding:.5rem}.data-table td,.data-table th{display:table-cell!important}.data-table tbody tr:hover{background:0 0}.task-table{border:1px solid #333;box-shadow:none}.task-list-container{height:auto!important;overflow:visible!important}.task-header-row,.task-row{grid-template-columns:1fr 100px 40px 80px 60px 80px 60px!important}.task-header-row .task-cell,.task-row .task-cell{display:block!important;border:1px solid #ccc;padding:.25rem .5rem}.task-row:hover{background:0 0}.virtual-scroller-spacer-bottom,.virtual-scroller-spacer-top{display:none!important}a{color:#000;text-decoration:underline}.view-header{page-break-after:avoid}.data-table{page-break-inside:avoid}}.weekly-review-content{max-width:900px;margin:0 auto;padding:1rem}.weekly-review-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1.5rem;padding-bottom:1rem;border-bottom:var(--border-width-sm) solid var(--border-color)}.week-info{display:flex;align-items:center;gap:1rem}.week-dates{font-family:var(--font-heading);font-size:1.25rem;font-weight:700;color:var(--text-primary)}.review-status{padding:.25rem .75rem;border-radius:var(--radius-xs);font-size:.875rem;font-weight:600;border:var(--border-width-sm) solid var(--border-color)}.review-status.completed{background:var(--accent-green);color:var(--text-on-accent)}.review-status.pending{background:var(--accent-yellow);color:var(--text-primary)}.stat-cards{display:flex;gap:1rem;margin-bottom:1rem;flex-wrap:wrap}.stat-card{flex:1;min-width:100px;max-width:150px;padding:1rem;background:var(--bg-card);border:var(--border-width) solid var(--border-color);text-align:center;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.stat-card .stat-number{display:block;font-family:var(--font-heading);font-size:2rem;font-weight:700;color:var(--accent-blue);line-height:1}.stat-card .stat-label{display:block;font-size:.75rem;font-weight:600;color:var(--text-muted);margin-top:.25rem;text-transform:uppercase;letter-spacing:.5px}.stat-card.stat-warning .stat-number{color:var(--accent-yellow)}.stat-card.stat-danger .stat-number{color:var(--accent-red)}.review-section{background:var(--bg-card);border:var(--border-width) solid var(--border-color);padding:1.25rem;margin-bottom:1.5rem;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.section-title{font-family:var(--font-heading);font-size:1.125rem;font-weight:700;color:var(--text-primary);margin-bottom:1rem;padding-bottom:.5rem;border-bottom:var(--border-width-sm) solid var(--border-color)}.review-details{margin-top:.75rem}.review-details summary{cursor:pointer;font-weight:600;color:var(--text-secondary);padding:.5rem;background:var(--bg-secondary);border:var(--border-width-sm) solid var(--border-color);user-select:none}.review-details summary:hover{background:var(--bg-tertiary)}.review-details[open] summary{margin-bottom:.5rem}.review-event-list,.review-task-list{list-style:none;padding:0;margin:0}.review-event-item,.review-task-item{display:flex;align-items:center;gap:.75rem;padding:.5rem .75rem;border-bottom:1px solid var(--border-color)}.review-event-item:last-child,.review-task-item:last-child{border-bottom:none}.review-event-item .event-title,.review-task-item .task-description{flex:1;color:var(--text-primary)}.event-time{font-size:.875rem;font-weight:600;color:var(--text-muted);min-width:80px}.project-badge{font-size:.75rem;padding:.125rem .5rem;background:var(--bg-tertiary);border:1px solid var(--border-color);color:var(--text-secondary)}.due-badge{font-size:.75rem;padding:.125rem .5rem;background:var(--bg-secondary);border:1px solid var(--border-color);color:var(--text-secondary)}.due-badge.overdue{background:var(--accent-red);color:var(--text-on-accent);border-color:var(--accent-red)}.focus-section{background:linear-gradient(135deg,var(--bg-card) 0,color-mix(in srgb,var(--accent-yellow) 15%,var(--bg-card)) 100%)}.focus-task-list{list-style:none;padding:0;margin:0 0 1rem 0}.focus-task-list.available{opacity:.8}.focus-toggle{background:0 0;border:none;font-size:1.25rem;cursor:pointer;color:var(--text-muted);padding:0;line-height:1;transition:transform .15s ease}.focus-toggle:hover{transform:scale(1.2)}.focus-toggle.focused{color:var(--accent-yellow)}.review-task-item.focused{background:color-mix(in srgb,var(--accent-yellow) 10%,var(--bg-card))}.no-focus-message{color:var(--text-muted);font-style:italic;margin-bottom:1rem}.focused-projects{display:flex;gap:.5rem;flex-wrap:wrap;margin-bottom:1rem}.project-tag{background:var(--accent-blue);color:var(--text-on-accent);padding:.25rem .75rem;font-size:.875rem;font-weight:600;border:var(--border-width-sm) solid var(--border-color)}.notes-section{background:var(--bg-card)}.review-notes-input{width:100%;padding:.75rem;font-family:var(--font-mono);font-size:.9rem;border:var(--border-width-sm) solid var(--border-color);background:var(--bg-secondary);color:var(--text-primary);resize:vertical;min-height:100px}.review-notes-input:focus{outline:0;background:var(--bg-card);box-shadow:inset 0 0 0 2px var(--accent-blue)}.review-actions{margin-top:1rem;text-align:center}.tab-badge{display:inline-block;width:8px;height:8px;background:var(--accent-red);border-radius:var(--radius-full);margin-left:.5rem;vertical-align:middle;animation:pulse-badge 2s infinite}@keyframes pulse-badge{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.6;transform:scale(.8)}}.tab-status-dot{width:8px;height:8px;border-radius:50%;display:inline-block;margin-left:.5rem;vertical-align:middle;transition:background-color .3s ease}.tab-status-dot.status-none{display:none}.tab-status-dot.status-green{background-color:var(--accent-green)}.tab-status-dot.status-yellow{background-color:var(--accent-yellow);animation:pulse-badge 2s ease-in-out infinite}.tab-status-dot.status-red{background-color:var(--accent-red);animation:pulse-badge 1.5s ease-in-out infinite}.review-grid{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem;max-width:1200px;margin:0 auto}.review-card{background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);padding:1.5rem;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.review-card .card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;padding-bottom:.75rem;border-bottom:var(--border-width-sm) solid var(--bg-secondary)}.review-card .card-title{font-family:var(--font-heading);font-size:1.1rem;font-weight:700;display:flex;align-items:center;gap:.5rem}.review-card .card-icon{font-size:1.25rem}.review-card .card-badge{font-size:.8rem;padding:.25rem .75rem;border-radius:var(--radius-md);font-weight:600}.week-timeline{grid-column:1/-1}.timeline-visual{display:flex;gap:.5rem;margin-top:1rem}.timeline-day{flex:1;text-align:center;padding:.75rem .5rem;background:var(--bg-secondary);border-radius:var(--radius-md);border:1px solid var(--border-color);position:relative}.timeline-day.today{background:var(--accent-blue);color:var(--text-on-accent);border-width:2px;font-weight:700}.timeline-day.past{opacity:.7}.timeline-day.future{background:var(--bg-card)}.timeline-day .day-name{font-size:.7rem;font-weight:600;text-transform:uppercase;color:var(--text-muted)}.timeline-day .day-number{font-size:1.1rem;font-weight:700}.day-dots{display:flex;justify-content:center;gap:3px;margin-top:.5rem;min-height:8px}.day-dot{width:8px;height:8px;border-radius:var(--radius-full)}.day-dot.task{background:var(--accent-blue)}.day-dot.event{background:var(--accent-purple)}.day-dot.completed{background:var(--accent-green)}.day-dot.overdue{background:var(--accent-red)}.day-dot.vacation-off{background:var(--text-muted);opacity:.5;width:12px;height:4px;border-radius:2px}.day-events{display:flex;flex-direction:column;gap:2px;margin-top:.5rem;text-align:left}.day-event{font-size:.6rem;line-height:1.3;padding:1px 4px;border-left:2px solid var(--accent-purple);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--text-secondary)}.day-event .event-time{font-size:.55rem;font-weight:600;color:var(--accent-purple);margin-right:2px;min-width:auto}.day-event-more{font-size:.55rem;color:var(--text-muted);padding:1px 4px;font-style:italic}.week-timeline-events{grid-column:1/-1}.timeline-events-day{margin-bottom:.75rem}.timeline-events-day:last-child{margin-bottom:0}.timeline-events-day-label{font-family:var(--font-heading);font-size:.8rem;font-weight:700;color:var(--text-secondary);margin-bottom:.25rem;text-transform:uppercase}.vacation-toggles-section{margin-top:1rem;padding-top:1rem;border-top:2px solid var(--border-color)}.vacation-toggles-section h3{margin:0 0 .75rem 0;font-size:.9rem;font-family:var(--font-heading);font-weight:700}.vacation-toggles{display:flex;gap:.5rem}.vacation-toggle{width:2.5rem;height:2.5rem;border-radius:var(--radius-sm);border:var(--border-width) solid var(--border-color);background:var(--bg-secondary);font-family:var(--font-heading);font-weight:700;font-size:.8rem;cursor:pointer;transition:background var(--transition-fast),color var(--transition-fast),border-color var(--transition-fast);display:flex;align-items:center;justify-content:center}.vacation-toggle:hover{background:var(--bg-hover)}.vacation-toggle.active{background:var(--accent-purple);color:var(--text-on-accent);border-color:var(--accent-purple)}.timeline-day.vacation{opacity:.5}.timeline-day.vacation .day-name{text-decoration:line-through}.vacation-day-banner{text-align:center;padding:.5rem 1rem;background:color-mix(in srgb,var(--accent-purple) 15%,var(--bg-secondary));border:var(--border-width-sm) solid var(--accent-purple);border-radius:var(--radius-sm);font-family:var(--font-heading);font-weight:700;font-size:.85rem;color:var(--accent-purple);margin-bottom:.75rem}.stats-row{display:flex;gap:1rem;margin-bottom:1rem}.stat-box{flex:1;text-align:center;padding:1rem;background:var(--bg-secondary);border-radius:var(--radius-md)}.stat-box .stat-number{font-family:var(--font-heading);font-size:2rem;font-weight:800;line-height:1}.stat-box .stat-number.green{color:var(--accent-green)}.stat-box .stat-number.red{color:var(--accent-red)}.stat-box .stat-number.blue{color:var(--accent-blue)}.stat-box .stat-number.purple{color:var(--accent-purple)}.stat-box .stat-label{font-size:.75rem;text-transform:uppercase;color:var(--text-muted);font-weight:600;margin-top:.25rem}.task-list{list-style:none;max-height:200px;overflow-y:auto}.task-item{display:flex;align-items:center;gap:.75rem;padding:.75rem;margin-bottom:.5rem;background:var(--bg-secondary);border-radius:var(--radius-md);cursor:pointer;transition:background-color var(--transition-normal)}.task-item:hover{background:var(--accent-blue);color:var(--text-on-accent)}.task-item.completed{opacity:.6;text-decoration:line-through}.task-checkbox{width:20px;height:20px;border:2px solid var(--border-color);border-radius:var(--radius-xs);display:flex;align-items:center;justify-content:center;flex-shrink:0}.task-checkbox.checked{background:var(--accent-green);color:var(--text-on-accent)}.task-text{flex:1;font-size:.9rem}.task-project{font-size:.75rem;padding:.2rem .5rem;background:var(--bg-card);border-radius:var(--radius-xs);color:var(--text-muted)}.task-due{font-size:.75rem;color:var(--text-muted)}.task-due.overdue{color:var(--accent-red);font-weight:600}.focus-section.full-width{grid-column:1/-1}.focus-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:1rem;margin-top:1rem}.focus-slot{padding:1.25rem;background:var(--bg-secondary);border:2px dashed var(--border-color);border-radius:var(--radius-md);min-height:100px;display:flex;flex-direction:column;gap:.5rem}.focus-slot.filled{border-style:solid;background:var(--bg-card)}.focus-slot.primary{border-color:var(--accent-yellow);background:linear-gradient(135deg,var(--bg-card) 0,color-mix(in srgb,var(--accent-yellow) 10%,var(--bg-card)) 100%)}.focus-label{font-size:.7rem;text-transform:uppercase;color:var(--text-muted);font-weight:600}.focus-task{font-weight:600;font-size:.95rem}.focus-meta{font-size:.8rem;color:var(--text-secondary)}.focus-empty{color:var(--text-muted);font-style:italic;font-size:.9rem}.projects-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:.75rem;margin-top:.5rem}.project-health{padding:.75rem;background:var(--bg-secondary);border-radius:var(--radius-md);border-left:4px solid var(--accent-blue)}.project-health.warning{border-left-color:var(--accent-yellow)}.project-health.danger{border-left-color:var(--accent-red)}.project-name{font-weight:600;font-size:.85rem;margin-bottom:.25rem}.project-stats{font-size:.75rem;color:var(--text-muted)}.reflection-section{grid-column:1/-1}.reflection-prompts{display:grid;grid-template-columns:1fr 1fr;gap:1rem;margin-top:1rem}.reflection-prompt{padding:1rem;background:var(--bg-secondary);border-radius:var(--radius-md)}.prompt-label{font-size:.8rem;font-weight:600;color:var(--text-secondary);margin-bottom:.5rem}.prompt-input{width:100%;padding:.75rem;border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-md);font-size:.9rem;font-family:inherit;resize:none;background:var(--bg-card)}.prompt-input:focus{outline:0;border-color:var(--accent-blue)}.review-actions-grid{grid-column:1/-1;display:flex;justify-content:flex-end;gap:1rem;padding-top:1rem}.event-item{display:flex;align-items:center;gap:.75rem;padding:.75rem;margin-bottom:.5rem;background:var(--bg-secondary);border-radius:var(--radius-md);border-left:3px solid var(--accent-purple)}.event-item .event-time{font-size:.8rem;font-weight:600;color:var(--accent-purple);min-width:100px}.event-item .event-title{flex:1;font-size:.9rem}.accomplishment-highlight{background:linear-gradient(135deg,color-mix(in srgb,var(--accent-green) 10%,var(--bg-card)) 0,color-mix(in srgb,var(--accent-green) 5%,var(--bg-card)) 100%);border:2px solid var(--accent-green);padding:1rem;border-radius:var(--radius-md);margin-bottom:1rem;display:flex;align-items:center;gap:1rem}.accomplishment-icon{font-size:2rem}.accomplishment-text{font-size:1rem}.accomplishment-text strong{color:var(--accent-green)}.task-list::-webkit-scrollbar{width:6px}.task-list::-webkit-scrollbar-track{background:var(--bg-secondary);border-radius:var(--radius-xs)}.task-list::-webkit-scrollbar-thumb{background:var(--border-color);border-radius:var(--radius-xs)}@media (max-width:900px){.review-grid{grid-template-columns:1fr}.focus-section.full-width,.reflection-section,.week-timeline,.week-timeline-events{grid-column:1}.focus-grid{grid-template-columns:1fr}.reflection-prompts{grid-template-columns:1fr}.projects-grid{grid-template-columns:1fr 1fr}}@media (max-width:600px){.stat-cards{flex-direction:column}.stat-card{max-width:none}.week-info{flex-direction:column;align-items:flex-start;gap:.5rem}.projects-grid{grid-template-columns:1fr}}.focus-slot{transition:background-color .2s ease-out,border-color .2s ease-out}.focus-slot.filled{animation:focusSlotFill .3s ease-out}@keyframes focusSlotFill{0%{transform:scale(.95);opacity:.7}100%{transform:scale(1);opacity:1}}.focus-slot:focus,.focus-slot:focus-within{outline:2px solid var(--accent-blue);outline-offset:2px}.focus-slot[tabindex]:focus{outline:2px solid var(--accent-blue);outline-offset:2px}.focus-section .btn{transition:transform .15s ease-out,opacity .15s ease-out}.focus-section .btn:active{transform:scale(.97)}@media print{.card-badge,.focus-section .btn,.focus-slot .btn,.header,.review-actions-grid,.sidebar,.tab-badge,.tab-nav,.tab-status-dot{display:none!important}.main-content,.weekly-review-content{margin:0;padding:0;width:100%;max-width:100%}.event-item,.focus-slot,.project-health,.reflection-prompt,.review-card,.weekly-review-content,body{background:#fff!important;color:#000!important;-webkit-print-color-adjust:exact;print-color-adjust:exact}.review-card{border:1px solid #ccc!important;box-shadow:none!important;page-break-inside:avoid;margin-bottom:1rem}.focus-slot{border:1px solid #999!important}.focus-slot.primary{border:2px solid #f7d154!important;background:#fffbea!important}.review-grid{display:block!important}.review-card{display:inline-block;vertical-align:top;width:48%;margin-right:2%}.focus-section.full-width,.reflection-section,.week-timeline,.week-timeline-events{width:100%!important;display:block!important}.weekly-review-header{border-bottom:2px solid #333;padding-bottom:1rem;margin-bottom:1.5rem}.week-dates{font-size:1.5rem;font-weight:700}.day-dot{-webkit-print-color-adjust:exact;print-color-adjust:exact}.day-dot.completed{background:#5cb85c!important}.day-dot.event{background:#9b59b6!important}.day-dot.overdue{background:#d9534f!important}.project-health{border-left:4px solid #337ab7!important}.project-health.warning{border-left-color:#f7d154!important}.project-health.danger{border-left-color:#d9534f!important}.focus-grid{display:flex!important;gap:1rem}.focus-slot{flex:1}.reflection-prompts{display:flex!important;gap:1rem}.reflection-prompt{flex:1}.prompt-input{border:1px solid #ccc!important;min-height:80px}.focus-section{page-break-before:auto}.reflection-section{page-break-before:always}}.monthly-review-nav{display:flex;align-items:center;gap:.5rem}.monthly-review-month-display{font-family:var(--font-heading);font-size:1.25rem;font-weight:700;color:var(--text-primary);margin-left:.5rem}.monthly-review-content{max-width:900px;margin:0 auto;padding:1rem}.month-heatmap{margin-bottom:1.5rem;border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-md);padding:1rem;background:var(--bg-secondary)}.month-heatmap-header{display:grid;grid-template-columns:repeat(7,1fr);text-align:center;margin-bottom:.5rem}.month-heatmap-day-header{font-family:var(--font-heading);font-size:.75rem;font-weight:600;color:var(--text-secondary);text-transform:uppercase}.month-heatmap-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:3px}.month-heatmap-cell{aspect-ratio:1;display:flex;flex-direction:column;align-items:center;justify-content:center;border-radius:var(--radius-xs);cursor:pointer;transition:transform .1s ease;border:var(--border-width-sm) solid transparent;position:relative;min-height:40px}.month-heatmap-cell:not(.empty):hover{transform:scale(1.1);border-color:var(--border-color);z-index:1}.month-heatmap-cell.empty{cursor:default;background:0 0}.month-heatmap-cell.intensity-0{background:var(--bg-primary)}.month-heatmap-cell.intensity-1{background:color-mix(in srgb,var(--accent-green) 20%,var(--bg-primary))}.month-heatmap-cell.intensity-2{background:color-mix(in srgb,var(--accent-green) 40%,var(--bg-primary))}.month-heatmap-cell.intensity-3{background:color-mix(in srgb,var(--accent-green) 60%,var(--bg-primary))}.month-heatmap-cell.vacation{background:var(--bg-tertiary);opacity:.6}.month-heatmap-cell.today{border-color:var(--accent-primary);border-width:2px}.month-heatmap-cell.past.intensity-0{background:var(--bg-tertiary)}.month-heatmap-day-number{font-family:var(--font-heading);font-size:.8rem;font-weight:600;color:var(--text-primary)}.month-heatmap-dots{display:flex;gap:2px;margin-top:2px}.month-dot{font-size:.6rem;font-weight:700;border-radius:var(--radius-xs);padding:0 3px;line-height:1.3}.month-dot.completed{color:var(--accent-green)}.month-dot.event{color:var(--accent-purple)}.monthly-review-cards{display:grid;grid-template-columns:1fr 1fr;gap:1rem}.review-card.month-goals-card,.review-card.month-stats-card{grid-column:span 1}.review-card.month-patterns-card,.review-card.month-pulse-card{grid-column:span 1}.review-card.month-reflection-card{grid-column:1/-1}.review-card-title{font-family:var(--font-heading);font-size:1rem;font-weight:700;margin-bottom:.75rem;color:var(--text-primary)}.month-stats-grid{display:grid;grid-template-columns:1fr 1fr;gap:.5rem}.month-stat-item{display:flex;flex-direction:column;align-items:center;padding:.5rem;border-radius:var(--radius-xs);background:var(--bg-primary);border:var(--border-width-sm) solid var(--border-color)}.month-stat-value{font-family:var(--font-heading);font-size:1.5rem;font-weight:700;color:var(--text-primary)}.month-stat-label{font-size:.75rem;color:var(--text-secondary);text-transform:uppercase;font-weight:600}.month-stats-highlights{display:flex;gap:1rem;margin-top:.5rem;justify-content:center}.stat-highlight{font-size:.8rem;color:var(--text-secondary)}.month-pulse-list{display:flex;flex-direction:column;gap:.5rem}.month-pulse-item{display:flex;align-items:center;gap:.5rem;padding:.5rem;border-radius:var(--radius-xs);background:var(--bg-primary);border:var(--border-width-sm) solid var(--border-color)}.pulse-name{font-weight:600;flex:1;font-size:.875rem}.pulse-stats{font-size:.75rem;color:var(--text-secondary)}.pulse-arrow{font-size:1rem;font-weight:700}.month-pulse-item.positive .pulse-arrow{color:var(--accent-green)}.month-pulse-item.negative .pulse-arrow{color:var(--accent-red)}.month-pulse-item.neutral .pulse-arrow{color:var(--text-secondary)}.month-goals-list{display:flex;flex-direction:column;gap:.5rem}.month-goal-item{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;border-radius:var(--radius-xs);background:var(--bg-primary);border:var(--border-width-sm) solid var(--border-color)}.month-goal-item.empty{cursor:pointer;border-style:dashed;justify-content:center}.month-goal-item.empty:hover{border-color:var(--accent-primary);background:var(--bg-secondary)}.month-goal-item.done{opacity:.7}.month-goal-item.done .month-goal-text{text-decoration:line-through}.month-goal-item.abandoned{opacity:.5}.month-goal-item.abandoned .month-goal-text{text-decoration:line-through}.month-goal-status-btn{background:0 0;border:none;cursor:pointer;font-size:1rem;padding:0;color:var(--text-secondary);width:24px;text-align:center}.month-goal-item.done .month-goal-status-btn{color:var(--accent-green)}.month-goal-item.abandoned .month-goal-status-btn{color:var(--accent-red)}.month-goal-text{flex:1;font-size:.875rem}.month-goal-delete-btn{background:0 0;border:none;cursor:pointer;color:var(--text-tertiary);padding:0 4px;font-size:.75rem;opacity:0;transition:opacity .15s}.month-goal-item:hover .month-goal-delete-btn{opacity:1}.month-goal-placeholder{color:var(--text-tertiary);font-size:.875rem}.month-reflection-fields{display:flex;flex-direction:column;gap:.5rem}.month-reflection-label{font-size:.875rem;font-weight:600;color:var(--text-secondary)}.month-reflection-textarea{width:100%;padding:.5rem;border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-xs);background:var(--bg-primary);color:var(--text-primary);font-family:var(--font-body);font-size:.875rem;resize:vertical}.month-reflection-textarea:focus{outline:2px solid var(--accent-primary);outline-offset:-1px}.month-patterns-list{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:.5rem}.month-pattern-item{font-size:.875rem;color:var(--text-secondary);padding:.5rem;background:var(--bg-primary);border-radius:var(--radius-xs);border:var(--border-width-sm) solid var(--border-color)}@media (max-width:640px){.monthly-review-cards{grid-template-columns:1fr}.review-card.month-goals-card,.review-card.month-patterns-card,.review-card.month-pulse-card,.review-card.month-stats-card{grid-column:span 1}.month-heatmap-cell{min-height:32px}.month-heatmap-day-number{font-size:.7rem}.month-heatmap-dots{display:none}}.import-wizard{display:flex;flex-direction:column;gap:1.5rem}.import-step{padding:1rem;background:var(--bg-secondary);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md)}.import-step h3{margin:0 0 1rem 0;font-size:var(--font-size-md);font-weight:600}.plugin-selector{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:.75rem}.plugin-option{display:flex;flex-direction:column;align-items:flex-start;padding:.75rem 1rem;background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-sm);cursor:pointer;text-align:left;transition:border-color var(--transition-fast),background var(--transition-fast)}.plugin-option:hover{border-color:var(--accent-primary);background:var(--bg-hover)}.plugin-option.selected{border-color:var(--accent-primary);background:color-mix(in srgb,var(--accent-primary) 10%,var(--bg-card));box-shadow:0 0 0 2px color-mix(in srgb,var(--accent-primary) 30%,transparent)}.plugin-option .plugin-name{font-weight:600;margin-bottom:.25rem}.plugin-option .plugin-meta{display:flex;gap:.5rem;font-size:var(--font-size-sm);color:var(--text-muted);margin-bottom:.25rem}.plugin-option .plugin-extensions{color:var(--accent-cyan)}.plugin-option .plugin-types{color:var(--text-secondary)}.plugin-option .plugin-description{font-size:var(--font-size-sm);color:var(--text-secondary);line-height:1.4}.file-selector{display:flex;align-items:center;gap:1rem}.selected-file-name{color:var(--text-secondary);font-family:monospace;font-size:var(--font-size-sm)}.import-preview-container{min-height:100px}.import-preview-table-wrapper{max-height:300px;overflow:auto;border:1px solid var(--border-color);border-radius:var(--radius-sm)}.import-preview-table{font-size:var(--font-size-sm);margin:0}.import-preview-table th{position:sticky;top:0;background:var(--bg-secondary);z-index:1}.import-preview-table td{max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.import-summary{margin:0 0 .75rem 0;color:var(--text-primary)}.import-more{margin:.5rem 0 0 0;color:var(--text-muted);font-style:italic;font-size:var(--font-size-sm)}.import-empty,.import-error{padding:2rem;text-align:center;color:var(--text-muted)}.import-error{color:var(--accent-red)}.import-warnings{margin-top:1rem;padding:.75rem;background:color-mix(in srgb,var(--accent-yellow) 10%,var(--bg-card));border:1px solid var(--accent-yellow);border-radius:var(--radius-sm);font-size:var(--font-size-sm)}.import-warnings ul{margin:.5rem 0 0 1.25rem;padding:0}.import-warnings li{margin-bottom:.25rem}.import-external-types{display:flex;gap:1rem;margin-bottom:1.5rem}.import-type-card{flex:1;display:flex;flex-direction:column;align-items:center;gap:.5rem;padding:1.5rem 1rem;background:var(--bg-card);border:2px solid var(--border-color);border-radius:var(--radius-md);cursor:pointer;transition:border-color .15s,background .15s}.import-type-card:hover{border-color:var(--accent-primary);background:var(--bg-secondary)}.import-type-icon{font-size:2rem}.import-type-label{font-weight:600;color:var(--text-primary)}.import-type-desc{font-size:var(--font-size-sm);color:var(--text-muted)}.plugin-list{display:flex;flex-direction:column;gap:.75rem}.plugin-item{display:flex;justify-content:space-between;align-items:center;padding:1rem;background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md)}.plugin-item .plugin-info{flex:1}.plugin-item .plugin-name{font-weight:600}.plugin-item .plugin-version{color:var(--text-muted);font-size:var(--font-size-sm);margin-left:.5rem}.plugin-item .plugin-description{margin:.25rem 0;color:var(--text-secondary);font-size:var(--font-size-sm)}.plugin-item .plugin-extensions{font-size:var(--font-size-xs);color:var(--text-muted)}.plugin-item .plugin-actions{margin-left:1rem}.toggle-switch{position:relative;display:inline-block;width:44px;height:24px}.toggle-switch input{opacity:0;width:0;height:0}.toggle-slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background-color:var(--bg-tertiary);border:2px solid var(--border-color);border-radius:var(--radius-xl);transition:background-color var(--transition-fast),border-color var(--transition-fast)}.toggle-slider:before{position:absolute;content:"";height:16px;width:16px;left:2px;bottom:2px;background-color:var(--text-muted);border-radius:var(--radius-full);transition:transform var(--transition-fast),background-color var(--transition-fast)}.toggle-switch input:checked+.toggle-slider{background-color:var(--accent-primary);border-color:var(--accent-primary)}.toggle-switch input:checked+.toggle-slider:before{transform:translateX(20px);background-color:var(--bg-card)}.toggle-switch input:focus+.toggle-slider{box-shadow:0 0 0 2px color-mix(in srgb,var(--accent-primary) 30%,transparent)}.milestones-section{margin-bottom:1.5rem}.milestones-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;padding-bottom:.5rem;border-bottom:2px solid var(--border-color)}.milestones-header h3{margin:0;font-size:1rem;font-family:var(--font-heading);font-weight:700}.milestone-card{background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);padding:1rem;margin-bottom:.75rem;transition:background-color .1s;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.milestone-card:hover{background:var(--bg-secondary)}.milestone-card-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:.5rem}.milestone-card-header h4{margin:0;font-size:.95rem;font-family:var(--font-heading);font-weight:700}.milestone-card-header .milestone-status{font-size:.7rem;font-weight:700;text-transform:uppercase;padding:.15rem .4rem;border-radius:var(--radius-sm);background:var(--bg-secondary);color:var(--text-muted)}.milestone-card-header .milestone-status.completed{background:color-mix(in srgb,var(--accent-green) 15%,var(--bg-secondary));color:var(--accent-green)}.milestone-meta{display:flex;gap:1rem;font-size:.8rem;color:var(--text-muted);margin-bottom:.5rem}.milestone-progress{height:6px;background:var(--bg-secondary);border-radius:var(--radius-full);overflow:hidden;border:var(--border-width-sm) solid var(--border-color)}.milestone-progress-fill{height:100%;background:var(--accent-green);border-radius:var(--radius-full);transition:width var(--transition-fast)}.milestone-actions{display:flex;gap:.5rem;margin-top:.5rem}.milestone-actions button{font-size:.75rem;padding:.2rem .5rem;background:var(--bg-secondary);border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-sm);cursor:pointer;color:var(--text-secondary);transition:background var(--transition-fast)}.milestone-actions button:hover{background:var(--bg-hover)}.milestone-actions button.danger:hover{background:color-mix(in srgb,var(--accent-red) 15%,var(--bg-secondary));color:var(--accent-red)}button.milestone-reorder-btn.btn{font-size:.65rem;padding:.15rem .35rem;line-height:1;min-width:1.5rem;text-align:center}.milestones-completed-section{margin-top:.75rem}.milestones-completed-toggle{font-size:.8rem;color:var(--text-secondary);padding:.25rem 0}.milestone-card-summary{padding:.5rem .75rem;opacity:.7}.milestone-card-summary .milestone-info{display:flex;align-items:center;gap:.5rem}.milestone-complete-badge{font-size:.7rem;font-weight:700;padding:.1rem .4rem;border-radius:var(--radius-sm);background:color-mix(in srgb,var(--accent-green) 15%,var(--bg-secondary));color:var(--accent-green)}.mobile-tab-bar{display:none;position:fixed;bottom:0;left:0;right:0;z-index:1100;background:var(--bg-card);border-top:var(--border-width) solid var(--border-color);padding-bottom:env(safe-area-inset-bottom,0);height:calc(52px + env(safe-area-inset-bottom,0px))}.mobile-tab{flex:1;display:flex;align-items:center;justify-content:center;height:52px;background:0 0;border:none;color:var(--text-muted);font-size:.7rem;font-weight:700;font-family:var(--font-sans);text-transform:uppercase;letter-spacing:.05em;cursor:pointer;-webkit-tap-highlight-color:transparent;transition:color .15s ease}.mobile-tab.active{color:var(--accent-blue)}.mobile-tab:active{background:var(--bg-secondary)}.mobile-tab-create{font-size:1.4rem;font-weight:400;color:var(--accent-green);letter-spacing:0;text-transform:none}.mobile-more-popover{display:none;position:fixed;bottom:calc(52px + env(safe-area-inset-bottom,0px));right:0;background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);padding:.25rem 0;z-index:1101;min-width:160px;box-shadow:0 -2px 8px rgba(0,0,0,.1)}.mobile-more-popover.visible{display:block}.mobile-more-popover button{display:block;width:100%;padding:.75rem 1rem;background:0 0;border:none;text-align:left;font-size:var(--font-size-sm);font-weight:600;color:var(--text-primary);cursor:pointer}.mobile-more-popover button:active{background:var(--bg-secondary)}.action-sheet{position:fixed;inset:0;z-index:10001;display:flex;flex-direction:column;justify-content:flex-end}.action-sheet.hidden{display:none}.action-sheet-backdrop{position:absolute;inset:0;background:rgba(0,0,0,.4)}.action-sheet-container{position:relative;background:var(--bg-card);border-top:var(--border-width) solid var(--border-color);border-radius:var(--radius-lg) var(--radius-lg) 0 0;padding:.5rem 1rem calc(.5rem + env(safe-area-inset-bottom,0px));max-height:60vh;overflow-y:auto;animation:sheetSlideUp .25s ease-out}.action-sheet-handle{width:36px;height:4px;border-radius:2px;background:var(--text-muted);margin:0 auto .75rem;opacity:.4}.action-sheet-content button{display:flex;align-items:center;gap:.75rem;width:100%;padding:.875rem .5rem;background:0 0;border:none;border-bottom:1px solid var(--bg-secondary);font-size:var(--font-size-base);font-weight:600;color:var(--text-primary);text-align:left;cursor:pointer}.action-sheet-content button:last-child{border-bottom:none}.action-sheet-content button:active{background:var(--bg-secondary)}.action-sheet-content button.danger{color:var(--accent-red)}.action-sheet-cancel{display:block;width:100%;padding:.875rem;margin-top:.5rem;background:var(--bg-secondary);border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-md);font-size:var(--font-size-base);font-weight:700;color:var(--text-primary);text-align:center;cursor:pointer}.action-sheet-cancel:active{background:var(--bg-tertiary)}.modal-drag-handle{display:none;width:36px;height:4px;border-radius:2px;background:var(--text-muted);margin:.5rem auto 0;opacity:.4}.mobile-sort-bar{display:none;gap:.5rem;padding:.5rem 0;align-items:center}.mobile-sort-bar select{flex:1;font-size:var(--font-size-sm)}.mobile-filter-toggle{display:none}.swipe-actions-container{position:relative;overflow:hidden}.swipe-actions-bg{position:absolute;top:0;bottom:0;display:flex;align-items:center;padding:0 1rem;font-weight:700;font-size:var(--font-size-sm);color:var(--text-on-accent)}.swipe-actions-bg.swipe-left{right:0;background:var(--accent-green)}.swipe-actions-bg.swipe-right{left:0;background:var(--accent-red)}.swipe-content{position:relative;background:var(--bg-card);transition:transform .15s ease}.pull-to-refresh-indicator{display:none;text-align:center;padding:.75rem;font-size:var(--font-size-sm);color:var(--text-muted);font-weight:600}.pull-to-refresh-indicator.visible{display:block}.event-date-group-header{display:none}.day-plan-sidebar-toggle{display:none}@keyframes sheetSlideUp{from{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes sheetSlideDown{from{transform:translateY(0)}to{transform:translateY(100%)}}@keyframes dialFadeIn{from{opacity:0}to{opacity:1}}@media (max-width:768px){body{padding-top:env(safe-area-inset-top,0);padding-bottom:calc(52px + env(safe-area-inset-bottom,0px))}.mobile-tab-bar{display:flex}.tab-navigation{display:none!important}.app-header{display:none}.pill-nav{overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;padding:var(--space-1) var(--space-3)}.tab-group>.subview>.page-header{position:static}.pill-nav::-webkit-scrollbar{display:none}.main-content{padding:.75rem}.page-header{flex-wrap:wrap;gap:.5rem}.page-header .btn-primary{display:none}.page-title{display:none}.modal-overlay{align-items:flex-end}.modal-container{width:100%!important;max-width:100%!important;max-height:90vh;border-radius:var(--radius-lg) var(--radius-lg) 0 0;margin:0;border-bottom:none;padding-bottom:env(safe-area-inset-bottom,0)}.modal-container.modal-large{max-width:100%!important;width:100%!important;max-height:95vh;border-radius:var(--radius-lg) var(--radius-lg) 0 0}.modal-drag-handle{display:block}.modal-header{padding:.75rem 1rem}.modal-content{padding:1rem}@keyframes modalSlideIn{from{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes modalSlideOut{from{transform:translateY(0)}to{transform:translateY(100%)}}.toast,.toast-undo{bottom:calc(env(safe-area-inset-bottom,0px) + 4.5rem)!important;left:1rem!important;right:1rem!important;max-width:none!important}.task-table{border:none;box-shadow:none;background:0 0}.task-header-row{display:none!important}.task-row{display:flex!important;flex-direction:column;gap:.25rem;padding:.75rem 1rem;margin-bottom:.5rem;background:var(--bg-card);border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-md);border-left:4px solid var(--text-muted)}.task-row.task-pending{border-left-color:var(--text-muted)}.task-row .task-cell.priority-h~.task-cell:first-child,.task-row:has(.priority-h){border-left-color:var(--accent-red)}.task-row:has(.priority-m){border-left-color:var(--accent-yellow)}.task-row:has(.priority-l){border-left-color:var(--text-muted)}.task-row .task-cell{display:flex!important;overflow:visible;padding:0}.task-cell.task-description{font-weight:600;font-size:var(--font-size-base)}.task-cell.task-due,.task-cell.task-project{font-size:var(--font-size-sm);color:var(--text-secondary)}.task-row .task-cell.task-project::before{content:none}.task-cell.task-progress,.task-cell.task-recurrence,.task-row .task-cell:nth-child(3){display:none!important}.task-cell.task-project{order:2}.task-cell.task-due{order:3}.task-cell.task-description{order:1}.task-cell.task-actions-cell{order:4;justify-content:flex-end}.task-cell.task-progress:has(.progress-bar-container){display:flex!important;order:5}.task-actions-cell .bulk-checkbox{display:none}.mobile-sort-bar{display:flex}.mobile-filter-toggle{display:inline-flex;align-items:center;gap:.25rem;padding:.5rem .75rem;background:var(--bg-card);border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-sm);font-size:var(--font-size-sm);font-weight:600;cursor:pointer}.filter-bar{display:none!important}.filter-bar.mobile-visible{display:flex!important;flex-direction:column;position:fixed;bottom:0;left:0;right:0;background:var(--bg-card);border-top:var(--border-width) solid var(--border-color);border-radius:var(--radius-lg) var(--radius-lg) 0 0;padding:1rem;padding-bottom:calc(1rem + env(safe-area-inset-bottom,0px));z-index:1050;box-shadow:0 -4px 12px rgba(0,0,0,.1)}.event-header-row{display:none!important}.event-row-virtual{display:flex!important;flex-direction:column;gap:.125rem;padding:.75rem 1rem;border-bottom:1px solid var(--bg-secondary)}.event-cell-date{font-weight:700;font-size:var(--font-size-sm);color:var(--text-secondary)}.event-cell-time{font-size:var(--font-size-sm);color:var(--text-muted)}.event-cell-title{font-weight:600;font-size:var(--font-size-base)}.event-cell-location{font-size:var(--font-size-sm);color:var(--text-secondary)}.event-date-group-header{display:flex;position:sticky;top:0;z-index:5;padding:.5rem 1rem;background:var(--bg-secondary);font-weight:700;font-size:var(--font-size-sm);text-transform:uppercase;letter-spacing:.05em;color:var(--text-primary);border-bottom:var(--border-width-sm) solid var(--border-color)}.email-item{padding:.625rem .75rem}.email-from{font-size:var(--font-size-sm)}.email-subject{font-size:var(--font-size-base)}.email-preview{display:none}.email-date{font-size:var(--font-size-xs)}.email-item .bulk-checkbox{display:none}.day-plan-content{flex-direction:column}.day-plan-sidebar{width:100%;max-height:none;border-top:var(--border-width-sm) solid var(--border-color);order:2}.day-plan-sidebar.collapsed .sidebar-task-list{display:none}.day-plan-sidebar-toggle{display:flex;align-items:center;justify-content:space-between;width:100%;padding:.625rem .75rem;background:var(--bg-secondary);border:none;border-bottom:1px solid var(--border-color);font-size:var(--font-size-sm);font-weight:700;cursor:pointer;color:var(--text-primary)}.day-plan-main{order:1}.day-plan-nav{flex-wrap:wrap;gap:.25rem}.weekly-review-content{padding:0}.monthly-review-content{padding:0}.month-reflection-textarea,.prompt-input{resize:none;overflow:hidden}.monthly-review-nav{flex-wrap:wrap;gap:.25rem}.monthly-review-month-display{font-size:1rem}.day-summary-sheet{padding:.5rem 0}.day-summary-date{font-size:1rem;font-weight:700;margin-bottom:.75rem;color:var(--text-primary)}.day-summary-stats{display:flex;gap:.5rem;margin-bottom:1rem}.day-summary-chip{padding:.25rem .75rem;background:var(--bg-secondary);border-radius:var(--radius-sm);font-size:var(--font-size-sm);font-weight:600;color:var(--text-secondary)}.day-summary-list{list-style:none;padding:0;margin:0 0 1rem 0}.day-summary-item{padding:.5rem 0;border-bottom:1px solid var(--bg-secondary);font-size:var(--font-size-sm);color:var(--text-primary)}.day-summary-time{font-weight:600;color:var(--text-secondary);margin-right:.5rem}.day-summary-more{color:var(--text-muted);font-style:italic}.day-summary-empty{color:var(--text-muted);font-size:var(--font-size-sm);margin:.5rem 0 1rem}.day-summary-go-btn{width:100%;margin-top:.5rem}.bulk-actions-bar{position:fixed;bottom:0;left:0;right:0;z-index:1050;border-radius:var(--radius-lg) var(--radius-lg) 0 0;padding-bottom:calc(.75rem + env(safe-area-inset-bottom,0px));box-shadow:0 -4px 12px rgba(0,0,0,.15)}.pagination-controls{padding:.5rem}.pagination-controls .btn{padding:.5rem .75rem;font-size:var(--font-size-sm)}}@media (hover:none){.task-row:hover{background-color:transparent}.task-row-clickable:hover{background:0 0}.event-row-virtual:hover{background-color:transparent}.email-item:hover{background-color:transparent}.card:hover{background-color:var(--bg-card);transform:none;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.btn:hover{background:var(--bg-card)}.btn-primary:hover{background-color:var(--accent-blue)}.btn-danger:hover{background-color:var(--accent-red)}.dashboard-item:hover{background:var(--bg-card);transform:none;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.kanban-card:hover{background:var(--bg-card);transform:none;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.saved-view-item:hover{background:var(--bg-card);color:var(--text-primary);transform:none;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.context-menu-item:hover{background:0 0;color:var(--text-primary)}.modal-close:hover{background:var(--bg-card);color:var(--text-primary)}.month-heatmap-cell:hover{background:0 0;transform:none}}.view-toggle{display:flex;gap:0;margin-left:auto}.view-toggle-btn{padding:.35rem .75rem;border:var(--border-width-sm) solid var(--border-color);background:var(--bg-secondary);font-family:var(--font-body);font-size:var(--font-size-md);cursor:pointer;transition:background var(--transition-fast),box-shadow var(--transition-fast)}.view-toggle-btn.active{background:var(--bg-card);font-weight:600}.view-toggle-btn:first-child{border-radius:var(--radius-xs) 0 0 var(--radius-xs)}.view-toggle-btn:last-child{border-radius:0 var(--radius-xs) var(--radius-xs) 0;border-left:none}.kanban-board{display:grid;grid-template-columns:repeat(3,1fr);gap:1rem;padding:.5rem 0;min-height:400px}.kanban-column{background:var(--bg-card);border:var(--border-width) solid var(--border-color);display:flex;flex-direction:column;min-height:300px;max-height:calc(100vh - 200px)}.kanban-column-header{padding:.75rem 1rem;border-bottom:2px solid var(--border-color);font-family:var(--font-heading);font-weight:700;display:flex;justify-content:space-between;align-items:center}.kanban-column-count{font-family:var(--font-body);font-size:var(--font-size-sm);color:var(--text-secondary)}.kanban-column-body{flex:1;overflow-y:auto;padding:.5rem;display:flex;flex-direction:column;gap:.5rem}.kanban-column.drag-over{background-color:var(--bg-tertiary)}.kanban-empty{text-align:center;padding:2rem 1rem;color:var(--text-secondary);font-size:var(--font-size-sm)}.kanban-card{padding:.75rem;background:var(--bg-card);border:var(--border-width-sm) solid var(--border-color);cursor:grab;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color);transition:transform .15s ease,box-shadow .15s ease,background-color .15s ease;border-left:4px solid transparent}.kanban-card:hover{background:var(--bg-secondary);transform:translate(-2px,-2px);box-shadow:calc(var(--shadow-offset) + 2px) calc(var(--shadow-offset) + 2px) 0 var(--border-color)}.kanban-card.dragging{opacity:.5;cursor:grabbing}.kanban-card.priority-high{border-left-color:var(--accent-red)}.kanban-card.priority-medium{border-left-color:var(--accent-yellow)}.kanban-card.priority-low{border-left-color:var(--accent-green)}.kanban-card-title{font-weight:600;margin-bottom:.25rem}.kanban-card-meta{font-size:var(--font-size-sm);color:var(--text-secondary);display:flex;gap:.5rem;flex-wrap:wrap}.kanban-card-due.overdue{color:var(--accent-red);font-weight:600}.progress-bar-mini{height:3px;background:var(--bg-tertiary);border-radius:2px;margin-top:.5rem}.progress-bar-mini .progress-fill{height:100%;background:var(--accent-green);border-radius:2px}@media (max-width:768px){.kanban-board{grid-template-columns:1fr}.kanban-column{max-height:none}}.timer-widget{position:fixed;bottom:0;left:0;right:0;z-index:900;background:var(--bg-primary);border-top:var(--border-width) solid var(--border-color);box-shadow:0 -2px 8px rgba(0,0,0,.1);padding:.5rem 1rem;transition:transform .2s ease}.timer-widget.hidden{transform:translateY(100%);pointer-events:none}.timer-widget-inner{display:flex;align-items:center;gap:1rem;max-width:800px;margin:0 auto}.timer-task-name{flex:1;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.timer-elapsed{font-family:var(--font-mono, monospace);font-size:1.125rem;font-weight:700;color:var(--accent-color);min-width:5rem;text-align:center}.timer-actions{display:flex;gap:.5rem}.focus-overlay{position:fixed;inset:0;z-index:1000;background:var(--bg-primary);display:flex;align-items:center;justify-content:center;transition:opacity .3s ease}.focus-overlay.hidden{opacity:0;pointer-events:none}.focus-overlay-content{text-align:center;max-width:400px;width:100%;padding:2rem}.focus-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:2rem}.focus-label{font-family:var(--font-heading);font-size:1.25rem;font-weight:700}.focus-presets{display:flex;gap:.5rem}.focus-preset-btn.active{background:var(--accent-color);color:var(--bg-primary);border-color:var(--accent-color)}.focus-countdown{font-family:var(--font-mono, monospace);font-size:4rem;font-weight:700;line-height:1;margin-bottom:1.5rem;color:var(--text-primary)}.focus-progress-bar{height:6px;background:var(--bg-tertiary);border-radius:3px;margin-bottom:1.5rem;overflow:hidden}.focus-progress-fill{height:100%;background:var(--accent-color);border-radius:3px;transition:width 1s linear}.focus-task-name{color:var(--text-secondary);margin-bottom:2rem;font-size:.9rem}.focus-actions{display:flex;gap:1rem;justify-content:center}.time-summary-section{margin-bottom:1rem}.time-summary-toggle{display:flex;align-items:center;gap:.5rem;width:100%;padding:.5rem;background:0 0;border:none;font-family:var(--font-heading);font-size:.875rem;font-weight:700;color:var(--text-primary);cursor:pointer;text-align:left}.time-summary-toggle:hover{color:var(--accent-color)}.time-summary-toggle-icon{font-size:.625rem;transition:transform .15s ease}.time-summary-body{padding:.5rem;overflow:hidden;transition:max-height .2s ease;max-height:500px}.time-summary-body.collapsed{max-height:0;padding:0 .5rem}.time-summary-today{display:flex;justify-content:space-between;align-items:center;padding:.5rem 0;border-bottom:1px solid var(--border-color);margin-bottom:.5rem}.time-summary-today-label{font-weight:600;font-size:.875rem}.time-summary-today-value{font-family:var(--font-mono, monospace);font-weight:700;font-size:1rem;color:var(--accent-color)}.time-summary-week-header{font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--text-secondary);margin-bottom:.5rem}.time-summary-project{margin-bottom:.5rem}.time-summary-project-info{display:flex;justify-content:space-between;align-items:center;font-size:.8125rem;margin-bottom:.25rem}.time-summary-project-name{color:var(--text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.time-summary-project-time{font-family:var(--font-mono, monospace);font-weight:600;font-size:.75rem;color:var(--text-secondary);margin-left:.5rem;flex-shrink:0}.time-summary-bar{height:4px;background:var(--bg-tertiary);border-radius:2px;overflow:hidden}.time-summary-bar-fill{height:100%;background:var(--accent-color);border-radius:2px}.unscheduled-task-actions{display:flex;gap:.25rem;margin-top:.375rem}.unscheduled-task-actions .btn{font-size:.7rem;padding:.125rem .375rem;min-height:auto;line-height:1.4}.task-time-badge{display:inline-block;font-family:var(--font-mono, monospace);font-size:.7rem;font-weight:600;color:var(--text-secondary);background:var(--bg-secondary);border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-sm);padding:.05rem .35rem;margin-left:.375rem;vertical-align:middle;white-space:nowrap}.task-time-badge.over-estimate{color:var(--accent-red);border-color:var(--accent-red)}.task-timer-active{display:inline-block;width:8px;height:8px;background:var(--accent-red);border-radius:var(--radius-full);margin-left:.375rem;vertical-align:middle;animation:timer-pulse 1.5s ease-in-out infinite}@keyframes timer-pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.4;transform:scale(.8)}}@media (max-width:768px){.timer-widget{bottom:60px}.focus-countdown{font-size:3rem}}.timer-active-banner{display:flex;align-items:center;gap:1rem;padding:.875rem 1rem;background:var(--bg-secondary);border:var(--border-width) solid var(--accent-color);border-radius:var(--radius-md);margin-bottom:1.5rem}.timer-active-info{flex:1;min-width:0}.timer-active-label{display:block;font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--accent-color);margin-bottom:.125rem}.timer-active-task{display:block;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.timer-active-elapsed{font-family:var(--font-mono, monospace);font-size:1.25rem;font-weight:700;color:var(--accent-color);min-width:5rem;text-align:center}.timer-active-actions{display:flex;gap:.5rem}.timer-focus-split{display:flex;align-items:center;gap:.375rem;padding:.5rem 0;margin-bottom:.5rem}.timer-focus-split-label{font-size:.8125rem;color:var(--text-secondary);font-weight:600;margin-right:.25rem}.timer-split-input{width:3.5rem;padding:.25rem .375rem;font-size:.875rem;font-family:var(--font-mono, monospace);text-align:center;border:var(--border-width) solid var(--border-color);border-radius:var(--radius-sm);background:var(--bg-primary);color:var(--text-primary)}.timer-focus-split-sep{font-size:.8125rem;color:var(--text-secondary)}.timer-task-list{display:flex;flex-direction:column;gap:0}.timer-task-item{display:flex;align-items:center;gap:1rem;padding:.75rem .5rem;border-bottom:1px solid var(--border-color)}.timer-task-item:last-child{border-bottom:none}.timer-task-info{flex:1;min-width:0}.timer-task-desc{display:block;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.timer-task-meta{display:flex;gap:.5rem;flex-wrap:wrap;margin-top:.25rem;font-size:.8125rem;color:var(--text-secondary)}.timer-task-project{font-weight:600}.timer-task-priority{font-weight:600}.timer-task-priority.priority-h,.timer-task-priority.priority-high{color:var(--accent-red)}.timer-task-priority.priority-m,.timer-task-priority.priority-medium{color:var(--accent-yellow,var(--accent-color))}.timer-task-estimate,.timer-task-tracked{font-family:var(--font-mono, monospace);font-size:.75rem}.timer-task-actions{display:flex;gap:.375rem;flex-shrink:0}@media (max-width:768px){.timer-active-banner{flex-wrap:wrap}.timer-active-elapsed{font-size:1rem}.timer-task-item{flex-wrap:wrap}.timer-task-actions{width:100%;justify-content:flex-end}}
1 > \ No newline at end of file
1 + @font-face{font-family:Reglo;src:url('../fonts/Reglo-Bold.woff2') format('woff2');font-weight:700;font-style:normal;font-display:swap}*,::after,::before{box-sizing:border-box;margin:0;padding:0}:root{--bg-primary:#E0E4FA;--bg-secondary:#CDD3F0;--bg-tertiary:#BAC2E6;--bg-card:#FFFFFF;--text-primary:#000000;--text-secondary:#2D2D2D;--text-muted:#6B6B6B;--accent-yellow:#F7D154;--accent-green:#5CB85C;--accent-blue:#6196FF;--accent-purple:#7B68EE;--accent-red:#DC3545;--accent-cyan:#17A2B8;--border-color:#000000;--border-width:2px;--border-width-sm:2px;--accent-color:var(--accent-blue);--accent-primary:var(--accent-blue);--bg-hover:var(--bg-tertiary);--border-light:var(--bg-tertiary);--text-on-accent:var(--bg-card);--shadow-offset-xs:1px;--shadow-offset-md:3px;--shadow-offset:4px;--shadow-offset-lg:6px;--shadow-offset-xl:8px;--shadow-brutal-xs:var(--shadow-offset-xs) var(--shadow-offset-xs) 0 var(--border-color);--shadow-brutal-md:var(--shadow-offset-md) var(--shadow-offset-md) 0 var(--border-color);--shadow-brutal-lg:var(--shadow-offset-lg) var(--shadow-offset-lg) 0 var(--border-color);--shadow-brutal-xl:var(--shadow-offset-xl) var(--shadow-offset-xl) 0 var(--border-color);--radius-xs:3px;--radius-sm:5px;--radius-md:5px;--radius-lg:10px;--radius-xl:20px;--radius-full:50%;--width-container:1400px;--width-modal:560px;--width-sidebar:280px;--space-1:0.25rem;--space-2:0.5rem;--space-3:0.75rem;--space-4:1rem;--space-5:1.25rem;--space-6:1.5rem;--font-sans:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;--font-serif:Georgia,'Times New Roman',serif;--font-mono:'SF Mono','Consolas','Liberation Mono',monospace;--font-display:'Reglo',var(--font-serif);--font-heading:var(--font-sans);--font-body:var(--font-sans);--font-size-xxs:0.65rem;--font-size-xs:0.7rem;--font-size-sm:0.75rem;--font-size-md:0.8rem;--font-size-base:0.875rem;--font-size-lg:1rem;--font-size-xl:1.1rem;--font-size-2xl:1.25rem;--font-size-3xl:1.5rem;--font-size-4xl:1.75rem;--line-height-tight:1.25;--line-height-normal:1.5;--line-height-relaxed:1.75;--transition-fast:0.1s;--transition-normal:0.15s;--transition-slow:0.3s;--overlay-color:color-mix(in srgb, var(--text-primary) 60%, transparent)}html{font-size:16px}.flex-1{flex:1}.flex-center-gap{display:flex;align-items:center;gap:.5rem}.text-sm-secondary{font-size:.875rem;color:var(--text-secondary)}.text-xs-secondary{font-size:.75rem;color:var(--text-secondary)}.text-accent-red{color:var(--accent-red)}.mb-1{margin-bottom:1rem}.settings-divider{margin-top:1.5rem;padding-top:1.5rem;border-top:2px solid var(--border-color)}.settings-heading{margin-bottom:1rem;font-family:var(--font-heading)}.settings-desc{font-size:.875rem;color:var(--text-secondary);margin-bottom:1rem}.subtask-item{display:flex;align-items:center;gap:.5rem;padding:.5rem;background:var(--bg-secondary);border-radius:4px;margin-bottom:.5rem}.subtask-item-linked{display:flex;align-items:center;gap:.5rem;padding:.5rem;background:var(--bg-tertiary);border-radius:4px;margin-bottom:.5rem;border-left:var(--border-width) solid var(--accent-color)}.subtask-checkbox{cursor:pointer;width:18px;height:18px}.subtask-checkbox-disabled{cursor:not-allowed;width:18px;height:18px;opacity:.5}.subtask-text-done{text-decoration:line-through;opacity:.6}body{font-family:var(--font-sans);background-color:var(--bg-primary);color:var(--text-primary);line-height:1.6;height:100vh;overflow:hidden;display:flex;flex-direction:column}.app-header{background:var(--bg-card);border-bottom:var(--border-width) solid var(--border-color);padding:.75rem 1.5rem;display:flex;justify-content:space-between;align-items:center}.header-content{display:flex;align-items:center;gap:.75rem}.header-actions{display:flex;align-items:center;gap:.5rem}.app-title{font-family:var(--font-display);font-size:1.75rem;font-weight:700;color:var(--text-primary);letter-spacing:-.02em}.app-subtitle{font-size:.875rem;color:var(--text-muted);font-weight:500;line-height:1}.mobile-view-title{display:none}.tab-navigation{display:flex;justify-content:center;gap:.5rem}.tab{display:flex;align-items:center;gap:.5rem;padding:.75rem 1.25rem;text-decoration:none;color:var(--text-primary);background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);font-weight:600;transition:background-color .15s ease}.tab:hover{background:var(--bg-secondary)}.tab.active{background-color:var(--accent-blue);color:var(--text-on-accent)}.tab-icon{font-size:1.1rem}.tab-label{font-weight:600;font-size:.9rem}.tab.tab-right{margin-left:auto}.tab-group .subview.hidden{display:none}.pill-nav{display:flex;align-items:center;gap:var(--space-1);padding:0;margin-bottom:1rem;min-height:2rem}.pill{padding:var(--space-1) var(--space-3);border-radius:var(--radius-xl);border:var(--border-width-sm) solid var(--border-color);background:var(--bg-card);font-family:var(--font-sans);font-size:var(--font-size-sm);font-weight:600;cursor:pointer;transition:background-color var(--transition-fast)}.pill:hover{background:var(--bg-tertiary)}.pill.active{background:var(--text-primary);color:var(--bg-card);border-color:var(--text-primary)}.main-content{flex:1;max-width:var(--width-container);width:100%;margin:0 auto;padding:1.5rem 1.75rem 2rem}.page-header{display:flex;justify-content:space-between;align-items:center;gap:.5rem;margin-bottom:1rem}.page-title{font-family:var(--font-heading);font-size:1.75rem;font-weight:700;color:var(--text-primary)}.tab-group{position:relative}.tab-group>.subview>.page-header{position:absolute;top:0;right:0;margin:0;z-index:1}.tab-group .page-header .page-title{display:none}#day-plan-view>.page-header,#project-dashboard-view>.page-header{position:static;margin-bottom:1rem}#project-dashboard-view .page-title{display:block}.btn{display:inline-flex;align-items:center;justify-content:center;gap:.5rem;padding:.625rem 1.25rem;border:var(--border-width) solid var(--border-color);border-radius:var(--radius-sm);font-size:.9rem;font-weight:600;cursor:pointer;transition:background-color .15s ease;text-decoration:none;background:var(--bg-card);color:var(--text-primary)}.btn:hover{background:var(--bg-secondary)}.btn:active{background:var(--bg-tertiary)}.btn:disabled{background:var(--bg-tertiary);color:var(--text-muted);cursor:not-allowed;opacity:.7}.btn:disabled:hover{background:var(--bg-tertiary)}.btn-primary{background-color:var(--accent-blue);color:var(--text-on-accent)}.btn-primary:hover{background-color:color-mix(in srgb,var(--accent-blue) 85%,#000)}.btn-primary:active{background-color:color-mix(in srgb,var(--accent-blue) 70%,#000)}.btn-secondary{background-color:var(--bg-secondary);color:var(--text-primary)}.btn-danger{background-color:var(--accent-red);color:var(--text-on-accent)}.btn-danger:hover{background-color:color-mix(in srgb,var(--accent-red) 85%,#000)}.btn-danger:active{background-color:color-mix(in srgb,var(--accent-red) 70%,#000)}.btn-sm{padding:.375rem .75rem;font-size:.8rem}.quick-add{display:flex;gap:.75rem;margin-bottom:1.5rem}.quick-add-input{flex:1;padding:.875rem 1rem;border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);background-color:var(--bg-card);font-size:1rem;color:var(--text-primary)}.quick-add-input::placeholder{color:var(--text-muted)}.quick-add-input:focus{outline:0;background-color:var(--accent-blue);color:var(--text-on-accent);box-shadow:0 0 0 2px var(--border-color)}.cards-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:1.25rem}.card{background-color:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);padding:1.25rem;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color);transition:transform .15s ease,box-shadow .15s ease,background-color .15s ease;cursor:pointer}.card:hover{background-color:var(--bg-secondary);transform:translate(-2px,-2px);box-shadow:calc(var(--shadow-offset) + 2px) calc(var(--shadow-offset) + 2px) 0 var(--border-color)}.card-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:.75rem}.card-title{font-family:var(--font-heading);font-size:1.1rem;font-weight:700;color:var(--text-primary)}.card-description{font-size:.9rem;color:var(--text-secondary);margin-bottom:1rem}.markdown-content{font-size:.9rem;color:var(--text-secondary);line-height:1.5}.markdown-content p{margin:0 0 .5em 0}.markdown-content p:last-child{margin-bottom:0}.markdown-content ol,.markdown-content ul{margin:0 0 .5em 1.5em;padding:0}.markdown-content code{background:var(--bg-tertiary);padding:.1em .3em;border-radius:3px;font-size:.85em}.markdown-content pre{background:var(--bg-tertiary);padding:.5em;border-radius:4px;overflow-x:auto;margin:0 0 .5em 0}.markdown-content pre code{background:0 0;padding:0}.markdown-content a{color:var(--accent-color)}.markdown-content blockquote{border-left:3px solid var(--border-color);margin:0 0 .5em 0;padding-left:.75em;color:var(--text-secondary)}.markdown-content h1,.markdown-content h2,.markdown-content h3{margin:.5em 0 .25em 0;font-size:1em;font-weight:600;color:var(--text-primary)}.markdown-content table{border-collapse:collapse;margin:.5em 0}.markdown-content td,.markdown-content th{border:1px solid var(--border-color);padding:.25em .5em}.markdown-content img{max-width:100%}.card-meta{display:flex;gap:.5rem;flex-wrap:wrap}.badge,.tag{display:inline-flex;align-items:center;padding:.25rem .625rem;border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-sm);font-size:.8125rem;font-weight:600;background:var(--bg-card);color:var(--text-primary)}.badge[data-color=green],.tag[data-color=green]{background-color:color-mix(in srgb,var(--accent-green) 20%,var(--bg-card));border-color:var(--accent-green)}.badge[data-color=yellow],.tag[data-color=yellow]{background-color:color-mix(in srgb,var(--accent-yellow) 20%,var(--bg-card));border-color:var(--accent-yellow)}.badge[data-color=red],.tag[data-color=red]{background-color:color-mix(in srgb,var(--accent-red) 20%,var(--bg-card));border-color:var(--accent-red)}.badge[data-color=cyan],.tag[data-color=cyan]{background-color:color-mix(in srgb,var(--accent-cyan) 20%,var(--bg-card));border-color:var(--accent-cyan)}.badge[data-color=purple],.tag[data-color=purple]{background-color:color-mix(in srgb,var(--accent-purple) 20%,var(--bg-card));border-color:var(--accent-purple)}.badge[data-color=muted],.tag[data-color=muted]{background-color:var(--bg-tertiary);border-color:var(--text-muted)}.tag.status-active{background-color:color-mix(in srgb,var(--accent-green) 20%,var(--bg-card));border-color:var(--accent-green)}.tag.status-on_hold,.tag.status-onhold{background-color:color-mix(in srgb,var(--accent-yellow) 20%,var(--bg-card));border-color:var(--accent-yellow)}.tag.status-archived{background-color:var(--bg-tertiary);border-color:var(--text-muted)}.tag.status-inactive{background-color:color-mix(in srgb,var(--accent-red) 20%,var(--bg-card));border-color:var(--accent-red)}.tag.status-completed{background-color:color-mix(in srgb,var(--accent-cyan) 20%,var(--bg-card));border-color:var(--accent-cyan)}.data-table{width:100%;border-collapse:separate;border-spacing:0;background-color:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);overflow:hidden;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.data-table td,.data-table th{padding:1rem 1.25rem;text-align:left;border-bottom:2px solid var(--border-color)}.data-table th{background-color:var(--bg-secondary);font-size:.8rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--text-primary)}.data-table tbody tr{transition:background-color .15s ease}.data-table tbody tr:hover{background-color:var(--bg-secondary)}.data-table tbody tr:last-child td{border-bottom:none}.data-table tbody tr.keyboard-selected,.data-table tbody tr.selected{background-color:color-mix(in srgb,var(--accent-blue) 25%,var(--bg-card))}.task-table{width:100%;background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-lg);overflow:hidden;display:flex;flex-direction:column;flex:1;min-height:0;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.task-header-row,.task-row{display:grid;grid-template-columns:1fr 140px 60px 110px 90px 100px 90px;align-items:center;gap:.75rem}.task-header-row{background-color:var(--bg-secondary);border-bottom:2px solid var(--border-color);padding:0 1.25rem}.task-header-row .task-cell{padding:.75rem 0;font-size:.8rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--text-primary)}.task-list-container{flex:1;min-height:0;overflow-y:auto;position:relative}.task-row{padding:.75rem 1.25rem;border-bottom:1px solid var(--border-color);transition:background-color .15s ease;cursor:pointer}.task-row:hover{background-color:var(--bg-secondary)}.task-row:last-child{border-bottom:none}.task-row.keyboard-selected,.task-row.selected{background-color:color-mix(in srgb,var(--accent-blue) 25%,var(--bg-card))}.task-cell{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.task-actions-header{text-align:right}.virtual-scroller-empty{padding:2rem;text-align:center;color:var(--text-secondary)}.event-table tbody tr{cursor:pointer}.task-description{font-weight:600;white-space:normal;display:flex;flex-wrap:wrap;align-items:center;gap:.25rem .5rem}.task-description-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}.task-project{font-size:.85rem;color:var(--text-secondary);white-space:nowrap}.priority-high,.priority-low,.priority-medium{display:inline-block;padding:.25rem .5rem;border-radius:var(--radius-xs);font-weight:700;text-align:center}.priority-high{color:var(--accent-red);background:#fde8ea;background:color-mix(in srgb,var(--accent-red) 15%,var(--bg-card))}.priority-medium{color:var(--accent-yellow);background:#fef8e6;background:color-mix(in srgb,var(--accent-yellow) 15%,var(--bg-card))}.priority-low{color:var(--text-muted);background:var(--bg-secondary)}.sortable{cursor:pointer;user-select:none;white-space:nowrap}.sortable:hover{background:var(--bg-hover)}.sort-arrow{display:inline-block;width:.8em;margin-left:.25rem;opacity:.3}.sort-arrow::after{content:'\2195'}.sortable.sort-asc .sort-arrow::after{content:'\2191'}.sortable.sort-desc .sort-arrow::after{content:'\2193'}.sortable.sort-asc .sort-arrow,.sortable.sort-desc .sort-arrow{opacity:1}.task-overdue .task-description-text{color:var(--accent-red)}.task-overdue .task-due{color:var(--accent-red);font-weight:600}.task-tags{display:flex;gap:.25rem;flex-wrap:wrap}.task-tag{background-color:var(--bg-tertiary);color:var(--text-primary);padding:.125rem .5rem;border-radius:var(--radius-xs);font-size:.75rem;font-weight:600;border:1px solid var(--border-color)}.recurrence-icon{color:var(--accent-purple);font-size:.85rem;font-weight:700}.annotation-badge{background-color:var(--accent-yellow);color:var(--text-primary);padding:.125rem .5rem;border-radius:var(--radius-xs);font-size:.7rem;font-weight:700;border:var(--border-width-sm) solid var(--border-color)}.subtask-badge{background-color:var(--bg-secondary);color:var(--text-primary);padding:.125rem .5rem;border-radius:var(--radius-xs);font-size:.7rem;font-weight:700;border:var(--border-width-sm) solid var(--border-color);margin-left:.25rem}.task-started{border-left:4px solid var(--accent-green)}.task-completed{opacity:.5;text-decoration:line-through}.task-deleted{display:none}.due-overdue{color:var(--accent-red);font-weight:700;background:#fde8ea;background:color-mix(in srgb,var(--accent-red) 15%,var(--bg-card));padding:.25rem .5rem;border-radius:var(--radius-xs)}.due-today{color:var(--accent-yellow);font-weight:700;background:#fef8e6;background:color-mix(in srgb,var(--accent-yellow) 15%,var(--bg-card));padding:.25rem .5rem;border-radius:var(--radius-xs)}.due-soon{color:var(--text-secondary)}.due-future{color:var(--text-muted)}.events-list{display:flex;flex-direction:column;flex:1;min-height:0;gap:1rem}.event-table-virtual{width:100%;background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-lg);overflow:hidden;display:flex;flex-direction:column;flex:1;min-height:0;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.event-header-row,.event-row-virtual{display:grid;grid-template-columns:100px 80px 1fr 150px;align-items:center;gap:.5rem}.event-header-row{background-color:var(--bg-secondary);border-bottom:2px solid var(--border-color);flex-shrink:0}.event-header-row .event-cell{padding:1rem 1.25rem;font-size:.8rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--text-primary)}.event-list-container{flex:1;min-height:0;overflow-y:auto;position:relative}.event-row-virtual{padding:.75rem 1.25rem;border-bottom:1px solid var(--border-color);transition:background-color .15s ease;cursor:pointer}.event-row-virtual:hover{background-color:var(--bg-secondary)}.event-row-virtual:last-child{border-bottom:none}.event-row-virtual.event-past{opacity:.7}.event-cell{overflow:hidden;text-overflow:ellipsis}.event-row{cursor:pointer}.event-cell-date{white-space:nowrap}.event-cell-date .event-date-num{font-weight:700;font-size:.9rem;color:var(--text-primary);margin-right:.5rem}.event-date-badge{display:inline-block;padding:.15rem .4rem;background:var(--accent-green);color:var(--text-on-accent);font-size:.7rem;font-weight:700;text-transform:uppercase;border-radius:var(--radius-xs);margin-right:.5rem}.event-cell-time{font-family:var(--font-mono);font-size:.85rem;color:var(--text-secondary)}.event-cell-title{font-weight:600}.event-cell-location{color:var(--text-secondary);font-size:.875rem}.event-date-badge.event-proximity-today{background:var(--accent-green)}.event-date-badge.event-proximity-tomorrow{background:var(--accent-yellow);color:var(--text-primary)}.event-date-badge.event-proximity-week{background:var(--accent-cyan)}.event-date-badge.event-proximity-future{background:var(--accent-blue)}.event-date-badge.event-proximity-past{background:var(--text-muted)}.event-row.event-past{opacity:.7}.no-upcoming-events{text-align:center;padding:2rem;color:var(--text-secondary);font-style:italic}.past-events-section{margin-top:.5rem}.past-events-toggle{display:flex;align-items:center;gap:.75rem;padding:.75rem 1rem;background:var(--bg-secondary);border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-sm);cursor:pointer;font-weight:600;color:var(--text-secondary);transition:background-color .15s ease,color .15s ease;list-style:none}.past-events-toggle::-webkit-details-marker{display:none}.past-events-toggle::before{content:'▶';font-size:.7rem;transition:transform .15s ease}.past-events-section[open] .past-events-toggle::before{transform:rotate(90deg)}.past-events-toggle:hover{background:var(--bg-tertiary);color:var(--text-primary)}.past-events-label{flex:1}.past-events-count{background:var(--text-muted);color:var(--text-on-accent);font-size:.75rem;padding:.15rem .5rem;border-radius:var(--radius-sm)}.past-events-section .event-table-past{margin-top:.75rem;opacity:.85}.past-events-section .event-list-container{max-height:300px}.event-item{display:flex;gap:1rem;padding:1rem;background-color:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);transition:background-color .15s ease;cursor:pointer}.event-item:hover{background-color:var(--bg-secondary)}.event-date{flex-shrink:0;width:80px;text-align:center;padding:.75rem;background-color:var(--accent-green);border-radius:var(--radius-sm);color:var(--text-on-accent)}.event-date-day{font-size:.7rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em}.event-date-num{font-size:1.5rem;font-weight:700}.event-content{flex:1}.event-title{font-family:var(--font-heading);font-weight:700;font-size:1.1rem;color:var(--text-primary);margin-bottom:.25rem}.event-details{font-size:.875rem;color:var(--text-secondary);display:flex;gap:1rem}.event-location,.event-time{display:flex;align-items:center;gap:.25rem}.event-project{margin-top:.5rem}.email-list{display:flex;flex-direction:column;background-color:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);overflow:hidden;flex:1;min-height:0;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.email-list-container{flex:1;min-height:0;overflow-y:auto;position:relative}.email-item{display:flex;gap:1rem;padding:1rem;border-bottom:2px solid var(--border-color);transition:background-color .15s ease;cursor:pointer}.email-item:last-child{border-bottom:none}.email-item:hover{background-color:var(--bg-secondary)}.email-item.unread{background-color:color-mix(in srgb,var(--accent-blue) 20%,var(--bg-card));border-left:4px solid var(--accent-blue)}.email-item.unread .email-subject{font-weight:700}.email-item.unread .email-from{font-weight:700}.email-item.outgoing{border-left:4px solid var(--accent-green)}.email-checkbox{flex-shrink:0;margin-top:.25rem}.email-content{flex:1;min-width:0}.email-header{display:flex;justify-content:space-between;margin-bottom:.25rem;align-items:center;gap:.5rem}.thread-badge{background-color:var(--bg-tertiary);color:var(--text-secondary);font-size:.7rem;font-weight:600;padding:.1rem .4rem;border-radius:var(--radius-md);min-width:1.25rem;text-align:center}.email-from{color:var(--text-primary);font-size:.9rem;font-weight:600}.email-date{color:var(--text-muted);font-size:.8rem;flex-shrink:0;font-weight:600}.email-subject{color:var(--text-primary);font-size:.95rem;margin-bottom:.25rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.email-preview{color:var(--text-muted);font-size:.85rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}@keyframes toastSlideIn{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.toast-undo{display:flex;align-items:center;gap:1rem}.undo-message{flex:1}.undo-btn{padding:.25rem .75rem;background:var(--accent-blue);color:var(--text-on-accent);border:2px solid var(--border-color);border-radius:var(--radius-sm);font-family:inherit;font-size:var(--font-size-sm);font-weight:600;cursor:pointer;transition:background .15s ease}.undo-btn:hover{background:color-mix(in srgb,var(--accent-blue) 80%,#000)}.undo-countdown{font-size:var(--font-size-sm);color:var(--text-muted);min-width:2.5rem;text-align:right}@keyframes modalFadeIn{from{opacity:0}to{opacity:1}}@keyframes modalSlideIn{from{opacity:0;transform:translateY(-20px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}@keyframes modalFadeOut{from{opacity:1}to{opacity:0}}@keyframes modalSlideOut{from{opacity:1;transform:translateY(0) scale(1)}to{opacity:0;transform:translateY(-20px) scale(.95)}}.modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:var(--overlay-color);display:flex;align-items:center;justify-content:center;z-index:1000;animation:modalFadeIn .15s ease-out}.modal-overlay.hidden{display:none}.modal-overlay.closing{animation:modalFadeOut .15s ease-in forwards}.modal-container{background-color:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-lg);box-shadow:var(--shadow-brutal-xl);max-width:var(--width-modal);width:90%;max-height:90vh;overflow:auto;animation:modalSlideIn .2s ease-out}.modal-container.modal-large{max-width:calc(100vw - 4rem);width:calc(100vw - 4rem);max-height:calc(100vh - 4rem);height:calc(100vh - 4rem);display:flex;flex-direction:column}.modal-container.modal-large .modal-content{flex:1;overflow:auto;display:flex;flex-direction:column}.modal-overlay.closing .modal-container{animation:modalSlideOut .15s ease-in forwards}.modal-header{display:flex;justify-content:space-between;align-items:center;padding:1rem 1.5rem;border-bottom:var(--border-width) solid var(--border-color);background:var(--bg-secondary)}.modal-header h2,.modal-title{font-family:var(--font-heading);font-size:1.25rem;font-weight:700}.modal-close{background:var(--bg-card);border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-sm);font-size:1.25rem;color:var(--text-primary);cursor:pointer;line-height:1;width:36px;height:36px;display:flex;align-items:center;justify-content:center;transition:background-color .15s ease}.modal-close:hover{background:var(--accent-blue);color:var(--text-on-accent)}.modal-content{padding:1.5rem}.form-group{margin-bottom:1.25rem}.form-more-toggle{display:block;background:0 0;border:none;cursor:pointer;font-size:.85rem;font-weight:600;color:var(--accent-blue);padding:.25rem 0;margin-bottom:.75rem}.form-more-toggle::before{content:'+ '}.form-more-toggle.expanded::before{content:'- '}.form-extended-fields.hidden{display:none}.form-label{display:block;font-size:.9rem;font-weight:700;color:var(--text-primary);margin-bottom:.5rem}.form-input,.form-select,.form-textarea{width:100%;padding:.75rem 1rem;border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-sm);background-color:var(--bg-card);color:var(--text-primary);font-size:1rem;box-shadow:none}.form-input:focus,.form-select:focus,.form-textarea:focus{outline:0;background-color:var(--bg-card);box-shadow:0 0 0 2px var(--accent-blue)}.form-textarea{min-height:100px;resize:vertical}.form-actions{display:flex;justify-content:flex-end;gap:.75rem;margin-top:1.5rem}.form-input[aria-invalid=true],.form-select[aria-invalid=true],.form-textarea[aria-invalid=true]{border-color:var(--accent-red);box-shadow:0 0 0 2px color-mix(in srgb,var(--accent-red) 30%,transparent)}.form-input[aria-invalid=true]:focus,.form-select[aria-invalid=true]:focus,.form-textarea[aria-invalid=true]:focus{box-shadow:0 0 0 2px var(--accent-red)}.form-error{color:var(--accent-red);font-size:.8rem;font-weight:600;margin-top:.25rem;display:none}.form-error.visible{display:block}.app-footer{background-color:var(--bg-card);border-top:var(--border-width) solid var(--border-color);padding:.75rem 1.5rem}.footer-content{max-width:var(--width-container);margin:0 auto;display:flex;justify-content:space-between;align-items:center}.keyboard-hints{display:flex;gap:1rem;font-size:.8rem;color:var(--text-muted)}kbd{display:inline-block;padding:.2rem .5rem;background-color:var(--bg-secondary);border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-xs);font-family:var(--font-mono);font-size:.75rem;font-weight:700}.version{font-size:.75rem;color:var(--text-muted);font-weight:600}.empty-state{text-align:center;padding:3rem;color:var(--text-secondary)}.empty-state-icon{font-size:4rem;margin-bottom:1rem}.empty-state-text{font-size:1.1rem;font-weight:600;margin-bottom:1rem}.error-state{text-align:center;padding:2rem;color:var(--accent-red);background:color-mix(in srgb,var(--accent-red) 10%,var(--bg-card));border:var(--border-width-sm) solid var(--accent-red);border-radius:var(--radius-sm);font-weight:600}.view{display:block}.view.hidden{display:none}.filter-bar{display:flex;flex-wrap:wrap;gap:.75rem;margin-bottom:1.5rem;padding:1rem;background-color:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.filter-group{display:flex;align-items:center;gap:.5rem}.filter-label{font-size:.8rem;font-weight:700;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.05em}.filter-select{padding:.5rem .75rem;border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-sm);background-color:var(--bg-card);color:var(--text-primary);font-size:.875rem;font-weight:600}.filter-select:focus{outline:0;background-color:var(--accent-blue);color:var(--text-on-accent)}.filter-checkbox{display:flex;align-items:center;gap:.4rem;font-size:.875rem;font-weight:600;color:var(--text-primary);cursor:pointer}.filter-checkbox input[type=checkbox]{width:1rem;height:1rem;cursor:pointer}.btn-link{background:0 0;border:none;box-shadow:none;color:var(--text-secondary);font-size:.875rem;cursor:pointer;text-decoration:underline;padding:.5rem}.btn-link:hover{box-shadow:none;transform:none;color:var(--text-primary)}@media (min-width:1400px){.main-content{max-width:1600px}.cards-grid{grid-template-columns:repeat(auto-fill,minmax(380px,1fr))}.project-dashboard-grid{gap:2rem}.day-plan-sidebar{width:320px}.modal-container{max-width:640px}}@media (max-width:1024px){.saved-views-sidebar{width:180px}.day-plan-sidebar{width:240px}.project-dashboard-grid{grid-template-columns:1fr 1fr;gap:1rem}.project-dashboard-grid .dashboard-column:last-child{grid-column:span 2}.filter-bar{flex-wrap:wrap}.filter-actions{width:100%;justify-content:flex-end;margin-top:.5rem}}@media (max-width:768px){.tab-navigation{flex-wrap:wrap;gap:.5rem}.tab{flex:1 1 auto;min-width:calc(33% - .5rem);justify-content:center;padding:.625rem .75rem}.tab-label{display:none}.tab-icon{font-size:1.25rem}.cards-grid{grid-template-columns:1fr}.task-table{font-size:.85rem}.task-header-row,.task-row{grid-template-columns:1fr 80px 40px 80px}.task-header-row .task-cell:nth-child(n+5),.task-row .task-cell:nth-child(n+5){display:none}.filter-bar{flex-direction:column}.keyboard-hints{display:none}.page-title{font-size:1.5rem}.saved-views-sidebar{display:none}.day-plan-content{flex-direction:column}.day-plan-sidebar{width:100%;max-height:200px}.project-dashboard-grid{grid-template-columns:1fr}.project-dashboard-grid .dashboard-column:last-child{grid-column:span 1}.modal-container{width:95%;max-height:95vh}.bulk-actions-bar{flex-wrap:wrap}.bulk-select-all{width:100%;margin-top:.5rem}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.pagination-controls{display:flex;align-items:center;justify-content:center;gap:1rem;padding:1rem;margin-top:1rem;background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md)}.pagination-info{font-weight:600;color:var(--text-secondary);font-size:.9rem}.pagination-controls .btn:disabled{opacity:.5;cursor:not-allowed}.btn:focus-visible,.card:focus-visible,.dashboard-item:focus-visible,.email-item:focus-visible,.event-row-virtual:focus-visible,.filter-select:focus-visible,.form-input:focus-visible,.form-select:focus-visible,.form-textarea:focus-visible,.modal-close:focus-visible,.saved-view-item:focus-visible,.snooze-option:focus-visible,.tab:focus-visible,.task-row:focus-visible,.timeline-item:focus-visible,.unscheduled-task:focus-visible{outline:3px solid var(--accent-blue);outline-offset:2px}.event-row,.task-row-clickable{cursor:pointer}.skip-link{position:absolute;top:-100px;left:0;background:var(--accent-blue);color:var(--text-on-accent);padding:.75rem 1.5rem;z-index:9999;font-weight:700;border:var(--border-width) solid var(--border-color);text-decoration:none}.skip-link:focus{top:0}.source-email-link{padding:.75rem;background:var(--bg-secondary);border-radius:var(--radius-sm);border-left:4px solid var(--accent-blue)}.thread-message{margin-bottom:1rem;padding:.75rem;background:var(--bg-secondary);border-radius:var(--radius-sm)}.thread-message-latest{border-left:3px solid var(--accent-blue)}.thread-message-header{display:flex;justify-content:space-between;margin-bottom:.5rem;font-size:.8rem;color:var(--text-secondary)}.thread-message-from{font-weight:700}.email-reader-body{white-space:pre-wrap;font-size:.9rem;line-height:1.6;color:var(--text-primary);word-wrap:break-word;overflow-wrap:break-word}.email-reader-body .email-link{color:var(--accent-blue);text-decoration:underline;cursor:pointer;word-break:break-all}.email-reader-body .email-link:hover{color:var(--accent-cyan)}.email-reader-body hr{border:none;border-top:2px solid var(--border-color);margin:1rem 0}.email-reader-quote{border-left:3px solid var(--text-muted);padding-left:1rem;margin:.5rem 0;color:var(--text-secondary);font-style:italic}.email-quote-toggle{display:inline-block;color:var(--text-muted);font-size:.8125rem;cursor:pointer;padding:.25rem 0;user-select:none}.email-quote-toggle:hover{color:var(--accent-blue)}.email-quote-block{border-left:3px solid var(--text-muted);padding-left:1rem;margin:.25rem 0 .5rem;color:var(--text-secondary)}.email-quote-block.hidden{display:none}.autocomplete-dropdown{background:var(--bg-card);border:1px solid var(--border-color);border-radius:var(--radius-sm);box-shadow:var(--shadow-brutal);z-index:100;max-height:200px;overflow-y:auto}.autocomplete-item{padding:.5rem .75rem;cursor:pointer;font-size:.875rem}.autocomplete-item.active,.autocomplete-item:hover{background:var(--bg-secondary)}.autocomplete-name{font-weight:500}.autocomplete-email{color:var(--text-secondary);margin-left:.25rem}.email-label-badge{display:inline-block;font-size:.6875rem;padding:.125rem .375rem;border-radius:var(--radius-sm);background:var(--accent-blue);color:var(--bg-primary);font-weight:600;vertical-align:middle}.email-reader-container{display:flex;flex-direction:column;height:100%;min-height:0}.email-reader-header{margin-bottom:1rem;padding-bottom:.75rem;border-bottom:1px solid var(--border-color)}.email-sender-contact{display:flex;align-items:center;gap:.5rem;margin-top:.5rem;padding:.4rem .5rem;background:var(--bg-tertiary);border-radius:4px}.email-sender-info{display:flex;flex-direction:column;flex:1;min-width:0}.email-sender-name{font-weight:600;font-size:.85rem}.email-sender-company{font-size:.75rem;color:var(--text-secondary)}.contact-avatar-sm{width:32px;height:32px;border-radius:50%;background:var(--accent-color);color:var(--bg-primary);display:flex;align-items:center;justify-content:center;font-size:.75rem;font-weight:700;flex-shrink:0}.contact-avatar-unknown{background:var(--bg-secondary);color:var(--text-secondary);border:var(--border-width-sm) solid var(--border-color)}.email-reader-thread{flex:1;overflow-y:auto;margin-bottom:1rem;min-height:0}.dropdown{position:relative;display:inline-block}.dropdown-menu{display:none;position:absolute;bottom:100%;left:0;margin-bottom:.25rem;background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);box-shadow:var(--shadow-brutal-md);min-width:160px;z-index:100}.dropdown-menu.show{display:block}.dropdown-item{display:block;width:100%;padding:.5rem 1rem;text-align:left;background:0 0;border:none;cursor:pointer;font-size:.875rem;color:var(--text-primary)}.dropdown-item:hover{background:var(--bg-secondary)}.dropdown-item:first-child{border-radius:var(--radius-md) var(--radius-md) 0 0}.dropdown-item:last-child{border-radius:0 0 var(--radius-md) var(--radius-md)}.context-menu{position:fixed;z-index:10000;min-width:180px;max-width:280px;background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-sm);box-shadow:var(--shadow-brutal-lg);padding:.25rem 0;display:none}.context-menu.visible{display:block}.context-menu-item{display:flex;align-items:center;gap:.75rem;padding:.5rem 1rem;font-size:.875rem;font-weight:500;color:var(--text-primary);cursor:pointer;border:none;background:0 0;width:100%;text-align:left;transition:background .1s}.context-menu-item:focus,.context-menu-item:hover{background:var(--accent-blue);color:var(--text-on-accent);outline:0}.context-menu-item:focus-visible{outline:2px solid var(--accent-blue);outline-offset:-2px}.context-menu-header{font-size:.7rem;font-weight:700;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.05em;padding:.5rem 1rem .25rem}.context-menu-item-icon{width:1.25rem;text-align:center;flex-shrink:0}.context-menu-item-label{flex:1}.context-menu-item-subtitle{display:block;font-size:.7rem;color:var(--text-secondary);font-weight:400}.context-menu-item-shortcut{font-size:.75rem;color:var(--text-muted);font-family:var(--font-mono)}.context-menu-item--danger{color:var(--accent-red)}.context-menu-item--danger:hover{background:var(--accent-red);color:var(--text-on-accent)}.context-menu-separator{height:2px;background:var(--border-color);margin:.25rem .5rem}.context-menu-hint{padding:.35rem 1rem;font-size:.7rem;color:var(--text-muted);border-top:1px solid var(--border-color);margin-top:.25rem}::-webkit-scrollbar{width:12px;height:12px}::-webkit-scrollbar-track{background:var(--bg-secondary);border-left:2px solid var(--border-color)}::-webkit-scrollbar-thumb{background:var(--text-muted);border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-sm)}::-webkit-scrollbar-thumb:hover{background:var(--text-secondary)}.loading{display:flex;justify-content:center;align-items:center;height:200px;color:var(--text-secondary);font-family:var(--font-heading)}.skeleton-shimmer{display:flex;flex-direction:column;gap:1rem;padding:1rem}.skeleton-shimmer .skeleton-row{display:flex;align-items:center;gap:.75rem;padding:.75rem;background:var(--bg-card);border-radius:var(--radius-md);border:var(--border-width) solid var(--border-color)}.skeleton-shimmer .skeleton-avatar{width:36px;height:36px;border-radius:var(--radius-full);background:linear-gradient(90deg,var(--bg-secondary) 25%,var(--bg-tertiary) 50%,var(--bg-secondary) 75%);background-size:200% 100%;animation:skeleton-pulse 1.5s ease-in-out infinite;flex-shrink:0}.skeleton-shimmer .skeleton-lines{flex:1;display:flex;flex-direction:column;gap:.4rem}.skeleton-shimmer .skeleton-line{height:.75rem;border-radius:var(--radius-sm);background:linear-gradient(90deg,var(--bg-secondary) 25%,var(--bg-tertiary) 50%,var(--bg-secondary) 75%);background-size:200% 100%;animation:skeleton-pulse 1.5s ease-in-out infinite}.skeleton-shimmer .skeleton-line.short{width:40%}.skeleton-shimmer .skeleton-line.medium{width:65%}.skeleton-shimmer .skeleton-line.long{width:90%}@keyframes skeleton-pulse{0%{background-position:200% 0}100%{background-position:-200% 0}}@keyframes spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}.spinner{display:inline-block;width:1em;height:1em;border:2px solid currentColor;border-top-color:transparent;border-radius:var(--radius-full);animation:spin .8s linear infinite}.btn-loading{position:relative;pointer-events:none;opacity:.8}.btn-loading .btn-text{visibility:hidden}.btn-loading::after{content:'';position:absolute;left:50%;top:50%;width:1em;height:1em;margin-left:-.5em;margin-top:-.5em;border:2px solid currentColor;border-top-color:transparent;border-radius:var(--radius-full);animation:spin .8s linear infinite}.hidden{display:none!important}.project-dashboard-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:1.5rem;flex:1;min-height:0}.dashboard-column{background:var(--bg-card);border:var(--border-width) solid var(--border-color);padding:1rem;display:flex;flex-direction:column;overflow:hidden}.dashboard-column-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;padding-bottom:.5rem;border-bottom:2px solid var(--border-color)}.dashboard-column-header h3{margin:0;font-size:1rem;font-family:var(--font-heading);font-weight:700}.dashboard-list{flex:1;overflow-y:auto}.dashboard-item{padding:.75rem;background:var(--bg-card);border:var(--border-width-sm) solid var(--border-color);margin-bottom:.5rem;cursor:pointer;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color);transition:transform .15s ease,box-shadow .15s ease,background-color .15s ease}.dashboard-item:hover{background:var(--bg-secondary);transform:translate(-2px,-2px);box-shadow:calc(var(--shadow-offset) + 2px) calc(var(--shadow-offset) + 2px) 0 var(--border-color)}.dashboard-item-title{font-weight:600;margin-bottom:.25rem}.dashboard-item-meta{font-size:.75rem;color:var(--text-secondary)}.empty-dashboard-list{text-align:center;padding:2rem 1rem;color:var(--text-secondary)}.task-badges{display:flex;gap:.25rem;margin-top:.25rem}.task-badge{font-size:.65rem;padding:.15rem .4rem;border:var(--border-width-sm) solid var(--border-color);background:var(--bg-secondary);color:var(--text-primary);font-weight:600}.task-badge.has-items{background:var(--accent-blue);color:var(--text-on-accent)}.task-badge.recurrence{background:var(--accent-purple);color:var(--text-on-accent)}.task-row-clickable{cursor:pointer;transition:background .1s}.task-row-clickable:hover{background:var(--bg-secondary)}.progress-bar-container{width:100%;height:10px;background:var(--bg-secondary);border:var(--border-width-sm) solid var(--border-color);overflow:hidden}.progress-bar{height:100%;background:var(--accent-green);transition:width .3s ease}.no-subtasks{color:var(--text-secondary);font-size:.875rem}#day-plan-view{display:flex;flex-direction:column;flex:1;min-height:0}#day-plan-view .page-header{flex-shrink:0}.day-plan-nav{display:flex;align-items:center;gap:.5rem}.day-plan-date-picker{padding:.5rem;background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-sm);color:var(--text-primary);font-family:var(--font-body)}.day-plan-date-display{font-size:1.25rem;font-weight:700;margin-left:1rem;font-family:var(--font-heading);line-height:1}.day-plan-content{flex:1;min-height:0;display:flex;gap:1.5rem}.day-plan-main{flex:1;min-height:0;display:flex;flex-direction:column;background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);overflow:hidden;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.day-plan-sidebar{width:280px;flex-shrink:0;display:flex;flex-direction:column;background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);overflow:hidden;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.sidebar-header{padding:1rem;border-bottom:2px solid var(--border-color);flex-shrink:0}.sidebar-header h3{margin:0;font-size:1rem;font-family:var(--font-heading);font-weight:700}.sidebar-task-list{flex:1;overflow-y:auto;padding:.75rem;display:flex;flex-direction:column;gap:.5rem}.timeline-container{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden}.timeline-scroll-area{position:relative;padding:.5rem 1rem 3rem .5rem;min-height:min-content}#timeline-slots{position:relative}#timeline-items{position:absolute;top:.5rem;left:.5rem;right:1rem;bottom:0;pointer-events:none}#timeline-items .timeline-item{pointer-events:auto}.timeline-slot{display:grid;grid-template-columns:50px 1fr;height:12px;position:relative}.timeline-slot.hour-start .timeline-slot-area{border-top:1px dashed color-mix(in srgb,var(--border-color) 50%,transparent)}.timeline-time{font-size:.7rem;color:var(--text-secondary);padding-right:.5rem;text-align:right;font-weight:500;transform:translateY(-.5em)}.timeline-slot-area{position:relative}.timeline-slot-area:hover{background:var(--bg-secondary)}.timeline-item{position:absolute;left:60px;right:10px;border:var(--border-width) solid var(--border-color);border-radius:var(--radius-sm);padding:.25rem .5rem;overflow:hidden;cursor:pointer;z-index:10}.timeline-item.task{background:var(--accent-green);color:var(--text-primary)}.timeline-item.event{background:var(--accent-blue);color:var(--text-on-accent)}.timeline-item.block{opacity:.85}.timeline-item.block-free_time{background:var(--accent-cyan);color:var(--text-primary)}.timeline-item.block-personal{background:var(--accent-yellow);color:var(--text-primary)}.timeline-item.block-vacation{background:var(--accent-purple);color:var(--text-on-accent)}.timeline-item.block-focus{background:var(--accent-red);color:var(--text-on-accent)}.timeline-item.conflict{box-shadow:0 0 0 3px var(--accent-red)}.timeline-item.selected{box-shadow:0 0 0 3px var(--bg-card),0 0 0 6px var(--accent-blue)}.timeline-item-title{font-weight:600;font-size:.75rem;line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.timeline-item-meta{font-size:.65rem;opacity:.85;line-height:1.1}.timeline-current-time{position:absolute;left:50px;right:0;height:2px;background:var(--accent-red);z-index:20;pointer-events:none}.timeline-current-time::before{content:'';position:absolute;left:-4px;top:-3px;width:8px;height:8px;background:var(--accent-red);border-radius:var(--radius-full)}.timeline-paint-preview{position:absolute;left:70px;right:10px;background:var(--accent-blue);opacity:.4;border:var(--border-width-sm) dashed var(--border-color);border-radius:var(--radius-sm);z-index:5;pointer-events:none}.timeline-container.is-painting{cursor:crosshair;user-select:none}.timeline-container.is-painting .timeline-slot-area{pointer-events:none}.unscheduled-task{padding:.75rem;background:var(--bg-card);border:var(--border-width-sm) solid var(--border-color);border-left:6px solid var(--accent-green);border-radius:var(--radius-sm);cursor:grab;transition:background-color .1s}.unscheduled-task:hover{background:var(--bg-secondary)}.unscheduled-task.priority-high{border-left-color:var(--accent-red)}.unscheduled-task.priority-medium{border-left-color:var(--accent-yellow)}.unscheduled-task.priority-low{border-left-color:var(--accent-green)}.unscheduled-task-title{font-weight:600;margin-bottom:.25rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.unscheduled-task-meta{font-size:.75rem;color:var(--text-secondary)}.empty-unscheduled{text-align:center;color:var(--text-secondary);padding:2rem 1rem}.settings-btn{background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-sm);color:var(--text-primary);font-size:1.25rem;cursor:pointer;padding:.5rem .75rem;margin-left:.5rem;transition:background-color .1s}.settings-btn:hover{background:var(--bg-secondary)}.settings-btn:active{background:var(--bg-tertiary)}.shortcut-hint-btn{font-family:var(--font-mono, monospace);font-weight:700;min-width:2rem;text-align:center;padding:.5rem}.settings-section h3{font-size:1rem;color:var(--text-primary)}.settings-section .form-hint{font-size:.75rem;color:var(--text-secondary)}.sync-indicator{background:0 0;border:none;cursor:pointer;padding:.25rem .5rem;display:flex;align-items:center}.sync-dot{width:8px;height:8px;border-radius:var(--radius-full);background:var(--text-muted);transition:background var(--transition-slow)}.sync-dot.connected{background:var(--accent-green)}.sync-dot.syncing{background:var(--accent-blue);animation:sync-pulse 1s infinite}.sync-dot.error{background:var(--accent-red)}@keyframes sync-pulse{0%,100%{opacity:1}50%{opacity:.4}}.snooze-options{display:flex;flex-direction:column;gap:.5rem}.snooze-option{display:flex;justify-content:space-between;align-items:center;padding:.75rem 1rem;background:var(--bg-secondary);border:var(--border-width-sm) solid var(--border-color);color:var(--text-primary);cursor:pointer;transition:background-color .1s;text-align:left;width:100%}.snooze-option:hover{background:var(--accent-blue);color:var(--text-on-accent)}.snooze-option-label{font-weight:600}.snooze-option-time{font-size:.75rem;color:var(--text-secondary)}.snooze-option:hover .snooze-option-time{color:var(--text-on-accent)}.snooze-custom{margin-top:.5rem;padding-top:.5rem;border-top:2px solid var(--border-color)}.snooze-badge{display:inline-block;font-size:.65rem;padding:.15rem .4rem;border:var(--border-width-sm) solid var(--border-color);background:var(--accent-yellow);color:var(--text-primary);font-weight:700;margin-top:.25rem}.contact-badge{display:inline-block;font-size:.65rem;padding:.15rem .4rem;border:var(--border-width-sm) solid var(--border-color);background:var(--accent-color);color:var(--bg-primary);font-weight:700;margin-top:.25rem}.bulk-checkbox{width:18px;height:18px;cursor:pointer;accent-color:var(--accent-blue);border:var(--border-width-sm) solid var(--border-color)}.task-actions-cell{text-align:right;white-space:nowrap;display:flex;align-items:center;justify-content:flex-end;gap:.5rem}.task-actions-cell .bulk-checkbox{margin-right:.5rem}.task-kebab-btn{background:0 0;border:none;cursor:pointer;font-size:1.1rem;line-height:1;padding:.2rem .4rem;border-radius:var(--radius-sm);color:var(--text-secondary);opacity:0;transition:opacity .15s ease}.task-row:focus-within .task-kebab-btn,.task-row:hover .task-kebab-btn{opacity:1}.task-kebab-btn:hover{background:var(--bg-hover);color:var(--text-primary)}.task-recurrence{font-size:.85rem;color:var(--text-secondary)}.task-due{white-space:nowrap}.bulk-actions-bar{display:flex;align-items:center;gap:.5rem;padding:.75rem 1rem;background:var(--accent-blue);color:var(--text-on-accent);border:var(--border-width) solid var(--border-color);box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color);margin-bottom:1rem;color:var(--text-primary)}.bulk-actions-bar.hidden{display:none}.bulk-count{font-weight:700;margin-right:1rem;font-family:var(--font-heading)}.bulk-actions-bar .btn{background:var(--bg-card);border:var(--border-width-sm) solid var(--border-color);color:var(--text-primary)}.bulk-actions-bar .btn:hover{background:var(--bg-secondary)}.bulk-select-all{margin-left:auto}.email-checkbox-cell{padding:.75rem .5rem;display:flex;align-items:center}.email-item-with-checkbox{display:flex;align-items:flex-start}.email-item-with-checkbox .email-content{flex:1}.schedule-task-btn{display:flex;align-items:center;gap:.5rem}.time-block-form{display:flex;flex-direction:column;gap:1rem}.time-block-quick-options{display:grid;grid-template-columns:repeat(3,1fr);gap:.5rem}.time-block-quick-btn{padding:.5rem;background:var(--bg-secondary);border:var(--border-width-sm) solid var(--border-color);color:var(--text-primary);cursor:pointer;font-size:.875rem;font-weight:600;transition:background-color .1s}.time-block-quick-btn:hover{background:var(--bg-tertiary)}.time-block-quick-btn.selected{background:var(--accent-blue);color:var(--text-on-accent);box-shadow:inset 0 0 0 2px var(--border-color)}.duration-presets{display:flex;gap:.5rem;flex-wrap:wrap}.duration-preset{padding:.35rem .75rem;background:var(--bg-secondary);border:var(--border-width-sm) solid var(--border-color);color:var(--text-primary);cursor:pointer;font-size:.75rem;font-weight:600;transition:background-color .1s}.duration-preset:hover{background:var(--bg-tertiary)}.duration-preset.selected{background:var(--accent-blue);color:var(--text-on-accent)}.conflict-warning{padding:.75rem;background:var(--accent-red);border:var(--border-width) solid var(--border-color);color:var(--text-on-accent);font-size:.875rem;font-weight:600;margin-top:.5rem}.app-body{display:flex;flex:1;min-height:0;overflow:hidden}.app-body .main-content{flex:1;min-width:0;display:flex;flex-direction:column;overflow-x:visible;overflow-y:auto}#emails-view,#events-view,#projects-view,#tasks-view{padding-bottom:2.5rem}#tasks-view{display:flex;flex-direction:column;flex:1;min-height:0}#tasks-view .bulk-actions-bar,#tasks-view .filter-bar,#tasks-view .page-header{flex-shrink:0}#events-view{display:flex;flex-direction:column;flex:1;min-height:0}#events-view .page-header{flex-shrink:0}#emails-view{display:flex;flex-direction:column;flex:1;min-height:0}#emails-view .bulk-actions-bar,#emails-view .page-header{flex-shrink:0}.saved-views-sidebar{width:200px;flex-shrink:0;background:var(--bg-card);border-right:var(--border-width) solid var(--border-color);display:flex;flex-direction:column;overflow:hidden}.sidebar-section{display:flex;flex-direction:column;flex:1;min-height:0}.sidebar-section-header{display:flex;justify-content:space-between;align-items:center;padding:.75rem 1rem;font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--text-secondary);border-bottom:2px solid var(--border-color);background:var(--bg-secondary)}.btn-icon{background:0 0;border:none;color:var(--text-muted);cursor:pointer;padding:.25rem;font-size:.875rem;line-height:1}.btn-icon:hover{color:var(--text-primary)}.pinned-views-list{flex:1;overflow-y:auto;padding:.5rem}.sidebar-empty{text-align:center;padding:1.5rem .5rem;color:var(--text-muted);font-size:.8rem}.saved-view-item{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;margin-bottom:.5rem;background:var(--bg-card);border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-sm);cursor:pointer;font-size:.85rem;font-weight:600;color:var(--text-primary);box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color);transition:transform .15s ease,box-shadow .15s ease,background-color .15s ease,color .15s ease}.saved-view-item:hover{background:var(--accent-blue);color:var(--text-on-accent)}.saved-view-item.active{background:var(--accent-blue);color:var(--text-on-accent);box-shadow:inset 0 0 0 2px var(--border-color)}.saved-view-item .view-icon{font-size:.75rem}.saved-view-item .view-name{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.saved-view-item .view-actions{opacity:0;transition:opacity .1s}.saved-view-item:hover .view-actions{opacity:1}.filter-actions{display:flex;gap:.5rem;margin-left:auto}.contact-avatar{width:40px;height:40px;min-width:40px;border-radius:50%;background-color:var(--accent-blue);color:var(--text-on-accent);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:.85rem;font-family:var(--font-heading);border:2px solid var(--border-color)}.contact-avatar-lg{width:60px;height:60px;min-width:60px;font-size:1.2rem}.contact-card .card-header{display:flex;align-items:center}.contact-nickname{display:block;font-size:.85rem;color:var(--text-secondary);font-style:italic}.contact-company{display:block;font-size:.85rem;color:var(--text-secondary)}.contact-email{font-size:.85rem;color:var(--text-secondary)}.contact-detail .detail-row{margin-bottom:.5rem;font-size:.9rem}.contact-detail .contact-info-section{margin-bottom:1rem;padding-bottom:1rem;border-bottom:1px solid var(--border-light,#e0e0e0)}.contact-detail .contact-notes{margin-bottom:1.5rem}.contact-detail .contact-notes p{margin-top:.25rem;white-space:pre-wrap;color:var(--text-secondary)}.sub-collection{margin-bottom:1.25rem}.sub-collection-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:.5rem}.sub-collection-header h4{margin:0;font-size:.95rem;font-weight:600}.sub-item{display:flex;justify-content:space-between;align-items:center;padding:.4rem 0;border-bottom:1px solid var(--border-light,#e0e0e0);font-size:.9rem}.sub-item:last-child{border-bottom:none}.sub-empty{font-size:.85rem;color:var(--text-secondary);font-style:italic;padding:.25rem 0}.edit-sub-collections{border-top:1px solid var(--border-color);padding-top:1rem;margin-bottom:.5rem}.edit-sub-section{margin-bottom:.75rem}.edit-sub-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:.25rem}.sub-item-compact{font-size:.85rem;color:var(--text-secondary);padding:.125rem 0}@media print{.btn,.context-menu,.filter-bar,.keyboard-hints,.modal-overlay,.pagination,.sidebar,.tabs,.toast{display:none!important}body{background:#fff;color:#000}.main-content{margin:0;padding:0;max-width:100%}.view{padding:0}.data-table{border:1px solid #333;box-shadow:none}.data-table td,.data-table th{border:1px solid #ccc;padding:.5rem}.data-table td,.data-table th{display:table-cell!important}.data-table tbody tr:hover{background:0 0}.task-table{border:1px solid #333;box-shadow:none}.task-list-container{height:auto!important;overflow:visible!important}.task-header-row,.task-row{grid-template-columns:1fr 100px 40px 80px 60px 80px 60px!important}.task-header-row .task-cell,.task-row .task-cell{display:block!important;border:1px solid #ccc;padding:.25rem .5rem}.task-row:hover{background:0 0}.virtual-scroller-spacer-bottom,.virtual-scroller-spacer-top{display:none!important}a{color:#000;text-decoration:underline}.view-header{page-break-after:avoid}.data-table{page-break-inside:avoid}}.weekly-review-content{max-width:900px;margin:0 auto;padding:1rem}.weekly-review-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1.5rem;padding-bottom:1rem;border-bottom:var(--border-width-sm) solid var(--border-color)}.week-info{display:flex;align-items:center;gap:1rem}.week-dates{font-family:var(--font-heading);font-size:1.25rem;font-weight:700;color:var(--text-primary)}.review-status{padding:.25rem .75rem;border-radius:var(--radius-xs);font-size:.875rem;font-weight:600;border:var(--border-width-sm) solid var(--border-color)}.review-status.completed{background:var(--accent-green);color:var(--text-on-accent)}.review-status.pending{background:var(--accent-yellow);color:var(--text-primary)}.stat-cards{display:flex;gap:1rem;margin-bottom:1rem;flex-wrap:wrap}.stat-card{flex:1;min-width:100px;max-width:150px;padding:1rem;background:var(--bg-card);border:var(--border-width) solid var(--border-color);text-align:center;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.stat-card .stat-number{display:block;font-family:var(--font-heading);font-size:2rem;font-weight:700;color:var(--accent-blue);line-height:1}.stat-card .stat-label{display:block;font-size:.75rem;font-weight:600;color:var(--text-muted);margin-top:.25rem;text-transform:uppercase;letter-spacing:.5px}.stat-card.stat-warning .stat-number{color:var(--accent-yellow)}.stat-card.stat-danger .stat-number{color:var(--accent-red)}.review-section{background:var(--bg-card);border:var(--border-width) solid var(--border-color);padding:1.25rem;margin-bottom:1.5rem;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.section-title{font-family:var(--font-heading);font-size:1.125rem;font-weight:700;color:var(--text-primary);margin-bottom:1rem;padding-bottom:.5rem;border-bottom:var(--border-width-sm) solid var(--border-color)}.review-details{margin-top:.75rem}.review-details summary{cursor:pointer;font-weight:600;color:var(--text-secondary);padding:.5rem;background:var(--bg-secondary);border:var(--border-width-sm) solid var(--border-color);user-select:none}.review-details summary:hover{background:var(--bg-tertiary)}.review-details[open] summary{margin-bottom:.5rem}.review-event-list,.review-task-list{list-style:none;padding:0;margin:0}.review-event-item,.review-task-item{display:flex;align-items:center;gap:.75rem;padding:.5rem .75rem;border-bottom:1px solid var(--border-color)}.review-event-item:last-child,.review-task-item:last-child{border-bottom:none}.review-event-item .event-title,.review-task-item .task-description{flex:1;color:var(--text-primary)}.event-time{font-size:.875rem;font-weight:600;color:var(--text-muted);min-width:80px}.project-badge{font-size:.75rem;padding:.125rem .5rem;background:var(--bg-tertiary);border:1px solid var(--border-color);color:var(--text-secondary)}.due-badge{font-size:.75rem;padding:.125rem .5rem;background:var(--bg-secondary);border:1px solid var(--border-color);color:var(--text-secondary)}.due-badge.overdue{background:var(--accent-red);color:var(--text-on-accent);border-color:var(--accent-red)}.focus-section{background:linear-gradient(135deg,var(--bg-card) 0,color-mix(in srgb,var(--accent-yellow) 15%,var(--bg-card)) 100%)}.focus-task-list{list-style:none;padding:0;margin:0 0 1rem 0}.focus-task-list.available{opacity:.8}.focus-toggle{background:0 0;border:none;font-size:1.25rem;cursor:pointer;color:var(--text-muted);padding:0;line-height:1;transition:transform .15s ease}.focus-toggle:hover{transform:scale(1.2)}.focus-toggle.focused{color:var(--accent-yellow)}.review-task-item.focused{background:color-mix(in srgb,var(--accent-yellow) 10%,var(--bg-card))}.no-focus-message{color:var(--text-muted);font-style:italic;margin-bottom:1rem}.focused-projects{display:flex;gap:.5rem;flex-wrap:wrap;margin-bottom:1rem}.project-tag{background:var(--accent-blue);color:var(--text-on-accent);padding:.25rem .75rem;font-size:.875rem;font-weight:600;border:var(--border-width-sm) solid var(--border-color)}.notes-section{background:var(--bg-card)}.review-notes-input{width:100%;padding:.75rem;font-family:var(--font-mono);font-size:.9rem;border:var(--border-width-sm) solid var(--border-color);background:var(--bg-secondary);color:var(--text-primary);resize:vertical;min-height:100px}.review-notes-input:focus{outline:0;background:var(--bg-card);box-shadow:inset 0 0 0 2px var(--accent-blue)}.review-actions{margin-top:1rem;text-align:center}.tab-badge{display:inline-block;width:8px;height:8px;background:var(--accent-red);border-radius:var(--radius-full);margin-left:.5rem;vertical-align:middle;animation:pulse-badge 2s infinite}@keyframes pulse-badge{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.6;transform:scale(.8)}}.tab-status-dot{width:8px;height:8px;border-radius:50%;display:inline-block;margin-left:.5rem;vertical-align:middle;transition:background-color .3s ease}.tab-status-dot.status-none{display:none}.tab-status-dot.status-green{background-color:var(--accent-green)}.tab-status-dot.status-yellow{background-color:var(--accent-yellow);animation:pulse-badge 2s ease-in-out infinite}.tab-status-dot.status-red{background-color:var(--accent-red);animation:pulse-badge 1.5s ease-in-out infinite}.review-grid{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem;max-width:1200px;margin:0 auto}.review-card{background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);padding:1.5rem;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.review-card .card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;padding-bottom:.75rem;border-bottom:var(--border-width-sm) solid var(--bg-secondary)}.review-card .card-title{font-family:var(--font-heading);font-size:1.1rem;font-weight:700;display:flex;align-items:center;gap:.5rem}.review-card .card-icon{font-size:1.25rem}.review-card .card-badge{font-size:.8rem;padding:.25rem .75rem;border-radius:var(--radius-md);font-weight:600}.week-timeline{grid-column:1/-1}.timeline-visual{display:flex;gap:.5rem;margin-top:1rem}.timeline-day{flex:1;text-align:center;padding:.75rem .5rem;background:var(--bg-secondary);border-radius:var(--radius-md);border:1px solid var(--border-color);position:relative}.timeline-day.today{background:var(--accent-blue);color:var(--text-on-accent);border-width:2px;font-weight:700}.timeline-day.past{opacity:.7}.timeline-day.future{background:var(--bg-card)}.timeline-day .day-name{font-size:.7rem;font-weight:600;text-transform:uppercase;color:var(--text-muted)}.timeline-day .day-number{font-size:1.1rem;font-weight:700}.day-dots{display:flex;justify-content:center;gap:3px;margin-top:.5rem;min-height:8px}.day-dot{width:8px;height:8px;border-radius:var(--radius-full)}.day-dot.task{background:var(--accent-blue)}.day-dot.event{background:var(--accent-purple)}.day-dot.completed{background:var(--accent-green)}.day-dot.overdue{background:var(--accent-red)}.day-dot.vacation-off{background:var(--text-muted);opacity:.5;width:12px;height:4px;border-radius:2px}.day-events{display:flex;flex-direction:column;gap:2px;margin-top:.5rem;text-align:left}.day-event{font-size:.6rem;line-height:1.3;padding:1px 4px;border-left:2px solid var(--accent-purple);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--text-secondary)}.day-event .event-time{font-size:.55rem;font-weight:600;color:var(--accent-purple);margin-right:2px;min-width:auto}.day-event-more{font-size:.55rem;color:var(--text-muted);padding:1px 4px;font-style:italic}.week-timeline-events{grid-column:1/-1}.timeline-events-day{margin-bottom:.75rem}.timeline-events-day:last-child{margin-bottom:0}.timeline-events-day-label{font-family:var(--font-heading);font-size:.8rem;font-weight:700;color:var(--text-secondary);margin-bottom:.25rem;text-transform:uppercase}.vacation-toggles-section{margin-top:1rem;padding-top:1rem;border-top:2px solid var(--border-color)}.vacation-toggles-section h3{margin:0 0 .75rem 0;font-size:.9rem;font-family:var(--font-heading);font-weight:700}.vacation-toggles{display:flex;gap:.5rem}.vacation-toggle{width:2.5rem;height:2.5rem;border-radius:var(--radius-sm);border:var(--border-width) solid var(--border-color);background:var(--bg-secondary);font-family:var(--font-heading);font-weight:700;font-size:.8rem;cursor:pointer;transition:background var(--transition-fast),color var(--transition-fast),border-color var(--transition-fast);display:flex;align-items:center;justify-content:center}.vacation-toggle:hover{background:var(--bg-hover)}.vacation-toggle.active{background:var(--accent-purple);color:var(--text-on-accent);border-color:var(--accent-purple)}.timeline-day.vacation{opacity:.5}.timeline-day.vacation .day-name{text-decoration:line-through}.vacation-day-banner{text-align:center;padding:.5rem 1rem;background:color-mix(in srgb,var(--accent-purple) 15%,var(--bg-secondary));border:var(--border-width-sm) solid var(--accent-purple);border-radius:var(--radius-sm);font-family:var(--font-heading);font-weight:700;font-size:.85rem;color:var(--accent-purple);margin-bottom:.75rem}.stats-row{display:flex;gap:1rem;margin-bottom:1rem}.stat-box{flex:1;text-align:center;padding:1rem;background:var(--bg-secondary);border-radius:var(--radius-md)}.stat-box .stat-number{font-family:var(--font-heading);font-size:2rem;font-weight:800;line-height:1}.stat-box .stat-number.green{color:var(--accent-green)}.stat-box .stat-number.red{color:var(--accent-red)}.stat-box .stat-number.blue{color:var(--accent-blue)}.stat-box .stat-number.purple{color:var(--accent-purple)}.stat-box .stat-label{font-size:.75rem;text-transform:uppercase;color:var(--text-muted);font-weight:600;margin-top:.25rem}.task-list{list-style:none;max-height:200px;overflow-y:auto}.task-item{display:flex;align-items:center;gap:.75rem;padding:.75rem;margin-bottom:.5rem;background:var(--bg-secondary);border-radius:var(--radius-md);cursor:pointer;transition:background-color var(--transition-normal)}.task-item:hover{background:var(--accent-blue);color:var(--text-on-accent)}.task-item.completed{opacity:.6;text-decoration:line-through}.task-checkbox{width:20px;height:20px;border:2px solid var(--border-color);border-radius:var(--radius-xs);display:flex;align-items:center;justify-content:center;flex-shrink:0}.task-checkbox.checked{background:var(--accent-green);color:var(--text-on-accent)}.task-text{flex:1;font-size:.9rem}.task-project{font-size:.75rem;padding:.2rem .5rem;background:var(--bg-card);border-radius:var(--radius-xs);color:var(--text-muted)}.task-due{font-size:.75rem;color:var(--text-muted)}.task-due.overdue{color:var(--accent-red);font-weight:600}.focus-section.full-width{grid-column:1/-1}.focus-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:1rem;margin-top:1rem}.focus-slot{padding:1.25rem;background:var(--bg-secondary);border:2px dashed var(--border-color);border-radius:var(--radius-md);min-height:100px;display:flex;flex-direction:column;gap:.5rem}.focus-slot.filled{border-style:solid;background:var(--bg-card)}.focus-slot.primary{border-color:var(--accent-yellow);background:linear-gradient(135deg,var(--bg-card) 0,color-mix(in srgb,var(--accent-yellow) 10%,var(--bg-card)) 100%)}.focus-label{font-size:.7rem;text-transform:uppercase;color:var(--text-muted);font-weight:600}.focus-task{font-weight:600;font-size:.95rem}.focus-meta{font-size:.8rem;color:var(--text-secondary)}.focus-empty{color:var(--text-muted);font-style:italic;font-size:.9rem}.projects-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:.75rem;margin-top:.5rem}.project-health{padding:.75rem;background:var(--bg-secondary);border-radius:var(--radius-md);border-left:4px solid var(--accent-blue)}.project-health.warning{border-left-color:var(--accent-yellow)}.project-health.danger{border-left-color:var(--accent-red)}.project-name{font-weight:600;font-size:.85rem;margin-bottom:.25rem}.project-stats{font-size:.75rem;color:var(--text-muted)}.reflection-section{grid-column:1/-1}.reflection-prompts{display:grid;grid-template-columns:1fr 1fr;gap:1rem;margin-top:1rem}.reflection-prompt{padding:1rem;background:var(--bg-secondary);border-radius:var(--radius-md)}.prompt-label{font-size:.8rem;font-weight:600;color:var(--text-secondary);margin-bottom:.5rem}.prompt-input{width:100%;padding:.75rem;border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-md);font-size:.9rem;font-family:inherit;resize:none;background:var(--bg-card)}.prompt-input:focus{outline:0;border-color:var(--accent-blue)}.review-actions-grid{grid-column:1/-1;display:flex;justify-content:flex-end;gap:1rem;padding-top:1rem}.event-item{display:flex;align-items:center;gap:.75rem;padding:.75rem;margin-bottom:.5rem;background:var(--bg-secondary);border-radius:var(--radius-md);border-left:3px solid var(--accent-purple)}.event-item .event-time{font-size:.8rem;font-weight:600;color:var(--accent-purple);min-width:100px}.event-item .event-title{flex:1;font-size:.9rem}.accomplishment-highlight{background:linear-gradient(135deg,color-mix(in srgb,var(--accent-green) 10%,var(--bg-card)) 0,color-mix(in srgb,var(--accent-green) 5%,var(--bg-card)) 100%);border:2px solid var(--accent-green);padding:1rem;border-radius:var(--radius-md);margin-bottom:1rem;display:flex;align-items:center;gap:1rem}.accomplishment-icon{font-size:2rem}.accomplishment-text{font-size:1rem}.accomplishment-text strong{color:var(--accent-green)}.task-list::-webkit-scrollbar{width:6px}.task-list::-webkit-scrollbar-track{background:var(--bg-secondary);border-radius:var(--radius-xs)}.task-list::-webkit-scrollbar-thumb{background:var(--border-color);border-radius:var(--radius-xs)}@media (max-width:900px){.review-grid{grid-template-columns:1fr}.focus-section.full-width,.reflection-section,.week-timeline,.week-timeline-events{grid-column:1}.focus-grid{grid-template-columns:1fr}.reflection-prompts{grid-template-columns:1fr}.projects-grid{grid-template-columns:1fr 1fr}}@media (max-width:600px){.stat-cards{flex-direction:column}.stat-card{max-width:none}.week-info{flex-direction:column;align-items:flex-start;gap:.5rem}.projects-grid{grid-template-columns:1fr}}.focus-slot{transition:background-color .2s ease-out,border-color .2s ease-out}.focus-slot.filled{animation:focusSlotFill .3s ease-out}@keyframes focusSlotFill{0%{transform:scale(.95);opacity:.7}100%{transform:scale(1);opacity:1}}.focus-slot:focus,.focus-slot:focus-within{outline:2px solid var(--accent-blue);outline-offset:2px}.focus-slot[tabindex]:focus{outline:2px solid var(--accent-blue);outline-offset:2px}.focus-section .btn{transition:transform .15s ease-out,opacity .15s ease-out}.focus-section .btn:active{transform:scale(.97)}@media print{.card-badge,.focus-section .btn,.focus-slot .btn,.header,.review-actions-grid,.sidebar,.tab-badge,.tab-nav,.tab-status-dot{display:none!important}.main-content,.weekly-review-content{margin:0;padding:0;width:100%;max-width:100%}.event-item,.focus-slot,.project-health,.reflection-prompt,.review-card,.weekly-review-content,body{background:#fff!important;color:#000!important;-webkit-print-color-adjust:exact;print-color-adjust:exact}.review-card{border:1px solid #ccc!important;box-shadow:none!important;page-break-inside:avoid;margin-bottom:1rem}.focus-slot{border:1px solid #999!important}.focus-slot.primary{border:2px solid #f7d154!important;background:#fffbea!important}.review-grid{display:block!important}.review-card{display:inline-block;vertical-align:top;width:48%;margin-right:2%}.focus-section.full-width,.reflection-section,.week-timeline,.week-timeline-events{width:100%!important;display:block!important}.weekly-review-header{border-bottom:2px solid #333;padding-bottom:1rem;margin-bottom:1.5rem}.week-dates{font-size:1.5rem;font-weight:700}.day-dot{-webkit-print-color-adjust:exact;print-color-adjust:exact}.day-dot.completed{background:#5cb85c!important}.day-dot.event{background:#9b59b6!important}.day-dot.overdue{background:#d9534f!important}.project-health{border-left:4px solid #337ab7!important}.project-health.warning{border-left-color:#f7d154!important}.project-health.danger{border-left-color:#d9534f!important}.focus-grid{display:flex!important;gap:1rem}.focus-slot{flex:1}.reflection-prompts{display:flex!important;gap:1rem}.reflection-prompt{flex:1}.prompt-input{border:1px solid #ccc!important;min-height:80px}.focus-section{page-break-before:auto}.reflection-section{page-break-before:always}}.monthly-review-nav{display:flex;align-items:center;gap:.5rem}.monthly-review-month-display{font-family:var(--font-heading);font-size:1.25rem;font-weight:700;color:var(--text-primary);margin-left:.5rem}.monthly-review-content{max-width:900px;margin:0 auto;padding:1rem}.month-heatmap{margin-bottom:1.5rem;border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-md);padding:1rem;background:var(--bg-secondary)}.month-heatmap-header{display:grid;grid-template-columns:repeat(7,1fr);text-align:center;margin-bottom:.5rem}.month-heatmap-day-header{font-family:var(--font-heading);font-size:.75rem;font-weight:600;color:var(--text-secondary);text-transform:uppercase}.month-heatmap-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:3px}.month-heatmap-cell{aspect-ratio:1;display:flex;flex-direction:column;align-items:center;justify-content:center;border-radius:var(--radius-xs);cursor:pointer;transition:transform .1s ease;border:var(--border-width-sm) solid transparent;position:relative;min-height:40px}.month-heatmap-cell:not(.empty):hover{transform:scale(1.1);border-color:var(--border-color);z-index:1}.month-heatmap-cell.empty{cursor:default;background:0 0}.month-heatmap-cell.intensity-0{background:var(--bg-primary)}.month-heatmap-cell.intensity-1{background:color-mix(in srgb,var(--accent-green) 20%,var(--bg-primary))}.month-heatmap-cell.intensity-2{background:color-mix(in srgb,var(--accent-green) 40%,var(--bg-primary))}.month-heatmap-cell.intensity-3{background:color-mix(in srgb,var(--accent-green) 60%,var(--bg-primary))}.month-heatmap-cell.vacation{background:var(--bg-tertiary);opacity:.6}.month-heatmap-cell.today{border-color:var(--accent-primary);border-width:2px}.month-heatmap-cell.past.intensity-0{background:var(--bg-tertiary)}.month-heatmap-day-number{font-family:var(--font-heading);font-size:.8rem;font-weight:600;color:var(--text-primary)}.month-heatmap-dots{display:flex;gap:2px;margin-top:2px}.month-dot{font-size:.6rem;font-weight:700;border-radius:var(--radius-xs);padding:0 3px;line-height:1.3}.month-dot.completed{color:var(--accent-green)}.month-dot.event{color:var(--accent-purple)}.monthly-review-cards{display:grid;grid-template-columns:1fr 1fr;gap:1rem}.review-card.month-goals-card,.review-card.month-stats-card{grid-column:span 1}.review-card.month-patterns-card,.review-card.month-pulse-card{grid-column:span 1}.review-card.month-reflection-card{grid-column:1/-1}.review-card-title{font-family:var(--font-heading);font-size:1rem;font-weight:700;margin-bottom:.75rem;color:var(--text-primary)}.month-stats-grid{display:grid;grid-template-columns:1fr 1fr;gap:.5rem}.month-stat-item{display:flex;flex-direction:column;align-items:center;padding:.5rem;border-radius:var(--radius-xs);background:var(--bg-primary);border:var(--border-width-sm) solid var(--border-color)}.month-stat-value{font-family:var(--font-heading);font-size:1.5rem;font-weight:700;color:var(--text-primary)}.month-stat-label{font-size:.75rem;color:var(--text-secondary);text-transform:uppercase;font-weight:600}.month-stats-highlights{display:flex;gap:1rem;margin-top:.5rem;justify-content:center}.stat-highlight{font-size:.8rem;color:var(--text-secondary)}.month-pulse-list{display:flex;flex-direction:column;gap:.5rem}.month-pulse-item{display:flex;align-items:center;gap:.5rem;padding:.5rem;border-radius:var(--radius-xs);background:var(--bg-primary);border:var(--border-width-sm) solid var(--border-color)}.pulse-name{font-weight:600;flex:1;font-size:.875rem}.pulse-stats{font-size:.75rem;color:var(--text-secondary)}.pulse-arrow{font-size:1rem;font-weight:700}.month-pulse-item.positive .pulse-arrow{color:var(--accent-green)}.month-pulse-item.negative .pulse-arrow{color:var(--accent-red)}.month-pulse-item.neutral .pulse-arrow{color:var(--text-secondary)}.month-goals-list{display:flex;flex-direction:column;gap:.5rem}.month-goal-item{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;border-radius:var(--radius-xs);background:var(--bg-primary);border:var(--border-width-sm) solid var(--border-color)}.month-goal-item.empty{cursor:pointer;border-style:dashed;justify-content:center}.month-goal-item.empty:hover{border-color:var(--accent-primary);background:var(--bg-secondary)}.month-goal-item.done{opacity:.7}.month-goal-item.done .month-goal-text{text-decoration:line-through}.month-goal-item.abandoned{opacity:.5}.month-goal-item.abandoned .month-goal-text{text-decoration:line-through}.month-goal-status-btn{background:0 0;border:none;cursor:pointer;font-size:1rem;padding:0;color:var(--text-secondary);width:24px;text-align:center}.month-goal-item.done .month-goal-status-btn{color:var(--accent-green)}.month-goal-item.abandoned .month-goal-status-btn{color:var(--accent-red)}.month-goal-text{flex:1;font-size:.875rem}.month-goal-delete-btn{background:0 0;border:none;cursor:pointer;color:var(--text-tertiary);padding:0 4px;font-size:.75rem;opacity:0;transition:opacity .15s}.month-goal-item:hover .month-goal-delete-btn{opacity:1}.month-goal-placeholder{color:var(--text-tertiary);font-size:.875rem}.month-reflection-fields{display:flex;flex-direction:column;gap:.5rem}.month-reflection-label{font-size:.875rem;font-weight:600;color:var(--text-secondary)}.month-reflection-textarea{width:100%;padding:.5rem;border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-xs);background:var(--bg-primary);color:var(--text-primary);font-family:var(--font-body);font-size:.875rem;resize:vertical}.month-reflection-textarea:focus{outline:2px solid var(--accent-primary);outline-offset:-1px}.month-patterns-list{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:.5rem}.month-pattern-item{font-size:.875rem;color:var(--text-secondary);padding:.5rem;background:var(--bg-primary);border-radius:var(--radius-xs);border:var(--border-width-sm) solid var(--border-color)}@media (max-width:640px){.monthly-review-cards{grid-template-columns:1fr}.review-card.month-goals-card,.review-card.month-patterns-card,.review-card.month-pulse-card,.review-card.month-stats-card{grid-column:span 1}.month-heatmap-cell{min-height:32px}.month-heatmap-day-number{font-size:.7rem}.month-heatmap-dots{display:none}}.import-wizard{display:flex;flex-direction:column;gap:1.5rem}.import-step{padding:1rem;background:var(--bg-secondary);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md)}.import-step h3{margin:0 0 1rem 0;font-size:var(--font-size-md);font-weight:600}.plugin-selector{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:.75rem}.plugin-option{display:flex;flex-direction:column;align-items:flex-start;padding:.75rem 1rem;background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-sm);cursor:pointer;text-align:left;transition:border-color var(--transition-fast),background var(--transition-fast)}.plugin-option:hover{border-color:var(--accent-primary);background:var(--bg-hover)}.plugin-option.selected{border-color:var(--accent-primary);background:color-mix(in srgb,var(--accent-primary) 10%,var(--bg-card));box-shadow:0 0 0 2px color-mix(in srgb,var(--accent-primary) 30%,transparent)}.plugin-option .plugin-name{font-weight:600;margin-bottom:.25rem}.plugin-option .plugin-meta{display:flex;gap:.5rem;font-size:var(--font-size-sm);color:var(--text-muted);margin-bottom:.25rem}.plugin-option .plugin-extensions{color:var(--accent-cyan)}.plugin-option .plugin-types{color:var(--text-secondary)}.plugin-option .plugin-description{font-size:var(--font-size-sm);color:var(--text-secondary);line-height:1.4}.file-selector{display:flex;align-items:center;gap:1rem}.selected-file-name{color:var(--text-secondary);font-family:monospace;font-size:var(--font-size-sm)}.import-preview-container{min-height:100px}.import-preview-table-wrapper{max-height:300px;overflow:auto;border:1px solid var(--border-color);border-radius:var(--radius-sm)}.import-preview-table{font-size:var(--font-size-sm);margin:0}.import-preview-table th{position:sticky;top:0;background:var(--bg-secondary);z-index:1}.import-preview-table td{max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.import-summary{margin:0 0 .75rem 0;color:var(--text-primary)}.import-more{margin:.5rem 0 0 0;color:var(--text-muted);font-style:italic;font-size:var(--font-size-sm)}.import-empty,.import-error{padding:2rem;text-align:center;color:var(--text-muted)}.import-error{color:var(--accent-red)}.import-warnings{margin-top:1rem;padding:.75rem;background:color-mix(in srgb,var(--accent-yellow) 10%,var(--bg-card));border:1px solid var(--accent-yellow);border-radius:var(--radius-sm);font-size:var(--font-size-sm)}.import-warnings ul{margin:.5rem 0 0 1.25rem;padding:0}.import-warnings li{margin-bottom:.25rem}.import-external-types{display:flex;gap:1rem;margin-bottom:1.5rem}.import-type-card{flex:1;display:flex;flex-direction:column;align-items:center;gap:.5rem;padding:1.5rem 1rem;background:var(--bg-card);border:2px solid var(--border-color);border-radius:var(--radius-md);cursor:pointer;transition:border-color .15s,background .15s}.import-type-card:hover{border-color:var(--accent-primary);background:var(--bg-secondary)}.import-type-icon{font-size:2rem}.import-type-label{font-weight:600;color:var(--text-primary)}.import-type-desc{font-size:var(--font-size-sm);color:var(--text-muted)}.plugin-list{display:flex;flex-direction:column;gap:.75rem}.plugin-item{display:flex;justify-content:space-between;align-items:center;padding:1rem;background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md)}.plugin-item .plugin-info{flex:1}.plugin-item .plugin-name{font-weight:600}.plugin-item .plugin-version{color:var(--text-muted);font-size:var(--font-size-sm);margin-left:.5rem}.plugin-item .plugin-description{margin:.25rem 0;color:var(--text-secondary);font-size:var(--font-size-sm)}.plugin-item .plugin-extensions{font-size:var(--font-size-xs);color:var(--text-muted)}.plugin-item .plugin-actions{margin-left:1rem}.toggle-switch{position:relative;display:inline-block;width:44px;height:24px}.toggle-switch input{opacity:0;width:0;height:0}.toggle-slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background-color:var(--bg-tertiary);border:2px solid var(--border-color);border-radius:var(--radius-xl);transition:background-color var(--transition-fast),border-color var(--transition-fast)}.toggle-slider:before{position:absolute;content:"";height:16px;width:16px;left:2px;bottom:2px;background-color:var(--text-muted);border-radius:var(--radius-full);transition:transform var(--transition-fast),background-color var(--transition-fast)}.toggle-switch input:checked+.toggle-slider{background-color:var(--accent-primary);border-color:var(--accent-primary)}.toggle-switch input:checked+.toggle-slider:before{transform:translateX(20px);background-color:var(--bg-card)}.toggle-switch input:focus+.toggle-slider{box-shadow:0 0 0 2px color-mix(in srgb,var(--accent-primary) 30%,transparent)}.milestones-section{margin-bottom:1.5rem}.milestones-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;padding-bottom:.5rem;border-bottom:2px solid var(--border-color)}.milestones-header h3{margin:0;font-size:1rem;font-family:var(--font-heading);font-weight:700}.milestone-card{background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);padding:1rem;margin-bottom:.75rem;transition:background-color .1s;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.milestone-card:hover{background:var(--bg-secondary)}.milestone-card-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:.5rem}.milestone-card-header h4{margin:0;font-size:.95rem;font-family:var(--font-heading);font-weight:700}.milestone-card-header .milestone-status{font-size:.7rem;font-weight:700;text-transform:uppercase;padding:.15rem .4rem;border-radius:var(--radius-sm);background:var(--bg-secondary);color:var(--text-muted)}.milestone-card-header .milestone-status.completed{background:color-mix(in srgb,var(--accent-green) 15%,var(--bg-secondary));color:var(--accent-green)}.milestone-meta{display:flex;gap:1rem;font-size:.8rem;color:var(--text-muted);margin-bottom:.5rem}.milestone-progress{height:6px;background:var(--bg-secondary);border-radius:var(--radius-full);overflow:hidden;border:var(--border-width-sm) solid var(--border-color)}.milestone-progress-fill{height:100%;background:var(--accent-green);border-radius:var(--radius-full);transition:width var(--transition-fast)}.milestone-actions{display:flex;gap:.5rem;margin-top:.5rem}.milestone-actions button{font-size:.75rem;padding:.2rem .5rem;background:var(--bg-secondary);border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-sm);cursor:pointer;color:var(--text-secondary);transition:background var(--transition-fast)}.milestone-actions button:hover{background:var(--bg-hover)}.milestone-actions button.danger:hover{background:color-mix(in srgb,var(--accent-red) 15%,var(--bg-secondary));color:var(--accent-red)}button.milestone-reorder-btn.btn{font-size:.65rem;padding:.15rem .35rem;line-height:1;min-width:1.5rem;text-align:center}.milestones-completed-section{margin-top:.75rem}.milestones-completed-toggle{font-size:.8rem;color:var(--text-secondary);padding:.25rem 0}.milestone-card-summary{padding:.5rem .75rem;opacity:.7}.milestone-card-summary .milestone-info{display:flex;align-items:center;gap:.5rem}.milestone-complete-badge{font-size:.7rem;font-weight:700;padding:.1rem .4rem;border-radius:var(--radius-sm);background:color-mix(in srgb,var(--accent-green) 15%,var(--bg-secondary));color:var(--accent-green)}.mobile-tab-bar{display:none;position:fixed;bottom:0;left:0;right:0;z-index:1100;background:var(--bg-card);border-top:var(--border-width) solid var(--border-color);padding-bottom:env(safe-area-inset-bottom,0);height:calc(52px + env(safe-area-inset-bottom,0px))}.mobile-tab{flex:1;display:flex;align-items:center;justify-content:center;height:52px;background:0 0;border:none;color:var(--text-muted);font-size:.7rem;font-weight:700;font-family:var(--font-sans);text-transform:uppercase;letter-spacing:.05em;cursor:pointer;-webkit-tap-highlight-color:transparent;transition:color .15s ease}.mobile-tab.active{color:var(--accent-blue)}.mobile-tab:active{background:var(--bg-secondary)}.mobile-tab-create{font-size:1.4rem;font-weight:400;color:var(--accent-green);letter-spacing:0;text-transform:none}.mobile-more-popover{display:none;position:fixed;bottom:calc(52px + env(safe-area-inset-bottom,0px));right:0;background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);padding:.25rem 0;z-index:1101;min-width:160px;box-shadow:0 -2px 8px rgba(0,0,0,.1)}.mobile-more-popover.visible{display:block}.mobile-more-popover button{display:block;width:100%;padding:.75rem 1rem;background:0 0;border:none;text-align:left;font-size:var(--font-size-sm);font-weight:600;color:var(--text-primary);cursor:pointer}.mobile-more-popover button:active{background:var(--bg-secondary)}.action-sheet{position:fixed;inset:0;z-index:10001;display:flex;flex-direction:column;justify-content:flex-end}.action-sheet.hidden{display:none}.action-sheet-backdrop{position:absolute;inset:0;background:rgba(0,0,0,.4)}.action-sheet-container{position:relative;background:var(--bg-card);border-top:var(--border-width) solid var(--border-color);border-radius:var(--radius-lg) var(--radius-lg) 0 0;padding:.5rem 1rem calc(.5rem + env(safe-area-inset-bottom,0px));max-height:60vh;overflow-y:auto;animation:sheetSlideUp .25s ease-out}.action-sheet-handle{width:36px;height:4px;border-radius:2px;background:var(--text-muted);margin:0 auto .75rem;opacity:.4}.action-sheet-content button{display:flex;align-items:center;gap:.75rem;width:100%;padding:.875rem .5rem;background:0 0;border:none;border-bottom:1px solid var(--bg-secondary);font-size:var(--font-size-base);font-weight:600;color:var(--text-primary);text-align:left;cursor:pointer}.action-sheet-content button:last-child{border-bottom:none}.action-sheet-content button:active{background:var(--bg-secondary)}.action-sheet-content button.danger{color:var(--accent-red)}.action-sheet-cancel{display:block;width:100%;padding:.875rem;margin-top:.5rem;background:var(--bg-secondary);border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-md);font-size:var(--font-size-base);font-weight:700;color:var(--text-primary);text-align:center;cursor:pointer}.action-sheet-cancel:active{background:var(--bg-tertiary)}.modal-drag-handle{display:none;width:36px;height:4px;border-radius:2px;background:var(--text-muted);margin:.5rem auto 0;opacity:.4}.mobile-sort-bar{display:none;gap:.5rem;padding:.5rem 0;align-items:center}.mobile-sort-bar select{flex:1;font-size:var(--font-size-sm)}.mobile-filter-toggle{display:none}.swipe-actions-container{position:relative;overflow:hidden}.swipe-actions-bg{position:absolute;top:0;bottom:0;display:flex;align-items:center;padding:0 1rem;font-weight:700;font-size:var(--font-size-sm);color:var(--text-on-accent)}.swipe-actions-bg.swipe-left{right:0;background:var(--accent-green)}.swipe-actions-bg.swipe-right{left:0;background:var(--accent-red)}.swipe-content{position:relative;background:var(--bg-card);transition:transform .15s ease}.pull-to-refresh-indicator{display:none;text-align:center;padding:.75rem;font-size:var(--font-size-sm);color:var(--text-muted);font-weight:600}.pull-to-refresh-indicator.visible{display:block}.event-date-group-header{display:none}.day-plan-sidebar-toggle{display:none}@keyframes sheetSlideUp{from{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes sheetSlideDown{from{transform:translateY(0)}to{transform:translateY(100%)}}@keyframes dialFadeIn{from{opacity:0}to{opacity:1}}@media (max-width:768px){body{padding-top:env(safe-area-inset-top,0);padding-bottom:calc(52px + env(safe-area-inset-bottom,0px))}.mobile-tab-bar{display:flex}.tab-navigation{display:none!important}.app-header{display:none}.pill-nav{overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;padding:var(--space-1) var(--space-3)}.tab-group>.subview>.page-header{position:static}.pill-nav::-webkit-scrollbar{display:none}.main-content{padding:.75rem}.page-header{flex-wrap:wrap;gap:.5rem}.page-header .btn-primary{display:none}.page-title{display:none}.modal-overlay{align-items:flex-end}.modal-container{width:100%!important;max-width:100%!important;max-height:90vh;border-radius:var(--radius-lg) var(--radius-lg) 0 0;margin:0;border-bottom:none;padding-bottom:env(safe-area-inset-bottom,0)}.modal-container.modal-large{max-width:100%!important;width:100%!important;max-height:95vh;border-radius:var(--radius-lg) var(--radius-lg) 0 0}.modal-drag-handle{display:block}.modal-header{padding:.75rem 1rem}.modal-content{padding:1rem}@keyframes modalSlideIn{from{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes modalSlideOut{from{transform:translateY(0)}to{transform:translateY(100%)}}.toast,.toast-undo{bottom:calc(env(safe-area-inset-bottom,0px) + 4.5rem)!important;left:1rem!important;right:1rem!important;max-width:none!important}.task-table{border:none;box-shadow:none;background:0 0}.task-header-row{display:none!important}.task-row{display:flex!important;flex-direction:column;gap:.25rem;padding:.75rem 1rem;margin-bottom:.5rem;background:var(--bg-card);border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-md);border-left:4px solid var(--text-muted)}.task-row.task-pending{border-left-color:var(--text-muted)}.task-row .task-cell.priority-h~.task-cell:first-child,.task-row:has(.priority-h){border-left-color:var(--accent-red)}.task-row:has(.priority-m){border-left-color:var(--accent-yellow)}.task-row:has(.priority-l){border-left-color:var(--text-muted)}.task-row .task-cell{display:flex!important;overflow:visible;padding:0}.task-cell.task-description{font-weight:600;font-size:var(--font-size-base)}.task-cell.task-due,.task-cell.task-project{font-size:var(--font-size-sm);color:var(--text-secondary)}.task-row .task-cell.task-project::before{content:none}.task-cell.task-progress,.task-cell.task-recurrence,.task-row .task-cell:nth-child(3){display:none!important}.task-cell.task-project{order:2}.task-cell.task-due{order:3}.task-cell.task-description{order:1}.task-cell.task-actions-cell{order:4;justify-content:flex-end}.task-cell.task-progress:has(.progress-bar-container){display:flex!important;order:5}.task-actions-cell .bulk-checkbox{display:none}.task-kebab-btn{opacity:1}.mobile-sort-bar{display:flex}.mobile-filter-toggle{display:inline-flex;align-items:center;gap:.25rem;padding:.5rem .75rem;background:var(--bg-card);border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-sm);font-size:var(--font-size-sm);font-weight:600;cursor:pointer}.filter-bar{display:none!important}.filter-bar.mobile-visible{display:flex!important;flex-direction:column;position:fixed;bottom:0;left:0;right:0;background:var(--bg-card);border-top:var(--border-width) solid var(--border-color);border-radius:var(--radius-lg) var(--radius-lg) 0 0;padding:1rem;padding-bottom:calc(1rem + env(safe-area-inset-bottom,0px));z-index:1050;box-shadow:0 -4px 12px rgba(0,0,0,.1)}.event-header-row{display:none!important}.event-row-virtual{display:flex!important;flex-direction:column;gap:.125rem;padding:.75rem 1rem;border-bottom:1px solid var(--bg-secondary)}.event-cell-date{font-weight:700;font-size:var(--font-size-sm);color:var(--text-secondary)}.event-cell-time{font-size:var(--font-size-sm);color:var(--text-muted)}.event-cell-title{font-weight:600;font-size:var(--font-size-base)}.event-cell-location{font-size:var(--font-size-sm);color:var(--text-secondary)}.event-date-group-header{display:flex;position:sticky;top:0;z-index:5;padding:.5rem 1rem;background:var(--bg-secondary);font-weight:700;font-size:var(--font-size-sm);text-transform:uppercase;letter-spacing:.05em;color:var(--text-primary);border-bottom:var(--border-width-sm) solid var(--border-color)}.email-item{padding:.625rem .75rem}.email-from{font-size:var(--font-size-sm)}.email-subject{font-size:var(--font-size-base)}.email-preview{display:none}.email-date{font-size:var(--font-size-xs)}.email-item .bulk-checkbox{display:none}.day-plan-content{flex-direction:column}.day-plan-sidebar{width:100%;max-height:none;border-top:var(--border-width-sm) solid var(--border-color);order:2}.day-plan-sidebar.collapsed .sidebar-task-list{display:none}.day-plan-sidebar-toggle{display:flex;align-items:center;justify-content:space-between;width:100%;padding:.625rem .75rem;background:var(--bg-secondary);border:none;border-bottom:1px solid var(--border-color);font-size:var(--font-size-sm);font-weight:700;cursor:pointer;color:var(--text-primary)}.day-plan-main{order:1}.day-plan-nav{flex-wrap:wrap;gap:.25rem}.weekly-review-content{padding:0}.monthly-review-content{padding:0}.month-reflection-textarea,.prompt-input{resize:none;overflow:hidden}.monthly-review-nav{flex-wrap:wrap;gap:.25rem}.monthly-review-month-display{font-size:1rem}.day-summary-sheet{padding:.5rem 0}.day-summary-date{font-size:1rem;font-weight:700;margin-bottom:.75rem;color:var(--text-primary)}.day-summary-stats{display:flex;gap:.5rem;margin-bottom:1rem}.day-summary-chip{padding:.25rem .75rem;background:var(--bg-secondary);border-radius:var(--radius-sm);font-size:var(--font-size-sm);font-weight:600;color:var(--text-secondary)}.day-summary-list{list-style:none;padding:0;margin:0 0 1rem 0}.day-summary-item{padding:.5rem 0;border-bottom:1px solid var(--bg-secondary);font-size:var(--font-size-sm);color:var(--text-primary)}.day-summary-time{font-weight:600;color:var(--text-secondary);margin-right:.5rem}.day-summary-more{color:var(--text-muted);font-style:italic}.day-summary-empty{color:var(--text-muted);font-size:var(--font-size-sm);margin:.5rem 0 1rem}.day-summary-go-btn{width:100%;margin-top:.5rem}.bulk-actions-bar{position:fixed;bottom:0;left:0;right:0;z-index:1050;border-radius:var(--radius-lg) var(--radius-lg) 0 0;padding-bottom:calc(.75rem + env(safe-area-inset-bottom,0px));box-shadow:0 -4px 12px rgba(0,0,0,.15)}.pagination-controls{padding:.5rem}.pagination-controls .btn{padding:.5rem .75rem;font-size:var(--font-size-sm)}}@media (hover:none){.task-row:hover{background-color:transparent}.task-row-clickable:hover{background:0 0}.event-row-virtual:hover{background-color:transparent}.email-item:hover{background-color:transparent}.card:hover{background-color:var(--bg-card);transform:none;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.btn:hover{background:var(--bg-card)}.btn-primary:hover{background-color:var(--accent-blue)}.btn-danger:hover{background-color:var(--accent-red)}.dashboard-item:hover{background:var(--bg-card);transform:none;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.kanban-card:hover{background:var(--bg-card);transform:none;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.saved-view-item:hover{background:var(--bg-card);color:var(--text-primary);transform:none;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.context-menu-item:hover{background:0 0;color:var(--text-primary)}.modal-close:hover{background:var(--bg-card);color:var(--text-primary)}.month-heatmap-cell:hover{background:0 0;transform:none}}.view-toggle{display:flex;gap:0;margin-left:auto}.view-toggle-btn{padding:.35rem .75rem;border:var(--border-width-sm) solid var(--border-color);background:var(--bg-secondary);font-family:var(--font-body);font-size:var(--font-size-md);cursor:pointer;transition:background var(--transition-fast),box-shadow var(--transition-fast)}.view-toggle-btn.active{background:var(--bg-card);font-weight:600}.view-toggle-btn:first-child{border-radius:var(--radius-xs) 0 0 var(--radius-xs)}.view-toggle-btn:last-child{border-radius:0 var(--radius-xs) var(--radius-xs) 0;border-left:none}.kanban-board{display:grid;grid-template-columns:repeat(3,1fr);gap:1rem;padding:.5rem 0;min-height:400px}.kanban-column{background:var(--bg-card);border:var(--border-width) solid var(--border-color);display:flex;flex-direction:column;min-height:300px;max-height:calc(100vh - 200px)}.kanban-column-header{padding:.75rem 1rem;border-bottom:2px solid var(--border-color);font-family:var(--font-heading);font-weight:700;display:flex;justify-content:space-between;align-items:center}.kanban-column-count{font-family:var(--font-body);font-size:var(--font-size-sm);color:var(--text-secondary)}.kanban-column-body{flex:1;overflow-y:auto;padding:.5rem;display:flex;flex-direction:column;gap:.5rem}.kanban-column.drag-over{background-color:var(--bg-tertiary)}.kanban-empty{text-align:center;padding:2rem 1rem;color:var(--text-secondary);font-size:var(--font-size-sm)}.kanban-card{padding:.75rem;background:var(--bg-card);border:var(--border-width-sm) solid var(--border-color);cursor:grab;box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color);transition:transform .15s ease,box-shadow .15s ease,background-color .15s ease;border-left:4px solid transparent}.kanban-card:hover{background:var(--bg-secondary);transform:translate(-2px,-2px);box-shadow:calc(var(--shadow-offset) + 2px) calc(var(--shadow-offset) + 2px) 0 var(--border-color)}.kanban-card.dragging{opacity:.5;cursor:grabbing}.kanban-card.priority-high{border-left-color:var(--accent-red)}.kanban-card.priority-medium{border-left-color:var(--accent-yellow)}.kanban-card.priority-low{border-left-color:var(--accent-green)}.kanban-card-title{font-weight:600;margin-bottom:.25rem}.kanban-card-meta{font-size:var(--font-size-sm);color:var(--text-secondary);display:flex;gap:.5rem;flex-wrap:wrap}.kanban-card-due.overdue{color:var(--accent-red);font-weight:600}.progress-bar-mini{height:3px;background:var(--bg-tertiary);border-radius:2px;margin-top:.5rem}.progress-bar-mini .progress-fill{height:100%;background:var(--accent-green);border-radius:2px}@media (max-width:768px){.kanban-board{grid-template-columns:1fr}.kanban-column{max-height:none}}.timer-widget{position:fixed;bottom:0;left:0;right:0;z-index:900;background:var(--bg-primary);border-top:var(--border-width) solid var(--border-color);box-shadow:0 -2px 8px rgba(0,0,0,.1);padding:.5rem 1rem;transition:transform .2s ease}.timer-widget.hidden{transform:translateY(100%);pointer-events:none}.timer-widget-inner{display:flex;align-items:center;gap:1rem;max-width:800px;margin:0 auto}.timer-task-name{flex:1;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.timer-elapsed{font-family:var(--font-mono, monospace);font-size:1.125rem;font-weight:700;color:var(--accent-color);min-width:5rem;text-align:center}.timer-actions{display:flex;gap:.5rem}.focus-overlay{position:fixed;inset:0;z-index:1000;background:var(--bg-primary);display:flex;align-items:center;justify-content:center;transition:opacity .3s ease}.focus-overlay.hidden{opacity:0;pointer-events:none}.focus-overlay-content{text-align:center;max-width:400px;width:100%;padding:2rem}.focus-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:2rem}.focus-label{font-family:var(--font-heading);font-size:1.25rem;font-weight:700}.focus-presets{display:flex;gap:.5rem}.focus-preset-btn.active{background:var(--accent-color);color:var(--bg-primary);border-color:var(--accent-color)}.focus-countdown{font-family:var(--font-mono, monospace);font-size:4rem;font-weight:700;line-height:1;margin-bottom:1.5rem;color:var(--text-primary)}.focus-progress-bar{height:6px;background:var(--bg-tertiary);border-radius:3px;margin-bottom:1.5rem;overflow:hidden}.focus-progress-fill{height:100%;background:var(--accent-color);border-radius:3px;transition:width 1s linear}.focus-task-name{color:var(--text-secondary);margin-bottom:2rem;font-size:.9rem}.focus-actions{display:flex;gap:1rem;justify-content:center}.time-summary-section{margin-bottom:1rem}.time-summary-toggle{display:flex;align-items:center;gap:.5rem;width:100%;padding:.5rem;background:0 0;border:none;font-family:var(--font-heading);font-size:.875rem;font-weight:700;color:var(--text-primary);cursor:pointer;text-align:left}.time-summary-toggle:hover{color:var(--accent-color)}.time-summary-toggle-icon{font-size:.625rem;transition:transform .15s ease}.time-summary-body{padding:.5rem;overflow:hidden;transition:max-height .2s ease;max-height:500px}.time-summary-body.collapsed{max-height:0;padding:0 .5rem}.time-summary-today{display:flex;justify-content:space-between;align-items:center;padding:.5rem 0;border-bottom:1px solid var(--border-color);margin-bottom:.5rem}.time-summary-today-label{font-weight:600;font-size:.875rem}.time-summary-today-value{font-family:var(--font-mono, monospace);font-weight:700;font-size:1rem;color:var(--accent-color)}.time-summary-week-header{font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--text-secondary);margin-bottom:.5rem}.time-summary-project{margin-bottom:.5rem}.time-summary-project-info{display:flex;justify-content:space-between;align-items:center;font-size:.8125rem;margin-bottom:.25rem}.time-summary-project-name{color:var(--text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.time-summary-project-time{font-family:var(--font-mono, monospace);font-weight:600;font-size:.75rem;color:var(--text-secondary);margin-left:.5rem;flex-shrink:0}.time-summary-bar{height:4px;background:var(--bg-tertiary);border-radius:2px;overflow:hidden}.time-summary-bar-fill{height:100%;background:var(--accent-color);border-radius:2px}.unscheduled-task-actions{display:flex;gap:.25rem;margin-top:.375rem}.unscheduled-task-actions .btn{font-size:.7rem;padding:.125rem .375rem;min-height:auto;line-height:1.4}.task-time-badge{display:inline-block;font-family:var(--font-mono, monospace);font-size:.7rem;font-weight:600;color:var(--text-secondary);background:var(--bg-secondary);border:var(--border-width-sm) solid var(--border-color);border-radius:var(--radius-sm);padding:.05rem .35rem;margin-left:.375rem;vertical-align:middle;white-space:nowrap}.task-time-badge.over-estimate{color:var(--accent-red);border-color:var(--accent-red)}.task-started-icon{display:inline-block;width:0;height:0;border-style:solid;border-width:5px 0 5px 8px;border-color:transparent transparent transparent var(--accent-green,#22c55e);margin-right:.375rem;vertical-align:middle;cursor:pointer;opacity:.8;flex-shrink:0}.task-started-icon:hover{opacity:1}.task-timer-active{display:inline-block;width:8px;height:8px;background:var(--accent-red);border-radius:var(--radius-full);margin-left:.375rem;vertical-align:middle;animation:timer-pulse 1.5s ease-in-out infinite}@keyframes timer-pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.4;transform:scale(.8)}}@media (max-width:768px){.timer-widget{bottom:60px}.focus-countdown{font-size:3rem}}.timer-active-banner{display:flex;align-items:center;gap:1rem;padding:.875rem 1rem;background:var(--bg-secondary);border:var(--border-width) solid var(--accent-color);border-radius:var(--radius-md);margin-bottom:1.5rem}.timer-active-info{flex:1;min-width:0}.timer-active-label{display:block;font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--accent-color);margin-bottom:.125rem}.timer-active-task{display:block;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.timer-active-elapsed{font-family:var(--font-mono, monospace);font-size:1.25rem;font-weight:700;color:var(--accent-color);min-width:5rem;text-align:center}.timer-active-actions{display:flex;gap:.5rem}.timer-focus-split{display:flex;align-items:center;gap:.375rem;padding:.5rem 0;margin-bottom:.5rem}.timer-focus-split-label{font-size:.8125rem;color:var(--text-secondary);font-weight:600;margin-right:.25rem}.timer-split-input{width:3.5rem;padding:.25rem .375rem;font-size:.875rem;font-family:var(--font-mono, monospace);text-align:center;border:var(--border-width) solid var(--border-color);border-radius:var(--radius-sm);background:var(--bg-primary);color:var(--text-primary)}.timer-focus-split-sep{font-size:.8125rem;color:var(--text-secondary)}.timer-task-list{display:flex;flex-direction:column;gap:0}.timer-task-item{display:flex;align-items:center;gap:1rem;padding:.75rem .5rem;border-bottom:1px solid var(--border-color)}.timer-task-item:last-child{border-bottom:none}.timer-task-info{flex:1;min-width:0}.timer-task-desc{display:block;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.timer-task-meta{display:flex;gap:.5rem;flex-wrap:wrap;margin-top:.25rem;font-size:.8125rem;color:var(--text-secondary)}.timer-task-project{font-weight:600}.timer-task-priority{font-weight:600}.timer-task-priority.priority-h,.timer-task-priority.priority-high{color:var(--accent-red)}.timer-task-priority.priority-m,.timer-task-priority.priority-medium{color:var(--accent-yellow,var(--accent-color))}.timer-task-estimate,.timer-task-tracked{font-family:var(--font-mono, monospace);font-size:.75rem}.timer-task-actions{display:flex;gap:.375rem;flex-shrink:0}@media (max-width:768px){.timer-active-banner{flex-wrap:wrap}.timer-active-elapsed{font-size:1rem}.timer-task-item{flex-wrap:wrap}.timer-task-actions{width:100%;justify-content:flex-end}}
1 < \ No newline at end of file
@@ -360,11 +360,21 @@
360 360 <div class="page-header">
361 361 <h2 class="page-title">Emails</h2>
362 362 <div style="display: flex; gap: 0.5rem;">
363 + <button class="btn btn-secondary" onclick="GoingsOn.emails.openDrafts()">Drafts</button>
363 364 <button class="btn btn-secondary" onclick="GoingsOn.emails.openAccountsModal()">Accounts</button>
364 365 <button class="btn btn-secondary" onclick="GoingsOn.emails.markAllRead()">Mark All Read</button>
365 366 <button class="btn btn-primary" onclick="GoingsOn.emails.openCompose()" title="Compose email (n)">+ Compose</button>
366 367 </div>
367 368 </div>
369 + <div style="display: flex; gap: 0.5rem; margin-bottom: 0.5rem; align-items: center;">
370 + <input type="text" class="form-input" id="email-search" placeholder="Search emails..." oninput="GoingsOn.emails.search(this.value)" style="flex: 1;">
371 + <select class="form-select" id="email-folder-filter" onchange="GoingsOn.emails.filterByFolder(this.value)" style="width: auto; min-width: 120px;">
372 + <option value="">All folders</option>
373 + </select>
374 + <select class="form-select" id="email-label-filter" onchange="GoingsOn.emails.filterByLabel(this.value)" style="width: auto; min-width: 120px;">
375 + <option value="">All labels</option>
376 + </select>
377 + </div>
368 378 <div id="email-bulk-actions" class="bulk-actions-bar hidden" role="toolbar" aria-label="Bulk email actions">
369 379 <span id="email-bulk-count" class="bulk-count">0 selected</span>
370 380 <button class="btn btn-sm" onclick="GoingsOn.bulk.markEmailsRead()">Mark Read</button>
@@ -508,6 +518,7 @@
508 518 <script src="js/task-forms.js"></script>
509 519 <script src="js/task-board.js"></script>
510 520 <script src="js/attachments.js"></script>
521 + <script src="js/autocomplete.js"></script>
511 522 <script src="js/tasks.js"></script>
512 523 <script src="js/events.js"></script>
513 524 <script src="js/emails.js"></script>
@@ -132,6 +132,13 @@ const api = {
132 132 markWaiting: (id, expectedResponse) => invoke('mark_email_waiting', { id, input: { expectedResponseDate: expectedResponse } }),
133 133 clearWaiting: (id) => invoke('clear_email_waiting', { id }),
134 134 listByThread: (threadId) => invoke('list_emails_by_thread', { threadId }),
135 + saveDraft: (input) => invoke('save_email_draft', { input }),
136 + listDrafts: () => invoke('list_email_drafts'),
137 + sendDraft: (id) => invoke('send_email_draft', { id }),
138 + setLabels: (id, labels) => invoke('set_email_labels', { id, labels }),
139 + listFolders: () => invoke('list_email_folders'),
140 + listLabels: () => invoke('list_email_labels'),
141 + moveToFolder: (id, folder) => invoke('move_email_to_folder', { id, folder }),
135 142 },
136 143
137 144 // Contacts — CRUD + multi-value fields (emails, phones, social, custom)
@@ -160,6 +167,8 @@ const api = {
160 167 create: (input) => invoke('create_email_account', { input }),
161 168 update: (id, input) => invoke('update_email_account', { id, input }),
162 169 updateSyncInterval: (id, syncIntervalMinutes) => invoke('update_email_sync_interval', { id, input: { syncIntervalMinutes } }),
170 + updateSignature: (id, emailSignature) => invoke('update_email_signature', { id, input: { emailSignature } }),
171 + updateNotify: (id, enabled) => invoke('update_email_notify', { id, enabled }),
163 172 delete: (id) => invoke('delete_email_account', { id }),
164 173 test: (id) => invoke('test_email_account', { id }), // Verify IMAP/SMTP credentials
165 174 sync: (id, fullSync = false) => invoke('sync_email_account', { id, fullSync }), // fullSync ignores last_sync_at
@@ -192,6 +201,11 @@ const api = {
192 201 reconnect: (accountId) => invoke('reconnect_oauth', { accountId }),
193 202 },
194 203
204 + // Search — full-text search across all entity types
205 + search: {
206 + query: (input) => invoke('search', { input }),
207 + },
208 +
195 209 // Export/Backup — data export (JSON/CSV/ICS) and automatic backup management
196 210 export: {
197 211 getSummary: () => invoke('get_export_summary'), // Counts per entity type for the export dialog
@@ -268,6 +282,8 @@ const api = {
268 282 open: (id) => invoke('open_attachment', { id }),
269 283 save: (id, destination) => invoke('save_attachment', { id, destination }),
270 284 convertFromEmail: (emailId, taskId) => invoke('convert_email_attachments', { emailId, taskId }),
285 + openEmailBlob: (blobHash, filename) => invoke('open_email_blob', { blobHash, filename }),
286 + saveEmailBlob: (blobHash, destination) => invoke('save_email_blob', { blobHash, destination }),
271 287 },
272 288
273 289 // External Import — vCard contacts and iCalendar events
@@ -190,5 +190,6 @@
190 190 saveAs,
191 191 remove,
192 192 renderBadge,
193 + getIcon,
193 194 };
194 195 })();
@@ -0,0 +1,154 @@
1 + /**
2 + * GoingsOn - Email Address Autocomplete
3 + * Typeahead for To/CC/BCC fields using contacts database.
4 + */
5 +
6 + (function() {
7 + 'use strict';
8 + const esc = GoingsOn.utils.escapeHtml;
9 +
10 + let contactEmails = null; // [{name, email}], lazy-loaded
11 +
12 + /**
13 + * Load all contact email addresses for autocomplete.
14 + * Cached after first load; call refresh() to reload.
15 + */
16 + async function ensureLoaded() {
17 + if (contactEmails !== null) return;
18 + try {
19 + const contacts = await GoingsOn.api.contacts.list();
20 + contactEmails = [];
21 + for (const c of contacts) {
22 + const name = c.displayName || c.display_name || '';
23 + if (c.emails && c.emails.length > 0) {
24 + for (const e of c.emails) {
25 + contactEmails.push({ name, email: e.address });
26 + }
27 + }
28 + }
29 + } catch (_) {
30 + contactEmails = [];
31 + }
32 + }
33 +
34 + function refresh() {
35 + contactEmails = null;
36 + }
37 +
38 + /**
39 + * Get the current token being typed (after the last comma).
40 + */
41 + function getLastToken(input) {
42 + const val = input.value;
43 + const cursor = input.selectionStart || val.length;
44 + const before = val.slice(0, cursor);
45 + const lastComma = before.lastIndexOf(',');
46 + return { token: before.slice(lastComma + 1).trim(), lastComma };
47 + }
48 +
49 + function filterContacts(token) {
50 + if (!token || token.length < 1 || !contactEmails) return [];
51 + const q = token.toLowerCase();
52 + return contactEmails
53 + .filter(c => c.email.toLowerCase().includes(q) || c.name.toLowerCase().includes(q))
54 + .slice(0, 8);
55 + }
56 +
57 + /**
58 + * Attach autocomplete behavior to an email address input.
59 + * Supports comma-separated addresses — autocompletes the current token.
60 + * @param {HTMLInputElement} input - The text input element
61 + */
62 + function attach(input) {
63 + let dropdown = null;
64 + let activeIndex = -1;
65 + let matches = [];
66 +
67 + function show(filteredMatches) {
68 + hide();
69 + if (filteredMatches.length === 0) return;
70 + matches = filteredMatches;
71 + activeIndex = -1;
72 +
73 + dropdown = document.createElement('div');
74 + dropdown.className = 'autocomplete-dropdown';
75 +
76 + filteredMatches.forEach((m, i) => {
77 + const item = document.createElement('div');
78 + item.className = 'autocomplete-item';
79 + item.innerHTML = `<span class="autocomplete-name">${esc(m.name)}</span> <span class="autocomplete-email">${esc(m.email)}</span>`;
80 + item.addEventListener('mousedown', (e) => {
81 + e.preventDefault();
82 + select(m.email);
83 + });
84 + dropdown.appendChild(item);
85 + });
86 +
87 + // Position relative to input's parent
88 + const wrapper = input.parentElement;
89 + wrapper.style.position = 'relative';
90 + dropdown.style.position = 'absolute';
91 + dropdown.style.top = input.offsetTop + input.offsetHeight + 'px';
92 + dropdown.style.left = '0';
93 + dropdown.style.right = '0';
94 + wrapper.appendChild(dropdown);
95 + }
96 +
97 + function hide() {
98 + if (dropdown) {
99 + dropdown.remove();
100 + dropdown = null;
101 + activeIndex = -1;
102 + matches = [];
103 + }
104 + }
105 +
106 + function select(email) {
107 + const val = input.value;
108 + const cursor = input.selectionStart || val.length;
109 + const before = val.slice(0, cursor);
110 + const after = val.slice(cursor);
111 + const lastComma = before.lastIndexOf(',');
112 + const prefix = lastComma >= 0 ? before.slice(0, lastComma + 1) + ' ' : '';
113 + input.value = prefix + email + ', ' + after.trimStart();
114 + const newCursor = (prefix + email + ', ').length;
115 + input.setSelectionRange(newCursor, newCursor);
116 + input.focus();
117 + hide();
118 + }
119 +
120 + input.addEventListener('input', async () => {
121 + await ensureLoaded();
122 + const { token } = getLastToken(input);
123 + show(filterContacts(token));
124 + });
125 +
126 + input.addEventListener('blur', () => {
127 + setTimeout(hide, 150);
128 + });
129 +
130 + input.addEventListener('keydown', (e) => {
131 + if (!dropdown) return;
132 + const items = dropdown.querySelectorAll('.autocomplete-item');
133 +
134 + if (e.key === 'ArrowDown') {
135 + e.preventDefault();
136 + activeIndex = Math.min(activeIndex + 1, items.length - 1);
137 + items.forEach((el, i) => el.classList.toggle('active', i === activeIndex));
138 + } else if (e.key === 'ArrowUp') {
139 + e.preventDefault();
140 + activeIndex = Math.max(activeIndex - 1, 0);
141 + items.forEach((el, i) => el.classList.toggle('active', i === activeIndex));
142 + } else if (e.key === 'Enter' || e.key === 'Tab') {
143 + if (activeIndex >= 0 && activeIndex < matches.length) {
144 + e.preventDefault();
145 + select(matches[activeIndex].email);
146 + }
147 + } else if (e.key === 'Escape') {
148 + hide();
149 + }
150 + });
151 + }
152 +
153 + GoingsOn.autocomplete = { attach, refresh };
154 + })();
@@ -88,6 +88,13 @@
88 88 ${esc(archiveHint)}
89 89 </div>
90 90 </div>
91 + <div class="form-group">
92 + <label class="form-label" for="${idPrefix}-signature">Email Signature</label>
93 + <textarea class="form-input" id="${idPrefix}-signature" name="email_signature" rows="3" placeholder="-- \nYour Name">${esc(values.emailSignature || '')}</textarea>
94 + <div style="font-size: 0.75rem; color: var(--text-secondary); margin-top: 0.25rem;">
95 + Appended to outbound emails. Plain text only.
96 + </div>
97 + </div>
91 98 <button type="button" class="form-more-toggle ${advancedExpanded}" onclick="this.classList.toggle('expanded'); this.nextElementSibling.classList.toggle('hidden');">Advanced settings</button>
92 99 <div class="${advancedHidden}">
93 100 <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;">
@@ -117,6 +124,15 @@
117 124 </label>
118 125 </div>
119 126 <div class="form-group">
127 + <label style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer;">
128 + <input type="checkbox" id="${idPrefix}-notify" name="notify_new_emails" ${values.notifyNewEmails ? 'checked' : ''}>
129 + <span>Notify on new emails</span>
130 + </label>
131 + <div style="font-size: 0.75rem; color: var(--text-secondary); margin-top: 0.25rem;">
132 + Show a system notification when new emails arrive during auto-sync. Off by default.
133 + </div>
134 + </div>
135 + <div class="form-group">
120 136 <label class="form-label" for="${idPrefix}-sync-interval">Auto-sync Interval</label>
121 137 <select class="form-select" id="${idPrefix}-sync-interval" name="sync_interval_minutes">
122 138 ${syncOptionsHtml}
@@ -409,11 +425,18 @@
409 425 syncIntervalMinutes: syncIntervalValue ? parseInt(syncIntervalValue) : null,
410 426 };
411 427
428 + const signature = form.email_signature.value || null;
429 + const notifyNewEmails = form.notify_new_emails.checked;
430 +
412 431 await GoingsOn.ui.apiCall(GoingsOn.api.emailAccounts.update(id, data), {
413 432 successMessage: 'Email account updated!',
414 433 errorMessage: 'Failed to update email account',
415 434 closeModal: false,
416 - onSuccess: () => openAccountsModal(),
435 + onSuccess: async () => {
436 + await GoingsOn.api.emailAccounts.updateSignature(id, signature);
437 + await GoingsOn.api.emailAccounts.updateNotify(id, notifyNewEmails);
438 + openAccountsModal();
439 + },
417 440 });
418 441 }
419 442