@co0ontty/wand 1.23.0 → 1.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -121,12 +121,33 @@
121
121
  }
122
122
  }
123
123
 
124
- /* ===== PWA 独立窗口模式 ===== */
125
- /* 顶部安全区:用 max() 保底,避免 iPad 横屏/Stage Manager 等 inset 为 0 的场景下贴顶,
126
- 同时为 iPadOS Stage Manager 浮动控件预留视觉缓冲 */
124
+ /* ===== 设备安全区 (notch / 状态栏 / 圆角) =====
125
+ 全局 --wand-safe-top/bottom/left/right 在所有上下文(PWA、Android APK 内嵌
126
+ WebView、普通移动浏览器、桌面) 中都可用。在没有 inset 的设备上回退到
127
+ 0px,所以不会影响桌面浏览体验。固定定位 (position: fixed) 的顶部元素
128
+ (如文件面板抽屉、各类预览/对话框) 必须自己消费这些 inset,因为它们脱离
129
+ 了 .app-container 的 padding 流。*/
130
+ :root {
131
+ --wand-safe-top: env(safe-area-inset-top, 0px);
132
+ --wand-safe-bottom: env(safe-area-inset-bottom, 0px);
133
+ --wand-safe-left: env(safe-area-inset-left, 0px);
134
+ --wand-safe-right: env(safe-area-inset-right, 0px);
135
+ --safe-bottom: env(safe-area-inset-bottom, 0px);
136
+ /* 默认与 wand-safe-top 等价; PWA / .is-pwa 下被覆盖为 max(inset, 14px) */
137
+ --pwa-safe-top: env(safe-area-inset-top, 0px);
138
+ }
139
+
140
+ /* 在普通移动浏览器 / Android WebView 边到边渲染 / 旋转后地址栏隐藏等场景下,
141
+ .app-container 同样需要消费 safe-area-inset-top, 否则 .main-header-row
142
+ 会被状态栏挡住。inset 为 0 的设备上为 0px, 不影响桌面。*/
143
+ .app-container {
144
+ padding-top: var(--wand-safe-top);
145
+ }
146
+
147
+ /* PWA 独立窗口模式: 加一个最小 14px 的视觉缓冲, 避开 iPadOS Stage Manager
148
+ 浮动控件 / iOS 状态栏底色和 UI 太贴的视觉粘连。 */
127
149
  @media (display-mode: standalone) {
128
150
  :root {
129
- --safe-bottom: env(safe-area-inset-bottom, 0px);
130
151
  --pwa-safe-top: max(env(safe-area-inset-top, 0px), 14px);
131
152
  }
132
153
  .app-container {
@@ -143,7 +164,6 @@
143
164
 
144
165
  /* iOS Safari PWA fallback (navigator.standalone adds .is-pwa via JS) */
145
166
  .is-pwa {
146
- --safe-bottom: env(safe-area-inset-bottom, 0px);
147
167
  --pwa-safe-top: max(env(safe-area-inset-top, 0px), 14px);
148
168
  }
149
169
  .is-pwa .app-container {
@@ -175,6 +195,9 @@
175
195
  text-rendering: optimizeLegibility;
176
196
  min-height: 100%;
177
197
  height: 100%;
198
+ /* 兜底背景:iOS Safari 在键盘弹起 / 地址栏收起 / 安全区时
199
+ body 可能短于 html,露出的部分必须也是奶油色,否则会看到默认白底。 */
200
+ background-color: #f6f1e8;
178
201
  }
179
202
 
180
203
  body {
@@ -666,10 +689,10 @@
666
689
  .tree-children.open { display: block; }
667
690
 
668
691
  .file-explorer-header {
669
- padding: 10px 14px 8px;
692
+ padding: 10px 14px 6px;
670
693
  display: flex;
671
694
  align-items: center;
672
- gap: 8px;
695
+ gap: 6px;
673
696
  flex-shrink: 0;
674
697
  }
675
698
 
@@ -688,11 +711,11 @@
688
711
  /* When rendered as <input> (editable path) keep the same look but reveal
689
712
  an editable affordance on hover/focus. */
690
713
  input.file-explorer-path {
691
- background: transparent;
692
- border: 1px solid transparent;
693
- border-radius: var(--radius-sm);
694
- padding: 4px 8px;
695
- height: 28px;
714
+ background: var(--bg-secondary);
715
+ border: 1px solid var(--border-subtle);
716
+ border-radius: 8px;
717
+ padding: 6px 10px;
718
+ height: 30px;
696
719
  line-height: 1.2;
697
720
  cursor: text;
698
721
  transition: background-color var(--transition-fast),
@@ -708,7 +731,7 @@
708
731
  opacity: 0.6;
709
732
  }
710
733
  input.file-explorer-path:hover {
711
- background: rgba(255, 255, 255, 0.5);
734
+ background: var(--bg-primary);
712
735
  border-color: var(--border);
713
736
  color: var(--text-secondary);
714
737
  }
@@ -717,6 +740,7 @@
717
740
  border-color: var(--accent);
718
741
  color: var(--text-primary);
719
742
  text-overflow: clip;
743
+ box-shadow: 0 0 0 3px rgba(212, 152, 99, 0.12);
720
744
  }
721
745
 
722
746
  .file-explorer-actions { display: flex; gap: 4px; margin-left: auto; flex-shrink: 0; }
@@ -751,6 +775,10 @@
751
775
  display: flex;
752
776
  flex-direction: column;
753
777
  box-shadow: -4px 0 24px rgba(0, 0, 0, 0.1);
778
+ /* 抽屉是 position:fixed, 脱离了 app-container 的 padding, 必须自己消费
779
+ 状态栏 / notch 安全区, 否则 .file-side-panel-header 会被刘海/状态栏挡住。*/
780
+ padding-top: var(--wand-safe-top);
781
+ padding-bottom: var(--wand-safe-bottom);
754
782
  }
755
783
 
756
784
  .file-side-panel.open {
@@ -788,15 +816,95 @@
788
816
  display: flex;
789
817
  align-items: center;
790
818
  justify-content: space-between;
791
- padding: 12px 16px;
792
- border-bottom: 1px solid var(--border);
819
+ padding: 12px 14px 10px;
820
+ border-bottom: 1px solid var(--border-subtle);
793
821
  flex-shrink: 0;
822
+ gap: 8px;
823
+ background: linear-gradient(
824
+ to bottom,
825
+ rgba(255, 255, 255, 0.4),
826
+ rgba(255, 255, 255, 0)
827
+ );
828
+ }
829
+
830
+ .file-side-panel-title-group {
831
+ display: flex;
832
+ align-items: center;
833
+ gap: 8px;
834
+ min-width: 0;
835
+ flex: 1;
836
+ }
837
+
838
+ .file-side-panel-icon {
839
+ display: inline-flex;
840
+ align-items: center;
841
+ justify-content: center;
842
+ color: var(--accent);
843
+ opacity: 0.85;
794
844
  }
795
845
 
796
846
  .file-side-panel-title {
797
847
  font-size: 0.875rem;
798
848
  font-weight: 600;
799
849
  color: var(--text-primary);
850
+ letter-spacing: 0.01em;
851
+ }
852
+
853
+ .file-side-panel-header-actions {
854
+ display: flex;
855
+ align-items: center;
856
+ gap: 2px;
857
+ flex-shrink: 0;
858
+ }
859
+
860
+ .file-side-panel-iconbtn {
861
+ width: 30px;
862
+ height: 30px;
863
+ display: inline-flex;
864
+ align-items: center;
865
+ justify-content: center;
866
+ padding: 0;
867
+ background: transparent;
868
+ border: 1px solid transparent;
869
+ border-radius: 8px;
870
+ color: var(--text-muted);
871
+ cursor: pointer;
872
+ transition: background var(--transition-fast),
873
+ color var(--transition-fast),
874
+ border-color var(--transition-fast),
875
+ transform var(--transition-fast);
876
+ }
877
+
878
+ .file-side-panel-iconbtn:hover {
879
+ background: var(--bg-tertiary);
880
+ color: var(--text-primary);
881
+ border-color: var(--border-subtle);
882
+ }
883
+
884
+ .file-side-panel-iconbtn:active {
885
+ transform: scale(0.94);
886
+ }
887
+
888
+ .file-side-panel-iconbtn.active {
889
+ color: var(--accent);
890
+ background: var(--accent-muted);
891
+ border-color: rgba(212, 152, 99, 0.35);
892
+ }
893
+
894
+ .file-side-panel-iconbtn.close:hover {
895
+ color: var(--text-primary);
896
+ background: rgba(220, 70, 70, 0.08);
897
+ border-color: rgba(220, 70, 70, 0.25);
898
+ }
899
+
900
+ .file-side-panel-iconbtn .wand-icon {
901
+ display: block;
902
+ }
903
+
904
+ /* The refresh icon nudges with a subtle spin on hover to reinforce its purpose. */
905
+ .file-side-panel-iconbtn:hover .wand-icon-refresh {
906
+ transform: rotate(60deg);
907
+ transition: transform 0.25s var(--ease-out-expo);
800
908
  }
801
909
 
802
910
  .file-side-panel-body {
@@ -861,30 +969,44 @@
861
969
  }
862
970
 
863
971
  .file-search-box {
864
- padding: 8px 12px;
972
+ padding: 8px 12px 10px;
865
973
  display: flex;
866
974
  align-items: center;
867
- gap: 8px;
975
+ gap: 0;
868
976
  border-bottom: 1px solid var(--border-subtle);
869
977
  flex-shrink: 0;
978
+ position: relative;
979
+ }
980
+
981
+ .file-search-icon {
982
+ position: absolute;
983
+ left: 22px;
984
+ top: 50%;
985
+ transform: translateY(-50%);
986
+ color: var(--text-muted);
987
+ pointer-events: none;
988
+ display: inline-flex;
989
+ align-items: center;
990
+ opacity: 0.7;
870
991
  }
871
992
 
872
993
  .file-search-input {
873
994
  flex: 1;
874
- padding: 6px 10px;
995
+ padding: 7px 32px 7px 30px;
875
996
  border: 1px solid var(--border);
876
- border-radius: 6px;
997
+ border-radius: 8px;
877
998
  font-size: 0.8125rem;
878
999
  font-family: var(--font-sans);
879
1000
  background: var(--bg-secondary);
880
1001
  color: var(--text-primary);
881
1002
  outline: none;
882
- transition: border-color 0.15s ease;
1003
+ transition: border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease;
883
1004
  }
884
1005
 
885
1006
  .file-search-input:focus {
886
1007
  border-color: var(--accent);
887
1008
  background: var(--bg-primary);
1009
+ box-shadow: 0 0 0 3px rgba(212, 152, 99, 0.12);
888
1010
  }
889
1011
 
890
1012
  .file-search-input::placeholder {
@@ -892,20 +1014,31 @@
892
1014
  }
893
1015
 
894
1016
  .file-search-clear {
1017
+ position: absolute;
1018
+ right: 18px;
1019
+ top: 50%;
1020
+ transform: translateY(-50%);
895
1021
  background: none;
896
1022
  border: none;
897
1023
  cursor: pointer;
898
- font-size: 1rem;
899
1024
  padding: 4px;
900
- border-radius: 4px;
1025
+ width: 22px;
1026
+ height: 22px;
1027
+ border-radius: 50%;
901
1028
  color: var(--text-muted);
902
1029
  display: none;
903
1030
  align-items: center;
904
1031
  justify-content: center;
1032
+ transition: background 0.15s ease, color 0.15s ease;
1033
+ }
1034
+
1035
+ .file-search-clear:hover {
1036
+ background: var(--bg-tertiary);
1037
+ color: var(--text-primary);
905
1038
  }
906
1039
 
907
1040
  .file-search-clear.visible {
908
- display: flex;
1041
+ display: inline-flex;
909
1042
  }
910
1043
 
911
1044
  .file-search-clear:hover {
@@ -3268,159 +3401,190 @@
3268
3401
  background: linear-gradient(180deg, rgba(255, 252, 248, 0.5) 0%, transparent 100%);
3269
3402
  }
3270
3403
 
3271
- .chat-overlay-controls {
3404
+ /* ===== Telegram 风格的"回到最新"胶囊 =====
3405
+ 规则:
3406
+ - 用户贴在底部时 → 胶囊隐藏,新消息自然出现。
3407
+ - 用户滚上去时 → 胶囊浮现在 chat-container 右下角,正好压在输入框上方边缘。
3408
+ - 没有未读时 → 紧凑圆形小胶囊,只显示一个回到底部的箭头。
3409
+ - 有未读时 → 胶囊横向展开成"↓ N 条新消息",行内显示数字。
3410
+ - 点胶囊 → 平滑滚回最新消息,未读分割线消失。 */
3411
+ .chat-unread-bubble {
3272
3412
  position: absolute;
3273
- top: 10px;
3274
- right: 10px;
3275
- display: inline-flex;
3276
- align-items: center;
3277
- z-index: 12;
3278
- pointer-events: auto;
3279
- opacity: 0;
3280
- transform: translateY(-4px);
3281
- transition: opacity 0.22s ease, transform 0.26s var(--ease-out-expo);
3282
- }
3283
-
3284
- .chat-container:hover .chat-overlay-controls,
3285
- .chat-overlay-controls:focus-within {
3286
- opacity: 1;
3287
- transform: translateY(0);
3288
- }
3289
-
3290
- .chat-follow-toggle {
3413
+ right: 14px;
3414
+ bottom: 10px;
3291
3415
  display: inline-flex;
3292
3416
  align-items: center;
3293
3417
  justify-content: center;
3294
- min-width: unset;
3295
- padding: 6px 11px;
3296
- color: var(--text-tertiary);
3297
- background: rgba(255, 250, 242, 0.7);
3298
- backdrop-filter: blur(12px);
3299
- -webkit-backdrop-filter: blur(12px);
3300
- border: 1px solid rgba(125, 91, 57, 0.1);
3418
+ gap: 6px;
3419
+ height: 32px;
3420
+ min-width: 32px;
3421
+ padding: 0 10px;
3422
+ box-sizing: border-box;
3423
+ border: 1px solid rgba(255, 255, 255, 0.72);
3301
3424
  border-radius: 999px;
3302
- box-shadow: 0 1px 4px rgba(89, 58, 32, 0.06);
3303
- cursor: pointer;
3304
- transition: all 0.2s ease;
3305
- }
3306
-
3307
- .chat-follow-toggle svg {
3308
- flex-shrink: 0;
3309
- opacity: 0.7;
3310
- transition: opacity 0.18s ease, transform 0.2s var(--ease-out-expo);
3311
- }
3312
-
3313
- .chat-follow-toggle.active {
3314
- color: var(--accent);
3315
- background: rgba(255, 250, 242, 0.88);
3316
- border-color: rgba(197, 101, 61, 0.18);
3317
- box-shadow:
3318
- 0 1px 4px rgba(89, 58, 32, 0.08),
3319
- 0 0 0 1px rgba(197, 101, 61, 0.06);
3320
- }
3321
-
3322
- .chat-follow-toggle.active svg {
3323
- opacity: 0.85;
3324
- stroke: var(--accent);
3325
- }
3326
-
3327
- .chat-follow-toggle:hover {
3328
- color: var(--text-primary);
3329
3425
  background: rgba(255, 252, 248, 0.92);
3330
- border-color: rgba(125, 91, 57, 0.16);
3331
- box-shadow: 0 2px 8px rgba(89, 58, 32, 0.1);
3332
- }
3333
-
3334
- .chat-follow-toggle:hover svg {
3335
- opacity: 0.8;
3336
- }
3337
-
3338
- .chat-follow-toggle.active:hover {
3339
- color: var(--accent-hover);
3340
- background: rgba(255, 248, 240, 0.94);
3341
- border-color: rgba(197, 101, 61, 0.24);
3342
- box-shadow: 0 2px 8px rgba(184, 92, 55, 0.12);
3343
- }
3344
-
3345
- .chat-follow-toggle.active:hover svg {
3346
- stroke: var(--accent-hover);
3347
- transform: translateY(1px);
3348
- }
3349
-
3350
- .chat-follow-toggle:active {
3351
- transform: scale(0.94);
3352
- transition-duration: 0.06s;
3353
- }
3354
-
3355
- .chat-jump-bottom {
3356
- position: absolute;
3357
- right: 14px;
3358
- bottom: 16px;
3359
- display: inline-flex;
3360
- align-items: center;
3361
- justify-content: center;
3362
- width: 34px;
3363
- height: 34px;
3364
- padding: 0;
3365
- border: 1px solid rgba(255, 255, 255, 0.65);
3366
- border-radius: 50%;
3367
- background: var(--accent);
3368
- color: #fff;
3426
+ color: var(--accent);
3427
+ font-size: 0.78rem;
3428
+ font-weight: 600;
3429
+ line-height: 1;
3430
+ letter-spacing: 0.01em;
3431
+ white-space: nowrap;
3432
+ backdrop-filter: blur(14px);
3433
+ -webkit-backdrop-filter: blur(14px);
3369
3434
  box-shadow:
3370
- 0 1px 2px rgba(89, 58, 32, 0.18),
3371
- 0 8px 22px rgba(184, 92, 55, 0.28),
3372
- inset 0 1px 0 rgba(255, 255, 255, 0.25);
3435
+ 0 1px 3px rgba(89, 58, 32, 0.12),
3436
+ 0 10px 24px rgba(184, 92, 55, 0.18),
3437
+ inset 0 1px 0 rgba(255, 255, 255, 0.6);
3373
3438
  cursor: pointer;
3374
3439
  z-index: 13;
3375
3440
  opacity: 0;
3376
- transform: translateY(10px) scale(0.86);
3441
+ transform: translateY(8px) scale(0.9);
3377
3442
  pointer-events: none;
3378
3443
  transition:
3379
3444
  opacity 0.26s var(--ease-out-expo),
3380
3445
  transform 0.32s var(--ease-spring),
3381
3446
  background 0.18s ease,
3382
3447
  border-color 0.18s ease,
3383
- box-shadow 0.2s ease;
3448
+ box-shadow 0.2s ease,
3449
+ color 0.18s ease,
3450
+ padding 0.22s var(--ease-out-expo);
3384
3451
  }
3385
3452
 
3386
- .chat-jump-bottom.visible {
3453
+ .chat-unread-bubble.visible {
3387
3454
  opacity: 1;
3388
3455
  transform: translateY(0) scale(1);
3389
3456
  pointer-events: auto;
3390
3457
  }
3391
3458
 
3392
- .chat-jump-bottom svg {
3459
+ /* 有未读时升级成"高亮色"——胶囊变成主题色实心,更显眼。 */
3460
+ .chat-unread-bubble.has-unread {
3461
+ background: var(--accent);
3462
+ color: #fff;
3463
+ border-color: rgba(255, 255, 255, 0.78);
3464
+ padding: 0 12px 0 10px;
3465
+ box-shadow:
3466
+ 0 1px 3px rgba(89, 58, 32, 0.18),
3467
+ 0 12px 28px rgba(184, 92, 55, 0.32),
3468
+ inset 0 1px 0 rgba(255, 255, 255, 0.28);
3469
+ }
3470
+
3471
+ .chat-unread-bubble-icon {
3472
+ display: inline-flex;
3473
+ align-items: center;
3474
+ justify-content: center;
3475
+ flex-shrink: 0;
3393
3476
  transition: transform 0.2s var(--ease-out-expo);
3394
3477
  }
3395
3478
 
3396
- .chat-jump-bottom:hover {
3479
+ .chat-unread-bubble:hover {
3480
+ background: rgba(255, 250, 242, 1);
3481
+ box-shadow:
3482
+ 0 2px 4px rgba(89, 58, 32, 0.16),
3483
+ 0 14px 30px rgba(184, 92, 55, 0.24),
3484
+ inset 0 1px 0 rgba(255, 255, 255, 0.7);
3485
+ }
3486
+
3487
+ .chat-unread-bubble.has-unread:hover {
3397
3488
  background: var(--accent-hover);
3398
- border-color: rgba(255, 255, 255, 0.85);
3399
3489
  box-shadow:
3400
- 0 2px 4px rgba(89, 58, 32, 0.20),
3401
- 0 12px 28px rgba(184, 92, 55, 0.38),
3490
+ 0 2px 5px rgba(89, 58, 32, 0.22),
3491
+ 0 14px 32px rgba(184, 92, 55, 0.42),
3402
3492
  inset 0 1px 0 rgba(255, 255, 255, 0.32);
3403
3493
  }
3404
3494
 
3405
- .chat-jump-bottom:hover svg {
3495
+ .chat-unread-bubble:hover .chat-unread-bubble-icon {
3406
3496
  transform: translateY(1.5px);
3407
3497
  }
3408
3498
 
3409
- .chat-jump-bottom:active {
3410
- transform: translateY(0) scale(0.92);
3411
- background: var(--accent-active);
3499
+ .chat-unread-bubble:active {
3500
+ transform: translateY(0) scale(0.94);
3412
3501
  transition-duration: 0.08s;
3413
3502
  }
3414
3503
 
3415
- .chat-jump-bottom:focus-visible {
3504
+ .chat-unread-bubble:focus-visible {
3416
3505
  outline: none;
3417
3506
  box-shadow:
3418
3507
  0 2px 4px rgba(89, 58, 32, 0.20),
3419
- 0 12px 28px rgba(184, 92, 55, 0.38),
3508
+ 0 12px 28px rgba(184, 92, 55, 0.32),
3420
3509
  inset 0 1px 0 rgba(255, 255, 255, 0.32),
3421
3510
  0 0 0 3px rgba(197, 101, 61, 0.32);
3422
3511
  }
3423
3512
 
3513
+ /* 未读计数:胶囊里行内显示,"↓ 3 条新消息"那种。
3514
+ 没有未读时 (.visible 不挂) 直接占 0 宽度,胶囊退化成圆形。 */
3515
+ .chat-unread-bubble-count {
3516
+ display: none;
3517
+ align-items: center;
3518
+ font-size: 0.78rem;
3519
+ font-weight: 600;
3520
+ line-height: 1;
3521
+ letter-spacing: 0.01em;
3522
+ pointer-events: none;
3523
+ }
3524
+
3525
+ .chat-unread-bubble-count.visible {
3526
+ display: inline-flex;
3527
+ animation: chatUnreadCountPop 0.22s var(--ease-out-back, cubic-bezier(0.34, 1.56, 0.64, 1));
3528
+ }
3529
+
3530
+ .chat-unread-bubble-count::after {
3531
+ content: " 条新消息";
3532
+ margin-left: 2px;
3533
+ font-weight: 500;
3534
+ opacity: 0.95;
3535
+ }
3536
+
3537
+ @keyframes chatUnreadCountPop {
3538
+ from { transform: scale(0.6); opacity: 0; }
3539
+ to { transform: scale(1); opacity: 1; }
3540
+ }
3541
+
3542
+ /* ===== 未读消息分隔线 =====
3543
+ 在第一条未读消息的"上方"渲染一条带文字的横线。
3544
+ column-reverse 下:分隔线在 DOM 里挂在第一条已读消息的前面(DOM 顺序:
3545
+ 新→旧),视觉上自然落在未读和已读之间。 */
3546
+ .chat-unread-divider {
3547
+ display: flex;
3548
+ align-items: center;
3549
+ gap: 12px;
3550
+ margin: 6px 4px 2px;
3551
+ padding: 0 2px;
3552
+ font-size: 0.72rem;
3553
+ font-weight: 500;
3554
+ color: var(--accent);
3555
+ letter-spacing: 0.04em;
3556
+ user-select: none;
3557
+ pointer-events: none;
3558
+ opacity: 0;
3559
+ animation: chatUnreadDividerIn 0.32s var(--ease-out-expo) forwards;
3560
+ }
3561
+
3562
+ .chat-unread-divider-line {
3563
+ flex: 1;
3564
+ height: 1px;
3565
+ background: linear-gradient(
3566
+ to right,
3567
+ rgba(197, 101, 61, 0.05),
3568
+ rgba(197, 101, 61, 0.32),
3569
+ rgba(197, 101, 61, 0.05)
3570
+ );
3571
+ }
3572
+
3573
+ .chat-unread-divider-label {
3574
+ flex-shrink: 0;
3575
+ padding: 3px 10px;
3576
+ background: rgba(255, 248, 240, 0.95);
3577
+ border: 1px solid rgba(197, 101, 61, 0.18);
3578
+ border-radius: 999px;
3579
+ box-shadow: 0 1px 3px rgba(184, 92, 55, 0.1);
3580
+ white-space: nowrap;
3581
+ }
3582
+
3583
+ @keyframes chatUnreadDividerIn {
3584
+ from { opacity: 0; transform: translateY(-2px); }
3585
+ to { opacity: 1; transform: translateY(0); }
3586
+ }
3587
+
3424
3588
  .chat-container.active { display: flex; }
3425
3589
 
3426
3590
  .chat-container::after {
@@ -6681,7 +6845,11 @@
6681
6845
  display: flex;
6682
6846
  align-items: center;
6683
6847
  justify-content: center;
6684
- padding: 32px;
6848
+ /* 给安全区让位, 避免 iPhone notch / Dynamic Island / 横屏圆角切到模态内容 */
6849
+ padding-top: max(32px, var(--wand-safe-top));
6850
+ padding-bottom: max(32px, var(--wand-safe-bottom));
6851
+ padding-left: max(32px, var(--wand-safe-left));
6852
+ padding-right: max(32px, var(--wand-safe-right));
6685
6853
  animation: liquidBackdropIn 0.28s var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1)) both;
6686
6854
  }
6687
6855
 
@@ -8206,6 +8374,10 @@
8206
8374
  .app-container {
8207
8375
  min-height: 100dvh;
8208
8376
  height: 100dvh;
8377
+ /* 与 body 同步:键盘弹起时 JS 注入 --app-viewport-height,
8378
+ app-container 跟随 visualViewport 收缩,input-panel 自动贴键盘上沿;
8379
+ 键盘收起后变量被移除,回到 100dvh,input-panel 自然回到屏幕底部。 */
8380
+ height: var(--app-viewport-height, 100dvh);
8209
8381
  overflow: hidden;
8210
8382
  }
8211
8383
 
@@ -8347,7 +8519,11 @@
8347
8519
  padding-bottom: calc(6px + var(--safe-bottom, 0px) + var(--keyboard-offset, 0px));
8348
8520
  flex: 0 0 auto;
8349
8521
  margin-top: auto;
8350
- background: linear-gradient(180deg, rgba(246, 241, 232, 0.5) 0%, rgba(246, 241, 232, 0.88) 30%, rgba(246, 241, 232, 0.96) 100%);
8522
+ /* 渐变只覆盖上半段做平滑过渡,下半段用纯色铺满 safe-area 区域,
8523
+ 避免 iOS Safari 在 home indicator / 安全区域露出 html 背景。 */
8524
+ background:
8525
+ linear-gradient(180deg, rgba(246, 241, 232, 0.5) 0%, rgba(246, 241, 232, 0.88) 30%, #f6f1e8 100%),
8526
+ #f6f1e8;
8351
8527
  backdrop-filter: blur(12px);
8352
8528
  }
8353
8529
 
@@ -8505,8 +8681,7 @@
8505
8681
  }
8506
8682
 
8507
8683
  /* 回到底部按钮 - 紧凑 */
8508
- .terminal-jump-bottom,
8509
- .chat-jump-bottom {
8684
+ .terminal-jump-bottom {
8510
8685
  width: 32px;
8511
8686
  height: 32px;
8512
8687
  padding: 0;
@@ -8514,6 +8689,23 @@
8514
8689
  bottom: 12px;
8515
8690
  }
8516
8691
 
8692
+ /* 移动端的未读胶囊 —— 略矮一点,贴紧 chat-container 的右下角,
8693
+ 离输入框边缘更近、避开虚拟键盘那一坨。 */
8694
+ .chat-unread-bubble {
8695
+ height: 30px;
8696
+ min-width: 30px;
8697
+ right: 10px;
8698
+ bottom: 8px;
8699
+ padding: 0 9px;
8700
+ font-size: 0.72rem;
8701
+ }
8702
+ .chat-unread-bubble.has-unread {
8703
+ padding: 0 11px 0 9px;
8704
+ }
8705
+ .chat-unread-bubble-count {
8706
+ font-size: 0.72rem;
8707
+ }
8708
+
8517
8709
  /* 小键盘 FAB */
8518
8710
  .mini-keyboard-fab {
8519
8711
  width: 40px;
@@ -8659,6 +8851,9 @@
8659
8851
  /* 模态框移动端优化 */
8660
8852
  .modal-backdrop {
8661
8853
  padding: 8px;
8854
+ padding-top: max(8px, var(--wand-safe-top));
8855
+ padding-left: max(8px, var(--wand-safe-left));
8856
+ padding-right: max(8px, var(--wand-safe-right));
8662
8857
  align-items: flex-end;
8663
8858
  }
8664
8859
 
@@ -8682,10 +8877,14 @@
8682
8877
  /* 文件面板 */
8683
8878
  .main-content.file-panel-open { margin-right: 0; }
8684
8879
  .file-side-panel { width: 100%; }
8685
- .file-side-panel-header { padding: 6px 8px; min-height: 36px; }
8686
- .file-explorer-header { padding: 4px 6px; }
8687
- .file-search-box { padding: 4px; }
8688
- .file-search-input { min-height: 32px; font-size: 14px; }
8880
+ .file-side-panel-header { padding: 10px 12px; min-height: 48px; }
8881
+ .file-side-panel-iconbtn { width: 34px; height: 34px; }
8882
+ .file-explorer-header { padding: 6px 10px 4px; gap: 6px; }
8883
+ .file-explorer-up { width: 32px; height: 32px; }
8884
+ .file-search-box { padding: 6px 10px 8px; }
8885
+ .file-search-icon { left: 20px; }
8886
+ .file-search-clear { right: 16px; }
8887
+ .file-search-input { min-height: 34px; font-size: 14px; padding: 7px 32px 7px 30px; }
8689
8888
  .file-item { padding: 6px 8px; min-height: 32px; }
8690
8889
 
8691
8890
  /* 欢迎页移动端 */
@@ -10638,12 +10837,16 @@
10638
10837
  flex-shrink: 0;
10639
10838
  }
10640
10839
  .file-preview-toolbar-btn {
10641
- padding: 4px 6px;
10840
+ padding: 0 6px;
10642
10841
  font-size: 0.75rem;
10643
10842
  }
10644
10843
  .file-preview-close {
10645
- font-size: 1.375rem;
10646
- padding: 6px 10px;
10844
+ width: 34px;
10845
+ height: 34px;
10846
+ padding: 0;
10847
+ }
10848
+ .file-preview-icon {
10849
+ font-size: 1.125rem;
10647
10850
  }
10648
10851
  /* 底部安全区(小白条 / 圆角) */
10649
10852
  .file-preview-body {
@@ -10655,8 +10858,8 @@
10655
10858
  display: flex;
10656
10859
  align-items: center;
10657
10860
  gap: 12px;
10658
- padding: 10px 16px;
10659
- border-bottom: 1px solid var(--border);
10861
+ padding: 12px 18px;
10862
+ border-bottom: 1px solid var(--border-subtle);
10660
10863
  background: var(--bg-secondary);
10661
10864
  flex-shrink: 0;
10662
10865
  }
@@ -10664,57 +10867,114 @@
10664
10867
  .file-preview-title {
10665
10868
  display: flex;
10666
10869
  align-items: center;
10667
- gap: 8px;
10870
+ gap: 10px;
10668
10871
  font-size: 0.9375rem;
10669
10872
  font-weight: 600;
10670
10873
  color: var(--text-primary);
10874
+ flex: 1;
10875
+ min-width: 0;
10876
+ }
10877
+
10878
+ .file-preview-icon {
10879
+ font-size: 1.25rem;
10880
+ line-height: 1;
10671
10881
  flex-shrink: 0;
10672
10882
  }
10673
10883
 
10884
+ .file-preview-name-block {
10885
+ display: flex;
10886
+ flex-direction: column;
10887
+ gap: 2px;
10888
+ min-width: 0;
10889
+ flex: 1;
10890
+ }
10891
+
10892
+ .file-preview-name-row {
10893
+ display: flex;
10894
+ align-items: baseline;
10895
+ gap: 8px;
10896
+ min-width: 0;
10897
+ }
10898
+
10674
10899
  .file-preview-filename {
10675
10900
  overflow: hidden;
10676
10901
  text-overflow: ellipsis;
10677
10902
  white-space: nowrap;
10903
+ font-size: 0.9375rem;
10904
+ font-weight: 600;
10905
+ color: var(--text-primary);
10906
+ letter-spacing: 0.01em;
10907
+ min-width: 0;
10678
10908
  }
10679
10909
 
10680
10910
  .file-preview-path {
10681
- font-size: 0.75rem;
10911
+ font-size: 0.7rem;
10682
10912
  color: var(--text-muted);
10683
10913
  font-family: var(--font-mono);
10684
10914
  overflow: hidden;
10685
10915
  text-overflow: ellipsis;
10686
10916
  white-space: nowrap;
10687
- flex: 1;
10688
10917
  min-width: 0;
10689
- margin: 0 12px;
10918
+ opacity: 0.8;
10919
+ }
10920
+
10921
+ .file-preview-dirty {
10922
+ font-size: 0.6875rem;
10923
+ color: #c66a1a;
10924
+ font-weight: 600;
10925
+ letter-spacing: 0.02em;
10926
+ flex-shrink: 0;
10927
+ padding: 1px 8px;
10928
+ background: rgba(198, 106, 26, 0.1);
10929
+ border: 1px solid rgba(198, 106, 26, 0.25);
10930
+ border-radius: 999px;
10931
+ animation: dirty-pulse 1.6s ease-in-out infinite;
10932
+ }
10933
+
10934
+ @keyframes dirty-pulse {
10935
+ 0%, 100% { opacity: 0.65; }
10936
+ 50% { opacity: 1; }
10690
10937
  }
10691
10938
 
10692
10939
  .file-preview-lang {
10693
- font-size: 0.75rem;
10694
- font-weight: 500;
10940
+ font-size: 0.6875rem;
10941
+ font-weight: 600;
10695
10942
  padding: 2px 8px;
10696
- border-radius: 4px;
10697
- background: var(--bg-tertiary);
10698
- color: var(--text-secondary);
10699
- border: 1px solid var(--border);
10943
+ border-radius: 999px;
10944
+ background: var(--accent-muted);
10945
+ color: var(--accent);
10946
+ border: 1px solid rgba(212, 152, 99, 0.3);
10947
+ letter-spacing: 0.04em;
10948
+ text-transform: uppercase;
10949
+ flex-shrink: 0;
10950
+ align-self: center;
10700
10951
  }
10701
10952
 
10702
10953
  .file-preview-close {
10703
10954
  background: none;
10704
- border: none;
10705
- font-size: 1.25rem;
10955
+ border: 1px solid transparent;
10706
10956
  cursor: pointer;
10707
10957
  color: var(--text-muted);
10708
- padding: 4px 8px;
10709
- border-radius: var(--radius-sm);
10710
- transition: background 0.15s, color 0.15s;
10958
+ width: 32px;
10959
+ height: 32px;
10960
+ padding: 0;
10961
+ border-radius: 8px;
10962
+ display: inline-flex;
10963
+ align-items: center;
10964
+ justify-content: center;
10965
+ transition: background 0.15s, color 0.15s, border-color 0.15s, transform 0.15s;
10711
10966
  line-height: 1;
10712
10967
  flex-shrink: 0;
10713
10968
  }
10714
10969
 
10715
10970
  .file-preview-close:hover {
10716
- background: var(--bg-tertiary);
10971
+ background: rgba(220, 70, 70, 0.08);
10717
10972
  color: var(--text-primary);
10973
+ border-color: rgba(220, 70, 70, 0.25);
10974
+ }
10975
+
10976
+ .file-preview-close:active {
10977
+ transform: scale(0.94);
10718
10978
  }
10719
10979
 
10720
10980
  .file-preview-body {
@@ -11031,31 +11291,31 @@
11031
11291
  }
11032
11292
 
11033
11293
  /* ── File explorer header extras ── */
11034
- .file-explorer-up,
11035
- .file-explorer-toggle-hidden {
11036
- background: none;
11037
- border: none;
11294
+ .file-explorer-up {
11295
+ background: transparent;
11296
+ border: 1px solid transparent;
11038
11297
  cursor: pointer;
11039
- font-size: 1rem;
11040
- padding: 4px;
11041
- width: 28px;
11042
- height: 28px;
11043
- border-radius: 6px;
11298
+ padding: 0;
11299
+ width: 30px;
11300
+ height: 30px;
11301
+ border-radius: 8px;
11044
11302
  color: var(--text-muted);
11045
- display: flex;
11303
+ display: inline-flex;
11046
11304
  align-items: center;
11047
11305
  justify-content: center;
11048
- transition: background var(--transition-fast), color var(--transition-fast);
11306
+ transition: background var(--transition-fast),
11307
+ color var(--transition-fast),
11308
+ border-color var(--transition-fast),
11309
+ transform var(--transition-fast);
11049
11310
  flex-shrink: 0;
11050
11311
  }
11051
- .file-explorer-up:hover,
11052
- .file-explorer-toggle-hidden:hover {
11312
+ .file-explorer-up:hover {
11053
11313
  background: var(--bg-tertiary);
11054
- color: var(--text-secondary);
11314
+ color: var(--text-primary);
11315
+ border-color: var(--border-subtle);
11055
11316
  }
11056
- .file-explorer-toggle-hidden.active {
11057
- color: var(--accent);
11058
- background: var(--accent-muted);
11317
+ .file-explorer-up:active {
11318
+ transform: translateY(-1px);
11059
11319
  }
11060
11320
 
11061
11321
  /* ── File tree size/meta column ── */
@@ -11147,29 +11407,127 @@
11147
11407
  margin-left: 8px;
11148
11408
  }
11149
11409
  .file-preview-toolbar-btn {
11150
- background: none;
11151
- border: 1px solid transparent;
11410
+ background: var(--bg-primary);
11411
+ border: 1px solid var(--border-subtle);
11152
11412
  cursor: pointer;
11153
- padding: 4px 8px;
11154
- min-width: 32px;
11155
- height: 28px;
11156
- border-radius: 6px;
11157
- color: var(--text-muted);
11413
+ padding: 0 8px;
11414
+ min-width: 30px;
11415
+ height: 30px;
11416
+ gap: 6px;
11417
+ border-radius: 8px;
11418
+ color: var(--text-secondary);
11158
11419
  font-size: 0.75rem;
11159
11420
  display: inline-flex;
11160
11421
  align-items: center;
11161
11422
  justify-content: center;
11162
- transition: background 0.15s, color 0.15s, border-color 0.15s;
11423
+ transition: background 0.15s, color 0.15s, border-color 0.15s,
11424
+ box-shadow 0.15s, transform 0.1s;
11163
11425
  }
11164
11426
  .file-preview-toolbar-btn:hover {
11165
11427
  background: var(--bg-tertiary);
11166
11428
  color: var(--text-primary);
11167
11429
  border-color: var(--border);
11430
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
11431
+ }
11432
+ .file-preview-toolbar-btn:active {
11433
+ transform: scale(0.95);
11434
+ }
11435
+ .file-preview-toolbar-btn:disabled {
11436
+ opacity: 0.5;
11437
+ cursor: not-allowed;
11438
+ transform: none;
11439
+ }
11440
+ .file-preview-toolbar-btn .toolbar-icon {
11441
+ display: inline-flex;
11442
+ align-items: center;
11443
+ justify-content: center;
11444
+ }
11445
+ .file-preview-toolbar-btn .toolbar-text {
11446
+ font-weight: 600;
11447
+ letter-spacing: 0.01em;
11168
11448
  }
11169
11449
  .file-preview-toolbar-btn.toolbar-active {
11170
11450
  background: var(--accent-muted);
11171
11451
  color: var(--accent);
11452
+ border-color: rgba(212, 152, 99, 0.4);
11453
+ }
11454
+ .file-preview-toolbar-btn.primary {
11455
+ background: var(--accent);
11456
+ color: #fff;
11172
11457
  border-color: var(--accent);
11458
+ box-shadow: 0 1px 2px rgba(89, 58, 32, 0.15);
11459
+ }
11460
+ .file-preview-toolbar-btn.primary:hover {
11461
+ background: #b87a47;
11462
+ border-color: #b87a47;
11463
+ color: #fff;
11464
+ box-shadow: 0 2px 6px rgba(89, 58, 32, 0.2);
11465
+ }
11466
+ .file-preview-toolbar-btn.danger {
11467
+ color: #c0392b;
11468
+ }
11469
+ .file-preview-toolbar-btn.danger:hover {
11470
+ background: rgba(220, 70, 70, 0.08);
11471
+ border-color: rgba(220, 70, 70, 0.3);
11472
+ color: #a02818;
11473
+ }
11474
+
11475
+ /* Grouped chip (e.g. font-size −/+ combo) */
11476
+ .file-preview-toolbar-group {
11477
+ display: inline-flex;
11478
+ align-items: stretch;
11479
+ border: 1px solid var(--border-subtle);
11480
+ border-radius: 8px;
11481
+ overflow: hidden;
11482
+ background: var(--bg-primary);
11483
+ }
11484
+ .file-preview-toolbar-group .file-preview-toolbar-btn {
11485
+ border: none;
11486
+ border-radius: 0;
11487
+ background: transparent;
11488
+ min-width: 28px;
11489
+ height: 28px;
11490
+ box-shadow: none;
11491
+ }
11492
+ .file-preview-toolbar-group .file-preview-toolbar-btn:hover {
11493
+ background: var(--bg-tertiary);
11494
+ }
11495
+ .file-preview-toolbar-grouplabel {
11496
+ display: inline-flex;
11497
+ align-items: center;
11498
+ justify-content: center;
11499
+ padding: 0 6px;
11500
+ color: var(--text-muted);
11501
+ border-left: 1px solid var(--border-subtle);
11502
+ border-right: 1px solid var(--border-subtle);
11503
+ background: var(--bg-secondary);
11504
+ }
11505
+
11506
+ /* In edit mode the toolbar shows only save / revert / cancel; emphasize the
11507
+ primary save action visually. */
11508
+ .file-preview-toolbar.editing {
11509
+ gap: 6px;
11510
+ }
11511
+
11512
+ /* Mobile: shrink toolbar buttons a hair so they fit. */
11513
+ @media (max-width: 768px) {
11514
+ .file-preview-toolbar-btn {
11515
+ height: 28px;
11516
+ min-width: 28px;
11517
+ padding: 0 6px;
11518
+ }
11519
+ .file-preview-toolbar-btn .toolbar-text {
11520
+ display: none;
11521
+ }
11522
+ .file-preview-toolbar-btn.primary .toolbar-text {
11523
+ display: inline-flex;
11524
+ }
11525
+ }
11526
+
11527
+ /* ── Inline file-icon glyph (header) ── */
11528
+ .wand-icon {
11529
+ flex-shrink: 0;
11530
+ vertical-align: middle;
11173
11531
  }
11174
11532
 
11175
11533
  /* ── Code preview wrap mode ── */
@@ -11178,6 +11536,50 @@
11178
11536
  word-break: break-word;
11179
11537
  }
11180
11538
 
11539
+ /* ── Code editor (inline file edit mode) ── */
11540
+ .file-preview-body.editing {
11541
+ padding: 0;
11542
+ background: var(--bg-primary);
11543
+ }
11544
+ .code-editor-wrapper {
11545
+ display: flex;
11546
+ width: 100%;
11547
+ height: 100%;
11548
+ min-height: 0;
11549
+ background: var(--bg-primary);
11550
+ }
11551
+ .code-editor-textarea {
11552
+ flex: 1;
11553
+ width: 100%;
11554
+ height: 100%;
11555
+ min-height: 0;
11556
+ box-sizing: border-box;
11557
+ padding: 16px 20px;
11558
+ margin: 0;
11559
+ border: none;
11560
+ outline: none;
11561
+ resize: none;
11562
+ font-family: var(--font-mono);
11563
+ font-size: var(--font-size-sm);
11564
+ line-height: var(--line-height);
11565
+ color: var(--text-primary);
11566
+ background: var(--bg-primary);
11567
+ tab-size: 2;
11568
+ -moz-tab-size: 2;
11569
+ white-space: pre;
11570
+ overflow: auto;
11571
+ caret-color: var(--accent);
11572
+ scrollbar-width: thin;
11573
+ scrollbar-color: var(--border-default) transparent;
11574
+ }
11575
+ .code-editor-textarea::-webkit-scrollbar { width: 8px; height: 8px; }
11576
+ .code-editor-textarea::-webkit-scrollbar-track { background: transparent; }
11577
+ .code-editor-textarea::-webkit-scrollbar-thumb { background: var(--border-default); border-radius: 4px; }
11578
+ .code-editor-textarea::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
11579
+ .code-editor-textarea:focus {
11580
+ box-shadow: inset 0 0 0 2px rgba(212, 152, 99, 0.15);
11581
+ }
11582
+
11181
11583
  /* ── Image preview ── */
11182
11584
  .file-preview-body.kind-image {
11183
11585
  display: flex;
@@ -13121,7 +13523,11 @@
13121
13523
  display: flex;
13122
13524
  align-items: center;
13123
13525
  justify-content: center;
13124
- padding: 24px;
13526
+ /* 给安全区让位, 避免对话框在 iPhone 横屏 / 小屏被刘海或 Home Indicator 切到 */
13527
+ padding-top: max(24px, var(--wand-safe-top));
13528
+ padding-bottom: max(24px, var(--wand-safe-bottom));
13529
+ padding-left: max(24px, var(--wand-safe-left));
13530
+ padding-right: max(24px, var(--wand-safe-right));
13125
13531
  background: rgba(20, 14, 8, 0.34);
13126
13532
  backdrop-filter: blur(18px) saturate(140%);
13127
13533
  -webkit-backdrop-filter: blur(18px) saturate(140%);