Skip to main content

max / goingson

v0.3.0: Beta-ready milestone — MCP contact tools, OTA updater, sync scheduler improvements, validation fixes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author: Max J. <87768334+MaxJMath@users.noreply.github.com> · 2026-03-17 02:58 UTC
Commit: c7c700f6d6f809de0826c01e54f538f33594df98
Parent: 92cd9cc
37 files changed, +1686 insertions, -188 deletions
M Cargo.lock +195 -3
@@ -100,6 +100,15 @@ dependencies = [
100 100 ]
101 101
102 102 [[package]]
103 + name = "arbitrary"
104 + version = "1.4.2"
105 + source = "registry+https://github.com/rust-lang/crates.io-index"
106 + checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
107 + dependencies = [
108 + "derive_arbitrary",
109 + ]
110 +
111 + [[package]]
103 112 name = "argon2"
104 113 version = "0.5.3"
105 114 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1004,6 +1013,17 @@ dependencies = [
1004 1013 ]
1005 1014
1006 1015 [[package]]
1016 + name = "derive_arbitrary"
1017 + version = "1.4.2"
1018 + source = "registry+https://github.com/rust-lang/crates.io-index"
1019 + checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
1020 + dependencies = [
1021 + "proc-macro2",
1022 + "quote",
1023 + "syn 2.0.114",
1024 + ]
1025 +
1026 + [[package]]
1007 1027 name = "derive_more"
1008 1028 version = "0.99.20"
1009 1029 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1863,6 +1883,7 @@ dependencies = [
1863 1883 "tauri-plugin-dialog",
1864 1884 "tauri-plugin-notification",
1865 1885 "tauri-plugin-shell",
1886 + "tauri-plugin-updater",
1866 1887 "tauri-plugin-window-state",
1867 1888 "tempfile",
1868 1889 "thiserror 1.0.69",
@@ -2909,6 +2930,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2909 2930 checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
2910 2931
2911 2932 [[package]]
2933 + name = "minisign-verify"
2934 + version = "0.2.5"
2935 + source = "registry+https://github.com/rust-lang/crates.io-index"
2936 + checksum = "22f9645cb765ea72b8111f36c522475d2daa0d22c957a9826437e97534bc4e9e"
2937 +
2938 + [[package]]
2912 2939 name = "miniz_oxide"
2913 2940 version = "0.8.9"
2914 2941 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2981,10 +3008,10 @@ dependencies = [
2981 3008 "libc",
2982 3009 "log",
2983 3010 "openssl",
2984 - "openssl-probe",
3011 + "openssl-probe 0.1.6",
2985 3012 "openssl-sys",
2986 3013 "schannel",
2987 - "security-framework",
3014 + "security-framework 2.11.1",
2988 3015 "security-framework-sys",
2989 3016 "tempfile",
2990 3017 ]
@@ -3437,6 +3464,18 @@ dependencies = [
3437 3464 ]
3438 3465
3439 3466 [[package]]
3467 + name = "objc2-osa-kit"
3468 + version = "0.3.2"
3469 + source = "registry+https://github.com/rust-lang/crates.io-index"
3470 + checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0"
3471 + dependencies = [
3472 + "bitflags 2.10.0",
3473 + "objc2 0.6.3",
3474 + "objc2-app-kit 0.3.2",
3475 + "objc2-foundation 0.3.2",
3476 + ]
3477 +
3478 + [[package]]
3440 3479 name = "objc2-quartz-core"
3441 3480 version = "0.2.2"
3442 3481 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3569,6 +3608,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
3569 3608 checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
3570 3609
3571 3610 [[package]]
3611 + name = "openssl-probe"
3612 + version = "0.2.1"
3613 + source = "registry+https://github.com/rust-lang/crates.io-index"
3614 + checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
3615 +
3616 + [[package]]
3572 3617 name = "openssl-sys"
3573 3618 version = "0.9.111"
3574 3619 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3607,6 +3652,20 @@ dependencies = [
3607 3652 ]
3608 3653
3609 3654 [[package]]
3655 + name = "osakit"
3656 + version = "0.3.1"
3657 + source = "registry+https://github.com/rust-lang/crates.io-index"
3658 + checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b"
3659 + dependencies = [
3660 + "objc2 0.6.3",
3661 + "objc2-foundation 0.3.2",
3662 + "objc2-osa-kit",
3663 + "serde",
3664 + "serde_json",
3665 + "thiserror 2.0.18",
3666 + ]
3667 +
3668 + [[package]]
3610 3669 name = "pango"
3611 3670 version = "0.18.3"
3612 3671 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4376,15 +4435,20 @@ dependencies = [
4376 4435 "http-body",
4377 4436 "http-body-util",
4378 4437 "hyper",
4438 + "hyper-rustls",
4379 4439 "hyper-util",
4380 4440 "js-sys",
4381 4441 "log",
4382 4442 "percent-encoding",
4383 4443 "pin-project-lite",
4444 + "rustls",
4445 + "rustls-pki-types",
4446 + "rustls-platform-verifier",
4384 4447 "serde",
4385 4448 "serde_json",
4386 4449 "sync_wrapper",
4387 4450 "tokio",
4451 + "tokio-rustls",
4388 4452 "tokio-util",
4389 4453 "tower",
4390 4454 "tower-http",
@@ -4545,6 +4609,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
4545 4609 checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b"
4546 4610 dependencies = [
4547 4611 "once_cell",
4612 + "ring",
4548 4613 "rustls-pki-types",
4549 4614 "rustls-webpki",
4550 4615 "subtle",
@@ -4552,6 +4617,18 @@ dependencies = [
4552 4617 ]
4553 4618
4554 4619 [[package]]
4620 + name = "rustls-native-certs"
4621 + version = "0.8.3"
4622 + source = "registry+https://github.com/rust-lang/crates.io-index"
4623 + checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63"
4624 + dependencies = [
4625 + "openssl-probe 0.2.1",
4626 + "rustls-pki-types",
4627 + "schannel",
4628 + "security-framework 3.5.1",
4629 + ]
4630 +
4631 + [[package]]
4555 4632 name = "rustls-pki-types"
4556 4633 version = "1.14.0"
4557 4634 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4561,6 +4638,33 @@ dependencies = [
4561 4638 ]
4562 4639
4563 4640 [[package]]
4641 + name = "rustls-platform-verifier"
4642 + version = "0.6.2"
4643 + source = "registry+https://github.com/rust-lang/crates.io-index"
4644 + checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
4645 + dependencies = [
4646 + "core-foundation 0.10.1",
4647 + "core-foundation-sys",
4648 + "jni",
4649 + "log",
4650 + "once_cell",
4651 + "rustls",
4652 + "rustls-native-certs",
4653 + "rustls-platform-verifier-android",
4654 + "rustls-webpki",
4655 + "security-framework 3.5.1",
4656 + "security-framework-sys",
4657 + "webpki-root-certs",
4658 + "windows-sys 0.61.2",
4659 + ]
4660 +
4661 + [[package]]
4662 + name = "rustls-platform-verifier-android"
4663 + version = "0.1.1"
4664 + source = "registry+https://github.com/rust-lang/crates.io-index"
4665 + checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
4666 +
4667 + [[package]]
4564 4668 name = "rustls-webpki"
4565 4669 version = "0.103.9"
4566 4670 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4672,6 +4776,19 @@ dependencies = [
4672 4776 ]
4673 4777
4674 4778 [[package]]
4779 + name = "security-framework"
4780 + version = "3.5.1"
4781 + source = "registry+https://github.com/rust-lang/crates.io-index"
4782 + checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef"
4783 + dependencies = [
4784 + "bitflags 2.10.0",
4785 + "core-foundation 0.10.1",
4786 + "core-foundation-sys",
4787 + "libc",
4788 + "security-framework-sys",
4789 + ]
4790 +
4791 + [[package]]
4675 4792 name = "security-framework-sys"
4676 4793 version = "2.15.0"
4677 4794 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5465,13 +5582,13 @@ dependencies = [
5465 5582 "reqwest 0.12.28",
5466 5583 "serde",
5467 5584 "serde_json",
5468 - "sha2",
5469 5585 "thiserror 2.0.18",
5470 5586 "tokio",
5471 5587 "tracing",
5472 5588 "unicode-normalization",
5473 5589 "urlencoding",
5474 5590 "uuid",
5591 + "zeroize",
5475 5592 ]
5476 5593
5477 5594 [[package]]
@@ -5571,6 +5688,17 @@ dependencies = [
5571 5688 ]
5572 5689
5573 5690 [[package]]
5691 + name = "tar"
5692 + version = "0.4.44"
5693 + source = "registry+https://github.com/rust-lang/crates.io-index"
5694 + checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a"
5695 + dependencies = [
5696 + "filetime",
5697 + "libc",
5698 + "xattr",
5699 + ]
5700 +
5701 + [[package]]
5574 5702 name = "target-lexicon"
5575 5703 version = "0.12.16"
5576 5704 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5789,6 +5917,39 @@ dependencies = [
5789 5917 ]
5790 5918
5791 5919 [[package]]
5920 + name = "tauri-plugin-updater"
5921 + version = "2.10.0"
5922 + source = "registry+https://github.com/rust-lang/crates.io-index"
5923 + checksum = "3fe8e9bebd88fc222938ffdfbdcfa0307081423bd01e3252fc337d8bde81fc61"
5924 + dependencies = [
5925 + "base64 0.22.1",
5926 + "dirs",
5927 + "flate2",
5928 + "futures-util",
5929 + "http",
5930 + "infer",
5931 + "log",
5932 + "minisign-verify",
5933 + "osakit",
5934 + "percent-encoding",
5935 + "reqwest 0.13.2",
5936 + "rustls",
5937 + "semver",
5938 + "serde",
5939 + "serde_json",
5940 + "tar",
5941 + "tauri",
5942 + "tauri-plugin",
5943 + "tempfile",
5944 + "thiserror 2.0.18",
5945 + "time",
5946 + "tokio",
5947 + "url",
5948 + "windows-sys 0.60.2",
5949 + "zip",
5950 + ]
5951 +
5952 + [[package]]
5792 5953 name = "tauri-plugin-window-state"
5793 5954 version = "2.4.1"
5794 5955 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -6773,6 +6934,15 @@ dependencies = [
6773 6934 ]
6774 6935
6775 6936 [[package]]
6937 + name = "webpki-root-certs"
6938 + version = "1.0.6"
6939 + source = "registry+https://github.com/rust-lang/crates.io-index"
6940 + checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca"
6941 + dependencies = [
6942 + "rustls-pki-types",
6943 + ]
6944 +
6945 + [[package]]
6776 6946 name = "webview2-com"
6777 6947 version = "0.38.2"
6778 6948 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -7436,6 +7606,16 @@ dependencies = [
7436 7606 ]
7437 7607
7438 7608 [[package]]
7609 + name = "xattr"
7610 + version = "1.6.1"
7611 + source = "registry+https://github.com/rust-lang/crates.io-index"
7612 + checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
7613 + dependencies = [
7614 + "libc",
7615 + "rustix",
7616 + ]
7617 +
7618 + [[package]]
7439 7619 name = "yoke"
7440 7620 version = "0.8.1"
7441 7621 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -7600,6 +7780,18 @@ dependencies = [
7600 7780 ]
7601 7781
7602 7782 [[package]]
7783 + name = "zip"
7784 + version = "4.6.1"
7785 + source = "registry+https://github.com/rust-lang/crates.io-index"
7786 + checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1"
7787 + dependencies = [
7788 + "arbitrary",
7789 + "crc32fast",
7790 + "indexmap 2.13.0",
7791 + "memchr",
7792 + ]
7793 +
7794 + [[package]]
7603 7795 name = "zmij"
7604 7796 version = "1.0.19"
7605 7797 source = "registry+https://github.com/rust-lang/crates.io-index"
M Cargo.toml +2 -1
@@ -10,7 +10,7 @@ default-members = ["src-tauri"]
10 10 resolver = "2"
11 11
12 12 [workspace.package]
13 - version = "0.2.1"
13 + version = "0.3.0"
14 14 edition = "2021"
15 15 license-file = "LICENSE"
16 16
@@ -61,6 +61,7 @@ tauri-plugin-dialog = "2.6.0"
61 61 tauri-plugin-shell = "2.3.5"
62 62 tauri-plugin-notification = "2.3.3"
63 63 tauri-plugin-window-state = "2.4.1"
64 + tauri-plugin-updater = "2"
64 65
65 66 # Plugin system
66 67 rhai = { version = "1.17", features = ["sync", "serde"] }
@@ -62,6 +62,9 @@ pub const MAX_EVENT_TITLE_LENGTH: usize = 255;
62 62 /// Maximum length for tags.
63 63 pub const MAX_TAG_LENGTH: usize = 50;
64 64
65 + /// Maximum length for contact display names.
66 + pub const MAX_CONTACT_DISPLAY_NAME_LENGTH: usize = 255;
67 +
65 68 /// Maximum scheduled duration in minutes (24 hours).
66 69 pub const MAX_SCHEDULED_DURATION_MINUTES: i32 = 24 * 60;
67 70
@@ -4,10 +4,11 @@
4 4 //! validating DTOs before persistence operations.
5 5
6 6 use crate::constants::{
7 - MAX_EVENT_TITLE_LENGTH, MAX_PROJECT_NAME_LENGTH, MAX_SCHEDULED_DURATION_MINUTES,
8 - MAX_TAG_LENGTH, MAX_TASK_DESCRIPTION_LENGTH,
7 + MAX_CONTACT_DISPLAY_NAME_LENGTH, MAX_EVENT_TITLE_LENGTH, MAX_PROJECT_NAME_LENGTH,
8 + MAX_SCHEDULED_DURATION_MINUTES, MAX_TAG_LENGTH, MAX_TASK_DESCRIPTION_LENGTH,
9 9 };
10 10 use crate::error::CoreError;
11 + use crate::contact::{NewContact, UpdateContact};
11 12 use crate::models::{NewEvent, NewProject, NewTask, UpdateEvent, UpdateProject, UpdateTask};
12 13
13 14 /// A trait for types that can validate their own data before persistence.
@@ -178,6 +179,46 @@ impl Validate for UpdateEvent {
178 179 }
179 180 }
180 181
182 + // Contact validation: non-empty display_name, valid tags.
183 + fn validate_contact_fields(display_name: &str, tags: &[String]) -> Result<(), CoreError> {
184 + if display_name.trim().is_empty() {
185 + return Err(CoreError::validation("display_name", "cannot be empty"));
186 + }
187 + if display_name.len() > MAX_CONTACT_DISPLAY_NAME_LENGTH {
188 + return Err(CoreError::validation(
189 + "display_name",
190 + format!(
191 + "must be {} characters or less",
192 + MAX_CONTACT_DISPLAY_NAME_LENGTH
193 + ),
194 + ));
195 + }
196 + for tag in tags {
197 + if tag.trim().is_empty() {
198 + return Err(CoreError::validation("tags", "tags cannot be empty strings"));
199 + }
200 + if tag.len() > MAX_TAG_LENGTH {
201 + return Err(CoreError::validation(
202 + "tags",
203 + format!("each tag must be {} characters or less", MAX_TAG_LENGTH),
204 + ));
205 + }
206 + }
207 + Ok(())
208 + }
209 +
210 + impl Validate for NewContact {
211 + fn validate(&self) -> Result<(), CoreError> {
212 + validate_contact_fields(&self.display_name, &self.tags)
213 + }
214 + }
215 +
216 + impl Validate for UpdateContact {
217 + fn validate(&self) -> Result<(), CoreError> {
218 + validate_contact_fields(&self.display_name, &self.tags)
219 + }
220 + }
221 +
181 222 #[cfg(test)]
182 223 mod tests {
183 224 use super::*;
@@ -653,4 +694,75 @@ mod tests {
653 694 let err = event.validate().unwrap_err();
654 695 assert!(matches!(err, CoreError::Validation { field: "end_time", .. }));
655 696 }
697 +
698 + // ---- Contact validation tests ----
699 +
700 + fn make_new_contact(name: &str) -> NewContact {
701 + NewContact {
702 + display_name: name.to_string(),
703 + nickname: None,
704 + company: None,
705 + title: None,
706 + notes: String::new(),
707 + tags: vec![],
708 + birthday: None,
709 + timezone: None,
710 + }
711 + }
712 +
713 + #[test]
714 + fn test_new_contact_valid() {
715 + assert!(make_new_contact("Alice").validate().is_ok());
716 + }
717 +
718 + #[test]
719 + fn test_new_contact_empty_name() {
720 + let err = make_new_contact(" ").validate().unwrap_err();
721 + assert!(matches!(err, CoreError::Validation { field: "display_name", .. }));
722 + }
723 +
724 + #[test]
725 + fn test_new_contact_name_too_long() {
726 + let err = make_new_contact(&"a".repeat(256)).validate().unwrap_err();
727 + assert!(matches!(err, CoreError::Validation { field: "display_name", .. }));
728 + }
729 +
730 + #[test]
731 + fn test_new_contact_empty_tag() {
732 + let mut c = make_new_contact("Bob");
733 + c.tags = vec!["valid".to_string(), "".to_string()];
734 + let err = c.validate().unwrap_err();
735 + assert!(matches!(err, CoreError::Validation { field: "tags", .. }));
736 + }
737 +
738 + #[test]
739 + fn test_update_contact_valid() {
740 + let update = UpdateContact {
741 + display_name: "Updated".to_string(),
742 + nickname: None,
743 + company: None,
744 + title: None,
745 + notes: String::new(),
746 + tags: vec!["ok".to_string()],
747 + birthday: None,
748 + timezone: None,
749 + };
750 + assert!(update.validate().is_ok());
751 + }
752 +
753 + #[test]
754 + fn test_update_contact_empty_name() {
755 + let update = UpdateContact {
756 + display_name: "".to_string(),
757 + nickname: None,
758 + company: None,
759 + title: None,
760 + notes: String::new(),
761 + tags: vec![],
762 + birthday: None,
763 + timezone: None,
764 + };
765 + let err = update.validate().unwrap_err();
766 + assert!(matches!(err, CoreError::Validation { field: "display_name", .. }));
767 + }
656 768 }
@@ -28,7 +28,7 @@ pub fn format_datetime_opt(dt: Option<DateTime<Utc>>) -> Option<String> {
28 28 }
29 29
30 30 /// Parse a datetime string from SQLite.
31 - /// Supports RFC3339 format and SQLite's default datetime format.
31 + /// Supports RFC3339, SQLite datetime, and date-only formats.
32 32 pub fn parse_datetime(s: &str) -> Result<DateTime<Utc>, CoreError> {
33 33 chrono::DateTime::parse_from_rfc3339(s)
34 34 .map(|dt| dt.with_timezone(&Utc))
@@ -36,6 +36,10 @@ pub fn parse_datetime(s: &str) -> Result<DateTime<Utc>, CoreError> {
36 36 chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S")
37 37 .map(|dt| dt.and_utc())
38 38 })
39 + .or_else(|_| {
40 + chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d")
41 + .map(|d| d.and_hms_opt(0, 0, 0).unwrap().and_utc())
42 + })
39 43 .map_err(|e| CoreError::database_msg(format!("Invalid date: {}", e)))
40 44 }
41 45
@@ -119,6 +123,7 @@ pub fn is_valid_email(email: &str) -> bool {
119 123 #[cfg(test)]
120 124 mod tests {
121 125 use super::*;
126 + use chrono::Timelike;
122 127
123 128 #[test]
124 129 fn test_parse_datetime_rfc3339() {
@@ -133,6 +138,15 @@ mod tests {
133 138 }
134 139
135 140 #[test]
141 + fn test_parse_datetime_date_only() {
142 + let result = parse_datetime("2024-01-15");
143 + assert!(result.is_ok());
144 + let dt = result.unwrap();
145 + assert_eq!(dt.hour(), 0);
146 + assert_eq!(dt.minute(), 0);
147 + }
148 +
149 + #[test]
136 150 fn test_parse_tags() {
137 151 let tags = parse_tags(r#"["work", "urgent"]"#);
138 152 assert_eq!(tags, vec!["work", "urgent"]);
@@ -5,9 +5,10 @@
5 5 //!
6 6 //! # Usage
7 7 //!
8 - //! Run the server:
9 8 //! ```bash
10 - //! goingson-mcp
9 + //! goingson-mcp # uses installed app database (com.goingson.app)
10 + //! goingson-mcp --dev # uses standalone MCP database (com.goingson.desktop)
11 + //! GOINGSON_DB=/path/to/db goingson-mcp # explicit database path
11 12 //! ```
12 13 //!
13 14 //! Configure Claude Code (~/.claude/mcp.json):
@@ -21,20 +22,13 @@
21 22 //! }
22 23 //! }
23 24 //! ```
24 - //!
25 - //! # Available Tools
26 - //!
27 - //! - `list_tasks`: List pending tasks, optionally filtered by project
28 - //! - `create_task`: Create a new task with description, priority, due date, project
29 - //! - `complete_task`: Mark a task as completed by ID
30 - //! - `list_projects`: Show available projects for task assignment
31 25
32 26 use rmcp::ServiceExt;
33 27 use tokio::io::{stdin, stdout};
34 28 use tracing::info;
35 29 use tracing_subscriber::{fmt, prelude::*, EnvFilter};
36 30
37 - use goingson_mcp::state::McpState;
31 + use goingson_mcp::state::{DbMode, McpState};
38 32 use goingson_mcp::tools::GoingsOnServer;
39 33
40 34 #[tokio::main]
@@ -45,11 +39,16 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
45 39 .with(EnvFilter::from_default_env().add_directive("goingson_mcp=info".parse()?))
46 40 .init();
47 41
48 - info!("Starting GoingsOn MCP server");
42 + let mode = if std::env::args().any(|a| a == "--dev") {
43 + DbMode::Dev
44 + } else {
45 + DbMode::App
46 + };
47 +
48 + info!(?mode, "Starting GoingsOn MCP server");
49 49
50 50 // Initialize database connection
51 - let state = McpState::new().await?;
52 - info!("Database connection established");
51 + let state = McpState::new(mode).await?;
53 52
54 53 // Create MCP server
55 54 let server = GoingsOnServer::new(state);
@@ -1,16 +1,29 @@
1 1 //! Database connection and repository state for MCP server.
2 2 //!
3 - //! Manages connection to the same SQLite database as the Tauri app.
3 + //! Connects to the GoingsOn SQLite database. By default uses the installed
4 + //! app's database (`com.goingson.app`). Pass `--dev` to use a standalone
5 + //! MCP database (`com.goingson.desktop`), or set `GOINGSON_DB` to an
6 + //! explicit path.
4 7
5 - use goingson_core::{EventRepository, MilestoneRepository, ProjectRepository, StatsRepository, TaskRepository, UserId};
6 - use goingson_db_sqlite::{SqliteEventRepository, SqliteMilestoneRepository, SqliteProjectRepository, SqliteStatsRepository, SqliteTaskRepository};
8 + use goingson_core::{ContactRepository, EventRepository, MilestoneRepository, ProjectRepository, StatsRepository, TaskRepository, UserId};
9 + use goingson_db_sqlite::{SqliteContactRepository, SqliteEventRepository, SqliteMilestoneRepository, SqliteProjectRepository, SqliteStatsRepository, SqliteTaskRepository};
7 10 use sqlx::SqlitePool;
11 + use std::path::PathBuf;
8 12 use std::sync::Arc;
9 13 use tracing::{debug, info};
10 14
11 15 /// Fixed user ID for single-user desktop app (matches Tauri app)
12 16 pub const DESKTOP_USER_ID: UserId = UserId::from_uuid(uuid::Uuid::from_u128(1));
13 17
18 + /// Which database the MCP server should connect to.
19 + #[derive(Debug, Clone, Copy, PartialEq, Eq)]
20 + pub enum DbMode {
21 + /// Installed Tauri app database (com.goingson.app)
22 + App,
23 + /// Standalone MCP development database (com.goingson.desktop)
24 + Dev,
25 + }
26 +
14 27 /// Application state with database connection and repositories
15 28 #[derive(Clone)]
16 29 pub struct McpState {
@@ -18,16 +31,20 @@ pub struct McpState {
18 31 pub projects: Arc<dyn ProjectRepository>,
19 32 pub events: Arc<dyn EventRepository>,
20 33 pub milestones: Arc<dyn MilestoneRepository>,
34 + pub contacts: Arc<dyn ContactRepository>,
21 35 pub stats: Arc<dyn StatsRepository>,
22 36 }
23 37
24 38 impl McpState {
25 39 /// Creates new state by connecting to the GoingsOn database.
26 40 ///
27 - /// Uses the same database path as the Tauri app: ~/.config/goingson/goingson.db
28 - pub async fn new() -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
29 - let db_path = get_database_path()?;
30 - info!(?db_path, "Connecting to GoingsOn database");
41 + /// Database resolution order:
42 + /// 1. `GOINGSON_DB` env var (explicit path)
43 + /// 2. `--dev` flag → `com.goingson.desktop`
44 + /// 3. Default → `com.goingson.app` (installed Tauri app)
45 + pub async fn new(mode: DbMode) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
46 + let db_path = get_database_path(mode)?;
47 + info!(?db_path, ?mode, "Connecting to GoingsOn database");
31 48
32 49 let db_url = format!("sqlite:{}?mode=rwc", db_path.display());
33 50 let pool = SqlitePool::connect(&db_url).await?;
@@ -46,27 +63,44 @@ impl McpState {
46 63 let projects = Arc::new(SqliteProjectRepository::new(pool.clone()));
47 64 let events = Arc::new(SqliteEventRepository::new(pool.clone()));
48 65 let milestones = Arc::new(SqliteMilestoneRepository::new(pool.clone()));
66 + let contacts = Arc::new(SqliteContactRepository::new(pool.clone()));
49 67 let stats = Arc::new(SqliteStatsRepository::new(pool));
50 68
51 - Ok(Self { tasks, projects, events, milestones, stats })
69 + Ok(Self { tasks, projects, events, milestones, contacts, stats })
52 70 }
53 71 }
54 72
55 73 /// Gets the path to the GoingsOn database.
56 74 ///
57 - /// Matches Tauri's app_data_dir:
58 - /// - macOS: ~/Library/Application Support/com.goingson.desktop/
59 - /// - Linux: ~/.local/share/com.goingson.desktop/
60 - /// - Windows: %APPDATA%/com.goingson.desktop/
61 - fn get_database_path() -> Result<std::path::PathBuf, Box<dyn std::error::Error + Send + Sync>> {
62 - // Use dirs crate to get data directory, matching Tauri's app_data_dir
75 + /// Resolution order:
76 + /// 1. `GOINGSON_DB` env var → use that path directly
77 + /// 2. `DbMode::Dev` → `<data_dir>/com.goingson.desktop/goingson.db`
78 + /// 3. `DbMode::App` → `<data_dir>/com.goingson.app/goingson.db`
79 + ///
80 + /// Data dir locations:
81 + /// - macOS: ~/Library/Application Support/
82 + /// - Linux: ~/.local/share/
83 + /// - Windows: %APPDATA%/
84 + fn get_database_path(mode: DbMode) -> Result<PathBuf, Box<dyn std::error::Error + Send + Sync>> {
85 + // Explicit override via env var
86 + if let Ok(explicit) = std::env::var("GOINGSON_DB") {
87 + let path = PathBuf::from(&explicit);
88 + if let Some(parent) = path.parent() {
89 + std::fs::create_dir_all(parent)?;
90 + }
91 + info!(path = %path.display(), "Using GOINGSON_DB override");
92 + return Ok(path);
93 + }
94 +
63 95 let data_dir = dirs::data_dir()
64 96 .ok_or("Could not determine data directory")?;
65 97
66 - // Tauri uses the bundle identifier as the app directory name
67 - let app_dir = data_dir.join("com.goingson.desktop");
98 + let dir_name = match mode {
99 + DbMode::App => "com.goingson.app",
100 + DbMode::Dev => "com.goingson.desktop",
101 + };
68 102
69 - // Create directory if it doesn't exist
103 + let app_dir = data_dir.join(dir_name);
70 104 std::fs::create_dir_all(&app_dir)?;
71 105
72 106 Ok(app_dir.join("goingson.db"))
@@ -0,0 +1,267 @@
1 + //! Contact implementation methods for GoingsOnServer.
2 +
3 + use chrono::NaiveDate;
4 + use tracing::instrument;
5 +
6 + use goingson_core::{
7 + ContactId, ContactEmailId, ContactPhoneId,
8 + NewContact, UpdateContact, NewContactEmail, NewContactPhone,
9 + NewSocialHandle, NewContactCustomField,
10 + };
11 +
12 + use crate::state::DESKTOP_USER_ID;
13 +
14 + use super::GoingsOnServer;
15 + use super::results::ContactResult;
16 + use super::contact_params::*;
17 +
18 + impl GoingsOnServer {
19 + pub(crate) fn contact_to_result(contact: &goingson_core::Contact) -> ContactResult {
20 + ContactResult {
21 + id: contact.id.to_string(),
22 + display_name: contact.display_name.clone(),
23 + company: contact.company.clone(),
24 + title: contact.title.clone(),
25 + tags: contact.tags.clone(),
26 + primary_email: contact.primary_email().map(|s| s.to_string()),
27 + email_count: contact.email_count(),
28 + phone_count: contact.phones.len(),
29 + has_socials: contact.has_social(),
30 + }
31 + }
32 +
33 + #[instrument(skip_all)]
34 + pub(crate) async fn list_contacts_impl(
35 + &self,
36 + params: ListContactsParams,
37 + ) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
38 + let contacts = self.state.contacts.list_filtered(
39 + DESKTOP_USER_ID,
40 + params.search.as_deref(),
41 + params.tag.as_deref(),
42 + ).await?;
43 +
44 + if contacts.is_empty() {
45 + return Ok("No contacts found.".to_string());
46 + }
47 + let results: Vec<ContactResult> = contacts.iter().map(Self::contact_to_result).collect();
48 + Ok(serde_json::to_string_pretty(&results)?)
49 + }
50 +
51 + #[instrument(skip_all)]
52 + pub(crate) async fn create_contact_impl(
53 + &self,
54 + params: CreateContactParams,
55 + ) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
56 + if params.name.trim().is_empty() {
57 + return Err("Contact name is required".into());
58 + }
59 +
60 + let tags: Vec<String> = params.tags
61 + .as_deref()
62 + .map(|s| s.split(',').map(|t| t.trim().to_string()).filter(|t| !t.is_empty()).collect())
63 + .unwrap_or_default();
64 +
65 + let birthday = match &params.birthday {
66 + Some(s) if !s.is_empty() => Some(NaiveDate::parse_from_str(s, "%Y-%m-%d")?),
67 + _ => None,
68 + };
69 +
70 + let new_contact = NewContact {
71 + display_name: params.name,
72 + nickname: None,
73 + company: params.company,
74 + title: params.title,
75 + notes: params.notes.unwrap_or_default(),
76 + tags,
77 + birthday,
78 + timezone: params.timezone,
79 + };
80 +
81 + let contact = self.state.contacts.create(DESKTOP_USER_ID, new_contact).await?;
82 + let result = Self::contact_to_result(&contact);
83 + Ok(format!("Contact created:\n{}", serde_json::to_string_pretty(&result)?))
84 + }
85 +
86 + #[instrument(skip_all)]
87 + pub(crate) async fn get_contact_impl(
88 + &self,
89 + params: GetContactParams,
90 + ) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
91 + let id: ContactId = params.id.parse::<uuid::Uuid>()?.into();
92 + let contact = self.state.contacts.get_by_id(id, DESKTOP_USER_ID).await?
93 + .ok_or_else(|| format!("Contact {} not found", id))?;
94 +
95 + // Full detail response (not just the summary ContactResult)
96 + Ok(serde_json::to_string_pretty(&contact)?)
97 + }
98 +
99 + #[instrument(skip_all)]
100 + pub(crate) async fn update_contact_impl(
101 + &self,
102 + params: UpdateContactParams,
103 + ) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
104 + let id: ContactId = params.id.parse::<uuid::Uuid>()?.into();
105 + let existing = self.state.contacts.get_by_id(id, DESKTOP_USER_ID).await?
106 + .ok_or_else(|| format!("Contact {} not found", id))?;
107 +
108 + let display_name = params.name.unwrap_or(existing.display_name);
109 + let company = match &params.company {
110 + Some(s) if s.is_empty() => None,
111 + Some(s) => Some(s.clone()),
112 + None => existing.company,
113 + };
114 + let title = match &params.title {
115 + Some(s) if s.is_empty() => None,
116 + Some(s) => Some(s.clone()),
117 + None => existing.title,
118 + };
119 + let notes = params.notes.unwrap_or(existing.notes);
120 + let tags: Vec<String> = params.tags
121 + .as_deref()
122 + .map(|s| s.split(',').map(|t| t.trim().to_string()).filter(|t| !t.is_empty()).collect())
123 + .unwrap_or(existing.tags);
124 + let birthday = match &params.birthday {
125 + Some(s) if s.is_empty() => None,
126 + Some(s) => Some(NaiveDate::parse_from_str(s, "%Y-%m-%d")?),
127 + None => existing.birthday,
128 + };
129 + let timezone = match &params.timezone {
130 + Some(s) if s.is_empty() => None,
131 + Some(s) => Some(s.clone()),
132 + None => existing.timezone,
133 + };
134 +
135 + let update = UpdateContact {
136 + display_name,
137 + nickname: existing.nickname,
138 + company,
139 + title,
140 + notes,
141 + tags,
142 + birthday,
143 + timezone,
144 + };
145 +
146 + let contact = self.state.contacts.update(id, DESKTOP_USER_ID, update).await?
147 + .ok_or_else(|| format!("Contact {} not found", id))?;
148 + let result = Self::contact_to_result(&contact);
149 + Ok(format!("Contact updated:\n{}", serde_json::to_string_pretty(&result)?))
150 + }
151 +
152 + #[instrument(skip_all)]
153 + pub(crate) async fn delete_contact_impl(
154 + &self,
155 + params: DeleteContactParams,
156 + ) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
157 + let id: ContactId = params.id.parse::<uuid::Uuid>()?.into();
158 + let deleted = self.state.contacts.delete(id, DESKTOP_USER_ID).await?;
159 + if deleted {
160 + Ok(format!("Contact {} deleted.", id))
161 + } else {
162 + Ok(format!("Contact {} not found.", id))
163 + }
164 + }
165 +
166 + #[instrument(skip_all)]
167 + pub(crate) async fn add_contact_email_impl(
168 + &self,
169 + params: AddContactEmailParams,
170 + ) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
171 + let contact_id: ContactId = params.contact_id.parse::<uuid::Uuid>()?.into();
172 + let new_email = NewContactEmail {
173 + address: params.address,
174 + label: params.label.unwrap_or_default(),
175 + is_primary: params.is_primary.unwrap_or(false),
176 + };
177 + let email = self.state.contacts.add_email(contact_id, DESKTOP_USER_ID, new_email).await?;
178 + Ok(format!("Email added: {} ({})", email.address, email.label))
179 + }
180 +
181 + #[instrument(skip_all)]
182 + pub(crate) async fn remove_contact_email_impl(
183 + &self,
184 + params: RemoveContactSubItemParams,
185 + ) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
186 + let id: ContactEmailId = params.id.parse::<uuid::Uuid>()?.into();
187 + let deleted = self.state.contacts.remove_email(id, DESKTOP_USER_ID).await?;
188 + if deleted {
189 + Ok(format!("Email {} removed.", id))
190 + } else {
191 + Ok(format!("Email {} not found.", id))
192 + }
193 + }
194 +
195 + #[instrument(skip_all)]
196 + pub(crate) async fn add_contact_phone_impl(
197 + &self,
198 + params: AddContactPhoneParams,
199 + ) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
200 + let contact_id: ContactId = params.contact_id.parse::<uuid::Uuid>()?.into();
201 + let new_phone = NewContactPhone {
202 + number: params.number,
203 + label: params.label.unwrap_or_default(),
204 + is_primary: params.is_primary.unwrap_or(false),
205 + };
206 + let phone = self.state.contacts.add_phone(contact_id, DESKTOP_USER_ID, new_phone).await?;
207 + Ok(format!("Phone added: {} ({})", phone.number, phone.label))
208 + }
209 +
210 + #[instrument(skip_all)]
211 + pub(crate) async fn remove_contact_phone_impl(
212 + &self,
213 + params: RemoveContactSubItemParams,
214 + ) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
215 + let id: ContactPhoneId = params.id.parse::<uuid::Uuid>()?.into();
216 + let deleted = self.state.contacts.remove_phone(id, DESKTOP_USER_ID).await?;
217 + if deleted {
218 + Ok(format!("Phone {} removed.", id))
219 + } else {
220 + Ok(format!("Phone {} not found.", id))
221 + }
222 + }
223 +
224 + #[instrument(skip_all)]
225 + pub(crate) async fn add_contact_social_impl(
226 + &self,
227 + params: AddContactSocialParams,
228 + ) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
229 + let contact_id: ContactId = params.contact_id.parse::<uuid::Uuid>()?.into();
230 + let new_handle = NewSocialHandle {
231 + platform: params.platform,
232 + handle: params.handle,
233 + url: params.url,
234 + };
235 + let handle = self.state.contacts.add_social_handle(contact_id, DESKTOP_USER_ID, new_handle).await?;
236 + Ok(format!("Social handle added: {} on {}", handle.handle, handle.platform))
237 + }
238 +
239 + #[instrument(skip_all)]
240 + pub(crate) async fn add_contact_custom_field_impl(
241 + &self,
242 + params: AddContactCustomFieldParams,
243 + ) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
244 + let contact_id: ContactId = params.contact_id.parse::<uuid::Uuid>()?.into();
245 + let new_field = NewContactCustomField {
246 + label: params.label,
247 + value: params.value,
248 + url: params.url,
249 + };
250 + let field = self.state.contacts.add_custom_field(contact_id, DESKTOP_USER_ID, new_field).await?;
251 + Ok(format!("Custom field added: {}: {}", field.label, field.value))
252 + }
253 +
254 + /// Find a contact by display name (case-insensitive partial match).
255 + /// Used internally by task/event creation to resolve contact names.
256 + pub(crate) async fn find_contact_by_name(
257 + &self,
258 + name: &str,
259 + ) -> Result<Option<goingson_core::Contact>, Box<dyn std::error::Error + Send + Sync>> {
260 + let contacts = self.state.contacts.list_filtered(
261 + DESKTOP_USER_ID,
262 + Some(name),
263 + None,
264 + ).await?;
265 + Ok(contacts.into_iter().next())
266 + }
267 + }
@@ -0,0 +1,188 @@
1 + //! Parameter types for contact MCP tools.
2 +
3 + use schemars::JsonSchema;
4 + use serde::Deserialize;
5 +
6 + /// Parameters for listing contacts
7 + #[derive(Debug, Deserialize, JsonSchema)]
8 + pub struct ListContactsParams {
9 + /// Filter by tag (optional)
10 + #[schemars(description = "Filter by tag (optional, e.g. 'client', 'friend')")]
11 + pub tag: Option<String>,
12 +
13 + /// Search query (optional, matches name, company, notes)
14 + #[schemars(description = "Search contacts by name, company, or notes (optional)")]
15 + pub search: Option<String>,
16 + }
17 +
18 + /// Parameters for creating a contact
19 + #[derive(Debug, Deserialize, JsonSchema)]
20 + pub struct CreateContactParams {
21 + /// Display name (required)
22 + #[schemars(description = "Contact display name")]
23 + pub name: String,
24 +
25 + /// Company (optional)
26 + #[schemars(description = "Company or organization (optional)")]
27 + pub company: Option<String>,
28 +
29 + /// Title/role (optional)
30 + #[schemars(description = "Job title or role (optional)")]
31 + pub title: Option<String>,
32 +
33 + /// Notes (optional)
34 + #[schemars(description = "Free-text notes about the contact (optional)")]
35 + pub notes: Option<String>,
36 +
37 + /// Tags as comma-separated string (optional)
38 + #[schemars(description = "Comma-separated tags, e.g. 'client,engineering'")]
39 + pub tags: Option<String>,
40 +
41 + /// Birthday in YYYY-MM-DD format (optional)
42 + #[schemars(description = "Birthday in YYYY-MM-DD format (optional)")]
43 + pub birthday: Option<String>,
44 +
45 + /// Timezone (optional, IANA format e.g. America/New_York)
46 + #[schemars(description = "Timezone in IANA format, e.g. 'America/New_York' (optional)")]
47 + pub timezone: Option<String>,
48 + }
49 +
50 + /// Parameters for getting a contact
51 + #[derive(Debug, Deserialize, JsonSchema)]
52 + pub struct GetContactParams {
53 + /// Contact ID
54 + #[schemars(description = "Contact ID (UUID)")]
55 + pub id: String,
56 + }
57 +
58 + /// Parameters for updating a contact
59 + #[derive(Debug, Deserialize, JsonSchema)]
60 + pub struct UpdateContactParams {
61 + /// Contact ID to update
62 + #[schemars(description = "Contact ID (UUID) to update")]
63 + pub id: String,
64 +
65 + /// New display name (optional)
66 + #[schemars(description = "New display name (optional)")]
67 + pub name: Option<String>,
68 +
69 + /// New company (optional, empty string clears)
70 + #[schemars(description = "New company (optional, empty string clears)")]
71 + pub company: Option<String>,
72 +
73 + /// New title (optional, empty string clears)
74 + #[schemars(description = "New title (optional, empty string clears)")]
75 + pub title: Option<String>,
76 +
77 + /// New notes (optional)
78 + #[schemars(description = "New notes (optional)")]
79 + pub notes: Option<String>,
80 +
81 + /// New tags as comma-separated string (optional)
82 + #[schemars(description = "New comma-separated tags (optional)")]
83 + pub tags: Option<String>,
84 +
85 + /// New birthday (optional, empty string clears)
86 + #[schemars(description = "New birthday in YYYY-MM-DD format (optional, empty string clears)")]
87 + pub birthday: Option<String>,
88 +
89 + /// New timezone (optional, empty string clears)
90 + #[schemars(description = "New timezone in IANA format (optional, empty string clears)")]
91 + pub timezone: Option<String>,
92 + }
93 +
94 + /// Parameters for deleting a contact
95 + #[derive(Debug, Deserialize, JsonSchema)]
96 + pub struct DeleteContactParams {
97 + /// Contact ID
98 + #[schemars(description = "Contact ID (UUID) to delete")]
99 + pub id: String,
100 + }
101 +
102 + /// Parameters for adding an email to a contact
103 + #[derive(Debug, Deserialize, JsonSchema)]
104 + pub struct AddContactEmailParams {
105 + /// Contact ID
106 + #[schemars(description = "Contact ID (UUID) to add email to")]
107 + pub contact_id: String,
108 +
109 + /// Email address
110 + #[schemars(description = "Email address")]
111 + pub address: String,
112 +
113 + /// Label (e.g. 'work', 'personal')
114 + #[schemars(description = "Label for this email, e.g. 'work', 'personal'")]
115 + pub label: Option<String>,
116 +
117 + /// Whether this is the primary email
118 + #[schemars(description = "Set as primary email (default: false)")]
119 + pub is_primary: Option<bool>,
120 + }
121 +
122 + /// Parameters for adding a phone to a contact
123 + #[derive(Debug, Deserialize, JsonSchema)]
124 + pub struct AddContactPhoneParams {
125 + /// Contact ID
126 + #[schemars(description = "Contact ID (UUID) to add phone to")]
127 + pub contact_id: String,
128 +
129 + /// Phone number
130 + #[schemars(description = "Phone number")]
131 + pub number: String,
132 +
133 + /// Label (e.g. 'mobile', 'work')
134 + #[schemars(description = "Label for this phone, e.g. 'mobile', 'work'")]
135 + pub label: Option<String>,
136 +
137 + /// Whether this is the primary phone
138 + #[schemars(description = "Set as primary phone (default: false)")]
139 + pub is_primary: Option<bool>,
140 + }
141 +
142 + /// Parameters for adding a social handle to a contact
143 + #[derive(Debug, Deserialize, JsonSchema)]
144 + pub struct AddContactSocialParams {
145 + /// Contact ID
146 + #[schemars(description = "Contact ID (UUID) to add social handle to")]
147 + pub contact_id: String,
148 +
149 + /// Platform name (e.g. 'GitHub', 'Twitter', 'LinkedIn')
150 + #[schemars(description = "Platform name, e.g. 'GitHub', 'Twitter', 'LinkedIn'")]
151 + pub platform: String,
152 +
153 + /// Handle/username
154 + #[schemars(description = "Handle or username on the platform")]
155 + pub handle: String,
156 +
157 + /// Profile URL (optional)
158 + #[schemars(description = "Profile URL (optional)")]
159 + pub url: Option<String>,
160 + }
161 +
162 + /// Parameters for adding a custom field to a contact
163 + #[derive(Debug, Deserialize, JsonSchema)]
164 + pub struct AddContactCustomFieldParams {
165 + /// Contact ID
166 + #[schemars(description = "Contact ID (UUID) to add custom field to")]
167 + pub contact_id: String,
168 +
169 + /// Field label
170 + #[schemars(description = "Custom field label, e.g. 'Portfolio', 'Office Hours'")]
171 + pub label: String,
172 +
173 + /// Field value
174 + #[schemars(description = "Custom field value")]
175 + pub value: String,
176 +
177 + /// URL (optional, makes the field a link)
178 + #[schemars(description = "URL associated with the field (optional)")]
179 + pub url: Option<String>,
180 + }
181 +
182 + /// Parameters for removing a sub-collection item by ID
183 + #[derive(Debug, Deserialize, JsonSchema)]
184 + pub struct RemoveContactSubItemParams {
185 + /// Item ID to remove
186 + #[schemars(description = "ID (UUID) of the email, phone, social handle, or custom field to remove")]
187 + pub id: String,
188 + }
@@ -3,7 +3,7 @@
3 3 use chrono::{DateTime, Utc};
4 4 use tracing::instrument;
5 5
6 - use goingson_core::{EventId, NewEvent, Recurrence, UpdateEvent};
6 + use goingson_core::{BlockType, EventId, NewEvent, Recurrence, UpdateEvent};
7 7
8 8 use crate::state::DESKTOP_USER_ID;
9 9
@@ -56,6 +56,23 @@ impl GoingsOnServer {
56 56 .map(Recurrence::from_str_or_default)
57 57 .unwrap_or(Recurrence::None);
58 58
59 + // Look up contact by name
60 + let contact_id = match &params.contact {
61 + Some(name) => {
62 + let contact = self.find_contact_by_name(name).await?;
63 + match contact {
64 + Some(c) => Some(c.id),
65 + None => return Err(format!("Contact '{}' not found", name).into()),
66 + }
67 + }
68 + None => None,
69 + };
70 +
71 + // Parse block type
72 + let block_type = params.block_type
73 + .as_deref()
74 + .and_then(BlockType::from_str_opt);
75 +
59 76 let new_event = NewEvent {
60 77 user_id: Some(DESKTOP_USER_ID),
61 78 project_id,
@@ -66,8 +83,8 @@ impl GoingsOnServer {
66 83 location: params.location,
67 84 linked_task_id: None,
68 85 recurrence,
69 - contact_id: None,
70 - block_type: None,
86 + contact_id,
87 + block_type,
71 88 };
72 89
73 90 let event = self.state.events.create(DESKTOP_USER_ID, new_event).await?;
@@ -171,6 +188,26 @@ impl GoingsOnServer {
171 188 .map(Recurrence::from_str_or_default)
172 189 .unwrap_or(existing.recurrence);
173 190
191 + // Look up contact (empty string clears)
192 + let contact_id = match &params.contact {
193 + Some(name) if name.is_empty() => None,
194 + Some(name) => {
195 + let contact = self.find_contact_by_name(name).await?;
196 + match contact {
197 + Some(c) => Some(c.id),
198 + None => return Err(format!("Contact '{}' not found", name).into()),
199 + }
200 + }
201 + None => existing.contact_id,
202 + };
203 +
204 + // Parse block type (empty string clears)
205 + let block_type = match &params.block_type {
206 + Some(s) if s.is_empty() => None,
207 + Some(s) => BlockType::from_str_opt(s),
208 + None => existing.block_type,
209 + };
210 +
174 211 let updated_event = UpdateEvent {
175 212 project_id,
176 213 title,
@@ -180,8 +217,8 @@ impl GoingsOnServer {
180 217 location,
181 218 linked_task_id: existing.linked_task_id,
182 219 recurrence,
183 - contact_id: None,
184 - block_type: None,
220 + contact_id,
221 + block_type,
185 222 };
186 223
187 224 let event = self.state.events.update(id, DESKTOP_USER_ID, updated_event).await?
@@ -33,6 +33,14 @@ pub struct CreateEventParams {
33 33 /// Recurrence (optional)
34 34 #[schemars(description = "Recurrence: Daily, Weekly, Monthly, or None (default: None)")]
35 35 pub recurrence: Option<String>,
36 +
37 + /// Contact name to link (optional, partial match)
38 + #[schemars(description = "Contact name to link to event (optional, partial match)")]
39 + pub contact: Option<String>,
40 +
41 + /// Block type (optional: Focus, Personal, Vacation)
42 + #[schemars(description = "Block type: Focus, Personal, or Vacation (optional)")]
43 + pub block_type: Option<String>,
36 44 }
37 45
38 46 /// Parameters for listing events
@@ -97,6 +105,14 @@ pub struct UpdateEventParams {
97 105 /// New recurrence (optional)
98 106 #[schemars(description = "New recurrence: Daily, Weekly, Monthly, or None (optional)")]
99 107 pub recurrence: Option<String>,
108 +
109 + /// New contact name (optional, empty string clears)
110 + #[schemars(description = "New contact name (optional, empty string clears)")]
111 + pub contact: Option<String>,
112 +
113 + /// New block type (optional, empty string clears)
114 + #[schemars(description = "New block type: Focus, Personal, Vacation, or empty string to clear (optional)")]
115 + pub block_type: Option<String>,
100 116 }
101 117
102 118 /// Parameters for deleting an event
@@ -1,6 +1,6 @@
1 1 //! MCP tool definitions for GoingsOn task management.
2 2 //!
3 - //! Provides 36 tools for full task, project, event, and dashboard management.
3 + //! Provides 47 tools for full task, project, event, contact, and dashboard management.
4 4 //!
5 5 //! This module is split into sub-modules by domain:
6 6 //! - `results` - All response/result structs
@@ -8,6 +8,7 @@
8 8 //! - `project_params` / `project_impl` - Project tools
9 9 //! - `event_params` / `event_impl` - Event tools
10 10 //! - `milestone_params` / `milestone_impl` - Milestone tools
11 + //! - `contact_params` / `contact_impl` - Contact tools
11 12 //! - `utility_params` / `utility_impl` - Search, context, roadmap, dashboard tools
12 13
13 14 mod results;
@@ -19,6 +20,8 @@ mod event_params;
19 20 mod event_impl;
20 21 mod milestone_params;
21 22 mod milestone_impl;
23 + mod contact_params;
24 + mod contact_impl;
22 25 mod utility_params;
23 26 mod utility_impl;
24 27
@@ -27,6 +30,7 @@ pub use task_params::*;
27 30 pub use project_params::*;
28 31 pub use event_params::*;
29 32 pub use milestone_params::*;
33 + pub use contact_params::*;
30 34 pub use utility_params::*;
31 35
32 36 use rmcp::tool;
@@ -67,7 +71,7 @@ impl GoingsOnServer {
67 71 }
68 72
69 73 /// Create a new task in GoingsOn.
70 - #[tool(description = "Create a new task in GoingsOn. Provide description and optionally priority (high/medium/low), due date (ISO 8601), project name, and tags.")]
74 + #[tool(description = "Create a new task in GoingsOn. Provide description and optionally priority (high/medium/low), due date (ISO 8601), project name, tags, contact name, and milestone name.")]
71 75 pub async fn create_task(
72 76 &self,
73 77 #[tool(aggr)] params: CreateTaskParams,
@@ -79,7 +83,7 @@ impl GoingsOnServer {
79 83 }
80 84
81 85 /// Update an existing task in GoingsOn.
82 - #[tool(description = "Update an existing task in GoingsOn. Provide task ID and the fields to update (description, priority, due, project, tags, status).")]
86 + #[tool(description = "Update an existing task in GoingsOn. Provide task ID and fields to update (description, priority, due, project, tags, status, contact, milestone).")]
83 87 pub async fn update_task(
84 88 &self,
85 89 #[tool(aggr)] params: UpdateTaskParams,
@@ -362,7 +366,7 @@ impl GoingsOnServer {
362 366 // --- Event Tools ---
363 367
364 368 /// Create a new event.
365 - #[tool(description = "Create a new calendar event. Provide title, start_time (ISO 8601), and optionally end_time, description, location, project name, and recurrence.")]
369 + #[tool(description = "Create a new calendar event. Provide title, start_time (ISO 8601), and optionally end_time, description, location, project name, recurrence, contact name, and block_type (Focus/Personal/Vacation).")]
366 370 pub async fn create_event(
367 371 &self,
368 372 #[tool(aggr)] params: CreateEventParams,
@@ -410,7 +414,7 @@ impl GoingsOnServer {
410 414 }
411 415
412 416 /// Update an existing event.
413 - #[tool(description = "Update an existing event. Provide event ID and fields to update (title, start_time, end_time, description, location, project, recurrence).")]
417 + #[tool(description = "Update an existing event. Provide event ID and fields to update (title, start_time, end_time, description, location, project, recurrence, contact, block_type).")]
414 418 pub async fn update_event(
415 419 &self,
416 420 #[tool(aggr)] params: UpdateEventParams,
@@ -483,6 +487,140 @@ impl GoingsOnServer {
483 487 }
484 488 }
485 489
490 + // --- Contact Tools ---
491 +
492 + /// List contacts.
493 + #[tool(description = "List contacts, optionally filtered by tag or search query (matches name, company, notes).")]
494 + pub async fn list_contacts(
495 + &self,
496 + #[tool(aggr)] params: ListContactsParams,
497 + ) -> String {
498 + match self.list_contacts_impl(params).await {
499 + Ok(result) => result,
500 + Err(e) => format!("Error listing contacts: {}", e),
501 + }
502 + }
503 +
504 + /// Create a new contact.
505 + #[tool(description = "Create a new contact. Provide name and optionally company, title, notes, tags, birthday (YYYY-MM-DD), and timezone (IANA format).")]
506 + pub async fn create_contact(
507 + &self,
508 + #[tool(aggr)] params: CreateContactParams,
509 + ) -> String {
510 + match self.create_contact_impl(params).await {
511 + Ok(result) => result,
512 + Err(e) => format!("Error creating contact: {}", e),
513 + }
514 + }
515 +
516 + /// Get a contact by ID.
517 + #[tool(description = "Get a contact by its ID. Returns full details including emails, phones, social handles, and custom fields.")]
518 + pub async fn get_contact(
519 + &self,
520 + #[tool(aggr)] params: GetContactParams,
521 + ) -> String {
522 + match self.get_contact_impl(params).await {
523 + Ok(result) => result,
524 + Err(e) => format!("Error getting contact: {}", e),
525 + }
526 + }
527 +
528 + /// Update a contact.
529 + #[tool(description = "Update a contact. Provide contact ID and fields to update (name, company, title, notes, tags, birthday, timezone).")]
530 + pub async fn update_contact(
531 + &self,
532 + #[tool(aggr)] params: UpdateContactParams,
533 + ) -> String {
534 + match self.update_contact_impl(params).await {
535 + Ok(result) => result,
536 + Err(e) => format!("Error updating contact: {}", e),
537 + }
538 + }
539 +
540 + /// Delete a contact.
541 + #[tool(description = "Delete a contact by its ID. Tasks/events linked to this contact will have their contact cleared.")]
542 + pub async fn delete_contact(
543 + &self,
544 + #[tool(aggr)] params: DeleteContactParams,
545 + ) -> String {
546 + match self.delete_contact_impl(params).await {
547 + Ok(result) => result,
548 + Err(e) => format!("Error deleting contact: {}", e),
549 + }
550 + }
551 +
552 + /// Add an email to a contact.
553 + #[tool(description = "Add an email address to a contact. Provide contact_id, address, and optionally label (e.g. 'work') and is_primary flag.")]
554 + pub async fn add_contact_email(
555 + &self,
556 + #[tool(aggr)] params: AddContactEmailParams,
557 + ) -> String {
558 + match self.add_contact_email_impl(params).await {
559 + Ok(result) => result,
560 + Err(e) => format!("Error adding email: {}", e),
561 + }
562 + }
563 +
564 + /// Remove an email from a contact.
565 + #[tool(description = "Remove an email address from a contact by its email ID.")]
566 + pub async fn remove_contact_email(
567 + &self,
568 + #[tool(aggr)] params: RemoveContactSubItemParams,
569 + ) -> String {
570 + match self.remove_contact_email_impl(params).await {
571 + Ok(result) => result,
572 + Err(e) => format!("Error removing email: {}", e),
573 + }
574 + }
575 +
576 + /// Add a phone number to a contact.
577 + #[tool(description = "Add a phone number to a contact. Provide contact_id, number, and optionally label (e.g. 'mobile') and is_primary flag.")]
578 + pub async fn add_contact_phone(
579 + &self,
580 + #[tool(aggr)] params: AddContactPhoneParams,
581 + ) -> String {
582 + match self.add_contact_phone_impl(params).await {
583 + Ok(result) => result,
584 + Err(e) => format!("Error adding phone: {}", e),
585 + }
586 + }
587 +
588 + /// Remove a phone number from a contact.
589 + #[tool(description = "Remove a phone number from a contact by its phone ID.")]
590 + pub async fn remove_contact_phone(
591 + &self,
592 + #[tool(aggr)] params: RemoveContactSubItemParams,
593 + ) -> String {
594 + match self.remove_contact_phone_impl(params).await {
595 + Ok(result) => result,
596 + Err(e) => format!("Error removing phone: {}", e),
597 + }
598 + }
599 +
600 + /// Add a social handle to a contact.
601 + #[tool(description = "Add a social media handle to a contact. Provide contact_id, platform (e.g. 'GitHub'), handle, and optionally profile URL.")]
602 + pub async fn add_contact_social(
603 + &self,
604 + #[tool(aggr)] params: AddContactSocialParams,
605 + ) -> String {
606 + match self.add_contact_social_impl(params).await {
607 + Ok(result) => result,
608 + Err(e) => format!("Error adding social handle: {}", e),
609 + }
610 + }
611 +
612 + /// Add a custom field to a contact.
613 + #[tool(description = "Add a custom field to a contact. Provide contact_id, label, value, and optionally a URL.")]
614 + pub async fn add_contact_custom_field(
615 + &self,
616 + #[tool(aggr)] params: AddContactCustomFieldParams,
617 + ) -> String {
618 + match self.add_contact_custom_field_impl(params).await {
619 + Ok(result) => result,
620 + Err(e) => format!("Error adding custom field: {}", e),
621 + }
622 + }
623 +
486 624 // --- Utility Tools ---
487 625
488 626 /// Search across tasks, projects, emails, and events.
@@ -550,6 +688,9 @@ impl ServerHandler for GoingsOnServer {
550 688 Projects: list_projects, get_project, create_project, update_project, delete_project. \
551 689 Milestones: list_milestones, create_milestone, update_milestone, delete_milestone. \
552 690 Events: list_events, list_upcoming_events, get_event, create_event, update_event, delete_event. \
691 + Contacts: list_contacts, get_contact, create_contact, update_contact, delete_contact, \
692 + add_contact_email, remove_contact_email, add_contact_phone, remove_contact_phone, \
693 + add_contact_social, add_contact_custom_field. \
553 694 Utility: search, export_roadmap."
554 695 .into(),
555 696 ),
@@ -85,6 +85,20 @@ pub struct MilestoneResult {
85 85 pub progress: f32,
86 86 }
87 87
88 + /// Response for a single contact (summary)
89 + #[derive(Debug, Clone, Serialize)]
90 + pub struct ContactResult {
91 + pub id: String,
92 + pub display_name: String,
93 + pub company: Option<String>,
94 + pub title: Option<String>,
95 + pub tags: Vec<String>,
96 + pub primary_email: Option<String>,
97 + pub email_count: usize,
98 + pub phone_count: usize,
99 + pub has_socials: bool,
100 + }
101 +
88 102 /// Response for dashboard statistics
89 103 #[derive(Debug, Clone, Serialize)]
90 104 pub struct DashboardStatsResult {
@@ -4,8 +4,8 @@ use chrono::{DateTime, Duration, Local, NaiveTime, Utc, Weekday, Datelike, TimeZ
4 4 use tracing::instrument;
5 5
6 6 use goingson_core::{
7 - AnnotationId, MilestoneStatus, NewTask, Priority, Recurrence, SubtaskId, TaskId, TaskStatus,
8 - UpdateTask, Validate, calculate_next_due, calculate_urgency, should_recur,
7 + AnnotationId, MilestoneId, MilestoneStatus, NewTask, Priority, Recurrence, SubtaskId, TaskId,
8 + TaskStatus, UpdateTask, Validate, calculate_next_due, calculate_urgency, should_recur,
9 9 };
10 10
11 11 use crate::state::DESKTOP_USER_ID;
@@ -130,6 +130,33 @@ impl GoingsOnServer {
130 130 .map(|s| s.split(',').map(|t| t.trim().to_string()).filter(|t| !t.is_empty()).collect())
131 131 .unwrap_or_default();
132 132
133 + // Look up contact by name
134 + let contact_id = match &params.contact {
135 + Some(name) => {
136 + let contact = self.find_contact_by_name(name).await?;
137 + match contact {
138 + Some(c) => Some(c.id),
139 + None => return Err(format!("Contact '{}' not found", name).into()),
140 + }
141 + }
142 + None => None,
143 + };
144 +
145 + // Look up milestone by name (requires project_id)
146 + let milestone_id = match &params.milestone {
147 + Some(ms_name) => {
148 + let pid = project_id.ok_or("Milestone requires a project")?;
149 + let milestones = self.state.milestones.list_by_project(pid, DESKTOP_USER_ID).await?;
150 + let ms = milestones.into_iter()
151 + .find(|m| m.name.to_lowercase() == ms_name.to_lowercase());
152 + match ms {
153 + Some(m) => Some(m.id),
154 + None => return Err(format!("Milestone '{}' not found in project", ms_name).into()),
155 + }
156 + }
157 + None => None,
158 + };
159 +
133 160 // Calculate urgency
134 161 let created_at = Utc::now();
135 162 let urgency = calculate_urgency(
@@ -152,8 +179,8 @@ impl GoingsOnServer {
152 179 source_email_id: None,
153 180 scheduled_start: None,
154 181 scheduled_duration: None,
155 - contact_id: None,
156 - milestone_id: None,
182 + contact_id,
183 + milestone_id,
157 184 };
158 185
159 186 new_task.validate()?;
@@ -215,13 +242,42 @@ impl GoingsOnServer {
215 242 .map(|s| s.split(',').map(|t| t.trim().to_string()).filter(|t| !t.is_empty()).collect())
216 243 .unwrap_or(existing.tags);
217 244
245 + // Look up contact (empty string clears)
246 + let contact_id = match &params.contact {
247 + Some(name) if name.is_empty() => None,
248 + Some(name) => {
249 + let contact = self.find_contact_by_name(name).await?;
250 + match contact {
251 + Some(c) => Some(c.id),
252 + None => return Err(format!("Contact '{}' not found", name).into()),
253 + }
254 + }
255 + None => existing.contact_id,
256 + };
257 +
258 + // Look up milestone (empty string clears)
259 + let milestone_id: Option<MilestoneId> = match &params.milestone {
260 + Some(name) if name.is_empty() => None,
261 + Some(ms_name) => {
262 + let pid = project_id.ok_or("Milestone requires a project")?;
263 + let milestones = self.state.milestones.list_by_project(pid, DESKTOP_USER_ID).await?;
264 + let ms = milestones.into_iter()
265 + .find(|m| m.name.to_lowercase() == ms_name.to_lowercase());
266 + match ms {
267 + Some(m) => Some(m.id),
268 + None => return Err(format!("Milestone '{}' not found in project", ms_name).into()),
269 + }
270 + }
271 + None => existing.milestone_id,
272 + };
273 +
218 274 // Calculate urgency
219 275 let urgency = calculate_urgency(&priority, &status, due.as_ref(), &existing.created_at, &tags);
220 276
221 277 let update = UpdateTask {
222 278 project_id,
223 - contact_id: existing.contact_id,
224 - milestone_id: existing.milestone_id,
279 + contact_id,
280 + milestone_id,
225 281 description,
226 282 status,
227 283 priority,
@@ -41,6 +41,14 @@ pub struct CreateTaskParams {
41 41 /// Tags as comma-separated string (optional)
42 42 #[schemars(description = "Comma-separated tags, e.g. 'urgent,code-review'")]
43 43 pub tags: Option<String>,
44 +
45 + /// Contact name to link (optional, partial match)
46 + #[schemars(description = "Contact name to link to task (optional, partial match)")]
47 + pub contact: Option<String>,
48 +
49 + /// Milestone name to assign (optional, must exist in the task's project)
50 + #[schemars(description = "Milestone name to assign task to (optional)")]
51 + pub milestone: Option<String>,
44 52 }
45 53
46 54 /// Parameters for updating a task
@@ -73,6 +81,14 @@ pub struct UpdateTaskParams {
73 81 /// New status: pending, started, or completed (optional)
74 82 #[schemars(description = "New status: pending, started, or completed (optional)")]
75 83 pub status: Option<String>,
84 +
85 + /// New contact name (optional, empty string clears)
86 + #[schemars(description = "New contact name (optional, empty string clears)")]
87 + pub contact: Option<String>,
88 +
89 + /// New milestone name (optional, empty string clears)
90 + #[schemars(description = "New milestone name (optional, empty string clears)")]
91 + pub milestone: Option<String>,
76 92 }
77 93
78 94 /// Parameters for completing a task
@@ -3,8 +3,9 @@
3 3 use std::sync::Arc;
4 4
5 5 use goingson_db_sqlite::{
6 - init_pool, run_migrations, SqliteEventRepository, SqliteMilestoneRepository,
7 - SqliteProjectRepository, SqliteStatsRepository, SqliteTaskRepository,
6 + init_pool, run_migrations, SqliteContactRepository, SqliteEventRepository,
7 + SqliteMilestoneRepository, SqliteProjectRepository, SqliteStatsRepository,
8 + SqliteTaskRepository,
8 9 };
9 10 use goingson_mcp::tools::GoingsOnServer;
10 11 use goingson_mcp::state::McpState;
@@ -51,6 +52,7 @@ pub async fn setup_mcp_server() -> GoingsOnServer {
51 52 projects: Arc::new(SqliteProjectRepository::new(pool.clone())),
52 53 events: Arc::new(SqliteEventRepository::new(pool.clone())),
53 54 milestones: Arc::new(SqliteMilestoneRepository::new(pool.clone())),
55 + contacts: Arc::new(SqliteContactRepository::new(pool.clone())),
54 56 stats: Arc::new(SqliteStatsRepository::new(pool)),
55 57 };
56 58
@@ -36,6 +36,8 @@ async fn create_and_list_task() {
36 36 due: None,
37 37 project: None,
38 38 tags: None,
39 + contact: None,
40 + milestone: None,
39 41 })
40 42 .await;
41 43
@@ -68,6 +70,8 @@ async fn create_task_with_priority() {
68 70 due: None,
69 71 project: None,
70 72 tags: None,
73 + contact: None,
74 + milestone: None,
71 75 })
72 76 .await;
73 77
@@ -88,6 +92,8 @@ async fn complete_task() {
88 92 due: None,
89 93 project: None,
90 94 tags: None,
95 + contact: None,
96 + milestone: None,
91 97 })
92 98 .await;
93 99
@@ -118,6 +124,8 @@ async fn snooze_task_with_datetime() {
118 124 due: None,
119 125 project: None,
120 126 tags: None,
127 + contact: None,
128 + milestone: None,
121 129 })
122 130 .await;
123 131
@@ -156,6 +164,8 @@ async fn update_task() {
156 164 due: None,
157 165 project: None,
158 166 tags: None,
167 + contact: None,
168 + milestone: None,
159 169 })
160 170 .await;
161 171
@@ -170,6 +180,8 @@ async fn update_task() {
170 180 project: None,
171 181 tags: None,
172 182 status: None,
183 + contact: None,
184 + milestone: None,
173 185 })
174 186 .await;
175 187
@@ -213,6 +225,8 @@ async fn list_tasks_with_project_filter() {
213 225 due: None,
214 226 project: Some("TestProject".to_string()),
215 227 tags: None,
228 + contact: None,
229 + milestone: None,
216 230 })
217 231 .await;
218 232
@@ -224,6 +238,8 @@ async fn list_tasks_with_project_filter() {
224 238 due: None,
225 239 project: None,
226 240 tags: None,
241 + contact: None,
242 + milestone: None,
227 243 })
228 244 .await;
229 245
@@ -251,6 +267,8 @@ async fn create_task_empty_description_fails() {
251 267 due: None,
252 268 project: None,
253 269 tags: None,
270 + contact: None,
271 + milestone: None,
254 272 })
255 273 .await;
256 274
@@ -367,6 +385,8 @@ async fn create_and_list_event() {
367 385 location: Some("Room A".to_string()),
368 386 project: None,
369 387 recurrence: None,
388 + contact: None,
389 + block_type: None,
370 390 })
371 391 .await;
372 392
@@ -400,6 +420,8 @@ async fn create_event_with_recurrence() {
400 420 location: None,
401 421 project: None,
402 422 recurrence: Some("Weekly".to_string()),
423 + contact: None,
424 + block_type: None,
403 425 })
404 426 .await;
405 427
@@ -420,6 +442,8 @@ async fn update_event() {
420 442 location: None,
421 443 project: None,
422 444 recurrence: None,
445 + contact: None,
446 + block_type: None,
423 447 })
424 448 .await;
425 449
@@ -435,6 +459,8 @@ async fn update_event() {
435 459 location: Some("Office".to_string()),
436 460 project: None,
437 461 recurrence: None,
462 + contact: None,
463 + block_type: None,
438 464 })
439 465 .await;
440 466
@@ -455,6 +481,8 @@ async fn delete_event() {
455 481 location: None,
456 482 project: None,
457 483 recurrence: None,
484 + contact: None,
485 + block_type: None,
458 486 })
459 487 .await;
460 488
@@ -496,6 +524,8 @@ async fn search_tasks() {
496 524 due: None,
497 525 project: None,
498 526 tags: None,
527 + contact: None,
528 + milestone: None,
499 529 })
500 530 .await;
501 531
@@ -506,6 +536,8 @@ async fn search_tasks() {
506 536 due: None,
507 537 project: None,
508 538 tags: None,
539 + contact: None,
540 + milestone: None,
509 541 })
510 542 .await;
511 543
@@ -533,6 +565,8 @@ async fn get_context_structure() {
533 565 due: None,
534 566 project: None,
535 567 tags: None,
568 + contact: None,
569 + milestone: None,
536 570 })
537 571 .await;
538 572
@@ -572,6 +606,8 @@ async fn export_roadmap_markdown() {
572 606 due: None,
573 607 project: Some("Roadmap Project".to_string()),
574 608 tags: None,
609 + contact: None,
610 + milestone: None,
575 611 })
576 612 .await;
577 613
@@ -599,6 +635,8 @@ async fn dashboard_stats() {
599 635 due: None,
600 636 project: None,
601 637 tags: None,
638 + contact: None,
639 + milestone: None,
602 640 })
603 641 .await;
604 642
@@ -638,6 +676,8 @@ async fn mcp_create_task_empty_title_fails() {
638 676 due: None,
639 677 project: None,
640 678 tags: None,
679 + contact: None,
680 + milestone: None,
641 681 })
642 682 .await;
643 683
@@ -659,6 +699,8 @@ async fn mcp_create_task_whitespace_only_title_fails() {
659 699 due: None,
660 700 project: None,
661 701 tags: None,
702 + contact: None,
703 + milestone: None,
662 704 })
663 705 .await;
664 706
@@ -680,6 +722,8 @@ async fn mcp_create_task_valid_data_succeeds() {
680 722 due: None,
681 723 project: None,
682 724 tags: Some("work,important".to_string()),
725 + contact: None,
726 + milestone: None,
683 727 })
684 728 .await;
685 729
@@ -705,6 +749,8 @@ async fn mcp_create_task_description_too_long_fails() {
705 749 due: None,
706 750 project: None,
707 751 tags: None,
752 + contact: None,
753 + milestone: None,
708 754 })
709 755 .await;
710 756
@@ -727,6 +773,8 @@ async fn mcp_snooze_completed_task_fails() {
727 773 due: None,
728 774 project: None,
729 775 tags: None,
776 + contact: None,
777 + milestone: None,
730 778 })
731 779 .await;
732 780
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "goingson-desktop"
3 - version = "0.2.1"
3 + version = "0.3.0"
4 4 edition = "2021"
5 5
6 6 [[bin]]
@@ -92,6 +92,7 @@ objc2-foundation = { version = "0.2", features = ["NSData", "NSThread"] }
92 92 tauri-plugin-shell = { workspace = true }
93 93 tauri-plugin-notification = { workspace = true }
94 94 tauri-plugin-window-state = { workspace = true }
95 + tauri-plugin-updater = { workspace = true }
95 96 # File watching (for external DB changes from MCP server)
96 97 notify = { workspace = true }
97 98 notify-debouncer-mini = { workspace = true }
@@ -115,6 +115,13 @@
115 115 --border-width: 3px;
116 116 --border-width-sm: 2px;
117 117
118 + /* Aliases for CSS that references these names */
119 + --accent-color: var(--accent-blue);
120 + --accent-primary: var(--accent-blue);
121 + --bg-hover: var(--bg-tertiary);
122 + --border-light: var(--bg-tertiary);
123 + --text-on-accent: var(--bg-card);
124 +
118 125 /* Shadow offset scale */
119 126 --shadow-offset-xs: 1px;
120 127 --shadow-offset-sm: 2px;
@@ -188,23 +195,23 @@
188 195 --texture-paper: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.03'/%3E%3C/svg%3E");
189 196
190 197 /* Emboss/deboss shadows for text and elements */
191 - --emboss-light: 1px 1px 0 rgba(255, 255, 255, 0.5);
192 - --emboss-dark: -1px -1px 0 rgba(0, 0, 0, 0.08);
193 - --deboss: inset 1px 1px 2px rgba(0, 0, 0, 0.08), inset -1px -1px 0 rgba(255, 255, 255, 0.5);
198 + --emboss-light: 1px 1px 0 color-mix(in srgb, var(--bg-card) 50%, transparent);
199 + --emboss-dark: -1px -1px 0 color-mix(in srgb, var(--text-primary) 8%, transparent);
200 + --deboss: inset 1px 1px 2px color-mix(in srgb, var(--text-primary) 8%, transparent), inset -1px -1px 0 color-mix(in srgb, var(--bg-card) 50%, transparent);
194 201
195 202 /* Modal overlay */
196 - --overlay-color: rgba(27, 54, 93, 0.6);
203 + --overlay-color: color-mix(in srgb, var(--text-primary) 60%, transparent);
197 204
198 205 /* Button gradients for tactile feel */
199 206 --btn-gradient: linear-gradient(to bottom,
200 - rgba(255, 255, 255, 0.15) 0%,
207 + color-mix(in srgb, var(--bg-card) 15%, transparent) 0%,
201 208 transparent 50%,
202 - rgba(0, 0, 0, 0.05) 100%
209 + color-mix(in srgb, var(--text-primary) 5%, transparent) 100%
203 210 );
204 211 --btn-gradient-pressed: linear-gradient(to bottom,
205 - rgba(0, 0, 0, 0.05) 0%,
212 + color-mix(in srgb, var(--text-primary) 5%, transparent) 0%,
206 213 transparent 50%,
207 - rgba(255, 255, 255, 0.1) 100%
214 + color-mix(in srgb, var(--bg-card) 10%, transparent) 100%
208 215 );
209 216 }
210 217
@@ -237,6 +244,26 @@ html {
237 244 .border-thick { border-width: 3px; }
238 245 .border-medium { border-width: 2px; }
239 246
247 + /* === LAYOUT UTILITIES === */
248 + .flex-1 { flex: 1; }
249 + .flex-center-gap { display: flex; align-items: center; gap: 0.5rem; }
250 + .text-sm-secondary { font-size: 0.875rem; color: var(--text-secondary); }
251 + .text-xs-secondary { font-size: 0.75rem; color: var(--text-secondary); }
252 + .text-accent-red { color: var(--accent-red); }
253 + .mb-1 { margin-bottom: 1rem; }
254 +
255 + /* === SETTINGS UTILITIES === */
256 + .settings-divider { margin-top: 1.5rem; padding-top: 1.5rem; border-top: 2px solid var(--border-color); }
257 + .settings-heading { margin-bottom: 1rem; font-family: var(--font-heading); }
258 + .settings-desc { font-size: 0.875rem; color: var(--text-secondary); margin-bottom: 1rem; }
259 +
260 + /* === SUBTASK UTILITIES === */
261 + .subtask-item { display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem; background: var(--bg-secondary); border-radius: 4px; margin-bottom: 0.5rem; }
262 + .subtask-item-linked { display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem; background: var(--bg-tertiary); border-radius: 4px; margin-bottom: 0.5rem; border-left: 3px solid var(--accent-color); }
263 + .subtask-checkbox { cursor: pointer; width: 18px; height: 18px; }
264 + .subtask-checkbox-disabled { cursor: not-allowed; width: 18px; height: 18px; opacity: 0.5; }
265 + .subtask-text-done { text-decoration: line-through; opacity: 0.6; }
266 +
240 267 /* ===================================================================
241 268 5. Body & App Shell
242 269 =================================================================== */
@@ -432,7 +459,7 @@ body {
432 459
433 460 .btn-danger {
434 461 background-color: var(--accent-red);
435 - color: white;
462 + color: var(--text-on-accent);
436 463 }
437 464
438 465 .btn-danger:hover {
@@ -577,6 +604,28 @@ body {
577 604 border-color: var(--text-muted);
578 605 }
579 606
607 + /* Project status tag colors */
608 + .tag.status-active {
609 + background-color: color-mix(in srgb, var(--accent-green) 20%, var(--bg-card));
610 + border-color: var(--accent-green);
611 + }
612 + .tag.status-onhold, .tag.status-on_hold {
613 + background-color: color-mix(in srgb, var(--accent-yellow) 20%, var(--bg-card));
614 + border-color: var(--accent-yellow);
615 + }
616 + .tag.status-archived {
617 + background-color: var(--bg-tertiary);
618 + border-color: var(--text-muted);
619 + }
620 + .tag.status-inactive {
621 + background-color: color-mix(in srgb, var(--accent-red) 20%, var(--bg-card));
622 + border-color: var(--accent-red);
623 + }
624 + .tag.status-completed {
625 + background-color: color-mix(in srgb, var(--accent-cyan) 20%, var(--bg-card));
626 + border-color: var(--accent-cyan);
627 + }
628 +
580 629 /* ===================================================================
581 630 13. Data Table
582 631 =================================================================== */
@@ -994,7 +1043,7 @@ body {
994 1043 display: inline-block;
995 1044 padding: 0.15rem 0.4rem;
996 1045 background: var(--accent-green);
997 - color: white;
1046 + color: var(--text-on-accent);
998 1047 font-size: 0.7rem;
999 1048 font-weight: 700;
1000 1049 text-transform: uppercase;
@@ -1093,7 +1142,7 @@ body {
1093 1142
1094 1143 .past-events-count {
1095 1144 background: var(--text-muted);
1096 - color: white;
1145 + color: var(--text-on-accent);
1097 1146 font-size: 0.75rem;
1098 1147 padding: 0.15rem 0.5rem;
1099 1148 border-radius: var(--radius-sm);
@@ -1134,7 +1183,7 @@ body {
1134 1183 padding: 0.75rem;
1135 1184 background-color: var(--accent-green);
1136 1185 border-radius: var(--radius-sm);
1137 - color: white;
1186 + color: var(--text-on-accent);
1138 1187 }
1139 1188
1140 1189 .event-date-day {
@@ -2225,7 +2274,7 @@ kbd {
2225 2274
2226 2275 .context-menu-item--danger:hover {
2227 2276 background: var(--accent-red);
2228 - color: white;
2277 + color: var(--text-on-accent);
2229 2278 }
2230 2279
2231 2280 .context-menu-separator {
@@ -2469,12 +2518,12 @@ kbd {
2469 2518
2470 2519 .task-badge.has-items {
2471 2520 background: var(--accent-blue);
2472 - color: white;
2521 + color: var(--text-on-accent);
2473 2522 }
2474 2523
2475 2524 .task-badge.recurrence {
2476 2525 background: var(--accent-purple);
2477 - color: white;
2526 + color: var(--text-on-accent);
2478 2527 }
2479 2528
2480 2529 /* Clickable task rows */
@@ -2684,7 +2733,7 @@ kbd {
2684 2733
2685 2734 .timeline-item.event {
2686 2735 background: var(--accent-blue);
2687 - color: white;
2736 + color: var(--text-on-accent);
2688 2737 }
2689 2738
2690 2739 .timeline-item.block {
@@ -2703,12 +2752,12 @@ kbd {
2703 2752
2704 2753 .timeline-item.block-vacation {
2705 2754 background: var(--accent-purple);
2706 - color: white;
2755 + color: var(--text-on-accent);
2707 2756 }
2708 2757
2709 2758 .timeline-item.block-focus {
2710 2759 background: var(--accent-red);
2711 - color: white;
2760 + color: var(--text-on-accent);
2712 2761 }
2713 2762
2714 2763 .timeline-item.conflict {
@@ -2907,7 +2956,7 @@ kbd {
2907 2956
2908 2957 .llm-test-result.error {
2909 2958 background: var(--accent-red);
2910 - color: white;
2959 + color: var(--text-on-accent);
2911 2960 }
2912 2961
2913 2962 .llm-cache-info {
@@ -3168,7 +3217,7 @@ kbd {
3168 3217 padding: 0.75rem;
3169 3218 background: var(--accent-red);
3170 3219 border: var(--border-width) solid var(--border-color);
3171 - color: white;
3220 + color: var(--text-on-accent);
3172 3221 font-size: 0.875rem;
3173 3222 font-weight: 600;
3174 3223 margin-top: 0.5rem;
@@ -3364,8 +3413,8 @@ kbd {
3364 3413 height: 40px;
3365 3414 min-width: 40px;
3366 3415 border-radius: 50%;
3367 - background-color: var(--accent-blue, #4A90D9);
3368 - color: #fff;
3416 + background-color: var(--accent-blue);
3417 + color: var(--text-on-accent);
3369 3418 display: flex;
3370 3419 align-items: center;
3371 3420 justify-content: center;
@@ -3634,7 +3683,7 @@ kbd {
3634 3683
3635 3684 .review-status.completed {
3636 3685 background: var(--accent-green);
3637 - color: white;
3686 + color: var(--text-on-accent);
3638 3687 }
3639 3688
3640 3689 .review-status.pending {
@@ -3783,13 +3832,13 @@ kbd {
3783 3832
3784 3833 .due-badge.overdue {
3785 3834 background: var(--accent-red);
3786 - color: white;
3835 + color: var(--text-on-accent);
3787 3836 border-color: var(--accent-red);
3788 3837 }
3789 3838
3790 3839 /* Focus Section */
3791 3840 .focus-section {
3792 - background: linear-gradient(135deg, var(--bg-card) 0%, #FFF8DC 100%);
3841 + background: linear-gradient(135deg, var(--bg-card) 0%, color-mix(in srgb, var(--accent-yellow) 15%, var(--bg-card)) 100%);
3793 3842 }
3794 3843
3795 3844 .focus-task-list {
@@ -3822,7 +3871,7 @@ kbd {
3822 3871 }
3823 3872
3824 3873 .review-task-item.focused {
3825 - background: rgba(247, 209, 84, 0.1);
3874 + background: color-mix(in srgb, var(--accent-yellow) 10%, var(--bg-card));
3826 3875 }
3827 3876
3828 3877 .no-focus-message {
@@ -3840,7 +3889,7 @@ kbd {
3840 3889
3841 3890 .project-tag {
3842 3891 background: var(--accent-blue);
3843 - color: white;
3892 + color: var(--text-on-accent);
3844 3893 padding: 0.25rem 0.75rem;
3845 3894 font-size: 0.875rem;
3846 3895 font-weight: 600;
@@ -4091,7 +4140,7 @@ kbd {
4091 4140
4092 4141 .vacation-toggle.active {
4093 4142 background: var(--accent-purple);
4094 - color: white;
4143 + color: var(--text-on-accent);
4095 4144 border-color: var(--accent-purple);
4096 4145 }
4097 4146
@@ -4195,7 +4244,7 @@ kbd {
4195 4244
4196 4245 .task-checkbox.checked {
4197 4246 background: var(--accent-green);
4198 - color: white;
4247 + color: var(--text-on-accent);
4199 4248 }
4200 4249
4201 4250 .task-text {
@@ -4251,7 +4300,7 @@ kbd {
4251 4300
4252 4301 .focus-slot.primary {
4253 4302 border-color: var(--accent-yellow);
4254 - background: linear-gradient(135deg, var(--bg-card) 0%, rgba(247, 209, 84, 0.1) 100%);
4303 + background: linear-gradient(135deg, var(--bg-card) 0%, color-mix(in srgb, var(--accent-yellow) 10%, var(--bg-card)) 100%);
4255 4304 }
4256 4305
4257 4306 .focus-label {
@@ -4387,7 +4436,7 @@ kbd {
4387 4436
4388 4437 /* Accomplishment celebration */
4389 4438 .accomplishment-highlight {
4390 - background: linear-gradient(135deg, rgba(92, 184, 92, 0.1) 0%, rgba(92, 184, 92, 0.05) 100%);
4439 + 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%);
4391 4440 border: 2px solid var(--accent-green);
4392 4441 padding: 1rem;
4393 4442 border-radius: var(--radius-md);
@@ -4927,7 +4976,7 @@ kbd {
4927 4976
4928 4977 .toggle-switch input:checked + .toggle-slider:before {
4929 4978 transform: translateX(20px);
4930 - background-color: white;
4979 + background-color: var(--bg-card);
4931 4980 }
4932 4981
4933 4982 .toggle-switch input:focus + .toggle-slider {
@@ -4948,7 +4997,7 @@ kbd {
4948 4997 font-size: 0.7rem;
4949 4998 padding: 0.2rem 0.5rem;
4950 4999 background: var(--accent-purple);
4951 - color: white;
5000 + color: var(--text-on-accent);
4952 5001 border: none;
4953 5002 border-radius: var(--radius-sm);
4954 5003 cursor: pointer;
@@ -5277,7 +5326,7 @@ kbd {
5277 5326 font-size: 1.25rem;
5278 5327 background-color: var(--accent-green);
5279 5328 background-image: var(--btn-gradient);
5280 - color: #fff;
5329 + color: var(--text-on-accent);
5281 5330 border-color: var(--border-color);
5282 5331 }
5283 5332
@@ -5422,7 +5471,7 @@ kbd {
5422 5471 padding: 0 1rem;
5423 5472 font-weight: 700;
5424 5473 font-size: var(--font-size-sm);
5425 - color: #fff;
5474 + color: var(--text-on-accent);
5426 5475 }
5427 5476
5428 5477 .swipe-actions-bg.swipe-left {
@@ -1,9 +1,9 @@
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:#E8F4F8;--bg-secondary:#D4EBF2;--bg-tertiary:#C0E2EC;--bg-card:#FFFFFF;--text-primary:#1B365D;--text-secondary:#3D5A80;--text-muted:#4d6b82;--accent-yellow:#F7D154;--accent-green:#5CB85C;--accent-blue:#1B365D;--accent-purple:#7B68EE;--accent-red:#DC3545;--accent-cyan:#17A2B8;--border-color:#1B365D;--border-width:3px;--border-width-sm:2px;--shadow-offset-xs:1px;--shadow-offset-sm:2px;--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) 1px var(--border-color);--shadow-brutal-sm:var(--shadow-offset-sm) var(--shadow-offset-sm) 1px var(--border-color);--shadow-brutal-md:var(--shadow-offset-md) var(--shadow-offset-md) 1px var(--border-color);--shadow-brutal:var(--shadow-offset) var(--shadow-offset) 1px var(--border-color);--shadow-brutal-lg:var(--shadow-offset-lg) var(--shadow-offset-lg) 1px var(--border-color);--shadow-brutal-xl:var(--shadow-offset-xl) var(--shadow-offset-xl) 1px var(--border-color);--radius-xs:4px;--radius-sm:6px;--radius-md:12px;--radius-lg:16px;--radius-xl:24px;--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-serif);--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;--texture-paper:url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.03'/%3E%3C/svg%3E");--emboss-light:1px 1px 0 rgba(255, 255, 255, 0.5);--emboss-dark:-1px -1px 0 rgba(0, 0, 0, 0.08);--deboss:inset 1px 1px 2px rgba(0, 0, 0, 0.08),inset -1px -1px 0 rgba(255, 255, 255, 0.5);--overlay-color:rgba(27, 54, 93, 0.6);--btn-gradient:linear-gradient(to bottom,
2 - rgba(255, 255, 255, 0.15) 0%,
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:#E8F4F8;--bg-secondary:#D4EBF2;--bg-tertiary:#C0E2EC;--bg-card:#FFFFFF;--text-primary:#1B365D;--text-secondary:#3D5A80;--text-muted:#4d6b82;--accent-yellow:#F7D154;--accent-green:#5CB85C;--accent-blue:#1B365D;--accent-purple:#7B68EE;--accent-red:#DC3545;--accent-cyan:#17A2B8;--border-color:#1B365D;--border-width:3px;--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-sm:2px;--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) 1px var(--border-color);--shadow-brutal-sm:var(--shadow-offset-sm) var(--shadow-offset-sm) 1px var(--border-color);--shadow-brutal-md:var(--shadow-offset-md) var(--shadow-offset-md) 1px var(--border-color);--shadow-brutal:var(--shadow-offset) var(--shadow-offset) 1px var(--border-color);--shadow-brutal-lg:var(--shadow-offset-lg) var(--shadow-offset-lg) 1px var(--border-color);--shadow-brutal-xl:var(--shadow-offset-xl) var(--shadow-offset-xl) 1px var(--border-color);--radius-xs:4px;--radius-sm:6px;--radius-md:12px;--radius-lg:16px;--radius-xl:24px;--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-serif);--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;--texture-paper:url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.03'/%3E%3C/svg%3E");--emboss-light:1px 1px 0 color-mix(in srgb, var(--bg-card) 50%, transparent);--emboss-dark:-1px -1px 0 color-mix(in srgb, var(--text-primary) 8%, transparent);--deboss:inset 1px 1px 2px color-mix(in srgb, var(--text-primary) 8%, transparent),inset -1px -1px 0 color-mix(in srgb, var(--bg-card) 50%, transparent);--overlay-color:color-mix(in srgb, var(--text-primary) 60%, transparent);--btn-gradient:linear-gradient(to bottom,
2 + color-mix(in srgb, var(--bg-card) 15%, transparent) 0%,
3 3 transparent 50%,
4 - rgba(0, 0, 0, 0.05) 100%
4 + color-mix(in srgb, var(--text-primary) 5%, transparent) 100%
5 5 );--btn-gradient-pressed:linear-gradient(to bottom,
6 - rgba(0, 0, 0, 0.05) 0%,
6 + color-mix(in srgb, var(--text-primary) 5%, transparent) 0%,
7 7 transparent 50%,
8 - rgba(255, 255, 255, 0.1) 100%
9 - )}html{font-size:16px}.shadow-xs{box-shadow:var(--shadow-brutal-xs)}.shadow-sm{box-shadow:var(--shadow-brutal-sm)}.shadow-md{box-shadow:var(--shadow-brutal)}.shadow-lg{box-shadow:var(--shadow-brutal-lg)}.shadow-xl{box-shadow:var(--shadow-brutal-xl)}.shadow-none{box-shadow:none}.hover-lift{transition:transform .15s ease,box-shadow .15s ease}.hover-lift:hover{transform:translate(-2px,-2px)}.hover-lift:active{transform:translate(2px,2px)}.border-thick{border-width:3px}.border-medium{border-width:2px}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:1rem 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{background-color:var(--bg-secondary);border-bottom:var(--border-width) solid var(--border-color);display:flex;justify-content:center;gap:.5rem;padding:1rem}.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:all .15s ease;box-shadow:var(--shadow-offset) var(--shadow-offset) 1px var(--border-color)}.tab:hover{transform:translate(-2px,-2px);box-shadow:calc(var(--shadow-offset) + 2px) calc(var(--shadow-offset) + 2px) 1px var(--border-color)}.tab.active{background-color:var(--accent-yellow);transform:translate(2px,2px);box-shadow:0 0 0 var(--border-color)}.tab-icon{font-size:1.1rem}.tab-label{font-weight:600;font-size:.9rem}.tab.tab-right{margin-left:auto}.main-content{flex:1;max-width:var(--width-container);width:100%;margin:0 auto;padding:1.5rem}.page-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1.5rem}.page-title{font-family:var(--font-serif);font-size:1.75rem;font-weight:700;color:var(--text-primary);text-shadow:var(--emboss-light),var(--emboss-dark)}.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:all .15s ease;text-decoration:none;background:var(--bg-card);background-image:var(--btn-gradient);background-clip:padding-box;color:var(--text-primary);box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.btn:hover{transform:translate(-2px,-2px);box-shadow:calc(var(--shadow-offset) + 2px) calc(var(--shadow-offset) + 2px) 0 var(--border-color)}.btn:active{transform:translate(1px,1px);box-shadow:var(--shadow-brutal-xs);background-image:var(--btn-gradient-pressed)}.btn:disabled{background:var(--bg-tertiary);color:var(--text-muted);cursor:not-allowed;opacity:.7;transform:none;box-shadow:var(--shadow-brutal-sm)}.btn:disabled:hover{transform:none;box-shadow:var(--shadow-brutal-sm)}.btn-primary{background-color:var(--accent-yellow);color:var(--text-primary)}.btn-secondary{background-color:var(--bg-secondary);color:var(--text-primary)}.btn-danger{background-color:var(--accent-red);color:#fff}.btn-danger:hover{background-color:color-mix(in srgb,var(--accent-red) 85%,#000)}.btn-sm{padding:.375rem .75rem;font-size:.8rem;box-shadow:var(--shadow-brutal-sm)}.btn-sm:hover{transform:translate(-1px,-1px);box-shadow:var(--shadow-brutal)}.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);box-shadow:var(--shadow-offset) var(--shadow-offset) 1px var(--border-color)}.quick-add-input::placeholder{color:var(--text-muted)}.quick-add-input:focus{outline:0;background-color:var(--accent-yellow);box-shadow:calc(var(--shadow-offset) + 2px) calc(var(--shadow-offset) + 2px) 1px 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);background-image:var(--texture-paper);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);padding:1.25rem;transition:all .15s ease;cursor:pointer;box-shadow:var(--shadow-offset) var(--shadow-offset) 1px var(--border-color),calc(var(--shadow-offset) + 2px) calc(var(--shadow-offset) + 2px) 0 var(--bg-secondary)}.card:hover{transform:translate(-2px,-2px);box-shadow:calc(var(--shadow-offset) + 2px) calc(var(--shadow-offset) + 2px) 1px var(--border-color)}.card-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:.75rem}.card-title{font-family:var(--font-serif);font-size:1.1rem;font-weight:700;color:var(--text-primary);text-shadow:var(--emboss-light),var(--emboss-dark)}.card-description{font-size:.9rem;color:var(--text-secondary);margin-bottom:1rem}.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)}.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}.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:var(--accent-yellow)}.task-table{width:100%;background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-lg);box-shadow:var(--shadow-brutal);overflow:hidden;display:flex;flex-direction:column;flex:1;min-height:0}.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:var(--accent-yellow)}.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);box-shadow:var(--shadow-brutal);overflow:hidden;display:flex;flex-direction:column;flex:1;min-height:0}.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:#fff;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:all .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:#fff;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:all .15s ease;cursor:pointer;box-shadow:var(--shadow-offset) var(--shadow-offset) 1px var(--border-color)}.event-item:hover{transform:translate(-2px,-2px);box-shadow:calc(var(--shadow-offset) + 2px) calc(var(--shadow-offset) + 2px) 1px var(--border-color)}.event-date{flex-shrink:0;width:80px;text-align:center;padding:.75rem;background-color:var(--accent-green);border-radius:var(--radius-sm);color:#fff}.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-serif);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);box-shadow:var(--shadow-brutal);overflow:hidden;flex:1;min-height:0}.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:var(--accent-yellow);border-left:4px solid var(--border-color)}.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-yellow);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-yellow) 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-serif);font-size:1.25rem;font-weight:700;text-shadow:var(--emboss-light),var(--emboss-dark)}.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;box-shadow:var(--shadow-brutal-sm);transition:all .15s ease}.modal-close:hover{background:var(--accent-yellow);transform:translate(-1px,-1px);box-shadow:var(--shadow-brutal-sm)}.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:var(--deboss)}.form-input:focus,.form-select:focus,.form-textarea:focus{outline:0;background-color:var(--bg-card);box-shadow:var(--deboss),0 0 0 3px var(--accent-yellow)}.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:var(--deboss),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:var(--deboss),0 0 0 3px 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;box-shadow:var(--shadow-brutal-sm)}.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.25rem;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) 1px 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;box-shadow:var(--shadow-brutal-sm)}.filter-select:focus{outline:0;background-color:var(--accent-yellow)}.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);box-shadow:var(--shadow-offset) var(--shadow-offset) 1px var(--border-color)}.pagination-info{font-weight:600;color:var(--text-secondary);font-size:.9rem}.pagination-controls .btn:disabled{opacity:.5;cursor:not-allowed;transform:none;box-shadow:var(--shadow-brutal-sm)}.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-yellow);outline-offset:2px}.event-row,.task-row-clickable{cursor:pointer}.skip-link{position:absolute;top:-100px;left:0;background:var(--accent-yellow);color:var(--text-primary);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-yellow);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:#fff}.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);background-image:var(--texture-paper);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;text-shadow:var(--emboss-light),var(--emboss-dark)}.dashboard-list{flex:1;overflow-y:auto}.dashboard-item{padding:.75rem;background:var(--bg-secondary);border:var(--border-width-sm) solid var(--border-color);margin-bottom:.5rem;cursor:pointer;transition:transform .1s,box-shadow .1s}.dashboard-item:hover{transform:translate(-2px,-2px);box-shadow:var(--shadow-brutal)}.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:#fff}.task-badge.recurrence{background:var(--accent-purple);color:#fff}.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);box-shadow:var(--shadow-brutal-sm)}.day-plan-date-display{font-size:1.25rem;font-weight:700;margin-left:1rem;font-family:var(--font-heading);text-shadow:var(--emboss-light),var(--emboss-dark);line-height:1}.day-plan-content{flex:1;min-height:0;display:flex;gap:1rem}.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}.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}.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;text-shadow:var(--emboss-light),var(--emboss-dark)}.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:#fff}.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:#fff}.timeline-item.block-focus{background:var(--accent-red);color:#fff}.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:transform .1s,box-shadow .1s}.unscheduled-task:hover{transform:translate(-2px,-2px);box-shadow:var(--shadow-brutal)}.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);background-image:var(--btn-gradient);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:transform .1s,box-shadow .1s;box-shadow:var(--shadow-brutal-sm)}.settings-btn:hover{transform:translate(-1px,-1px);box-shadow:var(--shadow-brutal-sm)}.settings-btn:active{background-image:var(--btn-gradient-pressed)}.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);text-shadow:var(--emboss-light),var(--emboss-dark)}.settings-section .form-hint{font-size:.75rem;color:var(--text-secondary)}.llm-settings-form .form-group{margin-bottom:1rem}.llm-settings-form .form-row{display:grid;grid-template-columns:1fr 1fr;gap:1rem}.llm-settings-form .form-hint{font-size:.75rem;color:var(--text-secondary);margin-top:.25rem}.llm-settings-form .slider-value{font-weight:700;color:var(--accent-blue)}.llm-test-result{padding:.75rem;border:var(--border-width) solid var(--border-color);margin-top:1rem}.llm-test-result.success{background:var(--accent-green);color:var(--text-primary)}.llm-test-result.error{background:var(--accent-red);color:#fff}.llm-cache-info{margin-top:1rem;padding-top:1rem;border-top:2px solid var(--border-color)}.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:transform .1s,box-shadow .1s;text-align:left;width:100%}.snooze-option:hover{transform:translate(-2px,-2px);box-shadow:var(--shadow-brutal);background:var(--accent-yellow)}.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-primary)}.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-yellow);border:var(--border-width) solid var(--border-color);box-shadow:var(--shadow-offset) var(--shadow-offset) 1px 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);box-shadow:var(--shadow-brutal-sm)}.bulk-actions-bar .btn:hover{transform:translate(-1px,-1px);box-shadow:var(--shadow-brutal-sm)}.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:transform .1s,box-shadow .1s}.time-block-quick-btn:hover{transform:translate(-2px,-2px);box-shadow:var(--shadow-brutal)}.time-block-quick-btn.selected{background:var(--accent-yellow);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:transform .1s,box-shadow .1s}.duration-preset:hover{transform:translate(-1px,-1px);box-shadow:var(--shadow-brutal-sm)}.duration-preset.selected{background:var(--accent-yellow)}.conflict-warning{padding:.75rem;background:var(--accent-red);border:var(--border-width) solid var(--border-color);color:#fff;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:.25rem;background:var(--bg-secondary);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);transition:all .1s}.saved-view-item:hover{background:var(--accent-yellow);transform:translate(-1px,-1px);box-shadow:var(--shadow-brutal-sm)}.saved-view-item.active{background:var(--accent-yellow);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,#4a90d9);color:#fff;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:#fff}.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);box-shadow:var(--shadow-brutal-sm);text-align:center}.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);box-shadow:var(--shadow-brutal-sm);padding:1.25rem;margin-bottom:1.5rem}.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:#fff;border-color:var(--accent-red)}.focus-section{background:linear-gradient(135deg,var(--bg-card) 0,#fff8dc 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:rgba(247,209,84,.1)}.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:#fff;padding:.25rem .75rem;font-size:.875rem;font-weight:600;border:var(--border-width-sm) solid var(--border-color)}.available-for-focus{margin-top:1rem;padding-top:1rem;border-top:1px dashed var(--border-color)}.available-for-focus h4{font-size:.875rem;font-weight:600;color:var(--text-muted);margin-bottom:.5rem}.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);box-shadow:var(--shadow-brutal);padding:1.5rem}.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-yellow);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}.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;text-shadow:var(--emboss-light),var(--emboss-dark)}.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:#fff;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:all var(--transition-normal)}.task-item:hover{transform:translateX(4px);background:var(--accent-yellow)}.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:#fff}.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,rgba(247,209,84,.1) 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,rgba(92,184,92,.1) 0,rgba(92,184,92,.05) 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{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:all .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{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}}.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}.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:#fff}.toggle-switch input:focus+.toggle-slider{box-shadow:0 0 0 2px color-mix(in srgb,var(--accent-primary) 30%,transparent)}.ai-fill-wrapper{position:relative}.ai-fill-btn{position:absolute;right:.5rem;top:.5rem;font-size:.7rem;padding:.2rem .5rem;background:var(--accent-purple);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer;font-family:var(--font-heading);font-weight:700;text-transform:uppercase;letter-spacing:.03em;transition:background var(--transition-fast),opacity var(--transition-fast);z-index:1}.ai-fill-btn:hover{background:color-mix(in srgb,var(--accent-purple) 80%,#000)}.ai-fill-btn:disabled{opacity:.6;cursor:wait}.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;text-shadow:var(--emboss-light),var(--emboss-dark)}.milestone-card{background:var(--bg-card);background-image:var(--texture-paper);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);padding:1rem;margin-bottom:.75rem;transition:transform .1s,box-shadow .1s}.milestone-card:hover{transform:translateY(-1px);box-shadow:var(--shadow-md)}.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)}.milestone-reorder-btn{font-size:.65rem!important;padding:.15rem .35rem!important;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)}.nav-dot{display:none;position:fixed;bottom:calc(20px + env(safe-area-inset-bottom,0px));right:20px;width:70px;height:70px;border-radius:var(--radius-full);background:var(--bg-card);background-image:var(--btn-gradient);background-clip:padding-box;color:var(--text-primary);border:var(--border-width) solid var(--border-color);box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color);z-index:1100;align-items:center;justify-content:center;cursor:pointer;user-select:none;touch-action:none;font-family:var(--font-display);font-size:1rem;font-weight:700;letter-spacing:.05em;transition:all .15s ease}.nav-dot:hover{transform:translate(-2px,-2px);box-shadow:calc(var(--shadow-offset) + 2px) calc(var(--shadow-offset) + 2px) 0 var(--border-color)}.nav-dot:active{transform:translate(1px,1px);box-shadow:var(--shadow-brutal-xs);background-image:var(--btn-gradient-pressed)}.nav-dot.dial-open{background-color:var(--accent-yellow);background-image:var(--btn-gradient-pressed);color:var(--text-primary)}.nav-dot-icon{pointer-events:none}.nav-dot-status{display:none;position:fixed;bottom:calc(8px + env(safe-area-inset-bottom,0px));right:20px;width:70px;z-index:1100;justify-content:center;gap:4px;pointer-events:none}.nav-dot-status .tab-status-dot{width:10px;height:10px;border-radius:50%;display:inline-block;border:none}.nav-dial{display:none;position:fixed;z-index:1099;pointer-events:none}.nav-dial.visible{display:block;pointer-events:auto}.nav-dial.hidden{display:none}.nav-dial-backdrop{display:none;position:fixed;inset:0;background:rgba(0,0,0,.3);z-index:1098}.nav-dial-backdrop.visible{display:block}.nav-dial-item{position:absolute;width:72px;height:28px;border-radius:var(--radius-sm);background:var(--bg-card);background-image:var(--btn-gradient);background-clip:padding-box;border:var(--border-width) solid var(--border-color);box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color);display:flex;align-items:center;justify-content:center;font-size:.6rem;font-weight:700;color:var(--text-primary);cursor:pointer;opacity:0;transform-origin:center center;transition:opacity .2s ease,background .15s ease,box-shadow .15s ease,filter .15s ease}.nav-dial.visible .nav-dial-item{opacity:1}.nav-dial-item:hover{filter:brightness(1.05);box-shadow:calc(var(--shadow-offset) + 1px) calc(var(--shadow-offset) + 1px) 0 var(--border-color)}.nav-dial-item:active{box-shadow:var(--shadow-brutal-xs);background-image:var(--btn-gradient-pressed)}.nav-dial-item.active{background-color:var(--accent-yellow);border-color:var(--accent-blue);box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--accent-blue)}.nav-dial-item.nav-dial-center{width:44px;height:44px;border-radius:var(--radius-full);font-size:1.25rem;background-color:var(--accent-green);background-image:var(--btn-gradient);color:#fff;border-color:var(--border-color)}.nav-dial-label{pointer-events:none;font-family:var(--font-sans);text-transform:uppercase;letter-spacing:.05em;line-height:1;text-align:center;white-space:nowrap}.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:#fff}.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-bottom:env(safe-area-inset-bottom,0)}.tab-navigation{display:none!important}.app-header{padding:calc(.5rem + env(safe-area-inset-top)) 1rem .5rem 1rem}.app-header .header-actions{display:flex;flex-direction:column;gap:.25rem}.app-header .header-actions .settings-btn{font-size:.75rem;padding:.3rem .6rem}.app-subtitle{display:none}.mobile-view-title{display:inline;font-size:.875rem;color:var(--text-muted);font-weight:500;line-height:1}.nav-dot{display:flex}.nav-dot-status{display:flex}.nav-dot-status.dial-open{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) + 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);box-shadow:var(--shadow-brutal-sm);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}.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{transform:none;box-shadow:var(--shadow-brutal)}.btn:hover{transform:none}.context-menu-item:hover{background:0 0}.modal-close:hover{background:var(--bg-card);transform:none;box-shadow:none}}
9 > \ No newline at end of file
8 + color-mix(in srgb, var(--bg-card) 10%, transparent) 100%
9 + )}html{font-size:16px}.shadow-xs{box-shadow:var(--shadow-brutal-xs)}.shadow-sm{box-shadow:var(--shadow-brutal-sm)}.shadow-md{box-shadow:var(--shadow-brutal)}.shadow-lg{box-shadow:var(--shadow-brutal-lg)}.shadow-xl{box-shadow:var(--shadow-brutal-xl)}.shadow-none{box-shadow:none}.hover-lift{transition:transform .15s ease,box-shadow .15s ease}.hover-lift:hover{transform:translate(-2px,-2px)}.hover-lift:active{transform:translate(2px,2px)}.border-thick{border-width:3px}.border-medium{border-width:2px}.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:3px 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:1rem 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{background-color:var(--bg-secondary);border-bottom:var(--border-width) solid var(--border-color);display:flex;justify-content:center;gap:.5rem;padding:1rem}.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:all .15s ease;box-shadow:var(--shadow-offset) var(--shadow-offset) 1px var(--border-color)}.tab:hover{transform:translate(-2px,-2px);box-shadow:calc(var(--shadow-offset) + 2px) calc(var(--shadow-offset) + 2px) 1px var(--border-color)}.tab.active{background-color:var(--accent-yellow);transform:translate(2px,2px);box-shadow:0 0 0 var(--border-color)}.tab-icon{font-size:1.1rem}.tab-label{font-weight:600;font-size:.9rem}.tab.tab-right{margin-left:auto}.main-content{flex:1;max-width:var(--width-container);width:100%;margin:0 auto;padding:1.5rem}.page-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:1.5rem}.page-title{font-family:var(--font-serif);font-size:1.75rem;font-weight:700;color:var(--text-primary);text-shadow:var(--emboss-light),var(--emboss-dark)}.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:all .15s ease;text-decoration:none;background:var(--bg-card);background-image:var(--btn-gradient);background-clip:padding-box;color:var(--text-primary);box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color)}.btn:hover{transform:translate(-2px,-2px);box-shadow:calc(var(--shadow-offset) + 2px) calc(var(--shadow-offset) + 2px) 0 var(--border-color)}.btn:active{transform:translate(1px,1px);box-shadow:var(--shadow-brutal-xs);background-image:var(--btn-gradient-pressed)}.btn:disabled{background:var(--bg-tertiary);color:var(--text-muted);cursor:not-allowed;opacity:.7;transform:none;box-shadow:var(--shadow-brutal-sm)}.btn:disabled:hover{transform:none;box-shadow:var(--shadow-brutal-sm)}.btn-primary{background-color:var(--accent-yellow);color:var(--text-primary)}.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-sm{padding:.375rem .75rem;font-size:.8rem;box-shadow:var(--shadow-brutal-sm)}.btn-sm:hover{transform:translate(-1px,-1px);box-shadow:var(--shadow-brutal)}.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);box-shadow:var(--shadow-offset) var(--shadow-offset) 1px var(--border-color)}.quick-add-input::placeholder{color:var(--text-muted)}.quick-add-input:focus{outline:0;background-color:var(--accent-yellow);box-shadow:calc(var(--shadow-offset) + 2px) calc(var(--shadow-offset) + 2px) 1px 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);background-image:var(--texture-paper);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);padding:1.25rem;transition:all .15s ease;cursor:pointer;box-shadow:var(--shadow-offset) var(--shadow-offset) 1px var(--border-color),calc(var(--shadow-offset) + 2px) calc(var(--shadow-offset) + 2px) 0 var(--bg-secondary)}.card:hover{transform:translate(-2px,-2px);box-shadow:calc(var(--shadow-offset) + 2px) calc(var(--shadow-offset) + 2px) 1px var(--border-color)}.card-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:.75rem}.card-title{font-family:var(--font-serif);font-size:1.1rem;font-weight:700;color:var(--text-primary);text-shadow:var(--emboss-light),var(--emboss-dark)}.card-description{font-size:.9rem;color:var(--text-secondary);margin-bottom:1rem}.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}.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:var(--accent-yellow)}.task-table{width:100%;background:var(--bg-card);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-lg);box-shadow:var(--shadow-brutal);overflow:hidden;display:flex;flex-direction:column;flex:1;min-height:0}.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:var(--accent-yellow)}.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);box-shadow:var(--shadow-brutal);overflow:hidden;display:flex;flex-direction:column;flex:1;min-height:0}.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:all .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:all .15s ease;cursor:pointer;box-shadow:var(--shadow-offset) var(--shadow-offset) 1px var(--border-color)}.event-item:hover{transform:translate(-2px,-2px);box-shadow:calc(var(--shadow-offset) + 2px) calc(var(--shadow-offset) + 2px) 1px var(--border-color)}.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-serif);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);box-shadow:var(--shadow-brutal);overflow:hidden;flex:1;min-height:0}.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:var(--accent-yellow);border-left:4px solid var(--border-color)}.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-yellow);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-yellow) 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-serif);font-size:1.25rem;font-weight:700;text-shadow:var(--emboss-light),var(--emboss-dark)}.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;box-shadow:var(--shadow-brutal-sm);transition:all .15s ease}.modal-close:hover{background:var(--accent-yellow);transform:translate(-1px,-1px);box-shadow:var(--shadow-brutal-sm)}.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:var(--deboss)}.form-input:focus,.form-select:focus,.form-textarea:focus{outline:0;background-color:var(--bg-card);box-shadow:var(--deboss),0 0 0 3px var(--accent-yellow)}.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:var(--deboss),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:var(--deboss),0 0 0 3px 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;box-shadow:var(--shadow-brutal-sm)}.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.25rem;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) 1px 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;box-shadow:var(--shadow-brutal-sm)}.filter-select:focus{outline:0;background-color:var(--accent-yellow)}.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);box-shadow:var(--shadow-offset) var(--shadow-offset) 1px var(--border-color)}.pagination-info{font-weight:600;color:var(--text-secondary);font-size:.9rem}.pagination-controls .btn:disabled{opacity:.5;cursor:not-allowed;transform:none;box-shadow:var(--shadow-brutal-sm)}.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-yellow);outline-offset:2px}.event-row,.task-row-clickable{cursor:pointer}.skip-link{position:absolute;top:-100px;left:0;background:var(--accent-yellow);color:var(--text-primary);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-yellow);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);background-image:var(--texture-paper);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;text-shadow:var(--emboss-light),var(--emboss-dark)}.dashboard-list{flex:1;overflow-y:auto}.dashboard-item{padding:.75rem;background:var(--bg-secondary);border:var(--border-width-sm) solid var(--border-color);margin-bottom:.5rem;cursor:pointer;transition:transform .1s,box-shadow .1s}.dashboard-item:hover{transform:translate(-2px,-2px);box-shadow:var(--shadow-brutal)}.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);box-shadow:var(--shadow-brutal-sm)}.day-plan-date-display{font-size:1.25rem;font-weight:700;margin-left:1rem;font-family:var(--font-heading);text-shadow:var(--emboss-light),var(--emboss-dark);line-height:1}.day-plan-content{flex:1;min-height:0;display:flex;gap:1rem}.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}.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}.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;text-shadow:var(--emboss-light),var(--emboss-dark)}.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:transform .1s,box-shadow .1s}.unscheduled-task:hover{transform:translate(-2px,-2px);box-shadow:var(--shadow-brutal)}.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);background-image:var(--btn-gradient);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:transform .1s,box-shadow .1s;box-shadow:var(--shadow-brutal-sm)}.settings-btn:hover{transform:translate(-1px,-1px);box-shadow:var(--shadow-brutal-sm)}.settings-btn:active{background-image:var(--btn-gradient-pressed)}.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);text-shadow:var(--emboss-light),var(--emboss-dark)}.settings-section .form-hint{font-size:.75rem;color:var(--text-secondary)}.llm-settings-form .form-group{margin-bottom:1rem}.llm-settings-form .form-row{display:grid;grid-template-columns:1fr 1fr;gap:1rem}.llm-settings-form .form-hint{font-size:.75rem;color:var(--text-secondary);margin-top:.25rem}.llm-settings-form .slider-value{font-weight:700;color:var(--accent-blue)}.llm-test-result{padding:.75rem;border:var(--border-width) solid var(--border-color);margin-top:1rem}.llm-test-result.success{background:var(--accent-green);color:var(--text-primary)}.llm-test-result.error{background:var(--accent-red);color:var(--text-on-accent)}.llm-cache-info{margin-top:1rem;padding-top:1rem;border-top:2px solid var(--border-color)}.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:transform .1s,box-shadow .1s;text-align:left;width:100%}.snooze-option:hover{transform:translate(-2px,-2px);box-shadow:var(--shadow-brutal);background:var(--accent-yellow)}.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-primary)}.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-yellow);border:var(--border-width) solid var(--border-color);box-shadow:var(--shadow-offset) var(--shadow-offset) 1px 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);box-shadow:var(--shadow-brutal-sm)}.bulk-actions-bar .btn:hover{transform:translate(-1px,-1px);box-shadow:var(--shadow-brutal-sm)}.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:transform .1s,box-shadow .1s}.time-block-quick-btn:hover{transform:translate(-2px,-2px);box-shadow:var(--shadow-brutal)}.time-block-quick-btn.selected{background:var(--accent-yellow);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:transform .1s,box-shadow .1s}.duration-preset:hover{transform:translate(-1px,-1px);box-shadow:var(--shadow-brutal-sm)}.duration-preset.selected{background:var(--accent-yellow)}.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:.25rem;background:var(--bg-secondary);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);transition:all .1s}.saved-view-item:hover{background:var(--accent-yellow);transform:translate(-1px,-1px);box-shadow:var(--shadow-brutal-sm)}.saved-view-item.active{background:var(--accent-yellow);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);box-shadow:var(--shadow-brutal-sm);text-align:center}.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);box-shadow:var(--shadow-brutal-sm);padding:1.25rem;margin-bottom:1.5rem}.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)}.available-for-focus{margin-top:1rem;padding-top:1rem;border-top:1px dashed var(--border-color)}.available-for-focus h4{font-size:.875rem;font-weight:600;color:var(--text-muted);margin-bottom:.5rem}.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);box-shadow:var(--shadow-brutal);padding:1.5rem}.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-yellow);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}.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;text-shadow:var(--emboss-light),var(--emboss-dark)}.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:all var(--transition-normal)}.task-item:hover{transform:translateX(4px);background:var(--accent-yellow)}.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{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:all .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{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}}.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}.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)}.ai-fill-wrapper{position:relative}.ai-fill-btn{position:absolute;right:.5rem;top:.5rem;font-size:.7rem;padding:.2rem .5rem;background:var(--accent-purple);color:var(--text-on-accent);border:none;border-radius:var(--radius-sm);cursor:pointer;font-family:var(--font-heading);font-weight:700;text-transform:uppercase;letter-spacing:.03em;transition:background var(--transition-fast),opacity var(--transition-fast);z-index:1}.ai-fill-btn:hover{background:color-mix(in srgb,var(--accent-purple) 80%,#000)}.ai-fill-btn:disabled{opacity:.6;cursor:wait}.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;text-shadow:var(--emboss-light),var(--emboss-dark)}.milestone-card{background:var(--bg-card);background-image:var(--texture-paper);border:var(--border-width) solid var(--border-color);border-radius:var(--radius-md);padding:1rem;margin-bottom:.75rem;transition:transform .1s,box-shadow .1s}.milestone-card:hover{transform:translateY(-1px);box-shadow:var(--shadow-md)}.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)}.milestone-reorder-btn{font-size:.65rem!important;padding:.15rem .35rem!important;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)}.nav-dot{display:none;position:fixed;bottom:calc(20px + env(safe-area-inset-bottom,0px));right:20px;width:70px;height:70px;border-radius:var(--radius-full);background:var(--bg-card);background-image:var(--btn-gradient);background-clip:padding-box;color:var(--text-primary);border:var(--border-width) solid var(--border-color);box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color);z-index:1100;align-items:center;justify-content:center;cursor:pointer;user-select:none;touch-action:none;font-family:var(--font-display);font-size:1rem;font-weight:700;letter-spacing:.05em;transition:all .15s ease}.nav-dot:hover{transform:translate(-2px,-2px);box-shadow:calc(var(--shadow-offset) + 2px) calc(var(--shadow-offset) + 2px) 0 var(--border-color)}.nav-dot:active{transform:translate(1px,1px);box-shadow:var(--shadow-brutal-xs);background-image:var(--btn-gradient-pressed)}.nav-dot.dial-open{background-color:var(--accent-yellow);background-image:var(--btn-gradient-pressed);color:var(--text-primary)}.nav-dot-icon{pointer-events:none}.nav-dot-status{display:none;position:fixed;bottom:calc(8px + env(safe-area-inset-bottom,0px));right:20px;width:70px;z-index:1100;justify-content:center;gap:4px;pointer-events:none}.nav-dot-status .tab-status-dot{width:10px;height:10px;border-radius:50%;display:inline-block;border:none}.nav-dial{display:none;position:fixed;z-index:1099;pointer-events:none}.nav-dial.visible{display:block;pointer-events:auto}.nav-dial.hidden{display:none}.nav-dial-backdrop{display:none;position:fixed;inset:0;background:rgba(0,0,0,.3);z-index:1098}.nav-dial-backdrop.visible{display:block}.nav-dial-item{position:absolute;width:72px;height:28px;border-radius:var(--radius-sm);background:var(--bg-card);background-image:var(--btn-gradient);background-clip:padding-box;border:var(--border-width) solid var(--border-color);box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--border-color);display:flex;align-items:center;justify-content:center;font-size:.6rem;font-weight:700;color:var(--text-primary);cursor:pointer;opacity:0;transform-origin:center center;transition:opacity .2s ease,background .15s ease,box-shadow .15s ease,filter .15s ease}.nav-dial.visible .nav-dial-item{opacity:1}.nav-dial-item:hover{filter:brightness(1.05);box-shadow:calc(var(--shadow-offset) + 1px) calc(var(--shadow-offset) + 1px) 0 var(--border-color)}.nav-dial-item:active{box-shadow:var(--shadow-brutal-xs);background-image:var(--btn-gradient-pressed)}.nav-dial-item.active{background-color:var(--accent-yellow);border-color:var(--accent-blue);box-shadow:var(--shadow-offset) var(--shadow-offset) 0 var(--accent-blue)}.nav-dial-item.nav-dial-center{width:44px;height:44px;border-radius:var(--radius-full);font-size:1.25rem;background-color:var(--accent-green);background-image:var(--btn-gradient);color:var(--text-on-accent);border-color:var(--border-color)}.nav-dial-label{pointer-events:none;font-family:var(--font-sans);text-transform:uppercase;letter-spacing:.05em;line-height:1;text-align:center;white-space:nowrap}.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-bottom:env(safe-area-inset-bottom,0)}.tab-navigation{display:none!important}.app-header{padding:calc(.5rem + env(safe-area-inset-top)) 1rem .5rem 1rem}.app-header .header-actions{display:flex;flex-direction:column;gap:.25rem}.app-header .header-actions .settings-btn{font-size:.75rem;padding:.3rem .6rem}.app-subtitle{display:none}.mobile-view-title{display:inline;font-size:.875rem;color:var(--text-muted);font-weight:500;line-height:1}.nav-dot{display:flex}.nav-dot-status{display:flex}.nav-dot-status.dial-open{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) + 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);box-shadow:var(--shadow-brutal-sm);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}.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{transform:none;box-shadow:var(--shadow-brutal)}.btn:hover{transform:none}.context-menu-item:hover{background:0 0}.modal-close:hover{background:var(--bg-card);transform:none;box-shadow: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;box-shadow:var(--shadow-brutal-sm)}.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);background-image:var(--texture-paper);border:var(--border-width) solid var(--border-color);box-shadow:var(--shadow-brutal);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-secondary);border:var(--border-width-sm) solid var(--border-color);cursor:grab;transition:transform var(--transition-fast),box-shadow var(--transition-fast);border-left:4px solid transparent}.kanban-card:hover{transform:translate(-2px,-2px);box-shadow:var(--shadow-brutal)}.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}}
9 < \ No newline at end of file