diff --git a/src/assets/main.css b/src/assets/main.css
index 432fcec..0a5bff9 100644
--- a/src/assets/main.css
+++ b/src/assets/main.css
@@ -196,8 +196,12 @@ input, select, textarea {
grid-template-columns: repeat(3, 1fr);
}
+.grid-4 {
+ grid-template-columns: repeat(4, 1fr);
+}
+
@media (max-width: 768px) {
- .grid-2, .grid-3 {
+ .grid-2, .grid-3, .grid-4 {
grid-template-columns: 1fr;
}
}
diff --git a/src/router/index.ts b/src/router/index.ts
index 865a1e7..905216e 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -30,6 +30,12 @@ const router = createRouter({
component: () => import('@/views/ShiftPointsView.vue'),
props: true,
},
+ {
+ path: '/analysis/grip/:id',
+ name: 'grip-analysis',
+ component: () => import('@/views/GripAnalysisView.vue'),
+ props: true,
+ },
{
path: '/tracks',
name: 'tracks',
diff --git a/src/types/index.ts b/src/types/index.ts
index b0b9954..5d3fbc2 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -5,6 +5,7 @@
export type UseCase =
| 'shift_points'
+ | 'car_grip'
| 'racing_line'
| 'braking_analysis'
| 'lap_times'
@@ -161,6 +162,43 @@ export interface ShiftPointAnalysis {
total_frames_analyzed: number
}
+// Car Grip Analysis (use_case = "car_grip")
+export interface GripEnvelopePoint {
+ speed_kmh: number
+ max_lateral_g: number
+ max_accel_g: number
+ max_brake_g: number
+ sample_count: number
+}
+
+export interface GripCirclePoint {
+ longitudinal_g: number
+ lateral_g: number
+}
+
+export interface SteeringResponsePoint {
+ steer: number
+ lateral_g: number
+ speed_kmh: number
+}
+
+export interface GripSummary {
+ max_lateral_g: number
+ max_accel_g: number
+ max_brake_g: number
+ max_combined_g: number
+ peak_lateral_speed_kmh: number
+}
+
+export interface CarGripAnalysis {
+ summary: GripSummary
+ grip_envelope: GripEnvelopePoint[]
+ grip_circle: GripCirclePoint[]
+ steering_response: SteeringResponsePoint[]
+ runs_analyzed: number
+ total_frames_analyzed: number
+}
+
// Track Map Analysis (use_case = "track_mapping")
export interface TrackPoint {
x: number
@@ -237,7 +275,7 @@ export interface BuildTrackModelRequest {
export type TrackMappingAnalysis = TrackMapAnalysis
-export type AnalysisResult = ShiftPointAnalysis | TrackMapAnalysis | NotImplementedResponse
+export type AnalysisResult = ShiftPointAnalysis | CarGripAnalysis | TrackMapAnalysis | NotImplementedResponse
// ── Characteristics (aggregated across sessions) ──
diff --git a/src/utils/analysis.ts b/src/utils/analysis.ts
index 370f80f..9debb18 100644
--- a/src/utils/analysis.ts
+++ b/src/utils/analysis.ts
@@ -1,4 +1,4 @@
-import type { ShiftPointAnalysis, TrackMapAnalysis } from '@/types'
+import type { ShiftPointAnalysis, CarGripAnalysis, TrackMapAnalysis } from '@/types'
/**
* Runtime type guard: checks if an unknown value is a ShiftPointAnalysis.
@@ -8,6 +8,14 @@ export function isShiftPointAnalysis(data: unknown): data is ShiftPointAnalysis
return typeof data === 'object' && data !== null && 'gear_curves' in data
}
+/**
+ * Runtime type guard: checks if an unknown value is a CarGripAnalysis.
+ * Uses the 'grip_envelope' discriminator field.
+ */
+export function isCarGripAnalysis(data: unknown): data is CarGripAnalysis {
+ return typeof data === 'object' && data !== null && 'grip_envelope' in data
+}
+
/**
* Runtime type guard: checks if an unknown value is a TrackMapAnalysis.
* Uses the 'boundaries' discriminator field that the backend includes.
diff --git a/src/views/GripAnalysisView.vue b/src/views/GripAnalysisView.vue
new file mode 100644
index 0000000..6114d25
--- /dev/null
+++ b/src/views/GripAnalysisView.vue
@@ -0,0 +1,300 @@
+
+
+
+ Car Grip Analysis
+