diff --git a/src/components/ThreeD/PhysicalPlaybackItem.tsx b/src/components/ThreeD/PhysicalPlaybackItem.tsx index 9c3327c..5484e37 100644 --- a/src/components/ThreeD/PhysicalPlaybackItem.tsx +++ b/src/components/ThreeD/PhysicalPlaybackItem.tsx @@ -8,12 +8,13 @@ import { getRenderedItemDimensions } from '../../domain/physicalLayout'; import * as THREE from 'three'; import RealItemModel, { isProductAssetReady } from './RealItemModel'; import { ItemVerificationOverlay } from './RealModelVerification'; +import { CATEGORY_COLORS, INDUSTRIAL_PALETTE } from '../../domain/industrialTheme'; const COLORS = { - B: '#16a34a', - C: '#ea580c', - D: '#7c3aed', - sensorAccent: '#3b82f6', + B: CATEGORY_COLORS.B, + C: CATEGORY_COLORS.C, + D: CATEGORY_COLORS.D, + sensorAccent: INDUSTRIAL_PALETTE.sensorAccent, }; /** Base real-model materials per SKU (Stage 1 §20 — basic, form-revealing). */ diff --git a/src/components/ThreeD/PhysicalPlaybackItemPhysics.tsx b/src/components/ThreeD/PhysicalPlaybackItemPhysics.tsx index a5564f2..a104da5 100644 --- a/src/components/ThreeD/PhysicalPlaybackItemPhysics.tsx +++ b/src/components/ThreeD/PhysicalPlaybackItemPhysics.tsx @@ -5,7 +5,7 @@ * C/D redirection is contact-only against kinematic CAD diverter colliders. * No junction setTranslation / route-specific lateral impulses. */ -import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { memo, useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react'; import * as THREE from 'three'; import { useFrame } from '@react-three/fiber'; import { @@ -62,7 +62,7 @@ function colliderDensity(profile: ReturnType): return profile.massKg / Math.max(volume, 1e-6); } -export const PhysicalPlaybackItemPhysics = memo(function PhysicalPlaybackItemPhysics({ +function PhysicalPlaybackItemPhysicsInner({ caseData, elapsedMs, slotIndex = 0, @@ -134,7 +134,7 @@ export const PhysicalPlaybackItemPhysics = memo(function PhysicalPlaybackItemPhy // eslint-disable-next-line react-hooks/exhaustive-deps }, [caseData.id, profile.productId]); - useEffect(() => { + useLayoutEffect(() => { bodyIdentity.current = `${caseData.id}:${itemId}`; authority.current = isFault ? 'fault_kinematic' : 'preparing'; phaseRef.current = 'preparing'; @@ -148,6 +148,7 @@ export const PhysicalPlaybackItemPhysics = memo(function PhysicalPlaybackItemPhy setSpawned(ready); const body = bodyRef.current; if (body) { + // Imperative spawn pose — never reapplied via React RigidBody props. body.setBodyType(RigidBodyType.KinematicPositionBased, false); body.setLinvel({ x: 0, y: 0, z: 0 }, true); body.setAngvel({ x: 0, y: 0, z: 0 }, true); @@ -350,16 +351,17 @@ export const PhysicalPlaybackItemPhysics = memo(function PhysicalPlaybackItemPhy if (elapsedMs < 0) return null; const density = colliderDensity(profile); - const enabledRotations: [boolean, boolean, boolean] = [ - !profile.lockRotationX, - !profile.lockRotationY, - !profile.lockRotationZ, - ]; + const enabledRotations = useMemo<[boolean, boolean, boolean]>( + () => [!profile.lockRotationX, !profile.lockRotationY, !profile.lockRotationZ], + [profile.lockRotationX, profile.lockRotationY, profile.lockRotationZ], + ); + // Initial pose props are mount-only (component is memoized against elapsedMs + // for non-fault cases). Never pass `type` — imperative setBodyType owns it. + // World transform authority after spawn: Rapier body → R3F object3D sync. return ( {spawned && profile.collider.type === 'cuboid' && ( )} - + ); -}); +} + +/** + * Block playback-tick re-renders for normal (non-fault) products so React + * RigidBody props cannot fight Rapier's mesh sync every frame. + */ +export const PhysicalPlaybackItemPhysics = memo( + PhysicalPlaybackItemPhysicsInner, + (prev, next) => { + if (prev.caseData.id !== next.caseData.id) return false; + if (prev.slotIndex !== next.slotIndex) return false; + if (prev.castShadow !== next.castShadow) return false; + if (prev.verifySku !== next.verifySku) return false; + if (prev.caseData.faultType !== next.caseData.faultType) return false; + if (prev.jitter !== next.jitter) return false; + // Fault kinematics still need elapsedMs; physical products do not. + if (next.caseData.faultType) { + return prev.elapsedMs === next.elapsedMs; + } + return true; + }, +); diff --git a/src/components/ThreeD/SorterDigitalTwinContinuous.tsx b/src/components/ThreeD/SorterDigitalTwinContinuous.tsx index 05a7104..89f73ae 100644 --- a/src/components/ThreeD/SorterDigitalTwinContinuous.tsx +++ b/src/components/ThreeD/SorterDigitalTwinContinuous.tsx @@ -114,12 +114,12 @@ const COLORS = { supports: INDUSTRIAL_PALETTE.plastic, motor: INDUSTRIAL_PALETTE.metalDark, sensorAccent: INDUSTRIAL_PALETTE.sensorAccent, - sensorActive: '#60a5fa', + sensorActive: INDUSTRIAL_PALETTE.sensorActive, gateFrame: INDUSTRIAL_PALETTE.metal, routeB: INDUSTRIAL_PALETTE.routeB, routeC: INDUSTRIAL_PALETTE.routeC, routeD: INDUSTRIAL_PALETTE.routeD, - itemShadow: '#3a4a5a', + itemShadow: INDUSTRIAL_PALETTE.metalDark, }; const ITEM_MATERIALS: Record = { @@ -505,10 +505,13 @@ function ZoneMarker({ position, label, color, active }: {
{label} @@ -553,7 +556,16 @@ function BReceiverBin({ active }: { active: boolean }) { -
B
+
B
); @@ -582,10 +594,13 @@ function RollCage({ position, label, color, active, shadows = false }: { {/* Label */}
{label} @@ -1025,8 +1040,9 @@ function ContinuousScene({ const protoShadows = proto ? (proto && stage0!.shadows) : shadowsEnabled; const protoCamera = proto && stage0!.camera; const shotOverride = protoCamera && stage0!.shot ? shotToPhaseCategory(stage0!.shot) : null; - // Stage 2: premium industrial dark environment is the default look. - const darkBg = proto ? stage0!.darkBackground : true; + // Ozon light presentation environment is the default product look. + // Prototype stage0 may still request a dark background explicitly. + const darkBg = proto ? stage0!.darkBackground : false; const cameraHighlight = shouldHighlightCamera(phase); const showScan = shouldShowScanEffect(phase); @@ -1174,13 +1190,18 @@ function ContinuousScene({ return ( <> - {/* Background: industrial dark product canvas (equipment must dominate) */} - - + {/* Background: Ozon light presentation canvas (equipment stays readable) */} + + {proto ? ( <> - {/* Cinematic rig: very low ambient, strong key, soft fill, cool rim */} - {/* Fill — keeps shadowed side readable */} {stage0!.fill && } - {/* Rim — cheap back light for edge separation */} {stage0!.rim && } ) : ( <> - {/* Stage 2 default: premium industrial rig — readable ambient, key with - PCF shadows, soft fill, cool rim (Stage 0 proven values, brightened - in 2B so brackets/rollers read as volumes from every angle) */} - - + {/* Light Ozon studio: cool ambient, neutral key, soft shadows */} + + - - - {/* low front fill so the +Z face (camera side) never goes black */} - - {/* Procedural studio environment (no external HDRI — offline-safe) */} + + + {shadowsEnabled && ( - - - + + + )} )} - {/* Grid — subdued so equipment remains the visual subject */} + {/* Grid — subtle Ozon blue-gray */} - {/* Floor — dark polished concrete with soft reflections */} + {/* Floor — light cool gray */} diff --git a/src/domain/industrialTheme.ts b/src/domain/industrialTheme.ts index e1802f3..d4d1ad3 100644 --- a/src/domain/industrialTheme.ts +++ b/src/domain/industrialTheme.ts @@ -1,30 +1,55 @@ /** - * Shared industrial visual tokens for the continuous digital twin. - * Keeps both modes looking like projections of one system. + * Shared visual tokens for the continuous digital twin. + * Ozon light presentation environment (runtime materials / lights only). + * CSS mirrors these values in `src/styles.css` (:root). */ +/** Canonical Ozon brand + semantic tokens (source of truth for tests / 3D). */ +export const OZON_TOKENS = { + blue: '#005BFF', + magenta: '#F1117E', + darkSpace: '#001A34', + morning: '#00A2FF', + green: '#00BE6C', + orange: '#FFA800', + white: '#FFFFFF', + page: '#F5F7FA', + surface: '#FFFFFF', + surfaceSoft: '#EEF4FF', + border: '#DCE6F5', + muted: '#5F6F82', +} as const; + export const INDUSTRIAL_PALETTE = { - background: '#e8eef4', - backgroundDark: '#0b1220', - floor: '#d5dde8', - gridCell: '#c5d0de', - gridSection: '#9aabbf', + /** Scene clear / fog — soft Ozon morning surface */ + background: OZON_TOKENS.surfaceSoft, + backgroundDark: OZON_TOKENS.darkSpace, + floor: '#E2EAF5', + gridCell: '#C5D4EA', + gridSection: OZON_TOKENS.blue, belt: '#2f3a48', - beltStripe: '#f1c40f', + beltStripe: OZON_TOKENS.orange, metal: '#7b8796', metalDark: '#4a5563', frame: '#5b6b7c', plastic: '#94a3b8', rubber: '#1f2937', cardboard: '#b68b58', - sensorAccent: '#3b82f6', - routeB: '#16a34a', - routeC: '#ea580c', - routeD: '#7c3aed', - fault: '#ef4444', - warning: '#f59e0b', - lightKey: '#f8fafc', - lightFill: '#d0dae8', + /** Ozon blue — primary technical markers */ + sensorAccent: OZON_TOKENS.blue, + sensorActive: OZON_TOKENS.morning, + /** Success / B */ + routeB: OZON_TOKENS.green, + /** Attention / C */ + routeC: OZON_TOKENS.orange, + /** Active selection / D accent (magenta used selectively) */ + routeD: OZON_TOKENS.magenta, + fault: OZON_TOKENS.magenta, + warning: OZON_TOKENS.orange, + success: OZON_TOKENS.green, + lightKey: OZON_TOKENS.white, + lightFill: OZON_TOKENS.surfaceSoft, + text: OZON_TOKENS.darkSpace, } as const; export const INDUSTRIAL_MATERIALS = { diff --git a/src/domain/productVisualOzon.test.ts b/src/domain/productVisualOzon.test.ts new file mode 100644 index 0000000..23e876d --- /dev/null +++ b/src/domain/productVisualOzon.test.ts @@ -0,0 +1,96 @@ +import { describe, expect, it } from 'vitest'; +import { + SETTLE_DURATION_SEC, + SETTLE_LINEAR_SPEED_MPS, + SETTLE_ANGULAR_SPEED_RAD_S, + DOCUMENTED_CONTACT_PLANE_S, + DOCUMENTED_CLEAR_PLANE_S, +} from './junctionContactPhysics'; +import { BELT_SPEED_MPS } from './productPhysicsProfiles'; +import { PHYSICS_TIMESTEP_SEC } from './physicsTimestep'; +import { + DIVERTER_LEFT_SIGNED_DEG, + DIVERTER_RIGHT_SIGNED_DEG, + categoryToPhysicalRoute, + OPENING_SAFETY_MARGIN_SEC, + rotationDurationSec, +} from './pusherMotion'; +import { INDUSTRIAL_PALETTE, OZON_TOKENS } from './industrialTheme'; +import physicsSrc from '../components/ThreeD/PhysicalPlaybackItemPhysics.tsx?raw'; +import visualSrc from '../components/ThreeD/PhysicalPlaybackItem.tsx?raw'; +import sceneSrc from '../components/ThreeD/SorterDigitalTwinContinuous.tsx?raw'; +import mainSrc from '../main.tsx?raw'; + +describe('product visual / rigid-body sync contracts', () => { + it('keeps a single RigidBody visual hierarchy with mount-only pose + memo', () => { + expect(physicsSrc).toMatch(//); + expect(physicsSrc).toMatch(/name="ProductVisualGroup"/); + // Initial pose is allowed; continuous type control is not (fights Dynamic). + expect(physicsSrc).toMatch(/position=\{spawnPose\.position\}/); + expect(physicsSrc).not.toMatch(/type="kinematicPosition"/); + expect(physicsSrc).toMatch(/setBodyType\(RigidBodyType\.Dynamic/); + expect(physicsSrc).toMatch(/Block playback-tick re-renders/); + expect(physicsSrc).toMatch(/castShadow=\{castShadow && spawned\}/); + }); + + it('visual model uses only local pivot offset (no world position writer)', () => { + expect(visualSrc).toMatch(/pivotOffsetY/); + expect(visualSrc).toMatch(//); + expect(visualSrc).not.toMatch(/position=\{pose\.position\}/); + expect(visualSrc).not.toMatch(/position=\{currentWorld/); + expect(visualSrc).not.toMatch(/mesh\.position\.copy/); + }); + + it('one physics instance path remains (no duplicate PhysicalPlaybackItem mount)', () => { + const physicsMounts = sceneSrc.match(/PhysicalPlaybackItemPhysics/g) ?? []; + const kinematicMounts = sceneSrc.match(/]/g) ?? []; + expect(physicsMounts.length).toBeGreaterThanOrEqual(1); + expect(kinematicMounts.length).toBe(0); + }); +}); + +describe('falling / physics freeze regression', () => { + it('receiver settle thresholds unchanged', () => { + expect(SETTLE_LINEAR_SPEED_MPS).toBe(0.20); + expect(SETTLE_ANGULAR_SPEED_RAD_S).toBe(1.0); + expect(SETTLE_DURATION_SEC).toBe(0.30); + }); + + it('B/C/D routing, belt, timestep, diverter timing unchanged', () => { + expect(categoryToPhysicalRoute('B')).toBe('STRAIGHT'); + expect(categoryToPhysicalRoute('C')).toBe('PHYSICAL_LEFT'); + expect(categoryToPhysicalRoute('D')).toBe('PHYSICAL_RIGHT'); + expect(DIVERTER_LEFT_SIGNED_DEG).toBe(-45); + expect(DIVERTER_RIGHT_SIGNED_DEG).toBe(45); + expect(rotationDurationSec()).toBeCloseTo(0.5, 6); + expect(OPENING_SAFETY_MARGIN_SEC).toBeCloseTo(0.15, 6); + expect(DOCUMENTED_CONTACT_PLANE_S).toBe(1.0538); + expect(DOCUMENTED_CLEAR_PLANE_S).toBe(1.6); + expect(BELT_SPEED_MPS).toBe(1.0); + expect(PHYSICS_TIMESTEP_SEC).toBeCloseTo(1 / 120, 12); + }); +}); + +describe('Ozon light theme tokens', () => { + it('defines centralized Ozon tokens; CSS stylesheet is wired', () => { + expect(OZON_TOKENS.blue).toBe('#005BFF'); + expect(OZON_TOKENS.magenta).toBe('#F1117E'); + expect(OZON_TOKENS.darkSpace).toBe('#001A34'); + expect(OZON_TOKENS.morning).toBe('#00A2FF'); + expect(OZON_TOKENS.green).toBe('#00BE6C'); + expect(OZON_TOKENS.orange).toBe('#FFA800'); + expect(OZON_TOKENS.page).toBe('#F5F7FA'); + expect(OZON_TOKENS.surfaceSoft).toBe('#EEF4FF'); + // Magenta is selective accent — never the page background token. + expect(OZON_TOKENS.page).not.toBe(OZON_TOKENS.magenta); + expect(mainSrc).toMatch(/styles\.css/); + }); + + it('scene default is light Ozon environment', () => { + expect(INDUSTRIAL_PALETTE.background.toUpperCase()).toBe('#EEF4FF'); + expect(INDUSTRIAL_PALETTE.sensorAccent.toUpperCase()).toBe('#005BFF'); + expect(INDUSTRIAL_PALETTE.routeB.toUpperCase()).toBe('#00BE6C'); + expect(sceneSrc).toMatch(/const darkBg = proto \? stage0!\.darkBackground : false/); + expect(sceneSrc).toMatch(/INDUSTRIAL_PALETTE\.background/); + }); +}); diff --git a/src/styles.css b/src/styles.css index 7968c46..66a52b8 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1,25 +1,54 @@ /* ========================================================= - OZON Tech Sorter — Product Demo UI + OZON Tech Sorter — Product Demo UI (light Ozon palette) ========================================================= */ :root { - color-scheme: dark; - --bg: #050910; - --panel: rgba(10, 19, 31, 0.92); - --panel-strong: #0c1725; - --line: #1e3a54; - --muted: #91a4b8; - --text: #e5f2ff; - --cyan: #38bdf8; - --cyan-hover: #7dd3fc; - --green: #4ade80; - --orange: #f59e0b; - --purple: #c084fc; - --red: #fb3d4e; + color-scheme: light; + + /* Brand tokens */ + --ozon-blue: #005BFF; + --ozon-magenta: #F1117E; + --ozon-dark-space: #001A34; + --ozon-morning: #00A2FF; + --ozon-green: #00BE6C; + --ozon-orange: #FFA800; + --ozon-white: #FFFFFF; + --ozon-page: #F5F7FA; + --ozon-surface: #FFFFFF; + --ozon-surface-soft: #EEF4FF; + --ozon-border: #DCE6F5; + --ozon-text: #001A34; + --ozon-muted: #5F6F82; + + /* Semantic */ + --brand-primary: var(--ozon-blue); + --brand-secondary: var(--ozon-magenta); + --brand-light: var(--ozon-morning); + --status-success: var(--ozon-green); + --status-warning: var(--ozon-orange); + --text-primary: var(--ozon-dark-space); + --page-background: var(--ozon-page); + --panel-background: var(--ozon-surface); + --panel-border: var(--ozon-border); + --focus-ring: var(--ozon-blue); + + /* Compat aliases used across the stylesheet */ + --bg: var(--page-background); + --panel: var(--panel-background); + --panel-strong: var(--ozon-surface-soft); + --line: var(--panel-border); + --muted: var(--ozon-muted); + --text: var(--text-primary); + --cyan: var(--brand-primary); + --cyan-hover: var(--brand-light); + --green: var(--status-success); + --orange: var(--status-warning); + --purple: var(--brand-secondary); + --red: var(--brand-secondary); --section-max: 1200px; - --radius: 16px; + --radius: 14px; --tap: 44px; - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: "Manrope", "Segoe UI", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif; } *, @@ -45,9 +74,9 @@ body { margin: 0; min-height: 100vh; background: - radial-gradient(circle at 18% 8%, rgba(56, 189, 248, 0.14), transparent 28%), - radial-gradient(circle at 88% 18%, rgba(192, 132, 252, 0.1), transparent 24%), - linear-gradient(160deg, #03060b 0%, #07111d 52%, #050910 100%); + radial-gradient(circle at 12% 0%, rgba(0, 162, 255, 0.10), transparent 32%), + radial-gradient(circle at 88% 8%, rgba(0, 91, 255, 0.06), transparent 28%), + linear-gradient(180deg, var(--ozon-surface-soft) 0%, var(--page-background) 42%, var(--page-background) 100%); color: var(--text); font-size: 16px; line-height: 1.5; @@ -77,25 +106,26 @@ a { button { min-height: var(--tap); - border: 1px solid #2a4a66; + border: 1px solid var(--panel-border); border-radius: 12px; - background: #0e1b2b; - color: var(--text); + background: var(--panel-background); + color: var(--text-primary); cursor: pointer; font-weight: 700; - transition: transform 160ms ease, border-color 160ms ease, background 160ms ease; + box-shadow: 0 1px 2px rgba(0, 26, 52, 0.06); + transition: transform 160ms ease, border-color 160ms ease, background 160ms ease, box-shadow 160ms ease; } button:hover { - border-color: var(--cyan); - background: #11273b; + border-color: var(--brand-primary); + background: var(--ozon-surface-soft); transform: translateY(-1px); } button:focus-visible, .nav-link:focus-visible, .criteria-link:focus-visible { - outline: 2px solid var(--cyan); + outline: 2px solid var(--focus-ring); outline-offset: 2px; } @@ -121,19 +151,20 @@ button:disabled { .btn-primary { border: none; - background: linear-gradient(135deg, #38bdf8, #0ea5e9); - color: #041018; + background: var(--brand-primary); + color: var(--ozon-white); + box-shadow: 0 2px 8px rgba(0, 91, 255, 0.28); } .btn-primary:hover { - background: linear-gradient(135deg, #7dd3fc, #38bdf8); - color: #041018; + background: var(--brand-light); + color: var(--ozon-white); } .btn-secondary { - background: #0e1b2b; - color: var(--text); - border: 1px solid #2a4a66; + background: var(--panel-background); + color: var(--text-primary); + border: 1px solid var(--panel-border); } .category-B { color: var(--green); } @@ -3686,7 +3717,7 @@ button:disabled { height: 100vh; max-width: 100vw; overflow: hidden; - background: #070d16; + background: var(--page-background); } .main-page--product .product-topbar, @@ -3701,8 +3732,8 @@ button:disabled { height: 68px; max-height: 72px; padding: 0 24px; - border-bottom: 1px solid rgba(56, 189, 248, 0.18); - background: rgba(6, 12, 20, 0.88); + border-bottom: 1px solid var(--panel-border); + background: var(--ozon-white); backdrop-filter: blur(14px); } @@ -3720,9 +3751,9 @@ button:disabled { width: 34px; height: 34px; border-radius: 9px; - border: 1px solid rgba(56, 189, 248, 0.4); - background: rgba(56, 189, 248, 0.12); - color: #7dd3fc; + border: 1px solid var(--panel-border); + background: var(--ozon-surface-soft); + color: var(--brand-primary); font-weight: 800; font-size: 15px; flex: 0 0 auto; @@ -3732,7 +3763,7 @@ button:disabled { font-size: 15px; font-weight: 700; line-height: 1.15; - color: #e8f2ff; + color: var(--text-primary); } .product-brand-sub { @@ -3829,7 +3860,7 @@ button:disabled { display: grid; place-items: center; color: var(--muted); - background: #070d16; + background: var(--page-background); z-index: 2; } @@ -3840,8 +3871,8 @@ button:disabled { height: 78px; max-height: 84px; padding: 0 24px 10px; - border-top: 1px solid rgba(56, 189, 248, 0.18); - background: rgba(6, 12, 20, 0.9); + border-top: 1px solid var(--panel-border); + background: var(--ozon-white); backdrop-filter: blur(14px); } @@ -3895,7 +3926,9 @@ button:disabled { position: static; min-height: 40px; border-radius: 10px; - background: rgba(12, 22, 34, 0.9); + background: var(--ozon-surface-soft); + border: 1px solid var(--panel-border); + color: var(--text-primary); } .product-sim-pill { @@ -3990,3 +4023,89 @@ button:disabled { font-size: 1.05rem; } } + +/* ========================================================= + Ozon light chrome overrides (shell / HUD / nav / controls) + ========================================================= */ + +.main-hud, +.app-nav-overlay, +.app-nav-bar, +.main-progress { + background: rgba(255, 255, 255, 0.92) !important; + border-color: var(--panel-border) !important; + color: var(--text-primary); + box-shadow: 0 4px 18px rgba(0, 26, 52, 0.08); +} + +.play-button, +.main-page--product .play-button { + background: var(--brand-primary) !important; + color: var(--ozon-white) !important; + box-shadow: 0 4px 16px rgba(0, 91, 255, 0.28); +} + +.play-button:hover { + background: var(--brand-light) !important; +} + +.app-nav-link.active { + color: var(--ozon-white) !important; + background: var(--brand-primary) !important; + border-color: var(--brand-primary) !important; +} + +.app-nav-link:hover { + color: var(--brand-primary); + border-color: var(--panel-border); + background: var(--ozon-surface-soft); +} + +.product-topbar, +.docs-product-topbar, +.main-page--product .product-topbar, +.docs-page .product-topbar { + background: var(--ozon-white) !important; + border-bottom: 1px solid var(--panel-border) !important; + color: var(--text-primary) !important; + box-shadow: 0 1px 0 rgba(0, 26, 52, 0.04); +} + +.product-brand-title, +.product-tele-value { + color: var(--text-primary) !important; +} + +.product-brand-sub, +.product-tele-label { + color: var(--ozon-muted) !important; +} + +.main-page--product .app-nav-solid, +.docs-page .product-topbar .app-nav-solid { + background: var(--ozon-surface-soft) !important; + border-color: var(--panel-border) !important; +} + +.product-webgl-note, +.docs-page { + background: var(--page-background) !important; + color: var(--text-primary); +} + +.product-tele-value.category-B { color: var(--status-success) !important; } +.product-tele-value.category-C { color: var(--status-warning) !important; } +.product-tele-value.category-D { color: var(--brand-secondary) !important; } + +.progress-dot { + background: var(--ozon-border); +} + +.main-page--product .product-dock, +.main-page--product .main-controls .auto-camera-toggle, +.main-page--product .presentation-toggle, +.stop-button { + background: var(--ozon-white) !important; + border-color: var(--panel-border) !important; + color: var(--text-primary) !important; +}