diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | TODO | 5 | ||||
-rw-r--r-- | api.txt | 34 | ||||
-rw-r--r-- | www/css/anim.css | 16 | ||||
-rw-r--r-- | www/css/index.css | 14 | ||||
-rw-r--r-- | www/index.html | 21 | ||||
-rw-r--r-- | www/js/api.js | 104 | ||||
-rw-r--r-- | www/js/html5ks.js | 335 | ||||
-rw-r--r-- | www/js/menu.js | 33 |
9 files changed, 340 insertions, 226 deletions
@@ -1,3 +1,3 @@ # ignore 'copyrighted' contents similar to emulators -/www/dump/* -/rpy/* +/www/dump +/rpy @@ -1,7 +1,8 @@ todo ordered by most to least important... ish -- imachine +- nvl - menu -- extend +- show command +- transitions. ALL THE TRANSITIONS - fix cursor to apply to all document - cache script.json files diff --git a/api.txt b/api.txt deleted file mode 100644 index eff997e..0000000 --- a/api.txt +++ /dev/null @@ -1,34 +0,0 @@ -== imachine{,_replay} api == -label * -> function -jump_out -> seen_scenes & call -call -> call -seen_scene(scene) -> return label in seen_scenes -int attraction_* -path_end(chr, is_good) -iscene(name, is_h, is_end) -> promise -✓ act_op(movie) -> promise -imenu(choice) -> promise - -== script api == -chars: in ui_settings.rpy - e.g. his(text) -> promise -with(transition, action) -> promise -play(stream, name, fade) -stream: sound,music,ambient - -== prefs == -h-scenes (tied to shrinkwrap) -fullscreen? -webp (default on chrome/opera, off on others or webpjs) -text speed -auto delay -music/sfx vol -skip unread -skip after choice - -== context menu == -show image, scrollback, skip, auto, options - -== save/load == -passive: seen_scenes -active: track attraction diff --git a/www/css/anim.css b/www/css/anim.css index ee2889d..e89959d 100644 --- a/www/css/anim.css +++ b/www/css/anim.css @@ -22,3 +22,19 @@ 0% { opacity: 1; } 100% { opacity: 0; } } + +@-webkit-keyframes blink { + 0% { opacity: 0; } + 50% { opacity: 1; } + 100% { opacity: 0; } +} +@-moz-keyframes blink { + 0% { opacity: 0; } + 50% { opacity: 1; } + 100% { opacity: 0; } +} +@keyframes blink { + 0% { opacity: 0; } + 50% { opacity: 1; } + 100% { opacity: 0; } +} diff --git a/www/css/index.css b/www/css/index.css index 533559c..e5a5c19 100644 --- a/www/css/index.css +++ b/www/css/index.css @@ -27,6 +27,8 @@ body { #container, #bg, #vid { cursor: url("../dump/ui/mousecursor.png"), default; position: absolute; +} +#container.scale, #bg.scale, #vid.scale { top: 50%; left: 50%; } @@ -96,7 +98,6 @@ html.no-js #warn-container { h2 { font: 20px/28px Playtime; font-weight: bold; - opacity: 0.4; } html.no-js #warn li:not(#js) { display: none; } #warn li#ie { display: none; } @@ -109,8 +110,7 @@ html #warn li#html-svg-filter { display: none; } label { display: block; } -.button-enabled { -/* cursor: pointer; */ +.button, h2, label { opacity: 0.4; } .button-disabled { @@ -141,3 +141,11 @@ input[type="checkbox"] + span:before { input[type="checkbox"]:checked + span:before { background: url("../dump/ui/bt-cf-checked.png") no-repeat left bottom; } +#ctc { + position: absolute; + bottom: 20px; + right: 10px; + -webkit-animation: blink 2s infinite; + -moz-animation: blink 2s infinite; + animation: blink 2s infinite; +} diff --git a/www/index.html b/www/index.html index 19b109c..b01fea4 100644 --- a/www/index.html +++ b/www/index.html @@ -49,6 +49,7 @@ <img id="window-image" src="dump/ui/bg-say.png" alt=""> <div id="who"></div> <div id="say"></div> + <img id="ctc" src="dump/ui/ctc.png" style="display: none"> </div> <div id="main-menu" style="display: none;"> <div id="main-menu-buttons" style="top: 377px; position: absolute; left: 81px;"> @@ -67,18 +68,18 @@ <div id="dialogs" style="display: none; position: absolute; top: 50%; width: 504px; height: 395px; background: url(dump/ui/bg-config.png); left: 50%; margin-left: -252px; margin-top: -197.5px;"> <div id="options" style="padding: 10px 20px;"> <h2>Options</h2> - <label class="button button-enabled"><input type="checkbox" id="hdisable"> <span>Disable adult content</span></label> - <label class="button button-enabled"><input type="checkbox" id="fullscreen"> <span>Fullscreen mode</span></label> - <label class="button button-enabled"><input type="checkbox" id="skip-unread"> <span>Skip unread text</span></label> - <label class="button button-enabled"><input type="checkbox" id="skip-after-choices"> <span>Keep skipping after choices</span></label> - <label class="button button-enabled"><input type="checkbox" id="use-webp"> <span>Use WebP images (less bandwidth, more CPU)</span></label> - <label class="button button-enabled"><input type="checkbox" id="scale-video"> <span>Scale video to fullscreen (decreases performance)</span></label> + <label class="button button-enabled"><input type="checkbox" class="option" id="hdisable"> <span>Disable adult content</span></label> + <label class="button button-enabled"><input type="checkbox" class="option" id="skipUnread"> <span>Skip unread text</span></label> + <label class="button button-enabled"><input type="checkbox" class="option" id="skipAfterChoices"> <span>Keep skipping after choices</span></label> + <label class="button button-enabled"><input type="checkbox" class="option" id="useWebP"> <span>Use WebP images (less bandwidth, more CPU)</span></label> + <label class="button button-enabled"><input type="checkbox" class="option" id="fullscreen"> <span>Scale content</span></label> + <label class="button button-enabled"><input type="checkbox" class="option" id="scaleVideo"> <span>Scale video</span></label> - <label><input type="range" id="text-speed"> <span class="tr">Text speed</span></label> - <label><input type="range" id="auto-mode-delay"> <span class="tr">Auto mode delay</span></label> + <label><input type="range" min="0.0" max="1.0" step="0.001" class="option" id="textSpeed"> <span class="tr">Text speed</span></label> + <label><input type="range" min="0.0" max="1.0" step="0.001" class="option" id="autoModeDelay"> <span class="tr">Auto mode delay</span></label> - <label><input type="range" id="music-volume"> <span class="tr">Music volume</span></label> - <label><input type="range" id="sfx-volume"> <span class="tr">SFX volume</span> <span id="test-sfx" class="button button-enabled"><img src="dump/ui/bt-musicplay.png"> Test</span></label> + <label><input type="range" min="0.0" max="1.0" step="0.001" class="option" id="musicVolume"> <span class="tr">Music volume</span></label> + <label><input type="range" min="0.0" max="1.0" step="0.001" class="option" id="sfxVolume"> <span class="tr">SFX volume</span> <span id="test-sfx" class="button button-enabled"><img src="dump/ui/bt-musicplay.png"> Test</span></label> </div> <div id="return" class="button button-enabled" style="position: absolute; bottom: 15px; right: 20px;"> <img src="dump/ui/bt-return.png"> diff --git a/www/js/api.js b/www/js/api.js index 128cd9c..1aec74d 100644 --- a/www/js/api.js +++ b/www/js/api.js @@ -1,5 +1,14 @@ "use strict"; window.html5ks.api = { + init: function () { + var chars = html5ks.data.characters; + for (var ch in chars) { + var char = chars[ch]; + if (char.name) { + char.name = this.tag(char.name); + } + } + }, seen_scene: function (scene) { return !!html5ks.persistent.seen_scenes[scene]; }, @@ -43,6 +52,11 @@ window.html5ks.api = { return deferred.promise; }, stop: function (channel, fade) { + if (channel === "all") { + this.stop("music"); + this.stop("sound"); + return this.stop("ambient"); + } var deferred = when.defer(), audio = html5ks.elements.audio[channel], fadeSet = html5ks.persistent.settings.fade; @@ -68,6 +82,8 @@ window.html5ks.api = { video.src = src + "mp4"; } + this.stop("all"); + video.load(); video.style.display = "block"; video.play(); @@ -115,13 +131,13 @@ window.html5ks.api = { var cmd = inst[0], args = inst.slice(1); if (html5ks.data.characters[cmd]) { - return this.character(cmd, args); + return this.character(cmd, args[0]); } else { if (this[cmd]) { return this[cmd].apply(this, args); } else if (/^[A-Z]/.test(cmd)) { console.log("cmd starts with caps, probably character"); - return this.character(cmd, args); + return this.character(cmd, args[0]); } else { console.error("no such cmd " + cmd); var deferred = when.defer(); @@ -194,38 +210,92 @@ window.html5ks.api = { return deferred.promise; }, - character: function (name, str) { + tag: function (str) { + var tags = [ + /&/g, "&", + /</g, "<", + />/g, ">", + /{b}/g, "<b>", + /{\/b}/g, "</b>", + /{s}/g, "<s>", + /{\/s}/g, "</s>", + /{size=(\d*)}/g, "<span style='color: $1'>", + /{\/size}/g, "</span>", + /{color=(\d*)}/g, "<span style='color: $1'>", + /{\/color}/g, "</span>", + /{w(=\d*\.\d*)?}.*/, "", + /{nw}/, "", + /{fast}/, "" + ]; + for (var i = 0; i < tags.length - 1; i += 2) { + str = str.replace(tags[i], tags[i+1]); + } + return str; + }, + + character: function (name, str, extend) { var deferred = when.defer(), - text = str, - char = html5ks.data.characters[name]; + text = this.tag(str), + char = html5ks.data.characters[name], + w = /{w(=\d*\.\d*)?}/.exec(str); + if (!char) { char = { name: name }; } - if (char.what_prefix) { + if (!extend && char.what_prefix) { text = char.what_prefix + text; } - if (char.what_suffix) { + if ((!w || !w[1] || extend) && char.what_suffix) { text = text + char.what_suffix; } var who = html5ks.elements.who; - who.textContent = char.name; - if (char.color) { - who.style.color = char.color; + if (!extend) { + who.innerHTML = char.name; + if (char.color) { + who.style.color = char.color; + } else { + who.style.color = "#ffffff"; + } + } + + if (extend) { + html5ks.elements.say.innerHTML += text; } else { - who.style.color = "#ffffff"; + html5ks.elements.say.innerHTML = text; } - html5ks.elements.say.textContent = text; - html5ks.next = function () { - deferred.resolve(text); - html5ks.next = function () {}; - }; - if (html5ks.state.skip) { + + if (w) { + html5ks.next = function () { + html5ks.next = function () {}; + html5ks.api.extend(str.substring(w.index + w[0].length)).then(function () { + deferred.resolve(); + }); + }; + if (w[1]) { + setTimeout(html5ks.next, parseFloat(w[1].substring(1), 10) * 1000); + return deferred.promise; + } + } else { + html5ks.next = function () { + html5ks.elements.ctc.style.display = "none"; + deferred.resolve(text); + html5ks.next = function () {}; + }; + } + if (html5ks.state.skip || str.indexOf("{nw}") > -1) { html5ks.next(); } else if (html5ks.state.auto) { setTimeout(html5ks.next, 1000 + html5ks.persistent.settings.autospeed * text.length); + } else { + html5ks.elements.ctc.style.display = "block"; } return deferred.promise; }, + + extend: function (str) { + return this.character(null, str, true); + }, + Pause: function (duration) { var deferred = when.defer(); setTimeout(function () { diff --git a/www/js/html5ks.js b/www/js/html5ks.js index a6518c0..653b407 100644 --- a/www/js/html5ks.js +++ b/www/js/html5ks.js @@ -1,174 +1,199 @@ -(function () { - "use strict"; - window.html5ks = { - data: { - script: {} +"use strict"; +window.html5ks = { + data: { + script: {} + }, + persistent: { + seen_scenes: {}, + attraction: { + kenji: 0, + sc: 0, + hanako: 0 }, - persistent: { - seen_scenes: {}, - attraction: { - kenji: 0, - sc: 0, - hanako: 0 + settings: { + fade: 100, + gotit: false, + hdisable: false, + skipUnread: false, + skipAfterChoices: false, + useWebP: null, + fullscreen: true, + scaleVideo: true, + textSpeed: 0.5, + autoModeDelay: 0.2, + musicVolume: 1, + sfxVolume: 1 + }, + store: {} + }, + state: {}, + initElements: function () { + this.elements = { + container: document.getElementById("container"), + video: document.getElementById("vid"), + audio: { + music: new Audio(), + ambient: new Audio(), + sound: new Audio() }, - hdisabled: false, - settings: { - // ms per character - autospeed: 20, - fade: 100, - gotit: false, - scale: true + who: document.getElementById("who"), + say: document.getElementById("say"), + bg: document.getElementById("bg"), + window: document.getElementById("window"), + ctc: document.getElementById("ctc") + }; + this.elements.audio.music.loop = true; + this.elements.audio.ambient.loop = true; + }, + load: function () { + if (localStorage.persistent) { + this.persistent = JSON.parse(localStorage.persistent); + this.loaded = true; + } + }, + save: function () { + localStorage.persistent = JSON.stringify(this.persistent); + }, + scale: function () { + if (html5ks.persistent.settings.fullscreen) { + var newScale = 1; + var height = document.documentElement.clientHeight, + width = document.documentElement.clientWidth; + if (height / width <= 0.75) { // widescreen + newScale = height / 600; + } else { + newScale = width / 800; } - }, - state: { - auto: false - }, - initElements: function () { - this.elements = { - container: document.getElementById("container"), - video: document.getElementById("vid"), - audio: { - music: new Audio(), - ambient: new Audio(), - sound: new Audio() - }, - who: document.getElementById("who"), - say: document.getElementById("say"), - bg: document.getElementById("bg"), - window: document.getElementById("window") - }; - this.elements.audio.music.loop = true; - this.elements.audio.ambient.loop = true; - }, - load: function () { - if (localStorage.persistent) this.persistent = JSON.parse(localStorage.persistent); - }, - save: function () { - localStorage.persistent = JSON.stringify(this.persistent); - }, - scale: function () { - if (html5ks.persistent.settings.scale) { - var newScale = 1; - var height = document.documentElement.clientHeight, - width = document.documentElement.clientWidth; - if (height / width <= 0.75) { // widescreen - newScale = height / 600; - } else { - newScale = width / 800; - } - var container = html5ks.elements.container; - container.style.webkitTransform = "scale(" + newScale + ")"; - container.style.mozTransform = "scale(" + newScale + ")"; - container.style.transform = "scale(" + newScale + ")"; + var container = html5ks.elements.container; + container.style.webkitTransform = "scale(" + newScale + ")"; + container.style.mozTransform = "scale(" + newScale + ")"; + container.style.transform = "scale(" + newScale + ")"; + container.className += " scale"; - var applyScale = function (el, scale) { - el.style.height = scale * 600 + "px"; - el.style.marginTop = "-" + scale * 300 + "px"; - el.style.width = scale * 800 + "px"; - el.style.marginLeft = "-" + scale * 400 + "px"; - }; + var applyScale = function (el, scale) { + el.style.height = scale * 600 + "px"; + el.style.marginTop = "-" + scale * 300 + "px"; + el.style.width = scale * 800 + "px"; + el.style.marginLeft = "-" + scale * 400 + "px"; + el.className += " scale"; + }; - applyScale(html5ks.elements.bg, newScale); + applyScale(html5ks.elements.bg, newScale); + if (html5ks.persistent.settings.scaleVideo) { applyScale(html5ks.elements.video, newScale); } - }, - next: function () {}, - initEvents: function () { - window.addEventListener("resize", html5ks.scale, false); - this.elements.container.addEventListener("mouseup", function () { - html5ks.next(); + } + }, + next: function () {}, + initEvents: function () { + window.addEventListener("resize", html5ks.scale, false); + this.elements.container.addEventListener("mouseup", function () { + html5ks.next(); + }, false); + var deselect = function () { + window.getSelection().collapse(this, 0); + }; + document.body.addEventListener("mousemove", deselect, true); + document.body.addEventListener("mouseup", deselect, true); + document.body.addEventListener("keyup", deselect, true); + }, + warnUnsupported: function () { + if (!html5ks.persistent.settings.gotit) { + var warn = document.getElementById("warn-container"); + document.getElementById("gotit").addEventListener("mouseup", function () { + warn.style.mozAnimation = "0.5s dissolveout"; + warn.style.webkitAnimation = "0.5s dissolveout"; + warn.style.animation = "0.5s dissolveout"; + warn.style.opacity = 0; + html5ks.persistent.settings.gotit = true; + html5ks.start(); }, false); - var deselect = function () { - window.getSelection().collapse(this, 0); - }; - document.body.addEventListener("mousemove", deselect, true); - document.body.addEventListener("mouseup", deselect, true); - document.body.addEventListener("keyup", deselect, true); - }, - warnUnsupported: function () { - if (!html5ks.persistent.settings.gotit) { - var warn = document.getElementById("warn-container"); - document.getElementById("gotit").addEventListener("mouseup", function () { - warn.style.mozAnimation = "0.5s dissolveout"; - warn.style.webkitAnimation = "0.5s dissolveout"; - warn.style.animation = "0.5s dissolveout"; - warn.style.opacity = 0; - html5ks.persistent.settings.gotit = true; - html5ks.start(); - }, false); - var warns = document.getElementById("warns").children; - if (/MSIE/.test(navigator.userAgent)) { - document.getElementById("ie").style.display = "block"; - } - if (!(/Firefox/.test(navigator.userAgent))) { - document.getElementById("html-svg-filter").style.display = "block"; - } - for (var i = 0; i < warns.length; i++) { - if (window.getComputedStyle(warns[i]).getPropertyValue("display") !== "none") { - warn.style.visibility = "visible"; - return true; - } - } + var warns = document.getElementById("warns").children; + if (/MSIE/.test(navigator.userAgent)) { + document.getElementById("ie").style.display = "block"; } - }, - onload: function () { - this.initElements(); - this.load(); - this.scale(); - this.initEvents(); - if (!this.warnUnsupported()) { - this.start(); + if (!(/Firefox/.test(navigator.userAgent))) { + document.getElementById("html-svg-filter").style.display = "block"; } - this.menu.init(); - }, - start: function () { - this.fetch("script", "a1-monday").then(function () { - html5ks.api.movie_cutscene("4ls").then(function () { - html5ks.menu.mainMenu(); - }); + for (var i = 0; i < warns.length; i++) { + if (window.getComputedStyle(warns[i]).getPropertyValue("display") !== "none") { + warn.style.visibility = "visible"; + return true; + } + } + } + }, + onload: function () { + this.initElements(); + this.load(); + this.scale(); + this.initEvents(); + if (!this.warnUnsupported()) { + this.start(); + } + this.api.init(); + this.menu.init(); + }, + winload: function () { + if (!this.loaded) { + this.persistent.settings.useWebP = Modernizr.webp; + } + if (!Modernizr.webp && this.persistent.settings.useWebP) { + var webpjs = document.createElement("script"); + webpjs.src = "js/webpjs-0.0.2.min.js"; + document.head.appendChild(webpjs); + } + }, + start: function () { + this.fetch("script", "a1-monday").then(function () { + html5ks.api.movie_cutscene("4ls").then(function () { + html5ks.menu.mainMenu(); }); - }, - fetch: function (type, name) { - var deferred = when.defer(); - var xhr = new XMLHttpRequest(); - switch (type) { - case "script": - var script = html5ks.data.script; - if (script[name]) { - deferred.resolve(); - } else { - xhr.open("GET", "scripts/script-" + name + ".json"); - xhr.onreadystatechange = function () { - script[name] = true; - if (xhr.readyState === 4) { - var resp = JSON.parse(xhr.responseText); - for (var label in resp) { - script[label] = resp[label]; - } - deferred.resolve(); - } - }; - xhr.send(); - } - break; - case "imachine": - xhr.open("GET", "scripts/imachine.json"); + }); + }, + fetch: function (type, name) { + var deferred = when.defer(); + var xhr = new XMLHttpRequest(); + switch (type) { + case "script": + var script = html5ks.data.script; + if (script[name]) { + deferred.resolve(); + } else { + xhr.open("GET", "scripts/script-" + name + ".json"); xhr.onreadystatechange = function () { + script[name] = true; if (xhr.readyState === 4) { - html5ks.data.imachine = JSON.parse(xhr.responseText); + var resp = JSON.parse(xhr.responseText); + for (var label in resp) { + script[label] = resp[label]; + } deferred.resolve(); } }; xhr.send(); - break; - default: - throw new Error("fetchtype " + type + " not implemented"); - } - return deferred.promise; + } + break; + case "imachine": + xhr.open("GET", "scripts/imachine.json"); + xhr.onreadystatechange = function () { + if (xhr.readyState === 4) { + html5ks.data.imachine = JSON.parse(xhr.responseText); + deferred.resolve(); + } + }; + xhr.send(); + break; + default: + throw new Error("fetchtype " + type + " not implemented"); } - }; - document.addEventListener("DOMContentLoaded", function () { - html5ks.onload(); - }, false); -}()); + return deferred.promise; + } +}; +document.addEventListener("DOMContentLoaded", function () { + html5ks.onload(); +}, false); +window.addEventListener("onload", function () { + html5ks.winload(); +}, false); diff --git a/www/js/menu.js b/www/js/menu.js index 589ae41..a57f9a0 100644 --- a/www/js/menu.js +++ b/www/js/menu.js @@ -14,15 +14,16 @@ activeDialog: null, dialog: function (name) { - this.activeDialog = html5ks.elements.dialog[name]; + this.activeDialog = this.elements.dialog[name]; this.activeDialog.style.display = "block"; - html5ks.elements.dialogs.style.display = "block"; + this.elements.dialogs.style.display = "block"; }, initElements: function () { this.elements = { dialogs: document.getElementById("dialogs"), dialog: { + options: document.getElementById("options"), return: document.getElementById("return") }, mainMenu: document.getElementById("main-menu"), @@ -33,8 +34,34 @@ }; }, + initOptions: function () { + var options = document.getElementsByClassName("option"), + values = html5ks.persistent.settings; + + for (var i = options.length - 1; i >= 0; i--) { + var option = options[i]; + switch (option.type) { + case "checkbox": + option.checked = values[option.id]; + option.addEventListener("change", function () { + values[this.id] = this.checked; + }, false); + break; + case "range": + option.value = values[option.id]; + option.addEventListener("change", function () { + values[this.id] = this.value; + }, false); + break; + default: + console.error("unknown option type %s", option.type); + } + } + }, + init: function () { this.initElements(); + this.initOptions(); this.elements.main.start.addEventListener("click", function () { if (this._imachine_loaded) { this.elements.mainMenu.style.display = "none"; @@ -46,7 +73,7 @@ }, false); this.elements.dialog.return.addEventListener("click", function (e) { html5ks.menu.activeDialog.style.display = "none"; - html5ks.elements.dialogs.style.display = "none"; + html5ks.menu.elements.dialogs.style.display = "none"; }, false); html5ks.fetch("imachine").then(function () { var start = this.elements.main.start; |