-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/js/main.js b/js/main.js
index 20901d2..dee440e 100644
--- a/js/main.js
+++ b/js/main.js
@@ -414,7 +414,7 @@ class Player extends PrimaryView {
// 1: turn-based mode, at the start of a tic
// 2: turn-based mode, in mid-tic, with the game frozen waiting for input
this.turn_mode = 0;
- this.turn_based_checkbox = this.root.querySelector('.controls .control-turn-based');
+ this.turn_based_checkbox = this.root.querySelector('.control-turn-based');
this.turn_based_checkbox.checked = false;
this.turn_based_checkbox.addEventListener('change', ev => {
if (this.turn_based_checkbox.checked) {
@@ -432,19 +432,19 @@ class Player extends PrimaryView {
});
// Bind buttons
- this.pause_button = this.root.querySelector('.controls .control-pause');
+ this.pause_button = this.root.querySelector('.control-pause');
this.pause_button.addEventListener('click', ev => {
this.toggle_pause();
ev.target.blur();
});
- this.restart_button = this.root.querySelector('.controls .control-restart');
+ this.restart_button = this.root.querySelector('.control-restart');
this.restart_button.addEventListener('click', ev => {
new ConfirmOverlay(this.conductor, "Abandon this attempt and try again?", () => {
this.restart_level();
}).open();
ev.target.blur();
});
- this.undo_button = this.root.querySelector('.controls .control-undo');
+ this.undo_button = this.root.querySelector('.control-undo');
this.undo_button.addEventListener('click', ev => {
let player_cell = this.level.player.cell;
// Keep undoing until (a) we're on another cell and (b) we're not sliding, i.e. we're
@@ -469,7 +469,7 @@ class Player extends PrimaryView {
this._redraw();
ev.target.blur();
});
- this.rewind_button = this.root.querySelector('.controls .control-rewind');
+ this.rewind_button = this.root.querySelector('.control-rewind');
this.rewind_button.addEventListener('click', ev => {
if (this.state === 'rewinding') {
this.set_state('playing');
@@ -480,19 +480,19 @@ class Player extends PrimaryView {
});
// Game actions
// TODO do these need buttons?? feel like they're not discoverable otherwise
- this.drop_button = this.root.querySelector('.actions .action-drop');
+ this.drop_button = this.root.querySelector('#player-actions .action-drop');
this.drop_button.addEventListener('click', ev => {
// Use the set of "buttons pressed between tics" because it's cleared automatically;
// otherwise these will stick around forever
this.current_keys_new.add('q');
ev.target.blur();
});
- this.cycle_button = this.root.querySelector('.actions .action-cycle');
+ this.cycle_button = this.root.querySelector('#player-actions .action-cycle');
this.cycle_button.addEventListener('click', ev => {
this.current_keys_new.add('e');
ev.target.blur();
});
- this.swap_button = this.root.querySelector('.actions .action-swap');
+ this.swap_button = this.root.querySelector('#player-actions .action-swap');
this.swap_button.addEventListener('click', ev => {
this.current_keys_new.add('c');
ev.target.blur();
@@ -1878,58 +1878,41 @@ class Player extends PrimaryView {
if (style['display'] === 'none')
return;
- let is_portrait = !! style.getPropertyValue('--is-portrait');
+ let is_portrait = window.matchMedia('(orientation: portrait)').matches;
// The base size is the size of the canvas, i.e. the viewport size times the tile size --
- // but note that we have 2x4 extra tiles for the inventory depending on layout
+ // but note that we have 2x4 extra tiles for the inventory depending on layout, plus half a
+ // tile's worth of padding around the game area, plus a quarter tile spacing
let base_x, base_y;
if (is_portrait) {
- base_x = this.renderer.tileset.size_x * this.renderer.viewport_size_x;
- base_y = this.renderer.tileset.size_y * (this.renderer.viewport_size_y + 2);
+ base_x = this.renderer.tileset.size_x * (this.renderer.viewport_size_x + 0.5);
+ base_y = this.renderer.tileset.size_y * (this.renderer.viewport_size_y + 2.75);
}
else {
- base_x = this.renderer.tileset.size_x * (this.renderer.viewport_size_x + 4);
- base_y = this.renderer.tileset.size_y * this.renderer.viewport_size_y;
+ base_x = this.renderer.tileset.size_x * (this.renderer.viewport_size_x + 4.75);
+ base_y = this.renderer.tileset.size_y * (this.renderer.viewport_size_y + 0.5);
}
- // Unfortunately, finding the available space is a little tricky. The container is a CSS
- // flex item, and the flex cell doesn't correspond directly to any element, so there's no
- // way for us to query its size directly. We also have various stuff up top and down below
- // that shouldn't count as available space. So instead we take a rough guess by adding up:
- // - the space currently taken up by the canvas
- let avail_x = this.renderer.canvas.offsetWidth;
- let avail_y = this.renderer.canvas.offsetHeight;
- // - the space currently taken up by the inventory, depending on orientation
- if (is_portrait) {
- avail_y += this.inventory_el.offsetHeight;
- }
- else {
- avail_x += this.inventory_el.offsetWidth;
- }
- // - the difference between the size of the play area and the size of our root (which will
- // add in any gap around the player, e.g. if the controls stretch the root to be wider)
- let root_rect = this.root.getBoundingClientRect();
- let player_rect = this.root.querySelector('#player-game-area').getBoundingClientRect();
- avail_x += root_rect.width - player_rect.width;
- avail_y += root_rect.height - player_rect.height;
+ // The element hierarchy is: the root is a wrapper that takes up the entire flex cell;
+ // within that is the main player element which contains everything; and within that is the
+ // game area which is the part we can scale. The available space is the size of the root,
+ // but minus the size of the controls and whatnot placed around it, which are the difference
+ // between the player container and the game area
+ let player = this.root.querySelector('#player-main');
+ let game_area = this.root.querySelector('#player-game-area');
+ let avail_x = this.root.offsetWidth - (player.offsetWidth - game_area.offsetWidth);
+ let avail_y = this.root.offsetHeight - (player.offsetHeight - game_area.offsetHeight);
// ...minus the width of the debug panel, if visible
if (this.debug.enabled) {
avail_x -= this.root.querySelector('#player-debug').getBoundingClientRect().width;
}
- // - the margins around our root, which consume all the extra space
- let margin_x = parseFloat(style['margin-left']) + parseFloat(style['margin-right']);
- let margin_y = parseFloat(style['margin-top']) + parseFloat(style['margin-bottom']);
- avail_x += margin_x;
- avail_y += margin_y;
- // If those margins are zero, by the way, we risk being too big for the viewport already,
- // and we need to subtract any extra scroll on the body
- if (margin_x === 0 || margin_y === 0) {
- avail_x -= document.body.scrollWidth - document.body.clientWidth;
- avail_y -= document.body.scrollHeight - document.body.clientHeight;
- }
+ // If there's already a scrollbar, the extra scrolled space is unavailable
+ avail_x -= Math.max(0, document.body.scrollWidth - document.body.clientWidth);
+ avail_y -= Math.max(0, document.body.scrollHeight - document.body.clientHeight);
let dpr = window.devicePixelRatio || 1.0;
- // Divide to find the biggest scale that still fits. But don't exceed 90% of the available
- // space, or it'll feel cramped (except on small screens, where being too small HURTS).
- let maxfrac = is_portrait ? 1 : 0.9;
+ // Divide to find the biggest scale that still fits. Leave a LITTLE wiggle room for pixel
+ // rounding and breathing (except on small screens, where being too small REALLY hurts), but
+ // not too much since there's already a flex gap between the game and header/footer
+ let maxfrac = is_portrait ? 1 : 0.99;
let scale = Math.floor(maxfrac * dpr * Math.min(avail_x / base_x, avail_y / base_y));
if (scale <= 1) {
scale = 1;
@@ -3534,9 +3517,9 @@ class Conductor {
if (this.current) {
this.current.deactivate();
}
- this.splash.activate();
this.current = this.splash;
document.body.setAttribute('data-mode', 'splash');
+ this.splash.activate();
}
switch_to_editor() {
@@ -3547,9 +3530,9 @@ class Conductor {
this.editor.load_level(this.stored_level);
this.loaded_in_editor = true;
}
- this.editor.activate();
this.current = this.editor;
document.body.setAttribute('data-mode', 'editor');
+ this.editor.activate();
}
switch_to_player() {
@@ -3560,9 +3543,11 @@ class Conductor {
this.player.load_level(this.stored_level);
this.loaded_in_player = true;
}
- this.player.activate();
this.current = this.player;
document.body.setAttribute('data-mode', 'player');
+ // Activate last, so any DOM inspection (ahem, auto-scaling) already sees the effects of
+ // data-mode revealing the header
+ this.player.activate();
}
reload_all_options() {
diff --git a/style.css b/style.css
index d35af06..c86b6a9 100644
--- a/style.css
+++ b/style.css
@@ -5,6 +5,7 @@ html {
body {
height: 100%;
margin: 0;
+ box-sizing: border-box;
display: flex;
flex-direction: column;
@@ -201,8 +202,8 @@ svg.svg-icon {
.radio-faux-button-set > label > input:checked + span {
background: hsl(225, 80%, 50%);
box-shadow:
- inset 0 1px 0 1px hsl(225, 10%, 20%),
- inset 0 -0em 2em 0.5em hsl(225, 50%, 30%),
+ inset 0 0 1px 1px hsl(225, 50%, 40%),
+ inset 0 -0.125em 0.5em 0.25em hsl(225, 50%, 30%),
0 1px 1px hsl(225, 10%, 10%);
}
@@ -433,7 +434,7 @@ body > header {
align-items: center;
gap: 0.5em;
- padding: 0.5em;
+ padding: 0.25em 0.5em;
line-height: 1.125;
}
body > header h1 {
@@ -453,6 +454,7 @@ body > header > nav {
}
body > header button {
font-size: 0.75em;
+ white-space: nowrap;
}
body > header h1 a {
color: inherit !important;
@@ -899,15 +901,149 @@ ol.packtest-summary > li {
* debug panel can use that to sit against the right edge; absolute positioning excludes
* margins, so if it were positioned as a child of THIS element, it would be stuffed into the
* game area (oops!) */
- display: flex;
- flex-direction: column;
+ /* It does also make auto-sizing easier! */
+ /* Default to a landscape layout, with the buttons on the left */
+ display: grid;
+ grid:
+ "buttons game actions"
+ "buttons game actions"
+ ". music music"
+ / 1fr auto 1fr
+ ;
justify-content: stretch;
gap: 0.5em;
margin: auto; /* center in both directions baby */
}
+#player-controls,
+#player-actions {
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ gap: 0.25em;
+}
+#player-controls {
+ grid-area: buttons;
+ align-self: start;
+}
+#player-controls button,
+#player-actions button svg {
+ font-size: 1.5em;
+}
+#player-controls button {
+ padding: 0.5em;
+ line-height: 1;
+}
+#player-controls .radio-faux-button-set {
+ margin: 0;
+}
+#player-actions {
+ grid-area: actions;
+ align-self: end;
+}
+#player-actions button {
+ text-align: center;
+}
+#player-actions button svg {
+ display: block;
+ margin: 0.125em auto;
+}
+#player-music {
+ grid-area: music;
+ margin: 0 calc(var(--tile-width) * var(--scale) / 4);
+ text-transform: lowercase;
+ color: #909090;
+}
+/* Key hints are placed on the sides */
+#player button {
+ position: relative;
+}
+#player button .keyhint {
+ font-size: 1rem;
+ position: absolute;
+ top: 1.25em;
+ margin: auto;
+ color: #404040;
+}
+#player-controls button .keyhint {
+ left: -2em;
+}
+#player-actions button .keyhint {
+ right: -2em;
+}
+#player button:disabled .keyhint {
+ display: none;
+}
+@media (orientation: portrait) {
+ /* On a portrait screen, put the controls on top */
+ #player-main {
+ grid:
+ "buttons actions"
+ "game game"
+ "music music"
+ ;
+ }
+ #player-controls,
+ #player-actions {
+ flex-direction: row;
+ white-space: nowrap;
+ }
+ #player-controls button,
+ #player-actions button svg {
+ font-size: 1em;
+ }
+ #player-controls button {
+ padding: 0.25em 0.5em;
+ line-height: 1.33;
+ }
+ /* Hackily remove the
s in "turn based mode" */ + #player-controls .radio-faux-button-set br { + display: none; + } + #player-actions { + justify-content: end; + } + #player-actions button svg { + display: inline-block; + margin: 0.125em; + } + #player button .keyhint { + top: -2em; + left: 0; + right: 0; + } +} +@media (orientation: portrait) and (max-width: 800px) { + /* On a /small/ portrait screen, also put the controls in two rows */ + #player-main { + grid: + "buttons" + "actions" + "game" + "music" + ; + } + #player-controls, + #player-actions { + justify-content: center; + } + #player .keyhint { + /* Hide key hints; there's nowhere to put them and they take up surprisingly a lot of space */ + display: none; + } +} +@media (max-width: 800px) { + #player-music { + font-size: 0.875em; + } +} + #player-game-area { + grid-area: game; + /* don't stretch if the buttons or music blow out somehow */ + justify-self: center; + align-self: center; + isolation: isolate; - align-self: center; /* don't stretch if the buttons or music blow out somehow */ display: grid; align-items: center; grid: @@ -919,22 +1055,19 @@ ol.packtest-summary > li { /* Need explicit min-content to force the hint to wrap */ / min-content min-content ; - column-gap: 2em; - row-gap: 0.5em; + column-gap: calc(var(--tile-width) * var(--scale) / 4); + row-gap: calc(var(--tile-height) * var(--scale) / 4); - padding: 1em; + padding: calc(var(--tile-height) * var(--scale) / 4) calc(var(--tile-width) * var(--scale) / 4); background: hsl(225, 10%, 20%); box-shadow: 0 0.25em 1em black; } -#player > .controls { - order: -1; -} .level { grid-area: level; position: relative; - border: 2px solid black; + outline: 2px solid black; } .level canvas { display: block; @@ -956,8 +1089,6 @@ ol.packtest-summary > li { height: 0; min-height: 100%; box-sizing: border-box; - /* Copy the canvas's border width too */ - border: 2px solid transparent; z-index: 2; font-size: calc(0.5 * var(--tile-width) * var(--scale)); @@ -1080,7 +1211,7 @@ dl.score-chart .-sum { .time h3, .bonus h3 { flex: 1; - font-size: 1.25em; + font-size: 1.5em; line-height: 1; color: hsl(225, 20%, 90%); } @@ -1148,6 +1279,8 @@ dl.score-chart .-sum { * parent happens to be. Magic! */ height: 0; min-height: 100%; + width: 0; + min-width: 100%; } #player-game-area > .player-hint-wrapper.--visible { display: initial; @@ -1185,50 +1318,29 @@ dl.score-chart .-sum { background: #0009; color: white; } -#player .actions { - display: flex; - gap: 0.5em; -} -#player .actions button { - flex: 1; - white-space: nowrap; -} -#player-music { - grid-area: music; - margin: 0 1em; - text-transform: lowercase; - color: #909090; -} - -#player .controls { - grid-area: controls; - display: flex; - gap: 0.25em; - justify-content: space-between; -} -#player button { - position: relative; -} -#player button .keyhint { - position: absolute; - left: 0; - right: 0; - top: -2em; - margin: auto; - color: #404040; -} -#player button:disabled .keyhint { - display: none; -} - -.play-controls { - display: flex; - align-items: center; - gap: 0.25em; -} -.play-controls { - align-self: start; +@media (orientation: portrait) { + #player-game-area { + /* Rearrange the grid to be vertical */ + grid: + "level level" + "chips inventory" calc((var(--tile-height) * var(--scale) * 2 - 1em) / 3) + "time inventory" calc((var(--tile-height) * var(--scale) * 2 - 1em) / 3) + "bonus inventory" calc((var(--tile-height) * var(--scale) * 2 - 1em) / 3) + / 1fr min-content + ; + } + #player .inventory { + /* stick me in the center right */ + place-self: center end; + } + #player-game-area > .player-hint-wrapper { + /* Overlay hints on the inventory area */ + grid-row: chips / bonus; + grid-column: level; + z-index: 1; + font-size: calc(var(--tile-height) * var(--scale) / 2.5); + } } /* Debug stuff */ @@ -1413,56 +1525,6 @@ body.--debug #player-debug { } -@media (max-width: 800px) { - #player { - /* sentinel for js */ - --is-portrait: 1; - /* The play area isn't necessarily the biggest thing any more, and it's ugly when stretched */ - align-items: center; - } - #player .controls { - flex-direction: column; - } - #player-game-area { - /* Rearrange the grid to be vertical */ - grid: - "level level" - "chips inventory" calc((var(--tile-height) * var(--scale) * 2 - 1em) / 3) - "time inventory" calc((var(--tile-height) * var(--scale) * 2 - 1em) / 3) - "bonus inventory" calc((var(--tile-height) * var(--scale) * 2 - 1em) / 3) - /* FIXME ideally the first column would be 1fr so the hearts/time have space, but that - * allows hints to grow to the entire width of the window, which incredibly sucks. i - * don't know how to get around this except by giving the grid a fixed width, which i - * guess wouldn't be that hard */ - / min-content min-content - ; - row-gap: 0.5em; - column-gap: 1em; - - padding: 0.5em; - } - #player .inventory { - /* stick me in the center right */ - place-self: center end; - } - #player-game-area > .player-hint-wrapper { - /* Overlay hints on the inventory area */ - grid-row: chips / bonus; - grid-column: level; - z-index: 1; - font-size: calc(var(--tile-height) * var(--scale) / 2.5); - } - #player .keyhint { - /* Hide key hints, they take up surprisingly a lot of space */ - display: none; - } - #player-music { - /* Stack the title/artist on the volume, since they don't fit well side by side */ - font-size: 0.875em; - } -} - - /**************************************************************************************************/ /* Editor */
s in "turn based mode" */ + #player-controls .radio-faux-button-set br { + display: none; + } + #player-actions { + justify-content: end; + } + #player-actions button svg { + display: inline-block; + margin: 0.125em; + } + #player button .keyhint { + top: -2em; + left: 0; + right: 0; + } +} +@media (orientation: portrait) and (max-width: 800px) { + /* On a /small/ portrait screen, also put the controls in two rows */ + #player-main { + grid: + "buttons" + "actions" + "game" + "music" + ; + } + #player-controls, + #player-actions { + justify-content: center; + } + #player .keyhint { + /* Hide key hints; there's nowhere to put them and they take up surprisingly a lot of space */ + display: none; + } +} +@media (max-width: 800px) { + #player-music { + font-size: 0.875em; + } +} + #player-game-area { + grid-area: game; + /* don't stretch if the buttons or music blow out somehow */ + justify-self: center; + align-self: center; + isolation: isolate; - align-self: center; /* don't stretch if the buttons or music blow out somehow */ display: grid; align-items: center; grid: @@ -919,22 +1055,19 @@ ol.packtest-summary > li { /* Need explicit min-content to force the hint to wrap */ / min-content min-content ; - column-gap: 2em; - row-gap: 0.5em; + column-gap: calc(var(--tile-width) * var(--scale) / 4); + row-gap: calc(var(--tile-height) * var(--scale) / 4); - padding: 1em; + padding: calc(var(--tile-height) * var(--scale) / 4) calc(var(--tile-width) * var(--scale) / 4); background: hsl(225, 10%, 20%); box-shadow: 0 0.25em 1em black; } -#player > .controls { - order: -1; -} .level { grid-area: level; position: relative; - border: 2px solid black; + outline: 2px solid black; } .level canvas { display: block; @@ -956,8 +1089,6 @@ ol.packtest-summary > li { height: 0; min-height: 100%; box-sizing: border-box; - /* Copy the canvas's border width too */ - border: 2px solid transparent; z-index: 2; font-size: calc(0.5 * var(--tile-width) * var(--scale)); @@ -1080,7 +1211,7 @@ dl.score-chart .-sum { .time h3, .bonus h3 { flex: 1; - font-size: 1.25em; + font-size: 1.5em; line-height: 1; color: hsl(225, 20%, 90%); } @@ -1148,6 +1279,8 @@ dl.score-chart .-sum { * parent happens to be. Magic! */ height: 0; min-height: 100%; + width: 0; + min-width: 100%; } #player-game-area > .player-hint-wrapper.--visible { display: initial; @@ -1185,50 +1318,29 @@ dl.score-chart .-sum { background: #0009; color: white; } -#player .actions { - display: flex; - gap: 0.5em; -} -#player .actions button { - flex: 1; - white-space: nowrap; -} -#player-music { - grid-area: music; - margin: 0 1em; - text-transform: lowercase; - color: #909090; -} - -#player .controls { - grid-area: controls; - display: flex; - gap: 0.25em; - justify-content: space-between; -} -#player button { - position: relative; -} -#player button .keyhint { - position: absolute; - left: 0; - right: 0; - top: -2em; - margin: auto; - color: #404040; -} -#player button:disabled .keyhint { - display: none; -} - -.play-controls { - display: flex; - align-items: center; - gap: 0.25em; -} -.play-controls { - align-self: start; +@media (orientation: portrait) { + #player-game-area { + /* Rearrange the grid to be vertical */ + grid: + "level level" + "chips inventory" calc((var(--tile-height) * var(--scale) * 2 - 1em) / 3) + "time inventory" calc((var(--tile-height) * var(--scale) * 2 - 1em) / 3) + "bonus inventory" calc((var(--tile-height) * var(--scale) * 2 - 1em) / 3) + / 1fr min-content + ; + } + #player .inventory { + /* stick me in the center right */ + place-self: center end; + } + #player-game-area > .player-hint-wrapper { + /* Overlay hints on the inventory area */ + grid-row: chips / bonus; + grid-column: level; + z-index: 1; + font-size: calc(var(--tile-height) * var(--scale) / 2.5); + } } /* Debug stuff */ @@ -1413,56 +1525,6 @@ body.--debug #player-debug { } -@media (max-width: 800px) { - #player { - /* sentinel for js */ - --is-portrait: 1; - /* The play area isn't necessarily the biggest thing any more, and it's ugly when stretched */ - align-items: center; - } - #player .controls { - flex-direction: column; - } - #player-game-area { - /* Rearrange the grid to be vertical */ - grid: - "level level" - "chips inventory" calc((var(--tile-height) * var(--scale) * 2 - 1em) / 3) - "time inventory" calc((var(--tile-height) * var(--scale) * 2 - 1em) / 3) - "bonus inventory" calc((var(--tile-height) * var(--scale) * 2 - 1em) / 3) - /* FIXME ideally the first column would be 1fr so the hearts/time have space, but that - * allows hints to grow to the entire width of the window, which incredibly sucks. i - * don't know how to get around this except by giving the grid a fixed width, which i - * guess wouldn't be that hard */ - / min-content min-content - ; - row-gap: 0.5em; - column-gap: 1em; - - padding: 0.5em; - } - #player .inventory { - /* stick me in the center right */ - place-self: center end; - } - #player-game-area > .player-hint-wrapper { - /* Overlay hints on the inventory area */ - grid-row: chips / bonus; - grid-column: level; - z-index: 1; - font-size: calc(var(--tile-height) * var(--scale) / 2.5); - } - #player .keyhint { - /* Hide key hints, they take up surprisingly a lot of space */ - display: none; - } - #player-music { - /* Stack the title/artist on the volume, since they don't fit well side by side */ - font-size: 0.875em; - } -} - - /**************************************************************************************************/ /* Editor */