@hanology/cham-browser 0.4.28 → 0.4.30

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanology/cham-browser",
3
- "version": "0.4.28",
3
+ "version": "0.4.30",
4
4
  "description": "CHAM — browser-compatible parser, serializer, and site generator for Classical Han Annotated Markdown",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -384,6 +384,12 @@ onBeforeUnmount(() => {
384
384
  justify-content: center;
385
385
  }
386
386
 
387
+ .ann-pane.vertical .ann-pane-handle {
388
+ right: auto;
389
+ left: -6px;
390
+ cursor: col-resize;
391
+ }
392
+
387
393
  .ann-handle-grip {
388
394
  display: block;
389
395
  width: 3px;
@@ -411,7 +417,7 @@ onBeforeUnmount(() => {
411
417
  }
412
418
 
413
419
  @media (max-width: 768px) {
414
- .ann-pane {
420
+ .ann-pane:not(.vertical) {
415
421
  width: 100% !important;
416
422
  height: auto;
417
423
  max-height: 55vh;
@@ -422,70 +428,90 @@ onBeforeUnmount(() => {
422
428
  border-radius: 14px 14px 0 0;
423
429
  box-shadow: 0 -4px 24px rgba(var(--shadow-rgb), 0.08);
424
430
  }
425
- .ann-pane-handle {
431
+ .ann-pane:not(.vertical) .ann-pane-handle {
426
432
  display: none;
427
433
  }
428
- .ann-pane-enter-from,
429
- .ann-pane-leave-to {
434
+ .ann-pane:not(.vertical).ann-pane-enter-from,
435
+ .ann-pane:not(.vertical).ann-pane-leave-to {
430
436
  transform: translateY(100%);
431
437
  }
432
438
  }
433
439
 
434
440
  /* ─── Vertical mode ─── */
435
- /* Pane stays in horizontal-tb; only the headword uses vertical-rl */
441
+
442
+ .ann-pane.vertical {
443
+ writing-mode: vertical-rl;
444
+ text-orientation: mixed;
445
+ border-right: none;
446
+ border-left: 1px solid var(--border);
447
+ box-shadow: -4px 0 24px rgba(var(--shadow-rgb), 0.06);
448
+ }
449
+
450
+ .ann-pane.vertical .ann-pane-header {
451
+ writing-mode: vertical-rl;
452
+ text-orientation: mixed;
453
+ border-bottom: none;
454
+ border-left: 1px solid var(--border-light);
455
+ padding: 16px 12px;
456
+ flex-direction: row;
457
+ align-items: center;
458
+ }
459
+
460
+ .ann-pane.vertical .ann-pane-body {
461
+ overflow-y: hidden;
462
+ overflow-x: auto;
463
+ overscroll-behavior: contain;
464
+ }
436
465
 
437
466
  .ann-pane.vertical .ann-pane-entry {
467
+ padding: 12px 0;
468
+ border-bottom: none;
469
+ border-right: 1px solid var(--border-light);
470
+ border-left: none;
438
471
  display: flex;
439
- flex-direction: row-reverse;
440
- gap: 8px;
472
+ flex-direction: row;
441
473
  align-items: flex-start;
442
- border-left: none;
443
- border-right: 3px solid transparent;
474
+ gap: 0;
475
+ }
476
+
477
+ .ann-pane.vertical .ann-pane-entry:last-child {
478
+ border-right: none;
444
479
  }
445
480
 
446
481
  .ann-pane.vertical .ann-pane-entry.active {
482
+ border-left-color: transparent;
447
483
  border-right-color: var(--vermillion);
448
484
  }
449
485
 
450
486
  .ann-pane.vertical .ann-pane-entry.active.pronunciation {
487
+ border-left-color: transparent;
451
488
  border-right-color: var(--jade);
452
489
  }
453
490
 
454
- .ann-pane.vertical .ann-pane-entry-main {
455
- flex: 1;
456
- min-width: 0;
457
- }
458
-
459
- /* Vertical headword — only this element uses vertical writing */
460
- .ann-pane-v-word {
491
+ .ann-pane.vertical .ann-pane-v-word {
461
492
  writing-mode: vertical-rl;
462
493
  text-orientation: upright;
463
494
  display: flex;
464
- flex-direction: row;
495
+ flex-direction: column;
465
496
  align-items: center;
466
- gap: 6px;
467
- padding: 0 8px;
497
+ gap: 4px;
498
+ padding: 0 6px;
468
499
  border-left: 1px solid var(--border-light);
469
500
  flex-shrink: 0;
470
501
  }
471
502
 
472
- .ann-pane-word-v {
473
- font-family: var(--serif);
474
- font-size: 20px;
475
- font-weight: 900;
476
- letter-spacing: 6px;
477
- color: var(--ink);
503
+ .ann-pane.vertical .ann-pane-entry-main {
504
+ padding: 0 8px;
478
505
  }
479
506
 
480
- .ann-pane-idx-v {
481
- font-family: var(--serif);
482
- font-size: 12px;
483
- font-weight: 700;
484
- color: var(--vermillion);
507
+ .ann-pane.vertical .ann-pane-entry-head {
508
+ flex-direction: column;
509
+ align-items: flex-start;
510
+ gap: 4px;
485
511
  }
486
512
 
487
- .ann-pane-entry.active.pronunciation .ann-pane-idx-v {
488
- color: var(--jade);
513
+ .ann-pane.vertical .ann-pane-entry-body {
514
+ padding-left: 0;
489
515
  }
490
516
 
491
517
  .ann-pane.vertical .ann-pane-text {
@@ -493,6 +519,21 @@ onBeforeUnmount(() => {
493
519
  letter-spacing: 1px;
494
520
  }
495
521
 
522
+ .ann-pane.vertical .ann-pane-close {
523
+ margin-left: 0;
524
+ margin-top: auto;
525
+ }
526
+
527
+ .ann-pane.vertical .ann-pane-count,
528
+ .ann-pane.vertical .ann-pane-kind,
529
+ .ann-pane.vertical .ann-pane-layer {
530
+ writing-mode: horizontal-tb;
531
+ }
532
+
533
+ .ann-pane.vertical .ann-pane-pron {
534
+ writing-mode: horizontal-tb;
535
+ }
536
+
496
537
  @media (max-width: 768px) {
497
538
  .ann-pane.vertical {
498
539
  width: 80vw !important;
@@ -500,10 +541,11 @@ onBeforeUnmount(() => {
500
541
  max-height: none !important;
501
542
  top: 0 !important;
502
543
  bottom: auto !important;
503
- border-right: 1px solid var(--border) !important;
544
+ border-left: 1px solid var(--border) !important;
545
+ border-right: none !important;
504
546
  border-top: none !important;
505
547
  border-radius: 0 !important;
506
- box-shadow: 4px 0 24px rgba(var(--shadow-rgb), 0.06) !important;
548
+ box-shadow: -4px 0 24px rgba(var(--shadow-rgb), 0.06) !important;
507
549
  }
508
550
  .ann-pane.vertical .ann-pane-handle {
509
551
  display: flex !important;
@@ -417,6 +417,30 @@ onBeforeUnmount(() => {
417
417
  .ann-sheet { display: none; }
418
418
  }
419
419
 
420
+ /* ─── Mobile sheet vertical mode ─── */
421
+ .ann-sheet.vertical {
422
+ left: 0;
423
+ right: auto;
424
+ bottom: auto;
425
+ top: 0;
426
+ width: 85vw;
427
+ max-height: none;
428
+ height: 100vh;
429
+ border-top: none;
430
+ border-radius: 0;
431
+ border-right: 1px solid var(--border);
432
+ box-shadow: 4px 0 32px rgba(var(--shadow-rgb), 0.15);
433
+ }
434
+
435
+ .ann-sheet.vertical .ann-sheet-handle {
436
+ display: none;
437
+ }
438
+
439
+ .ann-sheet.vertical.ann-sheet-enter-from,
440
+ .ann-sheet.vertical.ann-sheet-leave-to {
441
+ transform: translateX(-100%);
442
+ }
443
+
420
444
  /* ─── Vertical mode ─── */
421
445
  .ann-card.vertical {
422
446
  writing-mode: vertical-rl;
@@ -427,10 +451,19 @@ onBeforeUnmount(() => {
427
451
  }
428
452
 
429
453
  .ann-card.vertical .ann-card-head {
430
- writing-mode: horizontal-tb;
431
- padding: 10px 6px;
454
+ writing-mode: vertical-rl;
455
+ text-orientation: upright;
456
+ padding: 6px 10px;
432
457
  border-bottom: none;
433
458
  border-left: 1px solid var(--border-light);
459
+ flex-direction: row;
460
+ align-items: center;
461
+ }
462
+ .ann-card.vertical .ann-headword {
463
+ letter-spacing: 6px;
464
+ }
465
+ .ann-card.vertical .ann-badge-count {
466
+ writing-mode: horizontal-tb;
434
467
  }
435
468
 
436
469
  .ann-card.vertical .ann-card-scroll {
@@ -482,7 +515,7 @@ onBeforeUnmount(() => {
482
515
  }
483
516
 
484
517
  .ann-sheet-body.vertical {
485
- flex-direction: row-reverse;
518
+ flex-direction: row;
486
519
  }
487
520
 
488
521
  .ann-sheet-body.vertical > .ann-sheet-head {
@@ -527,8 +560,13 @@ onBeforeUnmount(() => {
527
560
  }
528
561
 
529
562
  .ann-sheet-body.vertical .ann-sheet-scroll {
563
+ writing-mode: vertical-rl;
564
+ text-orientation: mixed;
530
565
  flex: 1;
531
566
  min-width: 0;
567
+ overflow-x: auto;
568
+ overflow-y: hidden;
569
+ padding: 4px 12px 24px;
532
570
  }
533
571
 
534
572
  .ann-sheet-body.vertical .ann-pron-h {
@@ -538,5 +576,16 @@ onBeforeUnmount(() => {
538
576
  .ann-sheet-body.vertical .ann-text {
539
577
  white-space: pre-line;
540
578
  line-height: 2;
579
+ letter-spacing: 1px;
580
+ }
581
+
582
+ .ann-sheet-body.vertical .ann-entry {
583
+ border-bottom: none;
584
+ border-right: 1px solid var(--border-light);
585
+ padding: 0 10px;
586
+ }
587
+
588
+ .ann-sheet-body.vertical .ann-entry:last-child {
589
+ border-right: none;
541
590
  }
542
591
  </style>
@@ -48,18 +48,20 @@ onUnmounted(() => window.removeEventListener('scroll', onScroll))
48
48
  align-items: center;
49
49
  justify-content: center;
50
50
  box-shadow: 0 4px 16px rgba(var(--shadow-rgb), 0.1);
51
- transition: all 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
51
+ transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
52
+ backdrop-filter: blur(8px);
53
+ -webkit-backdrop-filter: blur(8px);
52
54
  }
53
55
 
54
56
  @media (max-width: 768px) {
55
57
  .btt { bottom: 88px; right: 16px; width: 36px; height: 36px; }
56
58
  }
57
59
  .btt:hover {
58
- background: var(--ink);
59
- color: var(--paper);
60
- border-color: var(--ink);
61
- transform: translateY(-2px);
62
- box-shadow: 0 8px 24px rgba(var(--shadow-rgb), 0.16);
60
+ background: var(--vermillion);
61
+ color: #fff;
62
+ border-color: var(--vermillion);
63
+ transform: translateY(-3px);
64
+ box-shadow: 0 8px 24px rgba(194, 58, 43, 0.2);
63
65
  }
64
66
 
65
67
  .btt-enter-active { transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); }
@@ -44,7 +44,8 @@ function onTap(event: MouseEvent) {
44
44
  <div
45
45
  v-for="(_, i) in verses"
46
46
  :key="i"
47
- class="h-display-line"
47
+ class="h-display-line h-verse-anim"
48
+ :style="{ animationDelay: (0.15 + i * 0.08) + 's' }"
48
49
  v-html="verseHtml(i)"
49
50
  />
50
51
  </div>
@@ -59,6 +60,7 @@ function onTap(event: MouseEvent) {
59
60
  padding: 40px 56px;
60
61
  box-shadow: 0 4px 16px rgba(var(--shadow-rgb), 0.08);
61
62
  text-align: center;
63
+ animation: poemReveal 0.5s var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1)) both;
62
64
  }
63
65
  .h-display-title {
64
66
  font-size: 32px; font-weight: 900;
@@ -71,20 +73,32 @@ function onTap(event: MouseEvent) {
71
73
  }
72
74
  .h-display-line {
73
75
  font-size: var(--main-font-size, 24px); line-height: 2.6;
74
- letter-spacing: 4px; color: var(--ink);
76
+ letter-spacing: 4px; color: var(--ink); white-space: pre-wrap;
77
+ }
78
+ .h-verse-anim {
79
+ animation: verseFade 0.45s var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1)) both;
80
+ }
81
+ @keyframes poemReveal {
82
+ from { opacity: 0; transform: translateY(16px) scale(0.98); }
83
+ to { opacity: 1; transform: translateY(0) scale(1); }
84
+ }
85
+ @keyframes verseFade {
86
+ from { opacity: 0; transform: translateY(8px); }
87
+ to { opacity: 1; transform: translateY(0); }
75
88
  }
76
89
 
77
90
  :deep(.ann-target) {
78
91
  border-bottom: 2px solid var(--vermillion);
79
92
  cursor: help;
80
- transition: background 0.15s;
93
+ transition: background 0.2s ease, box-shadow 0.2s ease;
81
94
  }
82
95
  :deep(.ann-target.ann-overlap) {
83
96
  border-bottom-width: 3px;
84
97
  border-bottom-style: double;
85
98
  }
86
99
  :deep(.ann-target:hover) {
87
- background: rgba(194, 58, 43, 0.08);
100
+ background: rgba(194, 58, 43, 0.1);
101
+ box-shadow: 0 2px 8px rgba(194, 58, 43, 0.08);
88
102
  }
89
103
  :deep(.ann-num) {
90
104
  font-size: 10px;
@@ -96,7 +110,8 @@ function onTap(event: MouseEvent) {
96
110
  letter-spacing: 0;
97
111
  }
98
112
  :deep(.ann-target.pronunciation:hover) {
99
- background: rgba(58, 107, 94, 0.08);
113
+ background: rgba(58, 107, 94, 0.1);
114
+ box-shadow: 0 2px 8px rgba(58, 107, 94, 0.08);
100
115
  }
101
116
  :deep(.ann-target.pronunciation) {
102
117
  border-bottom-color: var(--jade);
@@ -117,19 +132,24 @@ function onTap(event: MouseEvent) {
117
132
  border-bottom-color: var(--ann-allusion);
118
133
  }
119
134
  :deep(.ann-target.person:hover) {
120
- background: rgba(58, 90, 140, 0.08);
135
+ background: rgba(58, 90, 140, 0.1);
136
+ box-shadow: 0 2px 8px rgba(58, 90, 140, 0.08);
121
137
  }
122
138
  :deep(.ann-target.place:hover) {
123
- background: rgba(139, 105, 20, 0.08);
139
+ background: rgba(139, 105, 20, 0.1);
140
+ box-shadow: 0 2px 8px rgba(139, 105, 20, 0.08);
124
141
  }
125
142
  :deep(.ann-target.event:hover) {
126
- background: rgba(107, 76, 138, 0.08);
143
+ background: rgba(107, 76, 138, 0.1);
144
+ box-shadow: 0 2px 8px rgba(107, 76, 138, 0.08);
127
145
  }
128
146
  :deep(.ann-target.date:hover) {
129
- background: rgba(42, 122, 122, 0.08);
147
+ background: rgba(42, 122, 122, 0.1);
148
+ box-shadow: 0 2px 8px rgba(42, 122, 122, 0.08);
130
149
  }
131
150
  :deep(.ann-target.allusion:hover) {
132
- background: rgba(181, 101, 29, 0.08);
151
+ background: rgba(181, 101, 29, 0.1);
152
+ box-shadow: 0 2px 8px rgba(181, 101, 29, 0.08);
133
153
  }
134
154
 
135
155
  @media (max-width: 768px) {
@@ -40,15 +40,16 @@ const preview = computed(() => {
40
40
  position: absolute;
41
41
  top: 0; left: 0;
42
42
  width: 3px; height: 0;
43
- background: var(--vermillion);
44
- transition: height 0.3s var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1));
43
+ background: linear-gradient(180deg, var(--vermillion), var(--gold));
44
+ transition: height 0.35s var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1));
45
45
  }
46
46
  .pc-root:hover {
47
- transform: translateY(-2px);
48
- box-shadow: 0 8px 32px rgba(var(--shadow-rgb), 0.08);
47
+ transform: translateY(-3px);
48
+ box-shadow: 0 8px 28px rgba(var(--shadow-rgb), 0.1);
49
49
  border-color: var(--gold);
50
50
  }
51
51
  .pc-root:hover .pc-accent { height: 100%; }
52
+ .pc-root:hover .pc-title { color: var(--vermillion); }
52
53
  .pc-root:active { transform: scale(0.98); }
53
54
  .pc-body { padding: 24px; }
54
55
  .pc-num {
@@ -67,6 +68,7 @@ const preview = computed(() => {
67
68
  font-size: 20px; font-weight: 700;
68
69
  letter-spacing: 2px; margin-bottom: 6px;
69
70
  color: var(--ink);
71
+ transition: color 0.25s ease;
70
72
  }
71
73
  .pc-author {
72
74
  font-size: 13px; color: var(--ink-light);
@@ -130,6 +132,7 @@ const preview = computed(() => {
130
132
  .pc-vertical .pc-accent {
131
133
  top: auto; left: 0; bottom: 0;
132
134
  width: 0; height: 3px;
135
+ background: linear-gradient(90deg, var(--gold), var(--vermillion));
133
136
  transition: width 0.35s ease;
134
137
  }
135
138
  .pc-vertical:hover {
@@ -69,18 +69,18 @@ onUnmounted(detach)
69
69
  z-index: 1001;
70
70
  pointer-events: none;
71
71
  will-change: width, height;
72
- transition: width 0.1s ease, height 0.1s ease;
72
+ transition: width 0.08s linear, height 0.08s linear;
73
73
  }
74
74
  .rp:not(.rp-v) {
75
75
  top: 0; left: 0;
76
- height: 2px;
77
- background: var(--vermillion);
78
- box-shadow: 0 0 8px rgba(194, 58, 43, 0.3);
76
+ height: 3px;
77
+ background: linear-gradient(90deg, var(--vermillion), var(--gold));
78
+ box-shadow: 0 0 8px rgba(194, 58, 43, 0.2);
79
79
  }
80
80
  .rp-v {
81
81
  top: 0; left: 0;
82
- width: 2px;
83
- background: var(--vermillion);
84
- box-shadow: 0 0 8px rgba(194, 58, 43, 0.3);
82
+ width: 3px;
83
+ background: linear-gradient(180deg, var(--vermillion), var(--gold));
84
+ box-shadow: 0 0 8px rgba(194, 58, 43, 0.2);
85
85
  }
86
86
  </style>
@@ -89,8 +89,8 @@ const paragraphsHtml = computed(() => {
89
89
  .sb-root {
90
90
  margin-bottom: 40px;
91
91
  opacity: 0;
92
- transform: translateY(12px);
93
- transition: opacity 0.5s ease, transform 0.5s var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1));
92
+ transform: translateY(16px);
93
+ transition: opacity 0.6s ease, transform 0.6s var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1));
94
94
  }
95
95
  .sb-root.sb-visible {
96
96
  opacity: 1;
@@ -47,7 +47,8 @@ function onTap(event: MouseEvent) {
47
47
  <span
48
48
  v-for="(_, i) in verses"
49
49
  :key="i"
50
- class="v-scroll-line"
50
+ class="v-scroll-line v-verse-anim"
51
+ :style="{ animationDelay: (0.2 + i * 0.06) + 's' }"
51
52
  v-html="verseHtml(i)"
52
53
  />
53
54
  </div>
@@ -68,6 +69,7 @@ function onTap(event: MouseEvent) {
68
69
  position: relative;
69
70
  scrollbar-width: thin;
70
71
  scrollbar-color: var(--gold) transparent;
72
+ animation: poemRevealV 0.5s var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1)) both;
71
73
  }
72
74
  .v-scroll::-webkit-scrollbar { height: 3px; }
73
75
  .v-scroll::-webkit-scrollbar-thumb { background: var(--gold); border-radius: 2px; }
@@ -87,14 +89,25 @@ function onTap(event: MouseEvent) {
87
89
  .v-scroll-body { margin-left: 24px; }
88
90
  .v-scroll-line {
89
91
  font-size: var(--main-font-size, 24px); line-height: 2.4; letter-spacing: 6px;
90
- color: var(--ink); display: block;
92
+ color: var(--ink); display: block; white-space: pre-wrap;
93
+ }
94
+ .v-verse-anim {
95
+ animation: verseFadeV 0.4s var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1)) both;
96
+ }
97
+ @keyframes poemRevealV {
98
+ from { opacity: 0; transform: translateX(12px); }
99
+ to { opacity: 1; transform: translateX(0); }
100
+ }
101
+ @keyframes verseFadeV {
102
+ from { opacity: 0; }
103
+ to { opacity: 1; }
91
104
  }
92
105
 
93
106
  :deep(.ann-target) {
94
107
  border-left: 2px solid var(--vermillion);
95
108
  padding-left: 2px;
96
109
  cursor: help;
97
- transition: background 0.15s;
110
+ transition: background 0.2s ease, box-shadow 0.2s ease;
98
111
  }
99
112
  :deep(.ann-target.ann-overlap) {
100
113
  border-left-width: 3px;
@@ -115,10 +128,12 @@ function onTap(event: MouseEvent) {
115
128
  letter-spacing: -1px;
116
129
  }
117
130
  :deep(.ann-target:hover) {
118
- background: rgba(194, 58, 43, 0.08);
131
+ background: rgba(194, 58, 43, 0.1);
132
+ box-shadow: 0 -2px 8px rgba(194, 58, 43, 0.08);
119
133
  }
120
134
  :deep(.ann-target.pronunciation:hover) {
121
- background: rgba(58, 107, 94, 0.08);
135
+ background: rgba(58, 107, 94, 0.1);
136
+ box-shadow: 0 -2px 8px rgba(58, 107, 94, 0.08);
122
137
  }
123
138
  :deep(.ann-target.pronunciation) {
124
139
  border-left-color: var(--jade);
@@ -142,18 +157,23 @@ function onTap(event: MouseEvent) {
142
157
  border-left-color: var(--ann-allusion);
143
158
  }
144
159
  :deep(.ann-target.person:hover) {
145
- background: rgba(58, 90, 140, 0.08);
160
+ background: rgba(58, 90, 140, 0.1);
161
+ box-shadow: 0 -2px 8px rgba(58, 90, 140, 0.08);
146
162
  }
147
163
  :deep(.ann-target.place:hover) {
148
- background: rgba(139, 105, 20, 0.08);
164
+ background: rgba(139, 105, 20, 0.1);
165
+ box-shadow: 0 -2px 8px rgba(139, 105, 20, 0.08);
149
166
  }
150
167
  :deep(.ann-target.event:hover) {
151
- background: rgba(107, 76, 138, 0.08);
168
+ background: rgba(107, 76, 138, 0.1);
169
+ box-shadow: 0 -2px 8px rgba(107, 76, 138, 0.08);
152
170
  }
153
171
  :deep(.ann-target.date:hover) {
154
- background: rgba(42, 122, 122, 0.08);
172
+ background: rgba(42, 122, 122, 0.1);
173
+ box-shadow: 0 -2px 8px rgba(42, 122, 122, 0.08);
155
174
  }
156
175
  :deep(.ann-target.allusion:hover) {
157
- background: rgba(181, 101, 29, 0.08);
176
+ background: rgba(181, 101, 29, 0.1);
177
+ box-shadow: 0 -2px 8px rgba(181, 101, 29, 0.08);
158
178
  }
159
179
  </style>
@@ -141,6 +141,23 @@ html[dir="rtl"] ::-webkit-scrollbar-thumb { background: var(--gold); }
141
141
  a { color: inherit; text-decoration: none; }
142
142
  button { font-family: inherit; }
143
143
 
144
+ /* ===== FOCUS STYLES ===== */
145
+ :focus-visible {
146
+ outline: 2px solid var(--vermillion);
147
+ outline-offset: 2px;
148
+ }
149
+ button:focus-visible {
150
+ outline: 2px solid var(--vermillion);
151
+ outline-offset: 2px;
152
+ border-radius: 4px;
153
+ }
154
+
155
+ /* ===== ACTIVE ANNOTATION FLASH ===== */
156
+ @keyframes ann-flash-anim {
157
+ 0% { background: rgba(194, 58, 43, 0.25); box-shadow: 0 0 12px rgba(194, 58, 43, 0.15); }
158
+ 100% { background: transparent; box-shadow: none; }
159
+ }
160
+
144
161
  /* ===== LOADING SCREEN ===== */
145
162
  #app-loading {
146
163
  position: fixed; inset: 0;
@@ -148,10 +165,11 @@ button { font-family: inherit; }
148
165
  display: flex; flex-direction: column;
149
166
  align-items: center; justify-content: center;
150
167
  z-index: 9999;
151
- transition: opacity 0.4s ease;
168
+ transition: opacity 0.5s ease, transform 0.5s ease;
152
169
  }
153
170
  #app-loading.fade-out {
154
171
  opacity: 0;
172
+ transform: scale(1.02);
155
173
  pointer-events: none;
156
174
  }
157
175
  #app-loading .seal {
@@ -354,15 +354,17 @@ function openBook(bookId: string) {
354
354
  from { opacity: 0; transform: translateY(12px); }
355
355
  to { opacity: 1; transform: translateY(0); }
356
356
  }
357
- .lib-card:hover { border-color: var(--gold); box-shadow: 0 4px 20px rgba(var(--shadow-rgb), 0.1); }
357
+ .lib-card:hover { border-color: var(--gold); box-shadow: 0 6px 24px rgba(var(--shadow-rgb), 0.1); transform: translateY(-2px); }
358
+ .lib-card:active { transform: scale(0.98); }
358
359
  .lib-card-accent {
359
360
  position: absolute;
360
361
  top: 0; left: 0;
361
362
  width: 3px; height: 0;
362
- background: var(--vermillion);
363
- transition: height 0.35s ease;
363
+ background: linear-gradient(180deg, var(--vermillion), var(--gold));
364
+ transition: height 0.35s var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1));
364
365
  }
365
366
  .lib-card:hover .lib-card-accent { height: 100%; }
367
+ .lib-card:hover .lib-card-title { color: var(--vermillion); }
366
368
  .lib-card-top {
367
369
  display: flex;
368
370
  align-items: baseline;
@@ -372,6 +374,7 @@ function openBook(bookId: string) {
372
374
  .lib-card-title {
373
375
  font-size: 22px; font-weight: 900;
374
376
  letter-spacing: 4px; color: var(--ink);
377
+ transition: color 0.25s ease;
375
378
  }
376
379
  .lib-card-genre {
377
380
  font-size: 11px;
@@ -846,13 +846,15 @@ function tcy(n: number): string {
846
846
  border: 1px solid var(--border-light);
847
847
  border-radius: 6px;
848
848
  cursor: pointer;
849
- transition: all 0.2s;
849
+ transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
850
850
  line-height: 1.6;
851
+ position: relative;
851
852
  }
852
853
  .v-nav-btn:hover {
853
854
  border-color: var(--gold);
854
- box-shadow: 0 4px 16px rgba(var(--shadow-rgb), 0.08);
855
+ box-shadow: 0 4px 20px rgba(var(--shadow-rgb), 0.1);
855
856
  }
857
+ .v-nav-btn:hover .v-nav-title { color: var(--vermillion); }
856
858
  .v-nav-dir {
857
859
  font-size: 16px; color: var(--vermillion);
858
860
  margin-bottom: 0.5em;
@@ -865,6 +867,7 @@ function tcy(n: number): string {
865
867
  .v-nav-title {
866
868
  font-size: 18px; font-weight: 700;
867
869
  letter-spacing: 3px; color: var(--ink);
870
+ transition: color 0.25s ease;
868
871
  }
869
872
 
870
873
  /* ═══════ 橫排模式 ═══════ */
@@ -950,7 +953,7 @@ function tcy(n: number): string {
950
953
  .h-nav-arrow {
951
954
  width: 32px; height: 32px;
952
955
  border: 1px solid var(--border);
953
- border-radius: 4px;
956
+ border-radius: 6px;
954
957
  background: none;
955
958
  font-family: var(--sans);
956
959
  font-size: 16px;
@@ -1026,20 +1029,21 @@ function tcy(n: number): string {
1026
1029
  position: absolute;
1027
1030
  bottom: 0; left: 0; right: 0;
1028
1031
  height: 2px;
1029
- background: var(--vermillion);
1032
+ background: linear-gradient(90deg, var(--vermillion), var(--gold));
1030
1033
  transform: scaleX(0);
1031
- transition: transform 0.3s ease;
1034
+ transition: transform 0.35s var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1));
1032
1035
  }
1033
1036
  .h-nav-btn:hover {
1034
1037
  border-color: var(--gold);
1035
1038
  box-shadow: 0 8px 32px rgba(var(--shadow-rgb), 0.1);
1036
- transform: translateY(-2px);
1039
+ transform: translateY(-3px);
1037
1040
  }
1038
1041
  .h-nav-btn:hover::after { transform: scaleX(1); }
1042
+ .h-nav-btn:hover .h-nav-title { color: var(--vermillion); }
1039
1043
  .h-nav-btn:active { transform: scale(0.98); }
1040
1044
  .h-nav-btn.h-nav-next { text-align: right; }
1041
1045
  .h-nav-label { font-size: 11px; color: var(--ink-faint); font-family: var(--sans); letter-spacing: 2px; margin-bottom: 4px; }
1042
- .h-nav-title { font-size: 16px; font-weight: 600; letter-spacing: 1px; color: var(--ink); }
1046
+ .h-nav-title { font-size: 16px; font-weight: 600; letter-spacing: 1px; color: var(--ink); transition: color 0.25s ease; }
1043
1047
 
1044
1048
  .h-overlay {
1045
1049
  position: fixed; inset: 0;
@@ -1335,11 +1339,11 @@ function tcy(n: number): string {
1335
1339
 
1336
1340
  /* ─── 注釋閃爍 ─── */
1337
1341
  :deep(.ann-flash) {
1338
- animation: ann-flash-anim 1.2s ease-out;
1342
+ animation: ann-flash-anim 1.5s ease-out;
1339
1343
  }
1340
1344
  @keyframes ann-flash-anim {
1341
- 0% { background: rgba(194, 58, 43, 0.2); }
1342
- 100% { background: transparent; }
1345
+ 0% { background: rgba(194, 58, 43, 0.25); box-shadow: 0 0 12px rgba(194, 58, 43, 0.15); }
1346
+ 100% { background: transparent; box-shadow: none; }
1343
1347
  }
1344
1348
 
1345
1349
  /* ═══════ 行動裝置適配 ═══════ */