feat: Implement mobile-first responsive architecture, bottom sheets, and touch ergonomics
This commit is contained in:
parent
03bc547769
commit
81ff22e502
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* FeatureLens — Visual Feedback & AI Task Staging Overlay
|
* FeatureLens — Visual Feedback & AI Task Staging Overlay
|
||||||
* Zero-dependency Web Component with Shadow DOM Isolation
|
* Zero-dependency Web Component with Shadow DOM Isolation & Mobile Touch Ergonomics
|
||||||
*/
|
*/
|
||||||
class FeatureLensElement extends HTMLElement {
|
class FeatureLensElement extends HTMLElement {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -23,7 +23,6 @@ class FeatureLensElement extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setupGlobalEvents() {
|
setupGlobalEvents() {
|
||||||
// Listen for custom open event
|
|
||||||
window.addEventListener('featurelens:toggle', () => this.toggleDrawer());
|
window.addEventListener('featurelens:toggle', () => this.toggleDrawer());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,7 +35,7 @@ class FeatureLensElement extends HTMLElement {
|
|||||||
this.renderNotesList();
|
this.renderNotesList();
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('[FeatureLens] Could not fetch notes from backend, using memory cache:', err);
|
console.warn('[FeatureLens] Backend unreachable, using memory cache:', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +66,7 @@ class FeatureLensElement extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// Visual Inspector (2D DOM & 3D Three.js Raycaster)
|
// Visual Inspector (2D DOM & 3D Three.js Touch/Pointer Engine)
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
initInspector() {
|
initInspector() {
|
||||||
this.hoverOverlay = document.createElement('div');
|
this.hoverOverlay = document.createElement('div');
|
||||||
@ -108,7 +107,7 @@ class FeatureLensElement extends HTMLElement {
|
|||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
border-radius: 9999px;
|
border-radius: 9999px;
|
||||||
font-family: 'JetBrains Mono', monospace;
|
font-family: 'JetBrains Mono', monospace;
|
||||||
font-size: 0.8rem;
|
font-size: 0.775rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
box-shadow: 0 10px 25px rgba(0,0,0,0.8);
|
box-shadow: 0 10px 25px rgba(0,0,0,0.8);
|
||||||
z-index: 999999;
|
z-index: 999999;
|
||||||
@ -116,17 +115,23 @@ class FeatureLensElement extends HTMLElement {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
max-width: 90vw;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-align: center;
|
||||||
|
justify-content: center;
|
||||||
`;
|
`;
|
||||||
banner.innerHTML = `<span>🎯 Click any 2D UI element or 3D CAD part to attach note</span> <span style="color:#ef4444; margin-left:0.5rem;">[ESC to cancel]</span>`;
|
banner.innerHTML = `<span>🎯 Tap any 2D UI element or 3D CAD part</span> <span style="color:#ef4444; margin-left:0.5rem;">[Cancel]</span>`;
|
||||||
banner.onclick = () => this.stopInspectMode();
|
banner.onclick = () => this.stopInspectMode();
|
||||||
document.body.appendChild(banner);
|
document.body.appendChild(banner);
|
||||||
|
|
||||||
this._onMouseMove = this.handlePointerMove.bind(this);
|
this._onPointerMove = this.handlePointerMove.bind(this);
|
||||||
this._onClick = this.handlePointerClick.bind(this);
|
this._onPointerClick = this.handlePointerClick.bind(this);
|
||||||
this._onKeyDown = (e) => { if (e.key === 'Escape') this.stopInspectMode(); };
|
this._onKeyDown = (e) => { if (e.key === 'Escape') this.stopInspectMode(); };
|
||||||
|
|
||||||
window.addEventListener('mousemove', this._onMouseMove, true);
|
window.addEventListener('mousemove', this._onPointerMove, true);
|
||||||
window.addEventListener('click', this._onClick, true);
|
window.addEventListener('touchmove', this._onPointerMove, { passive: false, capture: true });
|
||||||
|
window.addEventListener('click', this._onPointerClick, true);
|
||||||
|
window.addEventListener('touchend', this._onPointerClick, { passive: false, capture: true });
|
||||||
window.addEventListener('keydown', this._onKeyDown, true);
|
window.addEventListener('keydown', this._onKeyDown, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,36 +141,45 @@ class FeatureLensElement extends HTMLElement {
|
|||||||
const banner = document.getElementById('fl-inspect-banner');
|
const banner = document.getElementById('fl-inspect-banner');
|
||||||
if (banner) banner.remove();
|
if (banner) banner.remove();
|
||||||
|
|
||||||
window.removeEventListener('mousemove', this._onMouseMove, true);
|
window.removeEventListener('mousemove', this._onPointerMove, true);
|
||||||
window.removeEventListener('click', this._onClick, true);
|
window.removeEventListener('touchmove', this._onPointerMove, { passive: false, capture: true });
|
||||||
|
window.removeEventListener('click', this._onPointerClick, true);
|
||||||
|
window.removeEventListener('touchend', this._onPointerClick, { passive: false, capture: true });
|
||||||
window.removeEventListener('keydown', this._onKeyDown, true);
|
window.removeEventListener('keydown', this._onKeyDown, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPointerCoords(e) {
|
||||||
|
if (e.touches && e.touches.length > 0) {
|
||||||
|
return { clientX: e.touches[0].clientX, clientY: e.touches[0].clientY, target: document.elementFromPoint(e.touches[0].clientX, e.touches[0].clientY) };
|
||||||
|
}
|
||||||
|
if (e.changedTouches && e.changedTouches.length > 0) {
|
||||||
|
return { clientX: e.changedTouches[0].clientX, clientY: e.changedTouches[0].clientY, target: document.elementFromPoint(e.changedTouches[0].clientX, e.changedTouches[0].clientY) };
|
||||||
|
}
|
||||||
|
return { clientX: e.clientX, clientY: e.clientY, target: e.target };
|
||||||
|
}
|
||||||
|
|
||||||
handlePointerMove(e) {
|
handlePointerMove(e) {
|
||||||
if (!this.isInspectMode) return;
|
if (!this.isInspectMode) return;
|
||||||
|
const { clientX, clientY, target } = this.getPointerCoords(e);
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
// Check if hovering 3D canvas
|
// Check if hovering 3D canvas
|
||||||
if (window.scene && window.camera && e.target.tagName === 'CANVAS' && this.raycaster) {
|
if (window.scene && window.camera && target.tagName === 'CANVAS' && this.raycaster) {
|
||||||
const rect = e.target.getBoundingClientRect();
|
const rect = target.getBoundingClientRect();
|
||||||
this.mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
|
this.mouse.x = ((clientX - rect.left) / rect.width) * 2 - 1;
|
||||||
this.mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
|
this.mouse.y = -((clientY - rect.top) / rect.height) * 2 + 1;
|
||||||
this.raycaster.setFromCamera(this.mouse, window.camera);
|
this.raycaster.setFromCamera(this.mouse, window.camera);
|
||||||
|
|
||||||
const intersects = this.raycaster.intersectObjects(window.scene.children, true);
|
const intersects = this.raycaster.intersectObjects(window.scene.children, true);
|
||||||
if (intersects.length > 0) {
|
if (intersects.length > 0) {
|
||||||
const hit = intersects[0];
|
|
||||||
let mesh = hit.object;
|
|
||||||
let partId = mesh.userData?.partId || mesh.name || '3d-mesh';
|
|
||||||
|
|
||||||
// Highlight 3D part
|
|
||||||
this.hoverOverlay.style.display = 'none';
|
this.hoverOverlay.style.display = 'none';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2D Element highlight
|
// 2D Element highlight
|
||||||
if (e.target && e.target !== this.hoverOverlay && !e.target.closest('feature-lens')) {
|
if (target && target !== this.hoverOverlay && !target.closest('feature-lens')) {
|
||||||
const rect = e.target.getBoundingClientRect();
|
const rect = target.getBoundingClientRect();
|
||||||
this.hoverOverlay.style.display = 'block';
|
this.hoverOverlay.style.display = 'block';
|
||||||
this.hoverOverlay.style.top = `${rect.top + window.scrollY}px`;
|
this.hoverOverlay.style.top = `${rect.top + window.scrollY}px`;
|
||||||
this.hoverOverlay.style.left = `${rect.left + window.scrollX}px`;
|
this.hoverOverlay.style.left = `${rect.left + window.scrollX}px`;
|
||||||
@ -176,19 +190,22 @@ class FeatureLensElement extends HTMLElement {
|
|||||||
|
|
||||||
handlePointerClick(e) {
|
handlePointerClick(e) {
|
||||||
if (!this.isInspectMode) return;
|
if (!this.isInspectMode) return;
|
||||||
e.preventDefault();
|
if (e.cancelable) e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const { clientX, clientY, target } = this.getPointerCoords(e);
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
let targetType = 'dom_element';
|
let targetType = 'dom_element';
|
||||||
let targetSelector = '';
|
let targetSelector = '';
|
||||||
let partId = null;
|
let partId = null;
|
||||||
let coords3d = null;
|
let coords3d = null;
|
||||||
|
|
||||||
// 3D Intersect check
|
// 3D Intersect check
|
||||||
if (window.scene && window.camera && e.target.tagName === 'CANVAS' && this.raycaster) {
|
if (window.scene && window.camera && target.tagName === 'CANVAS' && this.raycaster) {
|
||||||
const rect = e.target.getBoundingClientRect();
|
const rect = target.getBoundingClientRect();
|
||||||
this.mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
|
this.mouse.x = ((clientX - rect.left) / rect.width) * 2 - 1;
|
||||||
this.mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
|
this.mouse.y = -((clientY - rect.top) / rect.height) * 2 + 1;
|
||||||
this.raycaster.setFromCamera(this.mouse, window.camera);
|
this.raycaster.setFromCamera(this.mouse, window.camera);
|
||||||
|
|
||||||
const intersects = this.raycaster.intersectObjects(window.scene.children, true);
|
const intersects = this.raycaster.intersectObjects(window.scene.children, true);
|
||||||
@ -205,7 +222,7 @@ class FeatureLensElement extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (targetType === 'dom_element') {
|
if (targetType === 'dom_element') {
|
||||||
targetSelector = this.computeSelectorPath(e.target);
|
targetSelector = this.computeSelectorPath(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentTarget = {
|
this.currentTarget = {
|
||||||
@ -264,13 +281,13 @@ Technical Context & Candidate Source Files:
|
|||||||
async copyAiPrompt(noteId) {
|
async copyAiPrompt(noteId) {
|
||||||
const note = this.notes.find(n => n.id === noteId);
|
const note = this.notes.find(n => n.id === noteId);
|
||||||
if (!note) return;
|
if (!note) return;
|
||||||
const prompt = this.generateAiPrompt(note);
|
const promptText = this.generateAiPrompt(note);
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(prompt);
|
await navigator.clipboard.writeText(promptText);
|
||||||
alert('✅ AI Task Prompt copied to clipboard! Paste it directly into your AI assistant.');
|
alert('✅ AI Task Prompt copied to clipboard! Paste it directly into your AI assistant.');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('Clipboard write failed, showing text box:', err);
|
console.warn('Clipboard write failed, showing text box:', err);
|
||||||
prompt('Copy your AI task prompt:', prompt);
|
prompt('Copy your AI task prompt:', promptText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,7 +335,6 @@ Technical Context & Candidate Source Files:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto deduce candidate files
|
|
||||||
let suggestedFiles = ['viewer/index.html'];
|
let suggestedFiles = ['viewer/index.html'];
|
||||||
if (this.currentTarget?.type === '3d_mesh') {
|
if (this.currentTarget?.type === '3d_mesh') {
|
||||||
suggestedFiles.push('viewer/index.html#threejs-cad', 'mechanical/');
|
suggestedFiles.push('viewer/index.html#threejs-cad', 'mechanical/');
|
||||||
@ -411,9 +427,18 @@ Technical Context & Candidate Source Files:
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fl-sheet-handle {
|
||||||
|
display: none;
|
||||||
|
width: 36px;
|
||||||
|
height: 4px;
|
||||||
|
background: #475569;
|
||||||
|
border-radius: 9999px;
|
||||||
|
margin: 0 auto 0.5rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
#fl-activator {
|
#fl-activator {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 1.25rem;
|
bottom: max(1.25rem, env(safe-area-inset-bottom));
|
||||||
right: 1.25rem;
|
right: 1.25rem;
|
||||||
background: linear-gradient(135deg, #0f172a 0%, #1e1b4b 100%);
|
background: linear-gradient(135deg, #0f172a 0%, #1e1b4b 100%);
|
||||||
border: 1px solid var(--fl-accent);
|
border: 1px solid var(--fl-accent);
|
||||||
@ -430,6 +455,8 @@ Technical Context & Candidate Source Files:
|
|||||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
z-index: 999991;
|
z-index: 999991;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
min-height: 44px;
|
||||||
|
touch-action: manipulation;
|
||||||
}
|
}
|
||||||
#fl-activator:hover {
|
#fl-activator:hover {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
@ -453,14 +480,14 @@ Technical Context & Candidate Source Files:
|
|||||||
#fl-drawer {
|
#fl-drawer {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: -420px;
|
right: -440px;
|
||||||
width: 400px;
|
width: 420px;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background: rgba(11, 15, 23, 0.95);
|
background: rgba(11, 15, 23, 0.98);
|
||||||
backdrop-filter: blur(16px);
|
backdrop-filter: blur(20px);
|
||||||
border-left: 1px solid var(--fl-border);
|
border-left: 1px solid var(--fl-border);
|
||||||
box-shadow: -15px 0 35px rgba(0, 0, 0, 0.7);
|
box-shadow: -15px 0 35px rgba(0, 0, 0, 0.85);
|
||||||
transition: right 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: right 0.3s cubic-bezier(0.4, 0, 0.2, 1), bottom 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
z-index: 999992;
|
z-index: 999992;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -490,10 +517,15 @@ Technical Context & Candidate Source Files:
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
color: var(--fl-text-muted);
|
color: var(--fl-text-muted);
|
||||||
font-size: 1.2rem;
|
font-size: 1.4rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0.2rem 0.5rem;
|
padding: 0.25rem 0.6rem;
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
|
min-width: 44px;
|
||||||
|
min-height: 44px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
.fl-close-btn:hover {
|
.fl-close-btn:hover {
|
||||||
color: var(--fl-text);
|
color: var(--fl-text);
|
||||||
@ -507,6 +539,7 @@ Technical Context & Candidate Source Files:
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fl-actions {
|
.fl-actions {
|
||||||
@ -518,9 +551,9 @@ Technical Context & Candidate Source Files:
|
|||||||
background: var(--fl-card-bg);
|
background: var(--fl-card-bg);
|
||||||
border: 1px solid var(--fl-border);
|
border: 1px solid var(--fl-border);
|
||||||
color: var(--fl-text);
|
color: var(--fl-text);
|
||||||
padding: 0.5rem 0.75rem;
|
padding: 0.55rem 0.75rem;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
font-size: 0.75rem;
|
font-size: 0.775rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -528,7 +561,9 @@ Technical Context & Candidate Source Files:
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 0.35rem;
|
gap: 0.35rem;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-height: 44px;
|
||||||
transition: all 0.15s ease;
|
transition: all 0.15s ease;
|
||||||
|
touch-action: manipulation;
|
||||||
}
|
}
|
||||||
.fl-btn:hover {
|
.fl-btn:hover {
|
||||||
border-color: var(--fl-accent);
|
border-color: var(--fl-accent);
|
||||||
@ -548,19 +583,16 @@ Technical Context & Candidate Source Files:
|
|||||||
background: var(--fl-card-bg);
|
background: var(--fl-card-bg);
|
||||||
border: 1px solid var(--fl-border);
|
border: 1px solid var(--fl-border);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 0.75rem;
|
padding: 0.85rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.4rem;
|
gap: 0.45rem;
|
||||||
font-size: 0.75rem;
|
font-size: 0.775rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.fl-card:hover {
|
|
||||||
border-color: #334155;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fl-tag {
|
.fl-tag {
|
||||||
padding: 0.15rem 0.4rem;
|
padding: 0.15rem 0.45rem;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-size: 0.65rem;
|
font-size: 0.65rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@ -576,11 +608,12 @@ Technical Context & Candidate Source Files:
|
|||||||
background: #090d16;
|
background: #090d16;
|
||||||
border: 1px solid var(--fl-border);
|
border: 1px solid var(--fl-border);
|
||||||
color: var(--fl-text);
|
color: var(--fl-text);
|
||||||
padding: 0.5rem;
|
padding: 0.6rem;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
font-size: 0.75rem;
|
font-size: 0.8rem;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
|
min-height: 44px;
|
||||||
}
|
}
|
||||||
.fl-input:focus, .fl-textarea:focus, .fl-select:focus {
|
.fl-input:focus, .fl-textarea:focus, .fl-select:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
@ -588,7 +621,50 @@ Technical Context & Candidate Source Files:
|
|||||||
}
|
}
|
||||||
.fl-textarea {
|
.fl-textarea {
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
min-height: 80px;
|
min-height: 90px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile Responsive Bottom Sheet Transformation */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
#fl-activator {
|
||||||
|
bottom: max(1rem, env(safe-area-inset-bottom));
|
||||||
|
right: 1rem;
|
||||||
|
padding: 0.5rem 0.85rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fl-sheet-handle {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fl-drawer {
|
||||||
|
top: auto;
|
||||||
|
bottom: -100%;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 82vh;
|
||||||
|
border-left: none;
|
||||||
|
border-top: 1px solid var(--fl-border);
|
||||||
|
border-radius: 20px 20px 0 0;
|
||||||
|
padding-bottom: max(1rem, env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
#fl-drawer.open {
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fl-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 0.25rem;
|
||||||
|
padding: 0.6rem 1rem 0.5rem 1rem;
|
||||||
|
}
|
||||||
|
.fl-close-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.6rem;
|
||||||
|
right: 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@ -598,9 +674,10 @@ Technical Context & Candidate Source Files:
|
|||||||
<span id="fl-badge" class="fl-badge" style="display:none;">0</span>
|
<span id="fl-badge" class="fl-badge" style="display:none;">0</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Slide-Out Drawer Panel -->
|
<!-- Slide-Out Drawer / Bottom Sheet Panel -->
|
||||||
<div id="fl-drawer">
|
<div id="fl-drawer">
|
||||||
<div class="fl-header">
|
<div class="fl-header">
|
||||||
|
<div class="fl-sheet-handle"></div>
|
||||||
<div class="fl-title">
|
<div class="fl-title">
|
||||||
<span>FeatureLens Studio</span>
|
<span>FeatureLens Studio</span>
|
||||||
<span style="font-size:0.65rem; color:var(--fl-text-muted); font-weight:400;">(OSHW-AI)</span>
|
<span style="font-size:0.65rem; color:var(--fl-text-muted); font-weight:400;">(OSHW-AI)</span>
|
||||||
@ -618,11 +695,11 @@ Technical Context & Candidate Source Files:
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Create Note Form (Hidden by default) -->
|
<!-- Create Note Form -->
|
||||||
<div id="fl-form-container" style="display:none;">
|
<div id="fl-form-container" style="display:none;">
|
||||||
<div class="fl-card" style="border-color:var(--fl-accent);">
|
<div class="fl-card" style="border-color:var(--fl-accent);">
|
||||||
<div style="font-weight:700; color:var(--fl-accent);">Create AI Staging Note</div>
|
<div style="font-weight:700; color:var(--fl-accent);">Create AI Staging Note</div>
|
||||||
<div id="fl-target-label" style="font-size:0.65rem; color:var(--fl-text-muted);">🌐 Global Target</div>
|
<div id="fl-target-label" style="font-size:0.7rem; color:var(--fl-text-muted);">🌐 Global Target</div>
|
||||||
|
|
||||||
<input type="text" id="fl-title-input" class="fl-input" placeholder="Title (e.g. Wire WebSocket telemetry)" />
|
<input type="text" id="fl-title-input" class="fl-input" placeholder="Title (e.g. Wire WebSocket telemetry)" />
|
||||||
|
|
||||||
@ -683,18 +760,18 @@ Technical Context & Candidate Source Files:
|
|||||||
<span class="fl-tag fl-tag-${note.category}">${note.category}</span>
|
<span class="fl-tag fl-tag-${note.category}">${note.category}</span>
|
||||||
<span style="font-size:0.65rem; color:${note.status === 'completed' ? 'var(--fl-emerald)' : 'var(--fl-text-muted)'}; font-weight:700; text-transform:uppercase;">${note.status}</span>
|
<span style="font-size:0.65rem; color:${note.status === 'completed' ? 'var(--fl-emerald)' : 'var(--fl-text-muted)'}; font-weight:700; text-transform:uppercase;">${note.status}</span>
|
||||||
</div>
|
</div>
|
||||||
<div style="font-weight:700; color:var(--fl-text); font-size:0.8rem;">${note.title}</div>
|
<div style="font-weight:700; color:var(--fl-text); font-size:0.85rem;">${note.title}</div>
|
||||||
<div style="color:var(--fl-text-muted); font-size:0.65rem;">${targetInfo}</div>
|
<div style="color:var(--fl-text-muted); font-size:0.7rem;">${targetInfo}</div>
|
||||||
<div style="color:#cbd5e1; font-size:0.7rem; margin-top:0.2rem; line-height:1.3;">${note.description || 'No description provided.'}</div>
|
<div style="color:#cbd5e1; font-size:0.75rem; margin-top:0.2rem; line-height:1.35;">${note.description || 'No description provided.'}</div>
|
||||||
|
|
||||||
<div style="display:flex; gap:0.4rem; margin-top:0.4rem; padding-top:0.4rem; border-top:1px solid var(--fl-border);">
|
<div style="display:flex; gap:0.4rem; margin-top:0.4rem; padding-top:0.4rem; border-top:1px solid var(--fl-border);">
|
||||||
<button class="fl-btn fl-btn-primary" style="font-size:0.65rem; padding:0.25rem 0.5rem;" onclick="this.getRootNode().host.copyAiPrompt('${note.id}')">
|
<button class="fl-btn fl-btn-primary" style="font-size:0.7rem; padding:0.35rem 0.5rem;" onclick="this.getRootNode().host.copyAiPrompt('${note.id}')">
|
||||||
📋 Copy AI Prompt
|
📋 Copy AI Prompt
|
||||||
</button>
|
</button>
|
||||||
<button class="fl-btn" style="font-size:0.65rem; padding:0.25rem 0.4rem;" onclick="this.getRootNode().host.updateStatus('${note.id}', '${note.status === 'completed' ? 'open' : 'completed'}')">
|
<button class="fl-btn" style="font-size:0.7rem; padding:0.35rem 0.4rem;" onclick="this.getRootNode().host.updateStatus('${note.id}', '${note.status === 'completed' ? 'open' : 'completed'}')">
|
||||||
${note.status === 'completed' ? 'Reopen' : 'Complete'}
|
${note.status === 'completed' ? 'Reopen' : 'Complete'}
|
||||||
</button>
|
</button>
|
||||||
<button class="fl-btn" style="font-size:0.65rem; padding:0.25rem 0.4rem; color:var(--fl-rose);" onclick="this.getRootNode().host.deleteNote('${note.id}')">
|
<button class="fl-btn" style="font-size:0.7rem; padding:0.35rem 0.4rem; color:var(--fl-rose); flex:0 0 44px;" onclick="this.getRootNode().host.deleteNote('${note.id}')">
|
||||||
🗑️
|
🗑️
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -704,7 +781,6 @@ Technical Context & Candidate Source Files:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register Custom Web Component
|
|
||||||
if (!customElements.get('feature-lens')) {
|
if (!customElements.get('feature-lens')) {
|
||||||
customElements.define('feature-lens', FeatureLensElement);
|
customElements.define('feature-lens', FeatureLensElement);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -132,6 +132,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
|
touch-action: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Viewport Overlays */
|
/* Viewport Overlays */
|
||||||
@ -493,7 +494,206 @@
|
|||||||
background: rgba(6, 182, 212, 0.15);
|
background: rgba(6, 182, 212, 0.15);
|
||||||
border-color: var(--accent-cyan);
|
border-color: var(--accent-cyan);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
Mobile-First & Tablet Touch Ergonomics Breakpoints (WCAG 48px Compliance)
|
||||||
|
========================================================================== */
|
||||||
|
.sheet-drag-handle {
|
||||||
|
display: none;
|
||||||
|
width: 40px;
|
||||||
|
height: 4px;
|
||||||
|
background: #475569;
|
||||||
|
border-radius: 9999px;
|
||||||
|
margin: 0 auto 0.5rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
gap: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-group {
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-title {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-badge {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
padding: 0.15rem 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs {
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding-bottom: 0.35rem;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
scrollbar-width: none;
|
||||||
|
gap: 0.3rem;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
.nav-tabs::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tab {
|
||||||
|
padding: 0.45rem 0.75rem;
|
||||||
|
font-size: 0.725rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
min-height: 40px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sheet-drag-handle {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Content Panels Transformed to Slide-Up Bottom Sheets */
|
||||||
|
.content-panel {
|
||||||
|
position: fixed !important;
|
||||||
|
top: auto !important;
|
||||||
|
bottom: 0 !important;
|
||||||
|
left: 0 !important;
|
||||||
|
right: 0 !important;
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
height: 78vh !important;
|
||||||
|
max-height: 85vh !important;
|
||||||
|
border-radius: 20px 20px 0 0 !important;
|
||||||
|
border-left: none !important;
|
||||||
|
border-top: 1px solid var(--border-color) !important;
|
||||||
|
background: rgba(17, 24, 39, 0.98) !important;
|
||||||
|
backdrop-filter: blur(20px) !important;
|
||||||
|
box-shadow: 0 -15px 40px rgba(0, 0, 0, 0.85) !important;
|
||||||
|
transform: translateY(100%);
|
||||||
|
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
||||||
|
display: block !important;
|
||||||
|
z-index: 100 !important;
|
||||||
|
padding: 0.75rem 1rem max(1rem, env(safe-area-inset-bottom)) 1rem !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-panel.active {
|
||||||
|
transform: translateY(0) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-panel.minimized {
|
||||||
|
transform: translateY(calc(100% - 58px)) !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header {
|
||||||
|
position: sticky;
|
||||||
|
top: -0.75rem;
|
||||||
|
background: rgba(17, 24, 39, 0.98);
|
||||||
|
margin: -0.75rem -1rem 1rem -1rem;
|
||||||
|
padding: 0.6rem 1rem 0.5rem 1rem;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
z-index: 5;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header-actions {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.6rem;
|
||||||
|
right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Viewport Controls on Mobile */
|
||||||
|
.viewport-overlay {
|
||||||
|
top: auto !important;
|
||||||
|
bottom: 0.75rem !important;
|
||||||
|
left: 0.75rem !important;
|
||||||
|
right: 0.75rem !important;
|
||||||
|
width: auto !important;
|
||||||
|
max-height: 38vh !important;
|
||||||
|
overflow-y: auto !important;
|
||||||
|
border-radius: 12px !important;
|
||||||
|
padding: 0.75rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewport-quickbar {
|
||||||
|
top: 0.5rem !important;
|
||||||
|
right: 0.5rem !important;
|
||||||
|
left: auto !important;
|
||||||
|
gap: 0.25rem !important;
|
||||||
|
padding: 0.25rem !important;
|
||||||
|
border-radius: 6px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-btn span {
|
||||||
|
display: none; /* Icon-only buttons on mobile */
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-btn {
|
||||||
|
padding: 0.4rem 0.5rem !important;
|
||||||
|
font-size: 0.85rem !important;
|
||||||
|
min-width: 38px;
|
||||||
|
min-height: 38px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Touch Target Optimization (WCAG Minimum 44px-48px) */
|
||||||
|
.btn, .btn-outline {
|
||||||
|
min-height: 44px;
|
||||||
|
padding: 0.55rem 0.85rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-item {
|
||||||
|
min-height: 48px;
|
||||||
|
padding: 0.5rem 0.25rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-item input[type="checkbox"] {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
min-width: 22px;
|
||||||
|
min-height: 22px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table {
|
||||||
|
display: block;
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.logo-title {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-panel {
|
||||||
|
height: 84vh !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewport-overlay {
|
||||||
|
max-height: 34vh !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@ -658,6 +858,7 @@
|
|||||||
<!-- Panel 1: Supply Chain & Dynamic BOM Suite -->
|
<!-- Panel 1: Supply Chain & Dynamic BOM Suite -->
|
||||||
<div id="supply-chain" class="content-panel">
|
<div id="supply-chain" class="content-panel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
|
<div class="sheet-drag-handle"></div>
|
||||||
<div class="panel-title"><i class="fa-solid fa-truck-ramp-box"></i> <span>Supply Chain & Procurement Matrix</span></div>
|
<div class="panel-title"><i class="fa-solid fa-truck-ramp-box"></i> <span>Supply Chain & Procurement Matrix</span></div>
|
||||||
<div class="panel-header-actions">
|
<div class="panel-header-actions">
|
||||||
<button class="panel-btn" title="Minimize / Expand" onclick="toggleMinimizePanel(this)"><i class="fa-solid fa-compress"></i></button>
|
<button class="panel-btn" title="Minimize / Expand" onclick="toggleMinimizePanel(this)"><i class="fa-solid fa-compress"></i></button>
|
||||||
@ -713,6 +914,7 @@
|
|||||||
<!-- Panel 2: Material Science & Physics Engine -->
|
<!-- Panel 2: Material Science & Physics Engine -->
|
||||||
<div id="materials" class="content-panel">
|
<div id="materials" class="content-panel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
|
<div class="sheet-drag-handle"></div>
|
||||||
<div class="panel-title"><i class="fa-solid fa-atom"></i> <span>Material Science & Physics Selector</span></div>
|
<div class="panel-title"><i class="fa-solid fa-atom"></i> <span>Material Science & Physics Selector</span></div>
|
||||||
<div class="panel-header-actions">
|
<div class="panel-header-actions">
|
||||||
<button class="panel-btn" title="Minimize / Expand" onclick="toggleMinimizePanel(this)"><i class="fa-solid fa-compress"></i></button>
|
<button class="panel-btn" title="Minimize / Expand" onclick="toggleMinimizePanel(this)"><i class="fa-solid fa-compress"></i></button>
|
||||||
@ -768,6 +970,7 @@
|
|||||||
<!-- Panel 3: Distributed 3D Print Farm & Machine Profiles -->
|
<!-- Panel 3: Distributed 3D Print Farm & Machine Profiles -->
|
||||||
<div id="print-farm" class="content-panel">
|
<div id="print-farm" class="content-panel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
|
<div class="sheet-drag-handle"></div>
|
||||||
<div class="panel-title"><i class="fa-solid fa-print"></i> <span>3D Print Farm & Slicer Profiles</span></div>
|
<div class="panel-title"><i class="fa-solid fa-print"></i> <span>3D Print Farm & Slicer Profiles</span></div>
|
||||||
<div class="panel-header-actions">
|
<div class="panel-header-actions">
|
||||||
<button class="panel-btn" title="Minimize / Expand" onclick="toggleMinimizePanel(this)"><i class="fa-solid fa-compress"></i></button>
|
<button class="panel-btn" title="Minimize / Expand" onclick="toggleMinimizePanel(this)"><i class="fa-solid fa-compress"></i></button>
|
||||||
@ -811,6 +1014,7 @@
|
|||||||
<!-- Panel 4: WebSerial Live Supervisor & Flasher -->
|
<!-- Panel 4: WebSerial Live Supervisor & Flasher -->
|
||||||
<div id="diagnostics" class="content-panel">
|
<div id="diagnostics" class="content-panel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
|
<div class="sheet-drag-handle"></div>
|
||||||
<div class="panel-title"><i class="fa-solid fa-terminal"></i> <span>WebSerial Supervisor Console</span></div>
|
<div class="panel-title"><i class="fa-solid fa-terminal"></i> <span>WebSerial Supervisor Console</span></div>
|
||||||
<div class="panel-header-actions">
|
<div class="panel-header-actions">
|
||||||
<button class="panel-btn" title="Minimize / Expand" onclick="toggleMinimizePanel(this)"><i class="fa-solid fa-compress"></i></button>
|
<button class="panel-btn" title="Minimize / Expand" onclick="toggleMinimizePanel(this)"><i class="fa-solid fa-compress"></i></button>
|
||||||
@ -852,6 +1056,7 @@
|
|||||||
<!-- Panel 5: Interactive QA & Step-by-Step Assembly Guide -->
|
<!-- Panel 5: Interactive QA & Step-by-Step Assembly Guide -->
|
||||||
<div id="qa-assembly" class="content-panel">
|
<div id="qa-assembly" class="content-panel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
|
<div class="sheet-drag-handle"></div>
|
||||||
<div class="panel-title"><i class="fa-solid fa-screwdriver-wrench"></i> <span>Assembly QA Checklist</span></div>
|
<div class="panel-title"><i class="fa-solid fa-screwdriver-wrench"></i> <span>Assembly QA Checklist</span></div>
|
||||||
<div class="panel-header-actions">
|
<div class="panel-header-actions">
|
||||||
<button class="panel-btn" title="Minimize / Expand" onclick="toggleMinimizePanel(this)"><i class="fa-solid fa-compress"></i></button>
|
<button class="panel-btn" title="Minimize / Expand" onclick="toggleMinimizePanel(this)"><i class="fa-solid fa-compress"></i></button>
|
||||||
@ -875,6 +1080,7 @@
|
|||||||
<!-- Panel: Serialized MES Chassis Travelers -->
|
<!-- Panel: Serialized MES Chassis Travelers -->
|
||||||
<div id="travelers" class="content-panel">
|
<div id="travelers" class="content-panel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
|
<div class="sheet-drag-handle"></div>
|
||||||
<div class="panel-title"><i class="fa-solid fa-barcode"></i> <span>Chassis Manufacturing Execution (MES)</span></div>
|
<div class="panel-title"><i class="fa-solid fa-barcode"></i> <span>Chassis Manufacturing Execution (MES)</span></div>
|
||||||
<div class="panel-header-actions">
|
<div class="panel-header-actions">
|
||||||
<button class="panel-btn" title="Minimize / Expand" onclick="toggleMinimizePanel(this)"><i class="fa-solid fa-compress"></i></button>
|
<button class="panel-btn" title="Minimize / Expand" onclick="toggleMinimizePanel(this)"><i class="fa-solid fa-compress"></i></button>
|
||||||
@ -896,6 +1102,7 @@
|
|||||||
<!-- Panel: Automated OS & Provisioning Config Generator -->
|
<!-- Panel: Automated OS & Provisioning Config Generator -->
|
||||||
<div id="provisioning" class="content-panel">
|
<div id="provisioning" class="content-panel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
|
<div class="sheet-drag-handle"></div>
|
||||||
<div class="panel-title"><i class="fa-solid fa-server"></i> <span>ZFS & OS Provisioning Studio</span></div>
|
<div class="panel-title"><i class="fa-solid fa-server"></i> <span>ZFS & OS Provisioning Studio</span></div>
|
||||||
<div class="panel-header-actions">
|
<div class="panel-header-actions">
|
||||||
<button class="panel-btn" title="Minimize / Expand" onclick="toggleMinimizePanel(this)"><i class="fa-solid fa-compress"></i></button>
|
<button class="panel-btn" title="Minimize / Expand" onclick="toggleMinimizePanel(this)"><i class="fa-solid fa-compress"></i></button>
|
||||||
@ -937,6 +1144,7 @@
|
|||||||
<!-- Panel 6: Parts Catalog & 3D CAD Component Library -->
|
<!-- Panel 6: Parts Catalog & 3D CAD Component Library -->
|
||||||
<div id="parts-catalog" class="content-panel">
|
<div id="parts-catalog" class="content-panel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
|
<div class="sheet-drag-handle"></div>
|
||||||
<div class="panel-title"><i class="fa-solid fa-boxes-stacked"></i> <span>Parts & 3D CAD Library</span></div>
|
<div class="panel-title"><i class="fa-solid fa-boxes-stacked"></i> <span>Parts & 3D CAD Library</span></div>
|
||||||
<div class="panel-header-actions">
|
<div class="panel-header-actions">
|
||||||
<button class="panel-btn" title="Minimize / Expand" onclick="toggleMinimizePanel(this)"><i class="fa-solid fa-compress"></i></button>
|
<button class="panel-btn" title="Minimize / Expand" onclick="toggleMinimizePanel(this)"><i class="fa-solid fa-compress"></i></button>
|
||||||
@ -1062,7 +1270,13 @@
|
|||||||
|
|
||||||
controls = new THREE.OrbitControls(camera, renderer.domElement);
|
controls = new THREE.OrbitControls(camera, renderer.domElement);
|
||||||
controls.enableDamping = true;
|
controls.enableDamping = true;
|
||||||
controls.dampingFactor = 0.05;
|
controls.dampingFactor = 0.08;
|
||||||
|
controls.touches = {
|
||||||
|
ONE: THREE.TOUCH.ROTATE,
|
||||||
|
TWO: THREE.TOUCH.DOLLY_PAN
|
||||||
|
};
|
||||||
|
controls.minDistance = 200;
|
||||||
|
controls.maxDistance = 1800;
|
||||||
|
|
||||||
// Lighting
|
// Lighting
|
||||||
ambientLight = new THREE.AmbientLight(0xffffff, 0.7);
|
ambientLight = new THREE.AmbientLight(0xffffff, 0.7);
|
||||||
@ -1860,10 +2074,11 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
function updateViewportOffset() {
|
function updateViewportOffset() {
|
||||||
|
const isMobile = window.innerWidth <= 768;
|
||||||
const activePanel = document.querySelector('.content-panel.active');
|
const activePanel = document.querySelector('.content-panel.active');
|
||||||
const isMinimized = activePanel ? activePanel.classList.contains('minimized') : true;
|
const isMinimized = activePanel ? activePanel.classList.contains('minimized') : true;
|
||||||
// Shift view window gently to the left (140px) when sidebar is open and expanded
|
// Shift view window gently to the left (140px) ONLY on desktop when sidebar is open
|
||||||
targetViewOffset = (activePanel && !isMinimized) ? 140 : 0;
|
targetViewOffset = (!isMobile && activePanel && !isMinimized) ? 140 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleTurntable() {
|
function toggleTurntable() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user