diff --git a/.github/workflows/consumer-nlos-ci.yml b/.github/workflows/consumer-nlos-ci.yml index 93e17c5b..99c50e19 100644 --- a/.github/workflows/consumer-nlos-ci.yml +++ b/.github/workflows/consumer-nlos-ci.yml @@ -166,6 +166,7 @@ jobs: run: | set -euo pipefail python - <<'PY' + import json import re import struct from pathlib import Path @@ -209,13 +210,36 @@ jobs: "44 by 44 points", "under 1.5 seconds", "under 100 milliseconds", - "zero runtime dependency", + "Outfit", + "JetBrains Mono", + "0288734c3426ca9125ef4cb2e067ef057c09f3ce", + "two pinned font asset packages", "Three.js", "physical iPhone validation pending", ) for phrase in required_adr_contract: assert phrase in adr, f"missing mobile UI contract: {phrase}" + package = json.loads(Path("ui/mobile/package.json").read_text(encoding="utf-8")) + assert package["dependencies"]["@expo-google-fonts/outfit"] == "0.4.3" + assert package["dependencies"]["@expo-google-fonts/jetbrains-mono"] == "0.4.1" + assert package["dependencies"]["expo-font"] == "~55.0.8" + typography = Path("ui/mobile/src/theme/typography.ts").read_text(encoding="utf-8") + assert "Outfit_700Bold" in typography + assert "Outfit_400Regular" in typography + assert "JetBrainsMono_500Medium" in typography + native_fonts = ( + "Outfit-Regular.ttf", + "Outfit-Medium.ttf", + "Outfit-Bold.ttf", + "JetBrainsMono-Regular.ttf", + "JetBrainsMono-Medium.ttf", + ) + info_plist = Path("ui/ios-nlos/App/Info.plist").read_text(encoding="utf-8") + for font in native_fonts: + assert Path("ui/ios-nlos/App/Fonts", font).is_file(), f"missing native font: {font}" + assert font in info_plist, f"native font not registered: {font}" + flow = flow_path.read_text(encoding="utf-8") required_flow_contract = ( "appId:", diff --git a/docs/adr/ADR-342-cognitum-inspired-mobile-instrument-ui.md b/docs/adr/ADR-342-cognitum-inspired-mobile-instrument-ui.md index fc1382fe..4d1d558e 100644 --- a/docs/adr/ADR-342-cognitum-inspired-mobile-instrument-ui.md +++ b/docs/adr/ADR-342-cognitum-inspired-mobile-instrument-ui.md @@ -19,22 +19,23 @@ most important state can consequently compete with setup copy, controls, and technical detail on a narrow phone viewport. The requested direction is similar to the visual language of Cognitum Explain -Studio: a near-black technical canvas, fine grid, restrained cyan and green -accents, compact monospaced labels, thin luminous outlines, and an orbital or -radar motif. This decision adopts that visual language as design provenance, -not as a product or code dependency. No Cognitum name, logo, wordmark, copy, -source code, private asset, authentication behavior, or investor-deck branding -is reused. RuView keeps its own name, logo, evidence vocabulary, information +Studio and the public `cognitum-one/website`: a near-black technical canvas, +soft blue-black cards, restrained cyan and green accents, Outfit display and +body typography, JetBrains Mono instrumentation, medium-weight headings, and +12 to 16 pixel radii. This decision adopts that visual language as explicit +design provenance. The authoritative token review is pinned to website commit +`0288734c3426ca9125ef4cb2e067ef057c09f3ce`, specifically `src/index.css`, +`tailwind.config.ts`, and `creative-brief.md`. No Cognitum name, logo, wordmark, +copy, private asset, authentication behavior, or investor-deck branding is +reused. RuView keeps its own name, logo, evidence vocabulary, information architecture, and privacy boundary. -The authenticated Studio route was not available to the automated cloud review -session. The initial contract is therefore derived from the product owner's -direction and the visual characteristics observable on the available hosted -Cognitum Explain reference surface. Pixel equivalence with the authenticated -Studio route is neither claimed nor required. A maintainer with authorized -Studio access can later provide an approved reference screenshot and request a -bounded visual refinement without changing the epistemic or privacy rules in -this decision. +The authenticated Studio route was not required for this refinement because +the public website source defines the typography, colors, radii, borders, and +surface treatment. Pixel equivalence with Studio is neither claimed nor +required. A maintainer with authorized Studio access can later provide an +approved reference screenshot and request a bounded visual refinement without +changing the epistemic or privacy rules in this decision. This is a UI-only decision. It must not change sensing, reconstruction, filtering, transport, credential storage, permissions, diagnostic contents, @@ -131,11 +132,15 @@ UNVERIFIED` only when an authenticated live attempt is active. It resolves to 100 milliseconds over at least 30 repetitions on the same device. 3. Continuous decorative animation must not be required for state recognition. It pauses when the app or page is inactive and honors reduced motion. -4. The redesign adds no network request, remote font, remote image, analytics - client, sensor subscription, or background timer. -5. Screenshot rendering is deterministic: fixed viewport, fixed fixtures, +4. The redesign adds no runtime network request, remote font, remote image, + analytics client, sensor subscription, or background timer. Outfit and + JetBrains Mono are bundled locally from two pinned font asset packages. +5. The production web export contains only four selected Outfit weights and + two selected JetBrains Mono weights. Their combined uncompressed font asset + size is 456 KB. The native target bundles five TTF files totaling 400 KB. +6. Screenshot rendering is deterministic: fixed viewport, fixed fixtures, reduced motion, local assets, and no dependency on a live endpoint. -6. Point-cloud geometry is bounded to 288 schematic relay points plus 96 +7. Point-cloud geometry is bounded to 288 schematic relay points plus 96 target-return points for each of at most 16 validated tracks, or 1,824 total points. Browser device pixel ratio is capped at 1.5. GPU resources, animation frames, resize observers, and pointer listeners are released on unmount. @@ -359,11 +364,12 @@ and 15 percent respectively. | Create a cross-platform generated token and component package now | 5 | 5 | 1 | 4 | 3 | 3.90 | Deferred because generation infrastructure is disproportionate to one screen and would expand the UI-only diff materially | | Keep the current generic card layout | 2 | 4 | 5 | 4 | 4 | 3.65 | Rejected because the evidence state remains visually subordinate and does not meet the requested design outcome | -The selected approach is expected to add roughly two small platform-specific -presentation implementations and one shared behavioral contract, while adding -zero runtime dependency. The cost is duplicated token maintenance. The control -is a screenshot review plus identical state names and accessibility assertions -on both platforms. +The selected approach adds two small platform-specific presentation +implementations, one shared behavioral contract, and two pinned font asset +packages. The 456 KB web and 400 KB native uncompressed font costs replace +runtime font requests with deterministic local loading. The remaining cost is +duplicated token maintenance. The control is a screenshot review plus identical +state names and accessibility assertions on both platforms. ### Point-cloud renderer alternatives @@ -371,7 +377,7 @@ The same weights apply to the narrower renderer choice. | Alternative | Clarity 25% | Epistemic safety 25% | Delivery cost 15% | Native accessibility 20% | Maintainability 15% | Weighted score | Decision | |---|---:|---:|---:|---:|---:|---:|---| -| Three.js web plus SwiftUI Canvas native | 5 | 5 | 4 | 4 | 4 | 4.50 | Selected because it satisfies the requested Three.js view while preserving platform-native accessibility and adding zero runtime dependency | +| Three.js web plus SwiftUI Canvas native | 5 | 5 | 4 | 4 | 4 | 4.50 | Selected because it satisfies the requested Three.js view while preserving platform-native accessibility and adding no renderer dependency | | Three.js inside a native WebView | 5 | 4 | 3 | 2 | 4 | 3.70 | Rejected because focus, Dynamic Type, startup, memory, and lifecycle behavior are weaker | | Add `expo-gl` and React Three Fiber for native Expo | 5 | 4 | 2 | 3 | 3 | 3.60 | Rejected because new native dependencies and build risk are disproportionate to a UI-only change | | Static SVG or Canvas cloud on every platform | 3 | 5 | 5 | 4 | 5 | 4.30 | Rejected as the sole implementation because it does not provide the requested interactive Three.js web experience | @@ -401,9 +407,9 @@ The same weights apply to the narrower renderer choice. ## Security and privacy -1. The redesign adds no permission, entitlement, endpoint, network request, - storage key, cookie, analytics event, remote asset, background mode, or data - retention path. +1. The redesign adds no permission, entitlement, endpoint, runtime network + request, storage key, cookie, analytics event, remote asset, background mode, + or data retention path. Locally bundled font files are the only new assets. 2. Existing credential rules remain unchanged: the Expo client holds an ephemeral credential in memory, while the approved native client uses its existing Keychain boundary. UI code cannot log, render, snapshot, export, or @@ -493,7 +499,7 @@ claim. A threshold without a completed physical run remains `TARGET`. | Requirement | Automated evidence | Human or physical evidence | Pass condition | |---|---|---|---| | UI-only scope | Pull-request diff allowlist and service or model tests | Reviewer checks no sensing, transport, permissions, persistence, or retention diff | Zero out-of-scope behavior changes | -| Cognitum-inspired, RuView-owned language | Four screenshot baselines and token review | Product reviewer compares hierarchy and general visual language | Requested characteristics present; zero Cognitum branding or private assets | +| Cognitum-inspired, RuView-owned language | Four screenshot baselines, Outfit and JetBrains Mono token assertions, pinned source review, and local font asset contract | Product reviewer compares hierarchy and general visual language | Requested characteristics present; zero Cognitum branding or private assets; no runtime font request | | Five honest states | Unit tests plus `nlos-evidence-state` Playwright and Maestro selectors | Reviewer verifies wording and fail-closed hierarchy | All five exact labels are representable; unknown never becomes verified | | Synthetic safety | Unit and E2E assertions for `nlos-synthetic-watermark` | Screenshot review of synthetic baseline | Label and watermark both visible | | LiDAR point-cloud UI | Deterministic generator tests, exact point budgets, Three.js canvas readiness, reduced-motion capture, and zero-return fail-closed assertion | Browser and native visual review plus physical iPhone GPU check | WebGL renders 288 schematic relay points plus 96 returns per gated track; unverified states render zero target returns; native equivalent is labeled as a projection | @@ -558,6 +564,9 @@ full iPhone validation. 7. Run Swift package tests and the Xcode 26 unsigned simulator build. Confirm zero changes to core packages, sensing, transport, credentials, permissions, diagnostic schema, or retention. +8. Assert the production web export contains only four Outfit and two JetBrains + Mono files totaling no more than 500 KB uncompressed. Confirm the native + target registers its five local TTF files and makes no font network request. ### Physical iPhone gate diff --git a/docs/screenshots/consumer-nlos-mobile-ui/README.md b/docs/screenshots/consumer-nlos-mobile-ui/README.md index 2462dc9e..f1a6ab74 100644 --- a/docs/screenshots/consumer-nlos-mobile-ui/README.md +++ b/docs/screenshots/consumer-nlos-mobile-ui/README.md @@ -7,6 +7,12 @@ watermark requirements, opens the deterministic Three.js LiDAR point cloud, verifies the reconstruction boundary, target lock, and confidence HUD, and writes the PNG files in this directory. +The visual system is pinned to `cognitum-one/website` commit +`0288734c3426ca9125ef4cb2e067ef057c09f3ce`. Outfit supplies display and body +roles, while JetBrains Mono supplies instrumentation and metric roles. Both +font families are bundled locally. Screenshot generation makes no runtime font +request. + The captures contain only the disconnected state, governed setup copy, and the built in synthetic fixture. The point cloud is generated locally from gated track hypotheses and schematic relay geometry. It is not a raw sensor point diff --git a/docs/screenshots/consumer-nlos-mobile-ui/overview-390x844.png b/docs/screenshots/consumer-nlos-mobile-ui/overview-390x844.png index 4c7f5dfe..9df65f3e 100644 Binary files a/docs/screenshots/consumer-nlos-mobile-ui/overview-390x844.png and b/docs/screenshots/consumer-nlos-mobile-ui/overview-390x844.png differ diff --git a/docs/screenshots/consumer-nlos-mobile-ui/point-cloud-390x844.png b/docs/screenshots/consumer-nlos-mobile-ui/point-cloud-390x844.png index 46175f87..5126f346 100644 Binary files a/docs/screenshots/consumer-nlos-mobile-ui/point-cloud-390x844.png and b/docs/screenshots/consumer-nlos-mobile-ui/point-cloud-390x844.png differ diff --git a/docs/screenshots/consumer-nlos-mobile-ui/setup-390x844.png b/docs/screenshots/consumer-nlos-mobile-ui/setup-390x844.png index 9136c1e1..67102549 100644 Binary files a/docs/screenshots/consumer-nlos-mobile-ui/setup-390x844.png and b/docs/screenshots/consumer-nlos-mobile-ui/setup-390x844.png differ diff --git a/docs/screenshots/consumer-nlos-mobile-ui/synthetic-390x844.png b/docs/screenshots/consumer-nlos-mobile-ui/synthetic-390x844.png index 62d192bd..7851d2f1 100644 Binary files a/docs/screenshots/consumer-nlos-mobile-ui/synthetic-390x844.png and b/docs/screenshots/consumer-nlos-mobile-ui/synthetic-390x844.png differ diff --git a/ui/ios-nlos/App/ContentView.swift b/ui/ios-nlos/App/ContentView.swift index e9664afa..2c2b4086 100644 --- a/ui/ios-nlos/App/ContentView.swift +++ b/ui/ios-nlos/App/ContentView.swift @@ -52,7 +52,7 @@ struct ContentView: View { HStack(alignment: .center, spacing: 12) { VStack(alignment: .leading, spacing: 7) { Text("RUVIEW / MOBILE FIELD LAB") - .font(.caption.bold().monospaced()) + .font(RuViewTypography.mono(11)) .tracking(1.6) .foregroundStyle(RuViewTheme.muted) HStack(spacing: 7) { @@ -61,7 +61,7 @@ struct ContentView: View { .frame(width: 8, height: 8) .shadow(color: evidenceState.color.opacity(0.8), radius: 5) Text(evidenceState.label) - .font(.caption.bold().monospaced()) + .font(RuViewTypography.mono(11)) .foregroundStyle(evidenceState.color) .lineLimit(2) } @@ -85,13 +85,13 @@ struct ContentView: View { ) ) } - .font(.system(size: heroFontSize, weight: .black, design: .rounded)) + .font(RuViewTypography.outfit(heroFontSize, weight: .bold)) .tracking(-1.4) .minimumScaleFactor(0.72) .accessibilityElement(children: .combine) Text("Review authenticated evidence, sensor provenance, and privacy controls in one fail closed instrument.") - .font(.subheadline) + .font(RuViewTypography.outfit(16)) .foregroundStyle(RuViewTheme.textSecondary) .fixedSize(horizontal: false, vertical: true) @@ -112,11 +112,11 @@ struct ContentView: View { Image(systemName: "lock.shield.fill") .foregroundStyle(RuViewTheme.green) Text("PRIVACY GUARD ACTIVE") - .font(.caption.bold().monospaced()) + .font(RuViewTypography.mono(11)) .tracking(0.7) Spacer() Text("FAIL CLOSED") - .font(.caption2.bold().monospaced()) + .font(RuViewTypography.mono(10)) .foregroundStyle(RuViewTheme.muted) } .padding(.horizontal, 12) @@ -144,10 +144,10 @@ struct ContentView: View { VStack(alignment: .leading, spacing: 4) { Text(evidenceState.label) - .font(.headline.bold().monospaced()) + .font(RuViewTypography.mono(16)) .foregroundStyle(evidenceState.color) Text(evidenceState.detail) - .font(.caption) + .font(RuViewTypography.outfit(12)) .foregroundStyle(RuViewTheme.textSecondary) .fixedSize(horizontal: false, vertical: true) } @@ -156,7 +156,7 @@ struct ContentView: View { .accessibilityElement(children: .combine) Text(model.statusMessage) - .font(.subheadline) + .font(RuViewTypography.outfit(14)) .foregroundStyle(.white) .fixedSize(horizontal: false, vertical: true) .padding(12) @@ -199,7 +199,7 @@ struct ContentView: View { ? "The point cloud is a deterministic rendering of gated reconstruction tracks and relay geometry. It is not raw iPhone LiDAR output." : "Only validated, fresh tracks are shown. Uncertainty rings represent the reported position covariance." ) - .font(.caption) + .font(RuViewTypography.outfit(12)) .foregroundStyle(RuViewTheme.textSecondary) .fixedSize(horizontal: false, vertical: true) @@ -210,7 +210,7 @@ struct ContentView: View { if let watermark = model.frame?.watermark { Text(watermark) - .font(.system(size: 35, weight: .black, design: .rounded)) + .font(RuViewTypography.outfit(35, weight: .bold)) .tracking(2) .foregroundStyle(RuViewTheme.orange.opacity(0.48)) .rotationEffect(.degrees(-18)) @@ -222,7 +222,7 @@ struct ContentView: View { Image(systemName: "scope") .font(.title2) Text("NO DISPLAYABLE TRACKS") - .font(.caption.bold().monospaced()) + .font(RuViewTypography.mono(11)) } .foregroundStyle(RuViewTheme.muted) .padding(.horizontal, 14) @@ -539,11 +539,11 @@ struct ContentView: View { private func headerMetric(eyebrow: String, value: String, color: Color) -> some View { VStack(alignment: .leading, spacing: 5) { Text(eyebrow) - .font(.caption2.bold().monospaced()) + .font(RuViewTypography.mono(10)) .tracking(0.8) .foregroundStyle(RuViewTheme.muted) Text(value) - .font(.caption.bold().monospaced()) + .font(RuViewTypography.mono(11)) .foregroundStyle(color) .lineLimit(1) .minimumScaleFactor(0.7) @@ -569,7 +569,7 @@ struct ContentView: View { ) VStack(alignment: .leading, spacing: 4) { Text(track.trackId) - .font(.subheadline.bold().monospaced()) + .font(RuViewTypography.mono(14)) .lineLimit(1) Text(String( format: "x %.2f y %.2f z %.2f m", @@ -577,13 +577,13 @@ struct ContentView: View { track.positionM.y, track.positionM.z )) - .font(.caption.monospacedDigit()) + .font(RuViewTypography.mono(11, medium: false)) .foregroundStyle(RuViewTheme.textSecondary) } Spacer(minLength: 4) VStack(alignment: .trailing, spacing: 4) { Text("\(Int(track.confidence * 100))%") - .font(.headline.monospacedDigit()) + .font(RuViewTypography.mono(17)) badge( track.state.rawValue.uppercased(), color: track.state == .degraded ? RuViewTheme.orange : RuViewTheme.cyan @@ -606,13 +606,13 @@ struct ContentView: View { VStack(spacing: 0) { HStack(spacing: 12) { Text(title) - .font(.subheadline) + .font(RuViewTypography.outfit(14)) Spacer() Label( availability.rawValue.capitalized, systemImage: availability == .available ? "checkmark.circle.fill" : "xmark.circle.fill" ) - .font(.caption.bold().monospaced()) + .font(RuViewTypography.mono(11)) .foregroundStyle(availability == .available ? RuViewTheme.green : RuViewTheme.muted) } .padding(.horizontal, 12) @@ -631,12 +631,12 @@ struct ContentView: View { VStack(spacing: 0) { HStack(alignment: .firstTextBaseline, spacing: 12) { Text(label) - .font(.caption2.bold().monospaced()) + .font(RuViewTypography.mono(10)) .tracking(0.6) .foregroundStyle(RuViewTheme.muted) Spacer() Text(value) - .font(.caption.monospaced()) + .font(RuViewTypography.mono(11, medium: false)) .foregroundStyle(.white) .multilineTextAlignment(.trailing) } @@ -660,7 +660,7 @@ struct ContentView: View { Image(systemName: icon) .foregroundStyle(color) } - .font(.caption) + .font(RuViewTypography.outfit(12)) .foregroundStyle(RuViewTheme.textSecondary) .padding(12) .frame(maxWidth: .infinity, alignment: .leading) @@ -675,14 +675,14 @@ struct ContentView: View { HStack(spacing: 5) { Circle().fill(color).frame(width: 6, height: 6) Text(label) - .font(.caption2.bold().monospaced()) + .font(RuViewTypography.mono(10)) .foregroundStyle(RuViewTheme.textSecondary) } } private func badge(_ text: String, color: Color) -> some View { Text(text) - .font(.caption2.bold().monospaced()) + .font(RuViewTypography.mono(10)) .lineLimit(1) .minimumScaleFactor(0.72) .padding(.horizontal, 8) @@ -707,23 +707,23 @@ struct ContentView: View { .frame(width: 22, height: 2) .shadow(color: accent.opacity(0.8), radius: 4) Text(eyebrow) - .font(.caption2.bold().monospaced()) + .font(RuViewTypography.mono(10)) .tracking(1.1) .foregroundStyle(accent) } Text(title) - .font(.title3.bold()) + .font(RuViewTypography.outfit(20, weight: .medium)) .foregroundStyle(.white) content() } .padding(16) .frame(maxWidth: .infinity, alignment: .leading) - .background(RuViewTheme.panel, in: RoundedRectangle(cornerRadius: 20)) + .background(RuViewTheme.panel, in: RoundedRectangle(cornerRadius: 16)) .overlay { - RoundedRectangle(cornerRadius: 20) + RoundedRectangle(cornerRadius: 16) .stroke(RuViewTheme.border, lineWidth: 1) } - .shadow(color: accent.opacity(0.06), radius: 18, y: 8) + .shadow(color: Color.black.opacity(0.5), radius: 18, y: 8) } } @@ -934,19 +934,19 @@ private struct EvidencePresentation { } private enum RuViewTheme { - static let background = Color(red: 0.020, green: 0.035, blue: 0.051) - static let panel = Color(red: 0.036, green: 0.061, blue: 0.078) - static let panelStrong = Color(red: 0.049, green: 0.082, blue: 0.102) - static let surface = Color(red: 0.050, green: 0.082, blue: 0.102) - static let cyan = Color(red: 0.129, green: 0.831, blue: 0.906) - static let green = Color(red: 0.361, green: 1.000, blue: 0.561) + static let background = Color(red: 0.043, green: 0.055, blue: 0.075) + static let panel = Color(red: 0.078, green: 0.094, blue: 0.122) + static let panelStrong = Color(red: 0.094, green: 0.114, blue: 0.145) + static let surface = Color(red: 0.113, green: 0.129, blue: 0.169) + static let cyan = Color(red: 0.098, green: 0.831, blue: 0.902) + static let green = Color(red: 0.149, green: 0.851, blue: 0.408) static let orange = Color(red: 1.000, green: 0.612, blue: 0.231) static let yellow = Color(red: 1.000, green: 0.827, blue: 0.318) static let red = Color(red: 1.000, green: 0.353, blue: 0.384) static let indigo = Color(red: 0.490, green: 0.584, blue: 1.000) - static let muted = Color(red: 0.494, green: 0.596, blue: 0.651) - static let textSecondary = Color(red: 0.665, green: 0.733, blue: 0.769) - static let border = cyan.opacity(0.18) + static let muted = Color(red: 0.482, green: 0.537, blue: 0.616) + static let textSecondary = Color(red: 0.482, green: 0.537, blue: 0.616) + static let border = Color(red: 0.153, green: 0.173, blue: 0.208) } private struct InstrumentGrid: View { diff --git a/ui/ios-nlos/App/DesignSystem.swift b/ui/ios-nlos/App/DesignSystem.swift new file mode 100644 index 00000000..07dede4a --- /dev/null +++ b/ui/ios-nlos/App/DesignSystem.swift @@ -0,0 +1,26 @@ +import SwiftUI + +enum RuViewTypography { + enum Weight { + case regular + case medium + case bold + } + + static func outfit(_ size: CGFloat, weight: Weight = .regular) -> Font { + let name: String + switch weight { + case .regular: + name = "Outfit-Regular" + case .medium: + name = "Outfit-Medium" + case .bold: + name = "Outfit-Bold" + } + return .custom(name, size: size) + } + + static func mono(_ size: CGFloat, medium: Bool = true) -> Font { + .custom(medium ? "JetBrainsMono-Medium" : "JetBrainsMono-Regular", size: size) + } +} diff --git a/ui/ios-nlos/App/Fonts/JetBrainsMono-Medium.ttf b/ui/ios-nlos/App/Fonts/JetBrainsMono-Medium.ttf new file mode 100644 index 00000000..ad71d92b Binary files /dev/null and b/ui/ios-nlos/App/Fonts/JetBrainsMono-Medium.ttf differ diff --git a/ui/ios-nlos/App/Fonts/JetBrainsMono-Regular.ttf b/ui/ios-nlos/App/Fonts/JetBrainsMono-Regular.ttf new file mode 100644 index 00000000..436c982f Binary files /dev/null and b/ui/ios-nlos/App/Fonts/JetBrainsMono-Regular.ttf differ diff --git a/ui/ios-nlos/App/Fonts/OFL-JetBrainsMono.txt b/ui/ios-nlos/App/Fonts/OFL-JetBrainsMono.txt new file mode 100644 index 00000000..821a3dac --- /dev/null +++ b/ui/ios-nlos/App/Fonts/OFL-JetBrainsMono.txt @@ -0,0 +1,93 @@ +Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. + +This license is copied below, and is also available with a FAQ at: https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/ui/ios-nlos/App/Fonts/OFL-Outfit.txt b/ui/ios-nlos/App/Fonts/OFL-Outfit.txt new file mode 100644 index 00000000..723cd447 --- /dev/null +++ b/ui/ios-nlos/App/Fonts/OFL-Outfit.txt @@ -0,0 +1,93 @@ +Copyright 2021 The Outfit Project Authors (https://github.com/Outfitio/Outfit-Fonts) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/ui/ios-nlos/App/Fonts/Outfit-Bold.ttf b/ui/ios-nlos/App/Fonts/Outfit-Bold.ttf new file mode 100644 index 00000000..f9f2f72a Binary files /dev/null and b/ui/ios-nlos/App/Fonts/Outfit-Bold.ttf differ diff --git a/ui/ios-nlos/App/Fonts/Outfit-Medium.ttf b/ui/ios-nlos/App/Fonts/Outfit-Medium.ttf new file mode 100644 index 00000000..4bbf1776 Binary files /dev/null and b/ui/ios-nlos/App/Fonts/Outfit-Medium.ttf differ diff --git a/ui/ios-nlos/App/Fonts/Outfit-Regular.ttf b/ui/ios-nlos/App/Fonts/Outfit-Regular.ttf new file mode 100644 index 00000000..3939ab24 Binary files /dev/null and b/ui/ios-nlos/App/Fonts/Outfit-Regular.ttf differ diff --git a/ui/ios-nlos/App/Info.plist b/ui/ios-nlos/App/Info.plist index 339dadd5..1e6f7696 100644 --- a/ui/ios-nlos/App/Info.plist +++ b/ui/ios-nlos/App/Info.plist @@ -22,6 +22,14 @@ 1 LSRequiresIPhoneOS + UIAppFonts + + Outfit-Regular.ttf + Outfit-Medium.ttf + Outfit-Bold.ttf + JetBrainsMono-Regular.ttf + JetBrainsMono-Medium.ttf + NSLocalNetworkUsageDescription RuView connects to an explicitly configured, authenticated NLOS processing server on your network. NSCameraUsageDescription diff --git a/ui/ios-nlos/App/TrackCanvas.swift b/ui/ios-nlos/App/TrackCanvas.swift index 19016995..023c0c39 100644 --- a/ui/ios-nlos/App/TrackCanvas.swift +++ b/ui/ios-nlos/App/TrackCanvas.swift @@ -133,7 +133,7 @@ struct TrackCanvas: View { ) context.draw( Text("\(String(track.trackId.prefix(12)).uppercased()) \(Int((track.confidence * 100).rounded()))%") - .font(.caption2.bold().monospaced()) + .font(RuViewTypography.mono(9)) .foregroundColor(color), at: CGPoint( x: min(size.width - 62, centerX + radiusX + 34), @@ -144,19 +144,19 @@ struct TrackCanvas: View { context.draw( Text("LIDAR POINT CLOUD / RECONSTRUCTION") - .font(.caption2.bold().monospaced()) + .font(RuViewTypography.mono(9)) .foregroundColor(cyan), at: CGPoint(x: size.width / 2, y: 18) ) context.draw( Text("NOT RAW IPHONE LIDAR") - .font(.caption2.bold().monospaced()) + .font(RuViewTypography.mono(9)) .foregroundColor(orange.opacity(0.82)), at: CGPoint(x: size.width / 2, y: 34) ) context.draw( Text("\(tracks.count * 72) GATED TARGET RETURNS") - .font(.caption2.bold().monospaced()) + .font(RuViewTypography.mono(9)) .foregroundColor(Color.white.opacity(0.64)), at: CGPoint(x: size.width / 2, y: size.height - 16) ) @@ -206,7 +206,7 @@ struct TrackCanvas: View { ) context.draw( Text(String(track.trackId.prefix(12)).uppercased()) - .font(.caption2.bold().monospaced()) + .font(RuViewTypography.mono(9)) .foregroundColor(.white), at: labelPoint ) @@ -269,7 +269,7 @@ struct TrackCanvas: View { context.draw( Text("RELAY ORIGIN") - .font(.caption2.bold().monospaced()) + .font(RuViewTypography.mono(9)) .foregroundColor(Color.white.opacity(0.5)), at: CGPoint(x: center.x, y: center.y + 17) ) diff --git a/ui/ios-nlos/RuViewNLOS.xcodeproj/project.pbxproj b/ui/ios-nlos/RuViewNLOS.xcodeproj/project.pbxproj index ec3c6680..18aba576 100644 --- a/ui/ios-nlos/RuViewNLOS.xcodeproj/project.pbxproj +++ b/ui/ios-nlos/RuViewNLOS.xcodeproj/project.pbxproj @@ -14,6 +14,12 @@ A00000000000000000000005 /* RuViewNLOSCore in Frameworks */ = {isa = PBXBuildFile; productRef = J00000000000000000000001 /* RuViewNLOSCore */; }; A00000000000000000000006 /* RuViewNLOSApple in Frameworks */ = {isa = PBXBuildFile; productRef = J00000000000000000000002 /* RuViewNLOSApple */; }; A00000000000000000000007 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = B00000000000000000000007 /* PrivacyInfo.xcprivacy */; }; + A00000000000000000000008 /* DesignSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B00000000000000000000008 /* DesignSystem.swift */; }; + A00000000000000000000009 /* Outfit-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B00000000000000000000009 /* Outfit-Regular.ttf */; }; + A0000000000000000000000A /* Outfit-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B0000000000000000000000A /* Outfit-Medium.ttf */; }; + A0000000000000000000000B /* Outfit-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B0000000000000000000000B /* Outfit-Bold.ttf */; }; + A0000000000000000000000C /* JetBrainsMono-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B0000000000000000000000C /* JetBrainsMono-Regular.ttf */; }; + A0000000000000000000000D /* JetBrainsMono-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B0000000000000000000000D /* JetBrainsMono-Medium.ttf */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -24,6 +30,12 @@ B00000000000000000000005 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B00000000000000000000006 /* RuViewNLOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RuViewNLOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; B00000000000000000000007 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + B00000000000000000000008 /* DesignSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesignSystem.swift; sourceTree = ""; }; + B00000000000000000000009 /* Outfit-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Outfit-Regular.ttf"; sourceTree = ""; }; + B0000000000000000000000A /* Outfit-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Outfit-Medium.ttf"; sourceTree = ""; }; + B0000000000000000000000B /* Outfit-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Outfit-Bold.ttf"; sourceTree = ""; }; + B0000000000000000000000C /* JetBrainsMono-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "JetBrainsMono-Regular.ttf"; sourceTree = ""; }; + B0000000000000000000000D /* JetBrainsMono-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "JetBrainsMono-Medium.ttf"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -54,6 +66,8 @@ B00000000000000000000002 /* AppModel.swift */, B00000000000000000000003 /* ContentView.swift */, B00000000000000000000004 /* TrackCanvas.swift */, + B00000000000000000000008 /* DesignSystem.swift */, + C00000000000000000000003 /* Fonts */, B00000000000000000000005 /* Info.plist */, B00000000000000000000007 /* PrivacyInfo.xcprivacy */, ); @@ -68,6 +82,18 @@ name = Products; sourceTree = ""; }; + C00000000000000000000003 /* Fonts */ = { + isa = PBXGroup; + children = ( + B00000000000000000000009 /* Outfit-Regular.ttf */, + B0000000000000000000000A /* Outfit-Medium.ttf */, + B0000000000000000000000B /* Outfit-Bold.ttf */, + B0000000000000000000000C /* JetBrainsMono-Regular.ttf */, + B0000000000000000000000D /* JetBrainsMono-Medium.ttf */, + ); + path = Fonts; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -134,6 +160,11 @@ buildActionMask = 2147483647; files = ( A00000000000000000000007 /* PrivacyInfo.xcprivacy in Resources */, + A00000000000000000000009 /* Outfit-Regular.ttf in Resources */, + A0000000000000000000000A /* Outfit-Medium.ttf in Resources */, + A0000000000000000000000B /* Outfit-Bold.ttf in Resources */, + A0000000000000000000000C /* JetBrainsMono-Regular.ttf in Resources */, + A0000000000000000000000D /* JetBrainsMono-Medium.ttf in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -148,6 +179,7 @@ A00000000000000000000002 /* AppModel.swift in Sources */, A00000000000000000000003 /* ContentView.swift in Sources */, A00000000000000000000004 /* TrackCanvas.swift in Sources */, + A00000000000000000000008 /* DesignSystem.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ui/mobile/App.tsx b/ui/mobile/App.tsx index ff6fa811..7d75316c 100644 --- a/ui/mobile/App.tsx +++ b/ui/mobile/App.tsx @@ -1,4 +1,12 @@ import { useEffect } from 'react'; +import { View } from 'react-native'; +import { useFonts } from 'expo-font'; +import { Outfit_400Regular } from '@expo-google-fonts/outfit/400Regular'; +import { Outfit_500Medium } from '@expo-google-fonts/outfit/500Medium'; +import { Outfit_600SemiBold } from '@expo-google-fonts/outfit/600SemiBold'; +import { Outfit_700Bold } from '@expo-google-fonts/outfit/700Bold'; +import { JetBrainsMono_400Regular } from '@expo-google-fonts/jetbrains-mono/400Regular'; +import { JetBrainsMono_500Medium } from '@expo-google-fonts/jetbrains-mono/500Medium'; import { NavigationContainer, DarkTheme } from '@react-navigation/native'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { StatusBar } from 'expo-status-bar'; @@ -12,6 +20,14 @@ import { useSettingsStore } from './src/stores/settingsStore'; import { RootNavigator } from './src/navigation/RootNavigator'; export default function App() { + const [fontsLoaded, fontError] = useFonts({ + Outfit_400Regular, + Outfit_500Medium, + Outfit_600SemiBold, + Outfit_700Bold, + JetBrainsMono_400Regular, + JetBrainsMono_500Medium, + }); const serverUrl = useSettingsStore((state) => state.serverUrl); const rssiScanEnabled = useSettingsStore((state) => state.rssiScanEnabled); @@ -47,15 +63,19 @@ export default function App() { (globalThis as { __appStartTime?: number }).__appStartTime = Date.now(); }, []); + if (!fontsLoaded && !fontError) { + return ; + } + const navigationTheme = { ...DarkTheme, colors: { ...DarkTheme.colors, - background: '#0A0E1A', - card: '#0D1117', - text: '#E2E8F0', - border: '#1E293B', - primary: '#32B8C6', + background: '#0B0E13', + card: '#14181F', + text: '#E7EBEF', + border: '#272C35', + primary: '#19D4E6', }, }; diff --git a/ui/mobile/app.json b/ui/mobile/app.json index cd6091a6..37a71650 100644 --- a/ui/mobile/app.json +++ b/ui/mobile/app.json @@ -5,11 +5,11 @@ "version": "1.0.0", "orientation": "portrait", "icon": "./assets/icon.png", - "userInterfaceStyle": "light", + "userInterfaceStyle": "dark", "splash": { "image": "./assets/splash-icon.png", "resizeMode": "contain", - "backgroundColor": "#ffffff" + "backgroundColor": "#0B0E13" }, "ios": { "supportsTablet": true diff --git a/ui/mobile/package-lock.json b/ui/mobile/package-lock.json index 672dabda..03238a18 100644 --- a/ui/mobile/package-lock.json +++ b/ui/mobile/package-lock.json @@ -8,6 +8,8 @@ "name": "mobile", "version": "1.0.0", "dependencies": { + "@expo-google-fonts/jetbrains-mono": "0.4.1", + "@expo-google-fonts/outfit": "0.4.3", "@expo/vector-icons": "^15.0.2", "@react-native-async-storage/async-storage": "2.2.0", "@react-navigation/bottom-tabs": "^7.15.10", @@ -15,6 +17,7 @@ "@types/three": "^0.183.1", "axios": "^1.15.2", "expo": "~55.0.4", + "expo-font": "~55.0.8", "expo-status-bar": "~55.0.6", "react": "19.2.0", "react-dom": "19.2.0", @@ -1694,6 +1697,18 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, + "node_modules/@expo-google-fonts/jetbrains-mono": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@expo-google-fonts/jetbrains-mono/-/jetbrains-mono-0.4.1.tgz", + "integrity": "sha512-CslACrtMOcRwoWXCO7OMEI+9w3fukWSoBtvNz46OqPoogEuuoY0tkDY1O8sFumk8t0pC6Cx0Xr95O0TOQhpkug==", + "license": "MIT AND OFL-1.1" + }, + "node_modules/@expo-google-fonts/outfit": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@expo-google-fonts/outfit/-/outfit-0.4.3.tgz", + "integrity": "sha512-2uQmDVJencWLllxds6SG92E+SjxyZfvg7eZKZ5XLHggmm5AuUyQK7lzMAFOUzT6kheq2kJ7BAiubMdjKT32fJg==", + "license": "MIT AND OFL-1.1" + }, "node_modules/@expo/cli": { "version": "55.0.35", "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-55.0.35.tgz", diff --git a/ui/mobile/package.json b/ui/mobile/package.json index 4893c991..54abb17d 100644 --- a/ui/mobile/package.json +++ b/ui/mobile/package.json @@ -15,6 +15,8 @@ "e2e:web": "playwright test --config playwright.config.ts" }, "dependencies": { + "@expo-google-fonts/jetbrains-mono": "0.4.1", + "@expo-google-fonts/outfit": "0.4.3", "@expo/vector-icons": "^15.0.2", "@react-native-async-storage/async-storage": "2.2.0", "@react-navigation/bottom-tabs": "^7.15.10", @@ -22,6 +24,7 @@ "@types/three": "^0.183.1", "axios": "^1.15.2", "expo": "~55.0.4", + "expo-font": "~55.0.8", "expo-status-bar": "~55.0.6", "react": "19.2.0", "react-dom": "19.2.0", diff --git a/ui/mobile/src/__tests__/screens/NLOSScreen.test.tsx b/ui/mobile/src/__tests__/screens/NLOSScreen.test.tsx index 2b6ff1ef..440d7e4d 100644 --- a/ui/mobile/src/__tests__/screens/NLOSScreen.test.tsx +++ b/ui/mobile/src/__tests__/screens/NLOSScreen.test.tsx @@ -4,6 +4,7 @@ import { fireEvent, render, screen } from '@testing-library/react-native'; import { createSyntheticNlosFrame } from '@/services/nlos.service'; import { createLiveNlosFrameFixture } from '@/testUtils/nlosFixtures'; import { ThemeProvider } from '@/theme/ThemeContext'; +import { typography } from '@/theme/typography'; import { getBetaPlatformGuidance, NLOS_EXPLAINER_URL, @@ -73,6 +74,12 @@ describe('NLOSScreen', () => { mockNlosResult.connectLive.mockClear(); }); + it('uses the pinned Cognitum typography roles', () => { + expect(typography.displayLg.fontFamily).toBe('Outfit_700Bold'); + expect(typography.bodyMd.fontFamily).toBe('Outfit_400Regular'); + expect(typography.mono.fontFamily).toBe('JetBrainsMono_500Medium'); + }); + it('renders the RuView NLOS screen and iPhone API boundary', () => { const { NLOSScreen } = require('@/screens/NLOSScreen'); render(); diff --git a/ui/mobile/src/components/GaugeArc.tsx b/ui/mobile/src/components/GaugeArc.tsx index 08050dea..21d04266 100644 --- a/ui/mobile/src/components/GaugeArc.tsx +++ b/ui/mobile/src/components/GaugeArc.tsx @@ -87,7 +87,7 @@ export const GaugeArc = ({ value, min = 0, max, label, unit, color, colorTo, siz y={size / 2 - 8} fill="#E2E8F0" fontSize={Math.round(size * 0.16)} - fontFamily="Courier New" + fontFamily="JetBrainsMono_500Medium" fontWeight="700" textAnchor="middle" > @@ -98,7 +98,7 @@ export const GaugeArc = ({ value, min = 0, max, label, unit, color, colorTo, siz y={size / 2 + 18} fill="#94A3B8" fontSize={Math.round(size * 0.085)} - fontFamily="Courier New" + fontFamily="JetBrainsMono_500Medium" textAnchor="middle" letterSpacing="0.6" > diff --git a/ui/mobile/src/components/InstrumentPanel.tsx b/ui/mobile/src/components/InstrumentPanel.tsx index 05d1e019..230466e5 100644 --- a/ui/mobile/src/components/InstrumentPanel.tsx +++ b/ui/mobile/src/components/InstrumentPanel.tsx @@ -3,21 +3,21 @@ import { StyleSheet, View, type StyleProp, type ViewStyle } from 'react-native'; import { ThemedText } from './ThemedText'; export const instrumentColors = { - background: '#05090D', - panel: '#091218', - panelRaised: '#0C171E', - cyan: '#24D3E5', - green: '#58F28B', - cyanDim: 'rgba(36, 211, 229, 0.38)', - greenDim: 'rgba(88, 242, 139, 0.36)', - border: 'rgba(65, 204, 219, 0.22)', - borderStrong: 'rgba(65, 204, 219, 0.42)', - grid: 'rgba(92, 151, 162, 0.065)', - text: '#F3F8FA', - textSecondary: '#91A4AE', + background: '#0B0E13', + panel: '#14181F', + panelRaised: '#181D25', + cyan: '#19D4E6', + green: '#26D968', + cyanDim: 'rgba(25, 212, 230, 0.34)', + greenDim: 'rgba(38, 217, 104, 0.32)', + border: 'rgba(39, 44, 53, 0.92)', + borderStrong: 'rgba(25, 212, 230, 0.30)', + grid: 'rgba(25, 212, 230, 0.045)', + text: '#E7EBEF', + textSecondary: '#7B899D', warning: '#FFB65C', danger: '#FF6478', - dimOverlay: 'rgba(5, 9, 13, 0.78)', + dimOverlay: 'rgba(11, 14, 19, 0.82)', } as const; interface InstrumentPanelProps { @@ -58,16 +58,15 @@ export const InstrumentPanel = ({ accessibilityLabel, }: InstrumentPanelProps) => ( + - - {(eyebrow || accessory) && ( {eyebrow ? ( - {eyebrow} + {eyebrow} ) : } {accessory} @@ -100,14 +99,23 @@ const styles = StyleSheet.create({ backgroundColor: instrumentColors.panel, borderColor: instrumentColors.border, borderWidth: 1, - borderRadius: 18, + borderRadius: 16, padding: 16, gap: 12, - shadowColor: instrumentColors.cyan, + shadowColor: '#020306', shadowOffset: { width: 0, height: 10 }, - shadowOpacity: 0.07, - shadowRadius: 24, - elevation: 2, + shadowOpacity: 0.72, + shadowRadius: 20, + elevation: 3, + }, + ambientGlow: { + position: 'absolute', + top: -76, + right: -54, + width: 190, + height: 190, + borderRadius: 95, + backgroundColor: 'rgba(25, 212, 230, 0.045)', }, accentRail: { position: 'absolute', @@ -119,24 +127,6 @@ const styles = StyleSheet.create({ }, accentRailCyan: { flex: 2, backgroundColor: instrumentColors.cyan }, accentRailGreen: { flex: 1, backgroundColor: instrumentColors.green }, - corner: { - position: 'absolute', - width: 11, - height: 11, - borderColor: instrumentColors.cyanDim, - }, - cornerTopLeft: { - top: 7, - left: 7, - borderTopWidth: 1, - borderLeftWidth: 1, - }, - cornerBottomRight: { - right: 7, - bottom: 7, - borderRightWidth: 1, - borderBottomWidth: 1, - }, headingRow: { minHeight: 22, flexDirection: 'row', @@ -147,9 +137,9 @@ const styles = StyleSheet.create({ }, eyebrow: { color: instrumentColors.cyan, - fontSize: 11, + fontSize: 12, lineHeight: 16, - letterSpacing: 1.4, + letterSpacing: 1.15, textTransform: 'uppercase', }, }); diff --git a/ui/mobile/src/navigation/MainTabs.tsx b/ui/mobile/src/navigation/MainTabs.tsx index e18f2b12..27397aa2 100644 --- a/ui/mobile/src/navigation/MainTabs.tsx +++ b/ui/mobile/src/navigation/MainTabs.tsx @@ -114,7 +114,7 @@ export const MainTabs = () => { }, tabBarIcon: ({ color, size }) => , tabBarLabelStyle: { - fontFamily: 'Courier New', + fontFamily: 'JetBrainsMono_500Medium', textTransform: 'uppercase', fontSize: 10, }, diff --git a/ui/mobile/src/screens/NLOSScreen/index.tsx b/ui/mobile/src/screens/NLOSScreen/index.tsx index f246f724..49070ca7 100644 --- a/ui/mobile/src/screens/NLOSScreen/index.tsx +++ b/ui/mobile/src/screens/NLOSScreen/index.tsx @@ -542,7 +542,7 @@ const styles = StyleSheet.create({ paddingVertical: spacing.md, color: instrumentColors.text, backgroundColor: instrumentColors.background, - fontFamily: 'Courier New', + fontFamily: 'JetBrainsMono_400Regular', fontSize: 14, }, credentialInputError: { borderColor: instrumentColors.danger }, diff --git a/ui/mobile/src/theme/typography.ts b/ui/mobile/src/theme/typography.ts index 95d65705..fe2cb642 100644 --- a/ui/mobile/src/theme/typography.ts +++ b/ui/mobile/src/theme/typography.ts @@ -1,26 +1,24 @@ -import { Platform } from 'react-native'; - export const typography = { - displayXl: { fontSize: 48, fontWeight: '700', letterSpacing: -1 }, - displayLg: { fontSize: 32, fontWeight: '700', letterSpacing: -0.5 }, - displayMd: { fontSize: 24, fontWeight: '600' }, + displayXl: { fontFamily: 'Outfit_700Bold', fontSize: 48, letterSpacing: -1.2 }, + displayLg: { fontFamily: 'Outfit_700Bold', fontSize: 32, letterSpacing: -0.7 }, + displayMd: { fontFamily: 'Outfit_600SemiBold', fontSize: 24, letterSpacing: -0.3 }, labelLg: { + fontFamily: 'Outfit_600SemiBold', fontSize: 16, - fontWeight: '600', letterSpacing: 0.5, textTransform: 'uppercase', }, labelMd: { + fontFamily: 'Outfit_600SemiBold', fontSize: 12, - fontWeight: '600', letterSpacing: 1, textTransform: 'uppercase', }, - bodyLg: { fontSize: 16, fontWeight: '400' }, - bodyMd: { fontSize: 14, fontWeight: '400' }, - bodySm: { fontSize: 12, fontWeight: '400' }, + bodyLg: { fontFamily: 'Outfit_400Regular', fontSize: 16 }, + bodyMd: { fontFamily: 'Outfit_400Regular', fontSize: 14 }, + bodySm: { fontFamily: 'Outfit_400Regular', fontSize: 12 }, mono: { - fontFamily: Platform.OS === 'ios' ? 'Courier New' : 'monospace', + fontFamily: 'JetBrainsMono_500Medium', fontSize: 13, }, };