0.2.3: prepare follow-up fixes and client features (#407, #408, #410, #413, #431, #435, #436, #440, #444)
Add special-target activation without bypassing attack cadence, cooperative card preferences and emotes, skill sound banks, per-operator voice choices, text sizing, install prompts, closed-tab recovery and unanimous setup rerolls. Retain late deployment elapsed time and resolve echo/counter pulses without recursive hook overflow. Independent final review remains pending. Golden: roster-016, roster-035, roster-036, roster-045; boss-boss_1-pair, boss-boss_3-pair, boss-boss_5-pair, boss-boss_7-pair, hidden-boss_9-pair; solo-HARD-9-boosted: Lemuen attack cadence. diy-132: Necras extra activation targets. coop2-NORMAL-14-standins: cooperative purchase preference and simulation fixes. hidden-boss_10-solo: echo pulse FIFO resolution. See DESIGN 28.13-28.24 for assumptions, sources and contributor credit.
This commit is contained in:
1 parent
314564c139
commit
f3658a0dd4
104 files changed
+3674
-741
No files matched your search
+41
-41
@@ -27,7 +27,7 @@
|
||||
border: 1px solid var(--btn-bd);
|
||||
background: var(--btn-bg);
|
||||
color: var(--btn-fg);
|
||||
font-size: .18rem;
|
||||
font-size: calc(.18 * var(--t));
|
||||
font-weight: 700;
|
||||
letter-spacing: .06em;
|
||||
line-height: 1;
|
||||
@@ -164,9 +164,9 @@
|
||||
.btn.is-loading { cursor: default; --btn-fg: var(--text-lo); }
|
||||
.btn--primary.is-loading { --btn-bg: var(--mint-700); --btn-fg: var(--text-on-mint); }
|
||||
|
||||
.btn--sm { height: .36rem; min-width: .36rem; padding: 0 .16rem; font-size: .15rem; gap: .07rem; }
|
||||
.btn--lg { height: .64rem; min-width: .64rem; padding: 0 .34rem; font-size: .22rem; gap: .12rem; }
|
||||
.btn--xl { height: .84rem; min-width: .84rem; padding: 0 .48rem; font-size: .28rem; letter-spacing: .24em; gap: .16rem; }
|
||||
.btn--sm { height: .36rem; min-width: .36rem; padding: 0 .16rem; font-size: calc(.15 * var(--t)); gap: .07rem; }
|
||||
.btn--lg { height: .64rem; min-width: .64rem; padding: 0 .34rem; font-size: calc(.22 * var(--t)); gap: .12rem; }
|
||||
.btn--xl { height: .84rem; min-width: .84rem; padding: 0 .48rem; font-size: calc(.28 * var(--t)); letter-spacing: .24em; gap: .16rem; }
|
||||
.btn--block { display: flex; width: 100%; }
|
||||
.btn--square { padding: 0; width: var(--sq, .5rem); }
|
||||
.btn--square.btn--sm { --sq: .36rem; }
|
||||
@@ -204,7 +204,7 @@
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
.panel__titles { display: flex; flex-direction: column; gap: .03rem; min-width: 0; }
|
||||
.panel__title { font-size: .21rem; font-weight: 700; letter-spacing: .04em; }
|
||||
.panel__title { font-size: calc(.21 * var(--t)); font-weight: 700; letter-spacing: .04em; }
|
||||
.panel__actions { display: flex; gap: .08rem; align-items: center; }
|
||||
.panel__body { position: relative; padding: .22rem; min-height: 0; }
|
||||
.panel--flush .panel__body { padding: 0; }
|
||||
@@ -472,9 +472,9 @@
|
||||
opacity: .55;
|
||||
}
|
||||
.modal__head { display: flex; flex-direction: column; gap: .04rem; padding: .22rem .3rem .04rem; }
|
||||
.modal__title { font-size: .26rem; font-weight: 700; letter-spacing: .04em; }
|
||||
.modal__title { font-size: calc(.26 * var(--t)); font-weight: 700; letter-spacing: .04em; }
|
||||
.modal__body { padding: .14rem .3rem .26rem; color: var(--text-md); overflow: auto; }
|
||||
.modal__text { font-size: .18rem; line-height: 1.7; }
|
||||
.modal__text { font-size: calc(.18 * var(--t)); line-height: 1.7; }
|
||||
.modal__actions {
|
||||
flex: none;
|
||||
display: flex;
|
||||
@@ -500,7 +500,7 @@
|
||||
border-left: 2px solid var(--mint-500);
|
||||
box-shadow: 0 .06rem .2rem rgba(0, 0, 0, .55);
|
||||
color: var(--text-md);
|
||||
font-size: .15rem;
|
||||
font-size: calc(.15 * var(--t));
|
||||
line-height: 1.55;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
@@ -515,8 +515,8 @@
|
||||
|
||||
/* ---- progress bar ----------------------------------------------------------------------------------- */
|
||||
.pbar { --pc: var(--mint-500); --ph: .1rem; display: flex; flex-direction: column; gap: .05rem; min-width: .8rem; }
|
||||
.pbar__meta { display: flex; justify-content: space-between; align-items: baseline; gap: .1rem; font-size: .14rem; color: var(--text-lo); }
|
||||
.pbar__value { font-family: var(--font-num); font-weight: 700; color: var(--text-hi); font-size: .16rem; }
|
||||
.pbar__meta { display: flex; justify-content: space-between; align-items: baseline; gap: .1rem; font-size: calc(.14 * var(--t)); color: var(--text-lo); }
|
||||
.pbar__value { font-family: var(--font-num); font-weight: 700; color: var(--text-hi); font-size: calc(.16 * var(--t)); }
|
||||
.pbar__value small { color: var(--text-dim); font-size: .8em; }
|
||||
.pbar__track { position: relative; height: var(--ph); background: #080a09; border: 1px solid var(--line); overflow: hidden; }
|
||||
.pbar__fill {
|
||||
@@ -549,7 +549,7 @@
|
||||
border: 0;
|
||||
background: none;
|
||||
color: var(--text-lo);
|
||||
font-size: .17rem;
|
||||
font-size: calc(.17 * var(--t));
|
||||
font-weight: 500;
|
||||
letter-spacing: .04em;
|
||||
transition: color var(--t-fast) var(--ease-out), background-color var(--t-fast);
|
||||
@@ -570,11 +570,11 @@
|
||||
}
|
||||
.tabs__tab.is-active { color: var(--mint-400); }
|
||||
.tabs__tab.is-active::after { transform: scaleX(1); }
|
||||
.tabs__badge { font-family: var(--font-num); font-size: .13rem; padding: .01rem .06rem; background: var(--bg-3); color: var(--text-md); }
|
||||
.tabs--sm .tabs__tab { font-size: .15rem; padding: .07rem .16rem .09rem; }
|
||||
.tabs__badge { font-family: var(--font-num); font-size: calc(.13 * var(--t)); padding: .01rem .06rem; background: var(--bg-3); color: var(--text-md); }
|
||||
.tabs--sm .tabs__tab { font-size: calc(.15 * var(--t)); padding: .07rem .16rem .09rem; }
|
||||
|
||||
/* ---- spinner -------------------------------------------------------------------------------------- */
|
||||
.spinner { --sp: .36rem; --sc: var(--mint-500); display: inline-flex; align-items: center; gap: .1rem; color: var(--text-lo); font-size: .15rem; }
|
||||
.spinner { --sp: .36rem; --sc: var(--mint-500); display: inline-flex; align-items: center; gap: .1rem; color: var(--text-lo); font-size: calc(.15 * var(--t)); }
|
||||
.spinner svg { width: var(--sp); height: var(--sp); animation: spin 1.6s linear infinite; overflow: visible; }
|
||||
.spinner__track { fill: none; stroke: var(--line-2); stroke-width: 3; }
|
||||
.spinner__arc { fill: none; stroke: var(--sc); stroke-width: 3; stroke-dasharray: 34 98; animation: spinner-dash 1.1s var(--ease-in-out) infinite; filter: drop-shadow(0 0 3px var(--sc)); }
|
||||
@@ -582,7 +582,7 @@
|
||||
.spinner--lg { --sp: .56rem; }
|
||||
.spinner--gold { --sc: var(--gold); }
|
||||
.spinner--amber { --sc: var(--amber); }
|
||||
.spinner__label { font-family: var(--font-display); letter-spacing: .2em; font-size: .12rem; text-transform: uppercase; }
|
||||
.spinner__label { font-family: var(--font-display); letter-spacing: .2em; font-size: calc(.12 * var(--t)); text-transform: uppercase; }
|
||||
@keyframes spinner-dash { 0% { stroke-dashoffset: 0; } 100% { stroke-dashoffset: -132; } }
|
||||
|
||||
/* ---- avatar frame ---------------------------------------------------------------------------------- */
|
||||
@@ -710,9 +710,9 @@
|
||||
animation: pb-sweep 700ms 250ms var(--ease-out) forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
.pbanner__micro { font-family: var(--font-display); font-size: .12rem; letter-spacing: .5em; color: var(--pb); padding-left: .5em; }
|
||||
.pbanner__micro { font-family: var(--font-display); font-size: calc(.12 * var(--t)); letter-spacing: .5em; color: var(--pb); padding-left: .5em; }
|
||||
.pbanner__title {
|
||||
font-size: .56rem;
|
||||
font-size: calc(.56 * var(--t));
|
||||
font-weight: 900;
|
||||
letter-spacing: .16em;
|
||||
padding-left: .16em;
|
||||
@@ -720,7 +720,7 @@
|
||||
color: #fff;
|
||||
text-shadow: 0 0 .22rem var(--pb-glow), 0 2px 0 rgba(0, 0, 0, .5);
|
||||
}
|
||||
.pbanner__sub { font-size: .16rem; color: var(--text-lo); letter-spacing: .12em; }
|
||||
.pbanner__sub { font-size: calc(.16 * var(--t)); color: var(--text-lo); letter-spacing: .12em; }
|
||||
.pbanner__chev { --ch-h: .42rem; height: .42rem; flex: none; }
|
||||
.pbanner--inline .pbanner__band { animation-duration: var(--t-slow); }
|
||||
.pbanner.is-leaving .pbanner__band { animation: pb-close 240ms var(--ease-in-out) forwards; }
|
||||
@@ -783,7 +783,7 @@
|
||||
.rdialog__tick--br { bottom: 8px; right: 8px; border-left: 0; border-top: 0; }
|
||||
.rdialog__micro {
|
||||
font-family: var(--font-display);
|
||||
font-size: .13rem;
|
||||
font-size: calc(.13 * var(--t));
|
||||
letter-spacing: .48em;
|
||||
padding-left: .48em;
|
||||
color: var(--rd);
|
||||
@@ -811,7 +811,7 @@
|
||||
animation: rd-sweep 780ms 300ms var(--ease-out) forwards;
|
||||
}
|
||||
.rdialog__title {
|
||||
font-size: .62rem;
|
||||
font-size: calc(.62 * var(--t));
|
||||
font-weight: 900;
|
||||
letter-spacing: .14em;
|
||||
padding-left: .14em;
|
||||
@@ -821,7 +821,7 @@
|
||||
}
|
||||
.rdialog__chev { --ch-h: .46rem; height: .46rem; flex: none; }
|
||||
.rdialog__sub {
|
||||
font-size: .18rem;
|
||||
font-size: calc(.18 * var(--t));
|
||||
letter-spacing: .1em;
|
||||
color: var(--text-lo);
|
||||
text-align: center;
|
||||
@@ -834,7 +834,7 @@
|
||||
@keyframes rd-sweep { 0% { left: -25%; opacity: 1; } 100% { left: 115%; opacity: 0; } }
|
||||
@media (max-width: 720px) {
|
||||
.rdialog__box { min-width: 0; width: 92vw; padding: .26rem .3rem .22rem; }
|
||||
.rdialog__title { font-size: .42rem; }
|
||||
.rdialog__title { font-size: calc(.42 * var(--t)); }
|
||||
.rdialog__chev { --ch-h: .34rem; height: .34rem; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
@@ -854,29 +854,29 @@
|
||||
border: 1px solid var(--line);
|
||||
color: var(--pc);
|
||||
font-family: var(--font-num);
|
||||
font-size: .18rem;
|
||||
font-size: calc(.18 * var(--t));
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.ping__icon { width: .15rem; height: .15rem; margin-right: .03rem; }
|
||||
.ping__unit { font-size: .13rem; color: var(--text-lo); font-weight: 500; }
|
||||
.ping__unit { font-size: calc(.13 * var(--t)); color: var(--text-lo); font-weight: 500; }
|
||||
.ping--low { --pc: var(--mint-400); }
|
||||
.ping--medium { --pc: var(--amber); }
|
||||
.ping--high { --pc: var(--red-premium); }
|
||||
.ping--off { --pc: var(--text-dim); }
|
||||
|
||||
/* ---- difficulty tag ---------------------------------------------------------------------------------- */
|
||||
.dtag { --d-color: var(--amber); display: inline-flex; align-items: center; gap: .06rem; color: var(--d-color); font-weight: 700; font-size: .18rem; letter-spacing: .04em; white-space: nowrap; }
|
||||
.dtag { --d-color: var(--amber); display: inline-flex; align-items: center; gap: .06rem; color: var(--d-color); font-weight: 700; font-size: calc(.18 * var(--t)); letter-spacing: .04em; white-space: nowrap; }
|
||||
.dtag__icon { width: 1.15em; height: 1.15em; filter: drop-shadow(0 0 4px var(--d-color)); filter: drop-shadow(0 0 4px color-mix(in srgb, var(--d-color) 65%, transparent)); }
|
||||
.dicon { object-fit: contain; image-rendering: auto; }
|
||||
.dtag__code { font-family: var(--font-num); font-size: .72em; font-weight: 500; color: var(--text-dim); letter-spacing: .1em; margin-left: .03rem; }
|
||||
.dtag--sm { font-size: .15rem; }
|
||||
.dtag--lg { font-size: .24rem; }
|
||||
.dtag--sm { font-size: calc(.15 * var(--t)); }
|
||||
.dtag--lg { font-size: calc(.24 * var(--t)); }
|
||||
|
||||
/* ---- text field ------------------------------------------------------------------------------------ */
|
||||
.field { display: flex; flex-direction: column; gap: .07rem; min-width: 0; }
|
||||
.field__label { display: flex; align-items: baseline; gap: .1rem; font-size: .15rem; color: var(--text-lo); font-weight: 500; letter-spacing: .04em; }
|
||||
.field__label { display: flex; align-items: baseline; gap: .1rem; font-size: calc(.15 * var(--t)); color: var(--text-lo); font-weight: 500; letter-spacing: .04em; }
|
||||
.field__box {
|
||||
--bk-c: var(--line-3);
|
||||
--bk-l: .1rem;
|
||||
@@ -899,15 +899,15 @@
|
||||
border: 0;
|
||||
outline: none;
|
||||
color: var(--text-hi);
|
||||
font-size: .2rem;
|
||||
font-size: calc(.2 * var(--t));
|
||||
letter-spacing: .04em;
|
||||
}
|
||||
.field__input::placeholder { color: var(--text-dim); font-weight: 400; letter-spacing: .04em; }
|
||||
.field__icon { width: .22rem; height: .22rem; color: var(--text-dim); }
|
||||
.field__box:focus-within .field__icon { color: var(--mint-500); }
|
||||
.field__hint { font-size: .13rem; color: var(--text-dim); }
|
||||
.field__hint { font-size: calc(.13 * var(--t)); color: var(--text-dim); }
|
||||
.field--lg .field__box { height: .68rem; }
|
||||
.field--lg .field__input { font-size: .26rem; font-weight: 700; }
|
||||
.field--lg .field__input { font-size: calc(.26 * var(--t)); font-weight: 700; }
|
||||
.field--code .field__box { height: .64rem; }
|
||||
.field--code .field__input { font-family: var(--font-num); font-size: .34rem; font-weight: 700; letter-spacing: .42em; text-transform: uppercase; }
|
||||
.field--code .field__input::placeholder { font-family: var(--font-cjk); font-size: .18rem; letter-spacing: .06em; font-weight: 400; text-transform: none; }
|
||||
@@ -943,14 +943,14 @@
|
||||
border-left: 3px solid var(--tc);
|
||||
box-shadow: 0 .08rem .24rem rgba(0, 0, 0, .55), -.04rem 0 .16rem transparent; box-shadow: 0 .08rem .24rem rgba(0, 0, 0, .55), -.04rem 0 .16rem color-mix(in srgb, var(--tc) 25%, transparent);
|
||||
color: var(--text-hi);
|
||||
font-size: .17rem;
|
||||
font-size: calc(.17 * var(--t));
|
||||
line-height: 1.45;
|
||||
cursor: pointer;
|
||||
animation: toast-in var(--t-med) var(--ease-out);
|
||||
}
|
||||
.toast__icon { width: .2rem; height: .2rem; fill: var(--tc); flex: none; }
|
||||
.toast__text { flex: 1; }
|
||||
.toast__count { font-family: var(--font-num); font-size: .14rem; color: var(--text-lo); }
|
||||
.toast__count { font-family: var(--font-num); font-size: calc(.14 * var(--t)); color: var(--text-lo); }
|
||||
.toast--success { --tc: var(--mint-400); }
|
||||
.toast--warn { --tc: var(--amber); }
|
||||
.toast--error { --tc: var(--red-premium); }
|
||||
@@ -973,7 +973,7 @@
|
||||
border: 1px solid rgba(232, 90, 26, .55);
|
||||
box-shadow: 0 .08rem .3rem rgba(0, 0, 0, .6), 0 0 .2rem rgba(232, 90, 26, .15);
|
||||
color: var(--text-hi);
|
||||
font-size: .16rem;
|
||||
font-size: calc(.16 * var(--t));
|
||||
/* own keyframes: toast-in's transform would drop the translateX(-50%) centring mid-animation */
|
||||
animation: conn-in var(--t-med) var(--ease-out);
|
||||
}
|
||||
@@ -990,11 +990,11 @@
|
||||
.sp-in-match .conn-banner { top: calc(1.34rem + var(--sa-t, 0px)); bottom: auto; }
|
||||
.sp-in-match.sp-conn .toast-host { top: calc(2.0rem + var(--sa-t, 0px)); }
|
||||
.conn-banner .icon { width: .22rem; height: .22rem; color: var(--orange-hi); }
|
||||
.conn-banner__sub { color: var(--text-lo); font-family: var(--font-num); font-size: .15rem; }
|
||||
.conn-banner__sub { color: var(--text-lo); font-family: var(--font-num); font-size: calc(.15 * var(--t)); }
|
||||
|
||||
/* ---- misc shared bits ------------------------------------------------------------------------------ */
|
||||
.kv { display: flex; align-items: baseline; gap: .08rem; }
|
||||
.kv__k { color: var(--text-lo); font-size: .14rem; }
|
||||
.kv__k { color: var(--text-lo); font-size: calc(.14 * var(--t)); }
|
||||
.kv__v { font-family: var(--font-num); font-weight: 700; }
|
||||
.divider { height: 1px; background: linear-gradient(90deg, transparent, var(--line-2) 15%, var(--line-2) 85%, transparent); }
|
||||
.status-dot { width: .09rem; height: .09rem; border-radius: 50%; background: var(--text-dim); flex: none; display: inline-block; }
|
||||
@@ -1028,16 +1028,16 @@
|
||||
}
|
||||
.topbar__left { justify-self: start; display: flex; align-items: center; gap: .18rem; min-width: 0; }
|
||||
.topbar__center { display: flex; flex-direction: column; align-items: center; gap: .03rem; text-align: center; }
|
||||
.topbar__title { display: flex; align-items: center; gap: .18rem; font-size: .3rem; font-weight: 700; letter-spacing: .1em; line-height: 1.2; }
|
||||
.topbar__title { display: flex; align-items: center; gap: .18rem; font-size: calc(.3 * var(--t)); font-weight: 700; letter-spacing: .1em; line-height: 1.2; }
|
||||
.topbar__sep { width: 1px; height: .28rem; background: var(--line-2); }
|
||||
.topbar__right { justify-self: end; display: flex; align-items: center; gap: .16rem; min-width: 0; }
|
||||
|
||||
.section-label { display: flex; align-items: baseline; gap: .12rem; margin: 0 0 .14rem; font-size: .2rem; font-weight: 700; letter-spacing: .08em; }
|
||||
.section-label__idx { font-size: .15rem; color: var(--mint-500); padding: 0 .06rem; border: 1px solid var(--mint-800); line-height: 1.35; letter-spacing: .06em; }
|
||||
.section-label { display: flex; align-items: baseline; gap: .12rem; margin: 0 0 .14rem; font-size: calc(.2 * var(--t)); font-weight: 700; letter-spacing: .08em; }
|
||||
.section-label__idx { font-size: calc(.15 * var(--t)); color: var(--mint-500); padding: 0 .06rem; border: 1px solid var(--mint-800); line-height: 1.35; letter-spacing: .06em; }
|
||||
|
||||
.me-chip { display: flex; align-items: center; gap: .12rem; padding: .06rem .18rem .06rem .06rem; border: 1px solid var(--line); background: rgba(0, 0, 0, .35); }
|
||||
.me-chip__text { display: flex; flex-direction: column; line-height: 1.3; min-width: 0; }
|
||||
.me-chip__name { font-weight: 700; font-size: .18rem; max-width: 2.4rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.me-chip__name { font-weight: 700; font-size: calc(.18 * var(--t)); max-width: 2.4rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
|
||||
.crash { align-items: center; justify-content: center; }
|
||||
.crash__box {
|
||||
@@ -1051,4 +1051,4 @@
|
||||
border: 1px solid var(--line-2);
|
||||
--bk-c: var(--red-premium);
|
||||
}
|
||||
.crash__box h2 { font-size: .3rem; }
|
||||
.crash__box h2 { font-size: calc(.3 * var(--t)); }
|
||||
@@ -146,10 +146,13 @@ html:not(.sp-in-match) .toast-host { margin-top: var(--sa-t); }
|
||||
.mode-cards { margin-bottom: .2rem; }
|
||||
.mode-card { min-height: 0; gap: .12rem; padding: .22rem .26rem .2rem; }
|
||||
.mode-card__icon { width: .66rem; height: .66rem; }
|
||||
.mode-card__name { font-size: .4rem; }
|
||||
.mode-card__name { font-size: calc(.4 * var(--t)); }
|
||||
.mode-card__points, .diff-card__effects, .tips, .create-box__hint { display: none; }
|
||||
.diff-list { gap: .1rem; }
|
||||
.diff-card { padding-top: .12rem; padding-bottom: .12rem; }
|
||||
.create-box { margin-top: .18rem; }
|
||||
.create-box .btn--xl { height: .8rem; }
|
||||
}
|
||||
|
||||
.rotate-hint__pwa { pointer-events: auto; margin-top: 6px; }
|
||||
.rotate-hint__pwa[hidden] { display: none; }
|
||||
@@ -23,18 +23,19 @@
|
||||
.stephead__left { display: flex; align-items: center; gap: .16rem; }
|
||||
.stephead__meta { display: flex; flex-direction: column; gap: .06rem; align-items: flex-start; }
|
||||
.stephead__center { display: flex; flex-direction: column; align-items: center; gap: .04rem; }
|
||||
.stephead__title { display: flex; align-items: baseline; gap: .02rem; font-size: .34rem; font-weight: 900; letter-spacing: .12em; }
|
||||
.stephead__title b { font-size: .56rem; line-height: 1; color: var(--mint-400); text-shadow: 0 0 .2rem rgba(23, 249, 183, .4); }
|
||||
.stephead__of { font-size: .26rem; color: var(--text-dim); margin-right: .18rem; }
|
||||
.stephead__title { display: flex; align-items: baseline; gap: .02rem; font-size: calc(.34 * var(--t)); font-weight: 900; letter-spacing: .12em; }
|
||||
.stephead__title b { font-size: calc(.56 * var(--t)); line-height: 1; color: var(--mint-400); text-shadow: 0 0 .2rem rgba(23, 249, 183, .4); }
|
||||
.stephead__of { font-size: calc(.26 * var(--t)); color: var(--text-dim); margin-right: .18rem; }
|
||||
.stephead__right { justify-self: end; display: flex; align-items: center; gap: .24rem; }
|
||||
.stephead__right .countdown { --cd-h: .64rem; }
|
||||
|
||||
.brief-h { display: flex; align-items: baseline; gap: .12rem; font-size: .21rem; font-weight: 700; letter-spacing: .1em; margin-bottom: .12rem; }
|
||||
.brief-h { display: flex; align-items: baseline; gap: .12rem; font-size: calc(.21 * var(--t)); font-weight: 700; letter-spacing: .1em; margin-bottom: .12rem; }
|
||||
.brief-h::before { content: ''; width: .06rem; height: .2rem; background: var(--mint-500); align-self: center; }
|
||||
|
||||
/* ---- briefing layout ---- */
|
||||
.brief__main { position: relative; z-index: 1; flex: 1; min-height: 0; display: grid; grid-template-columns: 6.2rem 1fr; gap: .36rem; padding: .3rem .5rem .1rem; }
|
||||
.brief__left { display: flex; flex-direction: column; gap: .2rem; min-height: 0; }
|
||||
.brief__left { display: flex; flex-direction: column; gap: .2rem; min-height: 0; overflow: auto; padding-right: .06rem; }
|
||||
.brief__left > * { flex-shrink: 0; }
|
||||
.brief__right { display: flex; flex-direction: column; gap: .2rem; min-height: 0; overflow: auto; padding-right: .06rem; }
|
||||
|
||||
.brief-boss {
|
||||
@@ -54,14 +55,14 @@
|
||||
background: repeating-linear-gradient(0deg, rgba(0, 0, 0, .22) 0 2px, transparent 2px 4px), radial-gradient(circle, transparent 45%, rgba(40, 8, 2, .75) 100%); }
|
||||
.brief-boss__q { font-size: 1rem; font-weight: 900; color: rgba(255, 110, 40, .6); }
|
||||
.brief-boss__text { display: flex; flex-direction: column; gap: .08rem; padding-top: .2rem; min-width: 0; }
|
||||
.brief-boss__name { font-size: .34rem; font-weight: 900; letter-spacing: .04em; line-height: 1.25; }
|
||||
.brief-boss__when { font-size: .15rem; color: var(--orange-hi); }
|
||||
.brief-boss__abil { margin: .04rem 0 0; padding-left: .18rem; display: flex; flex-direction: column; gap: .04rem; font-size: .14rem; line-height: 1.6; color: var(--text-md); }
|
||||
.brief-boss__name { font-size: calc(.34 * var(--t)); font-weight: 900; letter-spacing: .04em; line-height: 1.25; }
|
||||
.brief-boss__when { font-size: calc(.15 * var(--t)); color: var(--orange-hi); }
|
||||
.brief-boss__abil { margin: .04rem 0 0; padding-left: .18rem; display: flex; flex-direction: column; gap: .04rem; font-size: calc(.14 * var(--t)); line-height: 1.6; color: var(--text-md); }
|
||||
|
||||
.brief-stage { display: flex; align-items: center; justify-content: space-between; gap: .2rem; padding: .12rem .18rem; background: rgba(12, 15, 14, .8); border: 1px solid var(--line); }
|
||||
.brief-stage__k { display: flex; align-items: baseline; gap: .12rem; min-width: 0; }
|
||||
.brief-stage__pool { font-size: .14rem; color: var(--text-lo); white-space: nowrap; }
|
||||
.brief-stage__name { display: flex; align-items: center; gap: .08rem; font-size: .19rem; font-weight: 700; }
|
||||
.brief-stage__pool { font-size: calc(.14 * var(--t)); color: var(--text-lo); white-space: nowrap; }
|
||||
.brief-stage__name { display: flex; align-items: center; gap: .08rem; font-size: calc(.19 * var(--t)); font-weight: 700; }
|
||||
.brief-stage__name .icon { width: .2rem; height: .2rem; color: var(--mint-500); }
|
||||
|
||||
.brief-factions { display: flex; flex-direction: column; gap: .1rem; min-height: 0; }
|
||||
@@ -69,38 +70,57 @@
|
||||
.brief-faction__icon { width: .72rem; height: .72rem; flex: none; display: grid; place-items: center; background: radial-gradient(circle, rgba(246, 163, 41, .16), transparent 70%); }
|
||||
.brief-faction__icon img { width: 100%; height: 100%; object-fit: contain; }
|
||||
.brief-faction__text { display: flex; flex-direction: column; gap: .03rem; }
|
||||
.brief-faction__text b { font-size: .2rem; color: var(--amber); }
|
||||
.brief-faction__text span { font-size: .15rem; color: var(--text-lo); }
|
||||
.brief-faction__text b { font-size: calc(.2 * var(--t)); color: var(--amber); }
|
||||
.brief-faction__text span { font-size: calc(.15 * var(--t)); color: var(--text-lo); }
|
||||
|
||||
.brief-bonds { padding: .16rem .2rem .2rem; background: rgba(12, 15, 14, .8); border: 1px solid var(--line); }
|
||||
.brief-bonds__row { display: flex; flex-wrap: wrap; gap: .16rem .1rem; }
|
||||
.brief-bond { position: relative; width: 1rem; display: flex; justify-content: center; }
|
||||
.brief-bond .bond { --disc: .66rem; }
|
||||
.brief-bond .bond__name { font-size: .16rem; }
|
||||
.brief-bond__ban { position: absolute; top: -.04rem; right: .08rem; display: inline-flex; align-items: center; gap: .01rem; font-size: .12rem; font-weight: 700; padding: 0 .05rem; background: var(--red); color: #fff; line-height: 1.5; box-shadow: 0 0 0 1px #000; }
|
||||
.brief-bond { position: relative; min-width: 1rem; width: max-content; max-width: 100%; display: flex; justify-content: center; }
|
||||
.brief-bond .bond { --disc: .66rem; max-width: 100%; }
|
||||
.brief-bond .bond__name { font-size: calc(.16 * var(--t)); max-width: 100%; white-space: normal; overflow-wrap: anywhere; text-align: center; }
|
||||
.brief-bond__ban { position: absolute; top: -.04rem; right: .08rem; display: inline-flex; align-items: center; gap: .01rem; font-size: calc(.12 * var(--t)); font-weight: 700; padding: 0 .05rem; background: var(--red); color: #fff; line-height: 1.5; box-shadow: 0 0 0 1px #000; }
|
||||
.brief-bond__ban .icon { width: .12rem; height: .12rem; }
|
||||
.brief-bond.is-partial .bond__core { box-shadow: 0 0 0 2px rgba(255, 84, 84, .5); }
|
||||
.brief-legend { display: flex; align-items: center; gap: .08rem; flex-wrap: wrap; font-size: .14rem; color: var(--text-dim); margin-top: -.06rem; }
|
||||
.brief-legend { display: flex; align-items: center; gap: .08rem; flex-wrap: wrap; font-size: calc(.14 * var(--t)); color: var(--text-dim); margin-top: -.06rem; }
|
||||
.brief-legend__off { width: .16rem; height: .16rem; border-radius: 50%; background: #3a403d; border: 2px solid #555; }
|
||||
.brief-legend__ban { display: inline-grid; place-items: center; width: .2rem; height: .18rem; background: var(--red); color: #fff; }
|
||||
.brief-legend__ban .icon { width: .12rem; height: .12rem; }
|
||||
|
||||
.brief-banned { padding: .16rem .2rem .2rem; background: rgba(12, 15, 14, .8); border: 1px solid var(--line); }
|
||||
.brief-banned__n { margin-left: auto; font-size: .22rem; color: var(--red-premium); }
|
||||
.brief-banned__n { margin-left: auto; font-size: calc(.22 * var(--t)); color: var(--red-premium); }
|
||||
.brief-banned__grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(.6rem, 1fr)); gap: .08rem; }
|
||||
.brief-banned__grid .uthumb { --ut: .6rem; }
|
||||
/* 0.2.0: the player's own 自选 pieces out of the shop this match (ui/matchInfo.js DiyBannedLine; m.private.diyBanned) */
|
||||
.brief-banned__diy { margin: .1rem 0 0; font-size: .14rem; line-height: 1.5; color: var(--text-dim, #b8c2bf); }
|
||||
.diybanned__tag { display: inline-block; margin-right: .06rem; padding: .01rem .06rem; font-size: .12rem; font-weight: 700;
|
||||
.brief-banned__diy { margin: .1rem 0 0; font-size: calc(.14 * var(--t)); line-height: 1.5; color: var(--text-dim, #b8c2bf); }
|
||||
.diybanned__tag { display: inline-block; margin-right: .06rem; padding: .01rem .06rem; font-size: calc(.12 * var(--t)); font-weight: 700;
|
||||
letter-spacing: .08em; background: var(--mint-500); color: var(--text-on-mint); vertical-align: .01rem; }
|
||||
|
||||
.brief__foot { position: relative; z-index: 2; flex: none; display: flex; align-items: center; justify-content: flex-end; gap: .4rem; padding: .18rem .5rem .3rem; }
|
||||
.brief-ready { display: flex; align-items: center; gap: .16rem; }
|
||||
.brief-ready__txt { font-size: .2rem; color: var(--text-lo); }
|
||||
.brief-ready__txt b { font-size: .4rem; color: var(--mint-400); margin: 0 .02rem; }
|
||||
.brief-ready__txt span { font-size: .24rem; color: var(--text-dim); }
|
||||
.brief-ready__txt { font-size: calc(.2 * var(--t)); color: var(--text-lo); }
|
||||
.brief-ready__txt b { font-size: calc(.4 * var(--t)); color: var(--mint-400); margin: 0 .02rem; }
|
||||
.brief-ready__txt span { font-size: calc(.24 * var(--t)); color: var(--text-dim); }
|
||||
.brief-ready__pips { display: flex; gap: .06rem; }
|
||||
.brief-ready__pips i { width: .3rem; height: .3rem; display: grid; place-items: center; color: var(--text-dim); }
|
||||
.brief-ready__pips i .icon { width: 100%; height: 100%; }
|
||||
.brief-ready__pips i.on { color: var(--mint-400); filter: drop-shadow(0 0 .06rem rgba(23, 249, 183, .6)); }
|
||||
.brief-ready__pips i.me { border-bottom: 2px solid var(--mint-700); }
|
||||
|
||||
/* Full setup reroll: consent stays visible alongside the information being confirmed. */
|
||||
.brief-reroll {
|
||||
position: relative; z-index: 2; flex: none;
|
||||
display: flex; align-items: center; justify-content: space-between; gap: .2rem;
|
||||
margin: .1rem .5rem 0; padding: .14rem .2rem;
|
||||
background: rgba(12, 15, 14, .9); border: 1px solid var(--mint-700);
|
||||
}
|
||||
.brief-reroll__text { display: flex; flex-direction: column; gap: .05rem; min-width: 0; font-size: calc(.16 * var(--t)); }
|
||||
.brief-reroll__text strong { font-size: calc(.2 * var(--t)); color: var(--mint-400); }
|
||||
.brief-reroll__text > span { color: var(--text-lo); }
|
||||
.brief-reroll__players { display: flex; flex-wrap: wrap; gap: .08rem .2rem; color: var(--text-dim); }
|
||||
.brief-reroll__players .is-agreed { color: var(--mint-400); }
|
||||
.brief-reroll__actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: .1rem; flex-shrink: 0; }
|
||||
@media (max-width: 900px) {
|
||||
.brief-reroll { margin-inline: .3rem; gap: .1rem; padding: .1rem .14rem; }
|
||||
.brief-reroll__actions { flex-shrink: 1; }
|
||||
.brief-reroll__actions .btn { min-height: .44rem; padding-inline: .12rem; }
|
||||
}
|
||||
@@ -13,11 +13,11 @@
|
||||
transition: border-color var(--t-med), background var(--t-med);
|
||||
}
|
||||
.dorder .pavatar { --pa: .66rem; }
|
||||
.dorder__idx { font-size: .22rem; color: var(--text-dim); width: .26rem; text-align: center; }
|
||||
.dorder__idx { font-size: calc(.22 * var(--t)); color: var(--text-dim); width: .26rem; text-align: center; }
|
||||
.dorder__text { display: flex; flex-direction: column; gap: .02rem; min-width: 0; }
|
||||
.dorder__name { font-size: .18rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: flex; align-items: center; gap: .06rem; }
|
||||
.dorder__ai { font-family: var(--font-num); font-size: .11rem; padding: 0 .04rem; background: #a9c3d8; color: #0b1520; }
|
||||
.dorder__state { font-size: .14rem; }
|
||||
.dorder__name { font-size: calc(.18 * var(--t)); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: flex; align-items: center; gap: .06rem; }
|
||||
.dorder__ai { font-family: var(--font-num); font-size: calc(.11 * var(--t)); padding: 0 .04rem; background: #a9c3d8; color: #0b1520; }
|
||||
.dorder__state { font-size: calc(.14 * var(--t)); }
|
||||
.dorder__state span { display: inline-flex; align-items: center; gap: .04rem; }
|
||||
.dorder__state .icon { width: .16rem; height: .16rem; }
|
||||
.dorder__box { position: relative; width: .6rem; height: .6rem; display: grid; place-items: center; border: 1px dashed var(--line-2); }
|
||||
@@ -28,7 +28,7 @@
|
||||
.dorder.is-cur { border-color: var(--gold); background: linear-gradient(90deg, rgba(255, 198, 0, .12), rgba(12, 15, 14, .82)); box-shadow: 0 0 .2rem rgba(255, 198, 0, .15); }
|
||||
.dorder.is-done { border-color: var(--mint-800); }
|
||||
.dorder.is-self .dorder__name { color: var(--mint-400); }
|
||||
.draft-order__tip { margin-top: auto; font-size: .13rem; line-height: 1.6; color: var(--text-dim); }
|
||||
.draft-order__tip { margin-top: auto; font-size: calc(.13 * var(--t)); line-height: 1.6; color: var(--text-dim); }
|
||||
|
||||
.draft-grid {
|
||||
display: grid; grid-template-columns: repeat(auto-fill, minmax(1.36rem, 1fr)); grid-auto-rows: min-content; gap: .12rem;
|
||||
@@ -41,13 +41,13 @@
|
||||
}
|
||||
.dband:hover { transform: translateY(-.03rem); border-color: var(--line-3); }
|
||||
.dband .bandicon { --bi: 1.1rem; border: 1px solid var(--line-2); }
|
||||
.dband__name { font-size: .16rem; font-weight: 700; color: var(--text-hi); white-space: nowrap; }
|
||||
.dband__lp { display: inline-flex; align-items: center; gap: .04rem; font-size: .15rem; color: var(--mint-400); }
|
||||
.dband__name { font-size: calc(.16 * var(--t)); font-weight: 700; color: var(--text-hi); white-space: nowrap; }
|
||||
.dband__lp { display: inline-flex; align-items: center; gap: .04rem; font-size: calc(.15 * var(--t)); color: var(--mint-400); }
|
||||
.dband__lp i { width: .12rem; height: .14rem; background: var(--mint-500); clip-path: polygon(0 0, 30% 0, 30% 20%, 45% 20%, 45% 0, 55% 0, 55% 20%, 70% 20%, 70% 0, 100% 0, 100% 35%, 85% 50%, 85% 80%, 100% 90%, 100% 100%, 0 100%, 0 90%, 15% 80%, 15% 50%, 0 35%); }
|
||||
.dband__who { position: absolute; right: .04rem; top: .04rem; display: flex; flex-direction: column; gap: .02rem; }
|
||||
.dband__who .pavatar { --pa: .3rem; border-radius: 50%; overflow: hidden; border: 2px solid var(--mint-400); }
|
||||
.dband.is-sel { border-color: var(--mint-400); background: linear-gradient(180deg, rgba(78, 216, 175, .18), rgba(12, 15, 14, .85)); box-shadow: 0 0 0 1px var(--mint-400), 0 0 .24rem rgba(23, 249, 183, .2); }
|
||||
.dband.is-mine::after { content: '已选择'; position: absolute; left: 0; top: 0; font-size: .11rem; font-weight: 700; padding: 0 .06rem; background: var(--mint-500); color: var(--text-on-mint); }
|
||||
.dband.is-mine::after { content: '已选择'; position: absolute; left: 0; top: 0; font-size: calc(.11 * var(--t)); font-weight: 700; padding: 0 .06rem; background: var(--mint-500); color: var(--text-on-mint); }
|
||||
|
||||
.draft-detail {
|
||||
display: flex; flex-direction: column; align-items: center; gap: .12rem; padding: .26rem .26rem .22rem; min-height: 0;
|
||||
@@ -55,33 +55,33 @@
|
||||
}
|
||||
.draft-detail__art { position: relative; padding: .06rem; border: 1px solid var(--mint-800); background: radial-gradient(circle, rgba(78, 216, 175, .14), transparent 70%); }
|
||||
.draft-detail__art .bandicon { --bi: 2rem; }
|
||||
.draft-detail__hp { display: flex; align-items: center; gap: .12rem; font-size: .16rem; color: var(--text-lo); }
|
||||
.draft-detail__name { font-size: .42rem; font-weight: 900; letter-spacing: .1em; }
|
||||
.draft-detail__hp { display: flex; align-items: center; gap: .12rem; font-size: calc(.16 * var(--t)); color: var(--text-lo); }
|
||||
.draft-detail__name { font-size: calc(.42 * var(--t)); font-weight: 900; letter-spacing: .1em; }
|
||||
.draft-detail__eff { align-self: stretch; display: flex; flex-direction: column; gap: .06rem; padding: .12rem .14rem; background: rgba(0, 0, 0, .3); border-left: 3px solid var(--mint-500); flex: 1; min-height: 0; overflow: auto; }
|
||||
.draft-detail__eff b { font-size: .2rem; color: var(--mint-400); }
|
||||
.draft-detail__desc { font-size: .16rem; line-height: 1.7; color: var(--text-md); }
|
||||
.draft-detail__eff b { font-size: calc(.2 * var(--t)); color: var(--mint-400); }
|
||||
.draft-detail__desc { font-size: calc(.16 * var(--t)); line-height: 1.7; color: var(--text-md); }
|
||||
.draft-detail__actions { align-self: stretch; display: flex; flex-direction: column; gap: .1rem; }
|
||||
.draft-detail__status { display: flex; align-items: center; gap: .06rem; font-size: .15rem; color: var(--text-lo); }
|
||||
.draft-detail__status { display: flex; align-items: center; gap: .06rem; font-size: calc(.15 * var(--t)); color: var(--text-lo); }
|
||||
.draft-detail__status .icon { width: .18rem; height: .18rem; }
|
||||
.draft-detail__btns { display: flex; gap: .1rem; }
|
||||
.draft-detail__btns .btn { flex: 1; }
|
||||
.dorder__secs { margin-left: .06rem; font-size: .15rem; color: var(--gold); }
|
||||
.dorder__secs { margin-left: .06rem; font-size: calc(.15 * var(--t)); color: var(--gold); }
|
||||
/* 队友已选 (research 09 §5): a strategy a teammate picked cannot be chosen again */
|
||||
.dband.is-taken { cursor: not-allowed; border-color: rgba(246, 163, 41, .35); }
|
||||
.dband.is-taken .bandicon, .dband.is-taken .dband__name, .dband.is-taken .dband__lp { filter: grayscale(.8) brightness(.62); }
|
||||
.dband.is-taken:hover { transform: none; }
|
||||
.dband__taken {
|
||||
position: absolute; left: 50%; top: 42%; transform: translate(-50%, -50%) rotate(-8deg); white-space: nowrap; pointer-events: none;
|
||||
padding: .02rem .12rem; font-size: .15rem; font-weight: 900; letter-spacing: .16em; color: #1d1400; background: var(--amber);
|
||||
padding: .02rem .12rem; font-size: calc(.15 * var(--t)); font-weight: 900; letter-spacing: .16em; color: #1d1400; background: var(--amber);
|
||||
box-shadow: 0 .03rem .1rem rgba(0, 0, 0, .6);
|
||||
}
|
||||
.draft-detail__status--taken { color: var(--amber); }
|
||||
/* a strategy built around a bond this mode switches off (bandDraft.js BandOffTag / BandOffNote, DESIGN §21.26 — the
|
||||
§21.7 本局禁用 look: dim, dashed, the bond struck through): information only, it stays selectable. Px floors keep it
|
||||
readable on phones (1rem = 40 px). */
|
||||
.dband__off { font-size: max(.12rem, 8px); line-height: 1.5; padding: 0 .05rem; color: var(--text-dim); border: 1px dashed rgba(255, 255, 255, .28); white-space: nowrap; }
|
||||
.dband__off { font-size: max(calc(.12 * var(--t)), 8px); line-height: 1.5; padding: 0 .05rem; color: var(--text-dim); border: 1px dashed rgba(255, 255, 255, .28); white-space: nowrap; }
|
||||
.draft-detail__off { align-self: stretch; display: flex; align-items: flex-start; gap: .06rem; margin: 0; padding: .06rem .1rem;
|
||||
font-size: max(.14rem, 9px); line-height: 1.5; color: var(--text-lo); background: rgba(255, 255, 255, .03); border-left: 2px solid rgba(255, 255, 255, .28); }
|
||||
font-size: max(calc(.14 * var(--t)), 9px); line-height: 1.5; color: var(--text-lo); background: rgba(255, 255, 255, .03); border-left: 2px solid rgba(255, 255, 255, .28); }
|
||||
.draft-detail__off .icon { width: max(.16rem, 10px); height: max(.16rem, 10px); flex: none; margin-top: .02rem; }
|
||||
.draft-detail__offname { color: var(--text-dim); text-decoration: line-through; text-decoration-color: rgba(255, 84, 84, .6); }
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
footer (the turn's countdown, 关闭) stay in view. */
|
||||
.draft-order__info {
|
||||
margin-top: .04rem; justify-content: flex-start; padding: 0 .16rem;
|
||||
height: max(.5rem, 28px); font-size: max(.18rem, 10px);
|
||||
height: max(.5rem, 28px); font-size: max(calc(.18 * var(--t)), 10px);
|
||||
--btn-bd: var(--mint-800); --btn-bg: rgba(78, 216, 175, .06); --btn-bg-hover: rgba(78, 216, 175, .14);
|
||||
}
|
||||
.draft-order__info .btn__icon { color: var(--mint-400); }
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
.bglyph { display: inline-grid; place-items: center; width: .24rem; height: .24rem; flex: none; }
|
||||
.bglyph img { width: 100%; height: 100%; object-fit: contain; filter: brightness(0) invert(.86) sepia(.4) hue-rotate(110deg) saturate(2.5); }
|
||||
.bglyph__txt { font-weight: 900; font-size: .15rem; color: var(--mint-400); }
|
||||
.bglyph__txt { font-weight: 900; font-size: calc(.15 * var(--t)); color: var(--mint-400); }
|
||||
.bandicon { --bi: .6rem; display: inline-grid; place-items: center; width: var(--bi); height: var(--bi); flex: none; overflow: hidden; background: #111513; }
|
||||
.bandicon img { width: 100%; height: 100%; object-fit: cover; }
|
||||
.bandicon__txt { font-weight: 900; font-size: calc(var(--bi) * .44); color: var(--mint-400); }
|
||||
@@ -83,23 +83,23 @@
|
||||
.dhead__icon--enemy.is-elite { border-color: var(--gold-2); }
|
||||
.dhead__info { display: flex; flex-direction: column; gap: .05rem; min-width: 0; padding-top: .02rem; flex: 1; }
|
||||
.dhead__chips { display: flex; align-items: center; gap: .08rem; flex-wrap: wrap; }
|
||||
.dhead__name { font-size: .32rem; font-weight: 900; letter-spacing: .04em; line-height: 1.2; }
|
||||
.dhead__en { font-family: var(--font-display); font-size: .12rem; letter-spacing: .2em; color: var(--text-dim); text-transform: uppercase; }
|
||||
.dhead__name { font-size: calc(.32 * var(--t)); font-weight: 900; letter-spacing: .04em; line-height: 1.2; }
|
||||
.dhead__en { font-family: var(--font-display); font-size: calc(.12 * var(--t)); letter-spacing: .2em; color: var(--text-dim); text-transform: uppercase; }
|
||||
/* 0.2.0 补位: the replaced operator under a stand-in's name (「银灰的替补」, ui/detailPanel.js ChessDetail) */
|
||||
.dhead__for { display: block; font-size: max(.13rem, 9px); letter-spacing: .04em; color: var(--ice); }
|
||||
.dhead__flavor { font-size: .13rem; color: var(--text-lo); font-style: italic; }
|
||||
.dhead__class { display: flex; align-items: center; gap: .06rem; flex-wrap: wrap; font-size: .15rem; color: var(--text-md); margin-top: .06rem; }
|
||||
.dhead__for { display: block; font-size: max(calc(.13 * var(--t)), 9px); letter-spacing: .04em; color: var(--ice); }
|
||||
.dhead__flavor { font-size: calc(.13 * var(--t)); color: var(--text-lo); font-style: italic; }
|
||||
.dhead__class { display: flex; align-items: center; gap: .06rem; flex-wrap: wrap; font-size: calc(.15 * var(--t)); color: var(--text-md); margin-top: .06rem; }
|
||||
.dhead__class .sep { width: 1px; height: .16rem; background: var(--line-2); }
|
||||
.dhead__prof, .dhead__sub { width: .24rem; height: .24rem; object-fit: contain; }
|
||||
.dhead__pos { font-size: .12rem; padding: 0 .06rem; border: 1px solid var(--line-2); color: var(--text-lo); }
|
||||
.dtag-elite, .dtag-token, .dtag-kind { font-size: .13rem; font-weight: 700; padding: .01rem .08rem; letter-spacing: .08em; }
|
||||
.dhead__pos { font-size: calc(.12 * var(--t)); padding: 0 .06rem; border: 1px solid var(--line-2); color: var(--text-lo); }
|
||||
.dtag-elite, .dtag-token, .dtag-kind { font-size: calc(.13 * var(--t)); font-weight: 700; padding: .01rem .08rem; letter-spacing: .08em; }
|
||||
.dtag-elite { background: var(--gold); color: #1d1400; }
|
||||
.dtag-token { background: #a9c3d8; color: #0b1520; }
|
||||
/* 0.2.0 自选编队 / 补位: the card of a 自选 piece, of a chess fielded as its stand-in (ui/detailPanel.js ChessDetail) */
|
||||
.dtag-diy { font-size: .13rem; font-weight: 700; padding: .01rem .08rem; letter-spacing: .08em; background: var(--mint-500); color: var(--text-on-mint); }
|
||||
.dtag-standin { font-size: .13rem; font-weight: 700; padding: .01rem .08rem; letter-spacing: .08em; background: var(--ice); color: var(--ice-900); }
|
||||
.dtag-diy { font-size: calc(.13 * var(--t)); font-weight: 700; padding: .01rem .08rem; letter-spacing: .08em; background: var(--mint-500); color: var(--text-on-mint); }
|
||||
.dtag-standin { font-size: calc(.13 * var(--t)); font-weight: 700; padding: .01rem .08rem; letter-spacing: .08em; background: var(--ice); color: var(--ice-900); }
|
||||
.dtag-kind { border: 1px solid var(--line-2); color: var(--text-lo); font-weight: 500; }
|
||||
.drank { font-size: .13rem; font-weight: 700; padding: .01rem .08rem; }
|
||||
.drank { font-size: calc(.13 * var(--t)); font-weight: 700; padding: .01rem .08rem; }
|
||||
.drank--normal { background: var(--bg-4); color: var(--text-md); }
|
||||
.drank--elite { background: var(--gold-2); color: #1d1400; }
|
||||
.drank--boss { background: var(--orange); color: #fff; }
|
||||
@@ -107,7 +107,7 @@
|
||||
.dhp i { position: absolute; inset: 0 auto 0 0; background: linear-gradient(90deg, #2bb65a, #56e08a); }
|
||||
.dhp--enemy i { background: linear-gradient(90deg, #b5261a, #ff5a4a); }
|
||||
.dhp span { position: absolute; inset: 0; display: grid; place-items: center; font-size: .12rem; color: #fff; text-shadow: 0 1px 2px #000; }
|
||||
.dtrait { display: flex; gap: .08rem; align-items: flex-start; font-size: .14rem; line-height: 1.6; color: var(--text-md); padding: .08rem .1rem; background: rgba(255, 255, 255, .03); border-left: 2px solid var(--line-3); }
|
||||
.dtrait { display: flex; gap: .08rem; align-items: flex-start; font-size: calc(.14 * var(--t)); line-height: 1.6; color: var(--text-md); padding: .08rem .1rem; background: rgba(255, 255, 255, .03); border-left: 2px solid var(--line-3); }
|
||||
.dtrait .icon { width: .16rem; height: .16rem; margin-top: .03rem; color: var(--text-lo); flex: none; }
|
||||
.dstats-wrap { display: flex; gap: .12rem; align-items: stretch; }
|
||||
/* compact 4 × 2 grid (label over value): the skill stays close under the 特质 block (user playtest #3 item 8) */
|
||||
@@ -115,10 +115,10 @@
|
||||
.dstats { flex: none; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1px; background: var(--line); border: 1px solid var(--line); }
|
||||
.dstats-wrap > .dstats { flex: 1; }
|
||||
.dstat { display: flex; flex-direction: column; justify-content: center; gap: .01rem; min-width: 0; padding: .04rem .07rem; background: #0f1311; }
|
||||
.dstat__k { font-size: .12rem; color: var(--text-lo); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.dstat__k { font-size: calc(.12 * var(--t)); color: var(--text-lo); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.dstat__row { display: flex; align-items: baseline; min-width: 0; white-space: nowrap; }
|
||||
.dstat__v { font-size: .17rem; line-height: 1.15; color: #fff; }
|
||||
.dstat small { font-size: .11rem; color: var(--text-dim); margin-left: .04rem; }
|
||||
.dstat__v { font-size: calc(.17 * var(--t)); line-height: 1.15; color: #fff; }
|
||||
.dstat small { font-size: calc(.11 * var(--t)); color: var(--text-dim); margin-left: .04rem; }
|
||||
/* live stats (user playtest #4 item 7): buffed values green, debuffed red (the official card), the difference beside
|
||||
them; a 实时 (battle) / 开战时 (prep preview) tag over the grid's right edge */
|
||||
.dstats.is-live, .dstats-wrap.is-live { margin-top: .14rem; }
|
||||
@@ -128,7 +128,7 @@
|
||||
.dstat.is-down .dstat__v { color: var(--red-premium); text-shadow: 0 0 .08rem rgba(255, 84, 84, .3); }
|
||||
.dstat.is-down small { color: #ff8a80; }
|
||||
.dstats__tag {
|
||||
position: absolute; right: -1px; bottom: 100%; padding: .01rem .08rem; font-size: .11rem; font-weight: 700; letter-spacing: .1em;
|
||||
position: absolute; right: -1px; bottom: 100%; padding: .01rem .08rem; font-size: calc(.11 * var(--t)); font-weight: 700; letter-spacing: .1em;
|
||||
color: var(--text-on-mint); background: var(--mint-500); pointer-events: auto; cursor: help;
|
||||
}
|
||||
.dstats__tag.is-battle { background: var(--orange); color: #1d0c00; }
|
||||
@@ -137,47 +137,47 @@
|
||||
.rgrid i { width: var(--rg); height: var(--rg); border: 1px solid rgba(255, 255, 255, .06); }
|
||||
.rgrid i.on { background: rgba(246, 163, 41, .25); border-color: var(--amber); }
|
||||
.rgrid i.self { background: var(--mint-500); border-color: var(--mint-400); }
|
||||
.rgrid-all { font-size: .16rem; font-weight: 700; color: var(--amber); padding: .02rem .06rem; border: 1px solid var(--amber); background: rgba(246, 163, 41, .25); }
|
||||
.rgrid-all { font-size: calc(.16 * var(--t)); font-weight: 700; color: var(--amber); padding: .02rem .06rem; border: 1px solid var(--amber); background: rgba(246, 163, 41, .25); }
|
||||
.dsec { display: flex; flex-direction: column; gap: .08rem; }
|
||||
.dsec__title { display: flex; align-items: baseline; gap: .1rem; font-size: .17rem; font-weight: 700; color: var(--mint-400); padding-bottom: .04rem; border-bottom: 1px solid var(--line); }
|
||||
.dtext { font-size: .15rem; line-height: 1.65; color: var(--text-md); }
|
||||
.dsec__title { display: flex; align-items: baseline; gap: .1rem; font-size: calc(.17 * var(--t)); font-weight: 700; color: var(--mint-400); padding-bottom: .04rem; border-bottom: 1px solid var(--line); }
|
||||
.dtext { font-size: calc(.15 * var(--t)); line-height: 1.65; color: var(--text-md); }
|
||||
.dskill { display: flex; gap: .12rem; align-items: center; }
|
||||
.dskill__icon { width: .62rem; height: .62rem; flex: none; object-fit: cover; border: 1px solid var(--line-3); background: #0a0d0c; }
|
||||
.dskill__icon--empty { display: grid; place-items: center; }
|
||||
.dskill__icon--empty b { font-family: var(--font-display); font-size: .2rem; color: var(--mint-400); letter-spacing: .02em; }
|
||||
.dskill__icon--empty b { font-family: var(--font-display); font-size: calc(.2 * var(--t)); color: var(--mint-400); letter-spacing: .02em; }
|
||||
.dskill__slot { display: inline-block; margin-right: .06rem; padding: 0 .05rem; vertical-align: .02rem; font-family: var(--font-display);
|
||||
font-size: .12rem; line-height: 1.5; color: var(--text-lo); border: 1px solid var(--line-3); }
|
||||
font-size: calc(.12 * var(--t)); line-height: 1.5; color: var(--text-lo); border: 1px solid var(--line-3); }
|
||||
.dskill__meta { display: flex; flex-direction: column; gap: .05rem; min-width: 0; }
|
||||
.dskill__name { font-size: .19rem; }
|
||||
.dskill__name { font-size: calc(.19 * var(--t)); }
|
||||
.dskill__tags { display: flex; flex-wrap: wrap; gap: .06rem; align-items: center; }
|
||||
.dsp { font-size: .12rem; padding: 0 .06rem; line-height: 1.6; font-weight: 700; }
|
||||
.dsp { font-size: calc(.12 * var(--t)); padding: 0 .06rem; line-height: 1.6; font-weight: 700; }
|
||||
.dsp--time { background: #7fd37a; color: #0b1a0a; }
|
||||
.dsp--atk { background: #ff8a3d; color: #1f0c00; }
|
||||
.dsp--def { background: #f2bd3e; color: #1f1400; }
|
||||
.dsp--trig { border: 1px solid var(--line-3); color: var(--text-lo); font-weight: 500; }
|
||||
.dsp__num { display: inline-flex; align-items: center; gap: .03rem; font-size: .13rem; color: var(--text-lo); }
|
||||
.dsp__num { display: inline-flex; align-items: center; gap: .03rem; font-size: calc(.13 * var(--t)); color: var(--text-lo); }
|
||||
.dsp__num b { color: #fff; }
|
||||
.dsp__num .icon { width: .14rem; height: .14rem; color: var(--gold); }
|
||||
.dtalent { display: flex; flex-direction: column; gap: .02rem; }
|
||||
.dtalent b { font-size: .15rem; color: var(--text-hi); }
|
||||
.dtalent b { font-size: calc(.15 * var(--t)); color: var(--text-hi); }
|
||||
/* the operator's own effect (特质, user playtest #3 item 8): compact, right under the header, gold accent */
|
||||
.dgarrison {
|
||||
display: flex; flex-direction: column; gap: .04rem; padding: .07rem .1rem .08rem; flex: none;
|
||||
background: linear-gradient(90deg, rgba(242, 189, 62, .1), rgba(242, 189, 62, .02) 70%); border-left: 3px solid var(--gold-2);
|
||||
}
|
||||
.dgarrison__head { display: flex; align-items: center; gap: .08rem; }
|
||||
.dgarrison__k { font-size: .14rem; font-weight: 900; letter-spacing: .12em; color: var(--gold-2); }
|
||||
.dgarrison__type { display: inline-flex; align-items: center; gap: .04rem; font-size: .12rem; line-height: 1.5; color: var(--gold-2); padding: 0 .07rem 0 .03rem; border: 1px solid rgba(242, 189, 62, .4); }
|
||||
.dgarrison__k { font-size: calc(.14 * var(--t)); font-weight: 900; letter-spacing: .12em; color: var(--gold-2); }
|
||||
.dgarrison__type { display: inline-flex; align-items: center; gap: .04rem; font-size: calc(.12 * var(--t)); line-height: 1.5; color: var(--gold-2); padding: 0 .07rem 0 .03rem; border: 1px solid rgba(242, 189, 62, .4); }
|
||||
.dgarrison__icon { width: .18rem; height: .18rem; object-fit: contain; }
|
||||
.dgarrison__text { font-size: .15rem; line-height: 1.55; color: var(--text-hi); }
|
||||
.dgarrison__text { font-size: calc(.15 * var(--t)); line-height: 1.55; color: var(--text-hi); }
|
||||
.dbonds { display: flex; flex-wrap: wrap; gap: .08rem; }
|
||||
.dbond { display: inline-flex; align-items: center; gap: .05rem; padding: .03rem .1rem .03rem .05rem; font-size: .15rem; background: rgba(78, 216, 175, .08); border: 1px solid var(--mint-800); color: var(--mint-400); }
|
||||
.dbond { display: inline-flex; align-items: center; gap: .05rem; padding: .03rem .1rem .03rem .05rem; font-size: calc(.15 * var(--t)); background: rgba(78, 216, 175, .08); border: 1px solid var(--mint-800); color: var(--mint-400); }
|
||||
/* the unit's bonds in the header's right column, right under name / class (user playtest #2 item 9): icon, name,
|
||||
count / next threshold, tier pips — visible as soon as the card opens */
|
||||
.dbonds--top { gap: .05rem; margin-top: .06rem; }
|
||||
.dhead__info .dbonds--top .dbond { font-size: .14rem; padding: .02rem .07rem .02rem .03rem; }
|
||||
.dhead__info .dbonds--top .dbond { font-size: calc(.14 * var(--t)); padding: .02rem .07rem .02rem .03rem; }
|
||||
.dhead__info .dbond__icon { width: .24rem; height: .24rem; }
|
||||
.dbonds--top .dbond { gap: .06rem; padding: .03rem .08rem .03rem .04rem; font: inherit; font-size: .15rem; color: var(--text-md);
|
||||
.dbonds--top .dbond { gap: .06rem; padding: .03rem .08rem .03rem .04rem; font: inherit; font-size: calc(.15 * var(--t)); color: var(--text-md);
|
||||
background: rgba(255, 255, 255, .03); border: 1px solid var(--line-2); cursor: default; }
|
||||
.dbonds--top button.dbond { cursor: pointer; }
|
||||
.dbonds--top button.dbond:hover { border-color: var(--mint-500); color: #fff; }
|
||||
@@ -185,8 +185,8 @@
|
||||
.dbonds--top .dbond.is-core .dbond__name { font-weight: 700; }
|
||||
.dbond__icon { width: .26rem; height: .26rem; }
|
||||
.dbond:not(.is-active) .dbond__icon img { filter: brightness(0) invert(.6); }
|
||||
.dbond__count { font-size: .15rem; color: #fff; }
|
||||
.dbond__count small { font-size: .11rem; color: var(--text-dim); }
|
||||
.dbond__count { font-size: calc(.15 * var(--t)); color: #fff; }
|
||||
.dbond__count small { font-size: calc(.11 * var(--t)); color: var(--text-dim); }
|
||||
.dbond__count.is-max { color: var(--gold-2); }
|
||||
.dbond__tiers { display: inline-flex; gap: 2px; margin-left: .02rem; }
|
||||
.dbond__tiers i { width: .05rem; height: .12rem; background: rgba(255, 255, 255, .16); transform: skewX(-18deg); }
|
||||
@@ -195,23 +195,23 @@
|
||||
.dbonds--top .dbond.is-off { border-style: dashed; border-color: rgba(255, 255, 255, .16); background: transparent; color: var(--text-dim); }
|
||||
.dbond.is-off .dbond__name { text-decoration: line-through; text-decoration-color: rgba(255, 84, 84, .6); }
|
||||
.dbond.is-off .dbond__icon img { filter: grayscale(1) brightness(.5); }
|
||||
.dbond__off { font-size: .12rem; color: var(--text-dim); white-space: nowrap; }
|
||||
.dtag-loadout { display: inline-block; margin-left: .08rem; vertical-align: .02rem; font-size: .11rem; font-weight: 700; letter-spacing: .06em;
|
||||
.dbond__off { font-size: calc(.12 * var(--t)); color: var(--text-dim); white-space: nowrap; }
|
||||
.dtag-loadout { display: inline-block; margin-left: .08rem; vertical-align: .02rem; font-size: calc(.11 * var(--t)); font-weight: 700; letter-spacing: .06em;
|
||||
padding: 0 .06rem; line-height: 1.6; background: var(--mint-500); color: #062019; }
|
||||
.dmodule { display: flex; align-items: center; flex-wrap: wrap; gap: .08rem; font-size: .15rem; }
|
||||
.dmodule { display: flex; align-items: center; flex-wrap: wrap; gap: .08rem; font-size: calc(.15 * var(--t)); }
|
||||
.dmodule__name { color: var(--text-hi); }
|
||||
/* official module type icon (local-client art, white glyph) on a dark tile; the type letter without it */
|
||||
.dmodule__icon { flex: none; display: inline-grid; place-items: center; width: .4rem; height: .4rem; background: linear-gradient(180deg, #2a322f, #151a18); border: 1px solid var(--gold-2); border: 1px solid color-mix(in srgb, var(--gold-2) 60%, transparent); }
|
||||
.dmodule__icon img { width: 80%; height: 80%; object-fit: contain; }
|
||||
.dmodule__icon b { font-size: .2rem; font-weight: 700; line-height: 1; color: var(--gold-2); }
|
||||
.dmodule__type { font-family: var(--font-display); font-size: .12rem; letter-spacing: .1em; padding: 0 .06rem; border: 1px solid var(--line-3); color: var(--text-lo); }
|
||||
.dmodule__icon b { font-size: calc(.2 * var(--t)); font-weight: 700; line-height: 1; color: var(--gold-2); }
|
||||
.dmodule__type { font-family: var(--font-display); font-size: calc(.12 * var(--t)); letter-spacing: .1em; padding: 0 .06rem; border: 1px solid var(--line-3); color: var(--text-lo); }
|
||||
.dmodule.is-none .dmodule__name { color: var(--text-dim); font-weight: 500; }
|
||||
.ditem { display: flex; gap: .1rem; align-items: flex-start; }
|
||||
.ditem__text { display: flex; flex-direction: column; gap: .02rem; min-width: 0; }
|
||||
.ditem__text b { font-size: .15rem; }
|
||||
.ditem__desc { font-size: .13rem; line-height: 1.55; color: var(--text-lo); }
|
||||
.dempty { font-size: .14rem; }
|
||||
.dhint { display: flex; align-items: flex-start; gap: .06rem; font-size: .14rem; color: var(--text-lo); line-height: 1.6; }
|
||||
.ditem__text b { font-size: calc(.15 * var(--t)); }
|
||||
.ditem__desc { font-size: calc(.13 * var(--t)); line-height: 1.55; color: var(--text-lo); }
|
||||
.dempty { font-size: calc(.14 * var(--t)); }
|
||||
.dhint { display: flex; align-items: flex-start; gap: .06rem; font-size: calc(.14 * var(--t)); color: var(--text-lo); line-height: 1.6; }
|
||||
.dhint .icon { width: .16rem; height: .16rem; margin-top: .03rem; flex: none; }
|
||||
/* a merge-completing shop / reward card's 可晋升 destination (the tag's colour; touch shows no title — QA 6b) */
|
||||
.dhint--merge { color: var(--gold); }
|
||||
@@ -219,7 +219,7 @@
|
||||
.dhint--rule { color: var(--text-md); }
|
||||
/* 变形同构体's 天赋栏 (detailPanel MorphPairings, DESIGN §21.26): one line per bond — 【bond】 | the items that pair with it;
|
||||
本局禁用 struck through, the pairing a wearer wears highlighted. Px floors keep it readable on phones (1rem = 40 px). */
|
||||
.dmorph { display: flex; flex-direction: column; gap: .05rem; font-size: max(.14rem, 9px); line-height: 1.5; color: var(--text-md); }
|
||||
.dmorph { display: flex; flex-direction: column; gap: .05rem; font-size: max(calc(.14 * var(--t)), 9px); line-height: 1.5; color: var(--text-md); }
|
||||
.dmorph__lead { margin: 0; color: var(--text-hi); }
|
||||
.dmorph__list { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 1px; }
|
||||
.dmorph__row { display: grid; grid-template-columns: 6.2em minmax(0, 1fr); gap: .06rem; align-items: baseline; padding: .02rem .06rem;
|
||||
@@ -230,20 +230,20 @@
|
||||
.dmorph__row.is-off .dmorph__items { color: var(--text-dim); }
|
||||
.dmorph__row.is-worn { background: rgba(78, 216, 175, .14); border-left-color: var(--mint-400); }
|
||||
.dmorph__item.is-worn { color: #fff; font-weight: 700; }
|
||||
.dmorph__tag { display: inline-block; margin-left: .06rem; padding: 0 .05rem; font-size: max(.11rem, 8px); line-height: 1.5; font-weight: 700; white-space: nowrap; }
|
||||
.dmorph__tag { display: inline-block; margin-left: .06rem; padding: 0 .05rem; font-size: max(calc(.11 * var(--t)), 8px); line-height: 1.5; font-weight: 700; white-space: nowrap; }
|
||||
.dmorph__tag.is-on { background: var(--mint-500); color: #062019; }
|
||||
.dmorph__tag.is-off { border: 1px dashed rgba(255, 255, 255, .25); color: var(--text-dim); font-weight: 500; }
|
||||
.dmorph__none { margin: 0; color: var(--gold-2); }
|
||||
/* a bond item's line: what it gives a 变形同构体 wearer (MorphGrantLine) */
|
||||
.dhint--morph { font-size: max(.14rem, 9px); color: var(--text-md); }
|
||||
.dhint--morph { font-size: max(calc(.14 * var(--t)), 9px); color: var(--text-md); }
|
||||
.dhint--morph.is-worn { color: var(--mint-400); }
|
||||
.dhint--morph.is-off { color: var(--text-dim); }
|
||||
.dactions { display: flex; justify-content: flex-end; gap: .1rem; padding-top: .06rem; border-top: 1px solid var(--line); }
|
||||
.dsell { margin-left: .06rem; color: #1d1400; }
|
||||
.dfactions { display: flex; flex-wrap: wrap; gap: .06rem; }
|
||||
.dfaction { display: inline-flex; align-items: center; gap: .04rem; font-size: .13rem; color: var(--amber); padding: 0 .08rem 0 .02rem; border: 1px solid rgba(246, 163, 41, .45); }
|
||||
.dfaction { display: inline-flex; align-items: center; gap: .04rem; font-size: calc(.13 * var(--t)); color: var(--amber); padding: 0 .08rem 0 .02rem; border: 1px solid rgba(246, 163, 41, .45); }
|
||||
.dfaction img { width: .22rem; height: .22rem; object-fit: contain; }
|
||||
.dabil { margin: 0; padding-left: .2rem; display: flex; flex-direction: column; gap: .05rem; font-size: .15rem; line-height: 1.6; color: var(--text-md); }
|
||||
.dabil { margin: 0; padding-left: .2rem; display: flex; flex-direction: column; gap: .05rem; font-size: calc(.15 * var(--t)); line-height: 1.6; color: var(--text-md); }
|
||||
.dabil li.is-off { color: var(--text-dim); }
|
||||
.dabil li.is-off .rt-term { color: var(--text-dim); border-bottom-color: transparent; }
|
||||
|
||||
@@ -270,44 +270,44 @@
|
||||
.bpop__disc.is-active { background: var(--mint-500); border-color: var(--mint-400); box-shadow: 0 0 .24rem rgba(23, 249, 183, .45); }
|
||||
.bpop__disc.is-active .bglyph img { filter: brightness(0) opacity(.85); }
|
||||
.bpop__titles { display: flex; flex-direction: column; gap: .02rem; min-width: 0; }
|
||||
.bpop__name { font-size: .32rem; font-weight: 900; letter-spacing: .08em; }
|
||||
.bpop__owner { display: inline-flex; align-items: center; gap: .06rem; font-size: .14rem; color: var(--amber); }
|
||||
.bpop__name { font-size: calc(.32 * var(--t)); font-weight: 900; letter-spacing: .08em; }
|
||||
.bpop__owner { display: inline-flex; align-items: center; gap: .06rem; font-size: calc(.14 * var(--t)); color: var(--amber); }
|
||||
.bpop__owner b { color: #fff; font-weight: 700; max-width: 2.4rem; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
||||
.bpop.is-other .bpop__disc { box-shadow: 0 0 0 1px rgba(246, 163, 41, .55); }
|
||||
.bpop__facts { display: flex; gap: .16rem; font-size: .15rem; color: var(--text-lo); }
|
||||
.bpop__facts b { color: #fff; font-size: .18rem; }
|
||||
.bpop__facts small { font-size: .12rem; }
|
||||
.bpop__off { margin: .08rem 0 0; font-size: .14rem; color: var(--text-lo); border-left: 2px solid rgba(255, 255, 255, .2); padding-left: .08rem; }
|
||||
.bpop__facts { display: flex; gap: .16rem; font-size: calc(.15 * var(--t)); color: var(--text-lo); }
|
||||
.bpop__facts b { color: #fff; font-size: calc(.18 * var(--t)); }
|
||||
.bpop__facts small { font-size: calc(.12 * var(--t)); }
|
||||
.bpop__off { margin: .08rem 0 0; font-size: calc(.14 * var(--t)); color: var(--text-lo); border-left: 2px solid rgba(255, 255, 255, .2); padding-left: .08rem; }
|
||||
.bpop__tiers { display: flex; gap: .06rem; }
|
||||
.bpop__tier { display: flex; align-items: baseline; gap: .02rem; padding: .02rem .12rem; border: 1px solid var(--line-2); color: var(--text-dim); font-size: .13rem; }
|
||||
.bpop__tier b { font-size: .2rem; }
|
||||
.bpop__tier { display: flex; align-items: baseline; gap: .02rem; padding: .02rem .12rem; border: 1px solid var(--line-2); color: var(--text-dim); font-size: calc(.13 * var(--t)); }
|
||||
.bpop__tier b { font-size: calc(.2 * var(--t)); }
|
||||
.bpop__tier.is-on { background: var(--mint-500); color: var(--text-on-mint); border-color: var(--mint-400); }
|
||||
.bpop__sec { display: flex; flex-direction: column; gap: .06rem; }
|
||||
.bpop__sec h4 { font-size: .16rem; color: var(--mint-400); display: flex; align-items: baseline; gap: .08rem; }
|
||||
.bpop__sec h4 small { font-size: .13rem; color: var(--text-lo); font-weight: 400; }
|
||||
.bpop__sec h4 { font-size: calc(.16 * var(--t)); color: var(--mint-400); display: flex; align-items: baseline; gap: .08rem; }
|
||||
.bpop__sec h4 small { font-size: calc(.13 * var(--t)); color: var(--text-lo); font-weight: 400; }
|
||||
.bpop__sec--now { padding: .1rem .12rem; background: rgba(78, 216, 175, .06); border-left: 3px solid var(--mint-500); }
|
||||
.bpop__desc { font-size: .15rem; line-height: 1.7; color: var(--text-md); }
|
||||
.bpop__desc { font-size: calc(.15 * var(--t)); line-height: 1.7; color: var(--text-md); }
|
||||
.bpop__members { display: grid; grid-template-columns: repeat(5, 1fr); gap: .08rem; }
|
||||
.bpop__member { position: relative; display: flex; flex-direction: column; align-items: center; gap: .03rem; padding: .04rem 0; background: none; border: 1px solid transparent; }
|
||||
.bpop__member:hover { border-color: var(--line-2); background: rgba(255, 255, 255, .03); }
|
||||
.bpop__member.is-on .uthumb__art { border-color: var(--mint-400); box-shadow: 0 0 .1rem rgba(23, 249, 183, .45); }
|
||||
.bpop__mname { font-size: .12rem; color: var(--text-lo); max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.bpop__mname { font-size: calc(.12 * var(--t)); color: var(--text-lo); max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.bpop__member.is-on .bpop__mname { color: var(--mint-400); }
|
||||
.bpop__member.is-owned .bpop__mname { color: var(--text-hi); }
|
||||
.bpop__ban { position: absolute; top: .02rem; right: .1rem; width: .2rem; height: .2rem; display: grid; place-items: center; background: var(--red); color: #fff; border-radius: 50%; }
|
||||
.bpop__ban .icon { width: .14rem; height: .14rem; }
|
||||
/* a member through 变形同构体 (the item pairing, not the operator's own bond): a gold 同构 tag on the portrait */
|
||||
.bpop__iso { position: absolute; top: .02rem; right: .08rem; padding: 0 .04rem; font-size: .11rem; line-height: 1.45; font-weight: 700; color: #141414; background: var(--gold-2); }
|
||||
.bpop__iso { position: absolute; top: .02rem; right: .08rem; padding: 0 .04rem; font-size: calc(.11 * var(--t)); line-height: 1.45; font-weight: 700; color: #141414; background: var(--gold-2); }
|
||||
/* a 自选 pick all of whose bonds are off this match (m.private.diyBanned): its 「自选」 tag moves down to the portrait's
|
||||
bottom corner (the 替补 mark's place), clear of the red ✕ and the tier chip */
|
||||
.bpop__member.is-banned .bpop__diy { top: .42rem; }
|
||||
.dbonds--top .dbond.is-granted { border-style: dashed; }
|
||||
.dbond__iso { font-size: .11rem; font-weight: 700; color: var(--gold-2); }
|
||||
.dbond__iso { font-size: calc(.11 * var(--t)); font-weight: 700; color: var(--gold-2); }
|
||||
/* a count that holds 调和's +1 (the server's bond entry `harmony`, DESIGN §21.26): 在场 n(含调和 +1) and the 调和 row that
|
||||
heads the member list, naming the 调和 operators on the board (px floors: readable on phones) */
|
||||
.bpop__facts small.bpop__hnote { font-size: max(.12rem, 8px); color: var(--gold-2); }
|
||||
.bpop__facts small.bpop__hnote { font-size: max(calc(.12 * var(--t)), 8px); color: var(--gold-2); }
|
||||
.bpop__harmony { display: flex; align-items: center; gap: .08rem; width: 100%; margin: 0; padding: .05rem .1rem; text-align: left;
|
||||
font: inherit; font-size: max(.14rem, 9px); line-height: 1.45; color: var(--text-md); background: rgba(242, 189, 62, .08); border: 1px dashed rgba(242, 189, 62, .5); }
|
||||
font: inherit; font-size: max(calc(.14 * var(--t)), 9px); line-height: 1.45; color: var(--text-md); background: rgba(242, 189, 62, .08); border: 1px dashed rgba(242, 189, 62, .5); }
|
||||
button.bpop__harmony { cursor: pointer; }
|
||||
button.bpop__harmony:hover { border-style: solid; color: #fff; }
|
||||
.bpop__harmony b { flex: none; color: var(--gold-2); font-weight: 700; white-space: nowrap; }
|
||||
@@ -328,12 +328,12 @@ button.bpop__harmony:hover { border-style: solid; color: #fff; }
|
||||
.edrawer__close { width: .34rem; height: .34rem; margin-bottom: .06rem; display: grid; place-items: center; background: none; border: 1px solid var(--line-2); color: var(--text-lo); }
|
||||
.edrawer__close .icon { width: .18rem; height: .18rem; }
|
||||
.edrawer__body { overflow: auto; padding: .14rem .18rem .18rem; display: flex; flex-direction: column; gap: .12rem; }
|
||||
.edrawer__sum { display: flex; align-items: center; justify-content: space-between; gap: .12rem; flex-wrap: wrap; font-size: .15rem; color: var(--text-md); }
|
||||
.edrawer__sum b { font-size: .19rem; }
|
||||
.edrawer__empty { display: flex; align-items: center; gap: .08rem; justify-content: center; padding: .3rem; color: var(--text-dim); font-size: .16rem; }
|
||||
.edrawer__sum { display: flex; align-items: center; justify-content: space-between; gap: .12rem; flex-wrap: wrap; font-size: calc(.15 * var(--t)); color: var(--text-md); }
|
||||
.edrawer__sum b { font-size: calc(.19 * var(--t)); }
|
||||
.edrawer__empty { display: flex; align-items: center; gap: .08rem; justify-content: center; padding: .3rem; color: var(--text-dim); font-size: calc(.16 * var(--t)); }
|
||||
.edrawer__empty .icon { width: .24rem; height: .24rem; }
|
||||
.fchips { display: flex; gap: .06rem; flex-wrap: wrap; }
|
||||
.fchip { display: inline-flex; align-items: center; gap: .04rem; padding: 0 .1rem 0 .02rem; font-size: .13rem; color: var(--amber); border: 1px solid rgba(246, 163, 41, .45); background: rgba(246, 163, 41, .06); }
|
||||
.fchip { display: inline-flex; align-items: center; gap: .04rem; padding: 0 .1rem 0 .02rem; font-size: calc(.13 * var(--t)); color: var(--amber); border: 1px solid rgba(246, 163, 41, .45); background: rgba(246, 163, 41, .06); }
|
||||
.fchip__icon { width: .26rem; height: .26rem; object-fit: contain; }
|
||||
.elist { display: grid; grid-template-columns: 1fr 1fr; gap: .08rem; }
|
||||
.erow { display: flex; align-items: center; gap: .1rem; padding: .06rem .12rem .06rem .06rem; background: rgba(255, 255, 255, .025); border: 1px solid var(--line); text-align: left; color: inherit; transition: border-color var(--t-fast), background var(--t-fast); }
|
||||
@@ -341,10 +341,10 @@ button.bpop__harmony:hover { border-style: solid; color: #fff; }
|
||||
.erow--boss { border-color: rgba(255, 110, 40, .6); background: rgba(232, 90, 26, .1); }
|
||||
.erow--bounty { border-color: rgba(255, 198, 0, .45); }
|
||||
.erow__text { display: flex; flex-direction: column; gap: .03rem; min-width: 0; flex: 1; }
|
||||
.erow__name { font-size: .16rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.erow__name { font-size: calc(.16 * var(--t)); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.erow__tags { display: flex; gap: .04rem; flex-wrap: wrap; }
|
||||
.erow__count { font-size: .26rem; color: #fff; }
|
||||
.etag { font-size: .11rem; line-height: 1.5; padding: 0 .05rem; font-weight: 700; }
|
||||
.erow__count { font-size: calc(.26 * var(--t)); color: #fff; }
|
||||
.etag { font-size: calc(.11 * var(--t)); line-height: 1.5; padding: 0 .05rem; font-weight: 700; }
|
||||
.etag--boss { background: var(--orange); color: #fff; }
|
||||
.etag--bounty { background: var(--gold); color: #1d1400; }
|
||||
.etag--escort { background: var(--bg-4); color: var(--text-md); }
|
||||
@@ -353,29 +353,29 @@ button.bpop__harmony:hover { border-style: solid; color: #fff; }
|
||||
.etag--faction { border: 1px solid rgba(246, 163, 41, .5); color: var(--amber); font-weight: 500; }
|
||||
.eboss { display: flex; gap: .12rem; align-items: center; padding: .1rem .12rem; border: 1px solid rgba(255, 110, 40, .45); background: linear-gradient(90deg, rgba(232, 90, 26, .14), transparent); }
|
||||
.eboss > div { display: flex; flex-direction: column; gap: .02rem; }
|
||||
.eboss b { font-size: .19rem; }
|
||||
.eboss .t-lo { font-size: .13rem; }
|
||||
.eboss b { font-size: calc(.19 * var(--t)); }
|
||||
.eboss .t-lo { font-size: calc(.13 * var(--t)); }
|
||||
.iband { display: flex; gap: .12rem; align-items: flex-start; padding: .1rem; background: rgba(78, 216, 175, .05); border: 1px solid var(--mint-800); }
|
||||
.iband > div { display: flex; flex-direction: column; gap: .03rem; min-width: 0; }
|
||||
.iband b { font-size: .19rem; }
|
||||
.iband b small { font-size: .14rem; font-weight: 400; margin-left: .06rem; }
|
||||
.iband__desc { font-size: .14rem; line-height: 1.6; color: var(--text-md); }
|
||||
.istage { display: flex; align-items: center; gap: .06rem; font-size: .15rem; color: var(--text-lo); }
|
||||
.iband b { font-size: calc(.19 * var(--t)); }
|
||||
.iband b small { font-size: calc(.14 * var(--t)); font-weight: 400; margin-left: .06rem; }
|
||||
.iband__desc { font-size: calc(.14 * var(--t)); line-height: 1.6; color: var(--text-md); }
|
||||
.istage { display: flex; align-items: center; gap: .06rem; font-size: calc(.15 * var(--t)); color: var(--text-lo); }
|
||||
.istage .icon { width: .18rem; height: .18rem; color: var(--mint-500); }
|
||||
.istage b { color: var(--text-hi); }
|
||||
.ihead { font-size: .16rem; color: var(--mint-400); display: flex; gap: .08rem; align-items: baseline; }
|
||||
.ihead small { color: var(--text-lo); font-size: .14rem; }
|
||||
.ihead { font-size: calc(.16 * var(--t)); color: var(--mint-400); display: flex; gap: .08rem; align-items: baseline; }
|
||||
.ihead small { color: var(--text-lo); font-size: calc(.14 * var(--t)); }
|
||||
.ibonds { display: flex; flex-wrap: wrap; gap: .06rem; }
|
||||
.ibond { display: inline-flex; align-items: center; gap: .05rem; padding: .03rem .08rem .03rem .04rem; font-size: .14rem; border: 1px solid var(--line-2); color: var(--text-md); }
|
||||
.ibond { display: inline-flex; align-items: center; gap: .05rem; padding: .03rem .08rem .03rem .04rem; font-size: calc(.14 * var(--t)); border: 1px solid var(--line-2); color: var(--text-md); }
|
||||
.ibond.is-off { color: var(--text-dim); background: rgba(255, 255, 255, .02); text-decoration: line-through; text-decoration-color: rgba(255, 84, 84, .6); }
|
||||
.ibond.is-off .bglyph img { filter: grayscale(1) brightness(.5); }
|
||||
.ibond__ban { display: inline-flex; align-items: center; gap: .02rem; font-size: .12rem; padding: 0 .05rem; background: var(--red); color: #fff; text-decoration: none; }
|
||||
.ibond__ban { display: inline-flex; align-items: center; gap: .02rem; font-size: calc(.12 * var(--t)); padding: 0 .05rem; background: var(--red); color: #fff; text-decoration: none; }
|
||||
.ibond__ban .icon { width: .12rem; height: .12rem; }
|
||||
.ibanned { display: flex; flex-wrap: wrap; gap: .06rem; }
|
||||
.ibanned__one { padding: 0; background: none; border: 0; }
|
||||
/* 0.2.0: the player's own 自选 pieces out of the shop this match (ui/matchInfo.js DiyBannedLine) */
|
||||
.idiybanned { margin: .08rem 0 0; font-size: .13rem; line-height: 1.5; }
|
||||
.idiybanned .diybanned__tag { display: inline-block; margin-right: .05rem; padding: 0 .05rem; font-size: .11rem; font-weight: 700;
|
||||
.idiybanned { margin: .08rem 0 0; font-size: calc(.13 * var(--t)); line-height: 1.5; }
|
||||
.idiybanned .diybanned__tag { display: inline-block; margin-right: .05rem; padding: 0 .05rem; font-size: calc(.11 * var(--t)); font-weight: 700;
|
||||
background: var(--mint-500); color: var(--text-on-mint); }
|
||||
|
||||
/* ---- 机变 overlay ---- */
|
||||
@@ -385,13 +385,13 @@ button.bpop__harmony:hover { border-style: solid; color: #fff; }
|
||||
.spov__inner { position: absolute; left: .3rem; right: .5rem; top: 1.14rem; bottom: .5rem; display: flex; flex-direction: column; gap: .16rem; animation: screen-in var(--t-slow) var(--ease-out); }
|
||||
.spov__head { display: flex; align-items: flex-end; justify-content: space-between; gap: .3rem; }
|
||||
.spov__titles { display: flex; flex-direction: column; gap: .04rem; }
|
||||
.spov__title { display: flex; align-items: baseline; gap: .14rem; font-size: .5rem; font-weight: 900; letter-spacing: .08em; color: #eafff8; text-shadow: 0 0 .2rem rgba(23, 249, 183, .35); }
|
||||
.spov__title { display: flex; align-items: baseline; gap: .14rem; font-size: calc(.5 * var(--t)); font-weight: 900; letter-spacing: .08em; color: #eafff8; text-shadow: 0 0 .2rem rgba(23, 249, 183, .35); }
|
||||
.spov__title.is-special { color: var(--gold-2); text-shadow: 0 0 .2rem rgba(242, 189, 62, .35); }
|
||||
.spov__bar { color: var(--mint-700); font-weight: 400; }
|
||||
.spov__desc { font-size: .22rem; font-weight: 400; color: var(--text-lo); letter-spacing: .04em; text-shadow: none; }
|
||||
.spov__sub { font-size: .15rem; color: var(--text-dim); }
|
||||
.spov__desc { font-size: calc(.22 * var(--t)); font-weight: 400; color: var(--text-lo); letter-spacing: .04em; text-shadow: none; }
|
||||
.spov__sub { font-size: calc(.15 * var(--t)); color: var(--text-dim); }
|
||||
.spov__turn { display: flex; align-items: center; gap: .24rem; }
|
||||
.spov__turntxt { display: flex; align-items: center; gap: .08rem; font-size: .24rem; font-weight: 700; color: var(--text-md); }
|
||||
.spov__turntxt { display: flex; align-items: center; gap: .08rem; font-size: calc(.24 * var(--t)); font-weight: 700; color: var(--text-md); }
|
||||
.spov__turntxt .icon { width: .26rem; height: .26rem; }
|
||||
.spov__turntxt.is-mine { color: var(--gold); text-shadow: 0 0 .16rem rgba(255, 198, 0, .45); animation: blink 1.2s ease-in-out infinite; }
|
||||
.spov__turntxt.is-done { color: var(--mint-400); }
|
||||
@@ -400,8 +400,8 @@ button.bpop__harmony:hover { border-style: solid; color: #fff; }
|
||||
.spov__who.is-cur { border-color: var(--gold); background: rgba(255, 198, 0, .08); }
|
||||
.spov__who.is-done { border-color: var(--mint-800); }
|
||||
.spov__who.is-self .spov__wname { color: var(--mint-400); }
|
||||
.spov__idx { font-size: .16rem; color: var(--text-dim); width: .2rem; text-align: center; }
|
||||
.spov__wname { font-size: .15rem; max-width: 1.2rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.spov__idx { font-size: calc(.16 * var(--t)); color: var(--text-dim); width: .2rem; text-align: center; }
|
||||
.spov__wname { font-size: calc(.15 * var(--t)); max-width: 1.2rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.spov__wstate { display: grid; }
|
||||
.spov__wstate .icon { width: .2rem; height: .2rem; }
|
||||
.spov__who.is-done .spov__wstate { color: var(--mint-400); }
|
||||
@@ -624,7 +624,7 @@ button.bpop__harmony:hover { border-style: solid; color: #fff; }
|
||||
border-color: rgba(255, 84, 84, .35);
|
||||
}
|
||||
.eqr .modal__body { display: flex; flex-direction: column; align-items: stretch; gap: .1rem; padding-bottom: .16rem; }
|
||||
.eqr__lead { font-size: .17rem; line-height: 1.6; color: var(--text-md); }
|
||||
.eqr__lead { font-size: calc(.17 * var(--t)); line-height: 1.6; color: var(--text-md); }
|
||||
.eqr__lead b { color: var(--text-hi); }
|
||||
.eqr__lead .t-red { color: var(--red-premium); }
|
||||
.eqr__top { display: flex; align-items: center; justify-content: center; gap: .22rem; margin-top: .06rem; }
|
||||
@@ -635,7 +635,7 @@ button.bpop__harmony:hover { border-style: solid; color: #fff; }
|
||||
}
|
||||
.eqr__ava::after { content: ''; position: absolute; inset: .1rem; background: var(--eqr-avaf, none) center / 100% 100% no-repeat; pointer-events: none; }
|
||||
.eqr__ava .uthumb { --ut: .72rem; }
|
||||
.eqr__opname { font-size: .15rem; font-weight: 700; color: var(--text-hi); max-width: 1.1rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.eqr__opname { font-size: calc(.15 * var(--t)); font-weight: 700; color: var(--text-hi); max-width: 1.1rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.eqr__swap { display: grid; place-items: center; width: .4rem; height: .32rem; color: var(--mint-400); flex: none; }
|
||||
.eqr__swap img, .eqr__swap .icon { width: 100%; height: 100%; object-fit: contain; }
|
||||
.eqr__incoming { display: flex; flex-direction: column; gap: .04rem; min-width: 0; flex: 0 1 4.2rem; }
|
||||
@@ -649,9 +649,9 @@ button.bpop__harmony:hover { border-style: solid; color: #fff; }
|
||||
.eqr__card--new .eqr__icon .uthumb { --ut: .56rem; }
|
||||
.eqr__meta { grid-row: 1; grid-column: 2; display: flex; flex-direction: column; gap: .03rem; min-width: 0; }
|
||||
.eqr__chips { display: flex; align-items: center; gap: .06rem; min-height: .2rem; }
|
||||
.eqr__elite { font-size: .12rem; padding: 0 .06rem; background: var(--gold); color: #1b1300; font-weight: 700; }
|
||||
.eqr__name { font-size: .19rem; color: var(--text-hi); line-height: 1.2; }
|
||||
.eqr__desc { grid-row: 2; grid-column: 1 / -1; margin: 0; font-size: .145rem; line-height: 1.55; color: var(--text-md); max-height: 1.12rem; overflow: auto; }
|
||||
.eqr__elite { font-size: calc(.12 * var(--t)); padding: 0 .06rem; background: var(--gold); color: #1b1300; font-weight: 700; }
|
||||
.eqr__name { font-size: calc(.19 * var(--t)); color: var(--text-hi); line-height: 1.2; }
|
||||
.eqr__desc { grid-row: 2; grid-column: 1 / -1; margin: 0; font-size: calc(.145 * var(--t)); line-height: 1.55; color: var(--text-md); max-height: 1.12rem; overflow: auto; }
|
||||
.eqr__arrow { position: relative; height: .5rem; margin: 0 auto; width: 50%; }
|
||||
.eqr__aline { position: absolute; left: 0; right: 0; top: .2rem; height: .2rem; border: .045rem solid rgba(242, 242, 242, .82); border-bottom: 0; }
|
||||
.eqr__aline::before { content: ''; position: absolute; left: 50%; top: -.25rem; width: .045rem; height: .21rem; margin-left: -.0225rem; background: rgba(242, 242, 242, .82); }
|
||||
@@ -679,12 +679,12 @@ button.bpop__harmony:hover { border-style: solid; color: #fff; }
|
||||
}
|
||||
.eqr__flag {
|
||||
position: absolute; z-index: 2; left: 0; right: 0; bottom: 0; height: .44rem; display: flex; align-items: center; justify-content: center; gap: .08rem;
|
||||
font-size: .17rem; font-weight: 700; letter-spacing: .12em; color: #fff; text-shadow: 0 1px 2px rgba(0, 0, 0, .6);
|
||||
font-size: calc(.17 * var(--t)); font-weight: 700; letter-spacing: .12em; color: #fff; text-shadow: 0 1px 2px rgba(0, 0, 0, .6);
|
||||
background: var(--eqr-selbar, linear-gradient(90deg, #a3141a, #d42a2a)) center / 100% 100% no-repeat;
|
||||
opacity: 0; transform: translateY(100%); transition: opacity var(--t-fast), transform var(--t-med) var(--ease-out);
|
||||
}
|
||||
.eqr__flag-icon { width: .2rem; height: .2rem; }
|
||||
.eqr__opt.is-sel .eqr__flag { opacity: 1; transform: none; }
|
||||
.eqr__hint { margin: .04rem 0 0; text-align: center; font-size: .15rem; color: var(--text-lo); min-height: .24rem; }
|
||||
.eqr__hint { margin: .04rem 0 0; text-align: center; font-size: calc(.15 * var(--t)); color: var(--text-lo); min-height: .24rem; }
|
||||
.eqr__hint b { color: var(--text-hi); }
|
||||
.eqr__ok-icon { width: .2rem; height: .2rem; margin-right: .06rem; vertical-align: -.03rem; filter: brightness(0) invert(1); }
|
||||
+30
-24
@@ -43,8 +43,8 @@
|
||||
.spec__row.is-offline .spec__name { color: var(--text-dim); }
|
||||
.spec__ico { width: .18rem; height: .18rem; flex: none; color: var(--mint-500); }
|
||||
.spec__row.is-offline .spec__ico { color: var(--text-dim); }
|
||||
.spec__name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: .19rem; color: var(--text-hi); }
|
||||
.spec__hint { margin: .04rem 0 0; font-size: .15rem; line-height: 1.45; }
|
||||
.spec__name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: calc(.19 * var(--t)); color: var(--text-hi); }
|
||||
.spec__hint { margin: .04rem 0 0; font-size: calc(.15 * var(--t)); line-height: 1.45; }
|
||||
.gtop__exit { box-shadow: 0 .06rem .2rem rgba(0, 0, 0, .5); }
|
||||
.gtop__center {
|
||||
--bk-c: var(--mint-700);
|
||||
@@ -521,45 +521,51 @@
|
||||
.settings-btn .btn__label { display: inline-flex; align-items: center; gap: .1rem; }
|
||||
.settings-btn .btn__icon { color: var(--mint-500); }
|
||||
.set-list { display: flex; flex-direction: column; gap: .16rem; padding-top: .06rem; }
|
||||
.set-row { display: grid; grid-template-columns: 3.3rem 1fr auto; align-items: center; gap: .18rem; min-height: .44rem; }
|
||||
.set-row__label { display: flex; align-items: center; gap: .1rem; font-size: .18rem; color: var(--text-hi); white-space: nowrap; }
|
||||
.set-row { display: grid; grid-template-columns: minmax(0, 3.3rem) minmax(0, 1fr) auto; align-items: center; gap: .18rem; min-height: .44rem; }
|
||||
/* The label column may shrink (the label wraps) instead of pushing its control out of the modal: at 设置 →「文字大小」
|
||||
大 / 特大 a phone's label (a Chinese name plus its Latin micro label) is wider than 3.3rem at 1rem = 40 px. */
|
||||
.set-row__label { display: flex; flex-wrap: wrap; align-items: center; gap: .1rem; font-size: calc(.18 * var(--t)); color: var(--text-hi); }
|
||||
.set-row__label .icon { width: .2rem; height: .2rem; color: var(--mint-500); }
|
||||
.set-row__label .micro { margin-left: .04rem; }
|
||||
.set-row__val { width: .5rem; text-align: right; font-size: .2rem; color: var(--mint-400); }
|
||||
/* a floor, not a fixed width: the %. value grows with the text step (20 px on a phone, 3 digits at 特大) */
|
||||
.set-row__val { min-width: .5rem; text-align: right; font-size: calc(.2 * var(--t)); color: var(--mint-400); }
|
||||
.set-range {
|
||||
-webkit-appearance: none; appearance: none; width: 100%; height: .08rem; background: linear-gradient(90deg, var(--mint-500) var(--pct), #0a0d0c var(--pct));
|
||||
border: 1px solid var(--line-2); outline: none; cursor: pointer;
|
||||
}
|
||||
.set-range::-webkit-slider-thumb { -webkit-appearance: none; width: .2rem; height: .26rem; background: var(--mint-400); clip-path: polygon(50% 0, 100% 30%, 100% 100%, 0 100%, 0 30%); cursor: grab; }
|
||||
.set-range::-moz-range-thumb { width: .2rem; height: .26rem; background: var(--mint-400); border: 0; }
|
||||
.set-toggle { grid-column: 2 / 4; justify-self: start; display: flex; align-items: center; gap: .1rem; height: .38rem; padding: 0 .14rem 0 .06rem; background: #0a0d0c; border: 1px solid var(--line-2); color: var(--text-lo); font-size: .15rem; }
|
||||
.set-toggle { grid-column: 2 / 4; justify-self: start; display: flex; align-items: center; gap: .1rem; height: .38rem; padding: 0 .14rem 0 .06rem; background: #0a0d0c; border: 1px solid var(--line-2); color: var(--text-lo); font-size: calc(.15 * var(--t)); }
|
||||
.set-toggle i { width: .46rem; height: .24rem; background: var(--bg-3); position: relative; transition: background var(--t-fast); }
|
||||
.set-toggle i::after { content: ''; position: absolute; left: .03rem; top: .03rem; width: .18rem; height: .18rem; background: var(--text-lo); transition: transform var(--t-fast), background var(--t-fast); }
|
||||
.set-toggle.is-on { color: var(--mint-400); border-color: var(--mint-700); }
|
||||
.set-toggle.is-on i { background: var(--mint-800); }
|
||||
.set-toggle.is-on i::after { transform: translateX(.22rem); background: var(--mint-400); }
|
||||
.set-seg { grid-column: 2 / 4; justify-self: start; display: flex; border: 1px solid var(--line-2); }
|
||||
.set-seg button { min-width: .8rem; height: .38rem; background: #0a0d0c; border: 0; border-right: 1px solid var(--line-2); color: var(--text-lo); font-size: .16rem; }
|
||||
.set-seg { grid-column: 2 / 4; justify-self: start; display: flex; flex-wrap: wrap; max-width: 100%; border: 1px solid var(--line-2); }
|
||||
.set-seg button { flex: 1 1 auto; min-width: .8rem; min-height: .38rem; height: auto; white-space: normal; background: #0a0d0c; border: 0; border-right: 1px solid var(--line-2); color: var(--text-lo); font-size: calc(.16 * var(--t)); }
|
||||
/* 文字大小 (ui/settings.js): four steps, so its buttons are narrower than the two- and three-step groups' — at .8rem each
|
||||
the group would be wider than the row's 1fr column on a phone (1rem = 40 px) */
|
||||
.set-seg.set-textsize button { min-width: .62rem; }
|
||||
.set-seg button:last-child { border-right: 0; }
|
||||
.set-seg button.is-on { background: var(--mint-500); color: var(--text-on-mint); font-weight: 700; }
|
||||
.set-hint { margin-top: .06rem; font-size: .14rem; color: var(--text-dim); line-height: 1.8; }
|
||||
.set-hint { margin-top: .06rem; font-size: calc(.14 * var(--t)); color: var(--text-dim); line-height: 1.8; }
|
||||
.set-hint kbd, .lvcard__key, .toolbtn kbd { font-family: var(--font-num); font-size: .11rem; border: 1px solid var(--line-2); padding: 0 .04rem; color: var(--text-lo); background: rgba(0, 0, 0, .3); }
|
||||
/* 快捷键 (ui/settings.js HotkeySection): each shortcut with its key — click the key, press the new one; two columns,
|
||||
one under 768 px (phones in landscape) */
|
||||
.set-keys { display: flex; flex-direction: column; gap: .08rem; padding-top: .1rem; border-top: 1px solid var(--line); }
|
||||
.set-keys__head { display: flex; align-items: center; justify-content: space-between; gap: .12rem; min-height: .44rem; }
|
||||
.set-keys__head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .12rem; min-height: .44rem; }
|
||||
.set-keys__list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .06rem .14rem; margin: 0; padding: 0; list-style: none; }
|
||||
.set-keys__row { display: flex; align-items: center; justify-content: space-between; gap: .1rem; min-height: .42rem; padding: .03rem .03rem .03rem .12rem; background: rgba(0, 0, 0, .22); border: 1px solid var(--line); }
|
||||
.set-keys__name { min-width: 0; font-size: .15rem; line-height: 1.35; color: var(--text-md); overflow-wrap: anywhere; }
|
||||
.set-key { flex: none; display: inline-flex; align-items: center; justify-content: center; gap: .06rem; min-width: .9rem; height: .36rem; padding: 0 .1rem; background: #0a0d0c; border: 1px solid var(--line-2); color: var(--mint-400); font-family: var(--font-num); font-size: .15rem; cursor: pointer; }
|
||||
.set-keys__name { min-width: 0; font-size: calc(.15 * var(--t)); line-height: 1.35; color: var(--text-md); overflow-wrap: anywhere; }
|
||||
.set-key { flex: none; display: inline-flex; align-items: center; justify-content: center; gap: .06rem; min-width: .9rem; height: .36rem; padding: 0 .1rem; background: #0a0d0c; border: 1px solid var(--line-2); color: var(--mint-400); font-family: var(--font-num); font-size: calc(.15 * var(--t)); cursor: pointer; }
|
||||
.set-key kbd { font: inherit; }
|
||||
.set-key:hover, .set-key:focus-visible { border-color: var(--mint-700); }
|
||||
.set-key.is-waiting { border-color: var(--amber); color: var(--amber); font-family: inherit; font-size: .13rem; animation: set-key-wait 1s ease-in-out infinite alternate; }
|
||||
.set-key.is-waiting { border-color: var(--amber); color: var(--amber); font-family: inherit; font-size: calc(.13 * var(--t)); animation: set-key-wait 1s ease-in-out infinite alternate; }
|
||||
.set-key.is-fixed { cursor: default; color: var(--text-lo); border-style: dashed; }
|
||||
.set-key.is-fixed small { font-family: inherit; font-size: .12rem; color: var(--text-dim); }
|
||||
.set-keys__note { min-height: 1.5em; font-size: .14rem; line-height: 1.5; color: var(--mint-400); }
|
||||
.set-key.is-fixed small { font-family: inherit; font-size: calc(.12 * var(--t)); color: var(--text-dim); }
|
||||
.set-keys__note { min-height: 1.5em; font-size: calc(.14 * var(--t)); line-height: 1.5; color: var(--mint-400); }
|
||||
.set-keys__note.is-warn { color: var(--amber); }
|
||||
.set-keys__more > summary { padding: .06rem 0; font-size: .15rem; color: var(--mint-400); cursor: pointer; }
|
||||
.set-keys__more > summary { padding: .06rem 0; font-size: calc(.15 * var(--t)); color: var(--mint-400); cursor: pointer; }
|
||||
.set-keys__more[open] > summary { margin-bottom: .08rem; }
|
||||
@keyframes set-key-wait { to { box-shadow: 0 0 .12rem rgba(246, 163, 41, .45); } }
|
||||
/* 问题反馈 (ui/settings.js DiagSection): laid out like 快捷键 — a head with its button, a hint, the result line; the
|
||||
@@ -567,10 +573,10 @@
|
||||
clipboard refused it, styled like 干员调配's 导出 box (screens/loadout.css .lo-io__text) */
|
||||
.set-diag { display: flex; flex-direction: column; gap: .08rem; padding-top: .1rem; border-top: 1px solid var(--line); }
|
||||
.set-diag__meta { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: .04rem .2rem; }
|
||||
.set-diag__stat { display: inline-flex; align-items: baseline; gap: .1rem; font-size: .14rem; color: var(--text-lo); }
|
||||
.set-diag__count { font-family: var(--font-num); font-size: .2rem; line-height: 1; color: var(--mint-400); }
|
||||
.set-diag__stat { display: inline-flex; align-items: baseline; gap: .1rem; font-size: calc(.14 * var(--t)); color: var(--text-lo); }
|
||||
.set-diag__count { font-family: var(--font-num); font-size: calc(.2 * var(--t)); line-height: 1; color: var(--mint-400); }
|
||||
.set-diag__count.is-warn { color: var(--amber); }
|
||||
.set-diag__url { min-width: 0; font-size: .13rem; color: var(--text-dim); overflow-wrap: anywhere; -webkit-user-select: all; user-select: all; }
|
||||
.set-diag__url { min-width: 0; font-size: calc(.13 * var(--t)); color: var(--text-dim); overflow-wrap: anywhere; -webkit-user-select: all; user-select: all; }
|
||||
.set-diag__battle-hint { margin-top: -.06rem; }
|
||||
.set-diag__text {
|
||||
box-sizing: border-box; width: 100%; height: max(2rem, 140px); min-height: 120px; resize: vertical;
|
||||
@@ -589,11 +595,11 @@
|
||||
}
|
||||
|
||||
/* ---- exit / away ------------------------------------------------------------------------------------------------------ */
|
||||
.exitm { display: flex; flex-direction: column; gap: .1rem; font-size: .17rem; line-height: 1.7; }
|
||||
.exitm { display: flex; flex-direction: column; gap: .1rem; font-size: calc(.17 * var(--t)); line-height: 1.7; }
|
||||
.awayov { position: fixed; inset: 0; z-index: 70; display: grid; place-items: center; background: rgba(4, 7, 6, .78); -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px); animation: screen-in var(--t-slow) var(--ease-out); }
|
||||
.awayov__box { display: flex; flex-direction: column; align-items: center; gap: .12rem; padding: .44rem .7rem; background: rgba(12, 16, 14, .96); border: 1px solid var(--line-2); }
|
||||
.awayov__icon { width: .8rem; height: .8rem; color: var(--ice); filter: drop-shadow(0 0 .2rem rgba(159, 212, 255, .5)); }
|
||||
.awayov__box h2 { font-size: .34rem; letter-spacing: .12em; }
|
||||
.awayov__box h2 { font-size: calc(.34 * var(--t)); letter-spacing: .12em; }
|
||||
|
||||
/* ---- simplified (DOM) field view ---------------------------------------------------------------------------------------- */
|
||||
.ff { position: absolute; inset: 0; overflow: hidden; user-select: none; }
|
||||
@@ -703,7 +709,7 @@
|
||||
display: flex; flex-direction: column; align-items: center; gap: .02rem; min-width: 3.6rem; padding: .16rem .6rem .2rem;
|
||||
background: rgba(20, 22, 22, .9); background: var(--pause-plate, linear-gradient(90deg, transparent, rgba(12, 14, 14, .95) 18%, rgba(12, 14, 14, .95) 82%, transparent)) center / 100% 100% no-repeat;
|
||||
}
|
||||
.pauseov__micro { font-family: var(--font-num); font-size: .16rem; letter-spacing: .5em; color: var(--mint-400); }
|
||||
.pauseov__plate h2 { font-size: .5rem; font-weight: 900; letter-spacing: .3em; color: #fff; text-shadow: 0 0 .2rem rgba(0, 0, 0, .8); }
|
||||
.pauseov__note { font-size: .17rem; color: var(--text-md); text-shadow: 0 1px 2px #000; }
|
||||
.pauseov__micro { font-family: var(--font-num); font-size: calc(.16 * var(--t)); letter-spacing: .5em; color: var(--mint-400); }
|
||||
.pauseov__plate h2 { font-size: calc(.5 * var(--t)); font-weight: 900; letter-spacing: .3em; color: #fff; text-shadow: 0 0 .2rem rgba(0, 0, 0, .8); }
|
||||
.pauseov__note { font-size: calc(.17 * var(--t)); color: var(--text-md); text-shadow: 0 1px 2px #000; }
|
||||
.pauseov__btns { display: flex; gap: .2rem; }
|
||||
@@ -27,7 +27,7 @@
|
||||
/* header: title | chapter tabs | close */
|
||||
.guide__head { display: flex; align-items: center; gap: .3rem; min-height: .62rem; }
|
||||
.guide__titles { display: flex; flex-direction: column; gap: .02rem; min-width: 2.4rem; }
|
||||
.guide__title { margin: 0; font-size: .3rem; font-weight: 900; letter-spacing: .12em; color: var(--text-hi); line-height: 1.1; }
|
||||
.guide__title { margin: 0; font-size: calc(.3 * var(--t)); font-weight: 900; letter-spacing: .12em; color: var(--text-hi); line-height: 1.1; }
|
||||
.guide__chapters { display: flex; gap: .06rem; margin: 0 auto; }
|
||||
.guide__chapter {
|
||||
position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .01rem;
|
||||
@@ -38,8 +38,8 @@
|
||||
}
|
||||
.guide__chapter:hover { color: var(--text-hi); background: rgba(78, 216, 175, .06); }
|
||||
.guide__chapter.is-on { color: var(--text-on-mint); background: var(--mint-500); border-color: var(--mint-500); }
|
||||
.guide__chname { font-size: .19rem; font-weight: 700; letter-spacing: .1em; }
|
||||
.guide__chmicro { font-family: var(--font-display); font-size: .1rem; letter-spacing: .24em; opacity: .75; }
|
||||
.guide__chname { font-size: calc(.19 * var(--t)); font-weight: 700; letter-spacing: .1em; }
|
||||
.guide__chmicro { font-family: var(--font-display); font-size: calc(.1 * var(--t)); letter-spacing: .24em; opacity: .75; }
|
||||
.guide__close {
|
||||
width: .56rem; height: .56rem; display: grid; place-items: center; flex: none;
|
||||
background: rgba(0, 0, 0, .35); border: 1px solid var(--line-2); color: var(--text-md);
|
||||
@@ -77,10 +77,10 @@
|
||||
.guide__foot { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: .3rem; min-height: .44rem; }
|
||||
.guide__label { display: flex; align-items: center; gap: .14rem; min-width: 0; }
|
||||
.guide__chtag {
|
||||
flex: none; padding: .02rem .12rem; font-size: .14rem; font-weight: 700; letter-spacing: .1em;
|
||||
flex: none; padding: .02rem .12rem; font-size: calc(.14 * var(--t)); font-weight: 700; letter-spacing: .1em;
|
||||
color: var(--mint-400); border: 1px solid var(--mint-800); background: var(--mint-a10);
|
||||
}
|
||||
.guide__ptitle { font-size: .22rem; font-weight: 700; letter-spacing: .06em; color: var(--text-hi); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.guide__ptitle { font-size: calc(.22 * var(--t)); font-weight: 700; letter-spacing: .06em; color: var(--text-hi); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.guide__dots { display: flex; align-items: center; gap: .05rem; }
|
||||
.guide__dot {
|
||||
width: .2rem; height: .08rem; padding: 0; border: 0; background: var(--line-2);
|
||||
@@ -89,12 +89,12 @@
|
||||
.guide__dot.is-first { margin-left: .14rem; }
|
||||
.guide__dot:hover { background: var(--line-3); }
|
||||
.guide__dot.is-on { width: .42rem; background: var(--mint-500); box-shadow: 0 0 .08rem rgba(23, 249, 183, .5); }
|
||||
.guide__count { justify-self: end; font-size: .24rem; color: var(--text-md); letter-spacing: .06em; }
|
||||
.guide__count { justify-self: end; font-size: calc(.24 * var(--t)); color: var(--text-md); letter-spacing: .06em; }
|
||||
|
||||
/* text fallback: the official tips when no tutorial page can be shown (none listed, or none of a page's copies loads) */
|
||||
.guide__stage--text { width: min(92vw, 10rem); max-height: calc(100vh - 2rem); overflow: auto; padding: .1rem .06rem; }
|
||||
.guide__tips ol { margin: .14rem 0 0; padding-left: .34rem; display: flex; flex-direction: column; gap: .08rem; }
|
||||
.guide__tips li { font-size: .18rem; line-height: 1.6; color: var(--text-md); }
|
||||
.guide__tips li { font-size: calc(.18 * var(--t)); line-height: 1.6; color: var(--text-md); }
|
||||
.guide__tips li::marker { color: var(--mint-500); font-family: var(--font-num); }
|
||||
|
||||
/* standard trigger */
|
||||
|
||||
@@ -634,3 +634,8 @@
|
||||
.lo-io__text:focus { border-color: var(--mint-700); }
|
||||
.lo-io__text::placeholder { color: var(--text-lo); }
|
||||
.lo-io__file { display: none; }
|
||||
|
||||
/* Per-operator voice, a browser-local preference in roster and DIY details. */
|
||||
.lo-voice { display: flex; flex-wrap: wrap; align-items: center; gap: .12rem; padding: .14rem 0; font-size: calc(.18 * var(--t)); }
|
||||
.lo-voice > small { flex-basis: 100%; color: var(--text-dim); font-size: calc(.15 * var(--t)); }
|
||||
.lo-voice .lo-select { min-width: 1.6rem; max-width: 100%; }
|
||||
@@ -71,9 +71,9 @@
|
||||
}
|
||||
.mode-card__icon .icon { width: .44rem; height: .44rem; }
|
||||
.mode-card__text { position: relative; display: flex; flex-direction: column; gap: .1rem; }
|
||||
.mode-card__name { font-size: .46rem; font-weight: 900; letter-spacing: .08em; line-height: 1.1; }
|
||||
.mode-card__desc { font-size: .17rem; color: var(--text-lo); line-height: 1.65; max-width: 3.6rem; }
|
||||
.mode-card__points { display: flex; flex-direction: column; gap: .05rem; margin-top: .06rem; font-size: .15rem; color: var(--text-md); }
|
||||
.mode-card__name { font-size: calc(.46 * var(--t)); font-weight: 900; letter-spacing: .08em; line-height: 1.1; }
|
||||
.mode-card__desc { font-size: calc(.17 * var(--t)); color: var(--text-lo); line-height: 1.65; max-width: 3.6rem; }
|
||||
.mode-card__points { display: flex; flex-direction: column; gap: .05rem; margin-top: .06rem; font-size: calc(.15 * var(--t)); color: var(--text-md); }
|
||||
.mode-card__points span::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
@@ -94,7 +94,7 @@
|
||||
padding: .04rem .12rem .04rem .08rem;
|
||||
background: var(--mint-500);
|
||||
color: var(--text-on-mint);
|
||||
font-size: .14rem;
|
||||
font-size: calc(.14 * var(--t));
|
||||
font-weight: 700;
|
||||
opacity: 0;
|
||||
transform: translateY(-.06rem);
|
||||
@@ -118,13 +118,13 @@
|
||||
.join-row { display: flex; gap: .16rem; align-items: stretch; }
|
||||
.join-row .field { flex: 1; }
|
||||
.join-row .btn { height: .64rem; }
|
||||
.join-foot { display: flex; align-items: center; flex-wrap: wrap; gap: .1rem; min-height: .32rem; font-size: .14rem; }
|
||||
.join-foot { display: flex; align-items: center; flex-wrap: wrap; gap: .1rem; min-height: .32rem; font-size: calc(.14 * var(--t)); }
|
||||
.code-chip {
|
||||
padding: .03rem .12rem .03rem .16rem;
|
||||
border: 1px solid var(--line-2);
|
||||
background: rgba(0, 0, 0, .3);
|
||||
color: var(--amber);
|
||||
font-size: .17rem;
|
||||
font-size: calc(.17 * var(--t));
|
||||
font-weight: 700;
|
||||
letter-spacing: .2em;
|
||||
transition: border-color var(--t-fast), background-color var(--t-fast);
|
||||
@@ -139,9 +139,9 @@
|
||||
background: rgba(8, 10, 9, .55);
|
||||
border: 1px solid var(--line);
|
||||
}
|
||||
.tips__head { display: flex; align-items: center; gap: .1rem; font-size: .16rem; font-weight: 700; color: var(--text-md); }
|
||||
.tips__head { display: flex; align-items: center; gap: .1rem; font-size: calc(.16 * var(--t)); font-weight: 700; color: var(--text-md); }
|
||||
.tips__head > .icon { width: .2rem; height: .2rem; color: var(--mint-500); }
|
||||
.tips__idx { margin-left: auto; font-size: .16rem; color: var(--text-md); }
|
||||
.tips__idx { margin-left: auto; font-size: calc(.16 * var(--t)); color: var(--text-md); }
|
||||
.tips__nav {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
@@ -155,7 +155,7 @@
|
||||
}
|
||||
.tips__nav:hover { color: var(--mint-400); border-color: var(--mint-700); }
|
||||
.tips__nav .icon { width: .18rem; height: .18rem; }
|
||||
.tips__text { margin-top: .1rem; font-size: .17rem; color: var(--text-lo); line-height: 1.6; animation: tip-in var(--t-slow) var(--ease-out); }
|
||||
.tips__text { margin-top: .1rem; font-size: calc(.17 * var(--t)); color: var(--text-lo); line-height: 1.6; animation: tip-in var(--t-slow) var(--ease-out); }
|
||||
@keyframes tip-in { from { opacity: 0; transform: translateX(.1rem); } }
|
||||
|
||||
/* ---- difficulty cards -------------------------------------------------------------------------------- */
|
||||
@@ -182,20 +182,20 @@
|
||||
.diff-card__bar { position: absolute; left: 0; top: 0; bottom: 0; width: .06rem; background: var(--d-color); opacity: .45; transition: opacity var(--t-med), box-shadow var(--t-med); }
|
||||
.diff-card__head { grid-column: 1 / -1; display: flex; align-items: center; gap: .1rem; min-width: 0; }
|
||||
.diff-card__glyph { width: .28rem; height: .28rem; color: var(--d-color); filter: drop-shadow(0 0 5px var(--d-color)); filter: drop-shadow(0 0 5px color-mix(in srgb, var(--d-color) 55%, transparent)); }
|
||||
.diff-card__name { font-size: .27rem; font-weight: 900; letter-spacing: .06em; color: var(--d-color); }
|
||||
.diff-card__code { font-size: .14rem; color: var(--text-dim); letter-spacing: .1em; }
|
||||
.diff-card__meta { margin-left: auto; display: flex; align-items: center; gap: .04rem; font-size: .14rem; color: var(--text-lo); white-space: nowrap; }
|
||||
.diff-card__meta .num { color: var(--text-hi); font-weight: 700; font-size: .17rem; }
|
||||
.diff-card__name { font-size: calc(.27 * var(--t)); font-weight: 900; letter-spacing: .06em; color: var(--d-color); }
|
||||
.diff-card__code { font-size: calc(.14 * var(--t)); color: var(--text-dim); letter-spacing: .1em; }
|
||||
.diff-card__meta { margin-left: auto; display: flex; align-items: center; gap: .04rem; font-size: calc(.14 * var(--t)); color: var(--text-lo); white-space: nowrap; }
|
||||
.diff-card__meta .num { color: var(--text-hi); font-weight: 700; font-size: calc(.17 * var(--t)); }
|
||||
.diff-card__hidden {
|
||||
margin-left: .08rem;
|
||||
padding: 0 .07rem;
|
||||
border: 1px solid transparent; border: 1px solid color-mix(in srgb, var(--d-color) 45%, transparent);
|
||||
color: var(--d-color); color: color-mix(in srgb, var(--d-color) 80%, #fff);
|
||||
font-size: .12rem;
|
||||
font-size: calc(.12 * var(--t));
|
||||
line-height: 1.6;
|
||||
}
|
||||
.diff-card__desc { grid-column: 1; font-size: .16rem; color: var(--text-md); }
|
||||
.diff-card__effects { grid-column: 1; display: flex; flex-wrap: wrap; gap: .04rem .2rem; font-size: .14rem; color: var(--text-lo); }
|
||||
.diff-card__desc { grid-column: 1; font-size: calc(.16 * var(--t)); color: var(--text-md); }
|
||||
.diff-card__effects { grid-column: 1; display: flex; flex-wrap: wrap; gap: .04rem .2rem; font-size: calc(.14 * var(--t)); color: var(--text-lo); }
|
||||
.diff-card__effects span::before { content: '·'; margin-right: .05rem; color: var(--d-color); font-weight: 900; }
|
||||
/* battlefield pool note (config.json modes[].stages): 战场固定为 战场#01 / 战场随机(共N张) — the last item of the
|
||||
effects row (no extra card height: the lobby must not scroll at 1280×720 / 1920×1080) */
|
||||
@@ -210,7 +210,7 @@
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: .03rem;
|
||||
font-size: .12rem;
|
||||
font-size: calc(.12 * var(--t));
|
||||
color: var(--text-dim);
|
||||
}
|
||||
.diff-card__check .icon {
|
||||
@@ -236,5 +236,5 @@
|
||||
|
||||
/* ---- create ------------------------------------------------------------------------------------------ */
|
||||
.create-box { margin-top: .3rem; display: flex; flex-direction: column; gap: .12rem; }
|
||||
.create-box .btn--xl { height: .92rem; font-size: .32rem; letter-spacing: .3em; }
|
||||
.create-box__hint { display: flex; align-items: center; justify-content: center; min-height: .3rem; font-size: .14rem; color: var(--text-lo); }
|
||||
.create-box .btn--xl { height: .92rem; font-size: calc(.32 * var(--t)); letter-spacing: .3em; }
|
||||
.create-box__hint { display: flex; align-items: center; justify-content: center; min-height: .3rem; font-size: calc(.14 * var(--t)); color: var(--text-lo); }
|
||||
@@ -11,14 +11,14 @@
|
||||
.result__main { position: relative; z-index: 1; flex: 1; min-height: 0; display: grid; grid-template-columns: 5.6rem 1fr; gap: .5rem; padding: .36rem .5rem .3rem .6rem; }
|
||||
.result__hero { display: flex; flex-direction: column; align-items: flex-start; gap: .16rem; padding-top: .3rem; min-height: 0; }
|
||||
.result__logoimg { height: 1.1rem; width: auto; opacity: .92; }
|
||||
.result__headline { font-size: .96rem; font-weight: 900; letter-spacing: .12em; line-height: 1.05; color: var(--mint-400); text-shadow: 0 0 .4rem rgba(23, 249, 183, .45); animation: head-in 600ms var(--ease-out); }
|
||||
.result__headline { font-size: calc(.96 * var(--t)); font-weight: 900; letter-spacing: .12em; line-height: 1.05; color: var(--mint-400); text-shadow: 0 0 .4rem rgba(23, 249, 183, .45); animation: head-in 600ms var(--ease-out); }
|
||||
.result.is-lose .result__headline { color: var(--red-premium); text-shadow: 0 0 .4rem rgba(255, 60, 60, .4); }
|
||||
@keyframes head-in { from { opacity: 0; letter-spacing: .5em; } }
|
||||
.result__sub { font-size: .2rem; color: var(--text-lo); letter-spacing: .1em; }
|
||||
.result__sub { font-size: calc(.2 * var(--t)); color: var(--text-lo); letter-spacing: .1em; }
|
||||
.result__rounds { display: flex; align-items: baseline; gap: .12rem; margin-top: .1rem; }
|
||||
.result__rlabel { font-size: .22rem; color: var(--text-md); letter-spacing: .14em; }
|
||||
.result__rnum { font-size: 1.5rem; line-height: .9; font-weight: 700; color: #fff; }
|
||||
.result__rof { font-size: .4rem; color: var(--text-dim); }
|
||||
.result__rlabel { font-size: calc(.22 * var(--t)); color: var(--text-md); letter-spacing: .14em; }
|
||||
.result__rnum { font-size: calc(1.5 * var(--t)); line-height: .9; font-weight: 700; color: #fff; }
|
||||
.result__rof { font-size: calc(.4 * var(--t)); color: var(--text-dim); }
|
||||
.result__medals { display: flex; gap: .24rem; margin-top: .12rem; }
|
||||
.medal { position: relative; width: 1.1rem; display: flex; flex-direction: column; align-items: center; gap: .08rem; }
|
||||
.medal img { width: .96rem; height: .96rem; object-fit: cover; border-radius: 50%; border: 3px solid var(--line-3); background: #151a18; filter: grayscale(.7) brightness(.7); }
|
||||
@@ -27,8 +27,8 @@
|
||||
.medal__check { position: absolute; right: .04rem; top: .64rem; width: .32rem; height: .32rem; display: grid; place-items: center; background: var(--text-dim); color: #fff; border-radius: 50%; }
|
||||
.medal.is-done .medal__check { background: var(--gold); color: #1d1400; }
|
||||
.medal__check .icon { width: .2rem; height: .2rem; }
|
||||
.medal__label { font-size: .14rem; color: var(--text-lo); }
|
||||
.result__time { font-size: .16rem; }
|
||||
.medal__label { font-size: calc(.14 * var(--t)); color: var(--text-lo); }
|
||||
.result__time { font-size: calc(.16 * var(--t)); }
|
||||
.result__time b { color: var(--text-hi); }
|
||||
|
||||
.result__players { display: flex; flex-direction: column; gap: .1rem; min-height: 0; overflow: auto; padding-right: .06rem; }
|
||||
@@ -45,34 +45,34 @@
|
||||
.rcard__head { display: grid; grid-template-columns: auto minmax(1.5rem, 2.1rem) minmax(0, 1fr) auto 2.3rem; gap: .18rem; align-items: center; }
|
||||
.rcard__head .pavatar { --pa: .6rem; }
|
||||
.rcard__who { display: flex; flex-direction: column; gap: .03rem; min-width: 0; }
|
||||
.rcard__name { font-size: .22rem; display: flex; align-items: center; gap: .08rem; }
|
||||
.rcard__ai, .rcard__you { font-size: .11rem; padding: 0 .05rem; font-weight: 700; }
|
||||
.rcard__name { font-size: calc(.22 * var(--t)); display: flex; align-items: center; gap: .08rem; }
|
||||
.rcard__ai, .rcard__you { font-size: calc(.11 * var(--t)); padding: 0 .05rem; font-weight: 700; }
|
||||
.rcard__ai { background: #a9c3d8; color: #0b1520; }
|
||||
.rcard__you { background: var(--mint-500); color: var(--text-on-mint); }
|
||||
.rcard__band { display: inline-flex; align-items: center; gap: .06rem; font-size: .15rem; color: var(--text-lo); }
|
||||
.rcard__band { display: inline-flex; align-items: center; gap: .06rem; font-size: calc(.15 * var(--t)); color: var(--text-lo); }
|
||||
.rcard__round { display: flex; flex-direction: column; align-items: center; }
|
||||
.rcard__round b { font-size: .34rem; line-height: 1; color: #fff; }
|
||||
.rcard__gain { display: flex; gap: .08rem; margin-top: .03rem; font-size: .13rem; color: var(--gold); }
|
||||
.rcard__round b { font-size: calc(.34 * var(--t)); line-height: 1; color: #fff; }
|
||||
.rcard__gain { display: flex; gap: .08rem; margin-top: .03rem; font-size: calc(.13 * var(--t)); color: var(--gold); }
|
||||
.rcard__gain span { display: inline-flex; align-items: center; gap: .02rem; }
|
||||
.rcard__gain .icon { width: .14rem; height: .14rem; }
|
||||
.rcard__gain b { font-size: .14rem; color: var(--gold); }
|
||||
.rcard__gain b { font-size: calc(.14 * var(--t)); color: var(--gold); }
|
||||
.rcard__title { display: flex; align-items: center; gap: .08rem; padding: .06rem .14rem .06rem .06rem; border: 1px solid rgba(255, 198, 0, .4); background: rgba(255, 198, 0, .06); min-width: 0; }
|
||||
.rcard__title--none { border-style: dashed; border-color: var(--line); background: none; height: .64rem; }
|
||||
.rcard__title > span { display: flex; flex-direction: column; }
|
||||
.rcard__title b { font-size: .19rem; color: var(--gold); }
|
||||
.rcard__title b { font-size: calc(.19 * var(--t)); color: var(--gold); }
|
||||
.rcard__ticon { width: .5rem; height: .5rem; object-fit: contain; }
|
||||
.rcard__title .icon { width: .3rem; height: .3rem; color: var(--gold); }
|
||||
.rcard__mid { display: flex; flex-direction: column; justify-content: center; gap: .06rem; min-width: 0; }
|
||||
.rcard__lineup .uthumb { --ut: .46rem; }
|
||||
.rcard__lineup { display: flex; flex-wrap: wrap; gap: .05rem; }
|
||||
.rcard__noinfo { font-family: var(--font-display); font-size: .14rem; letter-spacing: .3em; color: var(--text-dim); padding: .14rem .2rem; border: 1px dashed var(--line-2); }
|
||||
.rcard__noinfo { font-family: var(--font-display); font-size: calc(.14 * var(--t)); letter-spacing: .3em; color: var(--text-dim); padding: .14rem .2rem; border: 1px dashed var(--line-2); }
|
||||
.rcard__bonds { display: flex; gap: .05rem; flex-wrap: wrap; }
|
||||
.rbond { display: inline-flex; align-items: center; gap: .04rem; padding: .01rem .07rem .01rem .03rem; border: 1px solid var(--line-2); font-size: .13rem; color: var(--text-lo); }
|
||||
.rbond { display: inline-flex; align-items: center; gap: .04rem; padding: .01rem .07rem .01rem .03rem; border: 1px solid var(--line-2); font-size: calc(.13 * var(--t)); color: var(--text-lo); }
|
||||
.rbond .bglyph { width: .2rem; height: .2rem; }
|
||||
.rbond.is-on { border-color: var(--mint-800); color: var(--mint-400); }
|
||||
.rcard__stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1px; }
|
||||
.rstat { display: flex; align-items: baseline; justify-content: space-between; gap: .06rem; padding: .05rem .1rem; background: #0e1210; box-shadow: 0 0 0 1px var(--line); font-size: .13rem; color: var(--text-lo); white-space: nowrap; min-width: 0; }
|
||||
.rstat b { font-size: .18rem; color: #fff; }
|
||||
.rstat { display: flex; align-items: baseline; justify-content: space-between; gap: .06rem; padding: .05rem .1rem; background: #0e1210; box-shadow: 0 0 0 1px var(--line); font-size: calc(.13 * var(--t)); color: var(--text-lo); white-space: nowrap; min-width: 0; }
|
||||
.rstat b { font-size: calc(.18 * var(--t)); color: #fff; }
|
||||
.rstat.is-best b { color: var(--gold); }
|
||||
.result__foot { margin-top: auto; padding-top: .2rem; display: flex; }
|
||||
.result__foot .btn { min-width: 3.4rem; }
|
||||
+23
-23
@@ -17,7 +17,7 @@
|
||||
background: rgba(0, 0, 0, .38);
|
||||
border: 1px solid var(--line);
|
||||
}
|
||||
.invite__label { grid-column: 1 / -1; display: flex; align-items: center; gap: .07rem; font-size: .14rem; color: var(--text-lo); }
|
||||
.invite__label { grid-column: 1 / -1; display: flex; align-items: center; gap: .07rem; font-size: calc(.14 * var(--t)); color: var(--text-lo); }
|
||||
.invite__label .icon { width: .16rem; height: .16rem; color: var(--mint-500); }
|
||||
.invite__code { display: flex; gap: .06rem; }
|
||||
.invite__code span {
|
||||
@@ -28,13 +28,13 @@
|
||||
background: linear-gradient(180deg, rgba(78, 216, 175, .1), rgba(78, 216, 175, .02));
|
||||
border-bottom: 2px solid var(--mint-600);
|
||||
color: var(--mint-400);
|
||||
font-size: .44rem;
|
||||
font-size: calc(.44 * var(--t));
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
text-shadow: 0 0 .16rem rgba(23, 249, 183, .45);
|
||||
}
|
||||
.invite__btns { display: flex; flex-direction: column; gap: .06rem; }
|
||||
.solo-note { display: flex; flex-direction: column; align-items: flex-end; gap: .03rem; font-size: .16rem; color: var(--text-lo); }
|
||||
.solo-note { display: flex; flex-direction: column; align-items: flex-end; gap: .03rem; font-size: calc(.16 * var(--t)); color: var(--text-lo); }
|
||||
|
||||
/* ---- seats ------------------------------------------------------------------------------------------------ */
|
||||
.seats {
|
||||
@@ -82,7 +82,7 @@
|
||||
transition: background var(--t-med), box-shadow var(--t-med);
|
||||
}
|
||||
.seat__head { align-self: stretch; display: flex; align-items: center; gap: .1rem; }
|
||||
.seat__no { font-size: .22rem; font-weight: 700; color: var(--text-md); padding-left: .08rem; border-left: 3px solid var(--line-3); line-height: 1.1; }
|
||||
.seat__no { font-size: calc(.22 * var(--t)); font-weight: 700; color: var(--text-md); padding-left: .08rem; border-left: 3px solid var(--line-3); line-height: 1.1; }
|
||||
.seat__host {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
@@ -91,7 +91,7 @@
|
||||
padding: .02rem .09rem;
|
||||
background: var(--gold);
|
||||
color: #1d1400;
|
||||
font-size: .13rem;
|
||||
font-size: calc(.13 * var(--t));
|
||||
font-weight: 700;
|
||||
}
|
||||
.seat__host .icon { width: .15rem; height: .15rem; }
|
||||
@@ -131,13 +131,13 @@
|
||||
background: rgba(159, 212, 255, .1);
|
||||
border: 1px solid rgba(159, 212, 255, .35);
|
||||
color: var(--ice);
|
||||
font-size: .14rem;
|
||||
font-size: calc(.14 * var(--t));
|
||||
font-weight: 700;
|
||||
}
|
||||
.seat__bot-label .icon { width: .16rem; height: .16rem; }
|
||||
.seat__who { display: flex; align-items: center; gap: .08rem; max-width: 100%; }
|
||||
.seat__name { font-size: .26rem; font-weight: 700; max-width: 3rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.seat__you { flex: none; padding: .01rem .07rem; background: var(--mint-500); color: var(--text-on-mint); font-size: .13rem; font-weight: 700; }
|
||||
.seat__name { font-size: calc(.26 * var(--t)); font-weight: 700; max-width: 3rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.seat__you { flex: none; padding: .01rem .07rem; background: var(--mint-500); color: var(--text-on-mint); font-size: calc(.13 * var(--t)); font-weight: 700; }
|
||||
.seat__foot {
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
@@ -150,7 +150,7 @@
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
.seat__foot .tt-anchor { position: absolute; right: .14rem; bottom: .14rem; }
|
||||
.seat__state { display: flex; align-items: center; gap: .06rem; font-size: .18rem; font-weight: 700; letter-spacing: .06em; }
|
||||
.seat__state { display: flex; align-items: center; gap: .06rem; font-size: calc(.18 * var(--t)); font-weight: 700; letter-spacing: .06em; }
|
||||
.seat__state .icon { width: .22rem; height: .22rem; }
|
||||
.seat__state--ready { color: var(--mint-400); }
|
||||
.seat__state--waiting { color: var(--text-lo); }
|
||||
@@ -185,7 +185,7 @@
|
||||
animation: radar-sweep 4s linear infinite;
|
||||
}
|
||||
@keyframes radar-sweep { to { transform: rotate(360deg); } }
|
||||
.seat__wait { font-size: .2rem; color: var(--text-lo); letter-spacing: .08em; }
|
||||
.seat__wait { font-size: calc(.2 * var(--t)); color: var(--text-lo); letter-spacing: .08em; }
|
||||
.seat--empty .seat__foot { border-top-style: dashed; }
|
||||
|
||||
.solo-brief {
|
||||
@@ -202,9 +202,9 @@
|
||||
border: 1px solid var(--line);
|
||||
animation: seat-in var(--t-slow) 80ms var(--ease-out) backwards;
|
||||
}
|
||||
.solo-brief h2 { font-size: .36rem; font-weight: 900; letter-spacing: .06em; }
|
||||
.solo-brief p { font-size: .18rem; color: var(--text-md); }
|
||||
.solo-brief ul { margin: .06rem 0 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: .08rem; font-size: .16rem; color: var(--text-lo); }
|
||||
.solo-brief h2 { font-size: calc(.36 * var(--t)); font-weight: 900; letter-spacing: .06em; }
|
||||
.solo-brief p { font-size: calc(.18 * var(--t)); color: var(--text-md); }
|
||||
.solo-brief ul { margin: .06rem 0 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: .08rem; font-size: calc(.16 * var(--t)); color: var(--text-lo); }
|
||||
.solo-brief li::before { content: ''; display: inline-block; width: .06rem; height: .06rem; margin: 0 .12rem .02rem 0; background: var(--mint-600); transform: rotate(45deg); vertical-align: middle; }
|
||||
.solo-brief b { color: var(--text-hi); }
|
||||
|
||||
@@ -217,7 +217,7 @@
|
||||
justify-content: center;
|
||||
gap: .14rem;
|
||||
padding: 0 .6rem .16rem;
|
||||
font-size: .15rem;
|
||||
font-size: calc(.15 * var(--t));
|
||||
color: var(--text-lo);
|
||||
}
|
||||
.specbar__label { display: flex; align-items: baseline; gap: .06rem; font-weight: 700; color: var(--text-md); }
|
||||
@@ -260,10 +260,10 @@
|
||||
background: linear-gradient(90deg, var(--mint-700), transparent 30%, transparent 70%, var(--mint-700));
|
||||
}
|
||||
.room-bar__left { display: flex; align-items: center; gap: .22rem; min-width: 0; }
|
||||
.room-bar__label { display: flex; flex-direction: column; font-size: .17rem; font-weight: 700; color: var(--text-md); line-height: 1.35; }
|
||||
.room-bar__label { display: flex; flex-direction: column; font-size: calc(.17 * var(--t)); font-weight: 700; color: var(--text-md); line-height: 1.35; }
|
||||
.room-bar__center { display: flex; flex-direction: column; align-items: center; gap: .06rem; }
|
||||
.room-bar__right { justify-self: end; display: flex; align-items: center; gap: .16rem; }
|
||||
.room-bar__status { font-size: .15rem; }
|
||||
.room-bar__status { font-size: calc(.15 * var(--t)); }
|
||||
.room-bar__status .icon { width: .18rem; height: .18rem; margin-right: .05rem; vertical-align: -.03rem; }
|
||||
|
||||
.dpick { display: flex; border: 1px solid var(--line-2); background: rgba(0, 0, 0, .3); }
|
||||
@@ -279,7 +279,7 @@
|
||||
border-right: 1px solid var(--line);
|
||||
background: transparent;
|
||||
color: var(--text-lo);
|
||||
font-size: .18rem;
|
||||
font-size: calc(.18 * var(--t));
|
||||
font-weight: 700;
|
||||
letter-spacing: .08em;
|
||||
transition: color var(--t-fast), background-color var(--t-med), box-shadow var(--t-med);
|
||||
@@ -295,19 +295,19 @@
|
||||
box-shadow: inset 0 -3px 0 var(--d-color);
|
||||
}
|
||||
.dpick__opt.is-active .icon { opacity: 1; filter: drop-shadow(0 0 5px var(--d-color)); }
|
||||
.dpick--ro { border: 0; background: none; gap: .18rem; align-items: center; font-size: .14rem; }
|
||||
.dpick--ro { border: 0; background: none; gap: .18rem; align-items: center; font-size: calc(.14 * var(--t)); }
|
||||
/* 「AI 队友最后选择」 (GitHub #338, co-op): one switch styled like a difficulty option, under the difficulty picker */
|
||||
.room-bar__opts { display: flex; flex-direction: column; align-items: flex-start; gap: .08rem; min-width: 0; }
|
||||
.ailast { display: flex; align-items: center; gap: .12rem; font-size: .14rem; }
|
||||
.ailast__opt { --d-color: var(--mint-400); height: .36rem; padding: 0 .14rem; border: 1px solid var(--line-2); background: rgba(0, 0, 0, .3); font-size: .15rem; letter-spacing: .04em; }
|
||||
.ailast { display: flex; align-items: center; gap: .12rem; font-size: calc(.14 * var(--t)); }
|
||||
.ailast__opt { --d-color: var(--mint-400); height: .36rem; padding: 0 .14rem; border: 1px solid var(--line-2); background: rgba(0, 0, 0, .3); font-size: calc(.15 * var(--t)); letter-spacing: .04em; }
|
||||
.ailast__opt:last-child { border-right: 1px solid var(--line-2); }
|
||||
.ailast__opt .icon { opacity: .25; }
|
||||
.ailast__opt .icon.is-on { opacity: 1; }
|
||||
|
||||
.ready-count[hidden] { display: none; }
|
||||
.ready-count { display: flex; align-items: baseline; gap: .06rem; font-size: .16rem; }
|
||||
.ready-count b { font-size: .36rem; line-height: 1; color: var(--mint-400); text-shadow: 0 0 .14rem rgba(23, 249, 183, .45); }
|
||||
.ready-count .t-dim { font-size: .22rem; }
|
||||
.ready-count { display: flex; align-items: baseline; gap: .06rem; font-size: calc(.16 * var(--t)); }
|
||||
.ready-count b { font-size: calc(.36 * var(--t)); line-height: 1; color: var(--mint-400); text-shadow: 0 0 .14rem rgba(23, 249, 183, .45); }
|
||||
.ready-count .t-dim { font-size: calc(.22 * var(--t)); }
|
||||
.ready-count__icons { display: flex; gap: .02rem; margin-left: .12rem; align-self: center; }
|
||||
.ready-count__icons .icon { width: .28rem; height: .28rem; color: var(--line-3); transition: color var(--t-med); }
|
||||
.ready-count__icons .icon.is-on { color: var(--mint-400); filter: drop-shadow(0 0 4px var(--mint-glow)); }
|
||||
|
||||
@@ -197,7 +197,7 @@
|
||||
.title-cn em { font-style: normal; color: var(--mint-500); text-shadow: 0 0 .26rem rgba(23, 249, 183, .45); }
|
||||
.title-tag {
|
||||
margin-top: .12rem;
|
||||
font-size: .19rem;
|
||||
font-size: calc(.19 * var(--t));
|
||||
color: var(--text-lo);
|
||||
letter-spacing: .1em;
|
||||
animation: rise-in 600ms 200ms var(--ease-out) backwards;
|
||||
@@ -238,21 +238,23 @@
|
||||
background: rgba(246, 163, 41, .08);
|
||||
border: 1px solid rgba(246, 163, 41, .35);
|
||||
color: var(--amber);
|
||||
font-size: .16rem;
|
||||
font-size: calc(.16 * var(--t));
|
||||
}
|
||||
.title-invite .icon { width: .2rem; height: .2rem; }
|
||||
.title-invite b { font-size: .2rem; letter-spacing: .2em; color: #ffd08a; }
|
||||
.title-conn { display: flex; align-items: center; justify-content: center; gap: .1rem; min-height: .32rem; font-size: .14rem; color: var(--text-lo); }
|
||||
.title-invite b { font-size: calc(.2 * var(--t)); letter-spacing: .2em; color: #ffd08a; }
|
||||
.title-conn { display: flex; align-items: center; justify-content: center; gap: .1rem; min-height: .32rem; font-size: calc(.14 * var(--t)); color: var(--text-lo); }
|
||||
/* 统计 (the local stats page) beside the language menu in the top-right corner */
|
||||
.title-corner__tools { display: flex; align-items: center; justify-content: flex-end; gap: .1rem; margin-bottom: .1rem; }
|
||||
.title-corner__tools .title-lang { margin-bottom: 0; }
|
||||
/* language menu (中文 | English | …, ui/lang.js) in the top-right corner; more than four languages make it a list */
|
||||
/* language menu (中文 | English | …, ui/lang.js) in the top-right corner; more than four languages make it a list.
|
||||
The height is max(design, 1.4em) so the buttons still hold their text at 设置 →「文字大小」 大 / 特大 (on a phone
|
||||
1rem is clamped at 40 px and .3rem is only 12 px, less than the enlarged label). */
|
||||
.title-lang { display: inline-flex; margin-bottom: .1rem; }
|
||||
.title-lang button { min-width: .74rem; height: .3rem; font-size: .14rem; cursor: pointer; }
|
||||
.lang-select select { height: .38rem; min-width: 1.6rem; padding: 0 .12rem; background: #0a0d0c; border: 0; color: var(--text-hi); font-size: .16rem; cursor: pointer; }
|
||||
.title-lang.lang-select select { height: .3rem; font-size: .14rem; }
|
||||
.title-lang button { min-width: .74rem; height: max(.3rem, 1.4em); font-size: calc(.14 * var(--t)); cursor: pointer; }
|
||||
.lang-select select { height: max(.38rem, 1.4em); min-width: 1.6rem; padding: 0 .12rem; background: #0a0d0c; border: 0; color: var(--text-hi); font-size: calc(.16 * var(--t)); cursor: pointer; }
|
||||
.title-lang.lang-select select { height: max(.3rem, 1.4em); font-size: calc(.14 * var(--t)); }
|
||||
html[data-script="alphabetic"] .title-login .btn--xl { letter-spacing: .3em; }
|
||||
.title-conn .ping { height: .26rem; font-size: .15rem; }
|
||||
.title-conn .ping { height: max(.26rem, 1.6em); font-size: calc(.15 * var(--t)); }
|
||||
|
||||
.title-foot {
|
||||
position: absolute;
|
||||
@@ -263,7 +265,7 @@ html[data-script="alphabetic"] .title-login .btn--xl { letter-spacing: .3em; }
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
font-size: .13rem;
|
||||
font-size: calc(.13 * var(--t));
|
||||
color: var(--text-dim);
|
||||
}
|
||||
/* a development build (shared/constants.js DEV_BUILD): an unmissable tag in the footer */
|
||||
|
||||
+18
-2
@@ -125,6 +125,22 @@ html {
|
||||
background: var(--bg-0);
|
||||
}
|
||||
|
||||
/* ---- text root: 设置 →「文字大小」 ---------------------------------------------------------------
|
||||
1rem above is the LAYOUT root, and five modules convert it to pixels with getComputedStyle:
|
||||
ui/fieldHost.js hudPadding / hudBands (the prep camera's HUD clearance), ui/gameLogic/panel.js
|
||||
panelSlots (which side the detail card docks), ui/underframe.js (the tile labels' pixel budget) and
|
||||
ui/fallbackField.js (the DOM board) — raising it would move the board, not just the text.
|
||||
--t is the TEXT root: it appears only in font-size declarations, so a player can enlarge the
|
||||
interface text without touching any layout. The field itself is sized from host.clientWidth /
|
||||
clientHeight (render/app.js), which no font-size can reach.
|
||||
小 = 1rem reproduces the design's own sizes exactly, on every device. The other steps keep the
|
||||
desktop gentle and lift the phone: 1rem there is clamped at 40 px by the rule above, which is what
|
||||
made the .18rem body text 7.2 CSS px. */
|
||||
:root { --t: 1rem; }
|
||||
:root[data-text="md"] { --t: max(1rem, 64px); }
|
||||
:root[data-text="lg"] { --t: max(calc(1rem * 1.15), 76px); }
|
||||
:root[data-text="xl"] { --t: max(calc(1rem * 1.3), 88px); }
|
||||
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
@@ -134,7 +150,7 @@ body {
|
||||
background: var(--bg-0);
|
||||
color: var(--text-hi);
|
||||
font-family: var(--font-cjk);
|
||||
font-size: .18rem;
|
||||
font-size: calc(.18 * var(--t));
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
@@ -170,7 +186,7 @@ h1, h2, h3, h4, p { margin: 0; }
|
||||
.display { font-family: var(--font-display); text-transform: uppercase; letter-spacing: .12em; }
|
||||
.micro {
|
||||
font-family: var(--font-display);
|
||||
font-size: .11rem;
|
||||
font-size: calc(.11 * var(--t));
|
||||
line-height: 1.2;
|
||||
letter-spacing: .18em;
|
||||
text-transform: uppercase;
|
||||
|
||||
+25
-1
@@ -1183,5 +1183,29 @@
|
||||
"精英2": "E2",
|
||||
"精英2 Lv.60": "E2 Lv.60",
|
||||
"开始模拟前可调整干员携带的技能与模组,以及潜能与练度;干员的局内等级不可调整": "Before the simulation starts you can change the Skills and Modules your Operators carry, and their potential and development; Operator levels in the match cannot be changed",
|
||||
"默认满潜能、精英2 Lv.60(满加成)。官方的潜能取你自己的潜能,练度是自持有加成(已持有的同名干员的养成,攻击 / 防御 / 生命单独乘算);未持有的特许干员在官方按潜能1、没有加成,要照官方打请手动设为潜能1、未精英化。": "Default: max Potential, Elite 2 Lv.60 (the full bonus). Officially, Potential is your own and development is the owned-operator bonus (your training of the same Operator: ATK / DEF / HP, a separate multiplier); a licensed Operator you do not own fights at Potential 1 with no bonus — to play it that way, set Potential 1 and Non-Elite here."
|
||||
"默认满潜能、精英2 Lv.60(满加成)。官方的潜能取你自己的潜能,练度是自持有加成(已持有的同名干员的养成,攻击 / 防御 / 生命单独乘算);未持有的特许干员在官方按潜能1、没有加成,要照官方打请手动设为潜能1、未精英化。": "Default: max Potential, Elite 2 Lv.60 (the full bonus). Officially, Potential is your own and development is the owned-operator bonus (your training of the same Operator: ATK / DEF / HP, a separate multiplier); a licensed Operator you do not own fights at Potential 1 with no bonus — to play it that way, set Potential 1 and Non-Elite here.",
|
||||
"文字大小": "Text Size",
|
||||
"小": "Small",
|
||||
"大": "Large",
|
||||
"特大": "Extra Large",
|
||||
"调整界面文字大小,棋盘保持原比例。": "Adjust interface text size while keeping the board at its original scale.",
|
||||
"此干员语音": "Operator voice",
|
||||
"跟随全局": "Use global setting",
|
||||
"仅保存在此浏览器,缺失的日语语音会回退到中文。": "Saved in this browser only. Missing Japanese lines use Chinese.",
|
||||
"添加到桌面": "Add to Home Screen",
|
||||
"恢复本机对局": "Resume local match",
|
||||
"此对局仍在其他窗口中,或当前浏览器无法安全恢复。请关闭原窗口后重试。": "This match is still open in another window, or this browser cannot safely resume it. Close the original window and retry.",
|
||||
"选择已关闭窗口的对局。服务器重启或对局过期后无法恢复。": "Select a match from a closed window. Matches cannot be resumed after a server restart or expiry.",
|
||||
"{name} · 同盟 {code}": "{name} · Alliance {code}",
|
||||
"重刷本局": "Reroll setup",
|
||||
"重刷本局投票:{agreed}/{total}": "Reroll vote: {agreed}/{total}",
|
||||
"全员同意后重新随机战场、领袖、特训敌人和禁用;确认倒计时已暂停。": "With unanimous consent, reroll the field, bosses, training enemies and bans. The confirmation timer is paused.",
|
||||
"保留房间和个人配置,重新随机全部开局信息。": "Keep the room and player settings, and reroll the full opening setup.",
|
||||
"已同意": "Agreed",
|
||||
"待同意": "Waiting",
|
||||
"全体真人玩家一致同意后刷新,随机结果可能重复。": "All human players must agree. Random results may repeat.",
|
||||
"请等待所有玩家连接后再发起投票": "Wait for all players to connect before starting a vote.",
|
||||
"同意重刷": "Agree to reroll",
|
||||
"拒绝重刷": "Reject reroll",
|
||||
"取消投票": "Cancel vote"
|
||||
}
|
||||
+25
-1
@@ -1189,5 +1189,29 @@
|
||||
"精英2": "昇進2",
|
||||
"精英2 Lv.60": "昇進2 Lv60",
|
||||
"开始模拟前可调整干员携带的技能与模组,以及潜能与练度;干员的局内等级不可调整": "シミュレーション開始前に、オペレーターのスキルとモジュール、潜在と育成度を調整できます(局内のオペレーターのレベルは変更不可)",
|
||||
"默认满潜能、精英2 Lv.60(满加成)。官方的潜能取你自己的潜能,练度是自持有加成(已持有的同名干员的养成,攻击 / 防御 / 生命单独乘算);未持有的特许干员在官方按潜能1、没有加成,要照官方打请手动设为潜能1、未精英化。": "初期値は潜在最大・昇進2 Lv60(ボーナス最大)。公式では潜在はあなた自身の潜在、育成度は所持オペレーターボーナス(同名オペレーターの育成度による攻撃・防御・HPの独立した乗算)です。未所持のプリセットオペレーターは公式では潜在1・ボーナスなしのため、公式どおりにするには潜在1・未昇進に設定してください。"
|
||||
"默认满潜能、精英2 Lv.60(满加成)。官方的潜能取你自己的潜能,练度是自持有加成(已持有的同名干员的养成,攻击 / 防御 / 生命单独乘算);未持有的特许干员在官方按潜能1、没有加成,要照官方打请手动设为潜能1、未精英化。": "初期値は潜在最大・昇進2 Lv60(ボーナス最大)。公式では潜在はあなた自身の潜在、育成度は所持オペレーターボーナス(同名オペレーターの育成度による攻撃・防御・HPの独立した乗算)です。未所持のプリセットオペレーターは公式では潜在1・ボーナスなしのため、公式どおりにするには潜在1・未昇進に設定してください。",
|
||||
"文字大小": "文字サイズ",
|
||||
"小": "小",
|
||||
"大": "大",
|
||||
"特大": "特大",
|
||||
"调整界面文字大小,棋盘保持原比例。": "画面の文字サイズを変更します。盤面の縮尺は変わりません。",
|
||||
"此干员语音": "このオペレーターの音声",
|
||||
"跟随全局": "全体設定に従う",
|
||||
"仅保存在此浏览器,缺失的日语语音会回退到中文。": "このブラウザーのみに保存されます。日本語音声がない場合は中国語に切り替わります。",
|
||||
"添加到桌面": "ホーム画面に追加",
|
||||
"恢复本机对局": "この端末の対戦を復帰",
|
||||
"此对局仍在其他窗口中,或当前浏览器无法安全恢复。请关闭原窗口后重试。": "別のウィンドウで対戦中か、このブラウザーでは安全に復帰できません。元のウィンドウを閉じて再試行してください。",
|
||||
"选择已关闭窗口的对局。服务器重启或对局过期后无法恢复。": "閉じたウィンドウの対戦を選択してください。サーバー再起動後や期限切れの場合は復帰できません。",
|
||||
"{name} · 同盟 {code}": "{name} · 同盟 {code}",
|
||||
"重刷本局": "開始条件を再抽選",
|
||||
"重刷本局投票:{agreed}/{total}": "再抽選の投票:{agreed}/{total}",
|
||||
"全员同意后重新随机战场、领袖、特训敌人和禁用;确认倒计时已暂停。": "全員の同意で戦場、ボス、特訓の敵と禁止対象を再抽選します。確認の制限時間は一時停止中です。",
|
||||
"保留房间和个人配置,重新随机全部开局信息。": "ルームと個人設定を維持して、開始条件をすべて再抽選します。",
|
||||
"已同意": "同意済み",
|
||||
"待同意": "同意待ち",
|
||||
"全体真人玩家一致同意后刷新,随机结果可能重复。": "人間プレイヤー全員の同意が必要です。同じ結果になる場合があります。",
|
||||
"请等待所有玩家连接后再发起投票": "全プレイヤーの接続を待ってから投票を開始してください。",
|
||||
"同意重刷": "再抽選に同意",
|
||||
"拒绝重刷": "再抽選を拒否",
|
||||
"取消投票": "投票を取り消す"
|
||||
}
|
||||
+25
-1
@@ -1185,5 +1185,29 @@
|
||||
"精英2": "정예 2",
|
||||
"精英2 Lv.60": "정예 2 Lv.60",
|
||||
"开始模拟前可调整干员携带的技能与模组,以及潜能与练度;干员的局内等级不可调整": "시뮬레이션 시작 전에 오퍼레이터의 스킬과 모듈, 잠재와 육성도를 조정할 수 있습니다. 게임 내 오퍼레이터 레벨은 조정할 수 없습니다.",
|
||||
"默认满潜能、精英2 Lv.60(满加成)。官方的潜能取你自己的潜能,练度是自持有加成(已持有的同名干员的养成,攻击 / 防御 / 生命单独乘算);未持有的特许干员在官方按潜能1、没有加成,要照官方打请手动设为潜能1、未精英化。": "기본값은 최대 잠재 · 2차 정예화 Lv.60(최대 보너스)입니다. 공식 규칙에서 잠재는 자신의 잠재, 육성도는 보유 오퍼레이터 보너스(같은 오퍼레이터의 육성도에 따른 공격 / 방어 / HP 별도 배율)입니다. 보유하지 않은 프리셋 오퍼레이터는 공식적으로 잠재 1·보너스 없음이므로, 공식대로 하려면 잠재 1, 비정예화로 직접 설정하세요."
|
||||
"默认满潜能、精英2 Lv.60(满加成)。官方的潜能取你自己的潜能,练度是自持有加成(已持有的同名干员的养成,攻击 / 防御 / 生命单独乘算);未持有的特许干员在官方按潜能1、没有加成,要照官方打请手动设为潜能1、未精英化。": "기본값은 최대 잠재 · 2차 정예화 Lv.60(최대 보너스)입니다. 공식 규칙에서 잠재는 자신의 잠재, 육성도는 보유 오퍼레이터 보너스(같은 오퍼레이터의 육성도에 따른 공격 / 방어 / HP 별도 배율)입니다. 보유하지 않은 프리셋 오퍼레이터는 공식적으로 잠재 1·보너스 없음이므로, 공식대로 하려면 잠재 1, 비정예화로 직접 설정하세요.",
|
||||
"文字大小": "글자 크기",
|
||||
"小": "작게",
|
||||
"大": "크게",
|
||||
"特大": "아주 크게",
|
||||
"调整界面文字大小,棋盘保持原比例。": "화면의 글자 크기를 조절합니다. 보드 배율은 유지됩니다.",
|
||||
"此干员语音": "오퍼레이터 음성",
|
||||
"跟随全局": "전체 설정 따르기",
|
||||
"仅保存在此浏览器,缺失的日语语音会回退到中文。": "이 브라우저에만 저장됩니다. 일본어 음성이 없으면 중국어로 재생합니다.",
|
||||
"添加到桌面": "홈 화면에 추가",
|
||||
"恢复本机对局": "이 기기의 게임 복귀",
|
||||
"此对局仍在其他窗口中,或当前浏览器无法安全恢复。请关闭原窗口后重试。": "다른 창에서 진행 중이거나 이 브라우저에서 안전하게 복귀할 수 없습니다. 원래 창을 닫고 다시 시도하세요.",
|
||||
"选择已关闭窗口的对局。服务器重启或对局过期后无法恢复。": "닫힌 창의 게임을 선택하세요. 서버 재시작 또는 게임 만료 후에는 복귀할 수 없습니다.",
|
||||
"{name} · 同盟 {code}": "{name} · 동맹 {code}",
|
||||
"重刷本局": "시작 조건 다시 추첨",
|
||||
"重刷本局投票:{agreed}/{total}": "다시 추첨 투표: {agreed}/{total}",
|
||||
"全员同意后重新随机战场、领袖、特训敌人和禁用;确认倒计时已暂停。": "전원 동의 시 전장, 보스, 특훈 적과 금지 대상을 다시 추첨합니다. 확인 제한 시간이 일시 정지됩니다.",
|
||||
"保留房间和个人配置,重新随机全部开局信息。": "방과 개인 설정을 유지하고 시작 조건 전체를 다시 추첨합니다.",
|
||||
"已同意": "동의함",
|
||||
"待同意": "동의 대기",
|
||||
"全体真人玩家一致同意后刷新,随机结果可能重复。": "모든 실제 플레이어의 동의가 필요합니다. 같은 결과가 나올 수 있습니다.",
|
||||
"请等待所有玩家连接后再发起投票": "모든 플레이어가 접속한 후 투표를 시작하세요.",
|
||||
"同意重刷": "다시 추첨에 동의",
|
||||
"拒绝重刷": "다시 추첨 거부",
|
||||
"取消投票": "투표 취소"
|
||||
}
|
||||
+25
-1
@@ -1187,5 +1187,29 @@
|
||||
"精英2": "精英2",
|
||||
"精英2 Lv.60": "精英2 Lv.60",
|
||||
"开始模拟前可调整干员携带的技能与模组,以及潜能与练度;干员的局内等级不可调整": "開始模擬前可調整幹員攜帶的技能與模組,以及潛能與練度;幹員的局內等級不可調整",
|
||||
"默认满潜能、精英2 Lv.60(满加成)。官方的潜能取你自己的潜能,练度是自持有加成(已持有的同名干员的养成,攻击 / 防御 / 生命单独乘算);未持有的特许干员在官方按潜能1、没有加成,要照官方打请手动设为潜能1、未精英化。": "預設滿潛能、精英2 Lv.60(滿加成)。官方的潛能取你自己的潛能,練度是自持有加成(已持有的同名幹員的養成,攻擊 / 防禦 / 生命單獨乘算);未持有的特許幹員在官方按潛能1、沒有加成,要照官方打請手動設為潛能1、未精英化。"
|
||||
"默认满潜能、精英2 Lv.60(满加成)。官方的潜能取你自己的潜能,练度是自持有加成(已持有的同名干员的养成,攻击 / 防御 / 生命单独乘算);未持有的特许干员在官方按潜能1、没有加成,要照官方打请手动设为潜能1、未精英化。": "預設滿潛能、精英2 Lv.60(滿加成)。官方的潛能取你自己的潛能,練度是自持有加成(已持有的同名幹員的養成,攻擊 / 防禦 / 生命單獨乘算);未持有的特許幹員在官方按潛能1、沒有加成,要照官方打請手動設為潛能1、未精英化。",
|
||||
"文字大小": "文字大小",
|
||||
"小": "小",
|
||||
"大": "大",
|
||||
"特大": "特大",
|
||||
"调整界面文字大小,棋盘保持原比例。": "調整介面文字大小,棋盤保持原比例。",
|
||||
"此干员语音": "此幹員語音",
|
||||
"跟随全局": "跟隨全域",
|
||||
"仅保存在此浏览器,缺失的日语语音会回退到中文。": "僅儲存在此瀏覽器,缺失的日語語音會回退到中文。",
|
||||
"添加到桌面": "加入主畫面",
|
||||
"恢复本机对局": "恢復本機對局",
|
||||
"此对局仍在其他窗口中,或当前浏览器无法安全恢复。请关闭原窗口后重试。": "此對局仍在其他視窗中,或目前瀏覽器無法安全恢復。請關閉原視窗後重試。",
|
||||
"选择已关闭窗口的对局。服务器重启或对局过期后无法恢复。": "選擇已關閉視窗的對局。伺服器重啟或對局過期後無法恢復。",
|
||||
"{name} · 同盟 {code}": "{name} · 同盟 {code}",
|
||||
"重刷本局": "重刷本局",
|
||||
"重刷本局投票:{agreed}/{total}": "重刷本局投票:{agreed}/{total}",
|
||||
"全员同意后重新随机战场、领袖、特训敌人和禁用;确认倒计时已暂停。": "全員同意後重新隨機戰場、領袖、特訓敵人和禁用;確認倒數已暫停。",
|
||||
"保留房间和个人配置,重新随机全部开局信息。": "保留房間和個人配置,重新隨機全部開局資訊。",
|
||||
"已同意": "已同意",
|
||||
"待同意": "待同意",
|
||||
"全体真人玩家一致同意后刷新,随机结果可能重复。": "全體真人玩家一致同意後刷新,隨機結果可能重複。",
|
||||
"请等待所有玩家连接后再发起投票": "請等待所有玩家連線後再發起投票",
|
||||
"同意重刷": "同意重刷",
|
||||
"拒绝重刷": "拒絕重刷",
|
||||
"取消投票": "取消投票"
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 886 B |
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><rect width="24" height="24" fill="#0c0f0e"/><path fill="#4ed8af" d="M5 3h3v2h2V3h4v2h2V3h3v5l-2 2v7l2 2v2H5v-2l2-2v-7L5 8z"/></svg>
|
||||
|
After Width: | Height: | Size: 193 B |
@@ -15,6 +15,7 @@
|
||||
<meta name="apple-mobile-web-app-title" content="卫戍协议" />
|
||||
<meta name="description" content="卫戍协议:盟约 · 网页联机复刻(非官方同人作品)" />
|
||||
<title>卫戍协议:盟约 · STRONGHOLD PROTOCOL</title>
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Crect width='24' height='24' fill='%230c0f0e'/%3E%3Cpath fill='%234ed8af' d='M5 3h3v2h2V3h4v2h2V3h3v5l-2 2v7l2 2v2H5v-2l2-2v-7L5 8z'/%3E%3C/svg%3E" />
|
||||
|
||||
<!-- Web fonts: never render-blocking (LAN/offline play must still start); system fallbacks in theme.css. -->
|
||||
@@ -69,6 +70,7 @@
|
||||
<div class="rotate-hint__phone"></div>
|
||||
<div class="rotate-hint__title">请将设备横屏</div>
|
||||
<div>卫戍协议:盟约 需要横屏显示以获得完整体验</div>
|
||||
<button type="button" hidden class="btn btn--secondary btn--sm rotate-hint__pwa" id="rotate-hint-pwa">添加到桌面</button>
|
||||
<div class="rotate-hint__micro">ROTATE DEVICE</div>
|
||||
</div>
|
||||
|
||||
|
||||
+10
-1
@@ -226,7 +226,11 @@ export function bgmEntry(m, kind) {
|
||||
/** SFX URL: group 'ui' | 'battle', key e.g. 'buy'. */
|
||||
export const sfxUrl = (m, group, key) => str(get(get(get(get(m, 'audio'), 'sfx'), str(group) || ''), str(key) || ''));
|
||||
|
||||
/** Per-unit SFX: kind 'attack' | 'hit' | 'skill' | 'die' | 'born'; `skillIndex` picks `skills[i]` when present. */
|
||||
/**
|
||||
* Per-unit SFX: kind 'attack' | 'hit' | 'skill' | 'die' | 'born'; `skillIndex` picks `skills[i]` and — for an attack or
|
||||
* an impact — `attacks[i]` / `hits[i]` (the running skill's own files, tools/assets/audio.mjs pickModeAttacks /
|
||||
* pickModeHits) when the manifest has them.
|
||||
*/
|
||||
export function unitSfxUrl(m, id, kind, skillIndex) {
|
||||
const u = get(get(get(get(m, 'audio'), 'sfx'), 'units'), str(id) || '') || get(get(get(get(m, 'audio'), 'sfx'), 'units'), baseCharId(id) || '');
|
||||
if (!isObj(u)) return null;
|
||||
@@ -234,6 +238,11 @@ export function unitSfxUrl(m, id, kind, skillIndex) {
|
||||
const s = str(u.skills[skillIndex]);
|
||||
if (s) return s;
|
||||
}
|
||||
const per = kind === 'attack' ? u.attacks : kind === 'hit' ? u.hits : null;
|
||||
if (Number.isInteger(skillIndex) && isObj(per)) {
|
||||
const a = str(per[skillIndex]);
|
||||
if (a) return a;
|
||||
}
|
||||
return str(u[kind]);
|
||||
}
|
||||
|
||||
|
||||
+44
-13
@@ -43,6 +43,16 @@
|
||||
// sound of that target. An operator's attack / hit sound that is a skill-mode file of its own (official names end in
|
||||
// `_n` for the normal attack, `_d` / `_h` / `_s` for its skill modes — the manifest picked 纯烬艾雅法拉's S3 impact
|
||||
// p_imp_gtshpbrnch_s as her `hit`) never plays for a normal attack (normalAttackSfx).
|
||||
// - The attack sound of the RUNNING skill (`attacks`, 技能1 / 2 / 3 = `_d` / `_h` / `_s`): an operator whose every attack
|
||||
// belongs to a skill mode has no normal attack bank at all — 司霆惊蛰 (解放者: she only attacks while a skill runs) was
|
||||
// completely silent when she attacked (report 「三技能攻击没有音效」). The manifest then carries that mode's own file
|
||||
// per skill index (`attacks`, tools/assets/audio.mjs pickModeAttacks) and it plays while the skill is active, from the
|
||||
// `['skill', id, 1]` / `0` events; a unit with a normal bank too (能天使 S2 过载模式, 史尔特尔 S2) keeps that bank
|
||||
// outside the skill. Being the official file of that very skill, it is not the normalAttackSfx case above.
|
||||
// - …and its impact (`hits`): a mode's swing is not always the whole difference — 赤刃明霄陈 S3's slashes swing
|
||||
// p_atk_hljdswd_s AND land p_imp_hljdswd_s, while her normal attack uses p_atk_hljdswd_n / p_imp_hljdswd_n
|
||||
// (report 「开启三技能后斩击音效应该和普通攻击不一样」). Same mechanism and the same window: the `hit` of an attack
|
||||
// aimed while the skill ran — the attacker's state travels with `lastAttacker`.
|
||||
// - The official bank mix of a unit's own attack / hit / die / born sound (`mix`, tools/assets/audio.mjs bankMix; community
|
||||
// report #30): it plays with chance `p` — 猎狗pro / 深池侦察犬's attack bank is 80 % silence, so they bark on about one
|
||||
// attack in five (never replaced by the generic enemy sound) — at its base gain × `vol`, capped at 1: an official volume
|
||||
@@ -55,6 +65,7 @@
|
||||
// `bgmKeyFor(route, pub)` picks the track for the current screen/phase (main.js calls `audio.install()`,
|
||||
// which follows the store).
|
||||
|
||||
import { sanitizeVoiceOverrides, voiceLangFor } from './voicePrefs.js';
|
||||
import { PHASE } from '../../shared/constants.js';
|
||||
import { mediaUrl } from './media.js';
|
||||
|
||||
@@ -514,6 +525,7 @@ export class AudioManager {
|
||||
this.voiceGate = new VoiceGate();
|
||||
this.voiceNode = null; // { src, gain, url, token } of the line on air
|
||||
this.voiceToken = 0;
|
||||
this.voiceOverrides = {};
|
||||
this.voiceLang = 'cn'; // settings 语音语言: 'cn' (audio.voice) | 'jp' (audio.voiceJp, falling back to audio.voice)
|
||||
this.startVoiceDone = false; // 行动出发 of this field (the first operator deployed says it)
|
||||
this.uiVoices = 0;
|
||||
@@ -522,7 +534,8 @@ export class AudioManager {
|
||||
this.bgmToken = 0;
|
||||
this.units = new Map(); // battle unit id → defId
|
||||
this.pendingSkill = new Map(); // unit id → the 'skill' tuple that arrived before the unit was known (see _track)
|
||||
this.lastAttacker = new Map(); // target id → { def, at } of the hostile attack last aimed at it (its impact sound)
|
||||
this.lastAttacker = new Map(); // target id → { def, at, skillIndex, skillActive } of the hostile attack aimed at it (its impact sound,
|
||||
// including the running skill's own `hits` — 赤刃明霄陈 S3's slash)
|
||||
this.consumed = new Set(); // summons used up by their own effect (香槟炸弹 exploded): no death sound
|
||||
this.installed = false;
|
||||
this._unlock = this._unlock.bind(this);
|
||||
@@ -660,8 +673,9 @@ export class AudioManager {
|
||||
* line on air finishes in its own dub; the next one follows the setting.
|
||||
* @param {string} lang
|
||||
*/
|
||||
setVoiceLang(lang) {
|
||||
setVoiceLang(lang, overrides = this.voiceOverrides) {
|
||||
this.voiceLang = lang === 'jp' ? 'jp' : 'cn';
|
||||
this.voiceOverrides = sanitizeVoiceOverrides(overrides);
|
||||
}
|
||||
|
||||
_applyVolumes() {
|
||||
@@ -870,18 +884,27 @@ export class AudioManager {
|
||||
* @param {string} defId charId/tokenId/enemyId (or chess id — mapped via its spine/char id by the caller)
|
||||
* @param {'attack'|'hit'|'skill'|'die'|'born'} kind
|
||||
* @param {number|string} unitId battle unit id (cooldown key)
|
||||
* @param {number} [skillIndex] the unit's equipped skill slot (0-based)
|
||||
* @param {boolean} [skillActive] whether that skill runs right now (`attacks` / `hits` need it, see header)
|
||||
* @returns {boolean} whether a unit-specific sound exists
|
||||
*/
|
||||
unit(defId, kind, unitId, skillIndex) {
|
||||
unit(defId, kind, unitId, skillIndex, skillActive = false) {
|
||||
try {
|
||||
const u = this.getManifest()?.audio?.sfx?.units?.[defId];
|
||||
// DESIGN §16: the equipped skill's own ON_SKILL_START sound (`skills[index]`) when the manifest has it
|
||||
const own = kind === 'skill' && Number.isInteger(skillIndex) && u?.skills ? u.skills[skillIndex] : null;
|
||||
const url = typeof own === 'string' ? own : u?.[kind];
|
||||
// the running skill's own swing / impact (header); only while it runs — otherwise 能天使 S2's file would ring on
|
||||
// every normal attack of hers (exactly what normalAttackSfx keeps out). 赤刃明霄陈 S3: `attacks[2]` = the S3 swing,
|
||||
// `hits[2]` = the S3 slash's impact.
|
||||
const table = kind === 'attack' ? u?.attacks : kind === 'hit' ? u?.hits : null;
|
||||
const mode = skillActive && Number.isInteger(skillIndex) && table ? table[skillIndex] : null;
|
||||
const url = typeof own === 'string' ? own : typeof mode === 'string' ? mode : u?.[kind];
|
||||
if (typeof url !== 'string') return false;
|
||||
if ((kind === 'attack' || kind === 'hit') && !normalAttackSfx(defId, url)) return false;
|
||||
// a skill-mode file is fine here when it IS the running skill's (mode); the gate is for normal attacks
|
||||
if ((kind === 'attack' || kind === 'hit') && typeof mode !== 'string' && !normalAttackSfx(defId, url)) return false;
|
||||
// the official bank's mix (header): a silent roll still counts as the unit's own sound (no generic fallback)
|
||||
const mix = kind === 'skill' ? null : u?.mix?.[kind];
|
||||
const mix = kind === 'skill' ? null : typeof mode === 'string'
|
||||
? (kind === 'attack' ? u?.attackMix : u?.hitMix)?.[skillIndex] : u?.mix?.[kind];
|
||||
if (!unitSoundPlays(mix, this.random())) return true;
|
||||
this._play(url, { volume: unitGain(kind === 'attack' || kind === 'hit' ? 0.55 : 0.8, mix), limited: true, unitKey: `${unitId}:${kind}` });
|
||||
return true;
|
||||
@@ -907,7 +930,7 @@ export class AudioManager {
|
||||
if (!this.ctx || !this.voiceGain || this.volumes.muted || this.volumes.voice <= 0) return false;
|
||||
if (typeof charId !== 'string' || typeof slot !== 'string') return false;
|
||||
// the chosen dub's line (settings 语音语言), with the Chinese file of the same name as its fallback (voiceLine)
|
||||
const line = voiceLine(this.getManifest()?.audio, charId, slot, this.voiceLang);
|
||||
const line = voiceLine(this.getManifest()?.audio, charId, slot, voiceLangFor(charId, this.voiceLang, this.voiceOverrides));
|
||||
if (!line) return false;
|
||||
const now = typeof performance !== 'undefined' ? performance.now() : Date.now();
|
||||
const verdict = this.voiceGate.request(slot, o.unitKey ?? null, now);
|
||||
@@ -1005,9 +1028,9 @@ export class AudioManager {
|
||||
_track(u) {
|
||||
if (!u || typeof u !== 'object' || u.id == null) return;
|
||||
// UnitInfo.spine is the model id (charId / tokenId / enemyId) — the key of sfx.units; kind/defId pick the
|
||||
// official class sounds (operator vs summon vs device)
|
||||
// official class sounds (operator vs summon vs device). `skillActive` follows the ['skill', id, 1] / 0 events.
|
||||
this.units.set(u.id, { def: u.spine || u.defId, defId: u.defId ?? null, kind: u.kind ?? null, side: u.side, boss: !!u.boss,
|
||||
skillIndex: Number.isInteger(u.skillIndex) ? u.skillIndex : null });
|
||||
skillIndex: Number.isInteger(u.skillIndex) ? u.skillIndex : null, skillActive: !!u.skillActive });
|
||||
// a cast that arrived before this unit was known (handleBattleEvents): it sounds now, once
|
||||
const held = this.pendingSkill.get(u.id);
|
||||
if (held) {
|
||||
@@ -1040,18 +1063,20 @@ export class AudioManager {
|
||||
if (!src) continue;
|
||||
// only a hostile attack authors the target's next impact (a heal — an ally aiming at an ally — never does)
|
||||
const tgt = this.units.get(e[2]);
|
||||
if (tgt && tgt.side !== src.side) this.lastAttacker.set(e[2], { def: src.def, at: now });
|
||||
if (!this.unit(src.def, 'attack', e[1]) && src.side === 'enemy') this.battle('enemyHit', { unitKey: `${e[1]}:atk`, volume: 0.35 });
|
||||
if (tgt && tgt.side !== src.side) this.lastAttacker.set(e[2], { def: src.def, at: now, skillIndex: src.skillIndex ?? null, skillActive: !!src.skillActive });
|
||||
if (!this.unit(src.def, 'attack', e[1], src.skillIndex ?? undefined, src.skillActive) && src.side === 'enemy') this.battle('enemyHit', { unitKey: `${e[1]}:atk`, volume: 0.35 });
|
||||
} else if (kind === 'dmg') {
|
||||
const by = this.lastAttacker.get(e[1]);
|
||||
if (!by || !IMPACT_TYPES.has(e[3])) continue;
|
||||
this.lastAttacker.delete(e[1]); // one impact per attack
|
||||
if (now - by.at <= IMPACT_WINDOW_MS) this.unit(by.def, 'hit', `h${e[1]}`);
|
||||
if (now - by.at <= IMPACT_WINDOW_MS) this.unit(by.def, 'hit', `h${e[1]}`, by.skillIndex ?? undefined, by.skillActive);
|
||||
} else if (kind === 'heal') {
|
||||
this.battle('heal', { unitKey: `heal:${e[1]}`, volume: 0.35 });
|
||||
} else if (kind === 'skill' && e[2]) {
|
||||
const u = this.units.get(e[1]);
|
||||
if (u) {
|
||||
// the running skill's own swing / impact (`attacks` / `hits`, header) follow this flag
|
||||
u.skillActive = true;
|
||||
this.unit(u.def, 'skill', e[1], u.skillIndex ?? undefined);
|
||||
// 作战中N: the equipped skill's own slot (0-based; 作战中4 is the fallback of a 4th slot)
|
||||
if (unitSoundClass(u) === 'char') {
|
||||
@@ -1066,6 +1091,12 @@ export class AudioManager {
|
||||
if (this.pendingSkill.size >= PENDING_SKILL_MAX) this.pendingSkill.delete(this.pendingSkill.keys().next().value);
|
||||
this.pendingSkill.set(e[1], e);
|
||||
}
|
||||
} else if (kind === 'skill') {
|
||||
// ['skill', id, 0] = the skill ENDED (the sim's skills.js: `b._ev(['skill', u.id, 0])`): back to the unit's own
|
||||
// attack / impact. The end carries no sound of its own (the activation sound belongs to the start).
|
||||
const u = this.units.get(e[1]);
|
||||
if (u) u.skillActive = false;
|
||||
this.pendingSkill.delete(e[1]);
|
||||
} else if (kind === 'engage') {
|
||||
// 行动开始: the first attack a unit makes on an enemy (the sim's ENGAGE, official ENCOUNTER_ENEMY, 3 s apart)
|
||||
const u = this.units.get(e[1]);
|
||||
@@ -1140,7 +1171,7 @@ export function installAudio(deps) {
|
||||
try {
|
||||
manifestGetter = typeof deps?.getManifest === 'function' ? deps.getManifest : manifestGetter;
|
||||
audio.install();
|
||||
if (deps?.settings) { audio.setVolumes(deps.settings); audio.setVoiceLang(deps.settings.voiceLang); }
|
||||
if (deps?.settings) { audio.setVolumes(deps.settings); audio.setVoiceLang(deps.settings.voiceLang, deps.settings.voiceOverrides); }
|
||||
if (typeof deps?.subscribe === 'function' && typeof deps?.getState === 'function') {
|
||||
const sync = (s) => {
|
||||
try {
|
||||
|
||||
@@ -136,6 +136,7 @@ function maybeFinishRestore() {
|
||||
* follow the store themselves).
|
||||
*/
|
||||
function backToLobby() {
|
||||
identity.rememberMatch(null);
|
||||
clearTimeout(restoreTimer);
|
||||
const s = store.get();
|
||||
if (s.room || s.match.public) closeAllDialogs();
|
||||
@@ -145,6 +146,7 @@ function backToLobby() {
|
||||
|
||||
function onWelcome(msg) {
|
||||
identity.saveToken(msg.token);
|
||||
identity.rememberMatch(null);
|
||||
const prev = store.get();
|
||||
const prevId = prev.me.playerId;
|
||||
const name = typeof msg.name === 'string' && msg.name ? msg.name : prev.me.name;
|
||||
@@ -188,6 +190,8 @@ function onRoomState(msg) {
|
||||
// A (new) match starts: forget the previous match's state so stale results never show.
|
||||
if (room.inMatch && !(prevRoom && prevRoom.inMatch && prevRoom.code === room.code)) store.set({ match: emptyMatch() });
|
||||
store.set({ room });
|
||||
identity.rememberMatch(room.inMatch && seats.some((seat) => seat?.playerId === myId)
|
||||
? { name: store.get().me.name, code: room.code || '' } : null);
|
||||
if (room.mode === 'coop' && typeof room.code === 'string') rememberRoom(room.code);
|
||||
maybeFinishRestore();
|
||||
}
|
||||
|
||||
@@ -660,6 +660,7 @@ export class Net {
|
||||
const K_NAME = 'sp.name';
|
||||
const K_TOKEN = 'sp.token'; // sessionStorage: this tab's token
|
||||
const K_RECENT = 'sp.tokens'; // localStorage: this browser's recent tokens, most recent first
|
||||
const K_MATCHES = 'sp.matches'; // local metadata for explicit recovery of another closed tab's seat
|
||||
const K_ENTERED = 'sp.entered'; // sessionStorage: this tab passed the title screen
|
||||
const RECENT_MAX = 4;
|
||||
const TOKEN_MAX_LEN = 64; // protocol limit for hello.token
|
||||
@@ -725,6 +726,7 @@ export function createIdentity(deps = {}) {
|
||||
let channel = null;
|
||||
let initPromise = null;
|
||||
let initialized = false;
|
||||
let resumeBusy = false;
|
||||
let current = null; // token this tab uses (null = let the server create a session)
|
||||
let resolving = null; // { hashes: Set<hash>, taken: Set<hash> } while init() waits for claims
|
||||
|
||||
@@ -738,6 +740,25 @@ export function createIdentity(deps = {}) {
|
||||
};
|
||||
const writeRecent = (list) => sset(local, K_RECENT, JSON.stringify([...new Set(list)].slice(0, RECENT_MAX)));
|
||||
|
||||
const readMatches = () => {
|
||||
try {
|
||||
const v = JSON.parse(sget(local, K_MATCHES) || '{}');
|
||||
return v && typeof v === 'object' && !Array.isArray(v) ? v : {};
|
||||
} catch { return {}; }
|
||||
};
|
||||
const rememberMatch = (info) => {
|
||||
if (!current) return;
|
||||
const old = readMatches(), next = {};
|
||||
for (const token of readRecent()) {
|
||||
const hash = tokenHash(token);
|
||||
const v = token === current ? info : old[hash];
|
||||
if (v && typeof v.name === 'string' && typeof v.code === 'string') {
|
||||
next[hash] = { name: v.name.slice(0, 64), code: v.code.slice(0, 32) };
|
||||
}
|
||||
}
|
||||
sset(local, K_MATCHES, JSON.stringify(next));
|
||||
};
|
||||
|
||||
const post = (msg) => {
|
||||
try { channel?.postMessage({ ...msg, from: tabId }); } catch { /* channel closed */ }
|
||||
};
|
||||
@@ -773,6 +794,10 @@ export function createIdentity(deps = {}) {
|
||||
// Without a channel we cannot tell whether a shared token is in use: never adopt one.
|
||||
const candidates = [...new Set([ownOk, ...(channel ? readRecent() : [])].filter(isToken))];
|
||||
if (!channel || candidates.length === 0) return ownOk;
|
||||
return queryCandidates(candidates);
|
||||
}
|
||||
|
||||
async function queryCandidates(candidates) {
|
||||
resolving = { hashes: new Set(candidates.map(tokenHash)), taken: new Set() };
|
||||
post({ type: 'who', hashes: [...resolving.hashes] });
|
||||
await new Promise((r) => setTimer(r, queryMs));
|
||||
@@ -802,6 +827,32 @@ export function createIdentity(deps = {}) {
|
||||
}
|
||||
return initPromise;
|
||||
},
|
||||
/** Record only display metadata; the existing recent-token ring remains the authority. */
|
||||
rememberMatch,
|
||||
recoverable() {
|
||||
const matches = readMatches();
|
||||
return readRecent().filter((token) => token !== current).flatMap((token) => {
|
||||
const hash = tokenHash(token), v = matches[hash];
|
||||
return v && typeof v.name === 'string' && typeof v.code === 'string'
|
||||
? [{ id: hash, name: v.name.slice(0, 64), code: v.code.slice(0, 32) }] : [];
|
||||
});
|
||||
},
|
||||
/** Explicit recovery only. Recheck live claims immediately before selecting a saved seat. */
|
||||
async resume(id) {
|
||||
await this.init();
|
||||
if (!channel || resumeBusy) return false;
|
||||
const token = readRecent().find((t) => t !== current && tokenHash(t) === id);
|
||||
if (!token || !this.recoverable().some((m) => m.id === id)) return false;
|
||||
resumeBusy = true;
|
||||
try {
|
||||
const pick = await queryCandidates([token]);
|
||||
if (!pick) return false;
|
||||
current = pick;
|
||||
sset(session, K_TOKEN, pick);
|
||||
return true;
|
||||
} catch { return false; }
|
||||
finally { resumeBusy = false; }
|
||||
},
|
||||
/** @returns {string} remembered player name ('' if none) */
|
||||
loadName: () => (sget(local, K_NAME) || '').slice(0, 64),
|
||||
/** @param {string} name */
|
||||
@@ -816,12 +867,14 @@ export function createIdentity(deps = {}) {
|
||||
/** @param {string} token from `welcome` */
|
||||
saveToken(token) {
|
||||
if (!isToken(token)) return;
|
||||
if (current !== token) rememberMatch(null);
|
||||
current = token;
|
||||
sset(session, K_TOKEN, token);
|
||||
writeRecent([token, ...readRecent().filter((t) => t !== token)]);
|
||||
},
|
||||
/** Forget this tab's token (e.g. the server said the session is invalid). */
|
||||
clearToken() {
|
||||
rememberMatch(null);
|
||||
const t = current || sget(session, K_TOKEN);
|
||||
current = null;
|
||||
sdel(session, K_TOKEN);
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// Install prompts are browser-issued, one-use capabilities. No service worker or application cache is installed.
|
||||
export function createPwaInstall(win) {
|
||||
let prompt = null, installed = false;
|
||||
const listeners = new Set();
|
||||
const display = win?.matchMedia?.('(display-mode: standalone), (display-mode: fullscreen)');
|
||||
const available = () => !!prompt && !installed && !display?.matches && !win?.navigator?.standalone;
|
||||
const emit = () => { for (const fn of listeners) fn(available()); };
|
||||
const offer = (event) => {
|
||||
if (installed || display?.matches || win?.navigator?.standalone || typeof event.prompt !== 'function') return;
|
||||
event.preventDefault();
|
||||
prompt = event;
|
||||
emit();
|
||||
};
|
||||
const done = () => { installed = true; prompt = null; emit(); };
|
||||
win?.addEventListener?.('beforeinstallprompt', offer);
|
||||
win?.addEventListener?.('appinstalled', done);
|
||||
display?.addEventListener?.('change', emit);
|
||||
return {
|
||||
available,
|
||||
subscribe(fn) { listeners.add(fn); fn(available()); return () => listeners.delete(fn); },
|
||||
async request() {
|
||||
if (!available()) return false;
|
||||
const event = prompt;
|
||||
prompt = null; // Consume before awaiting: dismissal, rejection and repeated clicks cannot reuse this event.
|
||||
emit();
|
||||
try {
|
||||
const result = await event.prompt();
|
||||
const choice = event.userChoice ? await event.userChoice : result;
|
||||
return choice?.outcome === 'accepted';
|
||||
} catch { return false; }
|
||||
},
|
||||
dispose() {
|
||||
win?.removeEventListener?.('beforeinstallprompt', offer);
|
||||
win?.removeEventListener?.('appinstalled', done);
|
||||
display?.removeEventListener?.('change', emit);
|
||||
prompt = null; listeners.clear();
|
||||
},
|
||||
};
|
||||
}
|
||||
export const pwaInstall = createPwaInstall(globalThis.window);
|
||||
@@ -530,6 +530,7 @@ export class UnitView {
|
||||
this.actor = null;
|
||||
this.spineReady = false;
|
||||
this._modelDirty = false; // died / stood up since the last frame: update() checks Front ⇄ Back (_syncModel)
|
||||
this._pendingDeployElapsed = null; // elapsed game seconds until a late model can continue Start
|
||||
this._spineBusy = false; // a Spine load of this view is in flight
|
||||
this._spineTries = 0; // failed loads since the last model (SPINE_RETRY_MS)
|
||||
this._retryAt = 0; // when the next retry is due (ms, performance clock; 0 = none)
|
||||
@@ -650,7 +651,8 @@ export class UnitView {
|
||||
// no fallback diamond in between; a deploy clip it was playing goes on on the new model (an operator facing UP
|
||||
// redeployed: its Back model takes over from the Front model it lay down with)
|
||||
const swap = !!this.actor;
|
||||
const deployed = swap && this.alive ? this.actor.deployElapsed() : null;
|
||||
const deployed = swap && this.alive ? this.actor.deployElapsed() : this._pendingDeployElapsed;
|
||||
this._pendingDeployElapsed = null;
|
||||
if (swap) this._dropActor();
|
||||
this.actor = actor;
|
||||
this._actorEntry = entry;
|
||||
@@ -961,6 +963,7 @@ export class UnitView {
|
||||
/** An attack was made (b.ev 'atk'). `target` = view or null. */
|
||||
onAttack(target, now, kind) {
|
||||
if (!this.alive) return;
|
||||
this._pendingDeployElapsed = null;
|
||||
// a one-off cast (PROJ[kind].once: 暴鸰's bomb drop) is no attack rhythm: its clip plays once at its own speed
|
||||
const once = !!PROJ[kind]?.once;
|
||||
if (!once) {
|
||||
@@ -1018,6 +1021,7 @@ export class UnitView {
|
||||
onDeploy() {
|
||||
this.fadeIn = 0;
|
||||
if (!this.alive) this.revive();
|
||||
this._pendingDeployElapsed = this.actor ? null : 0;
|
||||
if (this.actor) this.actor.deploy();
|
||||
}
|
||||
|
||||
@@ -1038,6 +1042,7 @@ export class UnitView {
|
||||
*/
|
||||
die(instant = false) {
|
||||
if (!this.alive) return;
|
||||
this._pendingDeployElapsed = null;
|
||||
this.alive = false;
|
||||
this._modelDirty = true;
|
||||
this._dieForm = this._formSpec();
|
||||
@@ -1105,6 +1110,7 @@ export class UnitView {
|
||||
/** @param {number} dt real seconds @param {any} cam camera @param {number} t real clock */
|
||||
update(dt, cam, t) {
|
||||
if (this.destroyed) return;
|
||||
if (this._pendingDeployElapsed != null) this._pendingDeployElapsed += dt * (this.ctx.animRate?.() || 1);
|
||||
// a displacement's slide (slideTo): held where it stood until the snapshot with the destination is shown, then
|
||||
// eased into the sampled position (sync keeps `lx, ly` current) under a constant deceleration
|
||||
if (this.slide) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useGameData, Img, RichText } from '../ui/gameComponents.js';
|
||||
import { StepHeader, ExitModal } from '../ui/matchChrome.js';
|
||||
import { MatchInfo, matchInfoModel } from '../ui/matchInfo.js';
|
||||
import { LoadoutButton } from './loadout.js';
|
||||
import { SetupReroll } from '../ui/setupReroll.js';
|
||||
import { actions } from '../ui/gameActions.js';
|
||||
import { factionTypes, sortedPlayers, phaseTotalSeconds } from '../ui/gameLogic.js';
|
||||
import { enemyIconUrl, factionIconUrl } from '../ui/assetUrls.js';
|
||||
@@ -50,7 +51,7 @@ export function BriefingScreen() {
|
||||
const ready = async () => {
|
||||
if (busy || me?.ready) return;
|
||||
setBusy(true);
|
||||
await actions.infoReady();
|
||||
await actions.infoReady(pub.setupRevision ?? 0);
|
||||
setBusy(false);
|
||||
};
|
||||
|
||||
@@ -91,6 +92,7 @@ export function BriefingScreen() {
|
||||
<${MatchInfo} model=${info} />
|
||||
</section>
|
||||
</main>
|
||||
<${SetupReroll} pub=${pub} />
|
||||
<footer class="brief__foot">
|
||||
<${LoadoutButton} from="briefing" size="lg" class="brief-loadout" />
|
||||
<div class="brief-ready">
|
||||
@@ -98,7 +100,7 @@ export function BriefingScreen() {
|
||||
<span class="brief-ready__pips">${players.map((p) => html`<i key=${p.playerId} class=${cx(p.ready && 'on', p.playerId === myId && 'me')} title=${p.name}><${Icon} name="user" /></i>`)}</span>
|
||||
</div>
|
||||
<${Button} variant="primary" size="xl" icon=${me?.ready ? 'check' : me ? 'play' : 'eye'} active=${!!me?.ready} loading=${busy}
|
||||
disabled=${!!me?.ready || !me} onClick=${ready}>${me?.ready ? t('已就绪') : me ? t('准备就绪') : t('观战中')}<//>
|
||||
disabled=${!!me?.ready || !me || !!pub.rerollVote} onClick=${ready}>${me?.ready ? t('已就绪') : me ? t('准备就绪') : t('观战中')}<//>
|
||||
</footer>
|
||||
<${ExitModal} open=${exit} onClose=${() => setExit(false)} solo=${solo} />
|
||||
</div>`;
|
||||
|
||||
@@ -16,6 +16,7 @@ import { chessAvatarUrl, chessPortraitUrl, profIconUrl, skillRecordIconUrl, modu
|
||||
import { data } from '../data.js';
|
||||
import { PROF_NAME, skillLabel, moduleBadge, fullTraitText } from '../ui/loadoutModel.js';
|
||||
import { diySlotList, pickChoices, pickOptions, slotRecord, defaultPick } from '../ui/diyModel.js';
|
||||
import { OperatorVoice } from '../ui/operatorVoice.js';
|
||||
import { CultivationSelects } from './cultivation.js';
|
||||
import { t } from '../../../shared/i18n.js';
|
||||
|
||||
@@ -77,6 +78,7 @@ function SlotCard({ m, slot, pick, illegal, onOpen, onClear, ops = {}, onOps = n
|
||||
</span>
|
||||
${proto ? html`<small class="diy-slot__cult t-dim">${t('原型干员没有潜能与练度')}</small>`
|
||||
: onOps ? html`<span class="diy-slot__cult"><${CultivationSelects} charId=${rec.charId} ops=${ops} onSet=${onOps} /></span>` : null}
|
||||
<${OperatorVoice} charId=${rec.charId} />
|
||||
${illegal ? html`<p class="diy-slot__bad"><${Icon} name="warn" />${t('这项自选在当前版本不可用,开局时会被移除')}</p>` : null}
|
||||
</div>
|
||||
<div class="diy-slot__acts">
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
opsOf, setOps, resetOps, moduleRecord,
|
||||
} from '../ui/loadoutModel.js';
|
||||
import { loadoutStore, openLoadout, closeLoadout, setEntries, setOpsMap, applyLoadoutEntries, setNotOwned, applyOwnershipImport, setDiyPicks, applyDiyImport } from '../ui/loadoutSync.js';
|
||||
import { OperatorVoice } from '../ui/operatorVoice.js';
|
||||
import { CultivationSelects, CultivationSection } from './cultivation.js';
|
||||
import { cultivationCharIds } from '../../../shared/protocol.js';
|
||||
import { atPotential } from '../../../shared/potential.js';
|
||||
@@ -387,6 +388,7 @@ function Detail({ m, chess, golden, entries, ops = {}, onChange, onOps, onReset,
|
||||
<${Button} variant="ghost" size="sm" icon="refresh" class="lo-dhead__reset" disabled=${!changed} onClick=${onReset}>${t('恢复默认')}<//>
|
||||
</div>
|
||||
<div class="lo-detail__body" ref=${bodyRef}>
|
||||
<${OperatorVoice} charId=${chess.charId} />
|
||||
<${CultivationSection} charId=${chess.charId} ops=${ops} onSet=${onOps} standIn=${notOwned} />
|
||||
<${LoadoutGarrisons} chess=${level === 'elite' && golden ? golden : chess} m=${m} />
|
||||
<section class="lo-sec">
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
// Texts go through t() (docs/I18N.md); the module-level tables hold msgids (N_) translated where they are shown, the
|
||||
// config.json mode texts come localized from data.js.
|
||||
|
||||
import { ResumeMatchButton } from '../ui/resumeMatch.js';
|
||||
import { useEffect, useRef, useState } from '../../vendor/hooks.module.js';
|
||||
import { DIFFICULTIES, DIFFICULTY_NAMES, DIFFICULTY_COLORS, ROOM_CODE_LEN, MAX_SEATS, MAX_SPECTATORS, modeIdFor, ERR } from '../../../shared/constants.js';
|
||||
import { html, Button, Icon, MicroLabel, Panel, TextField, PingPill, AvatarFrame, Tooltip, Spinner, DifficultyIcon, doctorNo } from '../ui/components.js';
|
||||
@@ -18,6 +19,7 @@ import { toast, toastError } from '../ui/toasts.js';
|
||||
import { GuideButton } from '../ui/guide.js';
|
||||
import { openStats } from './stats.js';
|
||||
import { SettingsButton } from '../ui/settings.js';
|
||||
import { PwaInstallButton } from '../ui/device.js';
|
||||
import { LoadoutButton } from './loadout.js';
|
||||
import { net, identity } from '../net.js';
|
||||
import { store, useStore, shallowEqual, loadPref, savePref } from '../store.js';
|
||||
@@ -315,6 +317,8 @@ export function LobbyScreen() {
|
||||
<div class="topbar__right">
|
||||
<${Button} variant="secondary" size="sm" icon="chart" class="stats-entry" onClick=${openStats} title=${t('统计数据')} aria-label=${t('统计数据')}>${t('统计')}<//>
|
||||
<${SettingsButton} class="lobby-settings" variant="secondary" label=${t('设置')} />
|
||||
<${ResumeMatchButton} />
|
||||
<${PwaInstallButton} class="lobby-pwa" />
|
||||
<${GuideButton} class="lobby-guide" variant="secondary" label=${t('玩法说明')} />
|
||||
<${LoadoutButton} from="lobby" size="sm" class="lobby-loadout" label=${t('干员调配')} />
|
||||
<div class="me-chip">
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
// entry/loading illustration names) it is layered under the CSS art; otherwise the screen is
|
||||
// pure CSS/SVG (radar, ridgelines, glow), so it never issues a request that can 404.
|
||||
|
||||
import { ResumeMatchButton } from '../ui/resumeMatch.js';
|
||||
import { useMemo, useState } from '../../vendor/hooks.module.js';
|
||||
import { NAME_MAX_LEN, APP_VERSION, DEV_BUILD } from '../../../shared/constants.js';
|
||||
import { html, Button, Icon, MicroLabel, TextField, PingPill } from '../ui/components.js';
|
||||
@@ -19,7 +20,7 @@ import { toast } from '../ui/toasts.js';
|
||||
import { net, identity } from '../net.js';
|
||||
import { store, useStore, shallowEqual } from '../store.js';
|
||||
import { data, useData } from '../data.js';
|
||||
import { FullscreenButton, detectFeatures } from '../ui/device.js';
|
||||
import { FullscreenButton, PwaInstallButton, detectFeatures } from '../ui/device.js';
|
||||
import { LangToggle, useLang } from '../ui/lang.js';
|
||||
import { t, N_ } from '../../../shared/i18n.js';
|
||||
import { scriptOf } from '../../../shared/i18nPacks.js';
|
||||
@@ -287,6 +288,8 @@ export function TitleScreen() {
|
||||
<${SettingsModal} open=${settingsOpen} onClose=${() => setSettingsOpen(false)} />
|
||||
|
||||
<footer class="title-foot">
|
||||
<${ResumeMatchButton} />
|
||||
<${PwaInstallButton} />
|
||||
<span>${t('非官方同人复刻 · 游戏素材版权归 上海鹰角网络 / Yostar 所有')}</span>
|
||||
<${MicroLabel}>v${APP_VERSION} · WEB SIMULATION<//>
|
||||
${DEV_BUILD ? html`<span class="title-dev" role="note">${t('开发版 · 不稳定,请勿用于公开服务器')}</span>` : null}
|
||||
|
||||
+23
-1
@@ -20,7 +20,8 @@
|
||||
// CSS counterpart: public/css/devices.css (safe-area insets, touch-action, overscroll, tap-target expansion).
|
||||
|
||||
import { useEffect, useState } from '../../vendor/hooks.module.js';
|
||||
import { html, Icon } from './components.js';
|
||||
import { pwaInstall } from '../pwa.js';
|
||||
import { html, Icon, Button } from './components.js';
|
||||
import { t } from '../../../shared/i18n.js';
|
||||
|
||||
/** A touch held this long without moving opens the detail (contextmenu) on DOM controls. */
|
||||
@@ -162,6 +163,20 @@ export function FullscreenButton({ class: cls = '' }) {
|
||||
</button>`;
|
||||
}
|
||||
|
||||
/** Only a browser-issued install offer shows a button; installed and unsupported environments stay quiet. */
|
||||
export function PwaInstallButton({ class: cls = '', size = 'sm' }) {
|
||||
const [available, setAvailable] = useState(pwaInstall.available);
|
||||
useEffect(() => pwaInstall.subscribe(setAvailable), []);
|
||||
if (!available) return null;
|
||||
return html`<${Button} variant="secondary" size=${size} icon="download" class=${cls}
|
||||
data-testid="pwa-install" onClick=${() => pwaInstall.request()}>${t('添加到桌面')}<//>`;
|
||||
}
|
||||
|
||||
export function updateRotateHintPwaI18n(doc = globalThis.document) {
|
||||
const btn = doc?.getElementById?.('rotate-hint-pwa');
|
||||
if (btn) btn.textContent = t('添加到桌面');
|
||||
}
|
||||
|
||||
// ---- boot-time installation ----------------------------------------------------------------------------------------
|
||||
|
||||
let installed = null;
|
||||
@@ -253,6 +268,13 @@ export function installDeviceSupport(win = globalThis) {
|
||||
if (lp.swallowUntil && Date.now() < lp.swallowUntil) { lp.swallowUntil = 0; e.preventDefault(); e.stopPropagation(); }
|
||||
}, { capture: true });
|
||||
|
||||
const pwaBtn = doc?.getElementById?.('rotate-hint-pwa');
|
||||
if (pwaBtn) {
|
||||
updateRotateHintPwaI18n(doc);
|
||||
offs.push(pwaInstall.subscribe((available) => { pwaBtn.hidden = !available; }));
|
||||
on(pwaBtn, 'click', () => { pwaInstall.request(); });
|
||||
}
|
||||
|
||||
installed = () => { for (const off of offs.splice(0)) { try { off(); } catch { /* ignore */ } } clearTimeout(rotTimer); installed = null; };
|
||||
return installed;
|
||||
}
|
||||
@@ -48,7 +48,10 @@ export async function act(t, fields = {}, opts = {}) {
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
infoReady: () => act('g.infoReady'),
|
||||
infoReady: (setupRevision = 0) => act('g.infoReady', { setupRevision }),
|
||||
rerollSetup: (setupRevision) => act('room.rerollSetup', { setupRevision }, { sfx: 'confirm' }),
|
||||
rerollVote: (voteId, agree) => act('g.rerollVote', { voteId, agree }, { sfx: agree ? 'confirm' : 'back' }),
|
||||
cancelReroll: (voteId) => act('room.cancelReroll', { voteId }, { sfx: 'back' }),
|
||||
band: (bandId) => act('g.band', { bandId }),
|
||||
bandSkip: () => act('g.bandSkip'),
|
||||
buy: (slot) => act('g.buy', { slot }),
|
||||
|
||||
@@ -40,7 +40,7 @@ export { normalizeDraft, normalizeSp, normalizePersonalChoice } from './gameLogi
|
||||
export { groupEnemies, PEN, penZoneTiles, penPlacement, previewEnemyKey, factionTypes } from './gameLogic/enemies.js';
|
||||
export { snapHud, bossFrac, bossPctText, hasFlag, UF, attackInterval, fmtNum, rangeGridBox } from './gameLogic/format.js';
|
||||
export { shortcutFor, closesOnFieldPress, shortcutBlocked, HOTKEY_ACTIONS, DEFAULT_HOTKEYS, isBindableCode, hotkeyLabel, sanitizeHotkeys, rebindHotkey, isDefaultHotkeys, hotkeyOf, actionForKey, captureHotkey, facingSwallows, facingEnter } from './gameLogic/shortcuts.js';
|
||||
export { DEFAULT_SETTINGS, VOICE_LANGS, sanitizeSettings } from './gameLogic/settings.js';
|
||||
export { DEFAULT_SETTINGS, VOICE_LANGS, TEXT_SIZES, sanitizeSettings } from './gameLogic/settings.js';
|
||||
export { normalizeResult } from './gameLogic/result.js';
|
||||
export { chessLoadout, unitLoadout, unitCultivation } from './gameLogic/loadout.js';
|
||||
export { standInIds, fieldsStandIn, standInOf, ownStandIn, cardStandIn, memberStandIn, standInGetter, standInLoadout, deployedRecord, deployedModuleId, standInLabel, standInForText, standInTip } from './gameLogic/standIn.js';
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
// ui/gameLogic/settings.js — settings defaults and sanitising. Re-exported from ../gameLogic.js.
|
||||
|
||||
import { VOICE_LANGS, sanitizeVoiceOverrides } from '../../voicePrefs.js';
|
||||
export { VOICE_LANGS } from '../../voicePrefs.js';
|
||||
|
||||
import { clamp, isObj } from './shared.js';
|
||||
import { DEFAULT_HOTKEYS, sanitizeHotkeys } from './shortcuts.js';
|
||||
|
||||
@@ -10,17 +13,25 @@ import { DEFAULT_HOTKEYS, sanitizeHotkeys } from './shortcuts.js';
|
||||
* The operator voice dubs (settings 语音语言): 'cn' 中文 (`audio.voice`, the default) and 'jp' 日本語 (`audio.voiceJp`,
|
||||
* falling back to the Chinese line it lacks — public/js/audio.js voiceLine). Not tied to the interface language.
|
||||
*/
|
||||
export const VOICE_LANGS = Object.freeze(['cn', 'jp']);
|
||||
|
||||
/** keys: the in-match shortcuts' key map (ui/gameLogic/shortcuts.js; settings → 快捷键). voiceLang: VOICE_LANGS. */
|
||||
export const DEFAULT_SETTINGS = Object.freeze({ bgm: 0.6, sfx: 0.8, voice: 0.8, voiceLang: 'cn', muted: false, damageNumbers: true, quality: 'high', keys: DEFAULT_HOTKEYS });
|
||||
|
||||
/**
|
||||
* The steps of 设置 →「文字大小」 (textSize): the interface text root `--t` of css/theme.css — 'sm' is the design's own
|
||||
* sizes (`--t: 1rem`), the others lift the phone's 40 px root by a floor and grow the desktop gently. Text only: the
|
||||
* layout root `1rem` (and with it the field camera, the detail card's side and the DOM fallback board) never moves.
|
||||
*/
|
||||
export const TEXT_SIZES = Object.freeze(['sm', 'md', 'lg', 'xl']);
|
||||
|
||||
/** keys: the in-match shortcuts' key map (ui/gameLogic/shortcuts.js; settings → 快捷键). voiceLang: VOICE_LANGS.
|
||||
* textSize: TEXT_SIZES (css/theme.css `--t`, applied by ui/settings.js applyTextSize). */
|
||||
export const DEFAULT_SETTINGS = Object.freeze({ bgm: 0.6, sfx: 0.8, voice: 0.8, voiceLang: 'cn', voiceOverrides: Object.freeze({}), muted: false, damageNumbers: true, quality: 'high', textSize: 'sm', keys: DEFAULT_HOTKEYS });
|
||||
const QUALITIES = ['high', 'medium', 'low'];
|
||||
|
||||
/**
|
||||
* Sanitize persisted settings.
|
||||
* @param {any} raw
|
||||
* @returns {{ bgm: number, sfx: number, voice: number, voiceLang: 'cn'|'jp', muted: boolean, damageNumbers: boolean, quality: 'high'|'medium'|'low',
|
||||
* keys: Record<'refresh'|'freeze'|'levelUp'|'retreat'|'sell'|'ready', string> }}
|
||||
* @returns {{ bgm: number, sfx: number, voice: number, voiceLang: 'cn'|'jp', voiceOverrides: Record<string, string>, muted: boolean, damageNumbers: boolean, quality: 'high'|'medium'|'low',
|
||||
* textSize: 'sm'|'md'|'lg'|'xl', keys: Record<'refresh'|'freeze'|'levelUp'|'retreat'|'sell'|'ready', string> }}
|
||||
*/
|
||||
export function sanitizeSettings(raw) {
|
||||
const r = isObj(raw) ? raw : {};
|
||||
@@ -30,9 +41,11 @@ export function sanitizeSettings(raw) {
|
||||
sfx: vol(r.sfx, DEFAULT_SETTINGS.sfx),
|
||||
voice: vol(r.voice, DEFAULT_SETTINGS.voice),
|
||||
voiceLang: VOICE_LANGS.includes(r.voiceLang) ? r.voiceLang : DEFAULT_SETTINGS.voiceLang,
|
||||
voiceOverrides: sanitizeVoiceOverrides(r.voiceOverrides),
|
||||
muted: typeof r.muted === 'boolean' ? r.muted : DEFAULT_SETTINGS.muted,
|
||||
damageNumbers: typeof r.damageNumbers === 'boolean' ? r.damageNumbers : DEFAULT_SETTINGS.damageNumbers,
|
||||
quality: QUALITIES.includes(r.quality) ? r.quality : DEFAULT_SETTINGS.quality,
|
||||
textSize: TEXT_SIZES.includes(r.textSize) ? r.textSize : DEFAULT_SETTINGS.textSize,
|
||||
keys: sanitizeHotkeys(r.keys),
|
||||
};
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import { PACKS_URL, PACK_INDEX_FILE, readPackIndex, langMetaOf } from '../../../
|
||||
import { DEV_BUILD } from '../../../shared/constants.js';
|
||||
import { loadPref, savePref } from '../store.js';
|
||||
import { data } from '../data.js';
|
||||
import { updateRotateHintPwaI18n } from './device.js';
|
||||
import { html } from './components.js';
|
||||
|
||||
/** The switch's own label, in both languages (whoever opens it may not read the current one). */
|
||||
@@ -160,6 +161,7 @@ function applyDocument(lang) {
|
||||
doc.documentElement.dataset.lang = lang;
|
||||
doc.documentElement.dataset.script = scriptOf(t('卫戍协议'));
|
||||
doc.title = t('卫戍协议:盟约 · STRONGHOLD PROTOCOL');
|
||||
updateRotateHintPwaI18n(doc);
|
||||
}
|
||||
|
||||
// `{ dn }` params and tName(): Chinese game-data names → the current language (data/i18n/<lang>.json names)
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// Local listening preference, shared by roster and DIY details; deliberately absent from room.loadout.
|
||||
import { html } from './components.js';
|
||||
import { useSettings, updateSettings } from './settings.js';
|
||||
import { t } from '../../../shared/i18n.js';
|
||||
|
||||
const VOICE_LANG_NAMES = { cn: '中文', jp: '日本語' }; // i18n-ignore
|
||||
|
||||
export function OperatorVoice({ charId }) {
|
||||
const settings = useSettings();
|
||||
if (!charId) return null;
|
||||
const value = settings.voiceOverrides[charId] || '';
|
||||
const change = (lang) => {
|
||||
const voiceOverrides = { ...settings.voiceOverrides };
|
||||
if (lang) voiceOverrides[charId] = lang;
|
||||
else delete voiceOverrides[charId];
|
||||
updateSettings({ voiceOverrides });
|
||||
};
|
||||
return html`<label class="lo-voice" data-voice-char=${charId}>
|
||||
<span>${t('此干员语音')}</span>
|
||||
<span class="lo-select"><select aria-label=${t('此干员语音')} value=${value} onChange=${(e) => change(e.currentTarget.value)}>
|
||||
<option value="">${t('跟随全局')}</option><option value="cn">${VOICE_LANG_NAMES.cn}</option><option value="jp">${VOICE_LANG_NAMES.jp}</option>
|
||||
</select></span>
|
||||
<small>${t('仅保存在此浏览器,缺失的日语语音会回退到中文。')}</small>
|
||||
</label>`;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Recover an explicitly selected local seat; nickname and IP never identify a player.
|
||||
import { useEffect, useState } from '../../vendor/hooks.module.js';
|
||||
import { html, Button, Modal } from './components.js';
|
||||
import { identity } from '../net.js';
|
||||
import { t } from '../../../shared/i18n.js';
|
||||
import { toast } from './toasts.js';
|
||||
|
||||
export function ResumeMatchButton() {
|
||||
const [items, setItems] = useState(() => identity.recoverable());
|
||||
const [open, setOpen] = useState(false);
|
||||
const [busy, setBusy] = useState(false);
|
||||
useEffect(() => {
|
||||
const refresh = () => setItems(identity.recoverable());
|
||||
window.addEventListener('storage', refresh);
|
||||
window.addEventListener('focus', refresh);
|
||||
refresh();
|
||||
return () => { window.removeEventListener('storage', refresh); window.removeEventListener('focus', refresh); };
|
||||
}, []);
|
||||
if (!items.length) return null;
|
||||
const resume = async (item) => {
|
||||
if (busy) return;
|
||||
setBusy(true);
|
||||
if (await identity.resume(item.id)) {
|
||||
identity.saveName(item.name);
|
||||
identity.setEntered(true);
|
||||
location.reload();
|
||||
} else {
|
||||
setBusy(false);
|
||||
toast(t('此对局仍在其他窗口中,或当前浏览器无法安全恢复。请关闭原窗口后重试。'), 'warn');
|
||||
}
|
||||
};
|
||||
return html`<${Button} variant="secondary" size="sm" data-testid="resume-local-match" onClick=${() => setOpen(true)}>${t('恢复本机对局')}<//>
|
||||
<${Modal} open=${open} onClose=${() => !busy && setOpen(false)} title=${t('恢复本机对局')}>
|
||||
<p>${t('选择已关闭窗口的对局。服务器重启或对局过期后无法恢复。')}</p>
|
||||
${items.map((item) => html`<${Button} key=${item.id} variant="secondary" block=${true} disabled=${busy} onClick=${() => resume(item)}>
|
||||
${t('{name} · 同盟 {code}', { name: item.name || t('博士'), code: item.code || '—' })}<//>`)}
|
||||
<//>`;
|
||||
}
|
||||
+50
-16
@@ -1,18 +1,24 @@
|
||||
// Player settings (BGM/SFX/voice volume, the voice dub 语音语言, mute, damage numbers, render quality, the shortcut keys): a
|
||||
// tiny observable store persisted in localStorage (`sp.pref.settings`), applied to the audio manager on every change, plus
|
||||
// the settings modal — which also holds the language switch (ui/lang.js; kept apart in `sp.pref.lang`; under it a note
|
||||
// while the current language's pack is a machine translation, `_meta.machineTranslated`) and the 快捷键
|
||||
// section that rebinds the in-match shortcuts (the key map: ui/gameLogic/shortcuts.js; the community request
|
||||
// 「快捷键可不可以自己设置」, the owner's decision of 2026-10-07) and 问题反馈, which copies the diagnostics of this page
|
||||
// for a bug report (diag.js: the error log, this browser, optionally the battle on screen; nothing is uploaded). The
|
||||
// lobby and the room open it from a 设置 button next to 玩法说明 (SettingsButton, GitHub #238); the title screen and the
|
||||
// match have their own gear.
|
||||
// Player settings (BGM/SFX/voice volume, the voice dub 语音语言, mute, damage numbers, render quality, 文字大小, the
|
||||
// shortcut keys): a tiny observable store persisted in localStorage (`sp.pref.settings`), applied to the audio manager
|
||||
// and to the document on every change, plus the settings modal — which also holds the language switch (ui/lang.js; kept
|
||||
// apart in `sp.pref.lang`; under it a note while the current language's pack is a machine translation,
|
||||
// `_meta.machineTranslated`) and the 快捷键 section that rebinds the in-match shortcuts (the key map:
|
||||
// ui/gameLogic/shortcuts.js; the community request 「快捷键可不可以自己设置」, the owner's decision of 2026-10-07) and
|
||||
// 问题反馈, which copies the diagnostics of this page for a bug report (diag.js: the error log, this browser, optionally
|
||||
// the battle on screen; nothing is uploaded). The lobby and the room open it from a 设置 button next to 玩法说明
|
||||
// (SettingsButton, GitHub #238); the title screen and the match have their own gear.
|
||||
//
|
||||
// 文字大小 (textSize, applied by applyTextSize): the interface text root `--t` of css/theme.css — a phone clamps the
|
||||
// layout root `1rem` at 40 px (theme.css), which left the .18rem body text at 7.2 CSS px while the browser's own font
|
||||
// settings only inflate the glyphs inside fixed boxes (and page zoom is off: index.html's viewport, ui/device.js).
|
||||
// Only font-size declarations read `--t`, so the board, the HUD bands the prep camera keeps clear and the detail
|
||||
// card's side do not move — the field is sized from the host element's clientWidth (render/app.js).
|
||||
|
||||
import { useLayoutEffect, useRef, useState } from '../../vendor/hooks.module.js';
|
||||
import { html, Modal, Button, Icon, MicroLabel } from './components.js';
|
||||
import { GIcon } from './gameComponents.js';
|
||||
import { createStore, useStore, loadPref, savePref, store } from '../store.js';
|
||||
import { sanitizeSettings, HOTKEY_ACTIONS, DEFAULT_HOTKEYS, hotkeyLabel, rebindHotkey, isDefaultHotkeys, captureHotkey, VOICE_LANGS } from './gameLogic.js';
|
||||
import { sanitizeSettings, HOTKEY_ACTIONS, DEFAULT_HOTKEYS, hotkeyLabel, rebindHotkey, isDefaultHotkeys, captureHotkey, VOICE_LANGS, TEXT_SIZES } from './gameLogic.js';
|
||||
import { audio } from '../audio.js';
|
||||
import { openGuide } from './guide.js';
|
||||
import { detectFeatures } from './device.js';
|
||||
@@ -23,16 +29,31 @@ import { copyText } from './clipboard.js';
|
||||
|
||||
const cx = (...p) => p.flat().filter(Boolean).join(' ');
|
||||
|
||||
/** Settings store: { bgm, sfx, voice, voiceLang, muted, damageNumbers, quality, keys }. */
|
||||
/** Settings store: { bgm, sfx, voice, voiceLang, voiceOverrides, muted, damageNumbers, quality, textSize, keys }. */
|
||||
export const settingsStore = createStore(sanitizeSettings(loadPref('settings', null)));
|
||||
|
||||
/**
|
||||
* 设置 →「文字大小」: put the step on <html data-text> — css/theme.css turns it into the text root `--t`
|
||||
* (`:root[data-text="md"] { --t: … }`), which every readable font-size reads; no layout value does. The attribute
|
||||
* (not an inline style) keeps the default in the stylesheet: without it — no JavaScript, an old saved profile, a
|
||||
* value a future version dropped — the page is the design's own sizes. Values outside TEXT_SIZES fall back to 小.
|
||||
* @param {'sm'|'md'|'lg'|'xl'} v
|
||||
*/
|
||||
export function applyTextSize(v) {
|
||||
const el = globalThis.document?.documentElement;
|
||||
if (el) el.dataset.text = TEXT_SIZES.includes(v) ? v : 'sm';
|
||||
}
|
||||
|
||||
settingsStore.subscribe((s) => {
|
||||
savePref('settings', sanitizeSettings(s));
|
||||
audio.setVolumes(s);
|
||||
audio.setVoiceLang(s.voiceLang);
|
||||
audio.setVoiceLang(s.voiceLang, s.voiceOverrides);
|
||||
applyTextSize(s.textSize);
|
||||
});
|
||||
audio.setVolumes(settingsStore.get());
|
||||
audio.setVoiceLang(settingsStore.get().voiceLang);
|
||||
audio.setVoiceLang(settingsStore.get().voiceLang, settingsStore.get().voiceOverrides);
|
||||
// before the first render (main.js boot renders after its imports ran): the stored step is on screen without a flash
|
||||
applyTextSize(settingsStore.get().textSize);
|
||||
|
||||
/** @param {Partial<ReturnType<typeof sanitizeSettings>>} patch */
|
||||
export function updateSettings(patch) {
|
||||
@@ -68,6 +89,11 @@ function Toggle({ label, micro, value, onChange }) {
|
||||
}
|
||||
|
||||
const QUALITY = [['high', N_('高')], ['medium', N_('中')], ['low', N_('低')]];
|
||||
/**
|
||||
* 文字大小: one step of TEXT_SIZES (ui/gameLogic/settings.js) with its label — 小 is the design's own sizes, the others
|
||||
* raise the text root `--t` (css/theme.css) and with it every readable font-size; the board and the HUD's boxes stay.
|
||||
*/
|
||||
const TEXT_SCALES = [['sm', N_('小')], ['md', N_('中')], ['lg', N_('大')], ['xl', N_('特大')]];
|
||||
/**
|
||||
* 语音语言: each dub named in its own language, like the interface language switch (ui/lang.js) — the owner's
|
||||
* 「中文 / 日本語」 (2026-10-08); VOICE_LANGS order.
|
||||
@@ -216,8 +242,8 @@ export function SettingsModal({ open, onClose }) {
|
||||
const [touchUi] = useState(() => detectFeatures().coarse && !detectFeatures().fine);
|
||||
const mtNote = machineTranslationNote(); // a pack marked as machine translation says so under the switch
|
||||
return html`<${Modal} open=${open} onClose=${onClose} title=${t('设置')} micro="SETTINGS" width="7.4rem"
|
||||
actions=${html`<${Button} variant="secondary" icon="book" class="set-guide" onClick=${() => openGuide(0)}>${t('玩法说明')}<//>
|
||||
<${Button} variant="primary" icon="check" onClick=${onClose}>${t('完成')}<//>`}>
|
||||
actions=${html`<${Button} variant="secondary" icon="book" class="set-guide" onClick=${() => openGuide(0)}>${t('玩法说明')}<//>
|
||||
<${Button} variant="primary" icon="check" onClick=${onClose}>${t('完成')}<//>`}>
|
||||
<div class="set-list">
|
||||
<div class="set-row">
|
||||
<span class="set-row__label">${t('语言')}<${MicroLabel}>LANGUAGE<//></span>
|
||||
@@ -234,7 +260,7 @@ export function SettingsModal({ open, onClose }) {
|
||||
</div>
|
||||
</div>
|
||||
<${Slider} label=${t('音效')} micro="SFX" icon="signal" value=${s.sfx}
|
||||
onInput=${(v) => { updateSettings({ sfx: v }); if (!tested) { setTested(true); setTimeout(() => setTested(false), 400); audio.sfx('click'); } }} />
|
||||
onInput=${(v) => { updateSettings({ sfx: v }); if (!tested) { setTested(true); setTimeout(() => setTested(false), 400); audio.sfx('click'); } }} />
|
||||
<${Toggle} label=${t('静音')} micro="MUTE" value=${s.muted} onChange=${(v) => updateSettings({ muted: v })} />
|
||||
<${Toggle} label=${t('显示伤害数字')} micro="DAMAGE NUMBERS" value=${s.damageNumbers} onChange=${(v) => updateSettings({ damageNumbers: v })} />
|
||||
<div class="set-row">
|
||||
@@ -244,6 +270,14 @@ export function SettingsModal({ open, onClose }) {
|
||||
class=${s.quality === id ? 'is-on' : ''} onClick=${() => updateSettings({ quality: id })}>${t(label)}</button>`)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="set-row">
|
||||
<span class="set-row__label">${t('文字大小')}<${MicroLabel}>TEXT SIZE<//></span>
|
||||
<div class="set-seg set-textsize" role="radiogroup" aria-label=${t('文字大小')} data-testid="text-size">
|
||||
${TEXT_SCALES.map(([id, label]) => html`<button key=${id} type="button" role="radio" aria-checked=${s.textSize === id ? 'true' : 'false'}
|
||||
class=${s.textSize === id ? 'is-on' : ''} onClick=${() => updateSettings({ textSize: id })}>${t(label)}</button>`)}
|
||||
</div>
|
||||
</div>
|
||||
<p class="set-hint set-textsize-note">${t('调整界面文字大小,棋盘保持原比例。')}</p>
|
||||
<${HotkeySection} keys=${s.keys} touchUi=${touchUi} />
|
||||
<${DiagSection} />
|
||||
<p class="set-hint">${touchUi ? t('触屏操作:点击单位选中(撤退 / 出售)· 长按单位或卡牌查看详情 · 拖动部署后滑动选择朝向') : t('右键查看详情')}</p>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// A server-owned unanimous vote. Reconnects render the current vote from m.public.
|
||||
import { useRef, useState } from '../../vendor/hooks.module.js';
|
||||
import { html, Button } from './components.js';
|
||||
import { actions } from './gameActions.js';
|
||||
import { useStore } from '../store.js';
|
||||
import { t } from '../../../shared/i18n.js';
|
||||
|
||||
export function SetupReroll({ pub }) {
|
||||
const myId = useStore((s) => s.me.playerId);
|
||||
const hostId = useStore((s) => s.room?.hostId);
|
||||
const online = useStore((s) => s.connection.status === 'online');
|
||||
const [busy, setBusy] = useState(false);
|
||||
const pending = useRef(false);
|
||||
const vote = pub.rerollVote;
|
||||
const host = myId === hostId;
|
||||
const players = pub.players || [];
|
||||
const me = players.find((p) => p.playerId === myId && p.status !== 'left');
|
||||
const allConnected = players.every((p) => p.isBot || p.status === 'left' || p.connected);
|
||||
const voter = !!vote?.voters.includes(myId);
|
||||
const agreed = !!vote?.agreed.includes(myId);
|
||||
const send = async (fn) => {
|
||||
if (pending.current) return;
|
||||
pending.current = true;
|
||||
setBusy(true);
|
||||
try { await fn(); } finally { pending.current = false; setBusy(false); }
|
||||
};
|
||||
if (!vote && (!host || !me)) return null;
|
||||
return html`<section class="brief-reroll" aria-label=${t('重刷本局')} aria-live="polite">
|
||||
<div class="brief-reroll__text">
|
||||
<strong>${vote ? t('重刷本局投票:{agreed}/{total}', { agreed: vote.agreed.length, total: vote.voters.length }) : t('重刷本局')}</strong>
|
||||
<span>${vote ? t('全员同意后重新随机战场、领袖、特训敌人和禁用;确认倒计时已暂停。') : t('保留房间和个人配置,重新随机全部开局信息。')}</span>
|
||||
${vote ? html`<div class="brief-reroll__players">${vote.voters.map((id) => {
|
||||
const p = players.find((p) => p.playerId === id);
|
||||
return html`<span key=${id} class=${vote.agreed.includes(id) ? 'is-agreed' : ''}>${p?.name || id} · ${vote.agreed.includes(id) ? t('已同意') : t('待同意')}</span>`;
|
||||
})}</div>` : null}
|
||||
</div>
|
||||
<div class="brief-reroll__actions">
|
||||
${!vote ? html`<${Button} icon="refresh" size="lg" loading=${busy} disabled=${!online || !allConnected}
|
||||
title=${allConnected ? t('全体真人玩家一致同意后刷新,随机结果可能重复。') : t('请等待所有玩家连接后再发起投票')}
|
||||
onClick=${() => send(() => actions.rerollSetup(pub.setupRevision ?? 0))}>${t('重刷本局')}<//>` : null}
|
||||
${vote && voter ? html`<${Button} variant="primary" loading=${busy} disabled=${!online || agreed}
|
||||
onClick=${() => send(() => actions.rerollVote(vote.id, true))}>${agreed ? t('已同意') : t('同意重刷')}<//>
|
||||
<${Button} variant="danger" disabled=${busy || !online}
|
||||
onClick=${() => send(() => actions.rerollVote(vote.id, false))}>${t('拒绝重刷')}<//>` : null}
|
||||
${vote && host && me ? html`<${Button} disabled=${busy || !online}
|
||||
onClick=${() => send(() => actions.cancelReroll(vote.id))}>${t('取消投票')}<//>` : null}
|
||||
</div>
|
||||
</section>`;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Browser-local dub preferences. Every variant of an operator shares its charId; absent entries follow the global dub.
|
||||
export const VOICE_LANGS = Object.freeze(['cn', 'jp']);
|
||||
export function sanitizeVoiceOverrides(raw) {
|
||||
const out = {};
|
||||
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return out;
|
||||
for (const [id, lang] of Object.entries(raw).slice(0, 512)) {
|
||||
if (/^char_[0-9]+_[a-z0-9]+$/.test(id) && id.length <= 80 && VOICE_LANGS.includes(lang)) out[id] = lang;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
export function voiceLangFor(charId, globalLang, overrides) {
|
||||
const own = Object.hasOwn(overrides || {}, charId) ? overrides[charId] : null;
|
||||
return VOICE_LANGS.includes(own) ? own : globalLang === 'jp' ? 'jp' : 'cn';
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "/",
|
||||
"name": "卫戍协议:盟约",
|
||||
"short_name": "卫戍协议",
|
||||
"description": "明日方舟「卫戍协议:盟约」非官方同人复刻",
|
||||
"start_url": "/",
|
||||
"scope": "/",
|
||||
"display": "fullscreen",
|
||||
"orientation": "landscape-primary",
|
||||
"background_color": "#0c0f0e",
|
||||
"theme_color": "#0c0f0e",
|
||||
"icons": [
|
||||
{ "src": "/icons/app-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" },
|
||||
{ "src": "/icons/app-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" }
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user