Spaces:
Running
Running
File size: 18,709 Bytes
fcce7e3 369b236 fcce7e3 369b236 fcce7e3 369b236 fcce7e3 bebd577 fcce7e3 369b236 fcce7e3 369b236 fcce7e3 6468c17 fcce7e3 b51e03a 369b236 181ede2 369b236 6468c17 369b236 181ede2 6468c17 b51e03a 369b236 6468c17 369b236 6468c17 369b236 6468c17 369b236 6468c17 369b236 fcce7e3 369b236 fcce7e3 bebd577 369b236 fcce7e3 c782e5b 3597f5a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 | /* ============================================================================
TUTORIAL — KEEL, ship liaison intelligence
----------------------------------------------------------------------------
Visual family: same panel gradient, same border-image trick, same type
ramp as the rest of the HUD (--panelG2, --fT/--fU, the bevelled border
gradients in src/styles/ui.css's :root) — those custom properties cascade
from :root regardless of which stylesheet declared them, so reusing them
here keeps KEEL native to the HUD instead of a decal stuck on top of it.
KEEL gets its own accent (cyan, --keelB below) so its panel reads as its
own character rather than a reskinned dispatch or coach banner.
Placement: docked centre, below the goal/hero/hive cluster (ends ~105px
from the safe-area top once deployed) and below the native #toast's usual
footprint (up to ~190px for a three-line message), so KEEL's line and a
stock toast very rarely fight for the same pixels. Verified empirically
against 412×900 screenshots — see docs/TUTORIAL.md.
============================================================================ */
:root{
--keelB:linear-gradient(160deg,#bdeeff 0%,#6fd0ff 26%,#0c3a52 58%,#1a6f99 80%,#bdeeff 100%);
}
#keelWrap{
position:absolute; left:50%; top:calc(var(--sat) + 204px);
transform:translateX(-50%);
width:min(90vw,380px);
z-index:19; /* level with #coach — both are HUD-native narration, neither an overlay */
pointer-events:none;
opacity:0; visibility:hidden;
transition:opacity .28s ease;
}
#keelWrap.show{ opacity:1; visibility:visible; pointer-events:none; }
#keelWrap.show #keelBar{ pointer-events:auto; }
#keelBar{
display:flex; align-items:flex-start; gap:9px;
border-radius:12px; padding:9px 10px 9px 9px;
border:1px solid transparent;
background:var(--panelG2) padding-box, var(--keelB) border-box;
box-shadow:0 6px 20px rgba(0,0,0,.55);
transition:opacity .3s ease, transform .2s ease;
}
#keelBar.resting{ opacity:.82; transform:scale(.99); }
#keelBar.pulse{ animation:keelPulse .5s cubic-bezier(.2,.8,.3,1); }
@keyframes keelPulse{
0%{ transform:scale(.96); box-shadow:0 6px 20px rgba(0,0,0,.55), 0 0 0 0 rgba(120,220,255,.5); }
60%{ transform:scale(1.015); }
100%{ transform:scale(1); box-shadow:0 6px 20px rgba(0,0,0,.55), 0 0 0 8px rgba(120,220,255,0); }
}
/* ---------- avatar: supplied Nova commander profile under a live signal
treatment, so tutorial dialogue has a recognizable speaker ---------- */
#keelBadge{ position:relative; width:42px; height:42px; flex:0 0 42px; margin-top:1px;
border-radius:9px;background:url('../../assets/factions/nova_192.jpg') center/cover no-repeat;
border:1px solid rgba(120,220,255,.78);box-shadow:0 0 12px rgba(70,190,245,.24);overflow:hidden; }
#keelBadge .kr{
position:absolute; inset:4px; border-radius:50%;
border:1.5px solid rgba(120,220,255,.55);
}
#keelBadge .kr2{ inset:10px; border-color:rgba(120,220,255,.85); }
#keelBadge .kd{
position:absolute; left:50%; top:50%; width:5px; height:5px; margin:-2.5px 0 0 -2.5px;
border-radius:50%; background:#bdeeff; box-shadow:0 0 6px 1px rgba(120,220,255,.9);
}
#keelBar.pulse #keelBadge .kr1{ animation:keelRing 1.1s ease-out; }
#keelBar.pulse #keelBadge .kr2{ animation:keelRing 1.1s .12s ease-out; }
@keyframes keelRing{
0%{ transform:scale(.6); opacity:1; }
100%{ transform:scale(1.7); opacity:0; }
}
#keelBody{ flex:1 1 auto; min-width:0; }
#keelHd{ display:flex; align-items:baseline; gap:7px; flex-wrap:wrap; }
#keelName{
font-family:var(--fT); font-size:11.5px; font-weight:800; letter-spacing:.14em;
color:#bdeeff; text-shadow:0 0 8px rgba(65,200,255,.5);
}
#keelStepTag{
font-family:var(--fT); font-size:9px; font-weight:700; letter-spacing:.1em;
color:#7fa8c6; text-transform:uppercase;
}
#keelTxt{
margin-top:2px; font-size:12.5px; line-height:1.36; font-weight:600; color:#eaf7ff;
}
#keelProgress{
height:3px;margin-top:7px;border-radius:4px;overflow:hidden;background:rgba(4,12,20,.78);
box-shadow:inset 0 0 0 1px rgba(120,205,242,.16);
}
#keelProgress i{
display:block;width:0;height:100%;border-radius:inherit;
background:linear-gradient(90deg,#42c9ff,#ffe06b);box-shadow:0 0 7px rgba(88,205,255,.62);
transition:width .3s ease;
}
#keelNext{
display:none;align-items:center;justify-content:center;min-width:92px;min-height:44px;margin-top:8px;padding:8px 15px;
border-radius:8px;border:1px solid rgba(255,218,112,.75);color:#171104;
background:linear-gradient(180deg,#fff0a8,#dca936);box-shadow:0 0 13px rgba(255,207,75,.28);
font:900 9px/1 var(--fT);letter-spacing:.13em;
}
#keelSkip{
flex:0 0 auto; align-self:flex-start;
/* Visual glyph is small; the tap target is not. 44px is the WCAG 2.5.5
floor — verified against the rendered box, not just the source, since
box-sizing elsewhere in this codebase can make padding eat into a
min-width instead of adding to it. */
min-width:52px; min-height:44px; margin:-9px -9px 0 0;
display:flex; align-items:center; justify-content:center;
border-radius:9px; border:none; background:transparent; color:#7fa8c6;
font:800 9px/1 var(--fT);letter-spacing:.08em;
box-sizing:border-box;
}
#keelSkip:active{ background:rgba(255,255,255,.08); color:#eaf7ff; }
/* Reactive dialogue stays available during battle, but yields most of its
height to the battlefield. The full line remains in the event feed and the
card expands again as soon as combat heat falls. */
body.mfCombatHot #keelBar{align-items:center;gap:6px;padding:5px 7px;border-radius:9px}
body.mfCombatHot #keelBadge{width:30px;height:30px;flex-basis:30px;margin:0}
body.mfCombatHot #keelHd{flex-wrap:nowrap}
body.mfCombatHot #keelName{font-size:9px}
body.mfCombatHot #keelStepTag{display:none}
body.mfCombatHot #keelTxt{margin:0;font-size:10px;line-height:1.15;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
body.mfCombatHot #keelProgress,body.mfCombatHot #keelNext{display:none!important}
body.mfCombatHot #keelSkip{min-width:40px;min-height:36px;margin:-5px -7px -5px 0;font-size:8px}
/* The guided target never receives input. It only makes the existing hit box
visible, so the button/tab/card keeps its normal mobile interaction path. */
.keelFocus{
position:relative!important;z-index:22!important;
outline:4px solid #ffe06b!important;outline-offset:4px!important;
box-shadow:0 0 0 4px rgba(255,214,70,.30),0 0 30px 9px rgba(255,202,50,.82)!important;
animation:keelTarget 1.05s ease-in-out infinite!important;
}
@keyframes keelTarget{
0%,100%{filter:brightness(1);}
50%{filter:brightness(1.38);}
}
#keelMapCue{
position:fixed;left:50%;top:50%;width:82px;height:82px;transform:translate(-50%,-50%);z-index:21;
display:none;border:3px solid #ffe06b;border-radius:50%;pointer-events:none;box-sizing:border-box;
box-shadow:0 0 0 7px rgba(255,215,72,.15),0 0 26px rgba(255,204,55,.72);
animation:keelMapPulse 1.05s ease-in-out infinite;
}
#keelMapCue.show{display:block}
#keelMapCue::before,#keelMapCue::after{content:'';position:absolute;background:#ffe06b;opacity:.9}
#keelMapCue::before{width:2px;height:112px;left:37px;top:-18px}
#keelMapCue::after{height:2px;width:112px;left:-18px;top:37px}
#keelMapCue span{
position:absolute;left:50%;top:calc(100% + 10px);transform:translateX(-50%);min-width:108px;padding:5px 7px;
border-radius:7px;background:rgba(10,15,22,.94);border:1px solid rgba(255,224,107,.75);color:#ffe891;
text-align:center;white-space:nowrap;font:900 9px/1.2 var(--fT);letter-spacing:.08em;
}
#keelModeBadge{position:fixed;left:50%;top:calc(var(--sat) + 72px);transform:translateX(-50%);z-index:20;display:none;
min-width:188px;padding:7px 10px;border:1px solid rgba(255,224,107,.72);border-radius:9px;text-align:center;
background:linear-gradient(135deg,rgba(51,37,9,.96),rgba(8,21,31,.95));box-shadow:0 5px 18px rgba(0,0,0,.42),0 0 16px rgba(255,208,67,.16);pointer-events:none}
#keelModeBadge.show{display:block}#keelModeBadge b,#keelModeBadge span{display:block;font-family:var(--fT);letter-spacing:.1em}#keelModeBadge b{color:#ffe792;font-size:9px}#keelModeBadge span{margin-top:3px;color:#a9d7ef;font-size:9px;font-weight:800}
/* One coaching surface on a phone. STEP/TOTAL already lives in #keelBar, and
ordinary command notices remain recorded in the notification log. Showing
both duplicates above/below KEEL produced three stacked panels before the
player could even see the unit being discussed. Emergency attack/wave rails
are separate and remain available. */
body.trainingOperation #keelModeBadge,body.trainingOperation #toast{display:none!important}
@keyframes keelMapPulse{0%,100%{transform:translate(-50%,-50%) scale(.92)}50%{transform:translate(-50%,-50%) scale(1.06)}}
/* First-time entry point. PLAY remains a normal skirmish; this explicit card
launches the fixed, protected training ruleset. */
#keelTrainingCallout{
width:min(88vw,360px);box-sizing:border-box;margin:15px auto 0;padding:9px;border-radius:12px;text-align:left;
display:flex;align-items:center;gap:9px;
border:1px solid rgba(255,216,86,.52);background:linear-gradient(180deg,rgba(43,35,15,.94),rgba(13,20,29,.97));
box-shadow:0 5px 18px rgba(0,0,0,.42),0 0 18px rgba(255,202,55,.1);
}
.ktMiniIdentity{display:flex;align-items:center;gap:8px;flex:1;min-width:0}.ktMiniIdentity>img{width:38px;height:38px;object-fit:contain;flex:0 0 38px}
.ktMiniIdentity>div{min-width:0}.ktMiniIdentity span,.ktMiniIdentity small{display:block;color:#ffe079;font:900 9px/1.1 var(--fT);letter-spacing:.12em}
.ktMiniIdentity b{display:block;margin:3px 0;color:#fff;font:900 10px/1.1 var(--fT);letter-spacing:.06em}.ktMiniIdentity small{color:#83a5bd;letter-spacing:.08em}
#keelTrainingCallout>button{
flex:0 0 98px;min-width:98px;min-height:48px;padding:8px;border-radius:8px;border:1px solid #ffe79a;color:#181203;
background:linear-gradient(180deg,#fff1a8,#dda837);font:900 9px/1 var(--fT);letter-spacing:.07em;
}
#keelTrainingCallout+#startBtn{margin-top:10px!important}
.keelTrainingOp{
--ktoAccent:#ffe079;position:relative;margin:0 0 13px;padding:0 12px 12px;border-radius:14px;text-align:left;overflow:hidden;
border:1px solid rgba(255,215,80,.56);background:linear-gradient(155deg,rgba(53,39,11,.92),rgba(13,21,31,.98) 48%,rgba(8,27,40,.98));
box-shadow:0 8px 22px rgba(0,0,0,.35),inset 0 1px rgba(255,255,255,.04);
}
.keelTrainingOp::after{content:'';position:absolute;right:-65px;top:42px;width:210px;height:210px;border-radius:50%;pointer-events:none;background:radial-gradient(circle,rgba(75,194,255,.12),transparent 67%)}
.keelTrainingOp.done{--ktoAccent:#8bf0b7;border-color:rgba(104,237,166,.48);background:linear-gradient(155deg,rgba(18,54,40,.82),rgba(10,23,27,.98) 52%,rgba(8,28,39,.98))}
.keelTrainingOp.active{--ktoAccent:#76ddff;border-color:rgba(89,211,255,.68)}
.ktoHead{height:38px;margin:0 -12px 10px;padding:0 12px;display:flex;align-items:center;justify-content:space-between;gap:8px;border-bottom:1px solid rgba(255,224,121,.21);background:rgba(4,12,19,.35)}
.ktoHead span{color:var(--ktoAccent);font:900 9px/1 var(--fT);letter-spacing:.13em}.ktoHead b{padding:5px 7px;border:1px solid rgba(255,224,120,.48);border-radius:7px;color:var(--ktoAccent);font:900 9px/1 var(--fT);letter-spacing:.07em;background:rgba(0,0,0,.25)}
.ktoHero{position:relative;z-index:1;display:grid;grid-template-columns:82px 1fr;gap:10px;align-items:center}.ktoPortrait{position:relative;width:82px;height:82px;border-radius:11px;overflow:hidden;border:1px solid rgba(117,214,255,.62);background:#07131e;box-shadow:0 0 15px rgba(66,190,247,.16)}
.ktoPortrait>img{width:100%;height:100%;display:block;object-fit:cover}.ktoPortrait>span{position:absolute;right:4px;bottom:4px;width:34px;height:34px;display:flex;align-items:center;justify-content:center;border-radius:8px;background:rgba(3,12,20,.88);border:1px solid rgba(116,214,255,.48)}
.ktoPortrait .facIc{width:28px!important;height:28px!important;object-fit:contain}.ktoIdentity{min-width:0}.ktoIdentity small{display:block;color:#77d8ff;font:900 9px/1 var(--fT);letter-spacing:.14em}.ktoIdentity>b{display:block;margin:4px 0;color:#fff;font:900 14px/1.05 var(--fT);letter-spacing:.07em}.ktoIdentity i{display:block;color:#d8ecf8;font:800 9px/1.2 var(--fT);font-style:normal}.ktoIdentity p{margin:5px 0 0;color:#91adbf;font-size:9px;line-height:1.35;font-weight:650}
.ktoTeach{position:relative;z-index:1;display:flex;flex-wrap:wrap;gap:5px;margin-top:10px}.ktoTeach span{flex:1 1 29%;min-width:82px;padding:6px 5px;border-radius:7px;text-align:center;color:#a9c8da;background:rgba(3,12,20,.55);border:1px solid rgba(116,167,200,.2);font:850 9px/1 var(--fT);letter-spacing:.06em}
.ktoProgress{position:relative;z-index:1;margin-top:9px;padding:8px 9px;border-radius:9px;background:rgba(3,11,18,.58);border:1px solid rgba(119,177,212,.22)}.ktoProgress>div:first-child{display:flex;align-items:center;justify-content:space-between}.ktoProgress span{color:#8aa9bf;font:900 9px/1 var(--fT);letter-spacing:.11em}.ktoProgress b{color:var(--ktoAccent);font:900 10px/1 var(--fT)}
.ktoProgressTrack{height:5px;margin-top:6px;border-radius:5px;overflow:hidden;background:#07121d!important}.ktoProgressTrack i{display:block;height:100%;border-radius:inherit;background:linear-gradient(90deg,#2bb9ef,var(--ktoAccent));box-shadow:0 0 8px var(--ktoAccent)}.ktoProgress small{display:block;margin-top:5px;color:#809aad;font-size:9px;line-height:1.25;font-weight:700}
.ktoFacts{position:relative;z-index:1;display:grid;grid-template-columns:repeat(3,1fr);gap:6px;margin-top:8px}.ktoFacts div{padding:6px 3px;border-radius:8px;text-align:center;background:rgba(5,15,23,.62);border:1px solid rgba(110,167,200,.2)}.ktoFacts b,.ktoFacts span{display:block}.ktoFacts b{color:#eaf8ff;font:900 9px/1 var(--fT)}.ktoFacts span{margin-top:3px;color:#7998ad;font:800 9px/1 var(--fT);letter-spacing:.06em}
.ktoAction{position:relative;z-index:1;width:100%;min-height:50px;margin-top:9px;padding:9px;border-radius:9px;border:1px solid rgba(255,232,144,.82);color:#171204;background:linear-gradient(180deg,#fff0a1,#d5a42e);box-shadow:0 0 14px rgba(255,207,69,.16);font:900 9px/1 var(--fT);letter-spacing:.09em}.keelTrainingOp.done .ktoAction{border-color:rgba(139,240,183,.7);background:linear-gradient(180deg,#b8f5d1,#58bf88)}
@media (max-width:380px){
#keelWrap{ top:calc(var(--sat) + 196px); width:92vw; }
#keelTxt{ font-size:12px; }
#keelTrainingCallout{width:94vw}.ktMiniIdentity>img{display:none}#keelTrainingCallout>button{flex-basis:92px;min-width:92px}
.keelTrainingOp{padding-left:10px;padding-right:10px}.ktoHead{margin-left:-10px;margin-right:-10px;padding-left:10px;padding-right:10px}.ktoHero{grid-template-columns:72px 1fr}.ktoPortrait{width:72px;height:76px}.ktoTeach span{min-width:76px}
}
@media (prefers-reduced-motion:reduce){
#keelWrap{ transition:none; }
#keelBar{ transition:none; }
#keelBar.pulse{ animation:none; }
#keelBar.pulse #keelBadge .kr1,#keelBar.pulse #keelBadge .kr2{ animation:none; }
#keelProgress i{transition:none}
.keelFocus,#keelMapCue{animation:none!important}
}
/* SKIP vs END. The same button leaves guidance (harmless) or abandons a
training match (destructive). Only the destructive state is dressed as
destructive — a bordered amber chip rather than a transparent 9px hint. */
#keelSkip.keelSkipEnd{
background:rgba(255,150,60,.14); border:1px solid rgba(255,170,80,.55);
color:#ffcf9a; letter-spacing:.10em;
}
#keelSkip.keelSkipEnd:active{ background:rgba(255,150,60,.30); color:#fff; }
/* ============================================================================
WAR TABLE PRIMER — src/warprimer.js
----------------------------------------------------------------------------
The card lives INSIDE the active .mfStagePanel rather than floating over it,
so there is deliberately no position/z-index/pointer-events here: it is a
block in the stage's own flow and inherits that panel's scrolling. It reuses
--panelG2 and the HUD type ramp for the same reason KEEL does, but takes the
war table's own amber-gold accent instead of KEEL's cyan — this is table
chrome, not the ship talking.
============================================================================ */
.wtpCard{
display:block; margin:0 0 12px; padding:11px 13px 10px;
background:var(--panelG2,linear-gradient(160deg,rgba(14,22,30,.94),rgba(8,13,19,.96)));
border:1px solid rgba(255,196,110,.34); border-radius:10px;
box-shadow:inset 0 1px 0 rgba(255,220,160,.14),0 6px 18px rgba(0,0,0,.42);
}
.wtpCard>header{
display:flex; align-items:baseline; justify-content:space-between; gap:10px;
margin-bottom:6px;
}
.wtpCard>header>span{
font:600 10px/1 var(--fU,system-ui); letter-spacing:.14em; color:#ffc86e;
}
.wtpCard>header>b{
font:700 10px/1 var(--fU,system-ui); letter-spacing:.1em; color:rgba(232,240,248,.78);
white-space:nowrap;
}
.wtpCard>p{
margin:0 0 9px; font:400 12px/1.48 var(--fT,system-ui); color:rgba(232,240,248,.90);
}
.wtpFoot{ display:flex; align-items:center; justify-content:space-between; gap:10px; }
/* The route echoes the stage stepper at the top of the screen so the card and
the nav agree on where the player is, without the card scrolling that nav
out of view being a problem. */
.wtpFoot>i{
display:flex; align-items:center; gap:4px; flex-wrap:wrap; font-style:normal;
font:600 9.5px/1.2 var(--fU,system-ui); letter-spacing:.08em; color:rgba(232,240,248,.62);
}
.wtpFoot>i>u{ text-decoration:none; color:rgba(232,240,248,.42); }
.wtpFoot>i>em{ font-style:normal; }
.wtpFoot>i>em.past{ color:rgba(255,200,110,.78); }
.wtpFoot>i>em.on{ color:#ffc86e; }
.wtpDone{
flex:0 0 auto; min-height:44px; min-width:44px; padding:0 14px;
background:rgba(255,196,110,.13); border:1px solid rgba(255,196,110,.44);
border-radius:7px; color:#ffd79a;
font:700 11px/1 var(--fU,system-ui); letter-spacing:.12em;
}
.wtpDone:active{ background:rgba(255,196,110,.30); color:#fff; }
.wtpSkip{
flex:0 0 auto; min-height:44px; padding:0 10px;
background:transparent; border:0; color:rgba(232,240,248,.78);
font:700 10px/1 var(--fU,system-ui); letter-spacing:.12em;
}
.wtpSkip:active{ color:#fff; }
.wtpCard.wtpLast{ border-color:rgba(150,255,190,.36); }
.wtpCard.wtpLast>header>span,.wtpCard.wtpLast .wtpFoot>i>em.on{ color:#9fffc4; }
.wtpCard.wtpLast .wtpDone{
background:rgba(120,255,180,.13); border-color:rgba(150,255,190,.46); color:#bfffd9;
}
@media (max-width:430px){
.wtpCard{ padding:9px 10px 9px; }
.wtpCard>p{ font-size:11.5px; }
.wtpFoot{ flex-wrap:wrap; }
.wtpFoot>i{ order:2; flex:1 1 100%; }
.wtpDone,.wtpSkip{ order:1; width:100%; }
}
|