L's blog

Twine macro: << hoverlink >>

This simple macro produces a usable internal link whose text changes whenever you mouseover it.

version.extensions.hoverlinkMacro={major:1,minor:1,revision:0};macros.hoverlink={handler:function(a,b,c){var d,l=Wikifier.createInternalLink(a,c[0]);
l.className+=" hoverLink";insertElement(l,"span",null,null,c[1]||c[0]);if(c[2]){d=insertElement(l,"span",null,null,c[2]);
d.style.display="none";}l.onmouseover=function(){if(this.childNodes.length>1){this.childNodes[0].style.display="none";
this.childNodes[1].style.display="inline";}};l.onmouseout=function(){if(this.childNodes.length>1){this.childNodes[1].style.display="none";
this.childNodes[0].style.display="inline";}};}};


This uses the << choice >> macro's parameter order - the destination passage name comes first. Then comes the visible label, then the hidden label.

Usage example: <<hoverlink "CryPassage" "Stand and fight, guns blazing" "Sit down and have a cry">>

For obvious reasons, this won't work on iPads!

Implementation details:

* The link has a class of "hoverLink" in addition to the standard internalLink/brokenLink classes.
* Each text label is a <span> inside the <a>, and the mouseover and mouseout events make one or the other visible.

Version history:

  1. 29-1-13 - Shortened name to "hoverlink"
  2. 12-5-13 - Initial.

Feel free to report any bugs to @webbedspace.

Twine macro: << addtag >>, << removetag >>, << toggletag >>

This extends my code for applying Twine tags to passage divs, and extends on it as a mechanism for CSS selection. What these macros do, in order, is add a tag to the current passage's tag attribute, remove a tag, and toggle its presence (remove it if it's there, add it if it isn't). This means that conditional application of CSS, controlled by << if >> statements, is possible.
Usage examples: <<addtag "dream">> <<toggletag "lightworld">> <<removetag "darkworld">>

These are guaranteed to work both inside passages, inside << replace >> macros, and, tantalisingly enough, inside << display >> macros. Interesting, very interesting.

If you're using the latest Twine 1.3.6 Alpha (28-1-13) , use this code:

version.extensions["toggletagMacros"]={major:1,minor:1,revision:0};macros["toggletag"]={handler:function(a,b,c){var p=e(a);var d=document.body;if(p){var t=p.getAttribute("data-tags");var i=t.indexOf(c[0]);if(b!="addtag"&&i>=0){var r=t.replace(c[0],"");p.setAttribute("data-tags",r);d.setAttribute("data-tags",r);}else{if(b!="removetag"&&i<0){var r=t+" "+c[0];p.setAttribute("data-tags",r);d.setAttribute("data-tags",r);}}}function e(f){while(f.parentNode&&!f.classList.contains("passage")){f=f.parentNode;
}if(f!=document&&f.getAttribute("data-tags")){return f;}return null;}}};macros["addtag"]=macros["toggletag"];macros["removetag"]=macros["toggletag"];


If you're using a standard version of Twine, use this code instead:

version.extensions["toggletagMacros"]={major:1,minor:1,revision:0};macros["toggletag"]={handler:function(a,b,c){var p=e(a);var d=document.body;if(p){var t=p.getAttribute("data-tags");var i=t.indexOf(c[0]);if(b!="addtag"&&i>=0){var r=t.replace(c[0],"");p.setAttribute("data-tags",r);d.setAttribute("data-tags",r);}else{if(b!="removetag"&&i<0){var r=t+" "+c[0];p.setAttribute("data-tags",r);d.setAttribute("data-tags",r);}}}else{var t=state.history[0].passage.tags;var i=t.indexOf(c[0]);if(b!="addtag"&&i>=0){t.splice(i,1);
}else{if(b!="removetag"&&i<0){t.push(c[0]);}}d.setAttribute("data-tags",t.join(" "));}function e(f){while(f.parentNode&&!f.classList.contains("passage")){f=f.parentNode;}if(f!=document&&f.getAttribute("data-tags")){return f;}return null;}}};macros["addtag"]=macros["toggletag"];macros["removetag"]=macros["toggletag"];

Version history:

  1. 1.1.0 - Now adjusts tags on the body element as well.
  2. 1.0.1 - Fixed not working in Jonah.
  3. 1.0.0 - Initial.
Feel free to report any bugs to @webbedspace.

Twine macro: << cyclinglink >>

