vanspod 0.1.2 → 0.1.3

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.
@@ -2,51 +2,51 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.connectionMascotScript = exports.connectionMascotStyles = exports.connectionMascot = void 0;
4
4
  /** Keep the original brand artwork; animate its layers in the browser. */
5
- exports.connectionMascot = `<div class="connection-mascot">
6
- <div class="mascot-traveler">
7
- <div class="mascot-arrival">
8
- <button type="button" class="mascot-play" aria-label="和法鬥超人打招呼" title="和法鬥超人打招呼">
9
- <span class="mascot-idle">
10
- <span class="mascot-facing">
11
- <svg class="mascot-art" viewBox="0 0 1024 1024" aria-hidden="true" focusable="false">
12
- <defs>
13
- <clipPath id="mascot-arm-clip"><path d="M85 320H220L235 400L222 462L241 508L254 530L207 589L149 522L104 451L85 410Z"/></clipPath>
14
- <mask id="mascot-arm-mask"><rect width="1024" height="1024" fill="white"/><path d="M85 320H220L235 400L222 462L241 508L254 530L207 589L149 522L104 451L85 410Z" fill="black"/></mask>
15
- <clipPath id="mascot-left-foot-clip"><rect x="235" y="915" width="175" height="100"/></clipPath>
16
- <clipPath id="mascot-right-foot-clip"><rect x="490" y="927" width="185" height="88"/></clipPath>
17
- <clipPath id="mascot-logo-clip"><rect x="283" y="612" width="315" height="105"/></clipPath>
18
- <filter id="mascot-logo-alpha" x="0" y="0" width="100%" height="100%" color-interpolation-filters="sRGB">
19
- <feColorMatrix in="SourceGraphic" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 32 -32 0 0.6" result="lettering"/>
20
- <feColorMatrix in="SourceGraphic" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 -16 0 0 3.5" result="outline"/>
21
- <feComposite in="lettering" in2="outline" operator="arithmetic" k2="1" k3="1" result="ink"/>
22
- <feComposite in="SourceGraphic" in2="ink" operator="in"/>
23
- </filter>
24
- <filter id="mascot-logo-erase" x="0" y="0" width="100%" height="100%" color-interpolation-filters="sRGB">
25
- <feColorMatrix in="SourceGraphic" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 32 -32 0 0.6" result="lettering"/>
26
- <feColorMatrix in="SourceGraphic" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -16 0 0 3.5" result="outline"/>
27
- <feComposite in="lettering" in2="outline" operator="arithmetic" k2="1" k3="1"/>
28
- </filter>
29
- <linearGradient id="mascot-shirt-fill"><stop stop-color="#3d6878"/><stop offset="0.5" stop-color="#3a677a"/><stop offset="1" stop-color="#356270"/></linearGradient>
30
- <mask id="mascot-detail-mask"><rect width="1024" height="1024" fill="white"/><rect x="235" y="915" width="175" height="100" fill="black"/><rect x="490" y="927" width="185" height="88" fill="black"/><image href="/fadou-superhero.png" width="1024" height="1024" clip-path="url(#mascot-logo-clip)" filter="url(#mascot-logo-erase)"/></mask>
31
- <clipPath id="mascot-body-clip"><path d="M0 0H1024V490H700L670 534L739 601L768 638L765 681L740 718L702 749L691 778L668 803L646 813V1024H0Z"/></clipPath>
32
- <clipPath id="mascot-cape-clip"><path d="M688 490L1024 490V850H640V789L670 760L704 730L744 679L744 620L668 545Z"/></clipPath>
33
- <clipPath id="mascot-eyes-clip"><ellipse cx="334" cy="359" rx="52" ry="43"/><ellipse cx="535" cy="369" rx="53" ry="43"/></clipPath>
34
- </defs>
35
- <g class="mascot-cape"><image href="/fadou-superhero.png" width="1024" height="1024" clip-path="url(#mascot-cape-clip)"/></g>
36
- <rect x="283" y="612" width="315" height="105" fill="url(#mascot-shirt-fill)"/>
37
- <g mask="url(#mascot-detail-mask)"><image href="/fadou-superhero.png" width="1024" height="1024" clip-path="url(#mascot-body-clip)" mask="url(#mascot-arm-mask)"/></g>
38
- <g class="mascot-foot mascot-left-foot"><image href="/fadou-superhero.png" width="1024" height="1024" clip-path="url(#mascot-left-foot-clip)"/></g>
39
- <g class="mascot-foot mascot-right-foot"><image href="/fadou-superhero.png" width="1024" height="1024" clip-path="url(#mascot-right-foot-clip)"/></g>
40
- <g class="mascot-logo"><image href="/fadou-superhero.png" width="1024" height="1024" clip-path="url(#mascot-logo-clip)" filter="url(#mascot-logo-alpha)"/></g>
41
- <g class="mascot-arm"><image href="/fadou-superhero.png" width="1024" height="1024" clip-path="url(#mascot-arm-clip)"/></g>
42
- <g class="mascot-blink" clip-path="url(#mascot-eyes-clip)"><image href="/fadou-superhero-blink.png" x="11" y="4" width="1024" height="1024"/></g>
43
- </svg>
44
- </span>
45
- </span>
46
- </button>
47
- </div>
48
- </div>
49
- <button type="button" class="mascot-pause" aria-label="暫停法鬥超人動畫" aria-pressed="false" title="暫停動畫"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d="M8 5v14M16 5v14" fill="none" stroke="currentColor" stroke-width="2"/></svg></button>
5
+ exports.connectionMascot = `<div class="connection-mascot">
6
+ <div class="mascot-traveler">
7
+ <div class="mascot-arrival">
8
+ <button type="button" class="mascot-play" aria-label="和法鬥超人打招呼" title="和法鬥超人打招呼">
9
+ <span class="mascot-idle">
10
+ <span class="mascot-facing">
11
+ <svg class="mascot-art" viewBox="0 0 1024 1024" aria-hidden="true" focusable="false">
12
+ <defs>
13
+ <clipPath id="mascot-arm-clip"><path d="M85 320H220L235 400L222 462L241 508L254 530L207 589L149 522L104 451L85 410Z"/></clipPath>
14
+ <mask id="mascot-arm-mask"><rect width="1024" height="1024" fill="white"/><path d="M85 320H220L235 400L222 462L241 508L254 530L207 589L149 522L104 451L85 410Z" fill="black"/></mask>
15
+ <clipPath id="mascot-left-foot-clip"><rect x="235" y="915" width="175" height="100"/></clipPath>
16
+ <clipPath id="mascot-right-foot-clip"><rect x="490" y="927" width="185" height="88"/></clipPath>
17
+ <clipPath id="mascot-logo-clip"><rect x="283" y="612" width="315" height="105"/></clipPath>
18
+ <filter id="mascot-logo-alpha" x="0" y="0" width="100%" height="100%" color-interpolation-filters="sRGB">
19
+ <feColorMatrix in="SourceGraphic" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 32 -32 0 0.6" result="lettering"/>
20
+ <feColorMatrix in="SourceGraphic" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 -16 0 0 3.5" result="outline"/>
21
+ <feComposite in="lettering" in2="outline" operator="arithmetic" k2="1" k3="1" result="ink"/>
22
+ <feComposite in="SourceGraphic" in2="ink" operator="in"/>
23
+ </filter>
24
+ <filter id="mascot-logo-erase" x="0" y="0" width="100%" height="100%" color-interpolation-filters="sRGB">
25
+ <feColorMatrix in="SourceGraphic" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 32 -32 0 0.6" result="lettering"/>
26
+ <feColorMatrix in="SourceGraphic" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -16 0 0 3.5" result="outline"/>
27
+ <feComposite in="lettering" in2="outline" operator="arithmetic" k2="1" k3="1"/>
28
+ </filter>
29
+ <linearGradient id="mascot-shirt-fill"><stop stop-color="#3d6878"/><stop offset="0.5" stop-color="#3a677a"/><stop offset="1" stop-color="#356270"/></linearGradient>
30
+ <mask id="mascot-detail-mask"><rect width="1024" height="1024" fill="white"/><rect x="235" y="915" width="175" height="100" fill="black"/><rect x="490" y="927" width="185" height="88" fill="black"/><image href="/fadou-superhero.png" width="1024" height="1024" clip-path="url(#mascot-logo-clip)" filter="url(#mascot-logo-erase)"/></mask>
31
+ <clipPath id="mascot-body-clip"><path d="M0 0H1024V490H700L670 534L739 601L768 638L765 681L740 718L702 749L691 778L668 803L646 813V1024H0Z"/></clipPath>
32
+ <clipPath id="mascot-cape-clip"><path d="M688 490L1024 490V850H640V789L670 760L704 730L744 679L744 620L668 545Z"/></clipPath>
33
+ <clipPath id="mascot-eyes-clip"><ellipse cx="334" cy="359" rx="52" ry="43"/><ellipse cx="535" cy="369" rx="53" ry="43"/></clipPath>
34
+ </defs>
35
+ <g class="mascot-cape"><image href="/fadou-superhero.png" width="1024" height="1024" clip-path="url(#mascot-cape-clip)"/></g>
36
+ <rect x="283" y="612" width="315" height="105" fill="url(#mascot-shirt-fill)"/>
37
+ <g mask="url(#mascot-detail-mask)"><image href="/fadou-superhero.png" width="1024" height="1024" clip-path="url(#mascot-body-clip)" mask="url(#mascot-arm-mask)"/></g>
38
+ <g class="mascot-foot mascot-left-foot"><image href="/fadou-superhero.png" width="1024" height="1024" clip-path="url(#mascot-left-foot-clip)"/></g>
39
+ <g class="mascot-foot mascot-right-foot"><image href="/fadou-superhero.png" width="1024" height="1024" clip-path="url(#mascot-right-foot-clip)"/></g>
40
+ <g class="mascot-logo"><image href="/fadou-superhero.png" width="1024" height="1024" clip-path="url(#mascot-logo-clip)" filter="url(#mascot-logo-alpha)"/></g>
41
+ <g class="mascot-arm"><image href="/fadou-superhero.png" width="1024" height="1024" clip-path="url(#mascot-arm-clip)"/></g>
42
+ <g class="mascot-blink" clip-path="url(#mascot-eyes-clip)"><image href="/fadou-superhero-blink.png" x="11" y="4" width="1024" height="1024"/></g>
43
+ </svg>
44
+ </span>
45
+ </span>
46
+ </button>
47
+ </div>
48
+ </div>
49
+ <button type="button" class="mascot-pause" aria-label="暫停法鬥超人動畫" aria-pressed="false" title="暫停動畫"><svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d="M8 5v14M16 5v14" fill="none" stroke="currentColor" stroke-width="2"/></svg></button>
50
50
  </div>`;
51
51
  // One lap: centre → left (6s), rest (6s), right (12s), rest (6s), centre (6s).
52
52
  // Both feet settle at each edge; no stepping in place during the rests.
@@ -61,175 +61,175 @@ function footstepKeyframes(name, direction) {
61
61
  }
62
62
  return `@keyframes ${name} { ${frames.join("\n")} }`;
63
63
  }
64
- exports.connectionMascotStyles = `
65
- .settings-card[data-setting="連線"]:has(.connection-mascot) { row-gap: 0; }
66
- .settings-card[data-setting="連線"]:has(.connection-mascot) .settings-content { padding-top: 16px; }
67
- .settings-card[data-setting="連線"]:has(.connection-mascot) .connection-feedback { order: -1; }
68
- .connection-mascot { position: relative; flex: 1 0 144px; width: 100%; min-height: 144px; overflow: hidden; container-type: inline-size; }
69
- /* Compensate for the PNG's transparent space below the soles. */
70
- .mascot-traveler { position: absolute; bottom: -4px; left: 8px; width: 136px; max-width: calc(100% - 16px); animation: mascot-walk 36s linear 780ms infinite both; }
71
- .mascot-facing { display: block; animation: mascot-turn 36s linear 780ms infinite; }
72
- .mascot-logo { transform-origin: 440.5px 664.5px; animation: mascot-turn 36s linear 780ms infinite; }
73
- .mascot-left-foot { transform-origin: 322px 915px; animation: mascot-left-step 36s linear 780ms infinite; }
74
- .mascot-right-foot { transform-origin: 580px 927px; animation: mascot-right-step 36s linear 780ms infinite; }
75
- .mascot-waiting { flex-basis: 164px; min-height: 164px; padding: 12px 148px 32px 0; }
76
- .mascot-waiting .mascot-traveler { left: auto; right: 8px; transform: none; }
77
- .mascot-waiting .mascot-traveler, .mascot-waiting .mascot-facing, .mascot-waiting .mascot-logo, .mascot-waiting .mascot-foot, .mascot-waiting .mascot-arrival { animation: none; }
78
- .mascot-speech { position: relative; padding: 10px 12px; border: 1px solid var(--line); border-radius: 12px; background: var(--input); font-size: 12px; line-height: 1.6; overflow-wrap: anywhere; }
79
- .mascot-speech:has(> .connection-feedback:empty) { display: none; }
80
- .mascot-speech::after { content: ''; position: absolute; right: -7px; top: 28px; width: 12px; height: 12px; background: var(--input); border-right: 1px solid var(--line); border-top: 1px solid var(--line); transform: rotate(45deg); }
81
- .mascot-quote { color: var(--text); }
82
- .mascot-speech .connection-error, .mascot-speech .connection-message { padding: 0; margin-top: 6px; border-radius: 0; background: transparent; color: var(--muted); font-size: 11px; }
83
- .mascot-speech .connection-error svg, .mascot-speech .connection-message svg { display: none; }
84
- .mascot-speaking .connection-message { margin-top: 0; color: var(--text); font-size: 12px; }
85
- .mascot-speaking { flex-basis: 144px; min-height: 144px; padding-bottom: 0; }
86
- main button.mascot-speech-close { margin-top: 6px; padding: 4px 0; min-height: 24px; background: transparent; color: var(--muted); border: 0; border-radius: 4px; font-size: 11px; }
87
- main button.mascot-speech-close:hover { color: var(--text); }
88
- main button.mascot-pause { position: absolute; right: 0; bottom: 0; display: grid; place-items: center; min-height: 24px; width: 24px; padding: 0; border: 0; border-radius: 6px; background: transparent; color: var(--muted); opacity: 0.65; }
89
- main button.mascot-pause:hover, main button.mascot-pause:focus-visible { opacity: 1; background: var(--input); }
90
- .connection-mascot:hover .mascot-traveler, .connection-mascot:hover .mascot-facing, .connection-mascot:hover .mascot-logo, .connection-mascot:hover .mascot-foot,
91
- .connection-mascot:focus-within .mascot-traveler, .connection-mascot:focus-within .mascot-facing, .connection-mascot:focus-within .mascot-logo, .connection-mascot:focus-within .mascot-foot,
92
- .connection-mascot[data-paused] * { animation-play-state: paused; }
93
- .mascot-arrival { animation: mascot-arrive 780ms cubic-bezier(0.22, 1, 0.36, 1) both; transform-origin: center bottom; }
94
- main button.mascot-play { display: block; width: 136px; max-width: 100%; min-height: 0; padding: 0; border: 0; border-radius: 16px; background: transparent; color: inherit; touch-action: manipulation; }
95
- .mascot-play:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
96
- .mascot-play:active { transform: scale(0.97); }
97
- .mascot-idle { display: block; user-select: none; transform-origin: 50% 97%; animation: mascot-breathe 3200ms ease-in-out 780ms infinite; }
98
- .mascot-art { display: block; width: 100%; height: auto; overflow: visible; }
99
- .mascot-cape { transform-origin: 680px 530px; animation: mascot-cape 2400ms ease-in-out infinite; }
100
- .mascot-arm { transform-origin: 235px 540px; }
101
- .mascot-blink { opacity: 0; animation: mascot-blink 7400ms steps(1, end) infinite; }
102
- @keyframes mascot-arrive {
103
- 0% { opacity: 0; transform: translateY(16px) scale(0.9); }
104
- 35% { opacity: 1; transform: translateY(-7px) scale(1.02); }
105
- 60% { transform: translateY(2px) scale(0.99); }
106
- 80% { transform: translateY(-2px) scale(1); }
107
- 100% { opacity: 1; transform: translateY(0) scale(1); }
108
- }
109
- @keyframes mascot-breathe {
110
- 0%, 100% { transform: translateY(0) scale(1); }
111
- 50% { transform: scale(1.012, 1.018); }
112
- }
113
- @keyframes mascot-cape {
114
- 0%, 100% { transform: rotate(0deg) scale(1); }
115
- 50% { transform: rotate(-1.5deg) scale(1.012, 0.99); }
116
- }
117
- @keyframes mascot-blink {
118
- 0%, 42%, 85%, 90%, 100% { opacity: 0; }
119
- 40%, 83%, 88% { opacity: 1; }
120
- }
121
- @keyframes mascot-walk {
122
- 0%, 100% { transform: translateX(max(0px, calc((100cqw - 152px) / 2))); }
123
- 16.666667%, 33.333333% { transform: translateX(0); }
124
- 66.666667%, 83.333333% { transform: translateX(max(0px, calc(100cqw - 152px))); }
125
- }
126
- @keyframes mascot-turn {
127
- 0%, 16.666667% { transform: scaleX(1); }
128
- 18%, 66.666667% { transform: scaleX(-1); }
129
- 68%, 100% { transform: scaleX(1); }
130
- }
131
- ${footstepKeyframes("mascot-left-step", 1)}
132
- ${footstepKeyframes("mascot-right-step", -1)}
133
- @media (prefers-reduced-motion: reduce) {
134
- .mascot-arrival, .mascot-idle, .mascot-cape, .mascot-blink, .mascot-traveler, .mascot-facing, .mascot-logo, .mascot-foot { animation: none; }
135
- .mascot-traveler { left: 50%; transform: translateX(-50%); }
136
- .mascot-waiting .mascot-traveler { left: auto; transform: none; }
137
- main button.mascot-pause { display: none; }
138
- .mascot-play:active { transform: none; }
139
- }
140
- @media (prefers-contrast: more) { .mascot-play:focus-visible { outline-width: 3px; } }
64
+ exports.connectionMascotStyles = `
65
+ .settings-card[data-setting="連線"]:has(.connection-mascot) { row-gap: 0; }
66
+ .settings-card[data-setting="連線"]:has(.connection-mascot) .settings-content { padding-top: 16px; }
67
+ .settings-card[data-setting="連線"]:has(.connection-mascot) .connection-feedback { order: -1; }
68
+ .connection-mascot { position: relative; flex: 1 0 144px; width: 100%; min-height: 144px; overflow: hidden; container-type: inline-size; }
69
+ /* Compensate for the PNG's transparent space below the soles. */
70
+ .mascot-traveler { position: absolute; bottom: -4px; left: 8px; width: 136px; max-width: calc(100% - 16px); animation: mascot-walk 36s linear 780ms infinite both; }
71
+ .mascot-facing { display: block; animation: mascot-turn 36s linear 780ms infinite; }
72
+ .mascot-logo { transform-origin: 440.5px 664.5px; animation: mascot-turn 36s linear 780ms infinite; }
73
+ .mascot-left-foot { transform-origin: 322px 915px; animation: mascot-left-step 36s linear 780ms infinite; }
74
+ .mascot-right-foot { transform-origin: 580px 927px; animation: mascot-right-step 36s linear 780ms infinite; }
75
+ .mascot-waiting { flex-basis: 164px; min-height: 164px; padding: 12px 148px 32px 0; }
76
+ .mascot-waiting .mascot-traveler { left: auto; right: 8px; transform: none; }
77
+ .mascot-waiting .mascot-traveler, .mascot-waiting .mascot-facing, .mascot-waiting .mascot-logo, .mascot-waiting .mascot-foot, .mascot-waiting .mascot-arrival { animation: none; }
78
+ .mascot-speech { position: relative; padding: 10px 12px; border: 1px solid var(--line); border-radius: 12px; background: var(--input); font-size: 12px; line-height: 1.6; overflow-wrap: anywhere; }
79
+ .mascot-speech:has(> .connection-feedback:empty) { display: none; }
80
+ .mascot-speech::after { content: ''; position: absolute; right: -7px; top: 28px; width: 12px; height: 12px; background: var(--input); border-right: 1px solid var(--line); border-top: 1px solid var(--line); transform: rotate(45deg); }
81
+ .mascot-quote { color: var(--text); }
82
+ .mascot-speech .connection-error, .mascot-speech .connection-message { padding: 0; margin-top: 6px; border-radius: 0; background: transparent; color: var(--muted); font-size: 11px; }
83
+ .mascot-speech .connection-error svg, .mascot-speech .connection-message svg { display: none; }
84
+ .mascot-speaking .connection-message { margin-top: 0; color: var(--text); font-size: 12px; }
85
+ .mascot-speaking { flex-basis: 144px; min-height: 144px; padding-bottom: 0; }
86
+ main button.mascot-speech-close { margin-top: 6px; padding: 4px 0; min-height: 24px; background: transparent; color: var(--muted); border: 0; border-radius: 4px; font-size: 11px; }
87
+ main button.mascot-speech-close:hover { color: var(--text); }
88
+ main button.mascot-pause { position: absolute; right: 0; bottom: 0; display: grid; place-items: center; min-height: 24px; width: 24px; padding: 0; border: 0; border-radius: 6px; background: transparent; color: var(--muted); opacity: 0.65; }
89
+ main button.mascot-pause:hover, main button.mascot-pause:focus-visible { opacity: 1; background: var(--input); }
90
+ .connection-mascot:hover .mascot-traveler, .connection-mascot:hover .mascot-facing, .connection-mascot:hover .mascot-logo, .connection-mascot:hover .mascot-foot,
91
+ .connection-mascot:focus-within .mascot-traveler, .connection-mascot:focus-within .mascot-facing, .connection-mascot:focus-within .mascot-logo, .connection-mascot:focus-within .mascot-foot,
92
+ .connection-mascot[data-paused] * { animation-play-state: paused; }
93
+ .mascot-arrival { animation: mascot-arrive 780ms cubic-bezier(0.22, 1, 0.36, 1) both; transform-origin: center bottom; }
94
+ main button.mascot-play { display: block; width: 136px; max-width: 100%; min-height: 0; padding: 0; border: 0; border-radius: 16px; background: transparent; color: inherit; touch-action: manipulation; }
95
+ .mascot-play:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
96
+ .mascot-play:active { transform: scale(0.97); }
97
+ .mascot-idle { display: block; user-select: none; transform-origin: 50% 97%; animation: mascot-breathe 3200ms ease-in-out 780ms infinite; }
98
+ .mascot-art { display: block; width: 100%; height: auto; overflow: visible; }
99
+ .mascot-cape { transform-origin: 680px 530px; animation: mascot-cape 2400ms ease-in-out infinite; }
100
+ .mascot-arm { transform-origin: 235px 540px; }
101
+ .mascot-blink { opacity: 0; animation: mascot-blink 7400ms steps(1, end) infinite; }
102
+ @keyframes mascot-arrive {
103
+ 0% { opacity: 0; transform: translateY(16px) scale(0.9); }
104
+ 35% { opacity: 1; transform: translateY(-7px) scale(1.02); }
105
+ 60% { transform: translateY(2px) scale(0.99); }
106
+ 80% { transform: translateY(-2px) scale(1); }
107
+ 100% { opacity: 1; transform: translateY(0) scale(1); }
108
+ }
109
+ @keyframes mascot-breathe {
110
+ 0%, 100% { transform: translateY(0) scale(1); }
111
+ 50% { transform: scale(1.012, 1.018); }
112
+ }
113
+ @keyframes mascot-cape {
114
+ 0%, 100% { transform: rotate(0deg) scale(1); }
115
+ 50% { transform: rotate(-1.5deg) scale(1.012, 0.99); }
116
+ }
117
+ @keyframes mascot-blink {
118
+ 0%, 42%, 85%, 90%, 100% { opacity: 0; }
119
+ 40%, 83%, 88% { opacity: 1; }
120
+ }
121
+ @keyframes mascot-walk {
122
+ 0%, 100% { transform: translateX(max(0px, calc((100cqw - 152px) / 2))); }
123
+ 16.666667%, 33.333333% { transform: translateX(0); }
124
+ 66.666667%, 83.333333% { transform: translateX(max(0px, calc(100cqw - 152px))); }
125
+ }
126
+ @keyframes mascot-turn {
127
+ 0%, 16.666667% { transform: scaleX(1); }
128
+ 18%, 66.666667% { transform: scaleX(-1); }
129
+ 68%, 100% { transform: scaleX(1); }
130
+ }
131
+ ${footstepKeyframes("mascot-left-step", 1)}
132
+ ${footstepKeyframes("mascot-right-step", -1)}
133
+ @media (prefers-reduced-motion: reduce) {
134
+ .mascot-arrival, .mascot-idle, .mascot-cape, .mascot-blink, .mascot-traveler, .mascot-facing, .mascot-logo, .mascot-foot { animation: none; }
135
+ .mascot-traveler { left: 50%; transform: translateX(-50%); }
136
+ .mascot-waiting .mascot-traveler { left: auto; transform: none; }
137
+ main button.mascot-pause { display: none; }
138
+ .mascot-play:active { transform: none; }
139
+ }
140
+ @media (prefers-contrast: more) { .mascot-play:focus-visible { outline-width: 3px; } }
141
141
  `;
