diff options
Diffstat (limited to 'kaios')
| -rwxr-xr-x | kaios/apppage.pl | 337 | ||||
| -rwxr-xr-x | kaios/manifest.pl | 36 |
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, +}) |