This simply produces a link whose text cycles between a number of values whenever you click on it. It otherwise leads nowhere. You can use it as a silly clicky trinket, a cheap alternative to the <<replace>> macro, or (as detailed below) as an input interface element.

Script code

version.extensions.cyclinglinkMacro={major:3,minor:3,revision:0};
macros.cyclinglink={handler:function(a,b,c){var rl="cyclingLink";
function toggleText(w){w.classList.remove("cyclingLinkInit");
w.classList.toggle(rl+"Enabled");w.classList.toggle(rl+"Disabled");
w.style.display=((w.style.display=="none")?"inline":"none")}switch(c[c.length-1]){case"end":var end=true;
c.pop();break;case"out":var out=true;c.pop();break}var v="";if(c.length&&c[0][0]=="$"){v=c[0].slice(1);
c.shift()}var h=state.history[0].variables;if(out&&h[v]===""){return
}var l=Wikifier.createInternalLink(a,null);l.className="internalLink cyclingLink";
l.setAttribute("data-cycle",0);for(var i=0;i<c.length;i++){var on=(i==Math.max(c.indexOf(h[v]),0));
var d=insertElement(null,"span",null,"cyclingLinkInit cyclingLink"+((on)?"En":"Dis")+"abled");
if(on){h[v]=c[i];l.setAttribute("data-cycle",i)}else{d.style.display="none"
}insertText(d,c[i]);if(on&&end&&i==c.length-1){l.parentNode.replaceChild(d,l)
}else{l.appendChild(d)}}l.onclick=function(){var t=this.childNodes;
var u=this.getAttribute("data-cycle")-0;var m=t.length;toggleText(t[u]);
u=(u+1);if(!(out&&u==m)){u%=m;if(v){h[v]=c[u]}}else{h[v]=""}if((end||out)&&u==m-(end?1:0)){if(end){var n=this.removeChild(t[u]);
n.className=rl+"End";n.style.display="inline";this.parentNode.replaceChild(n,this)
}else{this.parentNode.removeChild(this);return}return}toggleText(t[u]);
this.setAttribute("data-cycle",u)}}};

Features

If the first parameter begins with the "$" sigil, then it will interpret it as a variable to be altered by clicking the link. When the player visits the page or clicks the link, the variable will be changed to match the text of the link. If the variable already has a string value when you load the passage, then the link text that matches the variable will be selected, instead of the first one.

If the last parameter is the word "end", then it will terminate the cycling link at the parameter before last. The containing that text will replace the link element altogether. Similarly, if the last parameter is the word "out", then it will disappear altogether after the final link text is clicked.

Usage examples:
* You look around, but only see <<cyclinglink "grass" "a flower" "a cloud" "the road">>. - This is a purely cosmetic, endlessly cycling link.
* You see a bowl containing <<cyclinglink "3 cookies" "2 cookies" "1 cookie" "a few crumbs" end>>. - This link changes to the words "a few crumbs" when you get to the end.
* You see a dial: <<cyclinglink $heat "off" "low" "high" "fearsome">>. - The $heat variable will be changed to "off" when the page loads (unless it was already set to "high" or "fearsome"), and then sets it to "low", "high" and "fearsome" as the player clicks the link.
* You see a fuel gauge: <<cyclinglink $fuel "100%" "50%" "10%" "0%" end>>.
* You see an air meter: <<cyclinglink $air "********" "******" "****" "**" out>>.

Example program 1 (cosmetic).
Example program 2 (mechanical).
Example program 3 (mechanical with CSS)..

New: Advanced CSS effects

I've gone to the trouble of writing a number of advanced CSS effects for use with this macro. You can see them all and obtain the CSS code for them by clicking here. These use some very recent CSS animation features in unusual combinations, but they've been tested in both Chrome and Firefox.

Implementation details:
* For CSS users: the <a> tag has the class names "cyclingLink" and "internalLink".
* Each option is a <span> inside the <a> tag. Clicking it causes the next one down to have the class "cyclingLinkEnabled", and the others to have the class "cyclingLinkDisabled". New: on passage load, each option also has the class "cyclingLinkInit", which is removed as each is clicked.
* When you click, the "data-cycle" attribute of the <a> tag increases by 1 (wrapping around to 0 at the end). You could select this with CSS, I guess, using [data-cycle=1] or somesuch.

