aboutsummaryrefslogtreecommitdiff
path: root/kaios
diff options
context:
space:
mode:
Diffstat (limited to 'kaios')
-rwxr-xr-xkaios/apppage.pl337
-rwxr-xr-xkaios/manifest.pl36
2 files changed, 373 insertions, 0 deletions
diff --git a/kaios/apppage.pl b/kaios/apppage.pl
new file mode 100755
index 0000000..ecdffbc
--- /dev/null
+++ b/kaios/apppage.pl
@@ -0,0 +1,337 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+@ARGV == 2 or die "usage: apppage.pl <name> <displayname>";
+my ($name, $displayname) = @ARGV;
+
+print <<EOF;
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=ASCII" />
+<meta name="theme-color" content="rgb(50,50,50)" />
+<title>${displayname}</title>
+<script defer type="text/javascript" src="${name}.js"></script>
+<!-- Override some defaults for small screens -->
+<script id="environment" type="application/json">
+{ "PATTERN_DEFAULT": "10x10" }
+</script>
+<style class="text/css">
+body {
+ margin: 0;
+ display: flex;
+ position: fixed;
+ width: 100%;
+ top: 0;
+ bottom: 30px;
+ font-size: 17px;
+}
+
+/* Top-level form for the game menu */
+#gamemenu {
+ /* Add a little mild text formatting */
+ font-weight: bold;
+ font-size: 14px;
+ }
+
+/* Inside that form, the main menu bar and every submenu inside it is a <ul> */
+#gamemenu ul {
+ list-style: none; /* get rid of the normal unordered-list bullets */
+ display: flex;
+ margin: 0;
+ /* Compensate for the negative margins on menu items by adding a
+ * little bit of padding so that the borders of the items don't protrude
+ * beyond the menu. */
+ padding: 0.5px;
+ /* Switch to vertical stacking, for drop-down submenus */
+ flex-direction: column;
+ /* We must specify an explicit background colour for submenus, because
+ * they must be opaque (don't want other page contents showing through
+ * them). */
+ background: white;
+}
+
+/* Individual menu items are <li> elements within such a <ul> */
+#gamemenu li {
+ /* Suppress the text-selection I-beam pointer */
+ cursor: default;
+ /* Surround each menu item with a border. */
+ border: 1px solid rgb(180,180,180);
+ /* Arrange that the borders of each item overlap the ones next to it. */
+ margin: -0.5px;
+}
+
+#gamemenu ul li[role=separator] {
+ color: transparent;
+ border: 0;
+}
+
+/* The interactive contents of menu items are their child elements. */
+#gamemenu li > * {
+ padding: 0.2em 0.75em;
+ margin: 0;
+ display: block;
+}
+
+
+#gamemenu :disabled {
+ /* Grey out disabled buttons */
+ color: rgba(0,0,0,0.5);
+}
+
+#gamemenu li > :hover:not(:disabled),
+#gamemenu li > .focus-within {
+ /* When the mouse is over a menu item, highlight it */
+ background-color: rgba(0,0,0,0.3);
+}
+
+.transient {
+ /* When they are displayed, they are positioned immediately above
+ * their parent <li>, and with the left edge aligning */
+ position: fixed;
+ bottom: 30px;
+ max-height: calc(100vh - 30px);
+ left: 100%;
+ transition: left 0.1s;
+ box-sizing: border-box;
+ width: 100vw;
+ overflow: auto;
+ /* And make sure they appear in front. */
+ z-index: 50;
+}
+
+.transient.focus-within {
+ /* Once a menu is actually focussed, bring it on screen. */
+ left: 0;
+ /* Hiding what's behind. */
+ box-shadow: 0 0 1em 0 rgba(0, 0, 0, 0.8);
+}
+
+#gamemenu :hover > ul,
+#gamemenu .focus-within > ul {
+ /* Last but by no means least, the all-important line that makes
+ * submenus be displayed! Any <ul> whose parent <li> is being
+ * hovered over gets display:flex overriding the display:none
+ * from above. */
+ display: flex;
+}
+
+#gamemenu button {
+ /* Menu items that trigger an action. We put some effort into
+ * removing the default button styling. */
+ -moz-appearance: none;
+ -webkit-appearance: none;
+ appearance: none;
+ font: inherit;
+ color: inherit;
+ background: initial;
+ border: initial;
+ border-radius: initial;
+ text-align: inherit;
+ width: 100%;
+}
+
+#gamemenu .tick {
+ /* The tick at the start of a menu item, or its unselected equivalent.
+ * This is represented by an <input type="radio">, so we put some
+ * effort into overriding the default style. */
+ -moz-appearance: none;
+ -webkit-appearance: none;
+ appearance: none;
+ margin: initial;
+ font: inherit;
+}
+
+#gamemenu .tick::before {
+ content: "\\2713";
+}
+
+#gamemenu .tick:not(:checked) {
+ /* Tick for an unselected menu entry. */
+ color: transparent;
+}
+
+#gamemenu li > div::after {
+ content: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='10'%20height='10'%3E%3Cpolygon%20points='0,0,10,5,0,10'/%3E%3C/svg%3E");
+ float: right;
+}
+
+#puzzle {
+ background: var(--puzzle-background, #e6e6e6);
+ flex: 1 1 auto;
+ flex-direction: column;
+ align-items: center;
+ display: flex;
+ width: 100%
+}
+
+#statusbar {
+ overflow: hidden;
+ text-align: left;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ line-height: 1;
+ background: #d8d8d8;
+ border-left: 2px solid #c8c8c8;
+ border-top: 2px solid #c8c8c8;
+ border-right: 2px solid #e8e8e8;
+ border-bottom: 2px solid #e8e8e8;
+ height: 1em;
+}
+
+#dlgdimmer {
+ width: 100%;
+ height: 100%;
+ background: #000000;
+ position: fixed;
+ opacity: 0.3;
+ left: 0;
+ top: 0;
+ z-index: 99;
+}
+
+#dlgform {
+ width: 66.6667vw;
+ opacity: 1;
+ background: #ffffff;
+ color: #000000;
+ position: absolute;
+ border: 2px solid black;
+ padding: 20px;
+ top: 10vh;
+ left: 16.6667vw;
+ z-index: 100;
+}
+
+#dlgform h2 {
+ margin-top: 0px;
+}
+
+#puzzlecanvascontain {
+ flex: 1 1 auto;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ min-width: 0;
+ min-height: 0;
+}
+
+#puzzlecanvas {
+ max-width: 100%;
+ max-height: 100%;
+ background-color: white;
+ font-weight: 600;
+}
+
+#puzzlecanvas:focus {
+ /* The focus will be here iff there's nothing else on
+ * screen that can be focused, so the outline is
+ * redundant. */
+ outline: none;
+}
+
+#puzzle > div {
+ width: 100%;
+}
+
+.softkey {
+ position: fixed;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ height: 30px;
+ font-weight: 600;
+ font-size: 14px;
+ line-height: 1;
+ white-space: nowrap;
+ background: rgb(50,50,50);
+ color: white;
+ z-index: 150;
+}
+
+:not(.focus-within) > .softkey {
+ display: none;
+}
+
+.softkey > * {
+ position: absolute;
+ padding: 8px;
+}
+
+.lsk {
+ left: 0;
+ right: 70%;
+ text-align: left;
+ padding-right: 0;
+}
+
+.csk {
+ left: 30%;
+ right: 30%;
+ text-align: center;
+ text-transform: uppercase;
+ padding-left: 0;
+ padding-right: 0;
+}
+
+.rsk {
+ right: 0;
+ left: 70%;
+ text-align: right;
+ padding-left: 0
+}
+
+</style>
+</head>
+<body>
+<div id="puzzle">
+ <div id="puzzlecanvascontain">
+ <canvas id="puzzlecanvas" width="1px" height="1px" tabindex="0">
+ </canvas>
+ </div>
+ <div id="statusbar">
+ </div>
+ <div class="softkey"><div class="rsk">Menu</div></div>
+</div>
+<form id="gamemenu" class="transient">
+ <ul>
+ <li><div tabindex="0">Game<ul class="transient">
+ <li><button type="button" id="specific">Enter game ID...</button></li>
+ <li><button type="button" id="random">Enter random seed...</button></li>
+ <li><button type="button" id="save">Download save file...</button></li>
+ <li><button type="button" id="load">Upload save file...</button></li>
+ </ul></div></li>
+ <li><div tabindex="0">Type<ul id="gametype" class="transient"></ul></div></li>
+ <li role="separator"></li>
+ <li><button type="button" id="new">
+ New<span class="verbiage"> game</span>
+ </button></li>
+ <li><button type="button" id="restart">
+ Restart<span class="verbiage"> game</span>
+ </button></li>
+ <li><button type="button" id="undo">
+ Undo<span class="verbiage"> move</span>
+ </button></li>
+ <li><button type="button" id="redo">
+ Redo<span class="verbiage"> move</span>
+ </button></li>
+ <li><button type="button" id="solve">
+ Solve<span class="verbiage"> game</span>
+ </button></li>
+ <li><a target="_blank" href="help/en/${name}.html#${name}">
+ Instructions
+ </a></li>
+ <li><a target="_blank" href="help/en/index.html">
+ Full manual
+ </a></li>
+ </ul>
+ <div class="softkey">
+ <div class="csk">Select</div>
+ <div class="rsk">Dismiss</div>
+ </div>
+</form>
+</body>
+</html>
+EOF
diff --git a/kaios/manifest.pl b/kaios/manifest.pl
new file mode 100755
index 0000000..36fc195
--- /dev/null
+++ b/kaios/manifest.pl
@@ -0,0 +1,36 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use JSON::PP;
+
+@ARGV == 4 or
+ die "usage: manifest.pl <name> <displayname> <description> <objective>";
+my ($name, $displayname, $description, $objective) = @ARGV;
+
+# Limits from
+# https://developer.kaiostech.com/docs/getting-started/main-concepts/manifest
+length($displayname) <= 20 or die "Name too long: $displayname";
+length($description) <= 40 or die "Subtitle too long: $description";
+$objective .= " Part of Simon Tatham's Portable Puzzle Collection.";
+# https://developer.kaiostech.com/docs/distribution/submission-guideline
+length($objective) <= 220 or die "Description too long: $objective";
+
+print encode_json({
+ name => $displayname,
+ subtitle => $description,
+ description => $objective,
+ launch_path => "/${name}.html",
+ icons => {
+ "56" => "/${name}-56kai.png",
+ "112" => "/${name}-112kai.png",
+ },
+ developer => {
+ name => "Ben Harris",
+ url => "https://bjh21.me.uk",
+ },
+ default_locale => "en-GB",
+ categories => ["games"],
+ cursor => JSON::PP::false,
+})