🎵
title by
author
diff --git a/js/main.js b/js/main.js
index 514ec1c..f9a5265 100644
--- a/js/main.js
+++ b/js/main.js
@@ -469,8 +469,7 @@ class Player extends PrimaryView {
this.tic_offset = 0;
this.last_advance = 0; // performance.now timestamp
- // Auto-size the level canvas, both now and on resize
- this.adjust_scale();
+ // Auto-size the level canvas on resize
window.addEventListener('resize', ev => {
this.adjust_scale();
});
@@ -1467,11 +1466,19 @@ class Editor extends PrimaryView {
const BUILTIN_LEVEL_PACKS = [{
path: 'levels/CCLP1.ccl',
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',
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 {
@@ -1479,22 +1486,26 @@ class Splash extends PrimaryView {
super(conductor, document.body.querySelector('main#splash'));
// 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) {
- let li = mk('li',
+ let button = mk('button.button-big.level-pack-button',
mk('h3', packdef.title),
mk('p', packdef.desc),
+ mk('span.-score', "unplayed"),
);
- li.addEventListener('click', ev => {
+ button.addEventListener('click', ev => {
this.fetch_pack(packdef.path, packdef.title);
});
- pack_list.append(li);
+ pack_list.append(button);
}
// Bind to file upload control
let upload_el = this.root.querySelector('#splash-upload');
// Clear it out in case of refresh
upload_el.value = '';
+ this.root.querySelector('#splash-upload-button').addEventListener('click', ev => {
+ upload_el.click();
+ });
upload_el.addEventListener('change', async ev => {
let file = ev.target.files[0];
let buf = await file.arrayBuffer();
@@ -1559,11 +1570,18 @@ class Splash extends PrimaryView {
// About dialog
const ABOUT_HTML = `
-
Welcome to Lexy's Labyrinth, an exciting old-school tile-based puzzle adventure that is compatible with — but legally distinct from — Chip's Challenge and its exciting sequel Chip's Challenge 2.
+
Welcome to Lexy's Labyrinth, an exciting old-school tile-based puzzle adventure that is compatible with — but legally distinct from! — Chip's Challenge and its long-awaited sequel Chip's Challenge 2.
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.
-
The default level pack is the community-made Chip's Challenge Level Pack 1, 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!
+
The default level pack is the community-made Chip's Challenge Level Pack 1, which I had no hand in whatsoever; please follow the link for full attribution.
Source code is on GitHub.
-
Special thanks to the incredibly detailed Bit Busters Club 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 Tile World, an open source Chip's Challenge 1 emulator whose source code was indispensable, and the origin of the default tileset.
+
Special thanks to:
+
+ - The lovingly maintained Bit Busters Club, its incredibly detailed wiki, and its Discord 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.
+ - Tile World, the original Chip's Challenge 1 emulator whose source code was indispensable.
+ - Everyone who contributed to the soundtrack, without whom there would still only be one song.
+ - Chuck Somerville, for creating the original game!
+
+
Not affiliated with, endorsed by, aided by, or done with the permission of Chuck Somerville, Niffler Inc., or Alpha Omega Productions.
`;
class AboutOverlay extends DialogOverlay {
constructor(conductor) {
diff --git a/levels/CCLP4.ccl b/levels/CCLP4.ccl
new file mode 100644
index 0000000..658bb65
Binary files /dev/null and b/levels/CCLP4.ccl differ
diff --git a/levels/CCLXP2.ccl b/levels/CCLXP2.ccl
new file mode 100644
index 0000000..dfa93dd
Binary files /dev/null and b/levels/CCLXP2.ccl differ
diff --git a/style.css b/style.css
index 8e3bb11..cee64a3 100644
--- a/style.css
+++ b/style.css
@@ -10,9 +10,13 @@ body {
flex-direction: column;
font-family: Ubuntu, Source Sans Pro, DejaVu Sans, sans-serif;
- line-height: 1.25;
- background: #101214;
- color: white;
+ line-height: 1.33;
+ background: #080808;
+ 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 */
@@ -28,11 +32,27 @@ input[type=range] {
button {
font-size: inherit;
padding: 0.25em 0.5em;
- border: 1px solid black;
font-family: inherit;
- background: #909090;
- border-image: url(button.png) 33.333% fill / auto repeat;
+ color: white;
+ background: var(--button-bg-color);
+ border: none;
+ border-radius: 0.25em;
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 {
font-weight: normal;
@@ -43,6 +63,14 @@ ul, ol {
padding: 0;
list-style: none;
}
+ul.normal-list {
+ margin-left: 1em;
+ list-style: disc;
+}
+ol.normal-list {
+ margin-left: 1.5em;
+ list-style: decimal;
+}
p {
margin: 0.5em 0;
}
@@ -53,6 +81,27 @@ p:last-child {
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 {
display: flex;
@@ -71,8 +120,8 @@ p:last-child {
flex-direction: column;
min-width: 33%;
- max-width: 80%;
- max-height: 80%;
+ max-width: 75%;
+ max-height: 75%;
border: 1px solid black;
color: black;
background: #f4f4f4;
@@ -201,22 +250,19 @@ img.compat-icon {
body > header {
display: flex;
align-items: center;
- background: #00080c;
-}
-body > header h1,
-body > header h2,
-body > header h3 {
- margin: 0.25rem 0.5rem;
+ gap: 0.5em;
+
+ margin: 0.25em;
line-height: 1.125;
}
body > header h1 {
- font-size: 1.66rem;
+ font-size: 1.66em;
}
body > header h2 {
- font-size: 1.33rem;
+ font-size: 1.33em;
}
body > header h3 {
- font-size: 1.25rem;
+ font-size: 1.25em;
}
body > header > nav {
flex: 1;
@@ -239,48 +285,95 @@ body[data-mode=player] #editor-play {
}
#header-main {
- border-bottom: 1px solid #404040;
- box-shadow: 0 0 3px #0009;
+ order: 3;
+ color: #606060;
+}
+#header-level {
+ font-size: 1.5em;
}
/**************************************************************************************************/
/* Splash (intro part) */
#splash {
+ display: grid;
+ grid:
+ "header header header"
+ "intro intro intro"
+ "stock upload yours"
+ / 1fr 1fr 1fr
+ ;
+ gap: 1em;
+
+ padding: 1em 10%;
margin: 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 {
border-bottom: 1px solid #404040;
color: #909090;
text-shadow: 0 1px #0004;
}
-#splash * + h2 {
- margin-top: 2rem;
+#splash > section {
+ 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 {
- margin: 1em 0;
+button.level-pack-button {
+ display: grid;
+ grid:
+ "title score"
+ "desc desc"
+ / 1fr min-content
+ ;
+
+ padding: 0.5em;
+ text-align: left;
}
-#level-pack-list > li {
- margin: 1em 0;
- 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;
+button.level-pack-button h3 {
+ grid-area: title;
}
-#level-pack-list > li:hover {
- background: hsl(45, 60%, 90%);
- border-color: hsl(45, 80%, 50%);
+button.level-pack-button .-score {
+ grid-area: score;
+ 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-style: italic;
- color: #606060;
+ color: #c0c0c0;
}
/**************************************************************************************************/
@@ -288,23 +381,11 @@ body[data-mode=player] #editor-play {
#player {
flex: 0;
+ display: flex;
+ flex-direction: column;
+ justify-content: stretch;
+ gap: 0.5em;
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: pixelated;
@@ -313,6 +394,29 @@ body[data-mode=player] #editor-play {
--tile-height: 32px;
--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 {
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;
}
#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 {
display: grid;
@@ -388,13 +493,6 @@ dl.score-chart .-sum {
color: hsl(40, 75%, 80%);
}
-.meta {
- grid-area: meta;
-
- color: yellow;
- background: black;
- text-align: center;
-}
.chips {
grid-area: chips;
}
@@ -417,6 +515,7 @@ dl.score-chart .-sum {
flex: 1;
font-size: 1.25em;
line-height: 1;
+ color: hsl(225, 20%, 90%);
}
.chips output,
.time output,
@@ -429,25 +528,19 @@ dl.score-chart .-sum {
line-height: 1;
text-align: right;
font-family: monospace;
- color: hsl(45, 100%, 40%);
- background: hsl(0, 0%, 3%);
- border: 1px inset #202020;
+ color: hsl(225, 20%, 60%);
}
.chips output.--done {
- color: hsl(90, 100%, 40%);
- background: hsl(90, 50%, 3%);
+ color: hsl(225, 10%, 30%);
}
.time output.--warning {
color: hsl(30, 100%, 40%);
- background: hsl(0, 50%, 5%);
}
.time output.--danger {
- color: hsl(0, 100%, 40%);
- background: hsl(0, 75%, 8%);
+ color: hsl(345, 60%, 60%);
}
.time output.--frozen {
- color: hsl(195, 100%, 40%);
- background: hsl(195, 25%, 3%);
+ color: hsl(225, 10%, 30%);
}
#player .bonus {
visibility: hidden;
@@ -520,26 +613,6 @@ dl.score-chart .-sum {
#player-music #player-music-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 {
width: 8em;
}