/* You can add global styles to this file, and also import other style files */ @import 'tailwindcss'; /* 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-body: 'JetBrains Mono', ui-monospace, 'Cascadia Mono', 'Segoe UI Mono', monospace; --color-void: #04060c; --color-panel: #070d16; --color-accent: #4dd7ff; --color-text: #e3ecf7; --color-muted: #90a6bd; --color-border: #24455a; } html, body { height: 100%; margin: 0; background: var(--color-void); color: var(--color-text); overflow: hidden; font-family: var(--font-body); } /* The panel surface every HUD element shares: a hairline border over a near-opaque fill. One * definition, because spelling the recipe out per call site is how it forked into /85 and /92 * variants within a single change. Deliberately no backdrop blur: the canvas underneath is * redrawn every frame by the render loop, so a blur here is re-sampled continuously — a * standing GPU cost for an effect a 92%-opaque fill mostly hides anyway. */ @utility hud-surface { border: 1px solid color-mix(in oklab, var(--color-border) 60%, transparent); background-color: color-mix(in oklab, var(--color-panel) 92%, transparent); } /* The two label voices of the single-face type system, named so call sites cannot drift a few * thousandths of an em apart. `type-label` is the working voice — section headings, readout * labels, buttons; `type-eyebrow` is the wider-tracked identity line above a title. */ @utility type-label { font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase; } @utility type-eyebrow { font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase; } /* 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. */ /* `backwards`, not `both`: `both` leaves the final keyframe applied for good, and that keyframe * carries a clip-path — which goes on clipping the panel's own box forever after, so anything * that has to escape it (a dropdown opening upward, a tooltip) is cut away and cannot even be * clicked. `backwards` holds the opening frame before the wipe and lets the element return to * its own styles after it. */ @utility hud-acquire { animation: hud-acquire 380ms cubic-bezier(0.16, 1, 0.3, 1) backwards; } @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; } } /* Darkens the edges of the frame so the map reads as a lit display rather than a flat page. */ .hud-vignette { pointer-events: none; background: radial-gradient(ellipse at center, transparent 45%, color-mix(in srgb, var(--color-void) 72%, transparent) 100%); } /* Star name labels rendered by CSS2DRenderer (see StarLabelOverlay). These live outside * Angular's view encapsulation as plain DOM nodes, so they're styled here rather than through * a scoped component stylesheet. */ .star-label-layer { position: absolute; top: 0; left: 0; } /* Two-line map label: what the thing is called, then what it is. The type line is deliberately * much quieter than the name — it should be readable when looked at and invisible when not, * because on a crowded view it is printed as many times as there are labels. * * Offset from the anchor with a margin, not a translate: `CSS2DRenderer` rewrites the inline * `transform` every frame, and the margin is the one offset it can never overwrite. The * hairline `::before` is the leader line back to the point the label names. */ .map-label { position: relative; margin-left: 0.875rem; display: flex; flex-direction: column; line-height: 1.15; text-shadow: 0 0 6px var(--color-void), 0 0 3px var(--color-void); } /* Geometry assumes the label's left edge sits at the star — StarLabelOverlay sets the * CSS2DObject's center to (0, 0.5) to make that true. Under the default (0.5, 0.5) the * renderer keeps the box centred on the point and this line floats in empty space. */ .map-label::before { content: ''; position: absolute; top: 50%; left: -0.75rem; height: 1px; width: 0.625rem; background: color-mix(in oklab, var(--color-accent) 60%, transparent); } /* The quieter voice: a star outside the system the camera is in, named so its direction can be * read without leaving. Dimmed rather than shrunk — at the same size it still reads as a name, * where a smaller one would read as a footnote to the system rather than a place beyond it. */ .map-label--ghost .map-label-name { /* 65% of the accent over the void is 5.4:1 — a ghost is interactive, so it holds the same * contrast floor as body text rather than the lower one large or decorative text could take. */ color: color-mix(in oklab, var(--color-accent) 65%, transparent); } .map-label--ghost .map-label-kind { /* Quieter than the name it belongs to, and still 4.7:1. */ color: color-mix(in oklab, var(--color-accent) 60%, transparent); } .map-label--ghost::before { background: color-mix(in oklab, var(--color-accent) 30%, transparent); } /* Selectable labels are buttons: the only ones the pointer can reach, since the layer they * live in is transparent to it. They brighten to full strength on approach, which is the whole * hover affordance — a ghost lighting up is the map saying this one is somewhere to go. */ .map-label--select { pointer-events: auto; cursor: pointer; background: none; border: 0; padding: 0; font: inherit; } .map-label--select:hover .map-label-name, .map-label--select:focus-visible .map-label-name { color: var(--color-accent); } .map-label--select:hover::before, .map-label--select:focus-visible::before { background: color-mix(in oklab, var(--color-accent) 60%, transparent); } .map-label--select:focus-visible { outline: 1px solid var(--color-accent); outline-offset: 3px; } /* The mirror: text hangs off the point's left, right-aligned, leader line on its right. */ .map-label--left { margin-left: 0; margin-right: 0.875rem; align-items: flex-end; text-align: right; } .map-label--left::before { left: auto; right: -0.75rem; } .map-label-name { font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-accent); } .map-label-kind { font-size: 8px; letter-spacing: 0.3em; text-transform: uppercase; color: color-mix(in oklab, var(--color-accent) 70%, transparent); } /* The selection mark on a body in the scene: two hairline arcs bracketing the point — the one * mark borrowed from the ARK's control disc, at hairline weight. Sized in pixels because it sits * on the body's projected position and should read the same however far the camera is. Centred * on the point by CSS2DRenderer's default (0.5, 0.5) anchor. */ .map-select { width: 28px; height: 28px; border-radius: 50%; border: 1px solid transparent; border-left-color: var(--color-accent); border-right-color: var(--color-accent); pointer-events: none; }