mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
Every scrolling surface carries the kit's .ui-scroll, which draws the thin themed scrollbar from --scroll-thumb and --scroll-track; the Context panel and others drew the platform's light scrollbar because the page declares no dark color-scheme. The per-panel scrollbar rules go; Cyber sets the two tokens and Cockpit's HUD windows keep their accent tint. Selects draw .gev-field's own chevron instead of the platform arrow, the voice selects join .gev-field, number fields keep dark spinners, and the Street Level range uses the shared slider in its green.
416 lines
11 KiB
CSS
416 lines
11 KiB
CSS
/* ── First-run mission launcher ───────────────────────────────────
|
|
Compact and non-modal: it gives a new visitor a meaningful starting
|
|
choice without blocking manual map interaction or spending API quota. */
|
|
#first-run-launcher {
|
|
position: fixed;
|
|
z-index: 175;
|
|
top: 50%;
|
|
left: 50%;
|
|
width: min(34rem, calc(100vw - 2rem));
|
|
/* Five tiles do not fit a short landscape phone (667 x 390), where the card
|
|
would otherwise render past BOTH ends of the viewport. Cap it to the
|
|
viewport and let the mission list scroll inside — the heading, the checkbox
|
|
and the status line must stay reachable at every height. */
|
|
max-height: calc(100vh - 1.5rem);
|
|
max-height: calc(100dvh - 1.5rem);
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 1.15rem;
|
|
overflow: hidden;
|
|
color: var(--text-primary);
|
|
background:
|
|
linear-gradient(145deg, rgba(10, 27, 37, 0.97), rgba(7, 13, 22, 0.95)),
|
|
rgba(7, 17, 25, 0.96);
|
|
border: 1px solid rgba(54, 220, 255, 0.42);
|
|
border-radius: 0.65rem;
|
|
box-shadow:
|
|
0 1.4rem 4.5rem rgba(0, 0, 0, 0.72),
|
|
0 0 2.5rem rgba(0, 212, 255, 0.09),
|
|
inset 0 0 0 1px rgba(255, 255, 255, 0.025);
|
|
backdrop-filter: blur(28px) saturate(1.25);
|
|
-webkit-backdrop-filter: blur(28px) saturate(1.25);
|
|
opacity: 0;
|
|
transform: translate(-50%, calc(-50% + 0.8rem)) scale(0.985);
|
|
transition:
|
|
opacity 220ms ease,
|
|
transform 280ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
/* Inert until revealed AND again while fading out, so a click during the
|
|
dismiss animation cannot launch a second mission behind the fade. */
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* An AUTHOR `display` on this id beats the UA's `[hidden] { display: none }` in
|
|
the cascade, so the flex above would otherwise leave the card in the
|
|
accessibility tree from page parse until it is revealed — or, on a share
|
|
link, until it is removed. Restore the attribute's meaning explicitly. */
|
|
#first-run-launcher[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
#first-run-launcher.visible {
|
|
opacity: 1;
|
|
transform: translate(-50%, -50%) scale(1);
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.first-run-scanline {
|
|
position: absolute;
|
|
inset: 0 0 auto;
|
|
height: 2px;
|
|
background: linear-gradient(
|
|
90deg,
|
|
transparent,
|
|
rgba(54, 220, 255, 0.8),
|
|
transparent
|
|
);
|
|
box-shadow: 0 0 1rem rgba(54, 220, 255, 0.45);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.first-run-header,
|
|
.first-run-footer {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 1rem;
|
|
color: rgba(171, 210, 220, 0.6);
|
|
font-family: var(--font-mono);
|
|
font-size: 0.58rem;
|
|
letter-spacing: 0.1rem;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.first-run-kicker {
|
|
color: rgba(54, 220, 255, 0.86);
|
|
}
|
|
|
|
#first-run-launcher h2 {
|
|
margin-top: 0.6rem;
|
|
font-family: var(--font-mono);
|
|
font-size: clamp(1.25rem, 2.4vw, 1.75rem);
|
|
font-weight: 500;
|
|
letter-spacing: -0.045rem;
|
|
}
|
|
|
|
#first-run-description {
|
|
max-width: 29rem;
|
|
margin-top: 0.3rem;
|
|
color: rgba(215, 231, 235, 0.66);
|
|
font-size: 0.78rem;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.first-run-choices {
|
|
display: grid;
|
|
gap: 0.42rem;
|
|
margin-top: 1rem;
|
|
/* The one part of the card allowed to scroll when the viewport is too short.
|
|
min-height:0 is what actually lets this flex child shrink below its content. */
|
|
min-height: 0;
|
|
overflow-y: auto;
|
|
overscroll-behavior: contain;
|
|
scroll-behavior: smooth;
|
|
/* A visible, non-overlaying affordance that the list continues; the
|
|
scrollbar itself is the kit's .ui-scroll. */
|
|
scrollbar-gutter: stable;
|
|
}
|
|
|
|
.first-run-choices button {
|
|
display: grid;
|
|
grid-template-columns: 1.7rem 1fr 1.3rem;
|
|
align-items: center;
|
|
gap: 0.7rem;
|
|
width: 100%;
|
|
min-height: 3.55rem;
|
|
padding: 0.6rem 0.7rem;
|
|
color: rgba(225, 244, 248, 0.92);
|
|
text-align: left;
|
|
background: rgba(29, 69, 81, 0.18);
|
|
border: 1px solid rgba(100, 200, 220, 0.18);
|
|
border-radius: 0.42rem;
|
|
cursor: pointer;
|
|
transition:
|
|
background var(--transition-fast),
|
|
border-color var(--transition-fast),
|
|
transform var(--transition-fast);
|
|
}
|
|
|
|
.first-run-choices button:hover,
|
|
.first-run-choices button:focus-visible {
|
|
background: rgba(25, 151, 178, 0.16);
|
|
border-color: rgba(54, 220, 255, 0.62);
|
|
outline: none;
|
|
transform: translateX(2px);
|
|
}
|
|
|
|
/* aria-disabled, not :disabled — the JS keeps the buttons focusable while a
|
|
mission runs so the keyboard is never dumped out of the launcher mid-flight. */
|
|
.first-run-choices button[aria-disabled='true'] {
|
|
cursor: wait;
|
|
opacity: 0.48;
|
|
transform: none;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.first-run-choices button > .material-symbols-outlined:first-child {
|
|
color: #55dff5;
|
|
font-size: 1.35rem;
|
|
}
|
|
|
|
.first-run-choices button > span:nth-child(2) {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.15rem;
|
|
}
|
|
|
|
.first-run-choices strong {
|
|
font-family: var(--font-mono);
|
|
font-size: 0.72rem;
|
|
font-weight: 600;
|
|
letter-spacing: 0.08rem;
|
|
}
|
|
|
|
.first-run-choices small {
|
|
color: rgba(198, 220, 226, 0.55);
|
|
font-size: 0.66rem;
|
|
}
|
|
|
|
.first-run-arrow {
|
|
color: rgba(54, 220, 255, 0.4);
|
|
font-size: 1rem;
|
|
transition:
|
|
transform var(--transition-fast),
|
|
color var(--transition-fast);
|
|
}
|
|
|
|
.first-run-choices button:hover .first-run-arrow,
|
|
.first-run-choices button:focus-visible .first-run-arrow {
|
|
color: rgba(54, 220, 255, 0.9);
|
|
transform: translateX(2px);
|
|
}
|
|
|
|
/* Ordered after the shared button rule rather than shouting !important at it. */
|
|
.first-run-choices button.first-run-explore {
|
|
background: rgba(255, 255, 255, 0.025);
|
|
}
|
|
|
|
.first-run-footer {
|
|
margin-top: 0.85rem;
|
|
font-size: 0.48rem;
|
|
letter-spacing: 0.055rem;
|
|
}
|
|
|
|
.first-run-dismiss {
|
|
position: relative;
|
|
padding: 0 0 0 0.75rem;
|
|
border: 0;
|
|
background: none;
|
|
color: inherit;
|
|
font: inherit;
|
|
letter-spacing: inherit;
|
|
text-transform: inherit;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* A 44px touch target drawn outside the layout, so the footer row keeps the
|
|
height of its one line of text; a 44px button box would nearly treble it. */
|
|
.first-run-dismiss::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 50%;
|
|
right: 0;
|
|
left: 0;
|
|
height: 44px;
|
|
transform: translateY(-50%);
|
|
}
|
|
|
|
.first-run-dismiss:hover,
|
|
.first-run-dismiss:focus-visible {
|
|
color: rgba(54, 220, 255, 0.86);
|
|
}
|
|
|
|
.first-run-dismiss-touch {
|
|
display: none;
|
|
}
|
|
|
|
/* No hover and a coarse pointer: a phone or tablet, where ESC means nothing. */
|
|
@media (hover: none) and (pointer: coarse) {
|
|
.first-run-dismiss-key {
|
|
display: none;
|
|
}
|
|
.first-run-dismiss-touch {
|
|
display: inline;
|
|
}
|
|
}
|
|
|
|
.first-run-suppress {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
color: rgba(199, 225, 231, 0.66);
|
|
text-transform: none;
|
|
font-size: 0.62rem;
|
|
letter-spacing: 0.02rem;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.first-run-suppress input {
|
|
width: 0.82rem;
|
|
height: 0.82rem;
|
|
margin: 0;
|
|
accent-color: #36dcff;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.first-run-suppress:hover {
|
|
color: rgba(225, 244, 248, 0.92);
|
|
}
|
|
|
|
/* The live region carries mission progress and failures, so it owns a full row
|
|
and reserves its height — otherwise the card resizes as it speaks. */
|
|
.first-run-note {
|
|
min-height: 1.1rem;
|
|
margin-top: 0.5rem;
|
|
color: rgba(199, 225, 231, 0.62);
|
|
font-family: var(--font-mono);
|
|
font-size: 0.62rem;
|
|
line-height: 1.35;
|
|
}
|
|
|
|
#first-run-launcher[data-state='loading'] .first-run-note {
|
|
color: rgba(54, 220, 255, 0.85);
|
|
}
|
|
|
|
/* Every surface that claims the screen hides the launcher. This list is the CSS
|
|
half of the ESC arbitration: src/firstRunExperience.js watches these same
|
|
classes and makes the launcher step aside, so a hidden card is never left
|
|
holding a keyboard handler. Keep the two lists in step —
|
|
EXCLUSIVE_SURFACE_CLASSES pins them together. */
|
|
body.ui-clean-view #first-run-launcher,
|
|
body.recording-mode #first-run-launcher,
|
|
body.cockpit-mode #first-run-launcher,
|
|
body.scene-playback-mode #first-run-launcher {
|
|
display: none;
|
|
}
|
|
|
|
@media (max-width: 620px) {
|
|
#first-run-launcher {
|
|
top: auto;
|
|
bottom: 5.8rem;
|
|
padding: 0.9rem;
|
|
transform: translate(-50%, 0.8rem) scale(0.985);
|
|
}
|
|
|
|
#first-run-launcher.visible {
|
|
transform: translate(-50%, 0) scale(1);
|
|
}
|
|
/* Five tiles have to clear the dock on a 375 x 812 phone, so the rows
|
|
tighten. The dismiss button stays: it is the only way to close the card
|
|
without choosing a mission on a phone, which has no ESC key. */
|
|
#first-run-launcher h2 {
|
|
margin-top: 0.45rem;
|
|
}
|
|
#first-run-description {
|
|
margin-top: 0.25rem;
|
|
font-size: 0.72rem;
|
|
}
|
|
.first-run-choices {
|
|
margin-top: 0.75rem;
|
|
}
|
|
.first-run-choices button {
|
|
min-height: 3rem;
|
|
padding: 0.45rem 0.6rem;
|
|
}
|
|
.first-run-choices small {
|
|
font-size: 0.62rem;
|
|
}
|
|
}
|
|
|
|
/* Short viewports — landscape phones, small laptop windows. The chrome tightens
|
|
so the mission list keeps a usable share of the height. The intro line STAYS:
|
|
it is the only copy that says what this thing is, and with four tiles it
|
|
fits. (It was dropped here while there were five; the tile that went is
|
|
exactly why it can come back.) */
|
|
@media (max-height: 620px) {
|
|
#first-run-launcher {
|
|
padding: 0.75rem 0.85rem;
|
|
}
|
|
#first-run-launcher h2 {
|
|
margin-top: 0.3rem;
|
|
font-size: 1.1rem;
|
|
}
|
|
#first-run-description {
|
|
margin-top: 0.2rem;
|
|
font-size: 0.7rem;
|
|
line-height: 1.4;
|
|
}
|
|
.first-run-choices {
|
|
margin-top: 0.55rem;
|
|
}
|
|
.first-run-choices button {
|
|
min-height: 2.7rem;
|
|
padding: 0.36rem 0.6rem;
|
|
}
|
|
.first-run-choices strong {
|
|
font-size: 0.68rem;
|
|
}
|
|
.first-run-choices small {
|
|
font-size: 0.6rem;
|
|
}
|
|
.first-run-footer {
|
|
margin-top: 0.5rem;
|
|
}
|
|
.first-run-note {
|
|
margin-top: 0.3rem;
|
|
min-height: 0;
|
|
}
|
|
}
|
|
|
|
/* A soft edge on the list, so a tile cut by the fold reads as "more below"
|
|
rather than as a rendering seam. Gated on `data-scrollable`, which the JS sets
|
|
only when the list ACTUALLY overflows — a fade on a list that fits would
|
|
promise content that is not there, which is the opposite of the affordance
|
|
it is meant to be. Decorative only: the list scrolls under it. */
|
|
.first-run-choices[data-scrollable='true'] {
|
|
-webkit-mask-image: linear-gradient(
|
|
to bottom,
|
|
#000 calc(100% - 1.4rem),
|
|
transparent 100%
|
|
);
|
|
mask-image: linear-gradient(
|
|
to bottom,
|
|
#000 calc(100% - 1.4rem),
|
|
transparent 100%
|
|
);
|
|
}
|
|
|
|
/* Scrolled to the end, there is nothing left to promise, so the fade lifts.
|
|
Browsers without scroll-driven animations keep the static fade, which is
|
|
still honest — there IS more list, it is just above rather than below. */
|
|
@supports (animation-timeline: scroll(self block)) {
|
|
.first-run-choices[data-scrollable='true'] {
|
|
animation: first-run-scroll-fade linear both;
|
|
animation-timeline: scroll(self block);
|
|
animation-range: contain 80% contain 100%;
|
|
}
|
|
}
|
|
|
|
@keyframes first-run-scroll-fade {
|
|
to {
|
|
-webkit-mask-image: linear-gradient(to bottom, #000 100%, transparent 100%);
|
|
mask-image: linear-gradient(to bottom, #000 100%, transparent 100%);
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
#first-run-launcher,
|
|
.first-run-choices button,
|
|
.first-run-arrow {
|
|
transition: none;
|
|
}
|
|
/* Scrolling a tile into view must not animate either. */
|
|
.first-run-choices {
|
|
scroll-behavior: auto;
|
|
}
|
|
}
|