Add responsive scaling; improve scanlines and Mobile Safari support

- Replace CSS zoom with CSS transform scaling for better mobile compatibility
- Implement wrapper-based scaling approach that includes both content and navigation bar
- Replace Almanac layout with CSS Grid for better cross-browser layout
- Greatly improve scanline algorithm to handle a wide variety of displays
- Add setting to override automatic scanlines to user-specified scale factor
- Remove scanline scaling debug functions
- Refactor settings module: initialize settings upfront and improve change handler declarations
- Enhance scanline SCSS with repeating-linear-gradient for better performance
- Add app icon for iOS/iPadOS
- Add 'fullscreen' event listener
- De-bounce 'resize' event listener
- Add 'orientationchange' event listener
- Implement three resize scaling algorithms:
  - Baseline (when no scaling is needed, like on the index page)
  - Mobile scaling (except Mobile Safari kiosk mode)
  - Mobile Safari kiosk mode (using manual offset calculations)
  - Standard fullscreen/kiosk mode (using CSS centering)
This commit is contained in:
Eddy G
2025-06-28 13:05:04 -04:00
parent cc9e613ba7
commit b49433f5ff
17 changed files with 797 additions and 1008 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
@use 'shared/_colors'as c;
@use 'shared/_utils'as u;
@use 'shared/_colors' as c;
@use 'shared/_utils' as u;
#almanac-html.weather-display {
background-image: url('../images/backgrounds/3.png');
@@ -11,62 +11,57 @@
@include u.text-shadow();
.sun {
display: table;
margin-left: 50px;
height: 100px;
// Use CSS Grid for cross-browser consistency
// Grid is populated in reading order (left-to-right, top-to-bottom):
display: grid;
grid-template-columns: auto auto auto;
grid-template-rows: auto auto auto;
gap: 0px 90px;
margin: 3px auto 5px auto; // align the bottom of the div with the background
width: fit-content;
line-height: 30px;
&>div {
display: table-row;
.grid-item {
// Reset inherited styles that interfere with grid layout
width: auto;
height: auto;
padding: 0;
margin: 0;
position: relative;
&>div {
display: table-cell;
}
}
.days {
color: c.$column-header-text;
text-align: right;
top: -5px;
.day {
padding-right: 10px;
// Column headers (day names)
&.header {
color: c.$column-header-text;
text-align: center;
}
}
.times {
text-align: right;
.sun-time {
width: 200px;
// Row labels (Sunrise:, Sunset:)
&.row-label {
// color: c.$column-header-text; // screenshots show labels were white
text-align: right;
}
&.times-1 {
top: -10px;
}
&.times-2 {
top: -15px;
// Time values (sunrise/sunset)
&.time {
text-align: center;
}
}
}
.moon {
position: relative;
top: -10px;
padding: 0px 60px;
padding: 7px 50px;
line-height: 36px;
.title {
color: c.$column-header-text;
padding-left: 13px;
}
.day {
display: inline-block;
text-align: center;
width: 130px;
width: 132px;
.icon {
// shadow in image make it look off center
@@ -82,4 +77,4 @@
}
}

View File

@@ -1,5 +1,5 @@
@use 'shared/_colors'as c;
@use 'shared/_utils'as u;
@use 'shared/_colors' as c;
@use 'shared/_utils' as u;
.weather-display .main.current-weather {
&.main {
@@ -58,27 +58,19 @@
font-size: 24pt;
}
.condition {}
.icon {
height: 100px;
img {
max-width: 126px;
margin: 0 auto;
display: block;
}
}
.wind-container {
margin-bottom: 10px;
margin-left: 10px;
display: flex;
&>div {
width: 45%;
display: inline-block;
margin: 0px;
}
.wind-label {
margin-left: 5px;
width: 50%;
}
.wind {
@@ -87,7 +79,8 @@
}
.wind-gusts {
margin-left: 5px;
text-align: right;
font-size: 28px;
}
.location {
@@ -99,4 +92,4 @@
text-wrap: nowrap;
}
}
}
}

View File

@@ -9,6 +9,7 @@
body {
font-family: "Star4000";
margin: 0;
@media (prefers-color-scheme: dark) {
background-color: #000000;
@@ -23,13 +24,17 @@ body {
&.kiosk {
margin: 0px;
padding: 0px;
overflow: hidden;
width: 100vw;
// Always use black background in kiosk mode, regardless of light/dark preference
background-color: #000000 !important;
}
}
#divQuery {
max-width: 640px;
padding: 8px;
.buttons {
display: inline-block;
@@ -137,12 +142,26 @@ body {
color: #ffffff;
width: 100%;
max-width: 640px;
margin: 0; // Ensure edge-to-edge display
&.wide {
max-width: 854px;
}
}
.content-wrapper {
padding: 8px;
}
#divTwcMain {
width: 640px;
height: 480px;
.wide & {
width: 854px;
}
}
.kiosk #divTwc {
max-width: unset;
}
@@ -184,7 +203,11 @@ body {
background-color: #000000;
color: #ffffff;
width: 100%;
width: 640px;
.wide & {
width: 854px;
}
@media (prefers-color-scheme: dark) {
background-color: rgb(48, 48, 48);
@@ -196,25 +219,26 @@ body {
padding-left: 6px;
padding-right: 6px;
// scale down the buttons on narrower screens
// Use font-size scaling instead of zoom/transform to avoid layout gaps and preserve icon tap targets.
// While not semantically ideal, it works well for our fixed-layout design.
@media (max-width: 550px) {
zoom: 0.90;
font-size: 0.90em;
}
@media (max-width: 500px) {
zoom: 0.80;
font-size: 0.80em;
}
@media (max-width: 450px) {
zoom: 0.70;
font-size: 0.70em;
}
@media (max-width: 400px) {
zoom: 0.60;
font-size: 0.60em;
}
@media (max-width: 350px) {
zoom: 0.50;
font-size: 0.50em;
}
}
@@ -325,7 +349,6 @@ body {
// background-image: none;
width: unset;
height: unset;
transform-origin: unset;
}
#loading {
@@ -399,7 +422,8 @@ body {
label {
display: block;
max-width: 300px;
max-width: fit-content;
cursor: pointer;
.alert {
display: none;
@@ -414,6 +438,13 @@ body {
#divTwcBottom img {
transform: scale(0.75);
// Make icons larger in widescreen mode on mobile
@media (max-width: 550px) {
.wide & {
transform: scale(1.0); // Larger icons in widescreen
}
}
}
#divTwc:fullscreen,
@@ -446,9 +477,7 @@ body {
.kiosk {
#divTwc #divTwcBottom {
>div {
display: none;
}
display: none;
}
}

View File

@@ -1,5 +1,5 @@
@use 'shared/_colors'as c;
@use 'shared/_utils'as u;
@use 'shared/_colors' as c;
@use 'shared/_utils' as u;
.weather-display .progress {
@include u.text-shadow();
@@ -13,6 +13,7 @@
box-sizing: border-box;
height: 310px;
overflow: hidden;
line-height: 28px;
.item {
position: relative;
@@ -117,4 +118,4 @@
transition: width 1s steps(6);
}
}
}
}

View File

@@ -1,5 +1,5 @@
@use 'shared/_colors'as c;
@use 'shared/_utils'as u;
@use 'shared/_colors' as c;
@use 'shared/_utils' as u;
.weather-display .main.travel {
&.main {
@@ -8,14 +8,11 @@
.column-headers {
background-color: c.$column-header;
height: 20px;
position: absolute;
width: 100%;
}
.column-headers {
position: sticky;
top: 0px;
width: 100%;
z-index: 5;
overflow: hidden; // prevent thin gaps between header and content
div {
display: inline-block;
@@ -100,4 +97,4 @@
}
}
}
}
}

View File

@@ -94,11 +94,13 @@
&.has-scroll {
width: 640px;
margin-top: 0;
height: 310px;
overflow: hidden;
&.no-header {
height: 400px;
margin-top: 0; // Reset for no-header case since the gap issue is header-related
}
}

View File

@@ -83,10 +83,12 @@ $scan-opacity: .75;
bottom: 0;
left: 0;
z-index: $scan-z-index;
background: linear-gradient(to bottom,
transparent 50%,
$scan-color 51%);
background-size: 100% $scan-width*2;
// repeating-linear-gradient is more efficient than linear-gradient+background-size because it doesn't require the browser to calculate tiling
background: repeating-linear-gradient(to bottom,
transparent 0,
transparent $scan-width,
$scan-color $scan-width,
$scan-color calc($scan-width * 2));
@include scan-crt($scan-crt);
// Prevent sub-pixel aliasing on scaled displays
@@ -94,51 +96,21 @@ $scan-opacity: .75;
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);
}
// Scanlines use dynamic thickness calculated by JavaScript
// JavaScript calculates optimal thickness to prevent banding at any scale factor
// The --scanline-thickness custom property is set by applyScanlineScaling()
// The modes (hairline, thin, medium, thick) force the base thickness selection
// Some modes may appear the same (e.g. hairline and thin) depending on the display
&:before {
height: var(--scanline-thickness, $scan-width);
}
// 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);
}
&:after {
background: repeating-linear-gradient(to bottom,
transparent 0,
transparent var(--scanline-thickness, $scan-width),
$scan-color var(--scanline-thickness, $scan-width),
$scan-color calc(var(--scanline-thickness, $scan-width) * 2));
}
}