Overhaul the UI to be more good pretty; add CCLP2+4

This commit is contained in:
Eevee (Evelyn Woods) 2020-09-24 20:28:09 -06:00
parent c231e7fc53
commit 6aee8ed622
7 changed files with 258 additions and 136 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 277 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 260 B

After

Width:  |  Height:  |  Size: 257 B

View File

@ -11,8 +11,9 @@
<meta name="og:title" content="Lexy's Labyrinth"> <meta name="og:title" content="Lexy's Labyrinth">
<meta name="og:description" content="A (work in progress) reimplementation of Chip's Challenge 1 and 2, using entirely free assets."> <meta name="og:description" content="A (work in progress) reimplementation of Chip's Challenge 1 and 2, using entirely free assets.">
</head> </head>
<body> <body data-mode="splash">
<header id="header-main"> <header id="header-main">
<img src="icon.png" alt="">
<h1>Lexy's Labyrinth</h1> <h1>Lexy's Labyrinth</h1>
<nav> <nav>
<button id="main-about" type="button">about</button> <button id="main-about" type="button">about</button>
@ -31,47 +32,77 @@
<header id="header-level"> <header id="header-level">
<h3 id="level-name">Level 1 — Key Pyramid</h3> <h3 id="level-name">Level 1 — Key Pyramid</h3>
<nav> <nav>
<button id="main-prev-level" type="button">⬅️&#xfe0e;</button> <button id="main-prev-level" type="button"></button>
<button id="main-choose-level" type="button">Level select</button> <button id="main-choose-level" type="button">Level select</button>
<button id="main-next-level" type="button">➡️&#xfe0e;</button> <button id="main-next-level" type="button"></button>
</nav> </nav>
</header> </header>
<main id="splash"> <main id="splash">
<h2>Community levels</h2> <header>
<ul id="level-pack-list"> <h1><img src="og-preview.png" alt="">Lexy's Labyrinth</h1>
</ul> </header>
<section id="splash-intro">
<p><strong>Welcome</strong> to Lexy's Labyrinth, an open source puzzle game that is curiously similar to — but legally distinct from — the Atari classic <a href="https://en.wikipedia.org/wiki/Chip%27s_Challenge">Chip's Challenge</a>!</p>
<p>(This is a Chip's Challenge <em>emulator</em>, designed to be an accessible way to play community-made levels with free assets. It's 99% compatible with Chip's Challenge 1, and support for Chip's Challenge 2 is underway. But you can safely ignore all that and treat this as its own game.)</p>
<p>Please note that <em>levels themselves</em> may contain hints or lore referring to a guy named Chip collecting computer chips, even though you are clearly a fox named Lexy collecting hearts. Weird, right? Sorry for any confusion!</p>
<p>Pick a level pack to get started! You can also get more technical details or report bugs on <a href="https://github.com/eevee/lexys-labyrinth">GitHub</a>, find out more about Chip's Challenge via the <a href="https://bitbusters.club/">Bit Busters Club</a> fansite, or support this endeavor (and other things I do) via <a href="https://www.patreon.com/eevee">Patreon</a>!</p>
<!-- TODO i want to make clear this is a chip's challenge emulator without bogging people down too much about what that means -->
</section>
<h2>Commercial and other levels</h2> <section id="splash-stock-levels">
<p>You can play the original levels, or any you've downloaded from the web!</p> <h2>Just play something</h2>
<!-- TODO explain how to find chips.dat or steam folder --> <!-- populated by js -->
<!-- TODO drag and drop? --> </section>
<p><input id="splash-upload" type="file" accept=".dat,.ccl,.c2m,.ccs"></p>
<h2>Make your own (WIP lol)</h2> <section id="splash-upload-levels">
<p><button type="button" id="splash-create-level">Create a level</button></p> <h2>Other levels</h2>
<p>You can play any levels you have lying around, or perhaps ones you found on the <a href="https://sets.bitbusters.club/">Bit Busters Club set list</a>!</p>
<!-- TODO explain how to find chips.dat or steam folder -->
<!-- TODO drag and drop? -->
<input id="splash-upload" type="file" accept=".dat,.ccl,.c2m,.ccs">
<button type="button" id="splash-upload-button" class="button-big">Open a local level<br>(or drag and drop a file into this window)</button>
<p>Supports both the old Microsoft <code>CHIPS.DAT</code> format and the Steam <code>C2M</code> format.</p>
<p>Does <em>not</em> yet support the Steam <code>C2G</code> format, so tragically, the original Steam levels can only be played one at a time! This should be fixed soon!</p>
<!--
<p>If you own the Steam versions of <a href="https://store.steampowered.com/app/346850/Chips_Challenge_1/">Chip's Challenge 1</a> (<em>free!</em>) or <a href="https://store.steampowered.com/app/348300/Chips_Challenge_2/">Chip's Challenge 2</a> ($5 last I checked), you can play those too, even on Linux or Mac:</p>
<ol class="normal-list">
<li>Right-click the game in Steam and choose <em>Properties</em>. On the <em>Local Files</em> tab, click <em>Browse local files</em>.</li>
<li>Open the <code>data</code> folder, then <code>games</code>.</li>
<li>You should see either a <code>cc1</code> or <code>cc2</code> folder. Drag it into this window.</li>
</ol>
-->
</section>
<section id="splash-your-levels">
<h2>Make your own (WIP lol)</h2>
<p>Please note that the level editor is <strong>extremely</strong> unfinished, and can't even save yet.</p>
<p><button type="button" id="splash-create-level" class="button-big">Create a level</button></p>
</section>
</main> </main>
<main id="player" hidden> <main id="player" hidden>
<div class="level"><!-- level canvas and any overlays go here --></div> <section class="-main-area">
<div class="overlay-message"> <div class="level"><!-- level canvas and any overlays go here --></div>
<h1 class="-top"></h1> <div class="overlay-message">
<div class="-middle"></div> <h1 class="-top"></h1>
<p class="-bottom"></p> <div class="-middle"></div>
<p class="-keyhint"></p> <p class="-bottom"></p>
</div> <p class="-keyhint"></p>
<div class="message"></div> </div>
<div class="chips"> <div class="message"></div>
<h3>Chips</h3> <div class="chips">
<output></output> <h3>Chips</h3>
</div> <output></output>
<div class="time"> </div>
<h3>Time</h3> <div class="time">
<output></output> <h3>Time</h3>
</div> <output></output>
<div class="bonus"> </div>
<h3>Bonus</h3> <div class="bonus">
<output></output> <h3>Bonus</h3>
</div> <output></output>
<div class="inventory"></div> </div>
<div class="inventory"></div>
</section>
<div id="player-music"> <div id="player-music">
<div id="player-music-left"> <div id="player-music-left">
🎵 <a id="player-music-title">title</a> by <a id="player-music-author">author</a> 🎵 <a id="player-music-title">title</a> by <a id="player-music-author">author</a>