142
- exports.connectionMascotScript = `
143
- var mascotSpeechDismissed = false;
144
- function updateCopyFeedback(updated) {
145
- var card = document.querySelector('[data-setting="連線"]');
146
- var nextCard = updated.querySelector('[data-setting="連線"]');
147
- var stage = card?.querySelector('.connection-mascot') || document.querySelector('.classroom-update .connection-mascot');
148
- var nextStage = nextCard?.querySelector('.connection-mascot');
149
- if (!stage || !nextStage || !nextCard.querySelector('.connection-copy')) return false;
150
- var speech = nextStage.querySelector('.mascot-speech');
151
- var feedback = nextCard.querySelector('.settings-content > .connection-feedback');
152
- if (!speech && !feedback) return false;
153
- card.querySelector('.settings-content > .connection-feedback')?.remove();
154
- stage.querySelector('.mascot-speech')?.remove();
155
- if (nextStage.classList.contains('mascot-speaking')) {
156
- if (!speech) return false;
157
- mascotSpeechDismissed = false;
158
- stage.classList.add('mascot-waiting', 'mascot-speaking');
159
- stage.prepend(speech);
160
- } else {
161
- if (stage.classList.contains('mascot-speaking')) hideMascotSpeech(stage);
162
- card.querySelector('.settings-content').append(feedback);
163
- }
164
- return true;
165
- }
166
- function hideMascotSpeech(stage) {
167
- stage.querySelector('.mascot-speech')?.remove();
168
- stage.classList.remove('mascot-waiting', 'mascot-speaking');
169
- stage.getAnimations({ subtree: true }).forEach(function (animation) {
170
- if (animation.animationName === 'mascot-arrive') animation.currentTime = 780;
171
- if (['mascot-walk', 'mascot-turn', 'mascot-left-step', 'mascot-right-step'].includes(animation.animationName)) animation.currentTime = 27780;
172
- });
173
- }
174
- function mascotAnimationState() {
175
- var mascot = document.querySelector('.connection-mascot');
176
- return { speechDismissed: mascotSpeechDismissed, paused: mascot?.hasAttribute('data-paused') || false, animations: mascot ? mascot.getAnimations({ subtree: true }).filter(function (animation) {
177
- return typeof animation.animationName === 'string';
178
- }).map(function (animation) {
179
- return { name: animation.animationName, time: animation.currentTime };
180
- }) : [] };
181
- }
182
- function restoreMascotAnimationState(state) {
183
- var mascot = document.querySelector('.connection-mascot');
184
- if (!mascot) return;
185
- mascotSpeechDismissed = Boolean(state.speechDismissed);
186
- if (mascotSpeechDismissed && mascot.classList.contains('mascot-speaking')) hideMascotSpeech(mascot);
187
- if (state.paused) {
188
- mascot.setAttribute('data-paused', '');
189
- var pause = mascot.querySelector('.mascot-pause');
190
- pause.setAttribute('aria-pressed', 'true');
191
- pause.setAttribute('aria-label', '繼續法鬥超人動畫');
192
- pause.title = '繼續動畫';
193
- }
194
- mascot.getAnimations({ subtree: true }).forEach(function (animation) {
195
- var saved = (state.animations || []).find(function (value) { return value.name === animation.animationName; });
196
- if (saved && saved.time !== null) animation.currentTime = saved.time;
197
- });
198
- }
199
- document.addEventListener('click', function (event) {
200
- var close = event.target.closest?.('.mascot-speech-close');
201
- if (close && !close.disabled) {
202
- var speaking = close.closest('.connection-mascot');
203
- mascotSpeechDismissed = true;
204
- hideMascotSpeech(speaking);
205
- document.querySelector('[data-setting="連線"] .connection-copy')?.focus({ preventScroll: true });
206
- return;
207
- }
208
- var pause = event.target.closest?.('.mascot-pause');
209
- if (pause && !pause.disabled) {
210
- var stage = pause.closest('.connection-mascot');
211
- var paused = stage.toggleAttribute('data-paused');
212
- pause.setAttribute('aria-pressed', String(paused));
213
- pause.setAttribute('aria-label', paused ? '繼續法鬥超人動畫' : '暫停法鬥超人動畫');
214
- pause.title = paused ? '繼續動畫' : '暫停動畫';
215
- return;
216
- }
217
- var button = event.target.closest?.('.mascot-play');
218
- if (!button || button.disabled) return;
219
- var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
220
- var arm = button.querySelector('.mascot-arm');
221
- arm.getAnimations().forEach(function (animation) { animation.cancel(); });
222
- if (!reduced) arm.animate([
223
- { transform: 'rotate(0deg)' }, { transform: 'rotate(-8deg)', offset: 0.25 },
224
- { transform: 'rotate(5deg)', offset: 0.5 }, { transform: 'rotate(-6deg)', offset: 0.75 },
225
- { transform: 'rotate(0deg)' }
226
- ], { duration: 800, easing: 'ease-in-out' });
227
- button.getAnimations().forEach(function (animation) { animation.cancel(); });
228
- button.animate(reduced ? [{ opacity: 0.65 }, { opacity: 1 }] : [
229
- { transform: 'translateY(0) rotate(0deg)' },
230
- { transform: 'translateY(-8px) rotate(-4deg)', offset: 0.35 },
231
- { transform: 'translateY(-3px) rotate(3deg)', offset: 0.65 },
232
- { transform: 'translateY(0) rotate(0deg)' }
233
- ], { duration: reduced ? 180 : 600, easing: 'ease-out' });
234
- });
142
+ exports.connectionMascotScript = `
143
+ var mascotSpeechDismissed = false;
144
+ function updateCopyFeedback(updated) {
145
+ var card = document.querySelector('[data-setting="連線"]');
146
+ var nextCard = updated.querySelector('[data-setting="連線"]');
147
+ var stage = card?.querySelector('.connection-mascot') || document.querySelector('.classroom-update .connection-mascot');
148
+ var nextStage = nextCard?.querySelector('.connection-mascot');
149
+ if (!stage || !nextStage || !nextCard.querySelector('.connection-copy')) return false;
150
+ var speech = nextStage.querySelector('.mascot-speech');
151
+ var feedback = nextCard.querySelector('.settings-content > .connection-feedback');
152
+ if (!speech && !feedback) return false;
153
+ card.querySelector('.settings-content > .connection-feedback')?.remove();
154
+ stage.querySelector('.mascot-speech')?.remove();
155
+ if (nextStage.classList.contains('mascot-speaking')) {
156
+ if (!speech) return false;
157
+ mascotSpeechDismissed = false;
158
+ stage.classList.add('mascot-waiting', 'mascot-speaking');
159
+ stage.prepend(speech);
160
+ } else {
161
+ if (stage.classList.contains('mascot-speaking')) hideMascotSpeech(stage);
162
+ card.querySelector('.settings-content').append(feedback);
163
+ }
164
+ return true;
165
+ }
166
+ function hideMascotSpeech(stage) {
167
+ stage.querySelector('.mascot-speech')?.remove();
168
+ stage.classList.remove('mascot-waiting', 'mascot-speaking');
169
+ stage.getAnimations({ subtree: true }).forEach(function (animation) {
170
+ if (animation.animationName === 'mascot-arrive') animation.currentTime = 780;
171
+ if (['mascot-walk', 'mascot-turn', 'mascot-left-step', 'mascot-right-step'].includes(animation.animationName)) animation.currentTime = 27780;
172
+ });
173
+ }
174
+ function mascotAnimationState() {
175
+ var mascot = document.querySelector('.connection-mascot');
176
+ return { speechDismissed: mascotSpeechDismissed, paused: mascot?.hasAttribute('data-paused') || false, animations: mascot ? mascot.getAnimations({ subtree: true }).filter(function (animation) {
177
+ return typeof animation.animationName === 'string';
178
+ }).map(function (animation) {
179
+ return { name: animation.animationName, time: animation.currentTime };
180
+ }) : [] };
181
+ }
182
+ function restoreMascotAnimationState(state) {
183
+ var mascot = document.querySelector('.connection-mascot');
184
+ if (!mascot) return;
185
+ mascotSpeechDismissed = Boolean(state.speechDismissed);
186
+ if (mascotSpeechDismissed && mascot.classList.contains('mascot-speaking')) hideMascotSpeech(mascot);
187
+ if (state.paused) {
188
+ mascot.setAttribute('data-paused', '');
189
+ var pause = mascot.querySelector('.mascot-pause');
190
+ pause.setAttribute('aria-pressed', 'true');
191
+ pause.setAttribute('aria-label', '繼續法鬥超人動畫');
192
+ pause.title = '繼續動畫';
193
+ }
194
+ mascot.getAnimations({ subtree: true }).forEach(function (animation) {
195
+ var saved = (state.animations || []).find(function (value) { return value.name === animation.animationName; });
196
+ if (saved && saved.time !== null) animation.currentTime = saved.time;
197
+ });
198
+ }
199
+ document.addEventListener('click', function (event) {
200
+ var close = event.target.closest?.('.mascot-speech-close');
201
+ if (close && !close.disabled) {
202
+ var speaking = close.closest('.connection-mascot');
203
+ mascotSpeechDismissed = true;
204
+ hideMascotSpeech(speaking);
205
+ document.querySelector('[data-setting="連線"] .connection-copy')?.focus({ preventScroll: true });
206
+ return;
207
+ }
208
+ var pause = event.target.closest?.('.mascot-pause');
209
+ if (pause && !pause.disabled) {
210
+ var stage = pause.closest('.connection-mascot');
211
+ var paused = stage.toggleAttribute('data-paused');
212
+ pause.setAttribute('aria-pressed', String(paused));
213
+ pause.setAttribute('aria-label', paused ? '繼續法鬥超人動畫' : '暫停法鬥超人動畫');
214
+ pause.title = paused ? '繼續動畫' : '暫停動畫';
215
+ return;
216
+ }
217
+ var button = event.target.closest?.('.mascot-play');
218
+ if (!button || button.disabled) return;
219
+ var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
220
+ var arm = button.querySelector('.mascot-arm');
221
+ arm.getAnimations().forEach(function (animation) { animation.cancel(); });
222
+ if (!reduced) arm.animate([
223
+ { transform: 'rotate(0deg)' }, { transform: 'rotate(-8deg)', offset: 0.25 },
224
+ { transform: 'rotate(5deg)', offset: 0.5 }, { transform: 'rotate(-6deg)', offset: 0.75 },
225
+ { transform: 'rotate(0deg)' }
226
+ ], { duration: 800, easing: 'ease-in-out' });
227
+ button.getAnimations().forEach(function (animation) { animation.cancel(); });
228
+ button.animate(reduced ? [{ opacity: 0.65 }, { opacity: 1 }] : [
229
+ { transform: 'translateY(0) rotate(0deg)' },
230
+ { transform: 'translateY(-8px) rotate(-4deg)', offset: 0.35 },
231
+ { transform: 'translateY(-3px) rotate(3deg)', offset: 0.65 },
232
+ { transform: 'translateY(0) rotate(0deg)' }
233
+ ], { duration: reduced ? 180 : 600, easing: 'ease-out' });
234
+ });
235
235
  `;