Version history:

  1. 9-8-13 - Added 'cyclingLinkInit' class support.
  2. 27-3-13 - Bugfixes, added "out" parameter.
  3. 4-3-13 - Bugfix.
  4. 27-2-13 - Added CSS hooks and the "end" parameter.
  5. 26-2-13 - Added new variable-setting ability.
  6. 16-2-13 - Fixed typo preventing it from working.
  7. 29-1-13 - Initial.

Feel free to report any bugs to @webbedspace.

Twine macro: << replace >>, << insert >> and << continue >>

Similar to my <<timedreplace>> macro, this macro creates an internal link that, when clicked, vanishes and is replaced with whatever is between the << replace >> and << endreplace >> tags. This could be useful if (just for starters) you want to have a passage that can be modified by clicking specific details inside it.

Variations:
* You can also use <<insert>>, which is identical but does not remove the link text, instead merely changing it to a bare <span> (as with Jonah's <<choice>> macro.)
* You can use <<continue>> to make all subsequent text appear when it's clicked. It does not need an <<endcontinue>> at the end! Quite useful when you just need the reader to click to continue the passage.
* You can insert <<becomes>> or <<gains>> tags inside the contained text to create multiple "versions" of the replacement text. Clicking the link will display each successive version, stopping only when the last version is reached.
* If you omit the text in the starting tag, then the first "version" will be used as the link instead. This lets you, for instance, make an image into a replaced link. Of course, this requires that multiple <<becomes>> or <<gains>> tags are within the text.

Install my <<Replace>> Macro Set to use this macro.


Usage examples:
You see <<replace "a half-eaten cake">>a plate of crumbs<<endreplace>>
You see <<replace "ten dollars">>five dollars<<becomes>>two dollars<<becomes>>fifty cents<<endreplace>>
You see <<replace>>a //big// boulder!<<becomes>>some pebbles.<<endreplace>>
I find it <<insert "repugnant">>that he's made such comments<<endinsert>>.
You go outside. <<continue "Next.">>The sun blinds you momentarily.<<continue "Next.">>Has it been so long?

A short example program.
Hot hint: if you've got a lot of text, it's good to just put a single << display >> inside a << replace >>, and put the text in a separate passage.

Some notes:
* Code inside << replace >> tags is only executed when you click the link. You may notice that the "mailbox" link in the example program produces different text if you click the "front door" link first.
* If inserted text appears and descends below the bottom of the screen, the page should automatically scroll down to make it visible.
* Note: due to the way the browser and Twine interact, any changes made by code inside a <<timedreplace>> tag will be forgotten if you use the Back or Forward browser buttons, unless you use this script.

For a more powerful version of this macro, see <<revision>>.

Version history:
* 16-6-2013: Updated regarding Combined Replace Macro Set.
* 26-4-2013: Added << continue >> variant.
* 5-4-2013: Added << insert >> variant, scrolling.
* 7-3-2013: Added CSS hooks.
* 28-1-2013: Initial.
Feel free to report any bugs to @webbedspace.

Twine: Passage transition: Instant (no transition)

Suppose you want your Twine game to have that classic feel to it. You want pages to snap into place instantly, without even a moment of fade-in. All business. 90s web style. So sudden that it's like the game can read your thoughts.

Well, here's some CSS code that suppresses the default fade-in transition between passages.

.transition-out { display:none; }

All this code does is remove functionality, which is why it's pretty simple. It will also work with Jonah as well as Sugarcane.

If you want more types of transitions, also see these example files.

Feel free to report any bugs to @webbedspace.

Twine: auto-advance the Start passage

Update: this bug is fixed in Twine 1.4, so this script code is no longer necessary.

I've been a bit annoyed at how the order of startup events in the current versions of the standard Twine formats (which is to say, Sugarcane 1.2.1 and Jonah 2.1) is 1) render the Start passage, 2) load the story's custom JavaScript. This ordering means that various custom things like macros can't be used in the Start passage, because they're loaded afterward.

If you're using Twine 1.3.6 Alpha (28-1-13) then you don't have this problem. If you're using a stable version prior to that, read on.

What I'd much rather do is have the game automatically advance past the Start passage into the next passage, once all of the scripts are loaded. So, here's something to do that. Put this in a "script" passage:


setTimeout(function(){ var next = "Start2";

var h = state.history; if (h[0].passage.title=="Start") { var n = insertElement(null, 'div');n.style.display="none";n.appendChild($("passages").removeChild($("passageStart")));state.display(next,null);$("passages").appendChild(n);}},1);


What this does is conceal the Start passage's div inside a hidden div*, then goes to the page named "Start2". This happens instantaneously, and is imperceptible to the player.

You can change the passage name that the code uses by altering the name inside the quote marks in the first line of the script snippet.

*(Note that I can't just destroy the Start passage div outright, because the passage's fade() interval is still running, and cannot be halted without causing browser console errors (which are technically invisible to the player but are nonetheless untidy coding).)
Feel free to report any bugs to @webbedspace.

Twine: apply CSS to passages with specific tags (Tag CSS)

Update: Twine 1.4 now has an easier method of using Tag CSS, so this script code is no longer necessary.

Obsolete script removed: use Twine 1.4

CSS Syntax
The selector syntax for passages with specific tags is [data-tags~=tagname]. So, if you tag a bunch of passages with "dream", you can then apply specific CSS to just those passages like this:

[data-tags~=dream] {
  color: aqua;
  text-shadow: aqua 0 0 3px;
}
The preceding code will affect both the passage div and the body element. To select those elements separately, use syntax like this:
body[data-tags~=blood] {
  background-color:red;
  color: black;
}
.passage[data-tags~=blood] {
  border: 5px solid white;
  font-size: 110%;
  width: 30em;
}
Some variations on the selector syntax exists that you might find useful:
[data-tags*=swamp] for a passage whose tags contain "swamp" (such as "grayswamp" or "swampfort").
:not([data-tags~=gold]) for a passage which does not have the tag "gold".

And, of course, you can select elements of a matching passage <div> by combining selectors with ".body", ".body .internalLink" and such:

.passage[data-tags~=cave] .body .internalLink { color: gold; }

Caveat: all of this won't work for passage text displayed with the << display >> macro unless you use something like addtag.

Selector recap:
* To apply CSS to the body element, use "body[data-tags~=tag]", "body:not([data-tags~=tag])" etc.
* To apply CSS to the passage-class element, use ".passage[data-tags~=tag]", ".passage:not([data-tags~=tag])" etc.
* To apply CSS to both, use "[data-tags~=tag]" etc.

If you like this code, consider using these macros that let you control tags inside the game.

*This is really just a workaround until browsers support CSS selector subjects. Ideally you could just do !body .passage[data-tags:], but alas, not this year.

Version history:

  1. 11/2/13 - Possibly fixed crash in situations where 'state' hadn't been initialised yet.
  2. 6/2/13 - Additional code to affect the <body> tag was added for both versions.
  3. 5/2/13 - Altered to use "data-tags" attribute rather than "tags".
  4. 4/2/13 - Split into two snippets for Twine 1.3.6 alpha and Twine stable versions.
  5. 26/1/13 - Altered to affect the Start passage.
  6. 25/1/13 - Initial.

Twine: HTML file to Twee source code converter

Update: this converter is now built into Twine 1.4 (in the "Import Compiled HTML file" menu item), so this page is no longer necessary.

HTML to Twee Converter

Someone on my Twitter feed reported that they'd lost the source code of a Twine game of theirs, and wanted to know if they could convert the HTML back to a usable format. So, I decided to quickly knock up a utility that could do that. Here it is - it extracts the passages from a HTML twine game and converts them to Twee source code, which Twine can import using the "Import Source Code" menu option. I hope it works for you.

Twine: horizontal hacked Jonah format, "Journal"

This is version 1 of the hacked copy of the "Jonah" story format that I used in Capri Toot, which I have unimpressively called "Journal". (I'm unaware of what naming pattern the prepackaged Twine formats use, so I cannot follow it.)

Just move the Journal folder inside this zip file into the "targets" folder of your Twine installation to add this to the Story Format menu.
You can also see this example program (modified from 'Town' by Anna Anthropy)

An explanation of some of the CSS and JS is as follows:

#passages { white-space:nowrap; width:auto; height:auto; overflow-x: scroll; overflow-y: hidden; }

white-space:nowrap keeps the passage divs from wrapping when they reach the edge of the #passages div. overflow-x: scroll permits the #passages div to extend to accommodate the addition of passage divs, using a scrollbar.

.passage { display:inline-block; vertical-align: top; white-space:normal; background-color: #eee; font-size: 1.3em; line-height: 175%; margin: 1em; width: 33%; min-width: 35em; height: 56em; padding: 2em; border-width: 1px; border-style: solid; border-color: #333; box-shadow: 0.2em 0.2em 0.2em; }

display: inline-block ensures that the passage divs are arranged horizontally. vertical-align: top ensures that their tops are aligned if they are different sizes. Do note that Journal by default sets the height of passages to 56em, and the width of passages to 33% of the #passages div. This produces the satisfying look of consistent rectangular pages, but of course limits the text you can put in a passage. Feel free to add CSS to extend the height or the width if you wish.

function scrollDivTo(Q, E) {
var D = Q.scrollLeft;
var G = J(E) - Q.offsetLeft;
var C = Math.abs(D - G);
var B = 0;
var I = (D > G) ? -1 : 1;
var F = window.setInterval(H, 25);
function H() {
B += 0.05;
Q.scrollLeft = D + I * (C * Math.easeInOut(B));
if(B >= 1) {
window.clearInterval(F)
}
}
function J(N) {
var O = A(N);
var P = O + N.offsetWidth + N.offsetRight;
var K = Q.scrollLeft;
var L = Q.clientWidth;
var M = K + L;
if(O < K) {
return O - N.marginLeft
}
else {
if(P > M) {
if(N.offsetWidth < L) {
return(O + N.offsetRight - (L - N.offsetWidth))
}
else {
return O
}
}
else {
return O
}
}
}
function A(K) {
var L = 0;
while(K.offsetParent) {
L += K.offsetLeft;
K = K.offsetParent
}
return L
}
};

This is a horizontal replacement for the scrollWindowTo() function in Jonah. Although it takes two arguments, the additional first argument is simply the #passages div. You could probably rewrite it as a drop-in replacement for scrollWindowTo() by just removing the first argument (and adding " var Q = $('passages'); " to the other var definitions) and changing its name to "scrollWindowTo". However, since this was designed to encapsulate scrolling a div, the change of name was necessary in this case.

Twine: Sugarcane passage transition: dissolve

Update: Twine 1.4 has a reworked transition engine, so this script code is no longer applicable.

If you want more types of transitions, see also this code for an alternative transition engine for Sugarcane (more versatile but not compatible with this script).

Previously I was experimenting with alternatives to Twine's default fade-in animation. Now, I've figured out how to make such alterations with a single block of JavaScript insertion that you can drop into your story.

Here's a block of code that, when inserted into a Sugarcane formatted Twine game, changes the passage transition to a dissolve, where the previous passage fades out at the same time as the next passage fades in.

[Note: This will not work with the one-page Jonah format.]

addStyle(".passage { position:absolute !important; } ");History.prototype.display=function(d,b,a){var c=tale.get(d);this.history.unshift({passage:c,variables:clone(this.history[0].variables)});this.history[0].hash=this.save();var e=c.render();if(a!="offscreen"){var f=document.getElementById("passages").firstChild;if(f){fade(f,{fade:"out",onComplete:function(){document.getElementById("passages").removeChild(document.getElementById("passages").firstChild);}});}document.getElementById("passages").appendChild(e);if(a!="quietly"){fade(e,{fade:"in"});}}if((a=="quietly")||(a=="offscreen")){e.style.visibility="visible";}if(a!="offscreen"){document.title=tale.title;this.hash=this.save();document.title+=": "+c.title;window.location.hash=this.hash;window.scroll(0,0);}return e;};History.prototype.watchHash=function(){if(window.location.hash!=this.hash){if((window.location.hash!="")&&(window.location.hash!="#")){this.history=[{passage:null,variables:{}}];document.getElementById("passages").style.visibility="hidden";if(!this.restore()){alert("The passage you had previously visited could not be found.");}document.getElementById("passages").style.visibility="visible";}else{window.location.reload();}this.hash=window.location.hash;}};

Just put this in a vacant passage tagged with "script".

Note that in order to do this, it will set the passage divs to use the style "position: absolute", so keep that in mind if you're using CSS that requires a different positioning.

What this actually does is replace the Twine engine's History.prototype.display function with an alternative version that sets the previous passage to fade out and then be removed, instead of just immediately removing it. Since the passage divs are set to absolute positioning, they're drawn one top of the other while they simultaneously exist, instead of being vertically arranged.

This also replaces History.prototype.watchHash, which controls the Back button behaviour. Again, this is simply to keep the previous passage(s) from being removed immediately, and instead letting History.prototype.display do it. (If you're using a hack that modifies History.prototype.watchHash, you will probably have to do some tweaking to this or the other to get this to work.)

See attachment for an example (applied to "Town" by Anna Anthropy).)||(a==

Syndicate content
pensive-mosquitoes