dsh-theme-gallery 0.1.5 → 0.2.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.
package/lib/client.js CHANGED
@@ -118,7 +118,7 @@ window.__ModuleLoader__.load({
118
118
  * package.json, and the release workflow fails when re-running the embed step
119
119
  * changes a tracked file.
120
120
  */
121
- const BUNDLED_VERSION = '0.1.5'
121
+ const BUNDLED_VERSION = '0.2.0'
122
122
 
123
123
  /**
124
124
  * Themes this package contributes, inlined from lib/themes/*.json.
@@ -421,6 +421,294 @@ window.__ModuleLoader__.load({
421
421
  "fish": 3
422
422
  }
423
423
  },
424
+ {
425
+ "id": "pei-an-jie-xin",
426
+ "label": "佩安杰心",
427
+ "description": "檀香禅影,自在安顿 —— 复刻自电商新零售系统管理后台同名主题",
428
+ "colorScheme": "light",
429
+ "tokens": {
430
+ "--dsw-alias-bg-base": {
431
+ "light": "linear-gradient(to bottom,#FCFAF5 0%,#F8F3E9 55%,#F3ECDD 100%)",
432
+ "dark": "linear-gradient(to bottom,#FCFAF5 0%,#F8F3E9 55%,#F3ECDD 100%)"
433
+ },
434
+ "--dsw-alias-bg-layer-1": {
435
+ "light": "#FDFBF7",
436
+ "dark": "#FDFBF7"
437
+ },
438
+ "--dsw-alias-bg-layer-2": {
439
+ "light": "#FFFFFF",
440
+ "dark": "#FFFFFF"
441
+ },
442
+ "--dsw-alias-bg-layer-3": {
443
+ "light": "#FFFFFF",
444
+ "dark": "#FFFFFF"
445
+ },
446
+ "--dsw-alias-bg-overlay": {
447
+ "light": "#EFE7D7",
448
+ "dark": "#EFE7D7"
449
+ },
450
+ "--dsw-alias-bg-skeleton": {
451
+ "light": "#7A5C3E14",
452
+ "dark": "#7A5C3E14"
453
+ },
454
+ "--dsw-alias-bg-module-platform": {
455
+ "light": "#F3EBDD",
456
+ "dark": "#F3EBDD"
457
+ },
458
+ "--dsw-alias-bg-multi-select": {
459
+ "light": "#F3EBDD",
460
+ "dark": "#F3EBDD"
461
+ },
462
+ "--dsw-alias-border-l1": {
463
+ "light": "#7A5C3E0F",
464
+ "dark": "#7A5C3E0F"
465
+ },
466
+ "--dsw-alias-border-l2": {
467
+ "light": "#7A5C3E21",
468
+ "dark": "#7A5C3E21"
469
+ },
470
+ "--dsw-alias-border-l3": {
471
+ "light": "#7A5C3E2E",
472
+ "dark": "#7A5C3E2E"
473
+ },
474
+ "--dsw-alias-border-l4": {
475
+ "light": "#7A5C3E3D",
476
+ "dark": "#7A5C3E3D"
477
+ },
478
+ "--dsw-alias-brand-primary": {
479
+ "light": "#7A5C3E",
480
+ "dark": "#7A5C3E"
481
+ },
482
+ "--dsw-alias-brand-primary-invert": {
483
+ "light": "#FFFFFF",
484
+ "dark": "#FFFFFF"
485
+ },
486
+ "--dsw-alias-brand-text": {
487
+ "light": "#7A5C3E",
488
+ "dark": "#7A5C3E"
489
+ },
490
+ "--dsw-alias-label-primary": {
491
+ "light": "#3F3222",
492
+ "dark": "#3F3222"
493
+ },
494
+ "--dsw-alias-label-primary-bluish": {
495
+ "light": "#3F3222",
496
+ "dark": "#3F3222"
497
+ },
498
+ "--dsw-alias-label-secondary": {
499
+ "light": "#5E4A38",
500
+ "dark": "#5E4A38"
501
+ },
502
+ "--dsw-alias-label-tertiary": {
503
+ "light": "#85705A",
504
+ "dark": "#85705A"
505
+ },
506
+ "--dsw-alias-label-caption": {
507
+ "light": "#85705A",
508
+ "dark": "#85705A"
509
+ },
510
+ "--dsw-alias-label-dimmed": {
511
+ "light": "#AC9C86",
512
+ "dark": "#AC9C86"
513
+ },
514
+ "--dsw-alias-label-primary-dimmed": {
515
+ "light": "#5E4A38",
516
+ "dark": "#5E4A38"
517
+ },
518
+ "--dsw-alias-label-primary-foreground": {
519
+ "light": "#FFFFFF",
520
+ "dark": "#FFFFFF"
521
+ },
522
+ "--dsw-alias-label-primary-inverted": {
523
+ "light": "#FFFFFF",
524
+ "dark": "#FFFFFF"
525
+ },
526
+ "--dsw-alias-link": {
527
+ "light": "#B4653A",
528
+ "dark": "#B4653A"
529
+ },
530
+ "--dsw-alias-interactive-bg-hover": {
531
+ "light": "#9C7B5814",
532
+ "dark": "#9C7B5814"
533
+ },
534
+ "--dsw-alias-interactive-bg-active": {
535
+ "light": "#B4653A2E",
536
+ "dark": "#B4653A2E"
537
+ },
538
+ "--dsw-alias-interactive-bg-hover-solid": {
539
+ "light": "#F3EBDD",
540
+ "dark": "#F3EBDD"
541
+ },
542
+ "--dsw-alias-interactive-bg-hover-accent": {
543
+ "light": "#B4653A3D",
544
+ "dark": "#B4653A3D"
545
+ },
546
+ "--dsw-alias-button-primary-fill": {
547
+ "light": "#7A5C3E",
548
+ "dark": "#7A5C3E"
549
+ },
550
+ "--dsw-alias-button-primary-hover": {
551
+ "light": "#9C7B58",
552
+ "dark": "#9C7B58"
553
+ },
554
+ "--dsw-alias-button-primary-dimmed": {
555
+ "light": "#7A5C3E47",
556
+ "dark": "#7A5C3E47"
557
+ },
558
+ "--dsw-alias-button-ghost-active-fill": {
559
+ "light": "#B4653A29",
560
+ "dark": "#B4653A29"
561
+ },
562
+ "--dsw-alias-button-ghost-active-border": {
563
+ "light": "#B4653A",
564
+ "dark": "#B4653A"
565
+ },
566
+ "--dsw-alias-button-ghost-active-hover": {
567
+ "light": "#B4653A47",
568
+ "dark": "#B4653A47"
569
+ },
570
+ "--dsw-alias-button-info-fill": {
571
+ "light": "#9C7B58",
572
+ "dark": "#9C7B58"
573
+ },
574
+ "--dsw-alias-button-info-hover": {
575
+ "light": "#8A6E52",
576
+ "dark": "#8A6E52"
577
+ },
578
+ "--dsw-alias-button-elevated-fill": {
579
+ "light": "#FFFFFF",
580
+ "dark": "#FFFFFF"
581
+ },
582
+ "--dsw-alias-button-floating-fill": {
583
+ "light": "#FFFFFF",
584
+ "dark": "#FFFFFF"
585
+ },
586
+ "--dsw-alias-button-floating-hover": {
587
+ "light": "#FAF6EE",
588
+ "dark": "#FAF6EE"
589
+ },
590
+ "--dsw-alias-button-contrast-fill": {
591
+ "light": "#3F3222",
592
+ "dark": "#3F3222"
593
+ },
594
+ "--dsw-alias-markdown-code-block": {
595
+ "light": "#F5EFE3",
596
+ "dark": "#F5EFE3"
597
+ },
598
+ "--dsw-alias-markdown-code-block-banner": {
599
+ "light": "#EDE4D2",
600
+ "dark": "#EDE4D2"
601
+ },
602
+ "--dsw-alias-markdown-inline-code": {
603
+ "light": "#F3EBDD",
604
+ "dark": "#F3EBDD"
605
+ },
606
+ "--dsw-alias-markdown-citation": {
607
+ "light": "#F3EBDD",
608
+ "dark": "#F3EBDD"
609
+ },
610
+ "--dsw-alias-markdown-tag": {
611
+ "light": "#F3EBDD",
612
+ "dark": "#F3EBDD"
613
+ },
614
+ "--dsw-alias-markdown-placeholder": {
615
+ "light": "#EDE4D2",
616
+ "dark": "#EDE4D2"
617
+ },
618
+ "--dsw-alias-markdown-code-segment-selected": {
619
+ "light": "#B4653A29",
620
+ "dark": "#B4653A29"
621
+ },
622
+ "--dsw-alias-markdown-code-segment-unselected": {
623
+ "light": "#F5EFE3",
624
+ "dark": "#F5EFE3"
625
+ },
626
+ "--dsw-alias-scrollbar-bg-l1": {
627
+ "light": "#7A5C3E29",
628
+ "dark": "#7A5C3E29"
629
+ },
630
+ "--dsw-alias-scrollbar-bg-l2": {
631
+ "light": "#7A5C3E38",
632
+ "dark": "#7A5C3E38"
633
+ },
634
+ "--dsw-alias-scrollbar-hover-l1": {
635
+ "light": "#7A5C3E52",
636
+ "dark": "#7A5C3E52"
637
+ },
638
+ "--dsw-alias-scrollbar-hover-l2": {
639
+ "light": "#7A5C3E66",
640
+ "dark": "#7A5C3E66"
641
+ },
642
+ "--dsw-alias-tooltip-bg": {
643
+ "light": "#3F3222",
644
+ "dark": "#3F3222"
645
+ },
646
+ "--dsw-alias-toast-bg": {
647
+ "light": "#FFFFFF",
648
+ "dark": "#FFFFFF"
649
+ },
650
+ "--dsw-alias-state-error-primary": {
651
+ "light": "#C65B6A",
652
+ "dark": "#C65B6A"
653
+ },
654
+ "--dsw-alias-state-error-secondary": {
655
+ "light": "#DB5A6B",
656
+ "dark": "#DB5A6B"
657
+ },
658
+ "--dsw-alias-state-success-primary": {
659
+ "light": "#21A675",
660
+ "dark": "#21A675"
661
+ },
662
+ "--dsw-alias-state-success-secondary": {
663
+ "light": "#7A5C3E",
664
+ "dark": "#7A5C3E"
665
+ },
666
+ "--dsw-alias-state-success-tertiary": {
667
+ "light": "#C4B39A",
668
+ "dark": "#C4B39A"
669
+ },
670
+ "--dsw-alias-state-warn-primary": {
671
+ "light": "#B97F3A",
672
+ "dark": "#B97F3A"
673
+ },
674
+ "--dsw-alias-state-warn-secondary": {
675
+ "light": "#D9A45E",
676
+ "dark": "#D9A45E"
677
+ },
678
+ "--dsw-alias-state-warn-tertiary": {
679
+ "light": "#EFDCC0",
680
+ "dark": "#EFDCC0"
681
+ },
682
+ "--dsw-alias-state-warn-label": {
683
+ "light": "#8A5A22",
684
+ "dark": "#8A5A22"
685
+ },
686
+ "--dsw-alias-state-business-primary": {
687
+ "light": "#B4653A",
688
+ "dark": "#B4653A"
689
+ },
690
+ "--dsw-alias-state-business-tertiary": {
691
+ "light": "#D08A5C",
692
+ "dark": "#D08A5C"
693
+ },
694
+ "--dsw-specific-sidebar-fill": {
695
+ "light": "linear-gradient(to bottom,#F2E8D8 0%,#EBDCC4 22%,#E0CDAD 42%,#D4BC98 60%,#C3A87F 78%,#A98D6B 100%)",
696
+ "dark": "linear-gradient(to bottom,#F2E8D8 0%,#EBDCC4 22%,#E0CDAD 42%,#D4BC98 60%,#C3A87F 78%,#A98D6B 100%)"
697
+ }
698
+ },
699
+ "reading": {
700
+ "colorScheme": "light",
701
+ "bg": "#FFFFFF",
702
+ "alpha": 0.62,
703
+ "blur": 3,
704
+ "maxWidth": 640
705
+ },
706
+ "accent": "#B4653A",
707
+ "ambient": {
708
+ "kind": "jiexin",
709
+ "dust": 4
710
+ }
711
+ },
424
712
  {
425
713
  "id": "shan-qing-ting-cai",
426
714
  "label": "山青婷彩",
@@ -708,84 +996,372 @@ window.__ModuleLoader__.load({
708
996
  "kind": "shan",
709
997
  "petals": 7
710
998
  }
711
- }
712
- ]
713
-
714
- /**
715
- * Marks the ambient nodes this run created.
716
- *
717
- * The layer lives on the document body, so parentage can no longer identify
718
- * ownership. Anything found without this stamp is debris from an earlier build —
719
- * and because the shell keeps its DOM across a plugin reload, such nodes outlive
720
- * the code that made them.
721
- */
722
- const AMBIENT_OWNER = 'theme-gallery'
723
-
724
- /**
725
- * Ambient scenery drawn inside the sidebar column.
726
- *
727
- * Ported from the source admin system, where each theme carried its own
728
- * animation component mounted into a `left-sidebar-theme-container`. The
729
- * artwork is reproduced here (mountains, mist, water, dragonflies, falling
730
- * petals for 山青婷彩; corner glow, rising bubbles, swaying seaweed for
731
- * 梦海游鱼) with two deliberate changes:
732
- *
733
- * - sizes are expressed in **percentages and em**, not the source system's
734
- * fixed 223 px sidebar width, so the scene scales to whatever width the
735
- * user drags the sidebar to;
736
- * - nothing here carries colour of its own beyond the ported artwork, and the
737
- * layer is `pointer-events:none` / `z-index:0`, so it can never intercept a
738
- * click or cover a menu item. The source system has the same rule, and a
739
- * bug note there records opaque mountains hiding the bottom menu rows.
740
- *
741
- * `#dsh-theme-ambient` is a seat this plugin injects into the sidebar column;
742
- * `syncAmbient()` fills it, and each theme chooses its scene by `ambient.kind`.
743
- */
744
- const AMBIENT_CSS = [
745
- /* The layer, arranged exactly like the working `dsh-theme-firefly` ambient layer:
746
- a full-viewport fixed element styled by a CLASS, appended to `document.body`, at
747
- `z-index: 60`.
748
- Every other arrangement of this layer failed to paint on this machine — precise
749
- inline geometry, maximum z-index, `documentElement` as the parent, inline styles
750
- per element. The firefly plugin uses this one and renders, so this is now the
751
- layer's arrangement. The scenery is placed INSIDE it (`.dsh-ambient-scene`). */
752
- '#dsh-theme-ambient{position:fixed;inset:0;pointer-events:none;overflow:hidden;z-index:60}',
753
- '.dsh-ambient-scene{overflow:hidden}',
754
- /* The scene roots carry their own positioning inline too; the selectors below are
755
- kept only where a rule cannot be inlined (`@keyframes`) or where they must reach
756
- a shell element. */
757
- '.ZTP-Xa_sidebarCol{position:relative}',
758
-
759
- /* BRING-UP PROBE — remove with the rest of the diagnostics.
760
- The control layer, copied rule-for-rule from the working `dsh-theme-firefly`
761
- plugin: geometry and stacking from a class, motion from `@keyframes`, and only
762
- the per-element random values inline. */
763
- '.dsh-amb-control{position:fixed;inset:0;pointer-events:none;z-index:60;overflow:hidden}',
764
- /* BRING-UP PROBE — the scene box inside the proven layer. No `pointer-events` here on
765
- purpose: the layer above already disables it, and this box is the thing being
766
- tested rather than a decoration that must stay click-through. */
767
- '.dsh-amb-control-scene{overflow:hidden}',
768
-
769
- /* The scenery's own geometry. These live here rather than inline because the scene
770
- markup is built as a string, and they must reach it wherever it is mounted — which
771
- is now inside the proven layer rather than a layer of its own. */
772
- '.dsh-amb-control-scene .sta-mountains{position:absolute;left:0;right:0;bottom:0;height:46%;z-index:3}',
773
- '.dsh-amb-control-scene .sta-mountains svg{display:block;width:100%;height:100%}',
774
- '.dsh-amb-control-scene .sta-mist{position:absolute;height:1.6em;border-radius:1000px;z-index:4;',
775
- 'background:linear-gradient(90deg,transparent,rgba(255,255,255,.75),transparent);',
776
- 'filter:blur(5px);opacity:.85;animation:dsh-amb-mist 26s ease-in-out infinite alternate}',
777
- '.dsh-amb-control-scene .sta-mist-1{width:62%;top:56%;left:12%}',
778
- '.dsh-amb-control-scene .sta-mist-2{width:44%;top:61%;left:38%;opacity:.6;',
779
- 'animation-duration:32s;animation-delay:-9s}',
780
- '.dsh-amb-control-scene .sta-pond{position:absolute;left:0;right:0;bottom:0;height:13%;z-index:5;',
781
- 'background:linear-gradient(to bottom,rgba(104,178,150,.62),rgba(66,141,113,.78))}',
782
- '.dsh-amb-control-scene .sta-pond-line{position:absolute;top:0;left:0;right:0;height:1.2px;opacity:.6;',
783
- 'background:linear-gradient(90deg,transparent,rgba(255,255,255,.9),transparent)}',
784
- '.dsh-amb-control-scene .sta-ripple{position:absolute;z-index:6;width:.55em;height:.55em}',
785
- '.dsh-amb-control-scene .sta-ripple span{position:absolute;inset:0;',
786
- 'border:1.6px solid rgba(217,123,164,.9);border-radius:50%;',
787
- 'animation:dsh-amb-ring 3.2s ease-out infinite}',
788
- '.dsh-amb-control-scene .sta-ripple span:nth-child(2){animation-delay:1.6s}',
999
+ },
1000
+ {
1001
+ "id": "ying-mu-cai-yun",
1002
+ "label": "营慕彩云",
1003
+ "description": "营幕之下,彩云为伴 —— 复刻自电商新零售系统管理后台同名主题",
1004
+ "colorScheme": "light",
1005
+ "tokens": {
1006
+ "--dsw-alias-bg-base": {
1007
+ "light": "linear-gradient(to bottom,#FBFCF8 0%,#F5F8EF 55%,#EEF3E4 100%)",
1008
+ "dark": "linear-gradient(to bottom,#FBFCF8 0%,#F5F8EF 55%,#EEF3E4 100%)"
1009
+ },
1010
+ "--dsw-alias-bg-layer-1": {
1011
+ "light": "#FBFDF7",
1012
+ "dark": "#FBFDF7"
1013
+ },
1014
+ "--dsw-alias-bg-layer-2": {
1015
+ "light": "#FFFFFF",
1016
+ "dark": "#FFFFFF"
1017
+ },
1018
+ "--dsw-alias-bg-layer-3": {
1019
+ "light": "#FFFFFF",
1020
+ "dark": "#FFFFFF"
1021
+ },
1022
+ "--dsw-alias-bg-overlay": {
1023
+ "light": "#E7EFE0",
1024
+ "dark": "#E7EFE0"
1025
+ },
1026
+ "--dsw-alias-bg-skeleton": {
1027
+ "light": "#2D5A3D14",
1028
+ "dark": "#2D5A3D14"
1029
+ },
1030
+ "--dsw-alias-bg-module-platform": {
1031
+ "light": "#ECF3E6",
1032
+ "dark": "#ECF3E6"
1033
+ },
1034
+ "--dsw-alias-bg-multi-select": {
1035
+ "light": "#ECF3E6",
1036
+ "dark": "#ECF3E6"
1037
+ },
1038
+ "--dsw-alias-border-l1": {
1039
+ "light": "#2D5A3D0F",
1040
+ "dark": "#2D5A3D0F"
1041
+ },
1042
+ "--dsw-alias-border-l2": {
1043
+ "light": "#2D5A3D21",
1044
+ "dark": "#2D5A3D21"
1045
+ },
1046
+ "--dsw-alias-border-l3": {
1047
+ "light": "#2D5A3D2E",
1048
+ "dark": "#2D5A3D2E"
1049
+ },
1050
+ "--dsw-alias-border-l4": {
1051
+ "light": "#2D5A3D3D",
1052
+ "dark": "#2D5A3D3D"
1053
+ },
1054
+ "--dsw-alias-brand-primary": {
1055
+ "light": "#2D5A3D",
1056
+ "dark": "#2D5A3D"
1057
+ },
1058
+ "--dsw-alias-brand-primary-invert": {
1059
+ "light": "#FFFFFF",
1060
+ "dark": "#FFFFFF"
1061
+ },
1062
+ "--dsw-alias-brand-text": {
1063
+ "light": "#2D5A3D",
1064
+ "dark": "#2D5A3D"
1065
+ },
1066
+ "--dsw-alias-label-primary": {
1067
+ "light": "#234030",
1068
+ "dark": "#234030"
1069
+ },
1070
+ "--dsw-alias-label-primary-bluish": {
1071
+ "light": "#234030",
1072
+ "dark": "#234030"
1073
+ },
1074
+ "--dsw-alias-label-secondary": {
1075
+ "light": "#3E6B4A",
1076
+ "dark": "#3E6B4A"
1077
+ },
1078
+ "--dsw-alias-label-tertiary": {
1079
+ "light": "#62816C",
1080
+ "dark": "#62816C"
1081
+ },
1082
+ "--dsw-alias-label-caption": {
1083
+ "light": "#62816C",
1084
+ "dark": "#62816C"
1085
+ },
1086
+ "--dsw-alias-label-dimmed": {
1087
+ "light": "#93A996",
1088
+ "dark": "#93A996"
1089
+ },
1090
+ "--dsw-alias-label-primary-dimmed": {
1091
+ "light": "#3E6B4A",
1092
+ "dark": "#3E6B4A"
1093
+ },
1094
+ "--dsw-alias-label-primary-foreground": {
1095
+ "light": "#FFFFFF",
1096
+ "dark": "#FFFFFF"
1097
+ },
1098
+ "--dsw-alias-label-primary-inverted": {
1099
+ "light": "#FFFFFF",
1100
+ "dark": "#FFFFFF"
1101
+ },
1102
+ "--dsw-alias-link": {
1103
+ "light": "#C4899F",
1104
+ "dark": "#C4899F"
1105
+ },
1106
+ "--dsw-alias-interactive-bg-hover": {
1107
+ "light": "#3E6B4A14",
1108
+ "dark": "#3E6B4A14"
1109
+ },
1110
+ "--dsw-alias-interactive-bg-active": {
1111
+ "light": "#FFB3472E",
1112
+ "dark": "#FFB3472E"
1113
+ },
1114
+ "--dsw-alias-interactive-bg-hover-solid": {
1115
+ "light": "#ECF3E6",
1116
+ "dark": "#ECF3E6"
1117
+ },
1118
+ "--dsw-alias-interactive-bg-hover-accent": {
1119
+ "light": "#FFB3473D",
1120
+ "dark": "#FFB3473D"
1121
+ },
1122
+ "--dsw-alias-button-primary-fill": {
1123
+ "light": "#2D5A3D",
1124
+ "dark": "#2D5A3D"
1125
+ },
1126
+ "--dsw-alias-button-primary-hover": {
1127
+ "light": "#3E6B4A",
1128
+ "dark": "#3E6B4A"
1129
+ },
1130
+ "--dsw-alias-button-primary-dimmed": {
1131
+ "light": "#2D5A3D47",
1132
+ "dark": "#2D5A3D47"
1133
+ },
1134
+ "--dsw-alias-button-ghost-active-fill": {
1135
+ "light": "#FFB34729",
1136
+ "dark": "#FFB34729"
1137
+ },
1138
+ "--dsw-alias-button-ghost-active-border": {
1139
+ "light": "#FFB347",
1140
+ "dark": "#FFB347"
1141
+ },
1142
+ "--dsw-alias-button-ghost-active-hover": {
1143
+ "light": "#FFB34747",
1144
+ "dark": "#FFB34747"
1145
+ },
1146
+ "--dsw-alias-button-info-fill": {
1147
+ "light": "#3E6B4A",
1148
+ "dark": "#3E6B4A"
1149
+ },
1150
+ "--dsw-alias-button-info-hover": {
1151
+ "light": "#4A7D58",
1152
+ "dark": "#4A7D58"
1153
+ },
1154
+ "--dsw-alias-button-elevated-fill": {
1155
+ "light": "#FFFFFF",
1156
+ "dark": "#FFFFFF"
1157
+ },
1158
+ "--dsw-alias-button-floating-fill": {
1159
+ "light": "#FFFFFF",
1160
+ "dark": "#FFFFFF"
1161
+ },
1162
+ "--dsw-alias-button-floating-hover": {
1163
+ "light": "#F6FAF2",
1164
+ "dark": "#F6FAF2"
1165
+ },
1166
+ "--dsw-alias-button-contrast-fill": {
1167
+ "light": "#234030",
1168
+ "dark": "#234030"
1169
+ },
1170
+ "--dsw-alias-markdown-code-block": {
1171
+ "light": "#EFF6EA",
1172
+ "dark": "#EFF6EA"
1173
+ },
1174
+ "--dsw-alias-markdown-code-block-banner": {
1175
+ "light": "#E3EEDC",
1176
+ "dark": "#E3EEDC"
1177
+ },
1178
+ "--dsw-alias-markdown-inline-code": {
1179
+ "light": "#ECF3E6",
1180
+ "dark": "#ECF3E6"
1181
+ },
1182
+ "--dsw-alias-markdown-citation": {
1183
+ "light": "#ECF3E6",
1184
+ "dark": "#ECF3E6"
1185
+ },
1186
+ "--dsw-alias-markdown-tag": {
1187
+ "light": "#ECF3E6",
1188
+ "dark": "#ECF3E6"
1189
+ },
1190
+ "--dsw-alias-markdown-placeholder": {
1191
+ "light": "#E3EEDC",
1192
+ "dark": "#E3EEDC"
1193
+ },
1194
+ "--dsw-alias-markdown-code-segment-selected": {
1195
+ "light": "#FFB34729",
1196
+ "dark": "#FFB34729"
1197
+ },
1198
+ "--dsw-alias-markdown-code-segment-unselected": {
1199
+ "light": "#EFF6EA",
1200
+ "dark": "#EFF6EA"
1201
+ },
1202
+ "--dsw-alias-scrollbar-bg-l1": {
1203
+ "light": "#2D5A3D29",
1204
+ "dark": "#2D5A3D29"
1205
+ },
1206
+ "--dsw-alias-scrollbar-bg-l2": {
1207
+ "light": "#2D5A3D38",
1208
+ "dark": "#2D5A3D38"
1209
+ },
1210
+ "--dsw-alias-scrollbar-hover-l1": {
1211
+ "light": "#2D5A3D52",
1212
+ "dark": "#2D5A3D52"
1213
+ },
1214
+ "--dsw-alias-scrollbar-hover-l2": {
1215
+ "light": "#2D5A3D66",
1216
+ "dark": "#2D5A3D66"
1217
+ },
1218
+ "--dsw-alias-tooltip-bg": {
1219
+ "light": "#234030",
1220
+ "dark": "#234030"
1221
+ },
1222
+ "--dsw-alias-toast-bg": {
1223
+ "light": "#FFFFFF",
1224
+ "dark": "#FFFFFF"
1225
+ },
1226
+ "--dsw-alias-state-error-primary": {
1227
+ "light": "#C65B6A",
1228
+ "dark": "#C65B6A"
1229
+ },
1230
+ "--dsw-alias-state-error-secondary": {
1231
+ "light": "#DB5A6B",
1232
+ "dark": "#DB5A6B"
1233
+ },
1234
+ "--dsw-alias-state-success-primary": {
1235
+ "light": "#21A675",
1236
+ "dark": "#21A675"
1237
+ },
1238
+ "--dsw-alias-state-success-secondary": {
1239
+ "light": "#2D5A3D",
1240
+ "dark": "#2D5A3D"
1241
+ },
1242
+ "--dsw-alias-state-success-tertiary": {
1243
+ "light": "#A8C8B2",
1244
+ "dark": "#A8C8B2"
1245
+ },
1246
+ "--dsw-alias-state-warn-primary": {
1247
+ "light": "#B97F3A",
1248
+ "dark": "#B97F3A"
1249
+ },
1250
+ "--dsw-alias-state-warn-secondary": {
1251
+ "light": "#D9A45E",
1252
+ "dark": "#D9A45E"
1253
+ },
1254
+ "--dsw-alias-state-warn-tertiary": {
1255
+ "light": "#EFDCC0",
1256
+ "dark": "#EFDCC0"
1257
+ },
1258
+ "--dsw-alias-state-warn-label": {
1259
+ "light": "#8A5A22",
1260
+ "dark": "#8A5A22"
1261
+ },
1262
+ "--dsw-alias-state-business-primary": {
1263
+ "light": "#FFB347",
1264
+ "dark": "#FFB347"
1265
+ },
1266
+ "--dsw-alias-state-business-tertiary": {
1267
+ "light": "#FFD08A",
1268
+ "dark": "#FFD08A"
1269
+ },
1270
+ "--dsw-specific-sidebar-fill": {
1271
+ "light": "linear-gradient(160deg,#D9E8DC 0%,#E6D9C6 28%,#F2E0BE 58%,#F6E1EE 88%,#DCD5F0 100%)",
1272
+ "dark": "linear-gradient(160deg,#D9E8DC 0%,#E6D9C6 28%,#F2E0BE 58%,#F6E1EE 88%,#DCD5F0 100%)"
1273
+ }
1274
+ },
1275
+ "reading": {
1276
+ "colorScheme": "light",
1277
+ "bg": "#FFFFFF",
1278
+ "alpha": 0.62,
1279
+ "blur": 3,
1280
+ "maxWidth": 640
1281
+ },
1282
+ "accent": "#FFB347",
1283
+ "ambient": {
1284
+ "kind": "caiyun",
1285
+ "stars": 12
1286
+ }
1287
+ }
1288
+ ]
1289
+
1290
+ /**
1291
+ * Marks the ambient nodes this run created.
1292
+ *
1293
+ * The layer lives on the document body, so parentage can no longer identify
1294
+ * ownership. Anything found without this stamp is debris from an earlier build —
1295
+ * and because the shell keeps its DOM across a plugin reload, such nodes outlive
1296
+ * the code that made them.
1297
+ */
1298
+ const AMBIENT_OWNER = 'theme-gallery'
1299
+
1300
+ /**
1301
+ * Ambient scenery drawn inside the sidebar column.
1302
+ *
1303
+ * Ported from the source admin system, where each theme carried its own
1304
+ * animation component mounted into a `left-sidebar-theme-container`. The
1305
+ * artwork is reproduced here (mountains, mist, water, dragonflies, falling
1306
+ * petals for 山青婷彩; corner glow, rising bubbles, swaying seaweed for
1307
+ * 梦海游鱼) with two deliberate changes:
1308
+ *
1309
+ * - sizes are expressed in **percentages and em**, not the source system's
1310
+ * fixed 223 px sidebar width, so the scene scales to whatever width the
1311
+ * user drags the sidebar to;
1312
+ * - nothing here carries colour of its own beyond the ported artwork, and the
1313
+ * layer is `pointer-events:none` / `z-index:0`, so it can never intercept a
1314
+ * click or cover a menu item. The source system has the same rule, and a
1315
+ * bug note there records opaque mountains hiding the bottom menu rows.
1316
+ *
1317
+ * `#dsh-theme-ambient` is a seat this plugin injects into the sidebar column;
1318
+ * `syncAmbient()` fills it, and each theme chooses its scene by `ambient.kind`.
1319
+ */
1320
+ const AMBIENT_CSS = [
1321
+ /* The layer, arranged exactly like the working `dsh-theme-firefly` ambient layer:
1322
+ a full-viewport fixed element styled by a CLASS, appended to `document.body`, at
1323
+ `z-index: 60`.
1324
+ Every other arrangement of this layer failed to paint on this machine — precise
1325
+ inline geometry, maximum z-index, `documentElement` as the parent, inline styles
1326
+ per element. The firefly plugin uses this one and renders, so this is now the
1327
+ layer's arrangement. The scenery is placed INSIDE it (`.dsh-ambient-scene`). */
1328
+ '#dsh-theme-ambient{position:fixed;inset:0;pointer-events:none;overflow:hidden;z-index:60}',
1329
+ '.dsh-ambient-scene{overflow:hidden}',
1330
+ /* The scene roots carry their own positioning inline too; the selectors below are
1331
+ kept only where a rule cannot be inlined (`@keyframes`) or where they must reach
1332
+ a shell element. */
1333
+ '.ZTP-Xa_sidebarCol{position:relative}',
1334
+
1335
+ /* BRING-UP PROBE — remove with the rest of the diagnostics.
1336
+ The control layer, copied rule-for-rule from the working `dsh-theme-firefly`
1337
+ plugin: geometry and stacking from a class, motion from `@keyframes`, and only
1338
+ the per-element random values inline. */
1339
+ '.dsh-amb-control{position:fixed;inset:0;pointer-events:none;z-index:60;overflow:hidden}',
1340
+ /* BRING-UP PROBE — the scene box inside the proven layer. No `pointer-events` here on
1341
+ purpose: the layer above already disables it, and this box is the thing being
1342
+ tested rather than a decoration that must stay click-through. */
1343
+ '.dsh-amb-control-scene{overflow:hidden}',
1344
+
1345
+ /* The scenery's own geometry. These live here rather than inline because the scene
1346
+ markup is built as a string, and they must reach it wherever it is mounted — which
1347
+ is now inside the proven layer rather than a layer of its own. */
1348
+ '.dsh-amb-control-scene .sta-mountains{position:absolute;left:0;right:0;bottom:0;height:46%;z-index:3}',
1349
+ '.dsh-amb-control-scene .sta-mountains svg{display:block;width:100%;height:100%}',
1350
+ '.dsh-amb-control-scene .sta-mist{position:absolute;height:1.6em;border-radius:1000px;z-index:4;',
1351
+ 'background:linear-gradient(90deg,transparent,rgba(255,255,255,.75),transparent);',
1352
+ 'filter:blur(5px);opacity:.85;animation:dsh-amb-mist 26s ease-in-out infinite alternate}',
1353
+ '.dsh-amb-control-scene .sta-mist-1{width:62%;top:56%;left:12%}',
1354
+ '.dsh-amb-control-scene .sta-mist-2{width:44%;top:61%;left:38%;opacity:.6;',
1355
+ 'animation-duration:32s;animation-delay:-9s}',
1356
+ '.dsh-amb-control-scene .sta-pond{position:absolute;left:0;right:0;bottom:0;height:13%;z-index:5;',
1357
+ 'background:linear-gradient(to bottom,rgba(104,178,150,.62),rgba(66,141,113,.78))}',
1358
+ '.dsh-amb-control-scene .sta-pond-line{position:absolute;top:0;left:0;right:0;height:1.2px;opacity:.6;',
1359
+ 'background:linear-gradient(90deg,transparent,rgba(255,255,255,.9),transparent)}',
1360
+ '.dsh-amb-control-scene .sta-ripple{position:absolute;z-index:6;width:.55em;height:.55em}',
1361
+ '.dsh-amb-control-scene .sta-ripple span{position:absolute;inset:0;',
1362
+ 'border:1.6px solid rgba(217,123,164,.9);border-radius:50%;',
1363
+ 'animation:dsh-amb-ring 3.2s ease-out infinite}',
1364
+ '.dsh-amb-control-scene .sta-ripple span:nth-child(2){animation-delay:1.6s}',
789
1365
  '@keyframes dsh-amb-ring{0%{transform:scale(.4);opacity:.8}100%{transform:scale(4.2);opacity:0}}',
790
1366
  '.dsh-amb-control-scene .sta-dfly{position:absolute;z-index:8;will-change:transform}',
791
1367
  '.dsh-amb-control-scene .sta-dfly svg{display:block;width:100%;height:auto}',
@@ -973,6 +1549,67 @@ window.__ModuleLoader__.load({
973
1549
  'box-shadow:0 0 10px 3px rgba(122,205,255,.55),0 0 22px 6px rgba(122,205,255,.22);',
974
1550
  'animation:dsh-amb-mote linear infinite}',
975
1551
 
1552
+ /* ── 营慕彩云 / 江畔冬云 / 徐山军月 / 佩安杰心 / 光彩凤晨 ─────────────
1553
+ The five scenes ported alongside shan and dream. Their element geometry,
1554
+ colour and per-element timing are INLINE in the scene builders (same rule
1555
+ as shan: a stylesheet that never applies cannot silently collapse them),
1556
+ so the only things that must live here are the `@keyframes` — the one
1557
+ construct that cannot be expressed inline. Each scene prefixes its own
1558
+ keyframe names (ym / jp / xs / pj / gc) so they cannot collide. */
1559
+ '@keyframes dsh-amb-ym-star{0%{opacity:.35;transform:scale(1)}100%{opacity:1;transform:scale(1.25)}}',
1560
+ '@keyframes dsh-amb-ym-drift{from{transform:translateX(0)}to{transform:translateX(24em)}}',
1561
+ '@keyframes dsh-amb-ym-sea{from{transform:translateX(0)}to{transform:translateX(-50%)}}',
1562
+ '@keyframes dsh-amb-ym-bob{0%,100%{transform:translateY(0) rotate(-2.5deg)}50%{transform:translateY(-9px) rotate(2.5deg)}}',
1563
+ /* The balloons' wide round-trip paths. Deliberately two distinct keyframes, so the
1564
+ two never move as a pair: the lower sidebar rows are covered and exposed in turn
1565
+ rather than blocked by one stationary balloon.
1566
+ The main balloon's loop is (0, +1.5em) → (7em, +0.5em) → back: the START and END
1567
+ sit 1.5em below its resting line and the rightmost apex sits 0.5em below it, so the
1568
+ path rises while travelling right and falls while returning — up, down, left and
1569
+ right — while the apex itself stays exactly where it was accepted.
1570
+ EVERY vertical term is at or below the resting baseline (y >= 0), and that is a
1571
+ requirement, not taste: the scene box clips at its own top edge (`overflow:hidden`),
1572
+ so any stop above y=0 slices the envelope's crown off at the apex, which is the hard
1573
+ crop the user reported. */
1574
+ '@keyframes dsh-amb-ym-wander{0%{transform:translate(0,1.5em)}50%{transform:translate(7em,0.5em)}100%{transform:translate(0,1.5em)}}',
1575
+ '@keyframes dsh-amb-ym-wander-2{0%{transform:translate(0,0)}50%{transform:translate(-5em,1.6em)}100%{transform:translate(0,0)}}',
1576
+ '@keyframes dsh-amb-jp-cloud{from{transform:translateX(0)}to{transform:translateX(22em)}}',
1577
+ '@keyframes dsh-amb-jp-snow{0%{transform:translate(0,-0.5em);opacity:0}8%{opacity:.9}'
1578
+ + '50%{transform:translate(0.9em,9em)}92%{opacity:.9}100%{transform:translate(-0.5em,19em);opacity:0}}',
1579
+ '@keyframes dsh-amb-jp-ripple{from{transform:translateX(0)}to{transform:translateX(25em)}}',
1580
+ '@keyframes dsh-amb-jp-glint{0%{opacity:.25;transform:scale(.8)}100%{opacity:.95;transform:scale(1.3)}}',
1581
+ '@keyframes dsh-amb-jp-boat{from{transform:translateX(0)}to{transform:translateX(3em)}}',
1582
+ '@keyframes dsh-amb-jp-bob{0%,100%{transform:translateY(0) rotate(-1.2deg)}50%{transform:translateY(-3px) rotate(1.4deg)}}',
1583
+ '@keyframes dsh-amb-jp-sway{0%,100%{transform:rotate(0deg)}50%{transform:rotate(3deg)}}',
1584
+ '@keyframes dsh-amb-xs-star{0%{opacity:.3;transform:scale(.85)}100%{opacity:1;transform:scale(1.25)}}',
1585
+ '@keyframes dsh-amb-xs-night{from{transform:translateX(0)}to{transform:translateX(26em)}}',
1586
+ '@keyframes dsh-amb-xs-meteor{0%{transform:rotate(-32deg) translateX(0);opacity:0}3%{opacity:1}'
1587
+ + '9%{transform:rotate(-32deg) translateX(-9em);opacity:0}'
1588
+ + '100%{transform:rotate(-32deg) translateX(-9em);opacity:0}}',
1589
+ '@keyframes dsh-amb-pj-smoke{0%{stroke-dashoffset:90;opacity:0}12%{opacity:.7}70%{opacity:.45}'
1590
+ + '100%{stroke-dashoffset:-20;opacity:0}}',
1591
+ '@keyframes dsh-amb-pj-dust{0%{transform:translate(0,0);opacity:0}15%{opacity:.9}'
1592
+ + '55%{transform:translate(-0.6em,-1.9em);opacity:.5}100%{transform:translate(0.5em,-4em);opacity:0}}',
1593
+ '@keyframes dsh-amb-gc-ray{0%{transform:translateY(-40%);opacity:.3}50%{transform:translateY(30%);opacity:.8}'
1594
+ + '100%{transform:translateY(90%);opacity:.3}}',
1595
+ '@keyframes dsh-amb-gc-feather{0%{transform:translate(0,0) rotate(0deg);opacity:0}10%{opacity:.85}'
1596
+ + '25%{transform:translate(1.9em,6em) rotate(95deg)}50%{transform:translate(-1.9em,13em) rotate(190deg);opacity:.85}'
1597
+ + '75%{transform:translate(1.9em,20em) rotate(285deg)}90%{opacity:.8}'
1598
+ + '100%{transform:translate(0,26em) rotate(380deg);opacity:0}}',
1599
+ '@keyframes dsh-amb-gc-fly{0%{transform:translate(-11em,0.6em) scaleX(1);opacity:0}6%{opacity:.95}'
1600
+ + '40%{transform:translate(-3em,-1.1em) scaleX(1);opacity:.95}'
1601
+ + '46%{transform:translate(2.5em,-0.5em) scaleX(1);opacity:.85}'
1602
+ + '50%{transform:translate(9em,0) scaleX(1);opacity:0}'
1603
+ + '50.1%{transform:translate(9em,0) scaleX(-1);opacity:0}'
1604
+ + '56%{transform:translate(2.5em,-0.6em) scaleX(-1);opacity:.9}'
1605
+ + '90%{transform:translate(-9em,0.4em) scaleX(-1);opacity:.6}'
1606
+ + '96%{transform:translate(-11em,0.7em) scaleX(-1);opacity:0}'
1607
+ + '96.1%{transform:translate(-11em,0.7em) scaleX(1);opacity:0}'
1608
+ + '100%{transform:translate(-11em,0.6em) scaleX(1);opacity:0}}',
1609
+ '@keyframes dsh-amb-gc-bob{0%,100%{transform:translateY(0) rotate(0deg)}50%{transform:translateY(-5px) rotate(-1.5deg)}}',
1610
+ '@keyframes dsh-amb-gc-wing{0%{transform:rotate(0deg)}40%{transform:rotate(-3.5deg)}60%{transform:rotate(2.5deg)}100%{transform:rotate(0deg)}}',
1611
+ '@keyframes dsh-amb-gc-dew{0%{opacity:.3;transform:scale(1)}100%{opacity:1;transform:scale(1.5)}}',
1612
+
976
1613
  /* Respect a user who has asked the system for less motion: the scenery
977
1614
  stays, the movement does not. */
978
1615
  '@media (prefers-reduced-motion:reduce){#dsh-theme-ambient *{animation:none!important}}',
@@ -986,6 +1623,10 @@ window.__ModuleLoader__.load({
986
1623
  current: '当前',
987
1624
  applied: '已应用',
988
1625
  empty: '正在读取官方主题注册表…',
1626
+ // The diagnostics below the cards are a FEATURE — a live reading of the active skin —
1627
+ // and a wall of monospace text reads as a fault unless the panel says otherwise.
1628
+ diagNote: '以下为有意设计的诊断信息,便于直观查看皮肤的实时运行状态,并非系统报错;'
1629
+ + '带 ⚠ 的行才表示装饰未生效',
989
1630
  }
990
1631
  const en = {
991
1632
  title: 'Theme skins',
@@ -994,6 +1635,8 @@ window.__ModuleLoader__.load({
994
1635
  current: 'Current',
995
1636
  applied: 'Applied',
996
1637
  empty: 'Reading the theme registry…',
1638
+ diagNote: 'The lines below are intentional diagnostics of the live skin state, '
1639
+ + 'not an error report; only lines starting with ⚠ mean the scenery is not working',
997
1640
  }
998
1641
 
999
1642
  /**
@@ -1024,6 +1667,12 @@ window.__ModuleLoader__.load({
1024
1667
  'background:var(--dsw-alias-bg-layer-3);border:.5px solid var(--dsw-alias-border-l3);',
1025
1668
  'border-radius:8px;padding:8px 10px;word-break:break-all}',
1026
1669
  '.tg-warn{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}',
1670
+ /* The diagnostics live BELOW the cards, set apart by their own margin and a divider,
1671
+ so they read as a separate reference block rather than as part of the picker — and
1672
+ so the picker itself stays the first thing the eye lands on. */
1673
+ '.tg-diag{margin-top:14px;padding-top:12px;border-top:.5px solid var(--dsw-alias-border-l2);',
1674
+ 'display:flex;flex-direction:column;gap:10px}',
1675
+ '.tg-diag-note{font-size:12px;line-height:1.5;color:var(--dsw-alias-label-secondary)}',
1027
1676
  '.tg-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px}',
1028
1677
  '.tg-card{display:flex;flex-direction:column;gap:8px;padding:12px;text-align:left;cursor:pointer;',
1029
1678
  'background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);',
@@ -1616,197 +2265,1018 @@ window.__ModuleLoader__.load({
1616
2265
  + 'animation:dsh-amb-wash 31s ease-in-out infinite alternate;animation-delay:-8s',
1617
2266
  }) + '</div>'
1618
2267
  + '</div>'
1619
- + open('div', { class: 'dof-bubbles', style: 'position:absolute;inset:0;z-index:6;pointer-events:none' })
1620
- + bubbleNodes + '</div>'
1621
- + open('div', { class: 'dof-fish-layer', style: 'position:absolute;inset:0;z-index:6;pointer-events:none' })
1622
- + fishNodes + '</div>'
1623
- + open('div', { class: 'dof-motes', style: 'position:absolute;inset:0;z-index:7;pointer-events:none' })
1624
- + moteNodes + '</div>'
2268
+ + open('div', { class: 'dof-bubbles', style: 'position:absolute;inset:0;z-index:6;pointer-events:none' })
2269
+ + bubbleNodes + '</div>'
2270
+ + open('div', { class: 'dof-fish-layer', style: 'position:absolute;inset:0;z-index:6;pointer-events:none' })
2271
+ + fishNodes + '</div>'
2272
+ + open('div', { class: 'dof-motes', style: 'position:absolute;inset:0;z-index:7;pointer-events:none' })
2273
+ + moteNodes + '</div>'
2274
+ + open('div', {
2275
+ class: 'dof-floor',
2276
+ style: 'position:absolute;left:0;right:0;bottom:0;height:20%;z-index:4;'
2277
+ + 'background:linear-gradient(to bottom,rgba(126,184,222,0) 0%,rgba(126,184,222,.4) 46%,rgba(84,152,199,.62) 100%)',
2278
+ }) + '</div>'
2279
+ + open('div', {
2280
+ class: 'dof-seaweed',
2281
+ style: 'position:absolute;left:0;right:0;bottom:0;height:38%;z-index:5',
2282
+ }) + seaweedMarkup() + '</div>'
2283
+ + '</div>'
2284
+ }
2285
+
2286
+ /**
2287
+ * The seaweed artwork, reproduced from the source system's paths.
2288
+ *
2289
+ * Five blades from three gradients, each animating on its own phase so the bed
2290
+ * sways rather than moving as one rigid shape, over three resting stones.
2291
+ * @returns the seaweed markup.
2292
+ */
2293
+ /**
2294
+ * One expanding ring on the water, positioned inline.
2295
+ * @param left - horizontal position.
2296
+ * @param bottom - vertical position.
2297
+ * @param delay - animation delay, so the rings do not pulse in unison.
2298
+ * @returns the ring markup.
2299
+ */
2300
+ function rippleMarkup(left, bottom, delay) {
2301
+ const ring = 'position:absolute;inset:0;border:1.6px solid rgba(232,139,176,.92);border-radius:50%;'
2302
+ + `animation:dsh-amb-ring 3.2s ease-out infinite;animation-delay:${delay}`
2303
+ return open('div', {
2304
+ class: 'sta-ripple',
2305
+ style: `position:absolute;z-index:6;width:.55em;height:.55em;left:${left};bottom:${bottom}`,
2306
+ }) + `<span style="${ring}"></span><span style="${ring}"></span></div>`
2307
+ }
2308
+
2309
+ /**
2310
+ * One hovering dragonfly: an outer element on its flight path and an inner one
2311
+ * bobbing on the wingbeat, so the two motions compose.
2312
+ * @param suffix - gradient id suffix.
2313
+ * @param className - the positioning class.
2314
+ * @param position - inline position.
2315
+ * @param flight - animation shorthand for the flight path.
2316
+ * @param delay - animation delay.
2317
+ * @returns the dragonfly markup.
2318
+ */
2319
+ function dragonflyBlock(suffix, className, position, flight, delay) {
2320
+ return open('div', {
2321
+ class: `sta-dfly ${className}`,
2322
+ style: `position:absolute;z-index:8;will-change:transform;${position};`
2323
+ + `animation:${flight};animation-delay:${delay}`,
2324
+ }) + open('div', {
2325
+ class: 'sta-bob',
2326
+ style: `animation:dsh-amb-bob ${suffix === '2' ? '1.1s' : '.9s'} ease-in-out infinite;`
2327
+ + `animation-delay:${suffix === '2' ? '-.4s' : '0s'}`,
2328
+ }) + dragonflyMarkup(suffix) + '</div></div>'
2329
+ }
2330
+
2331
+ /**
2332
+ * One seaweed blade. The sway is applied to the group so the blades move from their
2333
+ * base, and each blade animates on its own phase.
2334
+ * @param key - blade index.
2335
+ * @param d - path data.
2336
+ * @param gradient - gradient id.
2337
+ * @param width - stroke width.
2338
+ * @param opacity - blade opacity.
2339
+ * @returns the blade markup.
2340
+ */
2341
+ function bladeMarkup(key, d, gradient, width, opacity) {
2342
+ const durations = { 1: '6s', 2: '7.4s', 3: '5.2s', 4: '8.1s', 5: '6.6s' }
2343
+ const delays = { 1: '0s', 2: '-1.6s', 3: '-2.8s', 4: '-.9s', 5: '-3.4s' }
2344
+ return open('g', {
2345
+ class: `dof-blade dof-blade-${key}`,
2346
+ style: `transform-origin:50% 100%;animation:dsh-amb-sway ${durations[key]} ease-in-out infinite alternate;`
2347
+ + `animation-delay:${delays[key]}`,
2348
+ }) + open('path', {
2349
+ d, fill: `url(#${gradient})`, stroke: `url(#${gradient})`, 'stroke-width': width, opacity,
2350
+ }) + '</path></g>'
2351
+ }
2352
+
2353
+ function seaweedMarkup() {
2354
+ const blade = bladeMarkup
2355
+
2356
+ return open('svg', {
2357
+ viewBox: '0 0 140 120',
2358
+ preserveAspectRatio: 'none',
2359
+ style: 'display:block;width:100%;height:100%',
2360
+ })
2361
+ + open('defs', {})
2362
+ + open('linearGradient', { id: 'dsh-dof-weed-a', x1: '0', y1: '1', x2: '0', y2: '0' })
2363
+ + open('stop', { offset: '0', 'stop-color': '#1E6E93' }) + '</stop>'
2364
+ + open('stop', { offset: '0.55', 'stop-color': '#3E93BC' }) + '</stop>'
2365
+ + open('stop', { offset: '1', 'stop-color': '#A5DEF0', 'stop-opacity': '0.85' }) + '</stop>'
2366
+ + '</linearGradient>'
2367
+ + open('linearGradient', { id: 'dsh-dof-weed-b', x1: '0', y1: '1', x2: '0', y2: '0' })
2368
+ + open('stop', { offset: '0', 'stop-color': '#2B7FA6' }) + '</stop>'
2369
+ + open('stop', { offset: '0.6', 'stop-color': '#4FA3C6' }) + '</stop>'
2370
+ + open('stop', { offset: '1', 'stop-color': '#B8E2F2', 'stop-opacity': '0.85' }) + '</stop>'
2371
+ + '</linearGradient>'
2372
+ + open('linearGradient', { id: 'dsh-dof-weed-c', x1: '0', y1: '1', x2: '0', y2: '0' })
2373
+ + open('stop', { offset: '0', 'stop-color': '#4A78A8' }) + '</stop>'
2374
+ + open('stop', { offset: '0.6', 'stop-color': '#7FA9CE' }) + '</stop>'
2375
+ + open('stop', { offset: '1', 'stop-color': '#CBE2F4', 'stop-opacity': '0.8' }) + '</stop>'
2376
+ + '</linearGradient>'
2377
+ + '</defs>'
2378
+ + blade('1', 'M 22 120 C 12 96, 26 74, 18 48 C 15 38, 18 28, 24 20 C 20 34, 24 44, 30 60 C 36 80, 30 100, 32 120 Z', 'dsh-dof-weed-a', '2.2', '0.95')
2379
+ + blade('2', 'M 48 120 C 40 98, 54 80, 46 56 C 42 44, 48 34, 56 24 C 50 40, 56 52, 60 68 C 64 88, 56 104, 58 120 Z', 'dsh-dof-weed-b', '2', '0.92')
2380
+ + blade('3', 'M 74 120 C 68 102, 78 88, 72 68 C 69 58, 72 48, 78 40 C 74 52, 78 62, 82 76 C 86 94, 80 108, 82 120 Z', 'dsh-dof-weed-c', '1.8', '0.88')
2381
+ + blade('4', 'M 96 120 C 92 104, 102 90, 96 72 C 93 62, 96 54, 102 46 C 98 58, 102 68, 106 82 C 110 98, 102 110, 104 120 Z', 'dsh-dof-weed-a', '1.6', '0.85')
2382
+ + blade('5', 'M 118 120 C 114 108, 122 96, 117 82 C 115 74, 117 68, 121 62 C 118 72, 121 80, 124 92 C 127 104, 121 112, 123 120 Z', 'dsh-dof-weed-b', '1.4', '0.8')
2383
+ + open('ellipse', { cx: '30', cy: '119', rx: '14', ry: '4', fill: '#7FAFC6', opacity: '0.55' }) + '</ellipse>'
2384
+ + open('ellipse', { cx: '72', cy: '120', rx: '10', ry: '3.4', fill: '#8FB9CE', opacity: '0.5' }) + '</ellipse>'
2385
+ + open('ellipse', { cx: '108', cy: '119.5', rx: '12', ry: '3.6', fill: '#7FAFC6', opacity: '0.45' }) + '</ellipse>'
2386
+ + '</svg>'
2387
+ }
2388
+
2389
+ /**
2390
+ * One cartoon fish, swimming across the water.
2391
+ *
2392
+ * Ported from the source system's separate `FishAnimation.vue`. That component drives the
2393
+ * fish from JavaScript through entering / bubbling / leaving phases on a 60-second cycle,
2394
+ * but this skin is a static bundle with no component runtime to host a script — so the
2395
+ * same artwork swims continuously instead, on a CSS `@keyframes` cross, with the tail and
2396
+ * the whole body on separate animations so it reads as swimming rather than sliding.
2397
+ *
2398
+ * The artwork is reproduced shape for shape: the body curve, tail, dorsal fin, pectoral
2399
+ * fin, and the three-part eye. The colours are retuned into the theme's blue family —
2400
+ * the source values (`#38bdf8` body, `#1d4ed8` fins) sat outside the dream palette and
2401
+ * read as a sticker, the same treatment shan's mountains got when their source greens
2402
+ * were darkened for this sidebar.
2403
+ * @param suffix - unique id suffix, so several fish can coexist.
2404
+ * @param size - rendered width in em.
2405
+ * @param top - vertical position within the water, as a percentage.
2406
+ * @param duration - seconds for one crossing.
2407
+ * @param delay - animation delay, so the fish do not move in lockstep.
2408
+ * @param flip - whether this fish swims right-to-left instead.
2409
+ * @returns the fish markup.
2410
+ */
2411
+ function fishMarkup(suffix, size, top, duration, delay, flip) {
2412
+ const anim = flip ? 'dsh-amb-swim-back' : 'dsh-amb-swim'
2413
+ return open('div', {
2414
+ class: `dof-fish${flip ? ' dof-fish-flip' : ''}`,
2415
+ style: 'position:absolute;left:0;opacity:.94;'
2416
+ + `top:${top}%;width:${size}em;`
2417
+ + `animation:${anim} ${duration}s linear infinite;animation-delay:${delay}s`,
2418
+ })
2419
+ + open('div', {
2420
+ class: 'dof-fish-bob',
2421
+ style: `animation-duration:${(duration / 8).toFixed(2)}s`,
2422
+ })
2423
+ + open('svg', {
2424
+ viewBox: '0 0 50 18',
2425
+ preserveAspectRatio: 'xMidYMid meet',
2426
+ style: 'display:block;width:100%;height:auto;overflow:visible',
2427
+ })
2428
+ + open('g', { class: 'dof-fish-body' })
2429
+ // Body — recoloured into the theme's own blue family. The source values
2430
+ // (`#38bdf8` body, `#1d4ed8` fins) sat outside the dream palette and read
2431
+ // as a sticker pasted on the water; the same treatment shan's mountains
2432
+ // got when their source greens were darkened for this sidebar.
2433
+ + open('path', {
2434
+ d: 'M10 10 C20 5 35 5 45 10 C40 15 25 15 10 10 Z',
2435
+ fill: '#5FA5D6', stroke: '#2B6E9E', 'stroke-width': '1',
2436
+ }) + '</path>'
2437
+ // Tail
2438
+ + open('path', {
2439
+ d: 'M10 10 L5 7 L5 13 Z',
2440
+ fill: '#2B6E9E', stroke: '#2B6E9E', 'stroke-width': '1', class: 'dof-fish-tail',
2441
+ }) + '</path>'
2442
+ // Dorsal fin
2443
+ + open('path', {
2444
+ d: 'M20 7 L25 3 L30 7',
2445
+ fill: '#2B6E9E', stroke: '#2B6E9E', 'stroke-width': '1',
2446
+ }) + '</path>'
2447
+ // Pectoral fin
2448
+ + open('path', {
2449
+ d: 'M35 9 L40 12 L45 9',
2450
+ fill: '#5FA5D6', stroke: '#2B6E9E', 'stroke-width': '1',
2451
+ }) + '</path>'
2452
+ // Eye: white, pupil, highlight
2453
+ + open('circle', { cx: '40', cy: '8', r: '2', fill: '#FFFFFF' }) + '</circle>'
2454
+ + open('circle', { cx: '41', cy: '8', r: '1', fill: '#16384F' }) + '</circle>'
2455
+ + open('circle', { cx: '40.5', cy: '7.5', r: '0.5', fill: '#FFFFFF' }) + '</circle>'
2456
+ + '</g>'
2457
+ + '</svg>'
2458
+ + '</div></div>'
2459
+ }
2460
+
2461
+ /**
2462
+ * One drifting cloud-sea copy for 营慕彩云, ported shape-for-shape from
2463
+ * `YingMuCaiYunAnimation.vue`. The source drifts a 200%-wide flex track holding
2464
+ * two copies of the same SVG, so one marquee keyframe loops seamlessly; each
2465
+ * copy carries its own gradient id because two tracks (back and front) and two
2466
+ * preview seats all live in one document.
2467
+ * @param variant - 'back' (deep purple, slow) or 'front' (pink-gold, quicker).
2468
+ * @param copy - unique id suffix for this copy's gradient.
2469
+ * @returns one sea-section SVG.
2470
+ */
2471
+ function ymSeaSvg(variant, copy) {
2472
+ const back = variant === 'back'
2473
+ const gradient = `dsh-ym-sea-${variant}-${copy}`
2474
+ return open('svg', {
2475
+ viewBox: back ? '0 0 240 70' : '0 0 240 80',
2476
+ preserveAspectRatio: 'none',
2477
+ style: 'width:50%;height:100%;flex:none;display:block',
2478
+ })
2479
+ + open('defs', {})
2480
+ + open('linearGradient', { id: gradient, x1: '0', y1: '0', x2: '0', y2: '1' })
2481
+ + open('stop', { offset: '0', 'stop-color': back ? '#C9B6E4' : '#FFF0F7' }) + '</stop>'
2482
+ + open('stop', { offset: '1', 'stop-color': back ? '#A68FCA' : '#E8B4D6' }) + '</stop>'
2483
+ + '</linearGradient>'
2484
+ + '</defs>'
2485
+ + open('path', {
2486
+ d: back
2487
+ ? 'M0 70 L0 44 Q14 40 22 46 Q26 28 44 30 Q54 16 72 22 Q84 8 102 16 Q118 6 134 14 '
2488
+ + 'Q150 8 164 18 Q178 12 190 24 Q206 20 216 34 Q230 32 240 42 L240 70 Z'
2489
+ : 'M0 80 L0 52 Q12 48 22 54 Q28 36 48 38 Q60 24 78 30 Q92 14 110 24 Q126 12 142 22 '
2490
+ + 'Q158 14 172 26 Q188 22 198 36 Q214 34 224 48 Q234 50 240 56 L240 80 Z',
2491
+ fill: `url(#${gradient})`,
2492
+ }) + '</path>'
2493
+ + '</svg>'
2494
+ }
2495
+
2496
+ /**
2497
+ * One hot-air balloon for 营慕彩云, ported from the source component's two
2498
+ * inline SVGs: envelope with highlight, suspension ropes, and a basket.
2499
+ * @param variant - 'main' (orange-gold-pink, near) or 'mini' (pink-purple, far).
2500
+ * @returns the balloon SVG.
2501
+ */
2502
+ function ymBalloonMarkup(variant) {
2503
+ const main = variant === 'main'
2504
+ const gradient = main ? 'dsh-ym-balloon-main' : 'dsh-ym-balloon-mini'
2505
+ const svg = open('svg', {
2506
+ viewBox: main ? '0 0 64 96' : '0 0 40 62',
2507
+ style: 'display:block;width:100%;height:auto;overflow:visible;'
2508
+ + 'filter:drop-shadow(0 4px 6px rgba(90,58,90,.25))',
2509
+ })
2510
+ + open('defs', {})
2511
+ + open('linearGradient', { id: gradient, x1: '0', y1: '0', x2: '1', y2: '1' })
2512
+ + (main
2513
+ ? open('stop', { offset: '0', 'stop-color': '#FFC96B' }) + '</stop>'
2514
+ + open('stop', { offset: '0.55', 'stop-color': '#FFB347' }) + '</stop>'
2515
+ + open('stop', { offset: '1', 'stop-color': '#F08BB4' }) + '</stop>'
2516
+ : open('stop', { offset: '0', 'stop-color': '#F2C9E3' }) + '</stop>'
2517
+ + open('stop', { offset: '1', 'stop-color': '#A68FCA' }) + '</stop>')
2518
+ + '</linearGradient>'
2519
+ + '</defs>'
2520
+ if (main) {
2521
+ return svg
2522
+ + open('path', {
2523
+ d: 'M32 3 C48 3 59 15 59 30 C59 45 47 58 38 66 L26 66 C17 58 5 45 5 30 C5 15 16 3 32 3 Z',
2524
+ fill: `url(#${gradient})`,
2525
+ }) + '</path>'
2526
+ + open('path', {
2527
+ d: 'M32 3 C36 3 39 15 39 30 C39 45 37 58 35.5 66 L28.5 66 C27 58 25 45 25 30 C25 15 28 3 32 3 Z',
2528
+ fill: '#FFE3B8', opacity: '0.85',
2529
+ }) + '</path>'
2530
+ + open('path', { d: 'M13 13 C9 22 10 40 18 55', stroke: '#E88BB0', 'stroke-width': '1.4', fill: 'none', opacity: '0.55' }) + '</path>'
2531
+ + open('path', { d: 'M51 13 C55 22 54 40 46 55', stroke: '#E88BB0', 'stroke-width': '1.4', fill: 'none', opacity: '0.55' }) + '</path>'
2532
+ + open('path', { d: 'M27.5 66 L28.5 75 M36.5 66 L35.5 75', stroke: '#8A6A4A', 'stroke-width': '1.2' }) + '</path>'
2533
+ + open('rect', { x: '25.5', y: '75', width: '13', height: '9', rx: '2.5', fill: '#9C7C5C' }) + '</rect>'
2534
+ + open('path', { d: 'M25.5 78.5 L38.5 78.5', stroke: '#7E6146', 'stroke-width': '1.2' }) + '</path>'
2535
+ + '</svg>'
2536
+ }
2537
+ return svg
2538
+ + open('path', {
2539
+ d: 'M20 2 C30 2 36 10 36 19 C36 29 28 37 22.5 42 L17.5 42 C12 37 4 29 4 19 C4 10 10 2 20 2 Z',
2540
+ fill: `url(#${gradient})`,
2541
+ }) + '</path>'
2542
+ + open('path', {
2543
+ d: 'M20 2 C23 2 25.5 10 25.5 19 C25.5 29 24 37 23 42 L17 42 C16 37 14.5 29 14.5 19 C14.5 10 17 2 20 2 Z',
2544
+ fill: '#F6E7F4', opacity: '0.9',
2545
+ }) + '</path>'
2546
+ + open('path', { d: 'M17.5 42 L18 47 M22.5 42 L22 47', stroke: '#8A6A4A', 'stroke-width': '1' }) + '</path>'
2547
+ + open('rect', { x: '16', y: '47', width: '8', height: '6', rx: '1.5', fill: '#8A6A4A' }) + '</rect>'
2548
+ + '</svg>'
2549
+ }
2550
+
2551
+ /**
2552
+ * The 营慕彩云 scene: a dusk glow, warm stars, blurred drifting clouds, two
2553
+ * marquee cloud-seas (deep purple behind, pink-gold in front) and two bobbing
2554
+ * hot-air balloons.
2555
+ *
2556
+ * Ported from `YingMuCaiYunAnimation.vue`. The source stars are white on a dusk
2557
+ * sky; on this sidebar's pastel gradient white would vanish, so they are retuned
2558
+ * into the theme's warm gold — the same treatment the dream fish got when their
2559
+ * sticker blues moved into the theme family.
2560
+ * @param stars - how many stars to seed.
2561
+ * @returns the scene markup.
2562
+ */
2563
+ function caiyunAmbientScene(stars) {
2564
+ const count = Math.max(0, Math.min(30, stars ?? 12))
2565
+ let starNodes = ''
2566
+ for (let n = 1; n <= count; n += 1) {
2567
+ const size = 2 + ((n * 3) % 2)
2568
+ starNodes += open('div', {
2569
+ class: 'ym-star',
2570
+ style: 'position:absolute;border-radius:50%;'
2571
+ + `left:${((n * 37) % 84) + 6}%;top:${((n * 23) % 44) + 4}%;`
2572
+ + `width:${size}px;height:${size}px;`
2573
+ + 'background:radial-gradient(circle,#FFF7E2 0%,rgba(255,232,190,.55) 100%);'
2574
+ + 'box-shadow:0 0 4px rgba(255,224,160,.8);'
2575
+ + 'animation:dsh-amb-ym-star 2.2s ease-in-out infinite alternate;'
2576
+ + `animation-delay:${-(n * 0.37)}s`,
2577
+ }) + '</div>'
2578
+ }
2579
+
2580
+ const DRIFTS = [
2581
+ ['1', '18em', '5em', '8%', '70s', '-22s'],
2582
+ ['2', '24em', '7em', '28%', '110s', '-44s'],
2583
+ ['3', '16em', '4.5em', '52%', '90s', '-66s'],
2584
+ ]
2585
+ let driftNodes = ''
2586
+ for (const [key, width, height, top, duration, delay] of DRIFTS) {
2587
+ driftNodes += open('div', {
2588
+ class: `ym-drift ym-drift-${key}`,
2589
+ style: `position:absolute;border-radius:1000px;left:-14em;top:${top};width:${width};height:${height};`
2590
+ + 'background:radial-gradient(ellipse,rgba(232,180,214,.55) 0%,rgba(166,143,202,.3) 60%,rgba(166,143,202,0) 100%);'
2591
+ + 'filter:blur(10px);'
2592
+ + `animation:dsh-amb-ym-drift ${duration} linear infinite;animation-delay:${delay}`,
2593
+ }) + '</div>'
2594
+ }
2595
+
2596
+ const sea = (variant, bottom, height, zIndex, opacity, duration) => open('div', {
2597
+ class: `ym-sea ym-sea-${variant}`,
2598
+ style: `position:absolute;left:0;right:0;bottom:${bottom};height:${height};z-index:${zIndex};`
2599
+ + `opacity:${opacity};overflow:hidden`,
2600
+ })
2601
+ + open('div', {
2602
+ class: 'ym-sea-track',
2603
+ style: `position:absolute;left:0;top:0;width:200%;height:100%;display:flex;`
2604
+ + `animation:dsh-amb-ym-sea ${duration} linear infinite`,
2605
+ })
2606
+ + ymSeaSvg(variant, 'a') + ymSeaSvg(variant, 'b')
2607
+ + '</div></div>'
2608
+
2609
+ // The outer element carries a WIDE WANDER and the inner one keeps the source's
2610
+ // small bob + sway. Two separate transforms on two elements, so they compose:
2611
+ // the balloon drifts across the band while still breathing.
2612
+ //
2613
+ // Why the wander: the balloons legitimately float over the sidebar's lower rows,
2614
+ // and a bigger path means they take turns covering and exposing that text instead
2615
+ // of hovering in one spot. The layer/z-index is deliberately NOT lowered — being
2616
+ // in front is what gives them depth, and they are never still for long.
2617
+ const balloon = (variant, position, size, duration, delay, zIndex, wander, wanderDelay) => open('div', {
2618
+ class: `ym-balloon ym-balloon-${variant}`,
2619
+ style: `position:absolute;pointer-events:none;${position}width:${size};z-index:${zIndex};`
2620
+ + `animation:${wander} ease-in-out infinite;animation-delay:${wanderDelay}`,
2621
+ })
2622
+ + open('div', {
2623
+ class: 'ym-bob',
2624
+ style: `transform-origin:50% 25%;animation:dsh-amb-ym-bob ${duration} ease-in-out infinite;`
2625
+ + `animation-delay:${delay}`,
2626
+ })
2627
+ + ymBalloonMarkup(variant)
2628
+ + '</div></div>'
2629
+
2630
+ return open('div', { class: 'ym', style: 'position:absolute;inset:0;display:block' })
2631
+ + open('div', {
2632
+ class: 'ym-glow',
2633
+ style: 'position:absolute;left:0;right:0;bottom:0;height:52%;z-index:1;'
2634
+ + 'background:radial-gradient(ellipse at 35% 100%,rgba(255,214,170,.4) 0%,rgba(255,214,170,0) 70%)',
2635
+ }) + '</div>'
2636
+ + starNodes
2637
+ + driftNodes
2638
+ + sea('back', '16%', '26%', 4, '0.75', '80s')
2639
+ + balloon('mini', 'right:10%;bottom:56%;', '2.6em', '9.5s', '-3.5s', 5,
2640
+ 'dsh-amb-ym-wander-2 38s', '-13s')
2641
+ + sea('front', '0%', '32%', 6, '1', '50s')
2642
+ + balloon('main', 'left:12%;bottom:38%;', '4.6em', '7s', '0s', 7,
2643
+ 'dsh-amb-ym-wander 30s', '0s')
2644
+ + '</div>'
2645
+ }
2646
+
2647
+ /**
2648
+ * One 乌篷船 (black-awning boat) for 江畔冬云, ported from
2649
+ * `JiangPanDongYunAnimation.vue`: hull, awning, poling oar and a warm bow
2650
+ * lantern, plus its water reflection. The source carries the reflection as a
2651
+ * second SVG; here one SVG holds both, the mirror produced by a flipped
2652
+ * `<use>`, so there is no second element to keep in place.
2653
+ * @returns the boat SVG.
2654
+ */
2655
+ function jpBoatMarkup() {
2656
+ return open('svg', { viewBox: '0 0 120 92', style: 'display:block;width:100%;height:auto' })
2657
+ + open('defs', {})
2658
+ + open('g', { id: 'dsh-jp-boat-art' })
2659
+ + open('path', { d: 'M 6 24 Q 60 36 114 22 Q 106 36 62 40 Q 22 38 6 24 Z', fill: '#10222C' }) + '</path>'
2660
+ + open('path', { d: 'M 38 24 C 44 10, 78 10, 88 23 Z', fill: '#0C1B24' }) + '</path>'
2661
+ + open('path', { d: 'M 22 26 L 30 -4', stroke: '#0C1B24', 'stroke-width': '2', 'stroke-linecap': 'round', fill: 'none' }) + '</path>'
2662
+ + open('circle', { cx: '14', cy: '21', r: '2.6', fill: '#F2C879' }) + '</circle>'
2663
+ + open('circle', { cx: '14', cy: '21', r: '5.5', fill: '#F2C879', opacity: '0.25' }) + '</circle>'
2664
+ + '</g>'
2665
+ + '</defs>'
2666
+ + open('use', { href: '#dsh-jp-boat-art' }) + '</use>'
2667
+ + open('use', {
2668
+ href: '#dsh-jp-boat-art',
2669
+ transform: 'translate(0,90) scale(1,-1)',
2670
+ style: 'opacity:.22;filter:blur(2.5px)',
2671
+ }) + '</use>'
2672
+ + '</svg>'
2673
+ }
2674
+
2675
+ /**
2676
+ * The 江畔冬云 scene: a pale winter moon, slow drifting clouds, fine snow, and a
2677
+ * river band carrying a moonlight column, flowing ripples, twinkling wave
2678
+ * glints, a drifting sampan with its reflection, and swaying bank reeds.
2679
+ *
2680
+ * Ported from `JiangPanDongYunAnimation.vue`. The source moon and clouds sit on
2681
+ * a near-black sky; here they sit on the pastel one, so the moon keeps its
2682
+ * source colours but leans on its halo to separate, and the night clouds keep
2683
+ * their shape at a lighter opacity.
2684
+ * @param snow - how many snowflakes to seed.
2685
+ * @returns the scene markup.
2686
+ */
2687
+ function dongyunAmbientScene(snow) {
2688
+ const count = Math.max(0, Math.min(30, snow ?? 8))
2689
+ let snowNodes = ''
2690
+ for (let n = 1; n <= count; n += 1) {
2691
+ const size = 2 + ((n * 3) % 3)
2692
+ snowNodes += open('div', {
2693
+ class: 'jp-snowflake',
2694
+ style: 'position:absolute;top:-0.5em;border-radius:50%;'
2695
+ + `left:${((n * 29) % 88) + 5}%;width:${size}px;height:${size}px;`
2696
+ + 'background:radial-gradient(circle,#FFFFFF 0%,rgba(255,255,255,.75) 100%);'
2697
+ + 'box-shadow:0 0 4px rgba(255,255,255,.85);'
2698
+ + `animation:dsh-amb-jp-snow ${9 + ((n * 7) % 8)}s linear infinite;`
2699
+ + `animation-delay:${-(n * 1.9)}s`,
2700
+ }) + '</div>'
2701
+ }
2702
+
2703
+ const CLOUDS = [
2704
+ ['1', '12em', '3.2em', '10%', '95s', '0s'],
2705
+ ['2', '15em', '4em', '27%', '130s', '-40s'],
2706
+ ['3', '10.5em', '2.8em', '44%', '110s', '-70s'],
2707
+ ]
2708
+ let cloudNodes = ''
2709
+ for (const [key, width, height, top, duration, delay] of CLOUDS) {
2710
+ cloudNodes += open('div', {
2711
+ class: `jp-cloud jp-cloud-${key}`,
2712
+ style: `position:absolute;border-radius:1000px;left:-16em;top:${top};width:${width};height:${height};z-index:3;`
2713
+ + 'background:radial-gradient(ellipse,rgba(226,235,242,.85) 0%,rgba(190,206,220,.45) 60%,rgba(190,206,220,0) 100%);'
2714
+ + 'filter:blur(10px);'
2715
+ + `animation:dsh-amb-jp-cloud ${duration} linear infinite;animation-delay:${delay}`,
2716
+ }) + '</div>'
2717
+ }
2718
+
2719
+ const RIPPLES = [
2720
+ ['1', '9em', '30%', '17s', '0s', '0.4'],
2721
+ ['2', '7em', '55%', '23s', '-9s', '0.3'],
2722
+ ['3', '10.5em', '78%', '14s', '-4s', '0.4'],
2723
+ ]
2724
+ let rippleNodes = ''
2725
+ for (const [key, width, top, duration, delay, opacity] of RIPPLES) {
2726
+ rippleNodes += open('div', {
2727
+ class: `jp-ripple jp-ripple-${key}`,
2728
+ style: `position:absolute;height:2px;border-radius:2px;left:-10em;top:${top};width:${width};opacity:${opacity};`
2729
+ + 'background:linear-gradient(90deg,transparent 0%,rgba(210,232,240,.9) 30%,rgba(210,232,240,.5) 60%,transparent 100%);'
2730
+ + `animation:dsh-amb-jp-ripple ${duration} linear infinite;animation-delay:${delay}`,
2731
+ }) + '</div>'
2732
+ }
2733
+
2734
+ let glintNodes = ''
2735
+ for (let n = 1; n <= 5; n += 1) {
2736
+ glintNodes += open('div', {
2737
+ class: 'jp-glint',
2738
+ style: 'position:absolute;width:3px;height:3px;border-radius:50%;'
2739
+ + `left:${12 + ((n * 19) % 76)}%;top:${28 + ((n * 23) % 58)}%;`
2740
+ + 'background:radial-gradient(circle,#EAF6FA 0%,rgba(234,246,250,.4) 100%);'
2741
+ + 'box-shadow:0 0 6px rgba(220,240,250,.9);'
2742
+ + 'animation:dsh-amb-jp-glint 2.6s ease-in-out infinite alternate;'
2743
+ + `animation-delay:${-(n * 0.6)}s`,
2744
+ }) + '</div>'
2745
+ }
2746
+
2747
+ const REEDS = [
2748
+ ['1', 'M 14 120 C 15 92, 17 62, 24 34', 'M 26 27', '4.6', '13', '9 26 27', '5.2s', '0s'],
2749
+ ['2', 'M 34 120 C 36 96, 42 66, 54 42', 'M 57 35', '4.2', '12', '16 57 35', '6.1s', '-2.1s'],
2750
+ ['3', 'M 56 120 C 56 100, 58 78, 62 56', 'M 63 49', '3.8', '11', '5 63 49', '4.6s', '-1.2s'],
2751
+ ['4', 'M 76 120 C 80 100, 90 78, 102 58', 'M 105 51', '3.6', '10.5', '22 105 51', '5.8s', '-3s'],
2752
+ ]
2753
+ let reedNodes = ''
2754
+ for (const [key, stem, head, rx, ry, rot, duration, delay] of REEDS) {
2755
+ const [angle, cx, cy] = rot.split(' ')
2756
+ reedNodes += open('g', {
2757
+ class: `jp-reed jp-reed-${key}`,
2758
+ style: `transform-box:fill-box;transform-origin:50% 100%;`
2759
+ + `animation:dsh-amb-jp-sway ${duration} ease-in-out infinite;animation-delay:${delay}`,
2760
+ })
2761
+ + open('path', { d: stem, stroke: '#0E202A', 'stroke-width': '2.2', fill: 'none', 'stroke-linecap': 'round' }) + '</path>'
2762
+ + open('ellipse', {
2763
+ cx, cy, rx, ry, fill: '#12242F',
2764
+ transform: `rotate(${angle} ${cx} ${cy})`,
2765
+ }) + '</ellipse>'
2766
+ + '</g>'
2767
+ }
2768
+
2769
+ return open('div', { class: 'jp', style: 'position:absolute;inset:0;display:block' })
2770
+ + open('div', {
2771
+ class: 'jp-moon',
2772
+ style: 'position:absolute;top:5%;right:9%;width:3em;height:3em;border-radius:50%;z-index:2;opacity:.95;'
2773
+ + 'background:radial-gradient(circle at 38% 34%,#FDFAF2 0%,#E8E4D4 55%,#CBD2D8 100%);'
2774
+ + 'box-shadow:0 0 1.4em 0.35em rgba(240,244,240,.5),0 0 3.5em 1em rgba(190,210,225,.4)',
2775
+ }) + '</div>'
2776
+ + cloudNodes
2777
+ + snowNodes
2778
+ + open('div', {
2779
+ class: 'jp-river',
2780
+ style: 'position:absolute;left:0;right:0;bottom:0;height:38%;z-index:7;'
2781
+ + 'background:linear-gradient(to bottom,rgba(150,178,196,.4) 0%,rgba(110,142,164,.5) 100%);'
2782
+ + 'box-shadow:inset 0 0.9em 1.1em -0.6em rgba(16,34,44,.5)',
2783
+ })
2784
+ + open('div', {
2785
+ class: 'jp-waterline',
2786
+ style: 'position:absolute;top:0;left:0;right:0;height:1.5px;opacity:.5;'
2787
+ + 'background:linear-gradient(90deg,transparent 0%,rgba(214,234,242,.9) 35%,rgba(214,234,242,.35) 70%,transparent 100%)',
2788
+ }) + '</div>'
2789
+ + open('div', {
2790
+ class: 'jp-moonlight',
2791
+ style: 'position:absolute;top:4%;left:62%;width:3em;height:82%;border-radius:40%;filter:blur(5px);'
2792
+ + 'background:linear-gradient(to bottom,rgba(226,240,246,.18) 0%,rgba(226,240,246,.06) 55%,transparent 100%)',
2793
+ }) + '</div>'
2794
+ + rippleNodes
2795
+ + glintNodes
2796
+ + '</div>'
2797
+ + open('div', {
2798
+ class: 'jp-boat',
2799
+ style: 'position:absolute;bottom:22%;left:13%;width:7em;z-index:8;'
2800
+ + 'animation:dsh-amb-jp-boat 46s ease-in-out infinite alternate',
2801
+ })
2802
+ + open('div', {
2803
+ class: 'jp-boat-bob',
2804
+ style: 'transform-origin:50% 100%;animation:dsh-amb-jp-bob 5.2s ease-in-out infinite',
2805
+ })
2806
+ + jpBoatMarkup()
2807
+ + '</div></div>'
2808
+ + open('div', {
2809
+ class: 'jp-reeds',
2810
+ style: 'position:absolute;bottom:0;left:0;width:9em;z-index:9',
2811
+ })
2812
+ + open('svg', { viewBox: '0 0 120 120', style: 'display:block;width:100%;height:auto' })
2813
+ + reedNodes
2814
+ + open('path', { d: 'M 2 120 C 20 118, 42 112, 62 103', stroke: '#0E202A', 'stroke-width': '2.4', fill: 'none', 'stroke-linecap': 'round' }) + '</path>'
2815
+ + open('path', { d: 'M 6 120 C 28 118.5, 54 114, 82 108', stroke: '#0E202A', 'stroke-width': '2', fill: 'none', 'stroke-linecap': 'round', opacity: '0.85' }) + '</path>'
2816
+ + open('path', { d: 'M 18 120 C 40 119, 64 116.5, 92 113', stroke: '#0E202A', 'stroke-width': '1.6', fill: 'none', 'stroke-linecap': 'round', opacity: '0.7' }) + '</path>'
2817
+ + '</svg>'
2818
+ + '</div>'
2819
+ + '</div>'
2820
+ }
2821
+
2822
+ /**
2823
+ * One silhouette pine for 徐山军月, ported verbatim from
2824
+ * `XuShanJunYueAnimation.vue`'s `<defs>` art: a tiered pine over a short trunk.
2825
+ * @returns the pine group markup (a `<g>` for reuse inside one SVG).
2826
+ */
2827
+ function xsPineMarkup() {
2828
+ return open('g', { id: 'dsh-xsj-pine-art' })
2829
+ + open('path', {
2830
+ d: 'M 12 120 L 12 112 L 5 112 L 12 100 L 3 100 L 10 88 L 4 88 L 12 74 L 20 88 L 14 88 L 21 100 L 12 100 L 19 112 L 12 112 Z',
2831
+ fill: '#101A11',
2832
+ }) + '</path>'
2833
+ + open('rect', { x: '10.6', y: '112', width: '2.8', height: '8', fill: '#101A11' }) + '</rect>'
2834
+ + '</g>'
2835
+ }
2836
+
2837
+ /**
2838
+ * The 徐山军月 scene: a starfield, a full moon veiled by slow night clouds, a
2839
+ * periodic meteor, two dark mountain ridges, and a row of standing pines.
2840
+ *
2841
+ * Ported from `XuShanJunYueAnimation.vue`. The source stars and moon are pale
2842
+ * cream on a near-black sky; on the pastel sidebar they would vanish, so they
2843
+ * are warmed into the theme's brass family (the same retune the dream fish and
2844
+ * the caiyun stars got), while the ridges and pines keep their verbatim dark
2845
+ * silhouettes — dark on light reads as the classic ink shape.
2846
+ * @param stars - how many stars to seed.
2847
+ * @returns the scene markup.
2848
+ */
2849
+ function junyueAmbientScene(stars) {
2850
+ const count = Math.max(0, Math.min(30, stars ?? 12))
2851
+ let starNodes = ''
2852
+ for (let n = 1; n <= count; n += 1) {
2853
+ const size = 2 + ((n * 3) % 2)
2854
+ starNodes += open('div', {
2855
+ class: 'xs-star',
2856
+ style: 'position:absolute;border-radius:50%;'
2857
+ + `left:${((n * 37) % 84) + 6}%;top:${((n * 23) % 48) + 4}%;`
2858
+ + `width:${size}px;height:${size}px;`
2859
+ + 'background:radial-gradient(circle,#F5EFD2 0%,rgba(212,178,102,.55) 100%);'
2860
+ + 'box-shadow:0 0 5px rgba(212,178,102,.75);'
2861
+ + 'animation:dsh-amb-xs-star 2.4s ease-in-out infinite alternate;'
2862
+ + `animation-delay:${-(n * 0.7)}s`,
2863
+ }) + '</div>'
2864
+ }
2865
+
2866
+ const CLOUDS = [
2867
+ ['1', '10.5em', '1.6em', '12%', '90s', '0s'],
2868
+ ['2', '8.5em', '1.25em', '26%', '120s', '-55s'],
2869
+ ]
2870
+ let cloudNodes = ''
2871
+ for (const [key, width, height, top, duration, delay] of CLOUDS) {
2872
+ cloudNodes += open('div', {
2873
+ class: `xs-cloud xs-cloud-${key}`,
2874
+ style: `position:absolute;border-radius:1000px;left:-12em;top:${top};width:${width};height:${height};z-index:3;`
2875
+ + 'background:linear-gradient(90deg,rgba(24,32,25,0) 0%,rgba(24,32,25,.22) 30%,rgba(24,32,25,.22) 70%,rgba(24,32,25,0) 100%);'
2876
+ + 'filter:blur(5px);'
2877
+ + `animation:dsh-amb-xs-night ${duration} linear infinite;animation-delay:${delay}`,
2878
+ }) + '</div>'
2879
+ }
2880
+
2881
+ return open('div', { class: 'xs', style: 'position:absolute;inset:0;display:block' })
2882
+ + starNodes
2883
+ + open('div', {
2884
+ class: 'xs-moon',
2885
+ style: 'position:absolute;top:7%;right:11%;width:3.6em;height:3.6em;border-radius:50%;z-index:2;'
2886
+ + 'background:radial-gradient(circle at 36% 32%,#FBF6E4 0%,#EFE7CB 58%,#D6CCA8 100%);'
2887
+ + 'box-shadow:0 0 1.6em 0.5em rgba(245,239,217,.5),0 0 4em 1.4em rgba(212,178,102,.22)',
2888
+ }) + '</div>'
2889
+ + cloudNodes
2890
+ + open('div', {
2891
+ class: 'xs-meteor',
2892
+ style: 'position:absolute;top:16%;left:66%;width:4.6em;height:2px;border-radius:2px;z-index:4;'
2893
+ + 'background:linear-gradient(90deg,transparent,rgba(245,242,220,.95));'
2894
+ + 'animation:dsh-amb-xs-meteor 9s ease-in infinite',
2895
+ })
2896
+ + open('span', {
2897
+ style: 'position:absolute;right:-2px;top:-2.2px;width:5px;height:5px;border-radius:50%;'
2898
+ + 'background:#FFFBEA;box-shadow:0 0 8px 2px rgba(255,251,234,.9)',
2899
+ }) + '</span>'
2900
+ + '</div>'
1625
2901
  + open('div', {
1626
- class: 'dof-floor',
1627
- style: 'position:absolute;left:0;right:0;bottom:0;height:20%;z-index:4;'
1628
- + 'background:linear-gradient(to bottom,rgba(126,184,222,0) 0%,rgba(126,184,222,.4) 46%,rgba(84,152,199,.62) 100%)',
1629
- }) + '</div>'
2902
+ class: 'xs-ridges',
2903
+ style: 'position:absolute;left:0;right:0;bottom:0;height:46%;z-index:5',
2904
+ })
2905
+ + open('svg', { viewBox: '0 0 223 160', preserveAspectRatio: 'none', style: 'display:block;width:100%;height:100%' })
2906
+ + open('defs', {})
2907
+ + open('linearGradient', { id: 'dsh-xsj-ridge-back', x1: '0', y1: '0', x2: '0', y2: '1' })
2908
+ + open('stop', { offset: '0', 'stop-color': '#31412F' }) + '</stop>'
2909
+ + open('stop', { offset: '1', 'stop-color': '#273528' }) + '</stop>'
2910
+ + '</linearGradient>'
2911
+ + open('linearGradient', { id: 'dsh-xsj-ridge-front', x1: '0', y1: '0', x2: '0', y2: '1' })
2912
+ + open('stop', { offset: '0', 'stop-color': '#1F2C1F' }) + '</stop>'
2913
+ + open('stop', { offset: '1', 'stop-color': '#161F16' }) + '</stop>'
2914
+ + '</linearGradient>'
2915
+ + '</defs>'
2916
+ + open('path', {
2917
+ d: 'M 0 62 Q 28 30 58 52 Q 92 18 126 46 Q 158 24 188 50 Q 206 38 223 46 L 223 160 L 0 160 Z',
2918
+ fill: 'url(#dsh-xsj-ridge-back)',
2919
+ }) + '</path>'
2920
+ + open('path', {
2921
+ d: 'M 0 104 Q 34 72 68 94 Q 104 64 142 92 Q 176 74 223 100 L 223 160 L 0 160 Z',
2922
+ fill: 'url(#dsh-xsj-ridge-front)',
2923
+ }) + '</path>'
2924
+ + '</svg>'
2925
+ + '</div>'
1630
2926
  + open('div', {
1631
- class: 'dof-seaweed',
1632
- style: 'position:absolute;left:0;right:0;bottom:0;height:38%;z-index:5',
1633
- }) + seaweedMarkup() + '</div>'
2927
+ class: 'xs-pines',
2928
+ style: 'position:absolute;bottom:0;left:3%;width:8.5em;z-index:6',
2929
+ })
2930
+ + open('svg', { viewBox: '0 0 140 120', style: 'display:block;width:100%;height:auto' })
2931
+ + open('defs', {}) + xsPineMarkup() + '</defs>'
2932
+ + open('use', { href: '#dsh-xsj-pine-art', transform: 'translate(-4,-8) scale(1.35)' }) + '</use>'
2933
+ + open('use', { href: '#dsh-xsj-pine-art', transform: 'translate(34,2) scale(1.0)' }) + '</use>'
2934
+ + open('use', { href: '#dsh-xsj-pine-art', transform: 'translate(66,-4) scale(1.18)' }) + '</use>'
2935
+ + open('use', { href: '#dsh-xsj-pine-art', transform: 'translate(102,6) scale(0.88)' }) + '</use>'
2936
+ + open('use', { href: '#dsh-xsj-pine-art', transform: 'translate(126,0) scale(1.05)' }) + '</use>'
2937
+ + '</svg>'
2938
+ + '</div>'
1634
2939
  + '</div>'
1635
2940
  }
1636
2941
 
1637
2942
  /**
1638
- * The seaweed artwork, reproduced from the source system's paths.
2943
+ * The 佩安杰心 scene: misty hills behind, a zen enso circle with a seated
2944
+ * meditator, an incense burner with two rising smoke threads, drifting dust
2945
+ * motes, and the theme's own words.
1639
2946
  *
1640
- * Five blades from three gradients, each animating on its own phase so the bed
1641
- * sways rather than moving as one rigid shape, over three resting stones.
1642
- * @returns the seaweed markup.
1643
- */
1644
- /**
1645
- * One expanding ring on the water, positioned inline.
1646
- * @param left - horizontal position.
1647
- * @param bottom - vertical position.
1648
- * @param delay - animation delay, so the rings do not pulse in unison.
1649
- * @returns the ring markup.
2947
+ * Ported from `PeiAnJieXinAnimation.vue`. The artwork paths are verbatim; the
2948
+ * scene SVG's viewBox is cropped to the artwork (the source spans a full-height
2949
+ * sidebar and places the group far down inside it), which keeps the same drawing
2950
+ * coordinates while fitting the band.
2951
+ * @param dust - how many dust motes to seed.
2952
+ * @returns the scene markup.
1650
2953
  */
1651
- function rippleMarkup(left, bottom, delay) {
1652
- const ring = 'position:absolute;inset:0;border:1.6px solid rgba(232,139,176,.92);border-radius:50%;'
1653
- + `animation:dsh-amb-ring 3.2s ease-out infinite;animation-delay:${delay}`
1654
- return open('div', {
1655
- class: 'sta-ripple',
1656
- style: `position:absolute;z-index:6;width:.55em;height:.55em;left:${left};bottom:${bottom}`,
1657
- }) + `<span style="${ring}"></span><span style="${ring}"></span></div>`
1658
- }
2954
+ function jiexinAmbientScene(dust) {
2955
+ const count = Math.max(0, Math.min(20, dust ?? 4))
2956
+ let dustNodes = ''
2957
+ for (let n = 1; n <= count; n += 1) {
2958
+ const size = 2.5 + ((n * 5) % 4)
2959
+ dustNodes += open('div', {
2960
+ class: 'pj-dust',
2961
+ style: 'position:absolute;border-radius:50%;'
2962
+ + `left:${((n * 17) % 50) + 26}%;top:${((n * 13) % 30) + 28}%;`
2963
+ + `width:${size}px;height:${size}px;`
2964
+ + 'background:radial-gradient(circle,rgba(255,244,214,.95) 0%,rgba(255,244,214,0) 100%);'
2965
+ + 'box-shadow:0 0 6px rgba(255,236,190,.8);'
2966
+ + `animation:dsh-amb-pj-dust ${8 + ((n * 5) % 5)}s linear infinite;`
2967
+ + `animation-delay:${-(n * 2)}s`,
2968
+ }) + '</div>'
2969
+ }
1659
2970
 
1660
- /**
1661
- * One hovering dragonfly: an outer element on its flight path and an inner one
1662
- * bobbing on the wingbeat, so the two motions compose.
1663
- * @param suffix - gradient id suffix.
1664
- * @param className - the positioning class.
1665
- * @param position - inline position.
1666
- * @param flight - animation shorthand for the flight path.
1667
- * @param delay - animation delay.
1668
- * @returns the dragonfly markup.
1669
- */
1670
- function dragonflyBlock(suffix, className, position, flight, delay) {
1671
- return open('div', {
1672
- class: `sta-dfly ${className}`,
1673
- style: `position:absolute;z-index:8;will-change:transform;${position};`
1674
- + `animation:${flight};animation-delay:${delay}`,
1675
- }) + open('div', {
1676
- class: 'sta-bob',
1677
- style: `animation:dsh-amb-bob ${suffix === '2' ? '1.1s' : '.9s'} ease-in-out infinite;`
1678
- + `animation-delay:${suffix === '2' ? '-.4s' : '0s'}`,
1679
- }) + dragonflyMarkup(suffix) + '</div></div>'
2971
+ return open('div', { class: 'pj', style: 'position:absolute;inset:0;display:block' })
2972
+ + open('div', {
2973
+ class: 'pj-hills',
2974
+ style: 'position:absolute;left:0;right:0;top:8%;height:24%;z-index:2;opacity:.62',
2975
+ })
2976
+ + open('svg', { viewBox: '0 0 223 80', preserveAspectRatio: 'none', style: 'display:block;width:100%;height:100%' })
2977
+ + open('path', {
2978
+ d: 'M 0 60 Q 34 22 70 46 Q 108 14 146 44 Q 186 20 223 48 L 223 80 L 0 80 Z',
2979
+ fill: '#B49B78', opacity: '0.55',
2980
+ }) + '</path>'
2981
+ + open('path', {
2982
+ d: 'M 0 72 Q 40 42 82 60 Q 126 34 168 58 Q 196 44 223 60 L 223 80 L 0 80 Z',
2983
+ fill: '#A08A67', opacity: '0.6',
2984
+ }) + '</path>'
2985
+ + '</svg>'
2986
+ + '</div>'
2987
+ + open('div', {
2988
+ class: 'pj-zenscene',
2989
+ style: 'position:absolute;left:0;right:0;bottom:8%;height:62%;z-index:3',
2990
+ })
2991
+ + open('svg', {
2992
+ viewBox: '0 480 200 175',
2993
+ preserveAspectRatio: 'xMidYMax meet',
2994
+ style: 'display:block;width:100%;height:100%',
2995
+ })
2996
+ + open('defs', {})
2997
+ + open('radialGradient', { id: 'dsh-pjx-fig-grad', cx: '50%', cy: '32%', r: '80%' })
2998
+ + open('stop', { offset: '0%', 'stop-color': '#6B5238', 'stop-opacity': '0.9' }) + '</stop>'
2999
+ + open('stop', { offset: '60%', 'stop-color': '#57432C', 'stop-opacity': '0.85' }) + '</stop>'
3000
+ + open('stop', { offset: '100%', 'stop-color': '#4A3823', 'stop-opacity': '0.8' }) + '</stop>'
3001
+ + '</radialGradient>'
3002
+ + open('filter', { id: 'dsh-pjx-haze', x: '-30%', y: '-30%', width: '160%', height: '160%' })
3003
+ + open('feGaussianBlur', { stdDeviation: '1.8' }) + '</feGaussianBlur>'
3004
+ + '</filter>'
3005
+ + open('filter', { id: 'dsh-pjx-soft-glow', x: '-60%', y: '-60%', width: '220%', height: '220%' })
3006
+ + open('feGaussianBlur', { stdDeviation: '7' }) + '</feGaussianBlur>'
3007
+ + '</filter>'
3008
+ + open('g', { id: 'dsh-pjx-enso-art' })
3009
+ + open('circle', {
3010
+ cx: '80', cy: '64', r: '56', fill: 'none', stroke: '#A87E4C', 'stroke-width': '5',
3011
+ 'stroke-linecap': 'round', 'stroke-dasharray': '316 36',
3012
+ transform: 'rotate(-78 80 64)', opacity: '0.62',
3013
+ }) + '</circle>'
3014
+ + open('circle', {
3015
+ cx: '80', cy: '64', r: '56', fill: 'none', stroke: '#D9B98C', 'stroke-width': '1.8',
3016
+ 'stroke-linecap': 'round', 'stroke-dasharray': '255 92',
3017
+ transform: 'rotate(-60 80 64)', opacity: '0.5',
3018
+ }) + '</circle>'
3019
+ + '</g>'
3020
+ + open('g', { id: 'dsh-pjx-meditator-art' })
3021
+ + open('g', { filter: 'url(#dsh-pjx-soft-glow)', opacity: '0.35' })
3022
+ + open('path', {
3023
+ d: 'M 37 112 C 41 97, 58 90, 80 90 C 102 90, 119 97, 123 112 C 112 117.5, 96 119.5, 80 119.5 C 64 119.5, 48 117.5, 37 112 Z',
3024
+ fill: '#6B5238',
3025
+ }) + '</path>'
3026
+ + open('circle', { cx: '80', cy: '33', r: '13.5', fill: '#6B5238' }) + '</circle>'
3027
+ + open('path', {
3028
+ d: 'M 64 52 C 60 68, 62 80, 67 93 C 75 97.5, 85 97.5, 93 93 C 98 80, 100 68, 96 52 C 91 44.5, 69 44.5, 64 52 Z',
3029
+ fill: '#6B5238',
3030
+ }) + '</path>'
3031
+ + '</g>'
3032
+ + open('ellipse', {
3033
+ cx: '80', cy: '119', rx: '47', ry: '8.5', fill: '#8A6A46', opacity: '0.7',
3034
+ filter: 'url(#dsh-pjx-haze)',
3035
+ }) + '</ellipse>'
3036
+ + open('g', { fill: 'url(#dsh-pjx-fig-grad)', filter: 'url(#dsh-pjx-haze)' })
3037
+ + open('path', {
3038
+ d: 'M 37 112 C 41 97, 58 90, 80 90 C 102 90, 119 97, 123 112 C 112 117.5, 96 119.5, 80 119.5 C 64 119.5, 48 117.5, 37 112 Z',
3039
+ }) + '</path>'
3040
+ + open('path', {
3041
+ d: 'M 64 52 C 60 68, 62 80, 67 93 C 75 97.5, 85 97.5, 93 93 C 98 80, 100 68, 96 52 C 91 44.5, 69 44.5, 64 52 Z',
3042
+ }) + '</path>'
3043
+ + open('path', { d: 'M 64 54 C 55 62, 51 78, 53 93 C 56 99, 62 101, 67 99 C 62 85, 62 68, 67 56 Z' }) + '</path>'
3044
+ + open('path', { d: 'M 96 54 C 105 62, 109 78, 107 93 C 104 99, 98 101, 93 99 C 98 85, 98 68, 93 56 Z' }) + '</path>'
3045
+ + open('ellipse', { cx: '80', cy: '95', rx: '7', ry: '4.5' }) + '</ellipse>'
3046
+ + open('circle', { cx: '80', cy: '33', r: '13.5' }) + '</circle>'
3047
+ + '</g>'
3048
+ + '</g>'
3049
+ + '</defs>'
3050
+ + open('g', { transform: 'translate(18, 492) scale(1.17)' })
3051
+ + open('use', { href: '#dsh-pjx-enso-art' }) + '</use>'
3052
+ + open('use', { href: '#dsh-pjx-meditator-art' }) + '</use>'
3053
+ + '</g>'
3054
+ + open('g', {})
3055
+ + open('path', {
3056
+ d: 'M 20 631 C 20 623, 25 619, 32 619 C 39 619, 44 623, 44 631 Z',
3057
+ fill: '#6B5644', opacity: '0.8',
3058
+ }) + '</path>'
3059
+ + open('ellipse', { cx: '32', cy: '620', rx: '11.7', ry: '3', fill: '#55432F', opacity: '0.9' }) + '</ellipse>'
3060
+ + open('path', { d: 'M 32 620 L 32 604', stroke: '#8A6E52', 'stroke-width': '1.9', 'stroke-linecap': 'round', fill: 'none' }) + '</path>'
3061
+ + open('circle', { cx: '32', cy: '603.5', r: '1.9', fill: '#E8A05A' }) + '</circle>'
3062
+ + open('circle', { cx: '32', cy: '603.5', r: '4', fill: '#E8A05A', opacity: '0.3' }) + '</circle>'
3063
+ + open('path', {
3064
+ class: 'pj-smoke pj-smoke-1',
3065
+ d: 'M 32 601 C 28 593, 36 586, 32 577 C 28 568, 35 561, 31 551',
3066
+ fill: 'none', stroke: '#FFFFFF', 'stroke-width': '2.4', 'stroke-linecap': 'round', opacity: '0.85',
3067
+ style: 'stroke-dasharray:90;stroke-dashoffset:90;filter:blur(1.4px);'
3068
+ + 'animation:dsh-amb-pj-smoke 7s linear infinite',
3069
+ }) + '</path>'
3070
+ + open('path', {
3071
+ class: 'pj-smoke pj-smoke-2',
3072
+ d: 'M 32 601 C 36 592, 28 584, 33 575 C 37 566, 30 558, 34 549',
3073
+ fill: 'none', stroke: '#FFF6E8', 'stroke-width': '1.9', 'stroke-linecap': 'round', opacity: '0.6',
3074
+ style: 'stroke-dasharray:90;stroke-dashoffset:90;filter:blur(1.6px);'
3075
+ + 'animation:dsh-amb-pj-smoke 7s linear infinite;animation-delay:-3.5s',
3076
+ }) + '</path>'
3077
+ + '</g>'
3078
+ + '</svg>'
3079
+ + '</div>'
3080
+ + dustNodes
3081
+ + open('div', {
3082
+ class: 'pj-words',
3083
+ style: 'position:absolute;left:0;right:0;bottom:2%;text-align:center;z-index:7;'
3084
+ + "font-family:'KaiTi','STKaiti','SimSun',serif;font-size:1.05em;line-height:1;"
3085
+ + 'color:#6B5233;letter-spacing:.45em;text-shadow:0 1px 0 rgba(255,250,240,.6)',
3086
+ })
3087
+ + '自在<span style="color:#B08D5F;margin:0 2px"> · </span>安顿'
3088
+ + '</div>'
3089
+ + '</div>'
1680
3090
  }
1681
3091
 
1682
3092
  /**
1683
- * One seaweed blade. The sway is applied to the group so the blades move from their
1684
- * base, and each blade animates on its own phase.
1685
- * @param key - blade index.
1686
- * @param d - path data.
1687
- * @param gradient - gradient id.
1688
- * @param width - stroke width.
1689
- * @param opacity - blade opacity.
1690
- * @returns the blade markup.
3093
+ * One falling phoenix feather for 光彩凤晨, ported from
3094
+ * `GuangCaiFengChenAnimation.vue`: a full gradient vane over a gold rachis.
3095
+ * Each feather carries its own gradient id, the same rule the dragonflies follow.
3096
+ * @param key - unique id suffix for this feather's gradient.
3097
+ * @returns the feather SVG.
1691
3098
  */
1692
- function bladeMarkup(key, d, gradient, width, opacity) {
1693
- const durations = { 1: '6s', 2: '7.4s', 3: '5.2s', 4: '8.1s', 5: '6.6s' }
1694
- const delays = { 1: '0s', 2: '-1.6s', 3: '-2.8s', 4: '-.9s', 5: '-3.4s' }
1695
- return open('g', {
1696
- class: `dof-blade dof-blade-${key}`,
1697
- style: `transform-origin:50% 100%;animation:dsh-amb-sway ${durations[key]} ease-in-out infinite alternate;`
1698
- + `animation-delay:${delays[key]}`,
1699
- }) + open('path', {
1700
- d, fill: `url(#${gradient})`, stroke: `url(#${gradient})`, 'stroke-width': width, opacity,
1701
- }) + '</path></g>'
1702
- }
1703
-
1704
- function seaweedMarkup() {
1705
- const blade = bladeMarkup
1706
-
3099
+ function gcFeatherMarkup(key) {
3100
+ const gradient = `dsh-gc-feather-${key}`
1707
3101
  return open('svg', {
1708
- viewBox: '0 0 140 120',
1709
- preserveAspectRatio: 'none',
1710
- style: 'display:block;width:100%;height:100%',
3102
+ viewBox: '0 0 40 90',
3103
+ style: 'display:block;width:100%;height:auto;overflow:visible;'
3104
+ + 'filter:drop-shadow(0 0 5px rgba(255,214,0,.3))',
1711
3105
  })
1712
3106
  + open('defs', {})
1713
- + open('linearGradient', { id: 'dsh-dof-weed-a', x1: '0', y1: '1', x2: '0', y2: '0' })
1714
- + open('stop', { offset: '0', 'stop-color': '#1E6E93' }) + '</stop>'
1715
- + open('stop', { offset: '0.55', 'stop-color': '#3E93BC' }) + '</stop>'
1716
- + open('stop', { offset: '1', 'stop-color': '#A5DEF0', 'stop-opacity': '0.85' }) + '</stop>'
1717
- + '</linearGradient>'
1718
- + open('linearGradient', { id: 'dsh-dof-weed-b', x1: '0', y1: '1', x2: '0', y2: '0' })
1719
- + open('stop', { offset: '0', 'stop-color': '#2B7FA6' }) + '</stop>'
1720
- + open('stop', { offset: '0.6', 'stop-color': '#4FA3C6' }) + '</stop>'
1721
- + open('stop', { offset: '1', 'stop-color': '#B8E2F2', 'stop-opacity': '0.85' }) + '</stop>'
1722
- + '</linearGradient>'
1723
- + open('linearGradient', { id: 'dsh-dof-weed-c', x1: '0', y1: '1', x2: '0', y2: '0' })
1724
- + open('stop', { offset: '0', 'stop-color': '#4A78A8' }) + '</stop>'
1725
- + open('stop', { offset: '0.6', 'stop-color': '#7FA9CE' }) + '</stop>'
1726
- + open('stop', { offset: '1', 'stop-color': '#CBE2F4', 'stop-opacity': '0.8' }) + '</stop>'
3107
+ + open('linearGradient', { id: gradient, x1: '0', y1: '0', x2: '0', y2: '1' })
3108
+ + open('stop', { offset: '0%', 'stop-color': '#FFEB3B', 'stop-opacity': '0.9' }) + '</stop>'
3109
+ + open('stop', { offset: '60%', 'stop-color': '#FF6F00', 'stop-opacity': '0.8' }) + '</stop>'
3110
+ + open('stop', { offset: '100%', 'stop-color': '#D50000', 'stop-opacity': '0' }) + '</stop>'
1727
3111
  + '</linearGradient>'
1728
3112
  + '</defs>'
1729
- + blade('1', 'M 22 120 C 12 96, 26 74, 18 48 C 15 38, 18 28, 24 20 C 20 34, 24 44, 30 60 C 36 80, 30 100, 32 120 Z', 'dsh-dof-weed-a', '2.2', '0.95')
1730
- + blade('2', 'M 48 120 C 40 98, 54 80, 46 56 C 42 44, 48 34, 56 24 C 50 40, 56 52, 60 68 C 64 88, 56 104, 58 120 Z', 'dsh-dof-weed-b', '2', '0.92')
1731
- + blade('3', 'M 74 120 C 68 102, 78 88, 72 68 C 69 58, 72 48, 78 40 C 74 52, 78 62, 82 76 C 86 94, 80 108, 82 120 Z', 'dsh-dof-weed-c', '1.8', '0.88')
1732
- + blade('4', 'M 96 120 C 92 104, 102 90, 96 72 C 93 62, 96 54, 102 46 C 98 58, 102 68, 106 82 C 110 98, 102 110, 104 120 Z', 'dsh-dof-weed-a', '1.6', '0.85')
1733
- + blade('5', 'M 118 120 C 114 108, 122 96, 117 82 C 115 74, 117 68, 121 62 C 118 72, 121 80, 124 92 C 127 104, 121 112, 123 120 Z', 'dsh-dof-weed-b', '1.4', '0.8')
1734
- + open('ellipse', { cx: '30', cy: '119', rx: '14', ry: '4', fill: '#7FAFC6', opacity: '0.55' }) + '</ellipse>'
1735
- + open('ellipse', { cx: '72', cy: '120', rx: '10', ry: '3.4', fill: '#8FB9CE', opacity: '0.5' }) + '</ellipse>'
1736
- + open('ellipse', { cx: '108', cy: '119.5', rx: '12', ry: '3.6', fill: '#7FAFC6', opacity: '0.45' }) + '</ellipse>'
3113
+ + open('path', { d: 'M 20 4 C 27 22, 27 50, 20 86 C 13 50, 13 22, 20 4 Z', fill: `url(#${gradient})` }) + '</path>'
3114
+ + open('path', { d: 'M 20 8 L 20 82', stroke: '#FFE082', 'stroke-width': '1.2', 'stroke-linecap': 'round', opacity: '0.55', fill: 'none' }) + '</path>'
1737
3115
  + '</svg>'
1738
3116
  }
1739
3117
 
1740
3118
  /**
1741
- * One cartoon fish, swimming across the water.
1742
- *
1743
- * Ported from the source system's separate `FishAnimation.vue`. That component drives the
1744
- * fish from JavaScript through entering / bubbling / leaving phases on a 60-second cycle,
1745
- * but this skin is a static bundle with no component runtime to host a script — so the
1746
- * same artwork swims continuously instead, on a CSS `@keyframes` cross, with the tail and
1747
- * the whole body on separate animations so it reads as swimming rather than sliding.
3119
+ * The 光彩凤晨 scene: sweeping dawn rays, falling phoenix feathers, a
3120
+ * brush-stroke phoenix flying out and back (the flip happens while transparent,
3121
+ * exactly as the source notes), and twinkling dew.
1748
3122
  *
1749
- * The artwork is reproduced shape for shape: the body curve, tail, dorsal fin, pectoral
1750
- * fin, and the three-part eye. The colours are retuned into the theme's blue family —
1751
- * the source values (`#38bdf8` body, `#1d4ed8` fins) sat outside the dream palette and
1752
- * read as a sticker, the same treatment shan's mountains got when their source greens
1753
- * were darkened for this sidebar.
1754
- * @param suffix - unique id suffix, so several fish can coexist.
1755
- * @param size - rendered width in em.
1756
- * @param top - vertical position within the water, as a percentage.
1757
- * @param duration - seconds for one crossing.
1758
- * @param delay - animation delay, so the fish do not move in lockstep.
1759
- * @param flip - whether this fish swims right-to-left instead.
1760
- * @returns the fish markup.
3123
+ * Ported from `GuangCaiFengChenAnimation.vue`. The source's fill path references
3124
+ * a gradient that file never defines, so the rendered phoenix is strokes only —
3125
+ * this port keeps that rendered look with `fill="none"` instead of inventing a
3126
+ * fill the source never showed. Stroke widths are the source's effective values
3127
+ * (its `!important` class overrides baked in).
3128
+ * @param feathers - how many feathers to seed.
3129
+ * @param dew - how many dew drops to seed.
3130
+ * @returns the scene markup.
1761
3131
  */
1762
- function fishMarkup(suffix, size, top, duration, delay, flip) {
1763
- const anim = flip ? 'dsh-amb-swim-back' : 'dsh-amb-swim'
1764
- return open('div', {
1765
- class: `dof-fish${flip ? ' dof-fish-flip' : ''}`,
1766
- style: 'position:absolute;left:0;opacity:.94;'
1767
- + `top:${top}%;width:${size}em;`
1768
- + `animation:${anim} ${duration}s linear infinite;animation-delay:${delay}s`,
3132
+ function fengchenAmbientScene(feathers, dew) {
3133
+ const featherCount = Math.max(0, Math.min(16, feathers ?? 6))
3134
+ let featherNodes = ''
3135
+ for (let n = 1; n <= featherCount; n += 1) {
3136
+ featherNodes += open('div', {
3137
+ class: 'gc-feather',
3138
+ style: `position:absolute;top:-6em;left:${((n * 23) % 80) + 8}%;width:2.6em;opacity:0;`
3139
+ + `animation:dsh-amb-gc-feather ${15 + ((n * 3) % 8)}s ease-in-out infinite;`
3140
+ + `animation-delay:${-(n * 1.5)}s`,
3141
+ })
3142
+ + open('div', {
3143
+ style: `transform:scale(${(0.5 + ((n * 7) % 6) / 10).toFixed(1)})`,
3144
+ })
3145
+ + gcFeatherMarkup(String(n))
3146
+ + '</div></div>'
3147
+ }
3148
+
3149
+ const dewCount = Math.max(0, Math.min(30, dew ?? 10))
3150
+ let dewNodes = ''
3151
+ for (let n = 1; n <= dewCount; n += 1) {
3152
+ dewNodes += open('div', {
3153
+ class: 'gc-dew',
3154
+ style: 'position:absolute;width:3px;height:3px;border-radius:50%;opacity:.5;'
3155
+ + `left:${((n * 37) % 84) + 6}%;top:${((n * 29) % 62) + 6}%;`
3156
+ + 'background:radial-gradient(circle,#FFFFFF 0%,rgba(255,214,0,.8) 100%);'
3157
+ + 'box-shadow:0 0 8px rgba(255,214,0,.8);'
3158
+ + 'animation:dsh-amb-gc-dew 3s ease-in-out infinite alternate;'
3159
+ + `animation-delay:${-(n * 0.2)}s`,
3160
+ }) + '</div>'
3161
+ }
3162
+
3163
+ const ray = (key, rotation, delay) => open('div', {
3164
+ class: `gc-ray gc-ray-${key}`,
3165
+ style: `position:absolute;top:26%;left:-30%;width:160%;height:46%;`
3166
+ + `transform:rotate(${rotation}deg);opacity:.5;pointer-events:none`,
1769
3167
  })
1770
3168
  + open('div', {
1771
- class: 'dof-fish-bob',
1772
- style: `animation-duration:${(duration / 8).toFixed(2)}s`,
3169
+ style: 'position:absolute;inset:0;'
3170
+ + 'background:linear-gradient(45deg,transparent 0%,rgba(255,214,0,.12) 30%,rgba(255,111,0,.06) 60%,transparent 100%);'
3171
+ + `animation:dsh-amb-gc-ray 8s ease-in-out infinite alternate;animation-delay:${delay}`,
3172
+ }) + '</div>'
3173
+ + '</div>'
3174
+
3175
+ return open('div', { class: 'gc', style: 'position:absolute;inset:0;display:block' })
3176
+ + ray('1', '-18', '0s')
3177
+ + ray('2', '-9', '-0.5s')
3178
+ + ray('3', '0', '-1s')
3179
+ + ray('4', '9', '-1.5s')
3180
+ + ray('5', '18', '-2s')
3181
+ + dewNodes
3182
+ + featherNodes
3183
+ + open('div', {
3184
+ class: 'gc-phoenix',
3185
+ style: 'position:absolute;top:14%;left:0;width:9.5em;z-index:8;will-change:transform;'
3186
+ + 'animation:dsh-amb-gc-fly 28s linear infinite',
3187
+ })
3188
+ + open('div', {
3189
+ class: 'gc-bob',
3190
+ style: 'animation:dsh-amb-gc-bob 3.4s ease-in-out infinite',
1773
3191
  })
1774
3192
  + open('svg', {
1775
- viewBox: '0 0 50 18',
1776
- preserveAspectRatio: 'xMidYMid meet',
1777
- style: 'display:block;width:100%;height:auto;overflow:visible',
3193
+ viewBox: '0 0 800 600',
3194
+ style: 'display:block;width:100%;height:auto;overflow:visible;'
3195
+ + 'filter:drop-shadow(0 0 18px rgba(255,214,0,.55))',
1778
3196
  })
1779
- + open('g', { class: 'dof-fish-body' })
1780
- // Body — recoloured into the theme's own blue family. The source values
1781
- // (`#38bdf8` body, `#1d4ed8` fins) sat outside the dream palette and read
1782
- // as a sticker pasted on the water; the same treatment shan's mountains
1783
- // got when their source greens were darkened for this sidebar.
3197
+ + open('defs', {})
3198
+ + open('linearGradient', { id: 'dsh-gc-phoenix-stroke', x1: '0', y1: '0', x2: '1', y2: '0' })
3199
+ + open('stop', { offset: '0%', 'stop-color': '#FFEB3B' }) + '</stop>'
3200
+ + open('stop', { offset: '40%', 'stop-color': '#FF9800' }) + '</stop>'
3201
+ + open('stop', { offset: '100%', 'stop-color': '#D50000' }) + '</stop>'
3202
+ + '</linearGradient>'
3203
+ + open('filter', { id: 'dsh-gc-glow' })
3204
+ + open('feGaussianBlur', { stdDeviation: '2', result: 'coloredBlur' }) + '</feGaussianBlur>'
3205
+ + open('feMerge', {})
3206
+ + open('feMergeNode', { in: 'coloredBlur' }) + '</feMergeNode>'
3207
+ + open('feMergeNode', { in: 'SourceGraphic' }) + '</feMergeNode>'
3208
+ + '</feMerge>'
3209
+ + '</filter>'
3210
+ + '</defs>'
3211
+ + open('g', { filter: 'url(#dsh-gc-glow)', transform: 'translate(100, 100) scale(1.2)' })
1784
3212
  + open('path', {
1785
- d: 'M10 10 C20 5 35 5 45 10 C40 15 25 15 10 10 Z',
1786
- fill: '#5FA5D6', stroke: '#2B6E9E', 'stroke-width': '1',
3213
+ d: 'M 380 50 C 360 40 350 60 360 90 C 350 120 380 140 320 180 C 280 220 350 280 200 350 C 150 380 100 360 50 400 C 140 300 220 220 320 180 C 360 140 380 90 380 50 Z',
3214
+ fill: 'none',
1787
3215
  }) + '</path>'
1788
- // Tail
1789
3216
  + open('path', {
1790
- d: 'M10 10 L5 7 L5 13 Z',
1791
- fill: '#2B6E9E', stroke: '#2B6E9E', 'stroke-width': '1', class: 'dof-fish-tail',
3217
+ class: 'gc-head',
3218
+ d: 'M 380 50 Q 360 40 346 58 T 358 90 L 374 86',
3219
+ stroke: 'url(#dsh-gc-phoenix-stroke)', 'stroke-width': '3.5', fill: 'none', 'stroke-linecap': 'round',
1792
3220
  }) + '</path>'
1793
- // Dorsal fin
1794
3221
  + open('path', {
1795
- d: 'M20 7 L25 3 L30 7',
1796
- fill: '#2B6E9E', stroke: '#2B6E9E', 'stroke-width': '1',
3222
+ class: 'gc-crest',
3223
+ d: 'M 360 50 Q 340 20 310 30',
3224
+ stroke: 'url(#dsh-gc-phoenix-stroke)', 'stroke-width': '2.5', fill: 'none', 'stroke-linecap': 'round',
1797
3225
  }) + '</path>'
1798
- // Pectoral fin
1799
3226
  + open('path', {
1800
- d: 'M35 9 L40 12 L45 9',
1801
- fill: '#5FA5D6', stroke: '#2B6E9E', 'stroke-width': '1',
3227
+ class: 'gc-crest',
3228
+ d: 'M 365 55 Q 355 35 330 40',
3229
+ stroke: 'url(#dsh-gc-phoenix-stroke)', 'stroke-width': '2.5', fill: 'none', 'stroke-linecap': 'round',
1802
3230
  }) + '</path>'
1803
- // Eye: white, pupil, highlight
1804
- + open('circle', { cx: '40', cy: '8', r: '2', fill: '#FFFFFF' }) + '</circle>'
1805
- + open('circle', { cx: '41', cy: '8', r: '1', fill: '#16384F' }) + '</circle>'
1806
- + open('circle', { cx: '40.5', cy: '7.5', r: '0.5', fill: '#FFFFFF' }) + '</circle>'
3231
+ + open('path', {
3232
+ class: 'gc-neck',
3233
+ d: 'M 360 90 C 350 120, 380 140, 320 180',
3234
+ stroke: 'url(#dsh-gc-phoenix-stroke)', 'stroke-width': '7', fill: 'none', 'stroke-linecap': 'round',
3235
+ }) + '</path>'
3236
+ + open('path', {
3237
+ class: 'gc-wing gc-wing-main',
3238
+ d: 'M 340 110 C 260 70, 200 40, 140 90',
3239
+ stroke: 'url(#dsh-gc-phoenix-stroke)', 'stroke-width': '6', fill: 'none', 'stroke-linecap': 'round',
3240
+ style: 'transform-box:fill-box;transform-origin:30% 40%;animation:dsh-amb-gc-wing 3.4s ease-in-out infinite',
3241
+ }) + '</path>'
3242
+ + open('path', {
3243
+ d: 'M 300 100 C 250 80, 180 80, 140 120',
3244
+ stroke: 'url(#dsh-gc-phoenix-stroke)', 'stroke-width': '3', fill: 'none', 'stroke-linecap': 'round',
3245
+ }) + '</path>'
3246
+ + open('path', {
3247
+ d: 'M 280 110 C 240 100, 180 110, 150 140',
3248
+ stroke: 'url(#dsh-gc-phoenix-stroke)', 'stroke-width': '2', fill: 'none', 'stroke-linecap': 'round',
3249
+ }) + '</path>'
3250
+ + open('path', {
3251
+ class: 'gc-wing gc-wing-back',
3252
+ d: 'M 360 100 C 400 78, 452 66, 490 92',
3253
+ stroke: 'url(#dsh-gc-phoenix-stroke)', 'stroke-width': '4', fill: 'none', 'stroke-linecap': 'round',
3254
+ style: 'transform-box:fill-box;transform-origin:30% 40%;animation:dsh-amb-gc-wing 3.4s ease-in-out infinite',
3255
+ }) + '</path>'
3256
+ + open('path', {
3257
+ d: 'M 370 110 C 400 100, 430 95, 460 110',
3258
+ stroke: 'url(#dsh-gc-phoenix-stroke)', 'stroke-width': '2', fill: 'none', 'stroke-linecap': 'round',
3259
+ }) + '</path>'
3260
+ + open('path', {
3261
+ class: 'gc-tail-1',
3262
+ d: 'M 320 180 C 280 220, 350 280, 200 350 C 150 380, 100 360, 50 400',
3263
+ stroke: 'url(#dsh-gc-phoenix-stroke)', 'stroke-width': '5', fill: 'none', 'stroke-linecap': 'round',
3264
+ }) + '</path>'
3265
+ + open('path', {
3266
+ class: 'gc-tail-2',
3267
+ d: 'M 310 185 C 250 250, 200 280, 100 300',
3268
+ stroke: 'url(#dsh-gc-phoenix-stroke)', 'stroke-width': '4', fill: 'none', 'stroke-linecap': 'round',
3269
+ }) + '</path>'
3270
+ + open('path', {
3271
+ class: 'gc-tail-3',
3272
+ d: 'M 330 175 C 300 220, 250 350, 150 420',
3273
+ stroke: 'url(#dsh-gc-phoenix-stroke)', 'stroke-width': '3', fill: 'none', 'stroke-linecap': 'round',
3274
+ }) + '</path>'
3275
+ + open('circle', { cx: '365', cy: '70', r: '2.5', fill: '#FFE082' }) + '</circle>'
1807
3276
  + '</g>'
1808
3277
  + '</svg>'
1809
3278
  + '</div></div>'
3279
+ + '</div>'
1810
3280
  }
1811
3281
 
1812
3282
  /**
@@ -1889,7 +3359,17 @@ window.__ModuleLoader__.load({
1889
3359
  ? shanAmbientScene(options?.petals)
1890
3360
  : kind === 'dream'
1891
3361
  ? dreamAmbientScene(options?.bubbles, options?.motes, options?.fish)
1892
- : ''
3362
+ : kind === 'caiyun'
3363
+ ? caiyunAmbientScene(options?.stars)
3364
+ : kind === 'dongyun'
3365
+ ? dongyunAmbientScene(options?.snow)
3366
+ : kind === 'junyue'
3367
+ ? junyueAmbientScene(options?.stars)
3368
+ : kind === 'jiexin'
3369
+ ? jiexinAmbientScene(options?.dust)
3370
+ : kind === 'fengchen'
3371
+ ? fengchenAmbientScene(options?.feathers, options?.dew)
3372
+ : ''
1893
3373
  // The builder is fed only attributes and hex values, but this is the one place
1894
3374
  // markup from outside this file could ever arrive, so it refuses anyway.
1895
3375
  if (/<script|\son[a-z]+\s*=/i.test(markup)) {
@@ -2648,7 +4128,9 @@ window.__ModuleLoader__.load({
2648
4128
  // the two need different fixes.
2649
4129
  const scene = seat.firstElementChild
2650
4130
  const sceneRect = scene === null ? null : scene.getBoundingClientRect()
2651
- const art = seat.querySelector('.sta-mountains, .dof-seaweed')
4131
+ const art = seat.querySelector(
4132
+ '.sta-mountains, .dof-seaweed, .ym-sea-front, .jp-river, .xs-ridges, .pj-zenscene, .gc-phoenix',
4133
+ )
2652
4134
  const artRect = art === null ? null : art.getBoundingClientRect()
2653
4135
  return {
2654
4136
  found: true,
@@ -3043,16 +4525,6 @@ window.__ModuleLoader__.load({
3043
4525
  jsx('span', { className: 'tg-hint', children: `v${BUNDLED_VERSION}` }),
3044
4526
  ],
3045
4527
  }),
3046
- debugEnabled() ? jsx('div', { className: 'tg-debug', children: themeDiagnostics(selected) }) : null,
3047
- // Routine reports wait for the debug switch; warnings and errors print
3048
- // regardless — when the active skin's decorations are missing, the reason
3049
- // has to reach the person looking at the panel.
3050
- showScenery
3051
- ? jsx('div', {
3052
- className: `tg-debug${ambientWarning(selected) === null ? '' : ' tg-warn'}`,
3053
- children: scenery,
3054
- })
3055
- : null,
3056
4528
  jsx('div', {
3057
4529
  className: 'tg-grid',
3058
4530
  children: ids.map((id) => jsx(ThemeCard, {
@@ -3066,6 +4538,31 @@ window.__ModuleLoader__.load({
3066
4538
  t,
3067
4539
  }, id)),
3068
4540
  }),
4541
+ // ── the diagnostics go BELOW the cards, set apart, behind a caption ──────
4542
+ //
4543
+ // They used to sit between the header and the picker, where a wall of monospace
4544
+ // text buried the thing the panel exists for. Below the cards they read as a
4545
+ // reference block — and the caption is what keeps them from being mistaken for a
4546
+ // malfunction, which is exactly how a user read them. They stay CONDITIONAL:
4547
+ // detail waits for the debug switch, while a warning or a failure always prints.
4548
+ (debugEnabled() || showScenery)
4549
+ ? jsxs('div', {
4550
+ className: 'tg-diag',
4551
+ children: [
4552
+ jsx('div', { className: 'tg-diag-note', children: t('diagNote') }),
4553
+ debugEnabled() ? jsx('div', { className: 'tg-debug', children: themeDiagnostics(selected) }) : null,
4554
+ // Routine reports wait for the debug switch; warnings and errors print
4555
+ // regardless — when the active skin's decorations are missing, the reason
4556
+ // has to reach the person looking at the panel.
4557
+ showScenery
4558
+ ? jsx('div', {
4559
+ className: `tg-debug${ambientWarning(selected) === null ? '' : ' tg-warn'}`,
4560
+ children: scenery,
4561
+ })
4562
+ : null,
4563
+ ],
4564
+ })
4565
+ : null,
3069
4566
  ],
3070
4567
  })
3071
4568
  }
@@ -4331,8 +5828,12 @@ window.__ModuleLoader__.load({
4331
5828
  * `overrideTokens(source, tokens)` folds a `{ light, dark }`-shaped layer
4332
5829
  * over the ACTIVE theme, so the lightening follows whichever skin and
4333
5830
  * palette is in play and disappears cleanly when the transcript empties.
4334
- * The source string is the layer's identity: re-calling it with the same
4335
- * source replaces the layer, which is what makes this idempotent.
5831
+ *
5832
+ * The source string is the layer's identity, so re-stacking the same source
5833
+ * REPLACES the layer instead of adding a second one. Replacement alone is not
5834
+ * idempotence: the call still emits `theme/change`, so it is the skip below
5835
+ * (`wanted === stackedReading`) that keeps an unchanged document from churning
5836
+ * the layer on every mutation batch.
4336
5837
  */
4337
5838
  function syncReadingLayer() {
4338
5839
  if (typeof document === 'undefined') return