Add comprehensive responsive scanline scaling system with anti-aliasing

- Attempt pixel-perfect scanline rendering for scaled displays and zoom scenarios
- Implement dynamic scanline thickness calculation to prevent sub-pixel rendering issues
- Add enhanced kiosk detection via isKioskLike for better fullscreen optimization
- Optimize scanlines for specific kiosk resolutions (1024x768, 1023x767)
- Add responsive SCSS media queries for different display densities
- Include extensive debugging utilities for scanline troubleshooting
- Improve noSleep error handling with proper promise rejection handling
- Update to modern fullscreen API method names
- Add async/await error handling for fullscreen requests
- Trigger resize after fullscreen engagement to apply optimal scaling
This commit is contained in:
Eddy G
2025-06-24 22:38:25 -04:00
parent be41d66de9
commit 65944dc3b5
4 changed files with 774 additions and 16 deletions

View File

@@ -89,6 +89,7 @@ const init = () => {
const query = parsedParameters.latLonQuery ?? localStorage.getItem('latLonQuery');
const latLon = parsedParameters.latLon ?? localStorage.getItem('latLon');
const fromGPS = localStorage.getItem('latLonFromGPS') && !loadFromParsed;
if (query && latLon && !fromGPS) {
const txtAddress = document.querySelector(TXT_ADDRESS_SELECTOR);
txtAddress.value = query;
@@ -171,16 +172,29 @@ const btnFullScreenClick = () => {
return false;
};
const enterFullScreen = () => {
// This is async because modern browsers return a Promise from requestFullscreen
const enterFullScreen = async () => {
const element = document.querySelector('#divTwc');
// Supports most browsers and their versions.
const requestMethod = element.requestFullScreen || element.webkitRequestFullScreen
|| element.mozRequestFullScreen || element.msRequestFullscreen;
const requestMethod = element.requestFullscreen || element.webkitRequestFullscreen
|| element.mozRequestFullscreen || element.msRequestFullscreen;
if (requestMethod) {
// Native full screen.
requestMethod.call(element, { navigationUI: 'hide' });
try {
// Native full screen with options for optimal display
await requestMethod.call(element, {
navigationUI: 'hide',
allowsInlineMediaPlayback: true,
});
// Allow a moment for fullscreen to engage, then optimize
setTimeout(() => {
resize();
}, 100);
} catch (error) {
console.error('❌ Fullscreen request failed:', error);
}
} else {
// iOS doesn't support FullScreen API.
window.scrollTo(0, 0);
@@ -202,8 +216,8 @@ const exitFullscreen = () => {
document.exitFullscreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.mozCancelFullscreen) {
document.mozCancelFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}

View File

@@ -216,11 +216,15 @@ const setPlaying = (newValue) => {
localStorage.setItem('play', playing);
if (playing) {
noSleep(true);
noSleep(true).catch(() => {
// Wake lock failed, but continue normally
});
playButton.title = 'Pause';
playButton.src = 'images/nav/ic_pause_white_24dp_2x.png';
} else {
noSleep(false);
noSleep(false).catch(() => {
// Wake lock disable failed, but continue normally
});
playButton.title = 'Play';
playButton.src = 'images/nav/ic_play_arrow_white_24dp_2x.png';
}
@@ -272,15 +276,24 @@ const getDisplay = (index) => displays[index];
// resize the container on a page resize
const resize = () => {
// Check for display optimization opportunities before applying zoom
const displayInfo = getDisplayInfo();
const targetWidth = settings.wide.value ? 640 + 107 + 107 : 640;
const widthZoomPercent = (document.querySelector('#divTwcBottom').getBoundingClientRect().width) / targetWidth;
const heightZoomPercent = (window.innerHeight) / 480;
const scale = Math.min(widthZoomPercent, heightZoomPercent);
if (scale < 1.0 || document.fullscreenElement || settings.kiosk) {
const { isKioskLike } = displayInfo;
if (scale < 1.0 || isKioskLike) {
document.querySelector('#container').style.zoom = scale;
// Apply scanline scaling for low-resolution displays and kiosk mode
applyScanlineScaling(scale);
} else {
document.querySelector('#container').style.zoom = 'unset';
// Reset scanline scaling
applyScanlineScaling(1.0);
}
};
@@ -289,6 +302,678 @@ const resetStatuses = () => {
displays.forEach((display) => { display.status = STATUS.loading; });
};
// Enhanced kiosk detection with automatic fullscreen optimization
const getDisplayInfo = () => {
const isKiosk = settings.kiosk?.value || false;
const isFullscreen = !!document.fullscreenElement;
const isKioskLike = isKiosk || isFullscreen || (window.innerHeight >= window.screen.height - 10);
return { isKiosk, isFullscreen, isKioskLike };
};
// Make function globally available for debugging
window.getDisplayInfo = getDisplayInfo;
// Apply dynamic scanline scaling based on zoom level
const applyScanlineScaling = (zoomScale) => {
// Only apply if scanlines are enabled
const container = document.querySelector('#container');
if (!container || !container.classList.contains('scanlines')) {
return;
}
// Get display and viewport information
const displayWidth = window.screen.width;
const displayHeight = window.screen.height;
const devicePixelRatio = window.devicePixelRatio || 1;
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const isFullscreen = !!document.fullscreenElement;
const isKiosk = settings.kiosk?.value || false;
const isKioskLike = isKiosk || isFullscreen || (window.innerHeight >= window.screen.height - 10);
// Check for sub-pixel rendering issues
const effectiveScanlineHeight = 1 * zoomScale * devicePixelRatio;
const willCauseAliasing = effectiveScanlineHeight < 1.0 || (effectiveScanlineHeight % 1 !== 0);
// Calculate optimal scanline thickness
let scanlineScale = 1;
let scalingReason = 'default';
// Primary strategy: Ensure scanlines render as whole pixels
if (willCauseAliasing) {
if (zoomScale > 1.0) {
// Upscaling scenario (like 1024x768 → 1.6x zoom)
const targetThickness = Math.ceil(1 / zoomScale);
scanlineScale = Math.max(1, targetThickness);
scalingReason = 'upscaling aliasing prevention';
} else {
// Downscaling scenario
scanlineScale = Math.ceil(1 / zoomScale);
scalingReason = 'downscaling aliasing prevention';
}
}
// Specific display-based adjustments
if (displayWidth <= 1024 && displayHeight <= 768 && devicePixelRatio < 2) {
if (zoomScale > 1.4) {
scanlineScale = Math.max(scanlineScale, Math.round(1 / zoomScale * 2));
scalingReason = '1024x768 high upscaling compensation';
} else {
scanlineScale = Math.max(scanlineScale, 1);
scalingReason = '1024x768 display optimization';
}
}
// Override for kiosk/fullscreen mode with specific viewport dimensions
if (isKioskLike && (
Math.abs(zoomScale - 1.598) < 0.05 // More flexible zoom detection for 1024x768 scenarios
|| (viewportWidth === 1023 && viewportHeight === 767) // Exact Chrome kiosk viewport
|| (viewportWidth === 1024 && viewportHeight === 768) // Perfect viewport
)) {
// Kiosk mode optimization for 1024x768 displays
// Use optimal scanlines that render as exactly 2px with no banding
if (viewportWidth === 1023 && viewportHeight === 767) {
// For the exact 1023x767 Chrome kiosk viewport
// Calculate precise thickness for exactly 2px rendering
const targetRendered = 2.0;
scanlineScale = targetRendered / zoomScale; // This gives us exactly 2px
scalingReason = 'Chrome kiosk 1023x767 - optimal 2px scanlines';
} else {
// For 1024x768 or similar zoomed scenarios
scanlineScale = 1.25; // Standard 2px optimization
scalingReason = 'Kiosk/fullscreen 1024x768 - optimal 2px scanlines';
}
}
// Calculate precise thickness to avoid sub-pixel rendering
let preciseThickness = scanlineScale;
let backgroundSize = scanlineScale * 2;
// For upscaling scenarios, try to make the final rendered size a whole number
// BUT skip this if we already have a specific override for the zoom level
if (zoomScale > 1.0 && willCauseAliasing && !scalingReason.includes('optimal') && !scalingReason.includes('Kiosk')) {
const targetRenderedHeight = Math.round(effectiveScanlineHeight);
preciseThickness = targetRenderedHeight / zoomScale / devicePixelRatio;
backgroundSize = preciseThickness * 2;
}
// Apply dynamic styles with fractional pixel compensation
let styleElement = document.getElementById('dynamic-scanlines');
if (!styleElement) {
styleElement = document.createElement('style');
styleElement.id = 'dynamic-scanlines';
document.head.appendChild(styleElement);
}
const cssRules = `
.scanlines:before {
height: ${preciseThickness}px !important;
image-rendering: pixelated !important;
image-rendering: crisp-edges !important;
}
.scanlines:after {
background-size: 100% ${backgroundSize}px !important;
image-rendering: pixelated !important;
image-rendering: crisp-edges !important;
}
`;
styleElement.textContent = cssRules;
// Only log when optimal kiosk mode is applied (minimize debug output)
if (scalingReason.includes('optimal') && !window.scanlineLoggedOnce) {
console.log(`Scanlines: ${preciseThickness}px (${scalingReason})`);
window.scanlineLoggedOnce = true;
}
};
// Debug function for scanlines
// All these can be called from browser console.
// Leaving them here for now, but they can potentially be removed later.
// Function to request perfect fullscreen for optimal display
const requestPerfectFullscreen = async () => {
const element = document.querySelector('#divTwc');
try {
// Use the Fullscreen API to get perfect viewport control
const requestMethod = element.requestFullscreen || element.webkitRequestFullscreen
|| element.mozRequestFullScreen || element.msRequestFullscreen;
if (requestMethod) {
// Request fullscreen with minimal logging
await requestMethod.call(element, {
navigationUI: 'hide',
// Request specific fullscreen options if supported
allowsInlineMediaPlayback: true,
});
// Allow a moment for fullscreen to engage
setTimeout(() => {
// Re-trigger resize to apply optimal scaling
resize();
// Apply scanline scaling based on new dimensions
const container = document.querySelector('#container');
const zoomScale = parseFloat(container.style.zoom) || 1;
applyScanlineScaling(zoomScale);
}, 100);
return true;
}
console.warn('Fullscreen API not supported');
return false;
} catch (error) {
console.error('Failed to request fullscreen:', error);
return false;
}
};
// Make function globally available for debugging
window.requestPerfectFullscreen = requestPerfectFullscreen;
const debugScanlines = () => {
console.group('Manual Scanlines Debug');
const container = document.querySelector('#container');
if (!container) {
console.error('Container element not found');
console.groupEnd();
return { error: 'Container element not found' };
}
const hasScanlinesClass = container.classList.contains('scanlines');
const containerRect = container.getBoundingClientRect();
const currentZoom = parseFloat(container.style.zoom) || 1;
console.log(`Scanlines class present: ${hasScanlinesClass}`);
console.log(`Container dimensions: ${containerRect.width.toFixed(2)}x${containerRect.height.toFixed(2)}`);
console.log(`Current zoom: ${currentZoom}`);
const debugInfo = {
hasScanlinesClass,
containerDimensions: {
width: containerRect.width,
height: containerRect.height,
left: containerRect.left,
top: containerRect.top,
},
currentZoom,
viewport: {
width: window.innerWidth,
height: window.innerHeight,
},
screen: {
width: window.screen.width,
height: window.screen.height,
},
devicePixelRatio: window.devicePixelRatio || 1,
isFullscreen: !!document.fullscreenElement,
};
if (hasScanlinesClass) {
console.log(`Triggering applyScanlineScaling with zoom: ${currentZoom}`);
applyScanlineScaling(currentZoom);
// Check if dynamic styles exist
const dynamicStyle = document.getElementById('dynamic-scanlines');
if (dynamicStyle) {
console.log('Current dynamic CSS:', dynamicStyle.textContent);
debugInfo.dynamicCSS = dynamicStyle.textContent;
} else {
console.log('No dynamic scanlines styles found');
debugInfo.dynamicCSS = null;
}
// Get computed styles for scanlines
const beforeStyle = window.getComputedStyle(container, ':before');
const afterStyle = window.getComputedStyle(container, ':after');
const computedStyles = {
before: {
height: beforeStyle.height,
background: beforeStyle.background,
opacity: beforeStyle.opacity,
imageRendering: beforeStyle.imageRendering,
},
after: {
backgroundSize: afterStyle.backgroundSize,
backgroundImage: afterStyle.backgroundImage,
opacity: afterStyle.opacity,
imageRendering: afterStyle.imageRendering,
},
};
console.log('Computed :before styles:');
console.log(' height:', computedStyles.before.height);
console.log(' background:', computedStyles.before.background);
console.log(' opacity:', computedStyles.before.opacity);
console.log(' image-rendering:', computedStyles.before.imageRendering);
console.log('Computed :after styles:');
console.log(' background-size:', computedStyles.after.backgroundSize);
console.log(' background-image:', computedStyles.after.backgroundImage);
console.log(' opacity:', computedStyles.after.opacity);
console.log(' image-rendering:', computedStyles.after.imageRendering);
debugInfo.computedStyles = computedStyles;
}
console.groupEnd();
return debugInfo;
};
// Make debug function globally available
window.debugScanlines = debugScanlines;
// Test function to manually set scanline scale - can be called from browser console
const testScanlineScale = (scale) => {
console.log(`Testing scanline scale: ${scale}x`);
let styleElement = document.getElementById('dynamic-scanlines');
if (!styleElement) {
styleElement = document.createElement('style');
styleElement.id = 'dynamic-scanlines';
document.head.appendChild(styleElement);
}
const cssRules = `
.scanlines:before {
height: ${scale}px !important;
image-rendering: pixelated !important;
image-rendering: crisp-edges !important;
}
.scanlines:after {
background-size: 100% ${scale * 2}px !important;
image-rendering: pixelated !important;
image-rendering: crisp-edges !important;
}
`;
styleElement.textContent = cssRules;
// Calculate what this will look like when rendered
const container = document.querySelector('#container');
const zoom = parseFloat(container?.style.zoom) || 1;
const expectedRendered = scale * zoom;
const isWholePixel = Math.abs(expectedRendered % 1) < 0.01;
const result = {
appliedScale: scale,
backgroundSize: scale * 2,
currentZoom: zoom,
expectedRendered,
isWholePixel,
cssRules: cssRules.trim(),
};
console.log(`Applied ${scale}px scanline height with ${scale * 2}px background-size`);
console.log(`Expected rendered height: ${expectedRendered.toFixed(4)}px`);
console.log(`Will render as whole pixels: ${isWholePixel}`);
return result;
};
// Make test function globally available
window.testScanlineScale = testScanlineScale;
// Test function for precise fractional values to eliminate banding
const testPreciseScanlines = () => {
const container = document.querySelector('#container');
const zoom = parseFloat(container?.style.zoom) || 1;
console.group('Testing Precise Scanline Values');
console.log(`Current zoom: ${zoom.toFixed(4)}`);
// Test values that should result in whole pixel rendering
const testValues = [
0.625, // Should render as 1px (0.625 * 1.598 ≈ 1.0)
1.25, // Should render as 2px (1.25 * 1.598 ≈ 2.0)
1.875, // Should render as 3px (1.875 * 1.598 ≈ 3.0)
2.5, // Should render as 4px (2.5 * 1.598 ≈ 4.0)
];
const results = testValues.map((value) => {
const rendered = value * zoom;
const isWholePixel = Math.abs(rendered % 1) < 0.01;
const result = {
inputValue: value,
renderedValue: rendered,
isWholePixel,
fractionalPart: rendered % 1,
};
console.log(`Test ${value}px → ${rendered.toFixed(4)}px rendered (${isWholePixel ? '✅ whole' : '❌ fractional'})`);
return result;
});
console.log('Use testScanlineScale(value) to try these values');
console.groupEnd();
return {
currentZoom: zoom,
testResults: results,
recommendation: 'Use testScanlineScale(value) to apply a specific value',
};
};
// Make precise test function globally available
window.testPreciseScanlines = testPreciseScanlines;
// Function to analyze container dimension issues
const analyzeContainerDimensions = () => {
const container = document.querySelector('#container');
if (!container) {
return { error: 'Container not found' };
}
const containerRect = container.getBoundingClientRect();
const containerStyle = window.getComputedStyle(container);
const { parentElement } = container;
const parentRect = parentElement ? parentElement.getBoundingClientRect() : null;
const parentStyle = parentElement ? window.getComputedStyle(parentElement) : null;
const analysis = {
container: {
rect: {
width: containerRect.width,
height: containerRect.height,
left: containerRect.left,
top: containerRect.top,
},
computedStyle: {
width: containerStyle.width,
height: containerStyle.height,
padding: containerStyle.padding,
margin: containerStyle.margin,
border: containerStyle.border,
boxSizing: containerStyle.boxSizing,
zoom: containerStyle.zoom,
transform: containerStyle.transform,
},
},
parent: parentRect ? {
rect: {
width: parentRect.width,
height: parentRect.height,
left: parentRect.left,
top: parentRect.top,
},
computedStyle: {
width: parentStyle.width,
height: parentStyle.height,
padding: parentStyle.padding,
margin: parentStyle.margin,
border: parentStyle.border,
boxSizing: parentStyle.boxSizing,
},
} : null,
viewport: {
width: window.innerWidth,
height: window.innerHeight,
},
screen: {
width: window.screen.width,
height: window.screen.height,
},
devicePixelRatio: window.devicePixelRatio || 1,
isFullscreen: !!document.fullscreenElement,
};
console.group('Container Dimension Analysis');
console.log('Container Rect:', analysis.container.rect);
console.log('Container Computed Style:', analysis.container.computedStyle);
if (analysis.parent) {
console.log('Parent Rect:', analysis.parent.rect);
console.log('Parent Computed Style:', analysis.parent.computedStyle);
}
console.log('Viewport:', analysis.viewport);
console.log('Screen:', analysis.screen);
// Check for fractional dimension causes
const expectedTargetWidth = 640; // Base width
const expectedTargetHeight = 480; // Base height
const actualScale = Math.min(analysis.viewport.width / expectedTargetWidth, analysis.viewport.height / expectedTargetHeight);
const fractionalWidth = analysis.container.rect.width % 1;
const fractionalHeight = analysis.container.rect.height % 1;
console.log(`Expected scale: ${actualScale.toFixed(4)}`);
console.log(`Fractional width: ${fractionalWidth.toFixed(4)}px`);
console.log(`Fractional height: ${fractionalHeight.toFixed(4)}px`);
console.log(`Width is fractional: ${fractionalWidth > 0.01}`);
console.log(`Height is fractional: ${fractionalHeight > 0.01}`);
analysis.scaling = {
expectedScale: actualScale,
fractionalWidth,
fractionalHeight,
hasFractionalDimensions: fractionalWidth > 0.01 || fractionalHeight > 0.01,
};
console.groupEnd();
return analysis;
};
// Make container analysis function globally available
window.analyzeContainerDimensions = analyzeContainerDimensions;
// Function to calculate optimal scanline thickness that eliminates fractional rendering
const calculateOptimalScanlineThickness = (targetZoom = null) => {
const container = document.querySelector('#container');
if (!container) {
return { error: 'Container not found' };
}
const currentZoom = targetZoom || parseFloat(container.style.zoom) || 1;
const devicePixelRatio = window.devicePixelRatio || 1;
console.group('Calculating Optimal Scanline Thickness');
console.log(`Current zoom: ${currentZoom.toFixed(4)}`);
console.log(`Device pixel ratio: ${devicePixelRatio}`);
// Calculate possible thickness values that result in whole pixel rendering
const candidates = [];
// Test thickness values from 0.1 to 3.0 in 0.001 increments
for (let thickness = 0.1; thickness <= 3.0; thickness += 0.001) {
const renderedHeight = thickness * currentZoom * devicePixelRatio;
const fractionalPart = renderedHeight % 1;
// If the rendered height is very close to a whole number
if (fractionalPart < 0.001 || fractionalPart > 0.999) {
const wholePixelHeight = Math.round(renderedHeight);
candidates.push({
thickness: Math.round(thickness * 1000) / 1000, // Round to 3 decimal places
renderedHeight: wholePixelHeight,
actualRendered: renderedHeight,
error: Math.abs(renderedHeight - wholePixelHeight),
});
}
}
// Sort by error (closest to whole pixel) and prefer reasonable thickness values
candidates.sort((a, b) => {
if (Math.abs(a.error - b.error) < 0.0001) {
// If errors are similar, prefer thickness closer to 1
return Math.abs(a.thickness - 1) - Math.abs(b.thickness - 1);
}
return a.error - b.error;
});
// Take the best candidates for different pixel heights
const recommendations = [];
const seenHeights = new Set();
candidates.some((candidate) => {
if (!seenHeights.has(candidate.renderedHeight) && recommendations.length < 5) {
seenHeights.add(candidate.renderedHeight);
recommendations.push(candidate);
}
return recommendations.length >= 5; // Stop when we have 5 recommendations
});
console.log('Recommendations:');
recommendations.forEach((rec, index) => {
console.log(`${index + 1}. ${rec.thickness}px → ${rec.renderedHeight}px (error: ${rec.error.toFixed(6)})`);
});
const result = {
currentZoom,
devicePixelRatio,
recommendations,
bestRecommendation: recommendations[0] || null,
};
if (result.bestRecommendation) {
console.log(`Best recommendation: ${result.bestRecommendation.thickness}px`);
console.log(` Will render as: ${result.bestRecommendation.renderedHeight}px`);
console.log(` Use: testScanlineScale(${result.bestRecommendation.thickness})`);
}
console.groupEnd();
return result;
};
// Make optimal calculation function globally available
window.calculateOptimalScanlineThickness = calculateOptimalScanlineThickness;
// Function to analyze viewport and provide fullscreen optimization recommendations
const analyzeViewportOptimization = () => {
const viewport = {
width: window.innerWidth,
height: window.innerHeight,
screen: {
width: window.screen.width,
height: window.screen.height,
},
devicePixelRatio: window.devicePixelRatio || 1,
isFullscreen: !!document.fullscreenElement,
isKiosk: settings.kiosk?.value || false,
};
// Check for fractional viewport dimensions
const hasFractionalViewport = (viewport.width % 1 !== 0) || (viewport.height % 1 !== 0);
// Check for common kiosk viewport sizes
const isKnownKioskSize = (
(viewport.width === 1023 && viewport.height === 767) // Common Chrome kiosk issue
|| (viewport.width === 1024 && viewport.height === 768) // Perfect kiosk size
);
// Minimize debug output for production use
if (window.debugMode) {
console.group('Viewport Optimization Analysis');
console.log('Current viewport:', `${viewport.width}x${viewport.height}`);
console.log('Screen resolution:', `${viewport.screen.width}x${viewport.screen.height}`);
console.log('Device pixel ratio:', viewport.devicePixelRatio);
console.log('Has fractional viewport:', hasFractionalViewport);
console.log('Is known kiosk size:', isKnownKioskSize);
console.log('Is fullscreen:', viewport.isFullscreen);
console.log('Is kiosk mode:', viewport.isKiosk);
}
// Kiosk-specific analysis
const recommendations = [];
if (viewport.isKiosk && isKnownKioskSize) {
if (viewport.width === 1023 && viewport.height === 767) {
recommendations.push('Detected 1023x767 kiosk viewport - using calculated optimal scanlines for perfect 2px rendering');
} else if (viewport.width === 1024 && viewport.height === 768) {
recommendations.push('Perfect 1024x768 kiosk viewport detected - optimal scanlines will be applied');
}
} else if (viewport.isKiosk && hasFractionalViewport) {
recommendations.push('Custom kiosk viewport detected - scanlines will be optimized for exact dimensions');
}
// Calculate what the zoom scale would be with current dimensions
const targetWidth = settings.wide?.value ? 640 + 107 + 107 : 640;
const targetHeight = 480;
const currentWidthRatio = viewport.width / targetWidth;
const currentHeightRatio = viewport.height / targetHeight;
const currentScale = Math.min(currentWidthRatio, currentHeightRatio);
// Calculate scanline rendering for current setup
const currentScanlineHeight = 1 * currentScale * viewport.devicePixelRatio;
const willCauseAliasing = currentScanlineHeight < 1.0 || (currentScanlineHeight % 1 !== 0);
if (window.debugMode) {
console.log('Scaling Analysis:');
console.log(` Current scale: ${currentScale.toFixed(6)}`);
console.log(` Base scanline rendering: ${currentScanlineHeight.toFixed(6)}px`);
console.log(` Will cause aliasing: ${willCauseAliasing}`);
if (viewport.isKiosk && isKnownKioskSize) {
// Calculate what our optimal scanline thickness would be
const targetRendered = 2.0; // We want 2px scanlines
const optimalThickness = targetRendered / (currentScale * viewport.devicePixelRatio);
console.log(`Optimal scanline thickness: ${optimalThickness.toFixed(6)}px`);
console.log(`Expected rendered height: ${(optimalThickness * currentScale * viewport.devicePixelRatio).toFixed(6)}px`);
}
if (recommendations.length > 0) {
console.log('Kiosk Optimization Status:');
recommendations.forEach((rec) => console.log(`${rec}`));
} else if (viewport.isKiosk) {
console.log('Custom kiosk configuration - using automatic optimization');
} else {
console.log('Not in kiosk mode - standard scaling applies');
}
console.groupEnd();
}
return {
viewport,
hasFractionalViewport,
isKnownKioskSize,
recommendations,
scaling: {
current: currentScale,
scanlineRendering: currentScanlineHeight,
willCauseAliasing,
},
};
};
// Make function globally available for debugging
window.analyzeViewportOptimization = analyzeViewportOptimization;
// Function to test fullscreen API capabilities
const testFullscreenCapabilities = () => {
const element = document.querySelector('#divTwc');
console.group('Fullscreen API Test');
const capabilities = {
requestFullscreen: !!element.requestFullscreen,
webkitRequestFullscreen: !!element.webkitRequestFullscreen,
mozRequestFullScreen: !!element.mozRequestFullScreen,
msRequestFullscreen: !!element.msRequestFullscreen,
fullscreenEnabled: !!document.fullscreenEnabled,
currentlyFullscreen: !!document.fullscreenElement,
};
console.log('API Support:', capabilities);
// Determine the best method
const requestMethod = element.requestFullscreen || element.webkitRequestFullscreen
|| element.mozRequestFullScreen || element.msRequestFullscreen;
if (requestMethod) {
console.log('Fullscreen API available');
console.log('Can attempt programmatic fullscreen for viewport optimization');
} else {
console.log('Fullscreen API not supported');
}
console.groupEnd();
return capabilities;
};
// Make function globally available for debugging
window.testFullscreenCapabilities = testFullscreenCapabilities;
// allow displays to register themselves
const registerDisplay = (display) => {
if (displays[display.navId]) console.warn(`Display nav ID ${display.navId} already in use`);

View File

@@ -7,12 +7,19 @@ const noSleep = (enable = false) => {
// get a nosleep controller
if (!noSleep.controller) noSleep.controller = new NoSleep();
// don't call anything if the states match
if (wakeLock === enable) return false;
if (wakeLock === enable) return Promise.resolve(false);
// store the value
wakeLock = enable;
// call the function
if (enable) return noSleep.controller.enable();
return noSleep.controller.disable();
if (enable) {
return noSleep.controller.enable().catch((error) => {
// Handle wake lock request failures gracefully
console.warn('Wake lock request failed:', error.message);
wakeLock = false;
return false;
});
}
return Promise.resolve(noSleep.controller.disable());
};
export default noSleep;

View File

@@ -1,7 +1,8 @@
/* REGULAR SCANLINES SETTINGS */
// width of 1 scanline (min.: 1px)
// width of 1 scanline (responsive units to prevent banding)
$scan-width: 1px;
$scan-width-scaled: 0.15vh; // viewport-relative unit for better scaling
// emulates a damage-your-eyes bad pre-2000 CRT screen ♥ (true, false)
$scan-crt: false;
@@ -75,7 +76,7 @@ $scan-opacity: .75;
@include scan-moving($scan-moving-line);
}
// the scanlines, so!
// the scanlines, so! - with responsive scaling for low-res displays
&:after {
top: 0;
right: 0;
@@ -87,6 +88,57 @@ $scan-opacity: .75;
$scan-color 51%);
background-size: 100% $scan-width*2;
@include scan-crt($scan-crt);
// Prevent sub-pixel aliasing on scaled displays
image-rendering: crisp-edges;
image-rendering: pixelated;
}
// Responsive scanlines for different display scenarios
// High DPI displays - use original sizing
@media (-webkit-min-device-pixel-ratio: 2),
(min-resolution: 192dpi) {
&:before {
height: $scan-width;
}
&:after {
background-size: 100% calc($scan-width * 2);
}
}
// Medium resolution displays (1024x768 and similar)
@media (max-width: 1200px) and (max-height: 900px) and (-webkit-max-device-pixel-ratio: 1.5) {
&:before {
height: calc($scan-width * 1.5);
}
&:after {
background-size: 100% calc($scan-width * 3);
}
}
// Low resolution displays - increase thickness to prevent banding
@media (max-width: 1024px) and (max-height: 768px) {
&:before {
height: calc($scan-width * 2);
}
&:after {
background-size: 100% calc($scan-width * 4);
}
}
// Very low resolution displays
@media (max-width: 800px) and (max-height: 600px) {
&:before {
height: calc($scan-width * 3);
}
&:after {
background-size: 100% calc($scan-width * 6);
}
}
}
@@ -103,4 +155,4 @@ $scan-opacity: .75;
background-position: 0 50%;
// bottom: 0%; // to have a continuous scanline move, use this line (here in 0% step) instead of transform and write, in &:before, { position: absolute; bottom: 100%; }
}
}
}