Improve dialog styling; add about text; stub out options

This commit is contained in:
Eevee (Evelyn Woods) 2020-09-01 06:46:29 -06:00
parent c8bdf121d0
commit aa7952a3dd
3 changed files with 171 additions and 27 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 B

After

Width:  |  Height:  |  Size: 277 B

View File

@ -643,6 +643,8 @@ class Level {
} }
} }
// Stackable modal overlay of some kind, usually a dialog
class Overlay { class Overlay {
constructor(game, root) { constructor(game, root) {
this.game = game; this.game = game;
@ -673,11 +675,104 @@ class Overlay {
this.root.closest('.overlay').remove(); this.root.closest('.overlay').remove();
} }
} }
class LevelBrowserOverlay extends Overlay {
// Overlay styled like a dialog box
class DialogOverlay extends Overlay {
constructor(game) { constructor(game) {
let root = mk('table.level-browser'); super(game, mk('div.dialog'));
this.root.append(
this.header = mk('header'),
this.main = mk('section'),
this.footer = mk('footer'),
);
}
set_title(title) {
this.header.textContent = '';
this.header.append(mk('h1', {}, title));
}
add_button(label, onclick) {
let button = mk('button', {type: 'button'}, label);
button.addEventListener('click', onclick);
this.footer.append(button);
}
}
// Yes/no popup dialog
class ConfirmOverlay extends DialogOverlay {
constructor(game, message, what) {
super(game);
this.set_title("just checking");
this.main.append(mk('p', {}, message));
let yes = mk('button', {type: 'button'}, "yep");
let no = mk('button', {type: 'button'}, "nope");
yes.addEventListener('click', ev => {
this.close();
what();
});
no.addEventListener('click', ev => {
this.close();
});
this.footer.append(yes, no);
}
}
// About dialog
const ABOUT_HTML = `
<p>Welcome to Lexy's Labyrinth, an exciting old-school tile-based puzzle adventure that is compatible with — but legally distinct from — <a href="https://store.steampowered.com/app/346850/Chips_Challenge_1/">Chip's Challenge</a> and its exciting sequel <a href="https://store.steampowered.com/app/348300/Chips_Challenge_2/">Chip's Challenge 2</a>.</p>
<p>This is a reimplementation from scratch of the game and uses none of its original code or assets. It aims to match the behavior of the Steam releases (sans obvious bugs), since those are now the canonical versions of the game, but compatibility settings aren't off the table.</p>
<p>The default level pack is the community-made <a href="https://wiki.bitbusters.club/Chip%27s_Challenge_Level_Pack_1">Chip's Challenge Level Pack 1</a>, which I had no hand in whatsoever; please follow the link for full attribution. With any luck, future releases will include other community level packs, the ability to play your own, and even a way to play the original levels once you've purchased them on Steam!</p>
<p>Source code is on <a href="https://github.com/eevee/lexys-labyrinth">GitHub</a>.</p>
<p>Special thanks to the incredibly detailed <a href="https://bitbusters.club/">Bit Busters Club</a> and its associated wiki and Discord, the latter of which is full of welcoming people who've been more than happy to answer all my burning arcane questions about Chip's Challenge mechanics. Thank you also to <a href="https://tw2.bitbusters.club/">Tile World</a>, an open source Chip's Challenge 1 emulator whose source code was indispensable, and the origin of the default tileset.</p>
`;
class AboutOverlay extends DialogOverlay {
constructor(game) {
super(game);
this.set_title("about");
this.main.innerHTML = ABOUT_HTML;
this.add_button("cool", ev => {
this.close();
});
}
}
// Options dialog
// functionality?:
// - store local levels and tilesets in localstorage? (will duplicate space but i'll be able to remember them)
// aesthetics:
// - tileset
// - animations on or off
// compat:
// - flicking
// - that cc2 hook wrapping thing
// - that cc2 thing where a brown button sends a 1-frame pulse to a wired trap
// - cc2 something about blue teleporters at 0, 0 forgetting they're looking for unwired only
// - monsters go in fire
// - rff blocks monsters
// - rff truly random
// - all manner of fucking bugs
class OptionsOverlay extends DialogOverlay {
constructor(game) {
super(game);
this.set_title("options");
this.main.append(mk('p', "Sorry! None implemented yet."));
this.add_button("well alright then", ev => {
this.close();
});
}
}
// List of levels
class LevelBrowserOverlay extends DialogOverlay {
constructor(game) {
super(game);
this.set_title("choose a level");
let table = mk('table.level-browser');
this.main.append(table);
for (let [i, stored_level] of game.stored_game.levels.entries()) { for (let [i, stored_level] of game.stored_game.levels.entries()) {
root.append(mk('tr', table.append(mk('tr',
{'data-index': i}, {'data-index': i},
mk('td', i + 1), mk('td', i + 1),
mk('td', stored_level.title), mk('td', stored_level.title),
@ -687,7 +782,7 @@ class LevelBrowserOverlay extends Overlay {
)); ));
} }
root.addEventListener('click', ev => { table.addEventListener('click', ev => {
let tr = ev.target.closest('table.level-browser tr'); let tr = ev.target.closest('table.level-browser tr');
if (! tr) if (! tr)
return; return;
@ -697,23 +792,9 @@ class LevelBrowserOverlay extends Overlay {
this.close(); this.close();
}); });
super(game, mk('div.dialog', root)); this.add_button("nevermind", ev => {
}
}
class ConfirmOverlay extends Overlay {
constructor(game, message, what) {
let root = mk('div.dialog', mk('p', {}, message));
let yes = mk('button', {type: 'button'}, "Yep");
let no = mk('button', {type: 'button'}, "Nope");
yes.addEventListener('click', ev => {
this.close();
what();
});
no.addEventListener('click', ev => {
this.close(); this.close();
}); });
root.append(yes, no);
super(game, root);
} }
} }
@ -724,7 +805,6 @@ class ConfirmOverlay extends Overlay {
// - bonus points (cc2 only, or maybe only if got any so far this level) // - bonus points (cc2 only, or maybe only if got any so far this level)
// - intro splash with list of available level packs // - intro splash with list of available level packs
// - button: quit to splash // - button: quit to splash
// - button: options
// - implement winning and show score for this level // - implement winning and show score for this level
// - show current score so far // - show current score so far
// - about, help // - about, help
@ -732,9 +812,9 @@ const GAME_UI_HTML = `
<header> <header>
<h1>Lexy's Labyrinth</h1> <h1>Lexy's Labyrinth</h1>
<nav> <nav>
<button class="nav-about" type="button" disabled>about</button> <button class="nav-about" type="button">about</button>
<button class="nav-help" type="button" disabled>help</button> <button class="nav-help" type="button" disabled>help</button>
<button class="nav-options" type="button" disabled>options</button> <button class="nav-options" type="button">options</button>
</nav> </nav>
</header> </header>
<main> <main>
@ -836,6 +916,15 @@ class Game {
this.input_el = this.container.querySelector('.input'); this.input_el = this.container.querySelector('.input');
this.demo_el = this.container.querySelector('.demo'); this.demo_el = this.container.querySelector('.demo');
// Populate stuff
let header = document.body.querySelector('body > header');
header.querySelector('.nav-about').addEventListener('click', ev => {
new AboutOverlay(this).open();
});
header.querySelector('.nav-options').addEventListener('click', ev => {
new OptionsOverlay(this).open();
});
// Populate navigation // Populate navigation
let nav_el = this.container.querySelector('.nav'); let nav_el = this.container.querySelector('.nav');
this.nav_prev_button = nav_el.querySelector('.nav-prev'); this.nav_prev_button = nav_el.querySelector('.nav-prev');

View File

@ -9,6 +9,7 @@ body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
line-height: 1.25;
background: #101214; background: #101214;
color: white; color: white;
} }
@ -26,6 +27,15 @@ h1, h2, h3, h4, h5, h6 {
font-weight: normal; font-weight: normal;
margin: 0; margin: 0;
} }
p {
margin: 0.5em 0;
}
p:first-child {
margin-top: 0;
}
p:last-child {
margin-bottom: 0;
}
/* Main page structure */ /* Main page structure */
body > header { body > header {
@ -43,6 +53,7 @@ body > header > nav {
gap: 0.5em; gap: 0.5em;
} }
/* Overlay styling */
.overlay { .overlay {
display: flex; display: flex;
align-items: center; align-items: center;
@ -53,19 +64,63 @@ body > header > nav {
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
background: #0004; background: #fff4;
} }
.dialog { .dialog {
display: flex;
flex-direction: column;
min-width: 33%;
max-width: 80%; max-width: 80%;
max-height: 80%; max-height: 80%;
overflow: auto;
padding: 1em;
border: 1px solid black; border: 1px solid black;
color: black; color: black;
background: #e8e8e8; background: #f4f4f4;
box-shadow: 0 1px 3px #000c; box-shadow: 0 1px 3px #000c;
} }
.dialog > header {
padding: 0.5em;
line-height: 1;
background: hsl(225, 20%, 40%);
color: white;
}
.dialog > header h1 {
font-size: 1em;
}
.dialog > footer {
display: flex;
justify-content: end;
gap: 0.5em;
padding: 0.5em;
background: #d0d0d0;
}
.dialog > header:empty,
.dialog > footer:empty {
display: none;
}
.dialog > section {
overflow: auto;
padding: 1em;
}
/* Individual overlays */
table.level-browser {
width: 100%;
/* for some reason the table ignores the bottom padding when it overflows */
margin-bottom: 1em;
line-height: 1.25;
border-spacing: 0;
cursor: pointer;
}
table.level-browser td {
padding: 0 0.25em;
}
table.level-browser tr:hover {
background: hsl(225, 60%, 90%);
}
/* Game area */
main { main {
flex: 0; flex: 0;
margin: auto; /* center in both directions baby */ margin: auto; /* center in both directions baby */
@ -278,5 +333,5 @@ dl.score-chart .-sum {
.input-action[data-action=drop] { grid-area: drop; } .input-action[data-action=drop] { grid-area: drop; }
.input-action.--pressed { .input-action.--pressed {
color: white; color: white;
background: hsl(215, 75%, 25%); background: hsl(225, 75%, 25%);
} }