mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-10 02:03:57 +08:00
zik try dis
This commit is contained in:
@@ -36,6 +36,7 @@
|
||||
--glass-border-light: rgba(255, 255, 255, 0.2);
|
||||
--glass-shadow: 0 8px 24px 0 rgba(0, 0, 0, 0.35);
|
||||
--glass-blur: blur(4px);
|
||||
--card-scroll-bg: rgba(22, 18, 38, 0.95);
|
||||
--glass-text: #f3f2f8;
|
||||
--glass-accent: #9d72ff;
|
||||
|
||||
@@ -105,6 +106,7 @@
|
||||
--surface-container: rgba(255, 255, 255, 0.75);
|
||||
--surface-container-high: rgba(255, 255, 255, 0.95);
|
||||
--surface-container-low: rgba(240, 243, 252, 0.75);
|
||||
--card-scroll-bg: rgba(245, 246, 252, 0.96);
|
||||
|
||||
--on-surface: #171526;
|
||||
--on-surface-variant: #5f5b7d;
|
||||
@@ -186,6 +188,8 @@ body {
|
||||
pointer-events: none;
|
||||
transform: translateZ(0);
|
||||
will-change: transform;
|
||||
isolation: isolate;
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
#galaxy-bg::before {
|
||||
@@ -204,7 +208,6 @@ body {
|
||||
opacity: 0.5;
|
||||
will-change: transform;
|
||||
transform: translate3d(var(--gx-para-x), 0, 0);
|
||||
animation: galaxy-drift-smooth 160s linear infinite;
|
||||
}
|
||||
|
||||
#galaxy-bg::after {
|
||||
@@ -221,7 +224,6 @@ body {
|
||||
opacity: 0.6;
|
||||
will-change: transform;
|
||||
transform: translate3d(var(--gx-para-x), 0, 0);
|
||||
animation: galaxy-drift-smooth 100s linear infinite;
|
||||
}
|
||||
|
||||
#galaxy-bg .galaxy-hero {
|
||||
@@ -234,18 +236,14 @@ body {
|
||||
animation: galaxy-twinkle 6s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes galaxy-drift-smooth {
|
||||
from { transform: translate3d(0, 0, 0); }
|
||||
to { transform: translate3d(0, -400px, 0); }
|
||||
}
|
||||
|
||||
@keyframes galaxy-twinkle {
|
||||
0%, 100% { opacity: 0.3; transform: scale(0.9); }
|
||||
50% { opacity: 1; transform: scale(1.2); }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
#galaxy-bg, #galaxy-bg::before, #galaxy-bg::after, #galaxy-bg .galaxy-hero {
|
||||
#galaxy-bg, #galaxy-bg::before, #galaxy-bg::after, #galaxy-bg .galaxy-hero,
|
||||
#galaxy-stars, #galaxy-stars .galaxy-hero {
|
||||
animation: none !important;
|
||||
}
|
||||
}
|
||||
@@ -306,6 +304,8 @@ ul { list-style: none; margin: 0; padding: 0; }
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: var(--z-appbar);
|
||||
transform: translateZ(0);
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
.gx-appbar__pill {
|
||||
@@ -513,7 +513,10 @@ ul { list-style: none; margin: 0; padding: 0; }
|
||||
position: fixed;
|
||||
bottom: calc(18px + env(safe-area-inset-bottom, 0px));
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
transform: translateX(-50%) translateZ(0);
|
||||
will-change: transform;
|
||||
isolation: isolate;
|
||||
contain: layout style;
|
||||
z-index: 1000;
|
||||
|
||||
display: flex;
|
||||
@@ -589,7 +592,81 @@ ul { list-style: none; margin: 0; padding: 0; }
|
||||
box-shadow: var(--elev-1);
|
||||
margin-bottom: var(--sp-4);
|
||||
overflow: hidden;
|
||||
transition: transform var(--motion-fast), box-shadow var(--motion-fast);
|
||||
transition: transform var(--motion-fast), box-shadow var(--motion-fast), background-color 0.15s ease-out;
|
||||
}
|
||||
|
||||
/* Surgical content-visibility for top-level non-collapsible cards */
|
||||
.dh-card:not(.dh-stat),
|
||||
.gx-content > div > .gx-card {
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: 280px;
|
||||
contain-intrinsic-size: auto 280px;
|
||||
}
|
||||
|
||||
.dh-stat {
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: 110px;
|
||||
contain-intrinsic-size: auto 110px;
|
||||
}
|
||||
|
||||
/* Disarm containment on collapsible sections and nested cards */
|
||||
.gx-card:has(.gx-section__body),
|
||||
.gx-card .gx-card,
|
||||
.gx-tree-node .gx-card {
|
||||
content-visibility: visible !important;
|
||||
contain-intrinsic-size: none !important;
|
||||
}
|
||||
|
||||
/* Velocity-Adaptive Glass: bypass blur on scrolling cards with contrast boost */
|
||||
body.is-scrolling .gx-card,
|
||||
body.is-scrolling .gx-tile {
|
||||
backdrop-filter: none !important;
|
||||
-webkit-backdrop-filter: none !important;
|
||||
background: var(--card-scroll-bg) !important;
|
||||
}
|
||||
|
||||
/* Layer 2: Celestial Starlight Canopy */
|
||||
#galaxy-stars {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 20;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
contain: strict;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
#galaxy-stars .galaxy-hero {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(255, 255, 255, 0.95) 0%,
|
||||
rgba(157, 114, 255, 0.65) 35%,
|
||||
rgba(77, 232, 232, 0.2) 65%,
|
||||
transparent 100%
|
||||
);
|
||||
box-shadow:
|
||||
0 0 8px rgba(157, 114, 255, 0.4),
|
||||
0 0 3px rgba(255, 255, 255, 0.8);
|
||||
mix-blend-mode: screen;
|
||||
will-change: transform, opacity;
|
||||
animation: galaxy-twinkle 6s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
/* Keep decorative animation out of modal backdrops and drawer content. */
|
||||
body:has(.gx-scrim, .gx-underlay, .gx-drawer.open) #galaxy-stars {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-theme="light"] #galaxy-stars {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
[data-theme="light"] #galaxy-stars .galaxy-hero {
|
||||
background: radial-gradient(circle at center, #7849e8 0%, #b38fff 60%, rgba(120, 73, 232, 0) 100%);
|
||||
box-shadow: 0 0 4px rgba(120, 73, 232, 0.45), 0 0 8px rgba(77, 232, 232, 0.3);
|
||||
mix-blend-mode: normal;
|
||||
}
|
||||
|
||||
.gx-unit-note {
|
||||
@@ -1459,6 +1536,9 @@ input[type="color"].gx-color {
|
||||
.gx-fade-enter-active, .gx-fade-leave-active { transition: opacity var(--motion-base); }
|
||||
.gx-fade-enter-from, .gx-fade-leave-to { opacity: 0; }
|
||||
|
||||
/* Keep the underlay (and star suppression) until the drawer finishes closing. */
|
||||
.gx-underlay.gx-fade-leave-active { transition: opacity var(--motion-slow); }
|
||||
|
||||
.gx-slide-enter-active, .gx-slide-leave-active {
|
||||
transition: transform var(--motion-base), opacity var(--motion-base);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
<div id="galaxy-app" v-cloak>
|
||||
<app-shell></app-shell>
|
||||
</div>
|
||||
<div id="galaxy-stars" aria-hidden="true"></div>
|
||||
<!-- Snackbar for messages -->
|
||||
<div id="snackbar_wrapper"></div>
|
||||
<script type="module" src="/assets/mobile/js/app.js"></script>
|
||||
|
||||
@@ -90,17 +90,82 @@ app.mount("#galaxy-app")
|
||||
|
||||
initRouter()
|
||||
|
||||
// Disable card blur during document scrolling.
|
||||
;(() => {
|
||||
const bg = document.getElementById("galaxy-bg")
|
||||
if (!bg) return
|
||||
let timer = null
|
||||
let scrollEnded = true
|
||||
const touches = new Set()
|
||||
const nativeScrollEnd = "onscrollend" in document
|
||||
const body = document.body
|
||||
|
||||
const isModalEvent = (e) => {
|
||||
const t = e.target
|
||||
return t instanceof Element && t.closest(".gx-scrim, .gx-sheet, .gx-dialog, .gx-drawer") !== null
|
||||
}
|
||||
|
||||
const setScrolling = (active) => {
|
||||
if (active) {
|
||||
if (!body.classList.contains("is-scrolling")) body.classList.add("is-scrolling")
|
||||
} else if (!touches.size && body.classList.contains("is-scrolling")) {
|
||||
body.classList.remove("is-scrolling")
|
||||
}
|
||||
}
|
||||
|
||||
const scheduleRestore = () => {
|
||||
clearTimeout(timer)
|
||||
timer = setTimeout(() => setScrolling(false), 120)
|
||||
}
|
||||
|
||||
document.addEventListener("scroll", () => {
|
||||
scrollEnded = false
|
||||
setScrolling(true)
|
||||
if (!nativeScrollEnd) scheduleRestore()
|
||||
}, { passive: true })
|
||||
|
||||
document.addEventListener("scrollend", () => {
|
||||
scrollEnded = true
|
||||
clearTimeout(timer)
|
||||
setScrolling(false)
|
||||
}, { passive: true })
|
||||
|
||||
window.addEventListener("touchstart", (e) => {
|
||||
if (isModalEvent(e)) return
|
||||
for (const touch of e.changedTouches) touches.add(touch.identifier)
|
||||
}, { passive: true })
|
||||
|
||||
const releaseTouches = (e) => {
|
||||
for (const touch of e.changedTouches) touches.delete(touch.identifier)
|
||||
if (scrollEnded) setScrolling(false)
|
||||
else if (!nativeScrollEnd && !touches.size) scheduleRestore()
|
||||
}
|
||||
window.addEventListener("touchend", releaseTouches, { passive: true })
|
||||
window.addEventListener("touchcancel", releaseTouches, { passive: true })
|
||||
|
||||
window.addEventListener("hashchange", () => {
|
||||
scrollEnded = true
|
||||
touches.clear()
|
||||
clearTimeout(timer)
|
||||
body.classList.remove("is-scrolling")
|
||||
}, { passive: true })
|
||||
})()
|
||||
|
||||
// Layer 2 Celestial Starlight Canopy Spawner
|
||||
;(() => {
|
||||
const container = document.getElementById("galaxy-stars") || document.getElementById("galaxy-bg")
|
||||
if (!container) return
|
||||
for (let i = 0; i < 14; i++) {
|
||||
const s = document.createElement("i")
|
||||
s.className = "galaxy-hero"
|
||||
s.style.left = (Math.random() * 100).toFixed(2) + "%"
|
||||
s.style.top = (Math.random() * 100).toFixed(2) + "%"
|
||||
const inTopCanopy = Math.random() < 0.7
|
||||
const topPercent = inTopCanopy ? Math.random() * 28 : Math.random() * 95
|
||||
const leftPercent = inTopCanopy
|
||||
? Math.random() * 96 + 2
|
||||
: Math.random() < 0.5 ? Math.random() * 12 + 2 : Math.random() * 12 + 86
|
||||
s.style.left = leftPercent.toFixed(2) + "%"
|
||||
s.style.top = topPercent.toFixed(2) + "%"
|
||||
s.style.animationDelay = (Math.random() * 4).toFixed(2) + "s"
|
||||
const size = Math.random() > 0.6 ? 3 : 2
|
||||
const size = Math.random() > 0.5 ? 6 : 4
|
||||
s.style.width = s.style.height = size + "px"
|
||||
bg.appendChild(s)
|
||||
container.appendChild(s)
|
||||
}
|
||||
})()
|
||||
|
||||
@@ -538,6 +538,80 @@ def test_ui_mobile_polish_regressions():
|
||||
assert 'tr("Longitudinal Maneuver Mode")' not in c4_developer
|
||||
|
||||
|
||||
@pytest.mark.parametrize("native_scrollend", [False, True])
|
||||
def test_scroll_coordinator_gesture_lifecycle(native_scrollend):
|
||||
node = _node_exe()
|
||||
if node is None:
|
||||
pytest.skip("no node.js runtime available")
|
||||
script = r"""
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const vm = require('node:vm');
|
||||
const native = process.argv[2] === 'true';
|
||||
const source = fs.readFileSync(process.argv[1], 'utf8');
|
||||
const handlers = { document: {}, window: {} };
|
||||
const classes = new Set();
|
||||
const timers = new Map();
|
||||
let nextTimer = 0;
|
||||
class Element {
|
||||
constructor(modal = false) { this.modal = modal; }
|
||||
closest() { return this.modal ? this : null; }
|
||||
}
|
||||
const document = {
|
||||
body: { classList: {
|
||||
contains: k => classes.has(k), add: k => classes.add(k), remove: k => classes.delete(k),
|
||||
} },
|
||||
addEventListener: (name, fn) => { handlers.document[name] = fn; },
|
||||
};
|
||||
if (native) document.onscrollend = null;
|
||||
vm.runInNewContext(source.slice(source.indexOf('// Disable card blur'), source.indexOf('// Layer 2')), {
|
||||
document, Element,
|
||||
window: { addEventListener: (name, fn) => { handlers.window[name] = fn; } },
|
||||
setTimeout: fn => { timers.set(++nextTimer, fn); return nextTimer; },
|
||||
clearTimeout: id => timers.delete(id),
|
||||
});
|
||||
const fire = (scope, name, ids = [], modal = false) => handlers[scope][name]?.({
|
||||
target: new Element(modal), changedTouches: ids.map(identifier => ({ identifier })),
|
||||
});
|
||||
const tick = () => { const pending = [...timers.values()]; timers.clear(); pending.forEach(fn => fn()); };
|
||||
const active = () => classes.has('is-scrolling');
|
||||
fire('window', 'wheel');
|
||||
assert.equal(active(), false, 'wheel without document movement');
|
||||
fire('window', 'touchstart', [1, 2]);
|
||||
fire('document', 'scroll');
|
||||
fire('window', 'touchend', [1]);
|
||||
fire('window', 'touchstart', [3], true);
|
||||
fire('window', 'touchend', [3], true);
|
||||
tick();
|
||||
assert.equal(active(), true, 'remaining page finger holds blur disabled');
|
||||
fire('window', 'touchcancel', [2]);
|
||||
tick();
|
||||
assert.equal(active(), native, 'native completion must be authoritative');
|
||||
fire('document', 'scrollend');
|
||||
assert.equal(active(), false);
|
||||
fire('window', 'touchstart', [4]);
|
||||
fire('document', 'scroll');
|
||||
fire('document', 'scrollend');
|
||||
assert.equal(active(), true, 'hold survives completion');
|
||||
fire('window', 'touchend', [4]);
|
||||
assert.equal(active(), false, 'release after completion cannot leave state stuck');
|
||||
fire('document', 'scroll');
|
||||
fire('window', 'hashchange');
|
||||
tick();
|
||||
assert.equal(active(), false, 'navigation clears pending gesture');
|
||||
fire('document', 'scroll');
|
||||
tick();
|
||||
assert.equal(active(), native, 'fallback only on unsupported browsers');
|
||||
fire('document', 'scrollend');
|
||||
assert.equal(active(), false);
|
||||
"""
|
||||
result = subprocess.run(
|
||||
[node, "-e", script, str(UI_ROOT / "js/app.js"), str(native_scrollend).lower()],
|
||||
capture_output=True, text=True,
|
||||
)
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
|
||||
|
||||
def _node_exe():
|
||||
candidates = [
|
||||
shutil.which("node"),
|
||||
|
||||
Reference in New Issue
Block a user