From 836816ffbd13c80d260adb46f4d1bd24020635db Mon Sep 17 00:00:00 2001 From: Senrokai Date: Fri, 7 Aug 2026 17:42:14 +0200 Subject: [PATCH] Restyle the HUD as an observatory instrument MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the generic rounded dark cards floating over the scene with a single instrument language: hairline frames cut by accent corner ticks, a viewport frame that reads the whole screen as one panel, and readouts whose figures are what the eye lands on. - styles.css: retune the tokens (deeper void/panel, brighter text/muted so every label clears 4.5:1 even over the brightest part of the skybox) and add two utilities — `hud-brackets` draws the four corner ticks from eight background gradients so no panel pays for them in DOM, `hud-acquire` wipes a panel down on mount as its one authored moment (reduced-motion aware). - Drop Orbitron: every string in this UI is a measurement, an identifier, or a catalog label, so one readout face carries the hierarchy through weight, size, and tracking — and the HUD paints one font request sooner. - Search: reticle mark instead of a magnifier, transparent field inside a framed shell, results as a dense two-column list with a match count, and the previously missing empty state. - Info panel: return rail, name/class header, and a readout table with tabular figures and the unit tinted back off the number. - Star labels: a hairline leader back to the star they name. The offset moved from `translate-*` to a margin because CSS2DRenderer overwrites the inline transform every frame, which silently beat the old classes. - Both top-anchored overlays drop below the search field under `sm` so they no longer stack on top of it on a phone, and focus moved from a removed outline plus ring to real `focus-visible` outlines. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi --- src/app/app.html | 6 ++ .../body-detail-scene.component.ts | 15 +++- .../body-detail/info-panel.component.ts | 54 ++++++++----- .../galaxy-system-scene.component.ts | 4 +- .../galaxy-system/star-label-overlay.ts | 8 +- src/app/features/search/search.component.ts | 50 +++++++----- src/index.html | 2 +- src/styles.css | 77 ++++++++++++++++--- 8 files changed, 161 insertions(+), 55 deletions(-) diff --git a/src/app/app.html b/src/app/app.html index 0ed047e..5e07294 100644 --- a/src/app/app.html +++ b/src/app/app.html @@ -1,2 +1,8 @@ + + diff --git a/src/app/features/body-detail/body-detail-scene.component.ts b/src/app/features/body-detail/body-detail-scene.component.ts index ee2f969..7643ff2 100644 --- a/src/app/features/body-detail/body-detail-scene.component.ts +++ b/src/app/features/body-detail/body-detail-scene.component.ts @@ -47,9 +47,18 @@ const GLOW_SCALE = 2.6; @if (viewModel()) { } @else if (notFound()) { -
-

Couldn't find that body.

- Back to the galaxy +
+

No record

+

That id isn't in the catalog — it may have been renamed or mistyped.

+ + + + + Back to the galaxy +
}
diff --git a/src/app/features/body-detail/info-panel.component.ts b/src/app/features/body-detail/info-panel.component.ts index 1029704..cefdde5 100644 --- a/src/app/features/body-detail/info-panel.component.ts +++ b/src/app/features/body-detail/info-panel.component.ts @@ -19,45 +19,63 @@ const KIND_LABELS: Record = { @Component({ selector: 'app-info-panel', template: ` -
+ +
-

{{ body().name }}

-

{{ kindLabel() }} · {{ body().hostStarName }}

+
+

{{ body().name }}

+

{{ kindLabel() }} · {{ body().hostStarName }}

+
-
+ +
@if (body().radiusKm) { -
Radius
-
{{ body().radiusKm | number: '1.0-1' }} km
+
+
Radius
+
{{ body().radiusKm | number: '1.0-1' }} km
+
} @if (body().massEarth) { -
Mass
-
{{ body().massEarth | number: '1.0-2' }} Earth masses
+
+
Mass
+
{{ body().massEarth | number: '1.0-2' }} Earth masses
+
} @if (body().orbit.semiMajorAxisAu) { -
Semi-major axis
-
{{ body().orbit.semiMajorAxisAu | number: '1.0-4' }} AU
+
+
Semi-major axis
+
{{ body().orbit.semiMajorAxisAu | number: '1.0-4' }} AU
+
} @if (body().orbit.eccentricity !== undefined) { -
Eccentricity
-
{{ body().orbit.eccentricity | number: '1.0-4' }}
+
+
Eccentricity
+
{{ body().orbit.eccentricity | number: '1.0-4' }}
+
} @if (body().orbit.inclinationDeg !== undefined) { -
Inclination
-
{{ body().orbit.inclinationDeg | number: '1.0-2' }}°
+
+
Inclination
+
{{ body().orbit.inclinationDeg | number: '1.0-2' }}°
+
} @if (body().discoveryYear) { -
Discovered
-
{{ body().discoveryYear }}
+
+
Discovered
+
{{ body().discoveryYear }}
+
}
diff --git a/src/app/features/galaxy-system/galaxy-system-scene.component.ts b/src/app/features/galaxy-system/galaxy-system-scene.component.ts index b084b75..dcd73f4 100644 --- a/src/app/features/galaxy-system/galaxy-system-scene.component.ts +++ b/src/app/features/galaxy-system/galaxy-system-scene.component.ts @@ -75,9 +75,9 @@ const STAR_MARKER_RADIUS_AU = 0.2; - - } - +
+

Matches {{ results().length }}

+
    + @for (result of results(); track result.bodyId ?? result.starId) { +
  • + +
  • + } +
+
+ } @else if (hasQuery()) { +

Nothing in the catalog matches “{{ query().trim() }}”

}
` @@ -72,6 +78,10 @@ export class SearchComponent { readonly query = signal(''); private readonly index = signal([]); + /** True once the query is long enough to have been searched — so "no matches" is only ever + * reported about a search that actually ran, never about a half-typed word. */ + readonly hasQuery = computed(() => this.query().trim().length >= MIN_QUERY_LENGTH); + readonly results = computed(() => { const query = this.query().trim().toLowerCase(); if (query.length < MIN_QUERY_LENGTH) { diff --git a/src/index.html b/src/index.html index d70b2e4..fec8bf3 100644 --- a/src/index.html +++ b/src/index.html @@ -8,7 +8,7 @@ - + diff --git a/src/styles.css b/src/styles.css index 1c7f127..2f87771 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1,19 +1,20 @@ /* You can add global styles to this file, and also import other style files */ @import 'tailwindcss'; -/* Design tokens for the sci-fi "HUD" look shared across the app. `--color-*`/`--font-*` names - * automatically become Tailwind utilities (e.g. `--color-accent` -> `bg-accent`/`text-accent`). */ +/* Design tokens for the "observatory instrument" HUD shared across the app. `--color-*`/`--font-*` + * names automatically become Tailwind utilities (e.g. `--color-accent` -> `bg-accent`/`text-accent`). + * + * One family only: every string in this UI is a measurement, an identifier, or a catalog label, so + * the readout face carries the whole hierarchy through weight, size, and tracking. */ @theme { - --font-display: 'Orbitron', 'Segoe UI', sans-serif; - --font-body: 'JetBrains Mono', 'Segoe UI', monospace; + --font-body: 'JetBrains Mono', ui-monospace, 'Cascadia Mono', 'Segoe UI Mono', monospace; - --color-void: #05070d; - --color-panel: #0b121f; + --color-void: #04060c; + --color-panel: #070d16; --color-accent: #4dd7ff; - --color-accent-dim: #1c4f66; - --color-text: #dbe6f3; - --color-muted: #7f93ab; - --color-border: #2a4a5c; + --color-text: #e3ecf7; + --color-muted: #90a6bd; + --color-border: #24455a; } html, @@ -26,6 +27,62 @@ body { font-family: var(--font-body); } +/* The HUD's signature: four corner tick brackets cut into a panel's hairline frame. Drawn as + * eight background gradients so any panel (or the viewport frame) picks them up without the + * eight extra DOM nodes the same mark would otherwise cost. Override `--hud-tick` per element + * (e.g. `[--hud-tick:1.75rem]`) to scale the marks with the surface they frame. */ +@utility hud-brackets { + --hud-tick: 0.625rem; + --hud-mark: color-mix(in oklab, var(--color-accent) 65%, transparent); + background-image: + linear-gradient(var(--hud-mark) 0 0), linear-gradient(var(--hud-mark) 0 0), + linear-gradient(var(--hud-mark) 0 0), linear-gradient(var(--hud-mark) 0 0), + linear-gradient(var(--hud-mark) 0 0), linear-gradient(var(--hud-mark) 0 0), + linear-gradient(var(--hud-mark) 0 0), linear-gradient(var(--hud-mark) 0 0); + background-repeat: no-repeat; + background-size: + var(--hud-tick) 1px, + 1px var(--hud-tick), + var(--hud-tick) 1px, + 1px var(--hud-tick), + var(--hud-tick) 1px, + 1px var(--hud-tick), + var(--hud-tick) 1px, + 1px var(--hud-tick); + background-position: + left top, + left top, + right top, + right top, + left bottom, + left bottom, + right bottom, + right bottom; +} + +/* The one authored moment: a panel doesn't fade in, it acquires — wiping down under an + * exponential ease-out the way a readout fills once an instrument locks onto a target. */ +@utility hud-acquire { + animation: hud-acquire 380ms cubic-bezier(0.16, 1, 0.3, 1) both; +} + +@keyframes hud-acquire { + from { + opacity: 0; + clip-path: inset(0 0 100% 0); + } + to { + opacity: 1; + clip-path: inset(0 0 0 0); + } +} + +@media (prefers-reduced-motion: reduce) { + .hud-acquire { + animation-duration: 1ms; + } +} + /* Star name labels rendered by CSS2DRenderer (see StarLabelOverlay). These live outside * Angular's view encapsulation as plain DOM nodes, so they're styled with Tailwind utility * classes assigned directly in TypeScript rather than a scoped component stylesheet. */