feat(mobile): align RuView with Cognitum typography

This commit is contained in:
rUv
2026-08-23 21:52:21 -04:00
parent 10ec346db1
commit 1a394d916c
29 changed files with 460 additions and 136 deletions

View File

@@ -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:",

View File

@@ -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

View File

@@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View File

@@ -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 {

View File

@@ -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)
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -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.

View File

@@ -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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -22,6 +22,14 @@
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIAppFonts</key>
<array>
<string>Outfit-Regular.ttf</string>
<string>Outfit-Medium.ttf</string>
<string>Outfit-Bold.ttf</string>
<string>JetBrainsMono-Regular.ttf</string>
<string>JetBrainsMono-Medium.ttf</string>
</array>
<key>NSLocalNetworkUsageDescription</key>
<string>RuView connects to an explicitly configured, authenticated NLOS processing server on your network.</string>
<key>NSCameraUsageDescription</key>

View File

@@ -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)
)

View File

@@ -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 = "<group>"; };
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 = "<group>"; };
B00000000000000000000008 /* DesignSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesignSystem.swift; sourceTree = "<group>"; };
B00000000000000000000009 /* Outfit-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Outfit-Regular.ttf"; sourceTree = "<group>"; };
B0000000000000000000000A /* Outfit-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Outfit-Medium.ttf"; sourceTree = "<group>"; };
B0000000000000000000000B /* Outfit-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Outfit-Bold.ttf"; sourceTree = "<group>"; };
B0000000000000000000000C /* JetBrainsMono-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "JetBrainsMono-Regular.ttf"; sourceTree = "<group>"; };
B0000000000000000000000D /* JetBrainsMono-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "JetBrainsMono-Medium.ttf"; sourceTree = "<group>"; };
/* 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 = "<group>";
};
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 = "<group>";
};
/* 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;
};

View File

@@ -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 <View testID="font-load-gate" style={{ flex: 1, backgroundColor: '#0B0E13' }} />;
}
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',
},
};

View File

@@ -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

View File

@@ -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",

View File

@@ -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",

View File

@@ -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(<ThemeProvider><NLOSScreen /></ThemeProvider>);

View File

@@ -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"
>

View File

@@ -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) => (
<View testID={testID} accessibilityLabel={accessibilityLabel} style={[styles.panel, style]}>
<View pointerEvents="none" style={styles.ambientGlow} />
<View pointerEvents="none" style={styles.accentRail}>
<View style={styles.accentRailCyan} />
<View style={styles.accentRailGreen} />
</View>
<View pointerEvents="none" style={[styles.corner, styles.cornerTopLeft]} />
<View pointerEvents="none" style={[styles.corner, styles.cornerBottomRight]} />
{(eyebrow || accessory) && (
<View style={styles.headingRow}>
{eyebrow ? (
<ThemedText preset="mono" style={styles.eyebrow}>{eyebrow}</ThemedText>
<ThemedText preset="labelMd" style={styles.eyebrow}>{eyebrow}</ThemedText>
) : <View />}
{accessory}
</View>
@@ -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',
},
});

View File

@@ -114,7 +114,7 @@ export const MainTabs = () => {
},
tabBarIcon: ({ color, size }) => <Ionicons name={toIconName(route.name)} size={size} color={color} />,
tabBarLabelStyle: {
fontFamily: 'Courier New',
fontFamily: 'JetBrainsMono_500Medium',
textTransform: 'uppercase',
fontSize: 10,
},

View File

@@ -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 },

View File

@@ -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,
},
};