View File

@ -469,8 +469,7 @@ class Player extends PrimaryView {
this.tic_offset = 0; this.tic_offset = 0;
this.last_advance = 0; // performance.now timestamp this.last_advance = 0; // performance.now timestamp
// Auto-size the level canvas, both now and on resize // Auto-size the level canvas on resize
this.adjust_scale();
window.addEventListener('resize', ev => { window.addEventListener('resize', ev => {
this.adjust_scale(); this.adjust_scale();
}); });
@ -1467,11 +1466,19 @@ class Editor extends PrimaryView {
const BUILTIN_LEVEL_PACKS = [{ const BUILTIN_LEVEL_PACKS = [{
path: 'levels/CCLP1.ccl', path: 'levels/CCLP1.ccl',
title: "Chip's Challenge Level Pack 1", title: "Chip's Challenge Level Pack 1",
desc: "Intended as an introduction to Chip's Challenge 1 for new players. Recommended.", desc: "Designed and recommended for new players, starting with gentle introductory levels. A prequel to the other packs.",
}, {
path: 'levels/CCLP4.ccl',
title: "Chip's Challenge Level Pack 4",
desc: "Moderately difficult, but not unfair.",
}, {
path: 'levels/CCLXP2.ccl',
title: "Chip's Challenge Level Pack 2-X",
desc: "The first community pack released, tricky and rough around the edges.",
}, { }, {
path: 'levels/CCLP3.ccl', path: 'levels/CCLP3.ccl',
title: "Chip's Challenge Level Pack 3", title: "Chip's Challenge Level Pack 3",
desc: "Another community level pack.", desc: "A tough challenge, by and for veteran players.",
}]; }];
class Splash extends PrimaryView { class Splash extends PrimaryView {
@ -1479,22 +1486,26 @@ class Splash extends PrimaryView {
super(conductor, document.body.querySelector('main#splash')); super(conductor, document.body.querySelector('main#splash'));
// Populate the list of available level packs // Populate the list of available level packs
let pack_list = document.querySelector('#level-pack-list'); let pack_list = document.querySelector('#splash-stock-levels');
for (let packdef of BUILTIN_LEVEL_PACKS) { for (let packdef of BUILTIN_LEVEL_PACKS) {
let li = mk('li', let button = mk('button.button-big.level-pack-button',
mk('h3', packdef.title), mk('h3', packdef.title),
mk('p', packdef.desc), mk('p', packdef.desc),
mk('span.-score', "unplayed"),
); );
li.addEventListener('click', ev => { button.addEventListener('click', ev => {
this.fetch_pack(packdef.path, packdef.title); this.fetch_pack(packdef.path, packdef.title);
}); });
pack_list.append(li); pack_list.append(button);
} }
// Bind to file upload control // Bind to file upload control
let upload_el = this.root.querySelector('#splash-upload'); let upload_el = this.root.querySelector('#splash-upload');
// Clear it out in case of refresh // Clear it out in case of refresh
upload_el.value = ''; upload_el.value = '';
this.root.querySelector('#splash-upload-button').addEventListener('click', ev => {
upload_el.click();
});
upload_el.addEventListener('change', async ev => { upload_el.addEventListener('change', async ev => {
let file = ev.target.files[0]; let file = ev.target.files[0];
let buf = await file.arrayBuffer(); let buf = await file.arrayBuffer();
@ -1559,11 +1570,18 @@ class Splash extends PrimaryView {
// About dialog // About dialog
const ABOUT_HTML = ` 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>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 long-awaited 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>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>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.</p>
<p>Source code is on <a href="https://github.com/eevee/lexys-labyrinth">GitHub</a>.</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> <p>Special thanks to:</p>
<ul class="normal-list">
<li>The lovingly maintained <a href="https://bitbusters.club/">Bit Busters Club</a>, its incredibly detailed <a href="https://wiki.bitbusters.club/Main_Page">wiki</a>, and its <a href="https://discord.gg/Xd4dUY9">Discord</a> full of welcoming and patient folks who've been more than happy to playtest this thing and answer all kinds of arcane questions about Chip's Challenge mechanics.</li>
<li><a href="https://tw2.bitbusters.club/">Tile World</a>, the original Chip's Challenge 1 emulator whose source code was indispensable.</li>
<li>Everyone who contributed to the soundtrack, without whom there would still only be one song.</li>
<li>Chuck Somerville, for creating the original game!</li>
</ul>
<p>Not affiliated with, endorsed by, aided by, or done with the permission of Chuck Somerville, Niffler Inc., or Alpha Omega Productions.</p>
`; `;
class AboutOverlay extends DialogOverlay { class AboutOverlay extends DialogOverlay {
constructor(conductor) { constructor(conductor) {

BIN
levels/CCLP4.ccl Normal file

Binary file not shown.

BIN
levels/CCLXP2.ccl Normal file

Binary file not shown.

255
style.css
View File

@ -10,9 +10,13 @@ body {
flex-direction: column; flex-direction: column;
font-family: Ubuntu, Source Sans Pro, DejaVu Sans, sans-serif; font-family: Ubuntu, Source Sans Pro, DejaVu Sans, sans-serif;
line-height: 1.25; line-height: 1.33;
background: #101214; background: #080808;
color: white; color: #ececec;
--panel-bg-color: hsl(225, 10%, 20%);
--button-bg-color: hsl(225, 10%, 25%);
--button-bg-hover-color: hsl(225, 15%, 30%);
} }
/* Generic element styling */ /* Generic element styling */
@ -28,11 +32,27 @@ input[type=range] {
button { button {
font-size: inherit; font-size: inherit;
padding: 0.25em 0.5em; padding: 0.25em 0.5em;
border: 1px solid black;
font-family: inherit; font-family: inherit;
background: #909090; color: white;
border-image: url(button.png) 33.333% fill / auto repeat; background: var(--button-bg-color);
border: none;
border-radius: 0.25em;
text-transform: lowercase; text-transform: lowercase;
cursor: pointer;
}
button:hover {
background: var(--button-bg-hover-color);
}
button:disabled {
color: #606060;
background: #202020;
cursor: auto;
}
button.button-big {
display: block;
width: 100%;
margin: 0.5em 0;
padding: 1em;
} }
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
font-weight: normal; font-weight: normal;
@ -43,6 +63,14 @@ ul, ol {
padding: 0; padding: 0;
list-style: none; list-style: none;
} }
ul.normal-list {
margin-left: 1em;
list-style: disc;
}
ol.normal-list {
margin-left: 1.5em;
list-style: decimal;
}
p { p {
margin: 0.5em 0; margin: 0.5em 0;
} }
@ -53,6 +81,27 @@ p:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
a {
color: #c0c0c0;
}
a:link,
a:visited {
text-decoration: underline dotted;
}
a:link {
color: hsl(225, 50%, 60%);
}
a:visited {
color: hsl(300, 50%, 60%);
}
a:link:hover,
a:visited:hover {
text-decoration: underline;
}
a:active {
color: hsl(0, 50%, 60%);
}
/* Overlay styling */ /* Overlay styling */
.overlay { .overlay {
display: flex; display: flex;
@ -71,8 +120,8 @@ p:last-child {
flex-direction: column; flex-direction: column;
min-width: 33%; min-width: 33%;
max-width: 80%; max-width: 75%;
max-height: 80%; max-height: 75%;
border: 1px solid black; border: 1px solid black;
color: black; color: black;
background: #f4f4f4; background: #f4f4f4;
@ -201,22 +250,19 @@ img.compat-icon {
body > header { body > header {
display: flex; display: flex;
align-items: center; align-items: center;
background: #00080c; gap: 0.5em;
}
body > header h1, margin: 0.25em;
body > header h2,
body > header h3 {
margin: 0.25rem 0.5rem;
line-height: 1.125; line-height: 1.125;
} }
body > header h1 { body > header h1 {
font-size: 1.66rem; font-size: 1.66em;
} }
body > header h2 { body > header h2 {
font-size: 1.33rem; font-size: 1.33em;
} }
body > header h3 { body > header h3 {
font-size: 1.25rem; font-size: 1.25em;
} }
body > header > nav { body > header > nav {
flex: 1; flex: 1;
@ -239,48 +285,95 @@ body[data-mode=player] #editor-play {
} }
#header-main { #header-main {
border-bottom: 1px solid #404040; order: 3;
box-shadow: 0 0 3px #0009; color: #606060;
}
#header-level {
font-size: 1.5em;
} }
/**************************************************************************************************/ /**************************************************************************************************/
/* Splash (intro part) */ /* Splash (intro part) */
#splash { #splash {
display: grid;
grid:
"header header header"
"intro intro intro"
"stock upload yours"
/ 1fr 1fr 1fr
;
gap: 1em;
padding: 1em 10%;
margin: auto; margin: auto;
overflow: auto; overflow: auto;
} }
#splash > header {
grid-area: header;
text-align: center;
}
#splash > header img {
display: block;
margin: auto;
}
#splash > header h1 {
font-size: 3em;
}
#splash h2 { #splash h2 {
border-bottom: 1px solid #404040; border-bottom: 1px solid #404040;
color: #909090; color: #909090;
text-shadow: 0 1px #0004; text-shadow: 0 1px #0004;
} }
#splash * + h2 { #splash > section {
margin-top: 2rem; padding: 1em;
background: var(--panel-bg-color);
}
#splash > #splash-intro {
grid-area: intro;
font-size: 18px;
}
#splash > #splash-stock-levels {
grid-area: stock;
}
#splash > #splash-upload-levels {
grid-area: upload;
}
#splash-upload {
/* Hide the file upload control, which is ugly */
display: none;
}
#splash > #splash-your-levels {
grid-area: yours;
} }
#level-pack-list { button.level-pack-button {
margin: 1em 0; display: grid;
grid:
"title score"
"desc desc"
/ 1fr min-content
;
padding: 0.5em;
text-align: left;
} }
#level-pack-list > li { button.level-pack-button h3 {
margin: 1em 0; grid-area: title;
padding: 0.5em 1em;
background: #ececec;
color: black;
border: 2px solid black;
border-radius: 0.5em;
box-shadow: inset 0 0 2px 2px #0006, 0 2px 0.25em #0006;
cursor: pointer;
} }
#level-pack-list > li:hover { button.level-pack-button .-score {
background: hsl(45, 60%, 90%); grid-area: score;
border-color: hsl(45, 80%, 50%); font-style: italic;
color: #c0c0c0;
text-align: right;
} }
#level-pack-list > li p { button.level-pack-button p {
grid-area: desc;
font-size: 0.833em; font-size: 0.833em;
font-style: italic; font-style: italic;
color: #606060; color: #c0c0c0;
} }
/**************************************************************************************************/ /**************************************************************************************************/
@ -288,23 +381,11 @@ body[data-mode=player] #editor-play {
#player { #player {
flex: 0; flex: 0;
display: flex;
flex-direction: column;
justify-content: stretch;
gap: 0.5em;
margin: auto; /* center in both directions baby */ margin: auto; /* center in both directions baby */
isolation: isolate;
display: grid;
align-items: center;
grid:
"controls controls" min-content
"level chips" min-content
"level time" min-content
"level bonus" min-content
"level message" 1fr
"level inventory" min-content
"music ." min-content
/* Need explicit min-content to force the hint to wrap */
/ min-content min-content
;
column-gap: 2em;
row-gap: 0.5em;
image-rendering: crisp-edges; image-rendering: crisp-edges;
image-rendering: pixelated; image-rendering: pixelated;
@ -313,6 +394,29 @@ body[data-mode=player] #editor-play {
--tile-height: 32px; --tile-height: 32px;
--scale: 1; --scale: 1;
} }
#player > .-main-area {
isolation: isolate;
display: grid;
align-items: center;
grid:
"level chips" min-content
"level time" min-content
"level bonus" min-content
"level message" 1fr
"level inventory" min-content
/* Need explicit min-content to force the hint to wrap */
/ min-content min-content
;
column-gap: 2em;
row-gap: 0.5em;
padding: 1em;
background: hsl(225, 10%, 20%);
box-shadow: 0 0.25em 1em black;
}
#player > .controls {
order: -1;
}
.level { .level {
grid-area: level; grid-area: level;
@ -365,7 +469,8 @@ body[data-mode=player] #editor-play {
box-shadow: inset 0 0 calc(4 * var(--tile-width)) var(--tile-width) black; box-shadow: inset 0 0 calc(4 * var(--tile-width)) var(--tile-width) black;
} }
#player .overlay-message[data-reason=success] { #player .overlay-message[data-reason=success] {
box-shadow: inset 0 0 var(--tile-width) white; background: hsla(225, 50%, 25%, 0.5);
box-shadow: inset 0 0 calc(4 * var(--tile-width)) hsl(225, 50%, 25%);
} }
dl.score-chart { dl.score-chart {
display: grid; display: grid;
@ -388,13 +493,6 @@ dl.score-chart .-sum {
color: hsl(40, 75%, 80%); color: hsl(40, 75%, 80%);
} }
.meta {
grid-area: meta;
color: yellow;
background: black;
text-align: center;
}
.chips { .chips {
grid-area: chips; grid-area: chips;
} }
@ -417,6 +515,7 @@ dl.score-chart .-sum {
flex: 1; flex: 1;
font-size: 1.25em; font-size: 1.25em;
line-height: 1; line-height: 1;
color: hsl(225, 20%, 90%);
} }
.chips output, .chips output,
.time output, .time output,
@ -429,25 +528,19 @@ dl.score-chart .-sum {
line-height: 1; line-height: 1;
text-align: right; text-align: right;
font-family: monospace; font-family: monospace;
color: hsl(45, 100%, 40%); color: hsl(225, 20%, 60%);
background: hsl(0, 0%, 3%);
border: 1px inset #202020;
} }
.chips output.--done { .chips output.--done {
color: hsl(90, 100%, 40%); color: hsl(225, 10%, 30%);
background: hsl(90, 50%, 3%);
} }
.time output.--warning { .time output.--warning {
color: hsl(30, 100%, 40%); color: hsl(30, 100%, 40%);
background: hsl(0, 50%, 5%);
} }
.time output.--danger { .time output.--danger {
color: hsl(0, 100%, 40%); color: hsl(345, 60%, 60%);
background: hsl(0, 75%, 8%);
} }
.time output.--frozen { .time output.--frozen {
color: hsl(195, 100%, 40%); color: hsl(225, 10%, 30%);
background: hsl(195, 25%, 3%);
} }
#player .bonus { #player .bonus {
visibility: hidden; visibility: hidden;
@ -520,26 +613,6 @@ dl.score-chart .-sum {
#player-music #player-music-right { #player-music #player-music-right {
text-align: right; text-align: right;
} }
#player-music a {
color: #c0c0c0;
}
#player-music a:link,
#player-music a:visited {
text-decoration: underline dotted;
}
#player-music a:link {
color: hsl(225, 50%, 75%);
}
#player-music a:visited {
color: hsl(300, 50%, 75%);
}
#player-music a:link:hover,
#player-music a:visited:hover {
text-decoration: underline;
}
#player-music a:active {
color: hsl(0, 50%, 75%);
}
#player-music #player-music-volume { #player-music #player-music-volume {
width: 8em; width: 8em;
} }