Compare commits

...

10 Commits

Author SHA1 Message Date
559e70fac5 feat(mobile): 全屏自适应布局,适配手机/折叠屏/平板
- #wrap 去除外框限制,竖屏/方形屏占满全屏(逻辑分辨率动态:宽240、高按屏比 clamp[280,640])
- 横屏 letterbox:中间 3:4 竖屏显示区域居中,UI 收紧压缩
- viewport-fit=cover + safe-area-inset 适配刘海屏/底部横条
- 星星背景按视口面积重建,保持星空密度
- resize/orientationchange 自动重新适配,对局中旋转位置自动修正
2026-08-06 16:05:03 +08:00
bb0d7e5704 机库战机卡改为单行横向滚动(#ship-pick nowrap+overflow-x,mode/rush 不受影响),标题加'左右滑动'提示 2026-08-06 15:18:11 +08:00
de33f67343 钢琴Boss大激光:白半圆与红半圆同心,底部对齐
- 白半圆圆心改为和红色半圆相同(bm.x1, bm.y1+pR),半径pR/3
- 白矩形起点改为bm.y1+pR,和红色半圆/矩形底部完美对齐
- 白芯从红色半圆下半部分开始,向下贯穿整个矩形,不再断裂
2026-08-06 15:13:01 +08:00
cf41515239 钢琴Boss大激光半圆:白芯也改上半圆
- 内部白芯从完整圆改为上半圆arc(...,0,Math.PI,true)
- 白半圆圆心(bm.x1, bm.y1+pR/3),顶部=炮口,和外部主色半圆同构
2026-08-06 15:11:14 +08:00
53733e6bfc 钢琴Boss大激光半圆:中间加白圆匹配激光内芯
- 在半圆中间位置(圆心bm.x1, bm.y1+pR/2)加一个实心白圆
- 白圆半径pR/3,直径=2pR/3=bm.w/3,和激光矩形内芯宽度一致
- 白圆完全在实心半圆内部,模拟激光从炮口射出的白芯效果
2026-08-06 15:09:34 +08:00
f7e41d3419 钢琴Boss大激光最终修正:5秒持续时间+fill实心完美对接
- 激光持续时间从40帧改为300帧(5秒),匹配用户要求的'持续5秒后消退'
- 大激光绘制改用fill实心风格:
  - 上半圆fill:圆心(bm.x1, bm.y1+pR),顶部=炮口bm.y1,底部直径=bm.y1+pR
  - 矩形fillRect:从半圆底部bm.y1+pR开始向下,宽度=直径bm.w,和半圆完美对接
  - 白边fillRect:窄矩形在主体内
- 彻底去掉stroke描边弧线,消除弧线描边在不同位置的展开差异导致的缝隙
2026-08-06 15:07:38 +08:00
4ea33f42d9 钢琴Boss大炮塔:蓄力/消退拆分为独立字段,消退期不显示圆环
- pedLasL(0-60)专门管蓄力,pedLasLCd(300→0)专门管消退倒计时
- 蓄力满60发射激光,发射后pedLasL清0,pedLasLCd置300开始5秒消退
- 消退期间每帧-1,不检测玩家,不显示蓄力圆环
- 消退结束后pedLasLCd=0,才重新允许检测玩家是否在炮塔下方
2026-08-06 15:01:13 +08:00
b8df012c4d 钢琴Boss大激光半圆:改用和激光线一致的描边风格
- 去掉fill()实心填充,改用stroke()弧线描边
- 粗主色弧线(lineWidth=bm.w)+细白边弧线(lineWidth=bm.w/3),和激光线绘制方式完全一致
- 半圆与激光都是描边发光风格,纹理自然统一
2026-08-06 14:59:52 +08:00
90ee0ea0f9 钢琴Boss大炮塔:蓄力发射后进入5秒消退期,期间不重复检测
- 三态状态机:空闲(0)→蓄力(1-60)→发射+消退(61-360)→空闲(0)
- 蓄满60帧发射激光,随后进入300帧(5秒)消退倒计时
- 消退期间不检测玩家位置,圆环保持满状态显示
- 倒计时结束后才重新允许检测玩家是否在炮塔下方
2026-08-06 14:58:17 +08:00
2d26af9958 钢琴Boss大激光:线从半圆底部开始,不重叠 2026-08-06 14:56:37 +08:00
7 changed files with 111 additions and 31 deletions

View File

@ -12,9 +12,8 @@ body{
user-select:none; user-select:none;
} }
#wrap{ #wrap{
position:relative; width:min(96vw,calc(96vh * 0.75)); aspect-ratio:3/4; position:fixed; inset:0; width:100%; height:100%;
border:4px solid var(--line); background:#000; background:#000;
box-shadow:0 0 0 2px #000, 0 0 42px rgba(65,230,255,.12), 0 24px 80px rgba(0,0,0,.7);
} }
canvas#game{width:100%;height:100%;display:block;image-rendering:pixelated;image-rendering:crisp-edges;background:#05070f;touch-action:none} canvas#game{width:100%;height:100%;display:block;image-rendering:pixelated;image-rendering:crisp-edges;background:#05070f;touch-action:none}
#scan{position:absolute;inset:0;pointer-events:none;z-index:40; #scan{position:absolute;inset:0;pointer-events:none;z-index:40;
@ -24,7 +23,9 @@ canvas#game{width:100%;height:100%;display:block;image-rendering:pixelated;image
background:radial-gradient(ellipse at center,transparent 55%,rgba(0,0,0,.5) 100%)} background:radial-gradient(ellipse at center,transparent 55%,rgba(0,0,0,.5) 100%)}
/* ---------- overlays ---------- */ /* ---------- overlays ---------- */
.ovl{position:absolute;inset:0;z-index:50;display:flex;flex-direction:column;align-items:center;justify-content:center; .ovl{position:absolute;inset:0;z-index:50;display:flex;flex-direction:column;align-items:center;justify-content:center;
background:rgba(4,7,16,.88); text-align:center; padding:4%; gap:10px; overflow-y:auto} background:rgba(4,7,16,.88); text-align:center;
padding:max(4%,env(safe-area-inset-top,0px)) max(4%,env(safe-area-inset-right,0px)) max(4%,env(safe-area-inset-bottom,0px)) max(4%,env(safe-area-inset-left,0px));
gap:10px; overflow-y:auto}
.ovl.hide{display:none} .ovl.hide{display:none}
#o-title{background:rgba(4,7,16,.5)} /* 半透明:露出背后实战模拟 */ #o-title{background:rgba(4,7,16,.5)} /* 半透明:露出背后实战模拟 */
h1.logo{font-size:clamp(20px,5.4vw,44px);color:#fff;letter-spacing:4px;line-height:1.35; h1.logo{font-size:clamp(20px,5.4vw,44px);color:#fff;letter-spacing:4px;line-height:1.35;
@ -52,6 +53,14 @@ h1.logo{font-size:clamp(20px,5.4vw,44px);color:#fff;letter-spacing:4px;line-heig
.stat-grid b{color:var(--gold);text-align:right} .stat-grid b{color:var(--gold);text-align:right}
/* ship / mode select cards */ /* ship / mode select cards */
.pick{display:flex;gap:10px;flex-wrap:wrap;justify-content:center} .pick{display:flex;gap:10px;flex-wrap:wrap;justify-content:center}
/* 机库5 战机单行横向滚动(仅 ship-pickmode/rush 保持换行居中) */
#ship-pick{flex-wrap:nowrap;justify-content:flex-start;width:100%;overflow-x:auto;overscroll-behavior-x:contain;
-webkit-overflow-scrolling:touch;padding:2px 2px 8px;scrollbar-width:thin;scrollbar-color:var(--line) transparent}
#ship-pick .pcard{flex:0 0 auto}
#ship-pick::-webkit-scrollbar{height:6px}
#ship-pick::-webkit-scrollbar-track{background:transparent}
#ship-pick::-webkit-scrollbar-thumb{background:var(--line);border-radius:3px}
#ship-pick::-webkit-scrollbar-thumb:hover{background:var(--cyan)}
.rush-grp{width:100%;margin:2px 0 10px} .rush-grp{width:100%;margin:2px 0 10px}
.rush-grp .h3{font-size:clamp(9px,1.6vw,12px);letter-spacing:3px;margin-bottom:5px} .rush-grp .h3{font-size:clamp(9px,1.6vw,12px);letter-spacing:3px;margin-bottom:5px}
.rush-row{display:flex;gap:10px;flex-wrap:wrap;justify-content:center} .rush-row{display:flex;gap:10px;flex-wrap:wrap;justify-content:center}
@ -102,10 +111,10 @@ h1.logo{font-size:clamp(20px,5.4vw,44px);color:#fff;letter-spacing:4px;line-heig
#tbtns button{pointer-events:auto;font:inherit;font-size:10px;width:64px;height:64px;border-radius:8px; #tbtns button{pointer-events:auto;font:inherit;font-size:10px;width:64px;height:64px;border-radius:8px;
background:rgba(16,26,51,.7);border:3px solid var(--line);color:var(--ink);letter-spacing:1px} background:rgba(16,26,51,.7);border:3px solid var(--line);color:var(--ink);letter-spacing:1px}
#tbtns button:active{background:var(--cyan);color:#000} #tbtns button:active{background:var(--cyan);color:#000}
#tb-pause{position:absolute;top:6px;right:6px;z-index:46;font:inherit;font-size:11px;width:40px;height:40px; #tb-pause{position:absolute;top:calc(6px + env(safe-area-inset-top,0px));right:calc(6px + env(safe-area-inset-right,0px));z-index:46;font:inherit;font-size:11px;width:40px;height:40px;
background:rgba(16,26,51,.7);border:3px solid var(--line);color:var(--ink);letter-spacing:1px} background:rgba(16,26,51,.7);border:3px solid var(--line);color:var(--ink);letter-spacing:1px}
#tb-pause:active{background:var(--cyan);color:#000} #tb-pause:active{background:var(--cyan);color:#000}
#b-mute{position:absolute;top:10px;right:10px;z-index:5;width:42px;height:42px; #b-mute{position:absolute;top:calc(10px + env(safe-area-inset-top,0px));right:calc(10px + env(safe-area-inset-right,0px));z-index:5;width:42px;height:42px;
display:flex;align-items:center;justify-content:center; display:flex;align-items:center;justify-content:center;
background:rgba(16,26,51,.85);border:3px solid var(--line);color:var(--ink);cursor:pointer;padding:0;line-height:1} background:rgba(16,26,51,.85);border:3px solid var(--line);color:var(--ink);cursor:pointer;padding:0;line-height:1}
#b-mute canvas{image-rendering:pixelated;pointer-events:none} #b-mute canvas{image-rendering:pixelated;pointer-events:none}
@ -153,3 +162,36 @@ h1.logo{font-size:clamp(20px,5.4vw,44px);color:#fff;letter-spacing:4px;line-heig
.lb-row.me{background:rgba(65,230,255,.12);outline:1px solid rgba(65,230,255,.35)} .lb-row.me{background:rgba(65,230,255,.12);outline:1px solid rgba(65,230,255,.35)}
.lb-list{flex:1;min-height:100px;overflow-y:auto} .lb-list{flex:1;min-height:100px;overflow-y:auto}
.lb-empty{color:var(--dim);font-size:clamp(7px,1.3vw,9px);padding:14px 0;letter-spacing:2px} .lb-empty{color:var(--dim);font-size:clamp(7px,1.3vw,9px);padding:14px 0;letter-spacing:2px}
/* ---------- 横屏中间竖屏显示区域letterboxUI 收紧 ---------- */
@media (orientation: landscape){
#wrap{position:fixed;inset:0;margin:auto;width:min(100vw,calc(100dvh * 0.75));height:auto;aspect-ratio:3/4}
/* 横屏高度矮:居中溢出不可滚 → 改顶部对齐 + 伪元素弹性撑开(内容少时视觉居中,多时可滚动) */
.ovl:not(#o-home):not(#o-demo){justify-content:flex-start}
.ovl:not(#o-home):not(#o-demo)::before,
.ovl:not(#o-home):not(#o-demo)::after{content:"";flex:1 0 auto;min-height:8px}
.ovl{gap:6px;padding:max(2.5%,env(safe-area-inset-top,0px)) max(2.5%,env(safe-area-inset-right,0px)) max(2.5%,env(safe-area-inset-bottom,0px)) max(2.5%,env(safe-area-inset-left,0px))}
h1.logo{font-size:clamp(14px,6vh,20px);letter-spacing:3px;line-height:1.2}
.logo-cn{font-size:clamp(9px,2.2vh,12px);letter-spacing:6px}
.tag{font-size:clamp(6px,1.4vh,8px)}
.h2{font-size:clamp(10px,2.6vh,14px)}
.h3{font-size:clamp(8px,2vh,10px)}
.small{font-size:clamp(6px,1.5vh,8px)}
.btn{font-size:clamp(8px,2.2vh,11px);padding:8px 14px;min-width:min(44vw,200px)}
.btn.primary{padding:9px 14px}
.row{gap:6px}.col{gap:6px}
.panel{padding:10px}
.auth-f input{padding:7px 10px}
#o-auth .panel{width:min(80%,320px)}
#tb-pause{width:36px;height:36px;font-size:10px}
.wallet{font-size:clamp(9px,2.2vh,11px)}
.pcard{width:min(26vw,120px);padding:8px 5px}
.pcard canvas{width:44px;height:54px}
.ucard{width:min(24vw,130px);padding:9px 6px}
.slotbox{width:min(18vw,76px);padding:6px 3px}
.litem{font-size:clamp(7px,1.7vh,9px);padding:4px 6px}
.cx-grid{grid-template-columns:repeat(auto-fill,minmax(54px,1fr));gap:5px}
.stat-grid{font-size:clamp(8px,1.9vh,10px);gap:5px 12px}
.lb-row{grid-template-columns:24px 1fr 40px 50px 62px;font-size:clamp(7px,1.6vh,9px)}
#b-go,#b-hangar,#b-codex,#b-rush{min-width:min(40vw,180px)}
}

View File

@ -2,7 +2,7 @@
<html lang="zh-CN"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"> <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover">
<link rel="icon" href="data:,"> <link rel="icon" href="data:,">
<title>星尘突击队 STAR RAID</title> <title>星尘突击队 STAR RAID</title>
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
@ -119,7 +119,7 @@
<div class="ovl hide" id="o-hangar"> <div class="ovl hide" id="o-hangar">
<div class="h2">— 机库 · 强化 —</div> <div class="h2">— 机库 · 强化 —</div>
<div class="wallet">星币 ✦ <span id="wallet">0</span></div> <div class="wallet">星币 ✦ <span id="wallet">0</span></div>
<div class="h3">机体(选择 / 解锁)</div> <div class="h3">机体(选择 / 解锁)· 左右滑动</div>
<div class="pick" id="ship-pick"></div> <div class="pick" id="ship-pick"></div>
<div class="h3">永久强化</div> <div class="h3">永久强化</div>
<div class="shop" id="shop" style="overflow-y:auto;max-height:42%"></div> <div class="shop" id="shop" style="overflow-y:auto;max-height:42%"></div>

View File

@ -45,9 +45,9 @@ function drawPiano(b,t,fl,split){
circ(px2,2,5.5,"#5a1a1a");circ(px2-1.5,0,2.5,"#8a2a2a"); circ(px2,2,5.5,"#5a1a1a");circ(px2-1.5,0,2.5,"#8a2a2a");
px(px2-1.5,4,3,8,"#1b2434"); px(px2-1.5,4,3,8,"#1b2434");
px(px2-1,10,2,3,"#ff3355"); px(px2-1,10,2,3,"#ff3355");
// 大激光蓄力前摇:画在炮塔口下方的蓄力圆(与小激光琴键蓄力圆同源,换色赤红) // 大激光蓄力圆环:仅在蓄力阶段(1-59)显示,消退期不显示
const prog=sd<0?((b.pedLasL||0)/60):((b.pedLasR||0)/60); const raw=sd<0?(b.pedLasL||0):(b.pedLasR||0);
if(prog>0){const a=Math.min(prog,1); if(raw>0&&raw<60){const a=raw/60;
ctx.globalAlpha=0.3+0.55*a;ctx.strokeStyle="#ff3355";ctx.lineWidth=1.5; ctx.globalAlpha=0.3+0.55*a;ctx.strokeStyle="#ff3355";ctx.lineWidth=1.5;
ctx.beginPath();ctx.arc(px2,14,2+3.5*a,0,TAU);ctx.stroke(); ctx.beginPath();ctx.arc(px2,14,2+3.5*a,0,TAU);ctx.stroke();
ctx.globalAlpha=0.22*a;ctx.fillStyle="#ff3355"; ctx.globalAlpha=0.22*a;ctx.fillStyle="#ff3355";

View File

@ -2,7 +2,21 @@
/* ================= 全局状态 ================= */ /* ================= 全局状态 ================= */
const cv=$("game"),ctx=cv.getContext("2d"); const cv=$("game"),ctx=cv.getContext("2d");
ctx.imageSmoothingEnabled=false; ctx.imageSmoothingEnabled=false;
const W=240,H=320; let W=240,H=320;
/* //
竖屏/方形>=占满全屏宽固定 240高按屏幕比例动态限幅 [280,640]
横屏<保持标准竖屏 3:4 逻辑分辨率240x320CSS letterbox 居中显示
逻辑分辨率随屏变化canvas 拉伸铺满游戏区域像素不变形所有实体坐标均为相对 W/H安全 */
function fitViewport(){
const vw=Math.max(1,window.innerWidth), vh=Math.max(1,window.innerHeight);
if(vh>=vw){
W=240; H=clamp(Math.round(240*vh/vw),280,640);
}else{
W=240; H=320;
}
cv.width=W; cv.height=H;
if(typeof initStars==="function")initStars();
}
let G=null,UI="title"; let G=null,UI="title";
function newRun(mode,shipKey,diff){ function newRun(mode,shipKey,diff){
const ship=SHIPS[shipKey],m=ship.mod,meta=SAVE.m; const ship=SHIPS[shipKey],m=ship.mod,meta=SAVE.m;

View File

@ -350,18 +350,26 @@ function updateBoss(){
} }
} }
const lck=(b.laserT||0)>0; // 巨宽核心激光期间压制其他攻击 const lck=(b.laserT||0)>0; // 巨宽核心激光期间压制其他攻击
// 钢琴:大炮塔惩罚激光——玩家站在炮塔水平正下方蓄力后发射99999伤害大激光不管上下远近只管左右 // 钢琴:大炮塔惩罚激光——蓄力与消退分离蓄力pedLasL(0-60) + 消退pedLasLCd(300→0)
if(b.idx===4&&!lck){ if(b.idx===4&&!lck){
const off0=b.phase>=1?16:0,p=G.p; const off0=b.phase>=1?16:0,p=G.p;
const leftX=b.x-(112+off0),rightX=b.x+(112+off0); const leftX=b.x-(112+off0),rightX=b.x+(112+off0);
if(p.dead<=0&&Math.abs(p.x-leftX)<14){ // 左炮:消退倒计时优先
b.pedLasL=Math.min(60,(b.pedLasL||0)+1); if((b.pedLasLCd||0)>0){b.pedLasLCd=b.pedLasLCd-1;}
if(b.pedLasL>=60){bossBeam(leftX,b.y+10,leftX,H+8,14,"#ff3355",10,40,99999,{b,ox:-(112+off0),oy:10,followX2:true});b.pedLasL=0;SFX.warn();} else{const l=b.pedLasL||0;
}else b.pedLasL=Math.max(0,(b.pedLasL||0)-1); if(l===0&&p.dead<=0&&Math.abs(p.x-leftX)<14){b.pedLasL=1;}
if(p.dead<=0&&Math.abs(p.x-rightX)<14){ else if(l>0&&l<60){
b.pedLasR=Math.min(60,(b.pedLasR||0)+1); if(p.dead<=0&&Math.abs(p.x-leftX)<14)b.pedLasL=l+1;
if(b.pedLasR>=60){bossBeam(rightX,b.y+10,rightX,H+8,14,"#ff3355",10,40,99999,{b,ox:(112+off0),oy:10,followX2:true});b.pedLasR=0;SFX.warn();} else b.pedLasL=Math.max(0,l-1);
}else b.pedLasR=Math.max(0,(b.pedLasR||0)-1); if(b.pedLasL>=60){bossBeam(leftX,b.y+10,leftX,H+8,14,"#ff3355",10,300,99999,{b,ox:-(112+off0),oy:10,followX2:true});b.pedLasL=0;b.pedLasLCd=300;SFX.warn();}}}
// 右炮
if((b.pedLasRCd||0)>0){b.pedLasRCd=b.pedLasRCd-1;}
else{const r=b.pedLasR||0;
if(r===0&&p.dead<=0&&Math.abs(p.x-rightX)<14){b.pedLasR=1;}
else if(r>0&&r<60){
if(p.dead<=0&&Math.abs(p.x-rightX)<14)b.pedLasR=r+1;
else b.pedLasR=Math.max(0,r-1);
if(b.pedLasR>=60){bossBeam(rightX,b.y+10,rightX,H+8,14,"#ff3355",10,300,99999,{b,ox:(112+off0),oy:10,followX2:true});b.pedLasR=0;b.pedLasRCd=300;SFX.warn();}}}
} }
// —— 主攻击 —— // —— 主攻击 ——
b.mainT--; b.mainT--;

View File

@ -76,3 +76,9 @@ function startHomeSim() {
G.demoSim = { home: true }; G.demoSim = { home: true };
G.homeSim = true; G.homeSim = true;
} }
/* ================= // =================
首次加载即适配当前屏幕旋转/缩放时重新计算逻辑分辨率并铺满全屏 */
addEventListener("resize",fitViewport);
addEventListener("orientationchange",()=>setTimeout(fitViewport,250));
fitViewport();

View File

@ -1,7 +1,15 @@
"use strict"; "use strict";
/* ================= 背景:星空与星云 ================= */ /* ================= 背景:星空与星云 ================= */
const STARS=[[],[],[]]; const STARS=[[],[],[]];
for(let i=0;i<3;i++)for(let j=0;j<26-i*6;j++)STARS[i].push({x:rnd(W),y:rnd(H),s:(0.35+i*0.4),sz:i===2?2:1,col:i===2?pick(["#aab8d8","#8fa8d8","#c0a8c8"]):"#3f4c74"}); /* 星星按视口面积重建fitViewport 改变逻辑分辨率后调用,保持星空密度一致 */
function initStars(){
const k=(W*H)/(240*320);
for(let i=0;i<3;i++){STARS[i].length=0;
const n=Math.round((26-i*6)*k);
for(let j=0;j<n;j++)STARS[i].push({x:rnd(W),y:rnd(H),s:(0.35+i*0.4),sz:i===2?2:1,col:i===2?pick(["#aab8d8","#8fa8d8","#c0a8c8"]):"#3f4c74"});
}
}
initStars();
const NEB=["#131c3f","#33102a","#0f2a1c","#101a33","#2a1030","#330f0f"]; const NEB=["#131c3f","#33102a","#0f2a1c","#101a33","#2a1030","#330f0f"];
let nebY=0,planet=null; let nebY=0,planet=null;
function drawBG(dt){ function drawBG(dt){
@ -224,16 +232,18 @@ function drawBossFx(){
ctx.beginPath();ctx.moveTo(bm.x1,bm.y1);ctx.lineTo(bm.x2,bm.y2);ctx.stroke();ctx.globalAlpha=1;}} ctx.beginPath();ctx.moveTo(bm.x1,bm.y1);ctx.lineTo(bm.x2,bm.y2);ctx.stroke();ctx.globalAlpha=1;}}
else{ctx.globalCompositeOperation="lighter"; else{ctx.globalCompositeOperation="lighter";
ctx.globalAlpha=clamp(bm.life/10,0,1)*0.85; ctx.globalAlpha=clamp(bm.life/10,0,1)*0.85;
// 大激光起点扩口半圆:直径=激光宽度圆心在炮口下方pR处最高点=炮口,和激光一起出现 if(bm.dmg>=90000){ // 大激光:外层红半圆+红矩形,内层白半圆+白矩形,全部同心底部对齐
if(bm.dmg>=90000){const pR=bm.w*0.5; const pR=bm.w*0.5;
ctx.fillStyle=bm.col; ctx.fillStyle=bm.col;
ctx.beginPath();ctx.arc(bm.x1,bm.y1+pR,pR,Math.PI,0);ctx.fill(); ctx.beginPath();ctx.arc(bm.x1,bm.y1+pR,pR,0,Math.PI,true);ctx.fill(); // 红半圆
ctx.strokeStyle="#fff";ctx.lineWidth=Math.max(1,pR/3); ctx.fillRect(bm.x1-pR,bm.y1+pR,bm.w,bm.y2-(bm.y1+pR)); // 红矩形
ctx.beginPath();ctx.arc(bm.x1,bm.y1+pR,pR,Math.PI,0);ctx.stroke();} ctx.fillStyle="#fff";
ctx.strokeStyle=bm.col;ctx.lineWidth=bm.w; ctx.beginPath();ctx.arc(bm.x1,bm.y1+pR,pR/3,0,Math.PI,true);ctx.fill(); // 白半圆(与红半圆同心,底部对齐)
ctx.beginPath();ctx.moveTo(bm.x1,bm.y1);ctx.lineTo(bm.x2,bm.y2);ctx.stroke(); ctx.fillRect(bm.x1-pR/3,bm.y1+pR,pR*2/3,bm.y2-(bm.y1+pR));} // 白矩形(与红矩形同起点,底部对齐)
ctx.strokeStyle="#fff";ctx.lineWidth=Math.max(1,bm.w/3); else{ctx.strokeStyle=bm.col;ctx.lineWidth=bm.w;
ctx.beginPath();ctx.moveTo(bm.x1,bm.y1);ctx.lineTo(bm.x2,bm.y2);ctx.stroke(); ctx.beginPath();ctx.moveTo(bm.x1,bm.y1);ctx.lineTo(bm.x2,bm.y2);ctx.stroke();
ctx.strokeStyle="#fff";ctx.lineWidth=Math.max(1,bm.w/3);
ctx.beginPath();ctx.moveTo(bm.x1,bm.y1);ctx.lineTo(bm.x2,bm.y2);ctx.stroke();}
ctx.globalAlpha=1;ctx.globalCompositeOperation="source-over";} ctx.globalAlpha=1;ctx.globalCompositeOperation="source-over";}
} }
} }