Rework mobile layout to be more compact, et al.
- On small screens, the top two headers (with the pack + level names) are now removed; instead the pack and level name are shown when starting each level, and the buttons from those headers are moved into a pause menu. - The options, compat, and level browser dialogs were all reworked to fit better on narrow screens. - The level overlay has a more consistent layout and tries harder to not draw in the middle, where the player generally is (except that the mobile pause menu goes there, but oh well). - The score tally at the end of a level is now less of a small table and more of... more numbers, I guess? - Links to the music source and author now open in a new window to reduce risk of accidentally clicking them and losing your progress. - A few obituaries were shortened, and several more were added. - The game ending screen is now accessible on a touchscreen (oops). - The pause and rewind buttons visually indicate when you're in that mode, suggesting you can hit them again to switch to normal play. - Touch controls are now relative to the player and only apply within the game viewport. - Disabled buttons look a bit less janky. Still some work to do on this, but it's a pretty solid start.
This commit is contained in:
parent
8b03d09c78
commit
41e5b5f9b8
28
index.html
28
index.html
@ -5,6 +5,7 @@
|
||||
<title>Lexy's Labyrinth</title>
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
<link rel="shortcut icon" type="image/png" href="icon.png">
|
||||
<link rel="manifest" type="application/json" href="manifest.json">
|
||||
<script>
|
||||
"use strict";
|
||||
{
|
||||
@ -73,6 +74,12 @@
|
||||
<g id="svg-icon-menu-chevron">
|
||||
<path d="M2,4 l6,6 l6,-6 v3 l-6,6 l-6,-6 z"></path>
|
||||
</g>
|
||||
<g id="svg-icon-prev">
|
||||
<path d="M14,1 2,8 14,14 z">
|
||||
</g>
|
||||
<g id="svg-icon-next">
|
||||
<path d="M2,1 14,8 2,14 z">
|
||||
</g>
|
||||
<!-- Actions -->
|
||||
<g id="svg-icon-up">
|
||||
<path d="M0,12 l8,-8 l8,8 z"></path>
|
||||
@ -116,8 +123,8 @@
|
||||
<h1><a href="https://github.com/eevee/lexys-labyrinth">Lexy's Labyrinth</a></h1>
|
||||
<p>— an <a href="https://github.com/eevee/lexys-labyrinth">open source</a> game by <a href="https://eev.ee/">eevee</a></p>
|
||||
<nav>
|
||||
<button id="main-compat" type="button">mode: <output>lexy</output></button>
|
||||
<button id="main-options" type="button">options</button>
|
||||
<button id="main-compat" type="button">compat mode: <output>lexy</output></button>
|
||||
</nav>
|
||||
</header>
|
||||
<header id="header-pack">
|
||||
@ -133,11 +140,11 @@
|
||||
<h3 id="level-name">Level 1 — Key Pyramid</h3>
|
||||
<nav>
|
||||
<button id="main-prev-level" type="button">
|
||||
<svg class="svg-icon" viewBox="0 0 16 16" title="previous"><path d="M14,1 2,8 14,14 z"></svg>
|
||||
<svg class="svg-icon" viewBox="0 0 16 16" title="previous"><use href="#svg-icon-prev"></svg>
|
||||
</button>
|
||||
<button id="main-choose-level" type="button">Level select</button>
|
||||
<button id="main-next-level" type="button">
|
||||
<svg class="svg-icon" viewBox="0 0 16 16" title="next"><path d="M2,1 14,8 2,14 z"></svg>
|
||||
<svg class="svg-icon" viewBox="0 0 16 16" title="next"><use href="#svg-icon-next"></svg>
|
||||
</button>
|
||||
</nav>
|
||||
</header>
|
||||
@ -229,7 +236,7 @@
|
||||
<svg class="svg-icon" viewBox="0 0 16 16"><path d="M1,8 7,2 7,14 z M9,8 15,2 15,14 z"></path></svg>
|
||||
<span class="-optional-label">rewind</span> <span class="keyhint"><kbd>z</kbd></span></button>
|
||||
<div class="radio-faux-button-set">
|
||||
<label><input class="control-turn-based" type="checkbox"> <span>Turn <br>based <br>mode</span></label>
|
||||
<label><input class="control-turn-based" type="checkbox"> <span>Step <br>mode</span></label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="player-actions">
|
||||
@ -245,16 +252,15 @@
|
||||
</div>
|
||||
<section id="player-game-area">
|
||||
<div class="level"><!-- level canvas and any overlays go here --></div>
|
||||
<div class="overlay-message">
|
||||
<h1 class="-top"></h1>
|
||||
<div class="-middle"></div>
|
||||
<p class="-bottom"></p>
|
||||
<p class="-keyhint"></p>
|
||||
</div>
|
||||
<div class="player-overlay-message"></div>
|
||||
<div class="player-hint-wrapper">
|
||||
<div class="player-hint"></div>
|
||||
<svg class="player-hint-bg-icon svg-icon" viewBox="0 0 16 16"><use href="#svg-icon-hint"></use></svg>
|
||||
</div>
|
||||
<div class="player-level-number">
|
||||
Level
|
||||
<output></output>
|
||||
</div>
|
||||
<div class="chips">
|
||||
<h3>
|
||||
<svg class="svg-icon" viewBox="0 0 16 16" title="Hearts">
|
||||
@ -304,7 +310,7 @@
|
||||
<div class="inventory"></div>
|
||||
</section>
|
||||
<div id="player-music">
|
||||
🎵 <a id="player-music-title">title</a> by <a id="player-music-author">author</a>
|
||||
🎵 <a id="player-music-title" target="_blank">title</a> by <a id="player-music-author" target="_blank">author</a>
|
||||
<audio loop preload="auto">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -127,8 +127,8 @@ export const PICKUP_PRIORITIES = {
|
||||
|
||||
export const COMPAT_RULESET_LABELS = {
|
||||
lexy: "Lexy",
|
||||
steam: "Steam/CC2",
|
||||
'steam-strict': "Steam/CC2 (strict)",
|
||||
steam: "Steam",
|
||||
'steam-strict': "Steam (strict)",
|
||||
lynx: "Lynx",
|
||||
ms: "Microsoft",
|
||||
custom: "Custom",
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { mk } from './util.js';
|
||||
import { mk, mk_svg } from './util.js';
|
||||
|
||||
// Superclass for the main display modes: the player, the editor, and the splash screen
|
||||
export class PrimaryView {
|
||||
@ -285,6 +285,13 @@ export function flash_button(button) {
|
||||
}, 500);
|
||||
}
|
||||
|
||||
export function svg_icon(name) {
|
||||
return mk_svg(
|
||||
'svg.svg-icon',
|
||||
{viewBox: '0 0 16 16'},
|
||||
mk_svg('use', {href: `#svg-icon-${name}`}));
|
||||
}
|
||||
|
||||
export function load_json_from_storage(key) {
|
||||
return JSON.parse(window.localStorage.getItem(key));
|
||||
}
|
||||
|
||||
531
js/main.js
531
js/main.js
@ -8,7 +8,7 @@ import * as dat from './format-dat.js';
|
||||
import * as format_base from './format-base.js';
|
||||
import * as format_tws from './format-tws.js';
|
||||
import { Level } from './game.js';
|
||||
import { PrimaryView, DialogOverlay, ConfirmOverlay, flash_button, load_json_from_storage, save_json_to_storage } from './main-base.js';
|
||||
import { PrimaryView, DialogOverlay, ConfirmOverlay, flash_button, svg_icon, load_json_from_storage, save_json_to_storage } from './main-base.js';
|
||||
import { Editor } from './editor/main.js';
|
||||
import CanvasRenderer from './renderer-canvas.js';
|
||||
import SOUNDTRACK from './soundtrack.js';
|
||||
@ -59,44 +59,44 @@ const OBITUARIES = {
|
||||
drowned: [
|
||||
"you tried out water cooling",
|
||||
"you fell into the c",
|
||||
"water disaster",
|
||||
"water disaster!",
|
||||
"you sank like a rock",
|
||||
"your stack overflowed",
|
||||
],
|
||||
burned: [
|
||||
"your core temp got too high",
|
||||
"your plans went up in smoke",
|
||||
"you got roasted",
|
||||
"you held your feet to the fire",
|
||||
"you really blazed through that one",
|
||||
"you turned up the heat",
|
||||
],
|
||||
slimed: [
|
||||
"what an oozefest",
|
||||
"you mutated",
|
||||
"quite a sticky situation",
|
||||
"you got dunked in the gunk",
|
||||
"you were garbage collected",
|
||||
"that'll leave a stain",
|
||||
"what a waste",
|
||||
],
|
||||
exploded: [
|
||||
//"
|
||||
"looks like you're having a blast",
|
||||
"you tripped over something of mine",
|
||||
"you were blown to bits",
|
||||
"you blew it",
|
||||
"you're having a blast",
|
||||
"you became 64 bits",
|
||||
"you will surely be mist",
|
||||
"try not to trip",
|
||||
],
|
||||
squished: [
|
||||
"that block of ram was too much for you",
|
||||
"you encountered a block of ram",
|
||||
"you became two-dimensional",
|
||||
"you're a little flat, not too sharp",
|
||||
"your hit box collided",
|
||||
"nice compression ratio",
|
||||
//"
|
||||
"you took a cube route",
|
||||
],
|
||||
time: [
|
||||
"you tried to overclock",
|
||||
"you lost track of time",
|
||||
"your speedrun went badly",
|
||||
"you're feeling quite alarmed",
|
||||
//"
|
||||
"you overslept",
|
||||
"you got ticked off",
|
||||
],
|
||||
electrocuted: [
|
||||
"a shocking revelation",
|
||||
@ -119,48 +119,48 @@ const OBITUARIES = {
|
||||
"you're having a ball",
|
||||
"you'll bounce back from this",
|
||||
"should've gone the other way",
|
||||
//"
|
||||
//"
|
||||
"ping? pong!",
|
||||
//"",
|
||||
],
|
||||
walker: [
|
||||
"you let it walk all over you",
|
||||
"step into, step over, step out",
|
||||
"don't just wander around at random",
|
||||
//"
|
||||
//"
|
||||
"you wandered around at random",
|
||||
//"",
|
||||
//"",
|
||||
],
|
||||
fireball: [
|
||||
"you had a meltdown",
|
||||
"you haven't been flamed like that since usenet",
|
||||
//"
|
||||
//"
|
||||
//"
|
||||
"watch your core temp",
|
||||
"you got roasted",
|
||||
"you lost the flamewar",
|
||||
"goodness gracious",
|
||||
],
|
||||
glider: [
|
||||
"your ship came in",
|
||||
"don't worry, everything's fin now",
|
||||
"everything turned out fin",
|
||||
"should've given it a wider berth",
|
||||
"watch out for that skipper",
|
||||
//"
|
||||
"don't harbor any resentment",
|
||||
],
|
||||
tank_blue: [
|
||||
"you didn't watch where they tread",
|
||||
"please and tank blue",
|
||||
"watch where you tread",
|
||||
"well, tanks for trying",
|
||||
"should've reversed course",
|
||||
"you strayed from the straight and narrow",
|
||||
//"
|
||||
"strayed from the straight and narrow",
|
||||
"you charged in blindly",
|
||||
],
|
||||
tank_yellow: [
|
||||
"you let things get out of control",
|
||||
"you need more direction in your life",
|
||||
"your chances of surviving that were remote",
|
||||
"things got out of control",
|
||||
"you lost all direction",
|
||||
"your chances of survival were remote",
|
||||
//"
|
||||
//"
|
||||
],
|
||||
bug: [
|
||||
"you got ants in your pants",
|
||||
"time for some debugging",
|
||||
//"
|
||||
"you need to debug",
|
||||
"all the pest to you",
|
||||
//"
|
||||
//"
|
||||
],
|
||||
@ -176,72 +176,72 @@ const OBITUARIES = {
|
||||
"you got a little nybble",
|
||||
"you're quite a mouthful",
|
||||
"you passed the taste test",
|
||||
//"
|
||||
"you ate it",
|
||||
],
|
||||
teeth_timid: [
|
||||
"you got a killer byte",
|
||||
//"
|
||||
"you were nibbled to bits",
|
||||
"you got a tongue-lashing",
|
||||
//"
|
||||
//"
|
||||
"how unvoretunate",
|
||||
"you had an acci-dent",
|
||||
],
|
||||
blob: [
|
||||
"your luck ran out",
|
||||
"gooed job on that one",
|
||||
"the rng manipulated you",
|
||||
"goo another way next time",
|
||||
//"
|
||||
//"
|
||||
"try gooing another way",
|
||||
"what're the odds",
|
||||
"ooze laughing now",
|
||||
],
|
||||
doppelganger1: [
|
||||
"you were outfoxed",
|
||||
"sometimes a copy beats the original",
|
||||
"better reflect on what went wrong",
|
||||
"you need some vixen up",
|
||||
"take some time to reflect",
|
||||
"you've been duped",
|
||||
//"
|
||||
"stop hitting yourself",
|
||||
],
|
||||
doppelganger2: [
|
||||
"your plans just didn't gel",
|
||||
"bet that makes you hopping mad",
|
||||
//"
|
||||
//"
|
||||
//"
|
||||
"you got hopping mad",
|
||||
"hare today, gone tomorrow",
|
||||
"she left quite an impression",
|
||||
"you were gänged up on",
|
||||
],
|
||||
rover: [
|
||||
"should've given it more roomba",
|
||||
"try giving it more roomba",
|
||||
"exterminate. exterminate.",
|
||||
"your space was invaded",
|
||||
"red rover, red rover, this playthrough is over",
|
||||
"the robots have taken over",
|
||||
"defeated by a confused frisbee",
|
||||
],
|
||||
ghost: [
|
||||
"you were scared to death",
|
||||
"that wasn't very friendly",
|
||||
"now you're both ghosts",
|
||||
//"
|
||||
//"
|
||||
"you were haunted down",
|
||||
"what did you ex-specter",
|
||||
],
|
||||
floor_mimic: [
|
||||
"you never saw that coming",
|
||||
"you were absolutely floored",
|
||||
"this seems fu-tile",
|
||||
"watch your step",
|
||||
//"
|
||||
"you put your foot in its mouth",
|
||||
],
|
||||
|
||||
// Misc
|
||||
dynamite_lit: [
|
||||
"you've got a short fuse",
|
||||
"you failed to put the pin back in",
|
||||
//"
|
||||
//"
|
||||
//"
|
||||
"it had a hair trigger",
|
||||
"no take-backs",
|
||||
"you ran the wrong way",
|
||||
],
|
||||
rolling_ball: [
|
||||
"you were bowled over",
|
||||
"you found some head cannon",
|
||||
"strike one!",
|
||||
"down for the ten-count",
|
||||
"watch out, pinhead",
|
||||
"you really dropped the ball",
|
||||
],
|
||||
};
|
||||
// Helper class used to let the game play sounds without knowing too much about the Player
|
||||
@ -426,8 +426,9 @@ class Player extends PrimaryView {
|
||||
this.play_speed = 1;
|
||||
|
||||
this.level_el = this.root.querySelector('.level');
|
||||
this.overlay_message_el = this.root.querySelector('.overlay-message');
|
||||
this.overlay_message_el = this.root.querySelector('.player-overlay-message');
|
||||
this.hint_el = this.root.querySelector('.player-hint');
|
||||
this.number_el = this.root.querySelector('.player-level-number output');
|
||||
this.chips_el = this.root.querySelector('.chips output');
|
||||
this.time_el = this.root.querySelector('.time output');
|
||||
this.bonus_el = this.root.querySelector('.bonus output');
|
||||
@ -491,6 +492,71 @@ class Player extends PrimaryView {
|
||||
ev.target.blur();
|
||||
});
|
||||
|
||||
// Create the mobile pause menu, which consolidates buttons from around the desktop UI
|
||||
// TODO i really need to, uh, consolidate this
|
||||
let btn = (...args) => {
|
||||
let onclick = args.pop();
|
||||
|
||||
let props = {};
|
||||
let last = args[args.length - 1];
|
||||
if (typeof last === 'object' && last.constructor === Object) {
|
||||
props = args.pop();
|
||||
}
|
||||
|
||||
let button = mk('button', props, ...args);
|
||||
button.addEventListener('click', onclick);
|
||||
return button;
|
||||
};
|
||||
this.mobile_pause_menu = mk('div.mobile-pause-menu',
|
||||
// waiting
|
||||
btn("Play", {'class': 'button-bright -only-waiting'}, () => {
|
||||
this.set_state('playing');
|
||||
}),
|
||||
// paused
|
||||
mk('p.-only-paused',
|
||||
btn("Resume", {'class': 'button-bright'}, () => {
|
||||
this.set_state('playing');
|
||||
}),
|
||||
btn("Retry", () => {
|
||||
this.confirm_game_interruption("Abandon this attempt and try again?", () => {
|
||||
this.restart_level();
|
||||
});
|
||||
}),
|
||||
),
|
||||
// failure
|
||||
btn("Retry", {'class': 'button-bright -only-failure -only-ended'}, () => {
|
||||
this.restart_level();
|
||||
}),
|
||||
// success
|
||||
btn("Onwards!", {'class': 'button-bright -only-success'}, () => {
|
||||
this.conductor.maybe_change_level(this.conductor.level_index + 1);
|
||||
}),
|
||||
mk('p',
|
||||
this.mobile_prev_button = btn(svg_icon('prev'), {'class': '-narrow'}, () => {
|
||||
this.confirm_game_interruption("Abandon this attempt and return to the previous level?", () => {
|
||||
this.conductor.maybe_change_level(this.conductor.level_index - 1);
|
||||
});
|
||||
}),
|
||||
btn("Level select", () => {
|
||||
// TODO this should really be in the level browser itself since you can check
|
||||
// scores without losing a game
|
||||
this.confirm_game_interruption("Abandon this attempt?", () => {
|
||||
this.open_level_browser();
|
||||
});
|
||||
}),
|
||||
this.mobile_next_button = btn(svg_icon('next'), {'class': '-narrow'}, () => {
|
||||
this.confirm_game_interruption("Abandon this attempt and proceed to the next level?", () => {
|
||||
this.conductor.maybe_change_level(this.conductor.level_index + 1);
|
||||
});
|
||||
}),
|
||||
),
|
||||
btn("Quit to pack list", () => {
|
||||
this.confirm_game_interruption("Abandon this attempt and return to the pack list?", () => {
|
||||
this.conductor.switch_to_splash();
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
this.use_interpolation = true;
|
||||
// Default to the LL tileset for safety, but change when we load a level
|
||||
// (Note also that this must be created in the constructor so the CC2 timing option can be
|
||||
@ -605,15 +671,7 @@ class Player extends PrimaryView {
|
||||
}
|
||||
else if (this.state === 'stopped') {
|
||||
if (this.level.state === 'success') {
|
||||
// Advance to the next level, if any
|
||||
if (this.conductor.level_index < this.conductor.stored_game.level_metadata.length - 1) {
|
||||
this.conductor.change_level(this.conductor.level_index + 1);
|
||||
}
|
||||
else {
|
||||
// TODO for CCLs, by default, this is also at level 144
|
||||
this.set_state('ended');
|
||||
this.update_ui();
|
||||
}
|
||||
this.proceed_to_next_level();
|
||||
}
|
||||
else {
|
||||
// Restart
|
||||
@ -672,48 +730,28 @@ class Player extends PrimaryView {
|
||||
// Similarly, grab touch events and translate them to directions
|
||||
this.current_touches = {}; // ident => action
|
||||
this.touch_restart_delay = new util.DelayTimer;
|
||||
let touch_target = this.root.querySelector('#player-game-area'); // FIXME should be .level but the message overlay blocks touching, whoops!
|
||||
let touch_target = this.root.querySelector('#player-game-area .level');
|
||||
let collect_touches = ev => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.using_touch = true;
|
||||
|
||||
// If state is anything other than playing/waiting, probably switch to playing, similar
|
||||
// to pressing spacebar
|
||||
if (ev.type === 'touchstart') {
|
||||
if (this.state === 'paused') {
|
||||
this.toggle_pause();
|
||||
return;
|
||||
}
|
||||
else if (this.state === 'stopped') {
|
||||
if (this.touch_restart_delay.active) {
|
||||
// If it's only been a very short time since the level ended, ignore taps
|
||||
// here, so you don't accidentally mash restart and lose the chance to undo
|
||||
}
|
||||
else if (this.level.state === 'success') {
|
||||
// Advance to the next level
|
||||
// TODO game ending?
|
||||
this.conductor.change_level(this.conductor.level_index + 1);
|
||||
}
|
||||
else {
|
||||
// Restart
|
||||
this.restart_level();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Figure out where these touches are, relative to the game area
|
||||
// Figure out where these touches are, relative to the player
|
||||
// TODO allow starting a level without moving?
|
||||
let rect = this.level_el.getBoundingClientRect();
|
||||
// TODO if you don't move the touch, the player can pass it and will keep going in that
|
||||
// direction?
|
||||
let [px, py] = this.level.player.visual_position();
|
||||
px += 0.5;
|
||||
py += 0.5;
|
||||
for (let touch of ev.changedTouches) {
|
||||
// Normalize touch coordinates to [-1, 1]
|
||||
let rx = (touch.clientX - rect.left) / rect.width * 2 - 1;
|
||||
let ry = (touch.clientY - rect.top) / rect.height * 2 - 1;
|
||||
let [x, y] = this.renderer.point_to_real_cell_coords(touch.clientX, touch.clientY);
|
||||
let dx = x - px;
|
||||
let dy = y - py;
|
||||
console.log(dx, dy);
|
||||
// Divine a direction from the results
|
||||
let action;
|
||||
if (Math.abs(rx) > Math.abs(ry)) {
|
||||
if (rx < 0) {
|
||||
if (Math.abs(dx) > Math.abs(dy)) {
|
||||
if (dx < 0) {
|
||||
action = 'left';
|
||||
}
|
||||
else {
|
||||
@ -721,7 +759,7 @@ class Player extends PrimaryView {
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (ry < 0) {
|
||||
if (dy < 0) {
|
||||
action = 'up';
|
||||
}
|
||||
else {
|
||||
@ -745,9 +783,29 @@ class Player extends PrimaryView {
|
||||
};
|
||||
touch_target.addEventListener('touchend', dismiss_touches);
|
||||
touch_target.addEventListener('touchcancel', dismiss_touches);
|
||||
// Also grab taps on the overlay, for the specific case that tapping on the end of level
|
||||
// tally advances to the next level
|
||||
this.overlay_message_el.addEventListener('touchstart', ev => {
|
||||
if (this.state === 'stopped') {
|
||||
if (this.touch_restart_delay.active) {
|
||||
// If it's only been a very short time since the level ended, ignore taps
|
||||
// here, so you don't accidentally mash restart and lose the chance to undo
|
||||
}
|
||||
else if (this.level.state === 'success') {
|
||||
// Advance to the next level
|
||||
this.proceed_to_next_level();
|
||||
}
|
||||
else {
|
||||
// Restart
|
||||
this.restart_level();
|
||||
}
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// When we lose focus, act as though every key was released, and pause the game
|
||||
window.addEventListener('blur', ev => {
|
||||
window.addEventListener('blur', () => {
|
||||
this.current_keys.clear();
|
||||
this.current_touches = {};
|
||||
|
||||
@ -1253,10 +1311,14 @@ class Player extends PrimaryView {
|
||||
this.update_tileset();
|
||||
this.renderer.set_level(this.level);
|
||||
this.update_viewport_size();
|
||||
this.number_el.textContent = stored_level.number;
|
||||
// TODO base this on a hash of the UA + some identifier for the pack + the level index. StoredLevel doesn't know its own index atm...
|
||||
this.change_music(this.conductor.level_index % SOUNDTRACK.length);
|
||||
this._clear_state();
|
||||
|
||||
this.mobile_prev_button.disabled = ! (this.conductor.level_index - 1 >= 0);
|
||||
this.mobile_next_button.disabled = ! (this.conductor.level_index + 1 < this.conductor.stored_game.level_metadata.length);
|
||||
|
||||
this._update_replay_ui();
|
||||
if (this.debug.enabled) {
|
||||
this.debug.replay_level_label.textContent = this.level.stored_level.has_replay ? "available" : "none";
|
||||
@ -1298,6 +1360,7 @@ class Player extends PrimaryView {
|
||||
this.current_keyring = {};
|
||||
this.current_toolbelt = [];
|
||||
this.previous_hint_tile = null;
|
||||
this.current_touches = {};
|
||||
|
||||
this.chips_el.classList.remove('--done');
|
||||
this.time_el.classList.remove('--frozen');
|
||||
@ -1328,6 +1391,15 @@ class Player extends PrimaryView {
|
||||
this._redraw();
|
||||
}
|
||||
|
||||
proceed_to_next_level() {
|
||||
// Advance to the next level, if any
|
||||
if (! this.conductor.maybe_change_level(this.conductor.level_index + 1)) {
|
||||
// TODO for CCLs, by default, this is also at level 144
|
||||
this.set_state('ended');
|
||||
this.update_ui();
|
||||
}
|
||||
}
|
||||
|
||||
open_level_browser() {
|
||||
new LevelBrowserOverlay(this.conductor).open();
|
||||
}
|
||||
@ -1732,46 +1804,52 @@ class Player extends PrimaryView {
|
||||
this.current_keys_new.clear();
|
||||
}
|
||||
|
||||
// TODO wonder if some other update_ui stuff could move here
|
||||
this.pause_button.classList.toggle('--pressed', this.state === 'paused');
|
||||
this.rewind_button.classList.toggle('--pressed', this.state === 'rewinding');
|
||||
|
||||
// Populate the overlay
|
||||
let overlay_reason = '';
|
||||
let overlay_top = '';
|
||||
let overlay_middle = null;
|
||||
let overlay_bottom = '';
|
||||
let overlay_keyhint = '';
|
||||
let overlay = this.overlay_message_el;
|
||||
overlay.setAttribute('data-reason', this.state);
|
||||
this.overlay_message_el.textContent = '';
|
||||
if (this.state === 'waiting') {
|
||||
overlay_reason = 'waiting';
|
||||
let stored_level = this.level.stored_level;
|
||||
overlay_top = `#${stored_level.number} ${stored_level.title}`;
|
||||
overlay_middle = "Ready!";
|
||||
if (stored_level.author) {
|
||||
overlay_bottom = `by ${stored_level.author}`;
|
||||
}
|
||||
overlay.append(
|
||||
mk('h1', this.conductor.stored_game.title),
|
||||
mk('h2', `#${stored_level.number} ${stored_level.title}`),
|
||||
mk('h3', stored_level.author ? `by ${stored_level.author}` : "\u200b"),
|
||||
this.mobile_pause_menu,
|
||||
mk('p.-controls-hint', "WASD/↑←↓→ to move · space to start without moving"),
|
||||
);
|
||||
}
|
||||
else if (this.state === 'paused') {
|
||||
overlay_reason = 'paused';
|
||||
overlay_bottom = "/// paused ///";
|
||||
overlay.append(mk('h2', "/// paused ///"));
|
||||
if (this.using_touch) {
|
||||
overlay_keyhint = "tap to resume";
|
||||
overlay.append(mk('p.-controls-hint', "tap to resume"));
|
||||
}
|
||||
else {
|
||||
overlay_keyhint = "press P to resume";
|
||||
overlay.append(mk('p.-controls-hint', "press space to resume"));
|
||||
}
|
||||
overlay.append(this.mobile_pause_menu);
|
||||
}
|
||||
else if (this.state === 'stopped') {
|
||||
// Set a timer before tapping the overlay will restart/advance
|
||||
this.touch_restart_delay.set(2000);
|
||||
|
||||
if (this.level.state === 'failure') {
|
||||
overlay_reason = 'failure';
|
||||
overlay_top = "whoops";
|
||||
overlay.setAttribute('data-reason', 'failure');
|
||||
let obits = OBITUARIES[this.level.fail_reason] ?? OBITUARIES['generic'];
|
||||
overlay_bottom = random_choice(obits);
|
||||
overlay.append(
|
||||
mk('h2', "whoops" + random_choice(["", "!", "?", "..."])),
|
||||
mk('h3', random_choice(obits)),
|
||||
this.mobile_pause_menu,
|
||||
);
|
||||
if (this.using_touch) {
|
||||
// TODO touch gesture to rewind?
|
||||
overlay_keyhint = "tap to try again, or use undo/rewind above";
|
||||
overlay.append(mk('p.-controls-hint', "tap to try again, or use undo/rewind above"));
|
||||
}
|
||||
else {
|
||||
overlay_keyhint = "press space to try again, or Z to rewind";
|
||||
overlay.append(mk('p.-controls-hint', "press space to try again, or Z to rewind"));
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -1831,7 +1909,7 @@ class Player extends PrimaryView {
|
||||
this.conductor.save_savefile();
|
||||
}
|
||||
|
||||
overlay_reason = 'success';
|
||||
overlay.setAttribute('data-reason', 'success');
|
||||
let base = level_number * 500;
|
||||
let time = scorecard.time * 10;
|
||||
// Pick a success message
|
||||
@ -1841,89 +1919,110 @@ class Player extends PrimaryView {
|
||||
time_left_fraction = this.level.time_remaining / TICS_PER_SECOND / this.level.stored_level.time_limit;
|
||||
}
|
||||
|
||||
let quip;
|
||||
if (this.level.chips_remaining > 0) {
|
||||
overlay_top = random_choice([
|
||||
quip = random_choice([
|
||||
"socket to em!", "go bug blaster!",
|
||||
]);
|
||||
}
|
||||
else if (this.level.time_remaining && this.level.time_remaining < 200) {
|
||||
overlay_top = random_choice([
|
||||
quip = random_choice([
|
||||
"in the nick of time!", "cutting it close!",
|
||||
]);
|
||||
}
|
||||
else if (time_left_fraction !== null && time_left_fraction > 1) {
|
||||
overlay_top = random_choice([
|
||||
quip = random_choice([
|
||||
"faster than light!", "impossible speed!", "pipelined!",
|
||||
]);
|
||||
}
|
||||
else if (time_left_fraction !== null && time_left_fraction > 0.75) {
|
||||
overlay_top = random_choice([
|
||||
quip = random_choice([
|
||||
"lightning quick!", "nice speedrun!", "eagerly evaluated!",
|
||||
]);
|
||||
}
|
||||
else {
|
||||
overlay_top = random_choice([
|
||||
quip = random_choice([
|
||||
"you did it!", "nice going!", "great job!", "good work!",
|
||||
"onwards!", "tubular!", "yeehaw!", "hot damn!",
|
||||
"alphanumeric!", "nice dynamic typing!",
|
||||
]);
|
||||
}
|
||||
overlay.append(mk('h2', quip));
|
||||
|
||||
let bonus = this.level.bonus_points;
|
||||
let score_improvement = mk('div.-improvement');
|
||||
let time_improvement = mk('div.-improvement');
|
||||
if (! old_scorecard) {
|
||||
score_improvement.classList.add('--new');
|
||||
score_improvement.append(mk('h3', "first time!"));
|
||||
// leave time improvement empty since we already say it's first time once
|
||||
}
|
||||
else {
|
||||
let diff = scorecard.score - old_scorecard.score;
|
||||
let diffstr = Math.abs(diff).toLocaleString();
|
||||
if (diff > 0) {
|
||||
score_improvement.classList.add('--better');
|
||||
score_improvement.append(mk('h4', "new record!"), mk('p', `+ ${diffstr}`));
|
||||
}
|
||||
else if (diff === 0) {
|
||||
score_improvement.classList.add('--same');
|
||||
score_improvement.append(mk('h4', "tied your best!"), mk('p', `+ ${diffstr}`));
|
||||
}
|
||||
else {
|
||||
score_improvement.classList.add('--worse');
|
||||
score_improvement.append(mk('h4', "vs your best:"), mk('p', `− ${diffstr}`));
|
||||
}
|
||||
|
||||
diff = scorecard.abstime - old_scorecard.abstime;
|
||||
diffstr = util.format_duration(Math.abs(diff) / TICS_PER_SECOND, 2);
|
||||
if (diff < 0) {
|
||||
time_improvement.classList.add('--better');
|
||||
time_improvement.append(mk('h4', "new record!"), mk('p', `− ${diffstr}`));
|
||||
}
|
||||
else if (diff === 0) {
|
||||
time_improvement.classList.add('--same');
|
||||
time_improvement.append(mk('h4', "tied your best!"), mk('p', `− ${diffstr}`));
|
||||
}
|
||||
else {
|
||||
time_improvement.classList.add('--worse');
|
||||
time_improvement.append(mk('h4', "vs your best:"), mk('p', `+ ${diffstr}`));
|
||||
}
|
||||
}
|
||||
|
||||
overlay.append(mk('div.scoreboard',
|
||||
// base score + time bonus + score bonus
|
||||
mk('div.-subscore', mk('h4', "base score"), mk('p', base.toLocaleString())),
|
||||
mk('div.-subscore',
|
||||
mk('h4', "time bonus"),
|
||||
mk('p', time ? `+ ${time.toLocaleString()}` : "—")),
|
||||
mk('div.-subscore',
|
||||
mk('h4', "score bonus"),
|
||||
mk('p', bonus ? `+ ${bonus.toLocaleString()}` : "—")),
|
||||
// level score ... first time OR new record OR x short
|
||||
mk('div.-level-score',
|
||||
mk('h4', "level score"),
|
||||
mk('p', scorecard.score.toLocaleString(), scorecard.aid === 0 ? "★" : "")),
|
||||
score_improvement,
|
||||
|
||||
mk('div.-level-score',
|
||||
mk('h4', "real time"),
|
||||
mk('p', util.format_duration(scorecard.abstime / TICS_PER_SECOND, 2))),
|
||||
time_improvement,
|
||||
|
||||
// TODO show your level time, time improvement...? not quite enough room...
|
||||
mk('div.-total-score',
|
||||
mk('h4', "total score"),
|
||||
mk('p', savefile.total_score.toLocaleString())),
|
||||
mk('div.-total-score',
|
||||
mk('h4', "total real time"),
|
||||
mk('p', util.format_duration(savefile.total_abstime / TICS_PER_SECOND, 2))),
|
||||
));
|
||||
|
||||
if (this.using_touch) {
|
||||
overlay_keyhint = "tap to move on";
|
||||
overlay.append(mk('p.-controls-hint', "tap to move on"));
|
||||
}
|
||||
else {
|
||||
overlay_keyhint = "press space to move on";
|
||||
}
|
||||
|
||||
overlay_middle = mk('dl.score-chart',
|
||||
mk('dt.-component', "base score"),
|
||||
mk('dd.-component', base.toLocaleString()),
|
||||
mk('dt.-component', "time bonus"),
|
||||
mk('dd.-component', `+ ${time.toLocaleString()}`),
|
||||
);
|
||||
if (this.level.bonus_points) {
|
||||
overlay_middle.append(
|
||||
mk('dt.-component', "score bonus"),
|
||||
mk('dd.-component', `+ ${this.level.bonus_points.toLocaleString()}`),
|
||||
);
|
||||
}
|
||||
|
||||
// TODO show your time, time improvement...?
|
||||
let score_dd = mk('dd.-sum', scorecard.score.toLocaleString());
|
||||
if (scorecard.aid === 0) {
|
||||
score_dd.append(mk('span.-star', "★"));
|
||||
}
|
||||
overlay_middle.append(mk('dt.-sum', "level score"), score_dd);
|
||||
|
||||
overlay_middle.append(
|
||||
mk('dt.-total', "total score"),
|
||||
mk('dd.-total', savefile.total_score.toLocaleString()),
|
||||
);
|
||||
|
||||
if (old_scorecard && old_scorecard.score < scorecard.score) {
|
||||
overlay_middle.append(
|
||||
mk('dd.-total', `(+ ${(scorecard.score - old_scorecard.score).toLocaleString()})`),
|
||||
);
|
||||
}
|
||||
else {
|
||||
overlay_middle.append(mk('dd', ""));
|
||||
}
|
||||
|
||||
overlay_middle.append(
|
||||
mk('dd', ""),
|
||||
mk('dt', "real time"),
|
||||
mk('dd', util.format_duration(scorecard.abstime / TICS_PER_SECOND, 2)),
|
||||
mk('dt.-total', "total time"),
|
||||
mk('dd.-total', util.format_duration(savefile.total_abstime / TICS_PER_SECOND, 2)),
|
||||
);
|
||||
|
||||
if (old_scorecard && old_scorecard.abstime > scorecard.abstime) {
|
||||
overlay_middle.append(
|
||||
mk('dd.-total', `(− ${util.format_duration((old_scorecard.abstime - scorecard.abstime) / TICS_PER_SECOND, 2)})`),
|
||||
);
|
||||
}
|
||||
else {
|
||||
overlay_middle.append(mk('dd', ""));
|
||||
overlay.append(mk('p.-controls-hint', "press space to move on"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1932,20 +2031,17 @@ class Player extends PrimaryView {
|
||||
// long and clunky? final score is not interesting. could show other stats, total
|
||||
// time, say something if you skipped levels...
|
||||
// TODO disable most of the ui here? probably??
|
||||
overlay_reason = 'ended';
|
||||
overlay_middle = "Congratulations! You solved a whole set of funny escape rooms. But is that the best score you can manage...?";
|
||||
let savefile = this.conductor.current_pack_savefile;
|
||||
overlay_bottom = `FINAL SCORE: ${savefile.total_score.toLocaleString()}`;
|
||||
overlay.append(
|
||||
mk('p.-score', "FINAL SCORE", mk('output', savefile.total_score.toLocaleString())),
|
||||
this.mobile_pause_menu,
|
||||
mk('p.-congrats', "Congratulations! You beat some funny escape rooms. Now improve your score!"),
|
||||
);
|
||||
// TODO press spacebar to... restart from level 1?? or what
|
||||
}
|
||||
this.overlay_message_el.setAttribute('data-reason', overlay_reason);
|
||||
this.overlay_message_el.querySelector('.-top').textContent = overlay_top;
|
||||
this.overlay_message_el.querySelector('.-bottom').textContent = overlay_bottom;
|
||||
this.overlay_message_el.querySelector('.-keyhint').textContent = overlay_keyhint;
|
||||
let middle = this.overlay_message_el.querySelector('.-middle');
|
||||
middle.textContent = '';
|
||||
if (overlay_middle) {
|
||||
middle.append(overlay_middle);
|
||||
else {
|
||||
// 'playing', or bogus
|
||||
overlay.setAttribute('data-reason', '');
|
||||
}
|
||||
|
||||
// Ask the renderer to apply a rewind effect only when rewinding, or when paused from
|
||||
@ -2041,6 +2137,7 @@ class Player extends PrimaryView {
|
||||
if (style['display'] === 'none')
|
||||
return;
|
||||
|
||||
let tolerable_fraction = 1;
|
||||
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, plus half a
|
||||
@ -2061,8 +2158,16 @@ class Player extends PrimaryView {
|
||||
// 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);
|
||||
let avail_x = this.root.offsetWidth;
|
||||
let avail_y = this.root.offsetHeight;
|
||||
if (is_portrait) {
|
||||
// Controls are only on top and bottom; anything to the sides is empty space
|
||||
avail_y -= (player.offsetHeight - game_area.offsetHeight);
|
||||
}
|
||||
else {
|
||||
// Other way around
|
||||
avail_x -= (player.offsetWidth - game_area.offsetWidth);
|
||||
}
|
||||
// ...minus the width of the debug panel, if visible
|
||||
if (this.debug.enabled) {
|
||||
avail_x -= this.root.querySelector('#player-debug').getBoundingClientRect().width;
|
||||
@ -2072,6 +2177,7 @@ class Player extends PrimaryView {
|
||||
avail_y -= Math.max(0, document.body.scrollHeight - document.body.clientHeight);
|
||||
|
||||
let dpr = window.devicePixelRatio || 1.0;
|
||||
dpr *= tolerable_fraction;
|
||||
// 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
|
||||
@ -2411,7 +2517,7 @@ class Splash extends PrimaryView {
|
||||
|
||||
_create_pack_element(ident, packdef = null) {
|
||||
let title = packdef ? packdef.title : ident;
|
||||
let button = mk('button.button-big', {type: 'button'}, title);
|
||||
let button = mk('button.button-big.button-bright', {type: 'button'}, title);
|
||||
if (packdef) {
|
||||
button.addEventListener('click', ev => {
|
||||
this.conductor.fetch_pack(packdef.path, packdef.title);
|
||||
@ -2959,26 +3065,6 @@ class OptionsOverlay extends DialogOverlay {
|
||||
);
|
||||
}
|
||||
|
||||
_add_options(root, options) {
|
||||
let ul = mk('ul');
|
||||
root.append(ul);
|
||||
for (let optdef of options) {
|
||||
let li = mk('li');
|
||||
let label = mk('label.option');
|
||||
label.append(mk('input', {type: 'checkbox', name: optdef.key}));
|
||||
label.append(mk('span.option-label', optdef.label));
|
||||
let help_icon = mk('img.-help', {src: 'icons/help.png'});
|
||||
label.append(help_icon);
|
||||
let help_text = mk('p.option-help', optdef.note);
|
||||
li.append(label);
|
||||
li.append(help_text);
|
||||
ul.append(li);
|
||||
help_icon.addEventListener('click', ev => {
|
||||
help_text.classList.toggle('--visible');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
// Ensure the player's music is set back how we left it
|
||||
this.conductor.player.update_music_playback_state();
|
||||
@ -3387,7 +3473,7 @@ class LevelBrowserOverlay extends DialogOverlay {
|
||||
this.set_title("choose a level");
|
||||
let thead = mk('thead', mk('tr',
|
||||
mk('th', ""),
|
||||
mk('th', "Level"),
|
||||
mk('th.-title', "Level"),
|
||||
mk('th.-time', mk('abbr', {
|
||||
title: "Time left on the clock when you finished; doesn't exist for untimed levels",
|
||||
}, "Best clock")),
|
||||
@ -3414,6 +3500,7 @@ class LevelBrowserOverlay extends DialogOverlay {
|
||||
}
|
||||
|
||||
// 0 means untimed level
|
||||
// FIXME wait, not necessarily! shouldn't untimed be null?
|
||||
if (scorecard.time !== 0) {
|
||||
time = String(scorecard.time);
|
||||
}
|
||||
@ -3504,7 +3591,7 @@ class LevelBrowserOverlay extends DialogOverlay {
|
||||
|
||||
table.append(mk('tfoot', mk('tr',
|
||||
mk('th'),
|
||||
mk('th', "Total"),
|
||||
mk('th.-title', "Total"),
|
||||
mk('th'),
|
||||
mk('th.-time', util.format_duration(total_abstime / TICS_PER_SECOND, 2)),
|
||||
mk('th.-score', total_score.toLocaleString()),
|
||||
@ -3836,6 +3923,14 @@ class Conductor {
|
||||
return this.change_level(level_index ?? (this.current_pack_savefile.current_level ?? 1) - 1);
|
||||
}
|
||||
|
||||
// Attempt to change level, but silently return false if the given level number doesn't exist
|
||||
maybe_change_level(level_index) {
|
||||
if (level_index < 0 || level_index >= this.stored_game.level_metadata.length)
|
||||
return false;
|
||||
|
||||
return this.change_level(level_index);
|
||||
}
|
||||
|
||||
change_level(level_index) {
|
||||
// FIXME handle errors here
|
||||
try {
|
||||
|
||||
@ -112,6 +112,15 @@ export class CanvasRenderer {
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
point_to_cell_coords(client_x, client_y) {
|
||||
let rect = this.canvas.getBoundingClientRect();
|
||||
let scale_x = rect.width / this.canvas.width;
|
||||
let scale_y = rect.height / this.canvas.height;
|
||||
let x = Math.floor((client_x - rect.x) / scale_x / this.tileset.size_x + this.viewport_x);
|
||||
let y = Math.floor((client_y - rect.y) / scale_y / this.tileset.size_y + this.viewport_y);
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
real_cell_coords_from_event(ev) {
|
||||
let rect = this.canvas.getBoundingClientRect();
|
||||
let scale_x = rect.width / this.canvas.width;
|
||||
@ -121,6 +130,15 @@ export class CanvasRenderer {
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
point_to_real_cell_coords(client_x, client_y) {
|
||||
let rect = this.canvas.getBoundingClientRect();
|
||||
let scale_x = rect.width / this.canvas.width;
|
||||
let scale_y = rect.height / this.canvas.height;
|
||||
let x = (client_x - rect.x) / scale_x / this.tileset.size_x + this.viewport_x;
|
||||
let y = (client_y - rect.y) / scale_y / this.tileset.size_y + this.viewport_y;
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
// Draw to a canvas using tile coordinates
|
||||
blit(ctx, sx, sy, dx, dy, w = 1, h = w) {
|
||||
let tw = this.tileset.size_x;
|
||||
|
||||
511
style.css
511
style.css
@ -18,9 +18,9 @@ body {
|
||||
color: #ececec;
|
||||
|
||||
--panel-bg-color: hsl(220, 10%, 15%);
|
||||
--button-bg-color: hsl(220, 10%, 25%);
|
||||
--button-bg-color: hsl(220, 20%, 25%);
|
||||
--button-bg-shadow-color: #fff1;
|
||||
--button-bg-hover-color: hsl(220, 15%, 30%);
|
||||
--button-bg-hover-color: hsl(220, 30%, 30%);
|
||||
--generic-bg-hover-on-white: hsl(220, 60%, 90%);
|
||||
--generic-bg-selected-on-white: hsl(220, 60%, 85%);
|
||||
--generic-border-selected-on-white: hsl(220, 60%, 75%);
|
||||
@ -50,10 +50,10 @@ button,
|
||||
color: white;
|
||||
background-color: var(--button-bg-color);
|
||||
background-image: linear-gradient(to bottom, var(--button-bg-shadow-color), transparent 75%);
|
||||
border: 1px solid hsl(220, 10%, 10%);
|
||||
border: 1px solid hsl(220, 10%, 7.5%);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px hsl(220, 10%, 33%),
|
||||
0 1px 1px hsl(220, 10%, 10%);
|
||||
inset 0 0 1px 1px #fff2,
|
||||
0 1px 1px hsl(220, 10%, 7.5%);
|
||||
border-radius: 0.25em;
|
||||
text-transform: lowercase;
|
||||
cursor: pointer;
|
||||
@ -68,12 +68,15 @@ button:active,
|
||||
/* Need this for the editor's tool help things and i'm not questioning it */
|
||||
z-index: 1;
|
||||
}
|
||||
button:enabled.button-bright {
|
||||
background-color: hsl(220, 50%, 25%);
|
||||
}
|
||||
button:enabled.button-bright:hover {
|
||||
background-color: hsl(220, 70%, 30%);
|
||||
}
|
||||
button:disabled {
|
||||
color: #606060;
|
||||
background-color: #202020;
|
||||
box-shadow:
|
||||
inset 0 0 2px 1px hsl(220, 0%, 10%),
|
||||
0 1px 0 hsl(220, 10%, 10%);
|
||||
cursor: auto;
|
||||
}
|
||||
button.button-big {
|
||||
@ -202,12 +205,13 @@ svg.svg-icon {
|
||||
border-top-right-radius: 0.25em;
|
||||
border-bottom-right-radius: 0.25em;
|
||||
}
|
||||
button.--pressed,
|
||||
.radio-faux-button-set > label > input:checked + span {
|
||||
background: hsl(220, 80%, 50%);
|
||||
box-shadow:
|
||||
inset 0 0 1px 1px hsl(220, 50%, 40%),
|
||||
inset 0 -0.125em 0.5em 0.25em hsl(220, 50%, 30%),
|
||||
0 1px 1px hsl(220, 10%, 10%);
|
||||
inset 0 1px 3px 1px hsl(220, 50%, 15%),
|
||||
inset 0 0.25em 1em 0.5em hsl(220, 50%, 30%),
|
||||
0 1px 1px hsl(220, 10%, 10%)
|
||||
}
|
||||
|
||||
.button-row {
|
||||
@ -411,6 +415,84 @@ table.level-browser tbody tr:hover {
|
||||
table.level-browser tbody tr:nth-child(10n) td {
|
||||
border-bottom: 2px solid hsl(220, 20%, 80%);
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
/* Unique media query: this is only necessary for VERY narrow screens */
|
||||
/* In order to wrap the rows, turn the table markup into a stack of grids */
|
||||
table.level-browser {
|
||||
display: block;
|
||||
}
|
||||
table.level-browser tr {
|
||||
display: grid;
|
||||
grid:
|
||||
"number star name name forget"
|
||||
"number . clock time score"
|
||||
/ 3em 1em 1fr 1fr 1fr
|
||||
;
|
||||
}
|
||||
table.level-browser td,
|
||||
table.level-browser th {
|
||||
display: block;
|
||||
}
|
||||
table.level-browser thead .-title,
|
||||
table.level-browser tfoot .-title {
|
||||
/* "Level" and "Total" column headers are not useful and eat a lot of space */
|
||||
display: none;
|
||||
}
|
||||
table.level-browser thead th:empty,
|
||||
table.level-browser tfoot th:empty {
|
||||
/* These are filler for table layout purposes */
|
||||
display: none;
|
||||
}
|
||||
table.level-browser .-number {
|
||||
grid-area: number;
|
||||
}
|
||||
table.level-browser .-title {
|
||||
grid-area: name;
|
||||
}
|
||||
table.level-browser .-time {
|
||||
grid-area: clock;
|
||||
}
|
||||
table.level-browser .-time + .-time {
|
||||
grid-area: time;
|
||||
}
|
||||
table.level-browser .-score {
|
||||
grid-area: score;
|
||||
}
|
||||
table.level-browser .-aid {
|
||||
grid-area: star;
|
||||
/* We overflow a bit because of our padding, so just leak into it */
|
||||
justify-self: center;
|
||||
}
|
||||
table.level-browser .-button {
|
||||
grid-area: forget;
|
||||
justify-self: end;
|
||||
}
|
||||
/* Move borders off cells and onto rows */
|
||||
table.level-browser thead tr th {
|
||||
border: none;
|
||||
}
|
||||
table.level-browser tfoot tr th {
|
||||
border: none;
|
||||
}
|
||||
table.level-browser tbody tr:nth-child(10n) td {
|
||||
border: none;
|
||||
}
|
||||
table.level-browser thead tr {
|
||||
border-bottom: 2px solid hsl(220, 20%, 60%);
|
||||
}
|
||||
table.level-browser tfoot tr {
|
||||
border-top: 2px solid hsl(220, 20%, 60%);
|
||||
}
|
||||
table.level-browser tbody tr {
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
table.level-browser tbody tr.--current {
|
||||
border: none;
|
||||
}
|
||||
table.level-browser tbody tr:nth-child(10n) {
|
||||
border-bottom: 2px solid hsl(220, 20%, 80%);
|
||||
}
|
||||
}
|
||||
|
||||
/* Compat dialog */
|
||||
.dialog-compat {
|
||||
@ -441,6 +523,46 @@ img.compat-icon,
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
/* Stack the formgrid, it doesn't fit very well as columns */
|
||||
.dialog dl.formgrid {
|
||||
display: block;
|
||||
}
|
||||
.dialog dl.formgrid > dt {
|
||||
margin: 0.5em 0;
|
||||
text-align: left;
|
||||
}
|
||||
.dialog dl.formgrid > dt:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
.dialog dl.formgrid > * + dt {
|
||||
border-top: 1px solid #ccc;
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
.dialog dl.formgrid > * + dt:empty {
|
||||
padding-top: 0;
|
||||
}
|
||||
.dialog dl.formgrid > dd {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
.dialog-compat .radio-faux-button-set {
|
||||
font-size: 0.83em;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.dialog-compat .radio-faux-button-set > * {
|
||||
flex: 1 0 30%;
|
||||
}
|
||||
.dialog-compat .radio-faux-button-set .-button {
|
||||
border-radius: 0;
|
||||
}
|
||||
ul.compat-flags img.compat-icon,
|
||||
ul.compat-flags span.compat-icon-gap {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Options dialog */
|
||||
.dialog-options {
|
||||
@ -481,6 +603,8 @@ label.option .option-label {
|
||||
max-width: 90%;
|
||||
max-height: 90%;
|
||||
}
|
||||
.dialog-options {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -511,7 +635,6 @@ body > header > nav {
|
||||
gap: 0.5em;
|
||||
}
|
||||
body > header button {
|
||||
font-size: 0.75em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
body > header h1 a {
|
||||
@ -587,14 +710,18 @@ pre.stack-trace {
|
||||
order: 3;
|
||||
color: #606060;
|
||||
}
|
||||
#header-icon {
|
||||
image-rendering: crisp-edges;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
@media ((orientation: portrait) and (max-width: 800px)) or ((orientation: landscape) and (max-height: 600px)) {
|
||||
body > header {
|
||||
padding: 0.125em 0.25em;
|
||||
padding: 1px;
|
||||
}
|
||||
/* All these headings are way too big on phones */
|
||||
body > header h1 {
|
||||
font-size: 1.25em;
|
||||
font-size: 1.125em;
|
||||
}
|
||||
body > header h2 {
|
||||
font-size: 1.125em;
|
||||
@ -606,6 +733,12 @@ pre.stack-trace {
|
||||
/* "a game by eevee" takes up too much space :( */
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hide the top/bottom nav while playing entirely */
|
||||
body[data-mode=player] #header-pack,
|
||||
body[data-mode=player] #header-level {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/**************************************************************************************************/
|
||||
@ -813,15 +946,6 @@ pre.stack-trace {
|
||||
/* this also forces the button to be 1 line of text high even for empty title */
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.played-pack-list > li > button:enabled {
|
||||
background-color: hsl(220, 30%, 25%);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px hsl(220, 30%, 33%),
|
||||
0 1px 1px hsl(220, 10%, 10%);
|
||||
}
|
||||
.played-pack-list > li > button:enabled:hover {
|
||||
background-color: hsl(220, 40%, 30%);
|
||||
}
|
||||
.played-pack-list p {
|
||||
color: #c0c0c0;
|
||||
font-style: italic;
|
||||
@ -1128,9 +1252,6 @@ ol.packtest-summary > li {
|
||||
#player-actions button svg {
|
||||
font-size: 1em;
|
||||
}
|
||||
#player-controls .-optional-label {
|
||||
display: none;
|
||||
}
|
||||
#player-controls button {
|
||||
padding: 0.25em 0.5em;
|
||||
line-height: 1.33;
|
||||
@ -1159,28 +1280,48 @@ ol.packtest-summary > li {
|
||||
"buttons"
|
||||
"game"
|
||||
"actions"
|
||||
"music"
|
||||
;
|
||||
}
|
||||
#player-controls,
|
||||
#player-actions {
|
||||
/* Not much in these rows so make them a bit bigger for hittability */
|
||||
font-size: 1.33em;
|
||||
justify-content: center;
|
||||
}
|
||||
#player-controls button,
|
||||
#player-actions button {
|
||||
flex: auto;
|
||||
padding: 0.25em 0.5em;
|
||||
}
|
||||
#player-controls .control-restart {
|
||||
/* This is a dedicated pause-menu button */
|
||||
display: none;
|
||||
}
|
||||
#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) {
|
||||
@media (orientation: landscape) and (max-height: 600px) {
|
||||
/* On a small landscape screen, remove the music row (it matters!) */
|
||||
#player-main {
|
||||
grid:
|
||||
"buttons game actions"
|
||||
"buttons game actions"
|
||||
/ 1fr auto 1fr
|
||||
;
|
||||
}
|
||||
}
|
||||
@media ((orientation: portrait) and (max-width: 800px)) or ((orientation: landscape) and (max-height: 600px)) {
|
||||
#player-controls .-optional-label {
|
||||
display: none;
|
||||
}
|
||||
#splash-fullscreen {
|
||||
display: revert;
|
||||
}
|
||||
#player-music {
|
||||
font-size: 0.875em;
|
||||
/* TODO :( */
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1206,7 +1347,7 @@ ol.packtest-summary > li {
|
||||
row-gap: calc(var(--tile-height) * var(--scale) / 4);
|
||||
|
||||
padding: calc(var(--tile-height) * var(--scale) / 4) calc(var(--tile-width) * var(--scale) / 4);
|
||||
background: hsl(220, 10%, 20%);
|
||||
background: hsl(220, 10%, 15%);
|
||||
box-shadow: 0 0.25em 1em black;
|
||||
}
|
||||
|
||||
@ -1214,7 +1355,7 @@ ol.packtest-summary > li {
|
||||
grid-area: level;
|
||||
|
||||
position: relative;
|
||||
outline: 2px solid black;
|
||||
outline: 1px solid hsl(220, 10%, 5%);
|
||||
}
|
||||
.level canvas {
|
||||
display: block;
|
||||
@ -1223,78 +1364,172 @@ ol.packtest-summary > li {
|
||||
--viewport-width: 9;
|
||||
--viewport-height: 9;
|
||||
}
|
||||
#player .overlay-message {
|
||||
|
||||
.player-overlay-message {
|
||||
grid-area: level;
|
||||
place-self: stretch;
|
||||
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-rows: 2fr 6fr 2fr 1fr;
|
||||
justify-content: stretch;
|
||||
grid:
|
||||
"pack" calc(1.25em * 1.25 * 1)
|
||||
"level" calc(1.333em * 1.25 * 2)
|
||||
"author" calc(1em * 1.25 * 1)
|
||||
"space" 1fr
|
||||
"controls" 1.5em
|
||||
;
|
||||
align-items: center;
|
||||
/* Prevent blowout; force using the canvas's height */
|
||||
gap: 0.25em;
|
||||
/* Prevent blowout; force using the canvas's size */
|
||||
height: 0;
|
||||
min-height: 100%;
|
||||
width: 0;
|
||||
min-width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
z-index: 2;
|
||||
font-size: calc(0.5 * var(--tile-width) * var(--scale));
|
||||
line-height: 1.25;
|
||||
background: #0009;
|
||||
color: white;
|
||||
text-align: center;
|
||||
text-shadow: 0 2px 1px black;
|
||||
}
|
||||
#player .overlay-message > * {
|
||||
padding: 0 5%;
|
||||
text-shadow: 0 1px 1px black;
|
||||
}
|
||||
/* Allow clicking through the overlay in debug mode */
|
||||
body.--debug .overlay-message {
|
||||
body.--debug .player-overlay-message {
|
||||
pointer-events: none;
|
||||
}
|
||||
#player .overlay-message p {
|
||||
margin: 0;
|
||||
.player-overlay-message > * {
|
||||
padding: 0 0.25em;
|
||||
}
|
||||
#player .overlay-message .-top {
|
||||
font-size: 1.5em;
|
||||
.player-overlay-message h1 {
|
||||
/* Pack title, doesn't need to be too big */
|
||||
grid-area: pack;
|
||||
font-size: 1em;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: hsl(220, 25%, 60%);
|
||||
}
|
||||
#player .overlay-message .-middle {
|
||||
.player-overlay-message > h2 {
|
||||
grid-area: level;
|
||||
font-size: 2em;
|
||||
}
|
||||
#player .overlay-message .-bottom {
|
||||
.player-overlay-message[data-reason='waiting'] > h2 {
|
||||
/* For 'waiting' this is a level name, so make it two lines of smaller text */
|
||||
font-size: 1.333em;
|
||||
}
|
||||
#player .overlay-message .-keyhint {
|
||||
align-self: end;
|
||||
font-size: 0.5em;
|
||||
.player-overlay-message > h3 {
|
||||
grid-area: author;
|
||||
font-size: 1em;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: hsl(220, 10%, 90%);
|
||||
}
|
||||
.player-overlay-message > .scoreboard {
|
||||
grid-row: author / space;
|
||||
}
|
||||
.player-overlay-message .-controls-hint {
|
||||
grid-area: controls;
|
||||
font-size: 0.75em;
|
||||
color: #c0c0c0;
|
||||
}
|
||||
#player .overlay-message[data-reason=""] {
|
||||
.player-overlay-message .mobile-pause-menu {
|
||||
grid-area: space;
|
||||
}
|
||||
.mobile-pause-menu {
|
||||
font-size: 1.25em;
|
||||
display: none; /* flex */
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 0.33em;
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
}
|
||||
.mobile-pause-menu button {
|
||||
padding: 0.33em;
|
||||
}
|
||||
.mobile-pause-menu > p {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
justify-content: stretch;
|
||||
margin: 0;
|
||||
gap: 0.33em;
|
||||
}
|
||||
.mobile-pause-menu > p > button {
|
||||
flex: 1;
|
||||
line-height: 1;
|
||||
}
|
||||
.mobile-pause-menu > p > button.-narrow {
|
||||
flex: initial;
|
||||
}
|
||||
.mobile-pause-menu .-only-waiting,
|
||||
.mobile-pause-menu .-only-paused,
|
||||
.mobile-pause-menu .-only-failure,
|
||||
.mobile-pause-menu .-only-success,
|
||||
.mobile-pause-menu .-only-ended {
|
||||
display: none;
|
||||
}
|
||||
#player .overlay-message[data-reason=failure] {
|
||||
box-shadow: inset 0 0 calc(4 * var(--tile-width)) var(--tile-width) black;
|
||||
.player-overlay-message[data-reason=waiting] .mobile-pause-menu .-only-waiting,
|
||||
.player-overlay-message[data-reason=paused] .mobile-pause-menu .-only-paused,
|
||||
.player-overlay-message[data-reason=failure] .mobile-pause-menu .-only-failure,
|
||||
.player-overlay-message[data-reason=success] .mobile-pause-menu .-only-success,
|
||||
.player-overlay-message[data-reason=ended] .mobile-pause-menu .-only-ended {
|
||||
display: initial;
|
||||
}
|
||||
#player .overlay-message[data-reason=success] {
|
||||
background: hsla(220, 50%, 25%, 0.5);
|
||||
box-shadow: inset 0 0 calc(4 * var(--tile-width)) hsl(220, 50%, 25%);
|
||||
.player-overlay-message[data-reason=paused] .mobile-pause-menu p.-only-paused {
|
||||
display: flex;
|
||||
}
|
||||
#player .overlay-message[data-reason=ended] {
|
||||
/* Shove the middle + bottom parts down so they don't overlay the busiest part of the ending image */
|
||||
grid-template-rows: 8fr 4fr 2fr 0;
|
||||
.player-overlay-message[data-reason=""] {
|
||||
display: none;
|
||||
}
|
||||
.player-overlay-message[data-reason=waiting] {
|
||||
background: linear-gradient(to bottom, #000d, #0008 40%, #0008 60%, #000d);
|
||||
}
|
||||
.player-overlay-message[data-reason=failure] {
|
||||
background: hsla(330, 20%, 10%, 0.5);
|
||||
background: radial-gradient(#0004, hsla(330, 10%, 10%, 0.5) 40%, hsl(330, 20%, 10%));
|
||||
}
|
||||
.player-overlay-message[data-reason=success] {
|
||||
background: radial-gradient(hsla(220, 60%, 5%, 0.75), 60%, hsla(220, 60%, 25%, 0.75));
|
||||
}
|
||||
.player-overlay-message[data-reason=ended] {
|
||||
/* Rearrange this entirely, to fit the ending image in */
|
||||
grid:
|
||||
"congrats" min-content
|
||||
"." 0.5em
|
||||
"menu" 1fr
|
||||
"." 0.5em
|
||||
"score" min-content
|
||||
;
|
||||
overflow: hidden;
|
||||
x-color: black;
|
||||
background: url(ending.png) no-repeat center center / cover;
|
||||
box-shadow: inset 0 0 calc(4 * var(--tile-width)) hsl(220, 50%, 25%);
|
||||
x-text-shadow: 0 0 2px white, 0 2px 2px white;
|
||||
}
|
||||
#player .overlay-message[data-reason=ended] .-middle {
|
||||
.player-overlay-message[data-reason=ended] .mobile-pause-menu {
|
||||
grid-area: menu;
|
||||
}
|
||||
.player-overlay-message[data-reason=ended] > .-congrats {
|
||||
grid-area: congrats;
|
||||
margin: 0;
|
||||
padding: 0.25em;
|
||||
background: #0009;
|
||||
}
|
||||
#player .overlay-message[data-reason=ended] .-bottom {
|
||||
font-size: 1.5em;
|
||||
.player-overlay-message[data-reason=ended] > .-score {
|
||||
grid-area: score;
|
||||
margin: 0;
|
||||
padding: 0.25em;
|
||||
background: #0006;
|
||||
}
|
||||
.player-overlay-message[data-reason=ended] > .-score output {
|
||||
font-size: 2em;
|
||||
display: block;
|
||||
}
|
||||
@supports (mask-image: none) or (-webkit-mask-image: none) {
|
||||
/* Do this complicated rotating sunburst thing only if masks work */
|
||||
#player .overlay-message[data-reason=ended]::before {
|
||||
.player-overlay-message[data-reason=ended]::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
@ -1317,35 +1552,68 @@ body.--debug .overlay-message {
|
||||
transform: rotate(1turn);
|
||||
}
|
||||
}
|
||||
dl.score-chart {
|
||||
.scoreboard {
|
||||
display: grid;
|
||||
grid-auto-columns: 1fr 1fr;
|
||||
grid-auto-rows: 1.33em;
|
||||
margin: auto 10%;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||
grid-auto-rows: min-content;
|
||||
align-items: center;
|
||||
row-gap: 0.75em;
|
||||
margin: auto 5%;
|
||||
font-weight: normal;
|
||||
text-align: center;
|
||||
}
|
||||
dl.score-chart dt {
|
||||
grid-column: 1;
|
||||
text-align: left;
|
||||
.scoreboard .-subscore {
|
||||
grid-column: span 2;
|
||||
color: #f4f4f4;
|
||||
}
|
||||
dl.score-chart dd {
|
||||
grid-column: 2;
|
||||
.scoreboard .-level-score {
|
||||
grid-column: span 3;
|
||||
}
|
||||
.scoreboard .-improvement {
|
||||
grid-column: span 3;
|
||||
}
|
||||
.scoreboard .-improvement.--same h4 {
|
||||
color: hsl(240, 50%, 60%);
|
||||
}
|
||||
.scoreboard .-improvement.--same p {
|
||||
color: hsl(240, 50%, 80%);
|
||||
}
|
||||
.scoreboard .-improvement.--worse h4 {
|
||||
color: hsl(330, 50%, 60%);
|
||||
}
|
||||
.scoreboard .-improvement.--worse p {
|
||||
color: hsl(330, 50%, 80%);
|
||||
}
|
||||
.scoreboard .-improvement.--better h4 {
|
||||
color: hsl(210, 50%, 60%);
|
||||
}
|
||||
.scoreboard .-improvement.--better p {
|
||||
color: hsl(210, 50%, 80%);
|
||||
}
|
||||
.scoreboard .-total-score {
|
||||
grid-column: span 3;
|
||||
color: hsl(45, 50%, 75%);
|
||||
}
|
||||
.scoreboard h4 {
|
||||
font-size: 0.833em;
|
||||
color: hsl(220, 10%, 80%);
|
||||
}
|
||||
.scoreboard .-total-score h4 {
|
||||
color: hsl(30, 50%, 60%);
|
||||
}
|
||||
.scoreboard p {
|
||||
margin: 0;
|
||||
text-align: right;
|
||||
}
|
||||
dl.score-chart .-component {
|
||||
color: #d8d8d8;
|
||||
}
|
||||
dl.score-chart .-sum {
|
||||
border-top: 1px solid white;
|
||||
}
|
||||
dl.score-chart .-total {
|
||||
color: hsl(40, 75%, 80%);
|
||||
}
|
||||
dl.score-chart .-star {
|
||||
position: absolute;
|
||||
.scoreboard .-total-score p {
|
||||
font-size: 1.333em;
|
||||
}
|
||||
|
||||
.player-level-number {
|
||||
grid-area: number;
|
||||
/* This is only for portrait, and mostly to fill space */
|
||||
display: none;
|
||||
line-height: 1;
|
||||
}
|
||||
.chips {
|
||||
grid-area: chips;
|
||||
}
|
||||
@ -1358,17 +1626,17 @@ dl.score-chart .-star {
|
||||
.chips,
|
||||
.time,
|
||||
.bonus {
|
||||
font-size: calc(var(--tile-height) * var(--scale) / 3);
|
||||
font-size: calc(var(--tile-height) * var(--scale) * 3/4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
gap: 0.25em;
|
||||
}
|
||||
.chips h3,
|
||||
.time h3,
|
||||
.bonus h3 {
|
||||
flex: 0;
|
||||
order: 2;
|
||||
font-size: 1.5em;
|
||||
font-size: 0.75em;
|
||||
line-height: 1;
|
||||
color: hsl(220, 20%, 80%);
|
||||
}
|
||||
@ -1376,7 +1644,6 @@ dl.score-chart .-star {
|
||||
.time output,
|
||||
.bonus output {
|
||||
flex: 1;
|
||||
font-size: 2em;
|
||||
min-width: 2em;
|
||||
min-height: 1em;
|
||||
line-height: 1;
|
||||
@ -1408,28 +1675,25 @@ dl.score-chart .-star {
|
||||
}
|
||||
}
|
||||
.chips output.--done,
|
||||
.time output.--frozen {
|
||||
.time output.--frozen,
|
||||
.bonus output {
|
||||
color: hsl(220, 10%, 30%);
|
||||
}
|
||||
.bonus output {
|
||||
#player.--bonus-visible .bonus output {
|
||||
color: #e2c9ff;
|
||||
}
|
||||
#player .bonus {
|
||||
visibility: hidden;
|
||||
display: flex;
|
||||
}
|
||||
#player.--bonus-visible .bonus {
|
||||
visibility: initial;
|
||||
}
|
||||
.player-rules {
|
||||
font-size: calc(var(--tile-height) * var(--scale) / 4);
|
||||
grid-area: rules;
|
||||
align-self: end;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
color: hsl(220, 20%, 80%);
|
||||
}
|
||||
.player-rules p {
|
||||
display: none;
|
||||
margin: 0.25em 0;
|
||||
margin: 0;
|
||||
}
|
||||
#player.--hide-logic .player-rules #player-rule-logic-hidden {
|
||||
display: revert;
|
||||
@ -1516,17 +1780,25 @@ dl.score-chart .-star {
|
||||
#player-game-area {
|
||||
/* Rearrange the grid to be vertical */
|
||||
grid:
|
||||
"level level level"
|
||||
"inventory rules chips" calc((var(--tile-height) * var(--scale) * (2 - 1/6)) / 3)
|
||||
"inventory rules time" calc((var(--tile-height) * var(--scale) * (2 - 1/6)) / 3)
|
||||
"inventory rules bonus" calc((var(--tile-height) * var(--scale) * (2 - 1/6)) / 3)
|
||||
/ min-content min-content 1fr
|
||||
"inventory chips chips time" min-content
|
||||
"inventory rules bonus bonus" min-content
|
||||
"level level level level" min-content
|
||||
/ min-content 1fr 1fr 2fr
|
||||
;
|
||||
row-gap: calc(var(--tile-height) * var(--scale) / 6);
|
||||
}
|
||||
.player-level-number {
|
||||
/* TODO this makes us too big on my phone, damn */
|
||||
/*display: initial;*/
|
||||
}
|
||||
.chips,
|
||||
.time,
|
||||
.bonus {
|
||||
/* These numbers need to be sliiightly smaller */
|
||||
font-size: calc(var(--tile-height) * var(--scale) * 2/3);
|
||||
}
|
||||
#player .inventory {
|
||||
/* stick me in the center right */
|
||||
place-self: center end;
|
||||
/* stick me in the center left */
|
||||
place-self: center start;
|
||||
}
|
||||
#player-game-area > .player-hint-wrapper {
|
||||
/* Overlay hints on the inventory area */
|
||||
@ -1536,17 +1808,38 @@ dl.score-chart .-star {
|
||||
font-size: calc(var(--tile-height) * var(--scale) / 2.5);
|
||||
}
|
||||
#player-game-area > .player-hint-wrapper > .player-hint {
|
||||
padding: 0.25em 0.33em;
|
||||
padding: 0.33em 0.5em;
|
||||
line-height: 1.33;
|
||||
}
|
||||
.player-rules {
|
||||
align-self: center;
|
||||
flex-direction: row;
|
||||
}
|
||||
.player-rules p span {
|
||||
/* There's only room for the icons, since there's no dedicated hint space */
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@media ((orientation: portrait) and (max-width: 800px)) or ((orientation: landscape) and (max-height: 600px)) {
|
||||
/* Overlay is a bit different on what I assume is a touchscreen */
|
||||
.player-overlay-message[data-reason='waiting'] > p {
|
||||
/* Hide the "Ready!" and controls, since there's a menu */
|
||||
display: none;
|
||||
}
|
||||
.mobile-pause-menu {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
@media (orientation: portrait) and (max-width: 800px) {
|
||||
#player-game-area {
|
||||
padding: 0;
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.level {
|
||||
outline: 1px solid black;
|
||||
}
|
||||
}
|
||||
|
||||
/* Debug stuff */
|
||||
body.--debug #player-debug {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user