102102 transition : opacity 0.8s ease;
103103 }
104104
105- /* ---- 遮罩(含音量控制 ) ---- */
105+ /* ---- 遮罩(含灵感来源和音量控制 ) ---- */
106106 # overlay {
107107 position : absolute;
108108 inset : 0 ;
109109 z-index : 20 ;
110110 display : flex;
111111 flex-direction : column;
112- gap : 20 px ;
112+ gap : 14 px ;
113113 align-items : center;
114114 justify-content : center;
115- background : rgba (79 , 56 , 38 , 0.85 );
115+ background : rgba (79 , 56 , 38 , 0.88 );
116116 backdrop-filter : blur (4px );
117- cursor : pointer;
118- transition : opacity 0.6s ease;
119- padding : 0 20px ;
120- }
121- # overlay .hide {
122- opacity : 0 ;
123- pointer-events : none;
117+ padding : 20px ;
118+ cursor : default;
119+ transition : opacity 0.5s ease;
124120 }
125121 # overlay .big {
126122 font-size : clamp (42px , 10vw , 82px );
127123 font-weight : 900 ;
128124 letter-spacing : 0.2em ;
129125 color : rgba (255 , 220 , 180 , 0.85 );
130126 text-shadow : 0 6px 30px rgba (0 , 0 , 0 , 0.5 );
127+ cursor : pointer;
128+ padding : 8px 20px ;
129+ border-radius : 12px ;
130+ transition : background 0.2s ;
131+ }
132+ # overlay .big : hover {
133+ background : rgba (255 , 200 , 150 , 0.06 );
131134 }
132135 # overlay .big em {
133136 font-style : normal;
138141 letter-spacing : 0.4em ;
139142 color : rgba (255 , 200 , 150 , 0.4 );
140143 animation : pulseHint 1.8s ease-in-out infinite;
141- margin-top : -4px ;
144+ margin-top : -6px ;
145+ cursor : pointer;
142146 }
143147 @keyframes pulseHint {
144148 0% ,
150154 }
151155 }
152156
157+ /* 灵感来源链接 */
158+ .inspire-links {
159+ display : flex;
160+ gap : 18px ;
161+ margin : 4px 0 6px ;
162+ flex-wrap : wrap;
163+ justify-content : center;
164+ }
165+ .inspire-links a {
166+ color : rgba (255 , 200 , 150 , 0.5 );
167+ font-size : 13px ;
168+ letter-spacing : 0.06em ;
169+ text-decoration : none;
170+ border-bottom : 1px solid rgba (255 , 200 , 150 , 0.15 );
171+ padding-bottom : 2px ;
172+ transition : color 0.25s , border-color 0.25s ;
173+ cursor : pointer;
174+ pointer-events : auto;
175+ }
176+ .inspire-links a : hover {
177+ color : rgba (255 , 220 , 180 , 0.85 );
178+ border-color : rgba (255 , 200 , 150 , 0.4 );
179+ }
180+
153181 /* 音量控制 */
154182 .volume-control {
155183 display : flex;
225253 text-align : center;
226254 }
227255
228- /* 阻止滑块事件冒泡到遮罩 */
229- .volume-control * {
256+ /* 底部按钮 */
257+ # bottom-links {
258+ position : absolute;
259+ bottom : max (14px , env (safe-area-inset-bottom));
260+ left : 50% ;
261+ transform : translateX (-50% );
262+ z-index : 8 ;
263+ display : flex;
264+ gap : 14px ;
265+ align-items : center;
266+ background : rgba (0 , 0 , 0 , 0.25 );
267+ padding : 6px 14px ;
268+ border-radius : 30px ;
269+ backdrop-filter : blur (4px );
270+ border : 1px solid rgba (255 , 200 , 150 , 0.08 );
271+ transition : opacity 0.4s ease;
272+ }
273+ # bottom-links a {
274+ display : flex;
275+ align-items : center;
276+ justify-content : center;
277+ width : 28px ;
278+ height : 28px ;
279+ color : rgba (255 , 210 , 160 , 0.5 );
280+ transition : color 0.25s , transform 0.2s ;
281+ cursor : pointer;
230282 pointer-events : auto;
231283 }
284+ # bottom-links a : hover {
285+ color : rgba (255 , 220 , 180 , 0.85 );
286+ transform : scale (1.12 );
287+ }
288+ # bottom-links svg {
289+ width : 22px ;
290+ height : 22px ;
291+ fill : currentColor;
292+ stroke : none;
293+ }
294+ /* 确保链接可点击 */
295+ # bottom-links a * {
296+ pointer-events : none;
297+ }
232298
233299 /* ---- 响应式 ---- */
234300 @media (max-width : 480px ) {
249315 font-size : 11px ;
250316 min-width : 26px ;
251317 }
318+ .inspire-links a {
319+ font-size : 11px ;
320+ }
321+ # bottom-links {
322+ padding : 4px 10px ;
323+ gap : 10px ;
324+ }
325+ # bottom-links a {
326+ width : 24px ;
327+ height : 24px ;
328+ }
329+ # bottom-links svg {
330+ width : 18px ;
331+ height : 18px ;
332+ }
333+ }
334+ @media (max-height : 600px ) {
335+ .inspire-links {
336+ gap : 12px ;
337+ }
338+ .volume-control {
339+ padding : 6px 12px ;
340+ }
252341 }
253342 </ style >
254343</ head >
262351 < div id ="title "> 拉伸< span > Tap</ span > </ div >
263352 < div id ="hint "> 点击 · 滑动 · 升调</ div >
264353
354+ <!-- 遮罩 -->
265355 < div id ="overlay ">
266- < div class ="big "> 拉伸< em > Tap</ em > </ div >
267- < div class ="sub "> 点 击 任 意 位 置 开 始</ div >
356+ < div class ="big " id ="start-trigger "> 拉伸< em > Tap</ em > </ div >
357+ < div class ="sub " id ="start-trigger-sub "> 点 击 任 意 位 置 开 始</ div >
358+
359+ < div class ="inspire-links ">
360+ < a href ="https://aidn.jp/mikutap/ " target ="_blank " rel ="noopener noreferrer "> Mikutap</ a >
361+ < a href ="https://www.bilibili.com/toy/Dagou-Tap/index.html " target ="_blank " rel ="noopener noreferrer "> 大狗Tap</ a >
362+ </ div >
363+
268364 < div class ="volume-control " id ="volume-control ">
269365 < label for ="bgm-volume "> 背景音量</ label >
270366 < input type ="range " id ="bgm-volume " min ="0 " max ="1 " step ="0.01 " value ="0.25 " />
271367 < span class ="vol-value " id ="vol-value "> 25%</ span >
272368 </ div >
273369 </ div >
370+
371+ <!-- 底部链接 -->
372+ < div id ="bottom-links ">
373+ < a href ="https://github.com/CraftSoul/soviettap/ " target ="_blank " rel ="noopener noreferrer " aria-label ="GitHub ">
374+ < svg viewBox ="0 0 24 24 " xmlns ="http://www.w3.org/2000/svg "> < path d ="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.468-2.38 1.235-3.22-.123-.3-.535-1.52.117-3.16 0 0 1.008-.322 3.3 1.23.96-.267 1.98-.399 3-.399s2.04.132 3 .399c2.292-1.552 3.3-1.23 3.3-1.23.653 1.64.24 2.86.118 3.16.768.84 1.233 1.91 1.233 3.22 0 4.61-2.804 5.62-5.476 5.92.43.37.824 1.102.824 2.22 0 1.602-.015 2.894-.015 3.287 0 .322.216.694.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12 "/> </ svg >
375+ </ a >
376+ < a href ="https://space.bilibili.com/3546775476505152 " target ="_blank " rel ="noopener noreferrer " aria-label ="Bilibili ">
377+ < svg viewBox ="0 0 24 24 " xmlns ="http://www.w3.org/2000/svg "> < path d ="M17.813 4.653h.854l1.338 1.259c.477.447.713.944.713 1.491v8.557c0 .547-.236 1.044-.713 1.491l-1.338 1.259h-.854l1.371-1.259c.35-.33.525-.718.525-1.163V7.403c0-.445-.175-.833-.525-1.163l-1.371-1.587zm-9.792 0l-1.37 1.587c-.35.33-.526.718-.526 1.163v8.557c0 .445.176.833.526 1.163l1.37 1.259h.854L7.47 16.457c-.477-.447-.713-.944-.713-1.491V7.403c0-.547.236-1.044.713-1.491l1.338-1.259h-.854zM3.492 1.955c-.822 0-1.491.668-1.491 1.491v17.108c0 .823.669 1.491 1.491 1.491h17.016c.823 0 1.491-.668 1.491-1.491V3.446c0-.823-.668-1.491-1.491-1.491H3.492z "/> </ svg >
378+ </ a >
379+ </ div >
274380 </ div >
275381
276382 < script >
277383 /* ============================================================
278384 * 拉伸Tap —— 四列音效 (sl.wav, boom.wav, sla.wav, a.wav)
279- * 自下至上升调 · 仿大狗Tap特效 + 背景音乐
280- * 隐藏网格 · 背景音量可调(默认1/4)
281- * 触发后等待节拍播放(量化队列)
385+ * 音调反向(上高下低)· 点击修复 · 灵感来源 · 底部链接
386+ * 遮罩彻底移除 · 触发后量化播放
282387 * ============================================================ */
283388
284389 // ---------- 常量 ----------
306411 let nextNoteTime = 0 ;
307412 let stepCount = 0 ;
308413
309- let bgmVolume = 0.25 ; // 默认1/4
414+ let bgmVolume = 0.25 ;
310415
311416 const buffers = { } ;
312417 let loadedCount = 0 ;
374479
375480 function nowSec ( ) { return ctx ? ctx . currentTime : performance . now ( ) / 1000 ; }
376481
377- function getStageMetrics ( ) {
378- const rect = document . getElementById ( 'stage' ) . getBoundingClientRect ( ) ;
379- return { width : rect . width , height : rect . height , left : rect . left , top : rect . top } ;
380- }
381-
382482 // ---------- 音频初始化 ----------
383483 function initAudio ( ) {
384484 ctx = new ( window . AudioContext || window . webkitAudioContext ) ( ) ;
592692 return t ;
593693 }
594694
595- // ---------- 播放音效 ----------
695+ // ---------- 播放音效(音调反向:上高下低) ----------
596696 function playSample ( sampleName , pitchTier , when ) {
597697 const buffer = buffers [ sampleName ] ;
598698 if ( ! buffer ) return ;
599- const rate = 0.6 + ( pitchTier / ( ROWS - 1 ) ) * 1.4 ;
699+ // 反向:pitchTier=0 → 最高音 (2.0), pitchTier=7 → 最低音 (0.6)
700+ const rate = 0.6 + ( ( ROWS - 1 - pitchTier ) / ( ROWS - 1 ) ) * 1.4 ;
600701 const source = ctx . createBufferSource ( ) ;
601702 source . buffer = buffer ;
602703 source . playbackRate . setValueAtTime ( rate , when ) ;
646747 }
647748 }
648749
750+ // 不再取消队列(点击修复)
649751 function cancelQueuedInputs ( pointerId ) {
650- for ( let i = inputQueue . length - 1 ; i >= 0 ; i -- ) {
651- if ( inputQueue [ i ] . pointerId === pointerId ) inputQueue . splice ( i , 1 ) ;
652- }
653- reflowQueuedInputTimes ( ) ;
752+ // 保留队列,不取消任何条目(点击修复)
654753 }
655754
656755 // ---------- 网格交互 ----------
13311430
13321431 stage . addEventListener ( 'pointerup' , ( e ) => {
13331432 const state = pointers . get ( e . pointerId ) ;
1334- if ( state ) cancelQueuedInputs ( e . pointerId ) ;
1433+ // 不取消队列,只清除状态
13351434 pointers . delete ( e . pointerId ) ;
13361435 try { stage . releasePointerCapture ( e . pointerId ) ; } catch ( _ ) { }
13371436 } ) ;
13381437
13391438 stage . addEventListener ( 'pointercancel' , ( e ) => {
13401439 const state = pointers . get ( e . pointerId ) ;
1341- if ( state ) cancelQueuedInputs ( e . pointerId ) ;
13421440 pointers . delete ( e . pointerId ) ;
13431441 try { stage . releasePointerCapture ( e . pointerId ) ; } catch ( _ ) { }
13441442 } ) ;
13451443
13461444 stage . addEventListener ( 'contextmenu' , ( e ) => e . preventDefault ( ) ) ;
13471445
13481446 window . addEventListener ( 'blur' , ( ) => {
1349- inputQueue . length = 0 ;
1350- pointers . clear ( ) ;
1447+ // 不清除队列,防止丢失
13511448 } ) ;
13521449 }
13531450
13661463 }
13671464 } ) ;
13681465
1369- // 阻止滑块事件冒泡到遮罩,避免误触发启动
1466+ // 阻止滑块事件冒泡到遮罩
13701467 control . addEventListener ( 'pointerdown' , ( e ) => e . stopPropagation ( ) ) ;
13711468 control . addEventListener ( 'pointerup' , ( e ) => e . stopPropagation ( ) ) ;
13721469 control . addEventListener ( 'click' , ( e ) => e . stopPropagation ( ) ) ;
13761473 function start ( ) {
13771474 if ( started ) return ;
13781475 const overlay = document . getElementById ( 'overlay' ) ;
1379- overlay . classList . add ( 'hide' ) ;
1380- document . getElementById ( 'hint' ) . style . opacity = '0' ;
1476+ if ( overlay ) {
1477+ // 彻底移除遮罩
1478+ overlay . remove ( ) ;
1479+ }
13811480
1382- // 禁用音量控件
1481+ // 禁用音量控件(已经移除,但以防万一)
13831482 const slider = document . getElementById ( 'bgm-volume' ) ;
1483+ if ( slider ) slider . disabled = true ;
13841484 const control = document . getElementById ( 'volume-control' ) ;
1385- slider . disabled = true ;
1386- control . classList . add ( 'disabled' ) ;
1485+ if ( control ) control . classList . add ( 'disabled' ) ;
13871486
13881487 initAudio ( ) ;
13891488 if ( ctx . state === 'suspended' ) ctx . resume ( ) ;
13901489
1391- // 应用当前音量设置
13921490 if ( bgmBus ) {
13931491 bgmBus . gain . value = bgmVolume ;
13941492 }
14011499 stepCount = 0 ;
14021500 started = true ;
14031501 console . log ( '启动完成' ) ;
1404- // 触发一次初始特效(不播放音效)
14051502 spawnEffect ( 0 , 3 , ctx . currentTime + 0.05 ) ;
14061503 } ) . catch ( err => {
14071504 console . warn ( '加载音效失败,仍可使用合成' , err ) ;
14201517 setupEvents ( ) ;
14211518 setupVolumeSlider ( ) ;
14221519
1423- // 点击遮罩只启动,不触发播放
1520+ // 遮罩点击触发启动,但排除链接和音量控件
14241521 const overlay = document . getElementById ( 'overlay' ) ;
1522+ // 使用事件代理,但链接会冒泡,所以需要阻止
14251523 overlay . addEventListener ( 'click' , ( e ) => {
1426- if ( e . target . closest ( '.volume-control' ) ) return ;
1524+ const target = e . target ;
1525+ if ( target . closest ( 'a' ) || target . closest ( '.volume-control' ) ) return ;
14271526 start ( ) ;
14281527 } ) ;
14291528 overlay . addEventListener ( 'touchstart' , ( e ) => {
1430- if ( e . target . closest ( '.volume-control' ) ) return ;
1529+ const target = e . target ;
1530+ if ( target . closest ( 'a' ) || target . closest ( '.volume-control' ) ) return ;
14311531 e . preventDefault ( ) ;
14321532 start ( ) ;
14331533 } , { passive : false } ) ;
14341534
1535+ // 链接点击不触发启动,且阻止冒泡
1536+ overlay . querySelectorAll ( 'a' ) . forEach ( link => {
1537+ link . addEventListener ( 'click' , ( e ) => e . stopPropagation ( ) ) ;
1538+ link . addEventListener ( 'touchstart' , ( e ) => e . stopPropagation ( ) ) ;
1539+ } ) ;
1540+
14351541 window . addEventListener ( 'resize' , resizeFx ) ;
14361542 requestAnimationFrame ( tick ) ;
14371543
14401546 if ( ! ctx ) initAudio ( ) ;
14411547 } , { once : true } ) ;
14421548
1443- console . log ( '拉伸Tap 已加载 (网格隐藏,背景音量可调,触发后量化播放 )' ) ;
1549+ console . log ( '拉伸Tap 已加载 (音调反向,点击修复,遮罩移除 )' ) ;
14441550 }
14451551
14461552 if ( document . readyState === 'loading' ) {
14491555 init ( ) ;
14501556 }
14511557 </ script >
1558+
14521559</ body >
14531560</ html >
0 commit comments