优化
This commit is contained in:
+205
-28
@@ -19,6 +19,8 @@ const state = {
|
||||
result: null,
|
||||
progressTimer: null,
|
||||
seatTwin: null,
|
||||
seatActuatorState: {},
|
||||
availableSeatAxes: null,
|
||||
};
|
||||
|
||||
const $ = (selector, root = document) => root.querySelector(selector);
|
||||
@@ -1629,36 +1631,83 @@ function classifyReferenceMaterial(material) {
|
||||
}
|
||||
|
||||
const TWIN_OUTLINE_STYLE = {
|
||||
body: { color: 0x315d69, lineOpacity: 0.52, fillOpacity: 0.014, renderOrder: 30 },
|
||||
glass: { color: 0x78a7b2, lineOpacity: 0.18, fillOpacity: 0.006, renderOrder: 31 },
|
||||
detail: { color: 0x536f76, lineOpacity: 0.28, fillOpacity: 0.009, renderOrder: 32 },
|
||||
wheel: { color: 0x203d45, lineOpacity: 0.68, fillOpacity: 0.02, renderOrder: 33 },
|
||||
interior: { color: 0x607e83, lineOpacity: 0.24, fillOpacity: 0.008, renderOrder: 36 },
|
||||
light: { color: 0x536f76, lineOpacity: 0.28, fillOpacity: 0.009, renderOrder: 32 },
|
||||
body: {
|
||||
color: 0x315d69,
|
||||
lineOpacity: 0.36,
|
||||
occludedColor: 0xa7b8bb,
|
||||
occludedLineOpacity: 0.12,
|
||||
fillOpacity: 0.11,
|
||||
thresholdAngle: 58,
|
||||
renderOrder: 30,
|
||||
},
|
||||
glass: { color: 0x78a7b2, lineOpacity: 0, fillOpacity: 0.045, renderOrder: 31 },
|
||||
detail: { color: 0x536f76, lineOpacity: 0, fillOpacity: 0.028, renderOrder: 32 },
|
||||
wheel: {
|
||||
color: 0x203d45,
|
||||
lineOpacity: 0.52,
|
||||
occludedColor: 0x9eafb2,
|
||||
occludedLineOpacity: 0.14,
|
||||
fillOpacity: 0.18,
|
||||
thresholdAngle: 50,
|
||||
renderOrder: 33,
|
||||
},
|
||||
interior: { color: 0x607e83, lineOpacity: 0, fillOpacity: 0.012, renderOrder: 36 },
|
||||
light: { color: 0x536f76, lineOpacity: 0, fillOpacity: 0.07, renderOrder: 32 },
|
||||
};
|
||||
|
||||
function addTwinMeshOutline(THREE, mesh, style, thresholdAngle = 30) {
|
||||
if (!mesh.geometry?.attributes?.position || mesh.userData.twinOutline) return null;
|
||||
if (
|
||||
!mesh.geometry?.attributes?.position
|
||||
|| mesh.userData.twinOutline
|
||||
|| style.lineOpacity <= 0
|
||||
) return null;
|
||||
const geometry = new THREE.EdgesGeometry(mesh.geometry, thresholdAngle);
|
||||
if (!geometry.getAttribute("position")?.count) {
|
||||
geometry.dispose();
|
||||
return null;
|
||||
}
|
||||
const material = new THREE.LineBasicMaterial({
|
||||
const visibleMaterial = new THREE.LineBasicMaterial({
|
||||
color: style.color,
|
||||
transparent: true,
|
||||
opacity: style.lineOpacity,
|
||||
depthWrite: false,
|
||||
depthTest: true,
|
||||
depthFunc: THREE.LessEqualDepth,
|
||||
toneMapped: false,
|
||||
});
|
||||
const outline = new THREE.LineSegments(geometry, material);
|
||||
outline.name = `${mesh.name || "mesh"}_outline`;
|
||||
outline.renderOrder = style.renderOrder;
|
||||
outline.frustumCulled = mesh.frustumCulled;
|
||||
mesh.add(outline);
|
||||
mesh.userData.twinOutline = outline;
|
||||
return outline;
|
||||
visibleMaterial.onBeforeCompile = (shader) => {
|
||||
shader.vertexShader = shader.vertexShader.replace(
|
||||
"#include <project_vertex>",
|
||||
"#include <project_vertex>\ngl_Position.z -= 0.0006 * gl_Position.w;",
|
||||
);
|
||||
};
|
||||
visibleMaterial.customProgramCacheKey = () => "twin-visible-line-depth-bias-v1";
|
||||
const visibleOutline = new THREE.LineSegments(geometry, visibleMaterial);
|
||||
visibleOutline.name = `${mesh.name || "mesh"}_outline_visible`;
|
||||
visibleOutline.renderOrder = style.renderOrder + 1;
|
||||
visibleOutline.frustumCulled = mesh.frustumCulled;
|
||||
mesh.add(visibleOutline);
|
||||
mesh.userData.twinOutline = visibleOutline;
|
||||
|
||||
const occludedOpacity = style.occludedLineOpacity ?? 0;
|
||||
if (occludedOpacity > 0) {
|
||||
const occludedMaterial = new THREE.LineBasicMaterial({
|
||||
color: style.occludedColor ?? style.color,
|
||||
transparent: true,
|
||||
opacity: occludedOpacity,
|
||||
depthWrite: false,
|
||||
depthTest: true,
|
||||
depthFunc: THREE.GreaterDepth,
|
||||
toneMapped: false,
|
||||
});
|
||||
const occludedOutline = new THREE.LineSegments(geometry, occludedMaterial);
|
||||
occludedOutline.name = `${mesh.name || "mesh"}_outline_occluded`;
|
||||
occludedOutline.renderOrder = style.renderOrder;
|
||||
occludedOutline.frustumCulled = mesh.frustumCulled;
|
||||
mesh.add(occludedOutline);
|
||||
mesh.userData.twinOccludedOutline = occludedOutline;
|
||||
}
|
||||
return visibleOutline;
|
||||
}
|
||||
|
||||
function applyTwinOutlineStyle(THREE, root, options = {}) {
|
||||
@@ -1694,6 +1743,9 @@ function configureTwinSolidEntity(THREE, roots, options = {}) {
|
||||
root.traverse((object) => {
|
||||
if (!object.isMesh || !object.visible) return;
|
||||
if (object.userData.twinOutline) object.userData.twinOutline.visible = false;
|
||||
if (object.userData.twinOccludedOutline) {
|
||||
object.userData.twinOccludedOutline.visible = false;
|
||||
}
|
||||
const materials = Array.isArray(object.material) ? object.material : [object.material];
|
||||
materials.filter(Boolean).forEach((material) => {
|
||||
material.transparent = Boolean(options.transparent);
|
||||
@@ -1792,12 +1844,20 @@ function configureReferenceVehicleModel(THREE, vehicle) {
|
||||
const material = sourceMaterial.clone();
|
||||
const descriptor = referenceMaterialDescriptor(material);
|
||||
const category = classifyReferenceMaterial(material);
|
||||
const outlineStyle = TWIN_OUTLINE_STYLE[category] || TWIN_OUTLINE_STYLE.detail;
|
||||
const key = `${category}:${material.name || "unnamed"}`;
|
||||
report.materials.set(key, (report.materials.get(key) || 0) + 1);
|
||||
if (material.map) {
|
||||
material.map.colorSpace = THREE.SRGBColorSpace;
|
||||
material.map.anisotropy = 8;
|
||||
}
|
||||
[
|
||||
"map",
|
||||
"aoMap",
|
||||
"bumpMap",
|
||||
"normalMap",
|
||||
"roughnessMap",
|
||||
"metalnessMap",
|
||||
"emissiveMap",
|
||||
].forEach((textureKey) => {
|
||||
if (textureKey in material) material[textureKey] = null;
|
||||
});
|
||||
if (material.emissive) {
|
||||
material.emissive.setHex(0x000000);
|
||||
if ("emissiveIntensity" in material) material.emissiveIntensity = 0;
|
||||
@@ -1832,11 +1892,15 @@ function configureReferenceVehicleModel(THREE, vehicle) {
|
||||
} else if (/chrome|metal|alloy|rim/.test(descriptor) && material.color) {
|
||||
material.color.setHex(0xaeb9bb);
|
||||
}
|
||||
const outlineStyle = TWIN_OUTLINE_STYLE[category] || TWIN_OUTLINE_STYLE.detail;
|
||||
material.transparent = true;
|
||||
material.opacity = outlineStyle.fillOpacity;
|
||||
material.depthWrite = false;
|
||||
material.side = THREE.DoubleSide;
|
||||
material.depthWrite = ["body", "detail", "wheel"].includes(category);
|
||||
material.depthTest = true;
|
||||
material.side = category === "glass" ? THREE.DoubleSide : THREE.FrontSide;
|
||||
material.wireframe = false;
|
||||
if ("flatShading" in material) material.flatShading = false;
|
||||
if ("roughness" in material) material.roughness = category === "body" ? 0.72 : 0.82;
|
||||
if ("metalness" in material) material.metalness = category === "wheel" ? 0.48 : 0.08;
|
||||
if (material.emissive) material.emissive.setHex(0x000000);
|
||||
if ("emissiveIntensity" in material) material.emissiveIntensity = 0;
|
||||
material.needsUpdate = true;
|
||||
@@ -1851,7 +1915,12 @@ function configureReferenceVehicleModel(THREE, vehicle) {
|
||||
const outlineCategory = categoryPriority.find((category) => categories.includes(category)) || "detail";
|
||||
const outlineStyle = TWIN_OUTLINE_STYLE[outlineCategory];
|
||||
object.renderOrder = outlineStyle.renderOrder - 1;
|
||||
addTwinMeshOutline(THREE, object, outlineStyle, 30);
|
||||
addTwinMeshOutline(
|
||||
THREE,
|
||||
object,
|
||||
outlineStyle,
|
||||
outlineStyle.thresholdAngle ?? 60,
|
||||
);
|
||||
});
|
||||
return report;
|
||||
}
|
||||
@@ -2048,6 +2117,7 @@ function initSeatTwin() {
|
||||
|
||||
const THREE = window.THREE;
|
||||
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, powerPreference: "high-performance" });
|
||||
renderer.autoClear = false;
|
||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
|
||||
if ("outputColorSpace" in renderer) renderer.outputColorSpace = THREE.SRGBColorSpace;
|
||||
else renderer.outputEncoding = THREE.sRGBEncoding;
|
||||
@@ -2058,8 +2128,11 @@ function initSeatTwin() {
|
||||
scene.background = new THREE.Color(0xe8eeec);
|
||||
scene.fog = new THREE.Fog(0xe8eeec, 12, 19);
|
||||
const camera = new THREE.PerspectiveCamera(34, 1, 0.1, 40);
|
||||
scene.add(new THREE.HemisphereLight(0xffffff, 0x7d8b90, 2.2));
|
||||
const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x7d8b90, 2.2);
|
||||
hemisphereLight.layers.enable(1);
|
||||
scene.add(hemisphereLight);
|
||||
const keyLight = new THREE.DirectionalLight(0xffffff, 3.2);
|
||||
keyLight.layers.enable(1);
|
||||
keyLight.position.set(-4, 7, 6);
|
||||
keyLight.castShadow = true;
|
||||
keyLight.shadow.mapSize.set(1024, 1024);
|
||||
@@ -2069,6 +2142,7 @@ function initSeatTwin() {
|
||||
keyLight.shadow.camera.bottom = -4;
|
||||
scene.add(keyLight);
|
||||
const fillLight = new THREE.DirectionalLight(0x9fc7d7, 1.15);
|
||||
fillLight.layers.enable(1);
|
||||
fillLight.position.set(5, 3, -5);
|
||||
scene.add(fillLight);
|
||||
|
||||
@@ -2148,6 +2222,14 @@ function initSeatTwin() {
|
||||
});
|
||||
scene.add(fixedModels.driver.root);
|
||||
vehicleSystems.root.add(fixedModels.rear);
|
||||
[
|
||||
vehicleSystems.root,
|
||||
models.passenger.root,
|
||||
fixedModels.driver.root,
|
||||
fixedModels.rear,
|
||||
].forEach((root) => {
|
||||
root.traverse((object) => object.layers.set(1));
|
||||
});
|
||||
|
||||
const initialDevice = () => ({
|
||||
ventilation: { state: "off", level: 0 },
|
||||
@@ -2300,6 +2382,7 @@ function initSeatTwin() {
|
||||
const anchorX = (projectedPosition.x * 0.5 + 0.5) * width;
|
||||
const anchorY = (-projectedPosition.y * 0.5 + 0.5) * height;
|
||||
const offset = {
|
||||
longitudinal: { x: -104, y: 50 },
|
||||
backrest_recline: { x: -108, y: -28 },
|
||||
cushion_tilt: { x: 106, y: -4 },
|
||||
legrest_height: { x: 112, y: 52 },
|
||||
@@ -2388,7 +2471,16 @@ function initSeatTwin() {
|
||||
);
|
||||
camera.lookAt(cameraTargetX, 1.14, -0.08);
|
||||
positionTwinCallouts();
|
||||
renderer.clear();
|
||||
camera.layers.set(0);
|
||||
renderer.render(scene, camera);
|
||||
renderer.clearDepth();
|
||||
const sceneBackground = scene.background;
|
||||
scene.background = null;
|
||||
camera.layers.set(1);
|
||||
renderer.render(scene, camera);
|
||||
scene.background = sceneBackground;
|
||||
camera.layers.set(0);
|
||||
if (!canvas.dataset.rendered) canvas.dataset.rendered = "true";
|
||||
window.requestAnimationFrame(animate);
|
||||
}
|
||||
@@ -2461,6 +2553,58 @@ function applyTwinAngleRanges(vehicle) {
|
||||
});
|
||||
}
|
||||
|
||||
function applySeatMotorAvailability(vehicle) {
|
||||
const motors = vehicle?.seat?.motors || {};
|
||||
const availableAxes = new Set(Object.keys(motors));
|
||||
state.availableSeatAxes = availableAxes;
|
||||
Object.keys(TWIN_DEFAULT_POSITIONS).forEach((axis) => {
|
||||
const available = availableAxes.has(axis);
|
||||
$$(`[data-axis="${axis}"], [data-twin-axis="${axis}"], [data-callout="${axis}"]`)
|
||||
.forEach((item) => {
|
||||
item.classList.toggle("seat-axis-unavailable", !available);
|
||||
item.setAttribute("aria-hidden", String(!available));
|
||||
});
|
||||
const motorName = motors[axis]?.name || axis;
|
||||
const twinLabel = axis === "longitudinal"
|
||||
? `${motorName}位置`
|
||||
: ["cushion_tilt", "backrest_recline", "legrest_height"].includes(axis)
|
||||
? `${motorName}夹角`
|
||||
: motorName;
|
||||
$$(`[data-twin-axis="${axis}"] > span, [data-callout="${axis}"] > span`)
|
||||
.forEach((label) => { label.textContent = twinLabel; });
|
||||
});
|
||||
const cushionName = motors.cushion_tilt?.name;
|
||||
const backrestName = motors.backrest_recline?.name;
|
||||
const legrestName = motors.legrest_height?.name;
|
||||
const backrestIncludedLabel = $('[data-callout="backrest_included_angle"] > span');
|
||||
const legrestIncludedLabel = $('[data-callout="legrest_included_angle"] > span');
|
||||
if (backrestIncludedLabel && backrestName && cushionName) {
|
||||
backrestIncludedLabel.textContent = `${backrestName}-${cushionName}夹角`;
|
||||
}
|
||||
if (legrestIncludedLabel && cushionName && legrestName) {
|
||||
legrestIncludedLabel.textContent = `${cushionName}-${legrestName}夹角`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderSeatMotorRows(vehicle) {
|
||||
const motors = Object.entries(vehicle?.seat?.motors || {});
|
||||
$$(".seat-card .motion-state-grid").forEach((grid) => {
|
||||
const fragment = document.createDocumentFragment();
|
||||
motors.forEach(([axis, motor]) => {
|
||||
const item = document.createElement("div");
|
||||
item.className = "motion-axis";
|
||||
item.dataset.axis = axis;
|
||||
const label = document.createElement("span");
|
||||
label.textContent = motor?.name || axis;
|
||||
const value = document.createElement("b");
|
||||
value.textContent = "--";
|
||||
item.append(label, value, document.createElement("i"));
|
||||
fragment.append(item);
|
||||
});
|
||||
grid.replaceChildren(fragment);
|
||||
});
|
||||
}
|
||||
|
||||
function selectVehicle(vehicleId, { silent = false } = {}) {
|
||||
const vehicle = findVehicleProfile(vehicleId);
|
||||
if (!vehicle) return;
|
||||
@@ -2468,6 +2612,8 @@ function selectVehicle(vehicleId, { silent = false } = {}) {
|
||||
const select = $("#vehicleSelect");
|
||||
if (select) select.value = vehicle.id;
|
||||
applyTwinAngleRanges(vehicle);
|
||||
renderSeatMotorRows(vehicle);
|
||||
applySeatMotorAvailability(vehicle);
|
||||
const longitudinal = vehicle.seat?.motors?.longitudinal;
|
||||
const backrest = vehicle.seat?.motors?.backrest_recline;
|
||||
$("#vehicleNote").textContent = longitudinal && backrest
|
||||
@@ -2476,7 +2622,7 @@ function selectVehicle(vehicleId, { silent = false } = {}) {
|
||||
if (state.result?.actuator_state) {
|
||||
renderSeatState(state.result.actuator_state);
|
||||
} else if (state.seatTwin?.actuatorState) {
|
||||
updateSeatTwin(state.seatTwin.actuatorState);
|
||||
renderSeatState(state.seatTwin.actuatorState);
|
||||
}
|
||||
if (!silent) showToast(`已切换车型:${vehicle.name}`);
|
||||
}
|
||||
@@ -2767,10 +2913,41 @@ function resetSeatState() {
|
||||
});
|
||||
}
|
||||
|
||||
function mergeSeatActuatorState(previousState, patchState) {
|
||||
const mergedState = { ...(previousState || {}) };
|
||||
Object.entries(patchState || {}).forEach(([seat, functions]) => {
|
||||
const previousSeat = mergedState[seat] || {};
|
||||
const mergedSeat = { ...previousSeat };
|
||||
Object.entries(functions || {}).forEach(([feature, value]) => {
|
||||
if (feature === "position") {
|
||||
const previousPosition = previousSeat.position || {};
|
||||
const mergedPosition = { ...previousPosition };
|
||||
Object.entries(value || {}).forEach(([axis, motion]) => {
|
||||
mergedPosition[axis] = motion && typeof motion === "object"
|
||||
? { ...(previousPosition[axis] || {}), ...motion }
|
||||
: motion;
|
||||
});
|
||||
mergedSeat.position = mergedPosition;
|
||||
return;
|
||||
}
|
||||
mergedSeat[feature] = value && typeof value === "object" && !Array.isArray(value)
|
||||
? { ...(previousSeat[feature] || {}), ...value }
|
||||
: value;
|
||||
});
|
||||
mergedState[seat] = mergedSeat;
|
||||
});
|
||||
return mergedState;
|
||||
}
|
||||
|
||||
function renderSeatState(actuatorState) {
|
||||
resetSeatState();
|
||||
if (!actuatorState) return;
|
||||
Object.entries(actuatorState).forEach(([seat, functions]) => {
|
||||
const mergedActuatorState = mergeSeatActuatorState(
|
||||
state.seatActuatorState,
|
||||
actuatorState,
|
||||
);
|
||||
state.seatActuatorState = mergedActuatorState;
|
||||
resetSeatState();
|
||||
Object.entries(mergedActuatorState).forEach(([seat, functions]) => {
|
||||
Object.entries(functions).forEach(([feature, value]) => {
|
||||
if (feature === "position") {
|
||||
Object.entries(value).forEach(([axis, motion]) => {
|
||||
@@ -2793,7 +2970,7 @@ function renderSeatState(actuatorState) {
|
||||
$("b", item).textContent = value.state === "on" ? `ON · L${value.level}` : "OFF";
|
||||
});
|
||||
});
|
||||
updateSeatTwin(actuatorState);
|
||||
updateSeatTwin(mergedActuatorState);
|
||||
}
|
||||
|
||||
function renderDomainState(domainState) {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<script src="https://unpkg.com/three@0.147.0/build/three.min.js" defer></script>
|
||||
<script src="https://unpkg.com/three@0.147.0/examples/js/libs/fflate.min.js" defer></script>
|
||||
<script src="https://unpkg.com/three@0.147.0/examples/js/loaders/FBXLoader.js" defer></script>
|
||||
<script src="/app.js?v=unified-cabin-13" defer></script>
|
||||
<script src="/app.js?v=unified-cabin-22" defer></script>
|
||||
<script type="module" src="/gesture-control.js?v=gesture-core-3"></script>
|
||||
</head>
|
||||
<body>
|
||||
@@ -289,7 +289,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="twin-stage" id="seatTwin" data-selected-seat="passenger">
|
||||
<canvas id="seatTwinCanvas" data-vehicle-model="byd-tang-2024-reference" data-seat-layout="five-seat" data-active-seat="front-right" aria-label="透明轮廓整车、固定主驾和后排、动态副驾、全景天幕、仪表台空调出风口与副驾手套箱上方氛围灯三维状态"></canvas>
|
||||
<canvas id="seatTwinCanvas" data-vehicle-model="byd-tang-2024-reference" data-seat-layout="five-seat" data-active-seat="front-right" aria-label="具有前后遮挡明暗层次的半透明整车外壳、始终清晰显示的动态副驾、全景天幕、仪表台空调出风口与副驾手套箱上方氛围灯三维状态"></canvas>
|
||||
<div class="vehicle-system-hud" aria-label="整车孪生系统状态">
|
||||
<span data-twin-system="roof"><i data-lucide="panels-top-left"></i><small>全景天幕</small><b id="twinRoofState">透光 100%</b></span>
|
||||
<span data-twin-system="climate"><i data-lucide="wind"></i><small>仪表台 HVAC</small><b id="twinClimateState">关闭</b></span>
|
||||
@@ -348,13 +348,7 @@
|
||||
<span data-function="ventilation"><i data-lucide="wind"></i>通风 <b>--</b></span>
|
||||
<span data-function="heating"><i data-lucide="flame"></i>加热 <b>--</b></span>
|
||||
</div>
|
||||
<div class="motion-state-grid">
|
||||
<div class="motion-axis" data-axis="longitudinal"><span>水平</span><b>--</b><i></i></div>
|
||||
<div class="motion-axis" data-axis="cushion_tilt"><span>坐垫俯仰</span><b>--</b><i></i></div>
|
||||
<div class="motion-axis" data-axis="backrest_recline"><span>靠背</span><b>--</b><i></i></div>
|
||||
<div class="motion-axis" data-axis="legrest_height"><span>腿托升降</span><b>--</b><i></i></div>
|
||||
<div class="motion-axis" data-axis="legrest_extension"><span>腿托伸缩</span><b>--</b><i></i></div>
|
||||
</div>
|
||||
<div class="motion-state-grid" aria-live="polite"></div>
|
||||
</article>
|
||||
<article class="seat-card" data-seat="passenger">
|
||||
<div class="seat-identity"><i data-lucide="armchair"></i><div><strong>副驾</strong><span>front_right</span></div></div>
|
||||
@@ -362,13 +356,7 @@
|
||||
<span data-function="ventilation"><i data-lucide="wind"></i>通风 <b>--</b></span>
|
||||
<span data-function="heating"><i data-lucide="flame"></i>加热 <b>--</b></span>
|
||||
</div>
|
||||
<div class="motion-state-grid">
|
||||
<div class="motion-axis" data-axis="longitudinal"><span>水平</span><b>--</b><i></i></div>
|
||||
<div class="motion-axis" data-axis="cushion_tilt"><span>坐垫俯仰</span><b>--</b><i></i></div>
|
||||
<div class="motion-axis" data-axis="backrest_recline"><span>靠背</span><b>--</b><i></i></div>
|
||||
<div class="motion-axis" data-axis="legrest_height"><span>腿托升降</span><b>--</b><i></i></div>
|
||||
<div class="motion-axis" data-axis="legrest_extension"><span>腿托伸缩</span><b>--</b><i></i></div>
|
||||
</div>
|
||||
<div class="motion-state-grid" aria-live="polite"></div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3015,11 +3015,14 @@ pre {
|
||||
bottom: 18px;
|
||||
}
|
||||
|
||||
.joint-twin-shell [data-callout="longitudinal"],
|
||||
.joint-twin-shell [data-callout="legrest_extension"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.seat-axis-unavailable {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.joint-twin-shell .twin-callout {
|
||||
min-width: 67px;
|
||||
padding: 5px 6px;
|
||||
|
||||
Reference in New Issue
Block a user