twine

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==

A breakdown of the CSS in my Twine game Capri Toot

This is the inline CSS I used in my Twine game, Capri Toot. Of course, I had to write some other CSS for my special horizontal hacked variant of Jonah, but this below is where almost all of the visual style of Capri Toot comes from.
----------------
#passages {
background-color: #000;
overflow:hidden;
}

----------------
"overflow: hidden" hides the horizontal scrollbar in the passages DIV, which is useful because backtracking isn't possible in Capri Toot.
----------------

@keyframes borderkeyframe
{
0% {box-shadow: 0 0 2.5em 2.5em }
50% {box-shadow: 0 0 0.5em 0.5em }
100% {box-shadow: 0 0 2.5em 2.5em }
}

@-moz-keyframes borderkeyframe
{
0% {box-shadow: 0 0 2.5em 2.5em }

50% {box-shadow: 0 0 0.5em 0.5em }

100% {box-shadow: 0 0 2.5em 2.5em }

}

@-webkit-keyframes borderkeyframe
{
0% {box-shadow: 0 0 2.5em 2.5em }

50% {box-shadow: 0 0 0.5em 0.5em }

100% {box-shadow: 0 0 2.5em 2.5em }

}

@-o-keyframes borderkeyframe
{
0% {box-shadow: 0 0 2.5em 2.5em }

50% {box-shadow: 0 0 0.5em 0.5em }

100% {box-shadow: 0 0 2.5em 2.5em }

}
----------------
CSS Keyframes: These define the animated pulsing glow behind the passages. There are four definitions because each browser has its own syntax (except IE, which doesn't support this). These define some timeline events which the browser tries to smoothly interpolate between. When the animation is 50% complete, the box-shadow is retracted to a size of 0.5em. When the animation starts and finishes, it's at its full size of 2.5em. Defining both a 0% and 100% point ensures that it loops smoothly, if an element uses it as a looping animation.
----------------

.passage:nth-child(8n+7) {
text-shadow: 0 0 0.6em hsl(0,100%,60%);

color: hsl(0,100%,50%);

background-color: hsl(0,100%,5%);

}

.passage:nth-child(8n) {
text-shadow: 0 0 0.6em hsl(45,100%,60%);

color: hsl(45,100%,50%);

background-color: hsl(45,100%,5%);

}

.passage:nth-child(8n+1) {
text-shadow: 0 0 0.6em hsl(90,100%,60%);

color: hsl(90,100%,50%);

background-color: hsl(90,100%,5%);

}

.passage:nth-child(8n+2) {
text-shadow: 0 0 0.6em hsl(135,100%,60%);

color: hsl(135,100%,50%);

background-color: hsl(135,100%,5%);

}

.passage:nth-child(8n+3) {
text-shadow: 0 0 0.6em hsl(180,100%,60%);

color: hsl(180,100%,50%);

background-color: hsl(180,100%,5%);

}

.passage:nth-child(8n+4) {
text-shadow: 0 0 0.6em hsl(225,100%,60%);

color: hsl(225,100%,50%);

background-color: hsl(225,100%,5%);

}

.passage:nth-child(8n+5) {
text-shadow: 0 0 0.6em hsl(270,100%,60%);

color: hsl(270,100%,50%);

background-color: hsl(270,100%,5%);

}

.passage:nth-child(8n+6) {
text-shadow: 0 0 0.6em hsl(315,100%,60%);

color: hsl(315,100%,50%);

background-color: hsl(315,100%,5%);

}

----------------
These are used to colour each of the zone tiers differently. When you click on a link in Jonah (as compared to Sugarcane) a new passage div is added to the end of the #passages div as a new child node. The nth-child() selector allows you to apply specific styles to an element depending on which child it is. nth-child(8n) means that every eighth child should have this style (starting with the first). nth-child(8n+1) means that every eighth child, plus 1 position over, gets this style. So, with these eight definitions, a looping cycle of eight colours is produced. This is kind of overkill for Capri Toot since you can only ever go 8 passages deep, but w/e.

Also, a note: if you don't specify a colour for box-shadow, then it falls back to using the text colour. Hence, we use a colour style here to change the animated shadow's colour without having to set up eight different keyframes (x 4 browsers).
----------------

.passage {
margin-left: 33%;

margin-right: 33%;

min-width: 33%;

margin-top: 3em;

margin-bottom: 3em;

border-radius: 8em;

border-color: white;

border-width: 2px;

box-shadow: 0 0 2.5em 2.5em;

animation: borderkeyframe 3s infinite;

-moz-animation: borderkeyframe 3s infinite;

-webkit-animation: borderkeyframe 3s infinite;

-o-animation: borderkeyframe 3s infinite;

}
----------------
The margin and min-width definitions mean that only one passage is ever visible at one time. The border-radius definition produces the delightful rounded rectangle edges. The animation definitions bind the aforementioned keyframe to this element, give it a speed (3 seconds) and tell it how many times to loop (infinite). Again, 4 definitions because each browser (except IE) has different syntax.
----------------

.passage .body .internalLink {

font-style: italic;

font-size: 1.5em;

color: white;

text-shadow: 0 0 0.6em silver;

}
.passage .body {

color: white;

text-shadow: inherit;

}
.passage .title {

margin-top: 0.2em;

font-style: italic;

font-size: 2em;

color: white;

text-align: center;

}
----------------
Fairly straightforward. Setting .body's text colour to white overrides the previous colour definition for .passage (which as I mentioned is being used for the box shadow).
----------------

.toolbar {

display:none;

}
----------------
This single-handedly hides the Jonah "rewind / bookmark" prompts for each passage. Quite convenient.

Twine: preloading images

Update: embedded images in Twine 1.4 do not need to be preloaded, so this is not necessary if you are using those.

If you use a lot of images in your Twine game, it would be very good of you if you preloaded them at the start of the game - having to wait for images to load during a story, even momentarily, can be distracting.

Now you could bother to convert them all to inline Base64, but there's other, less intrusive ways. You could, rather, put every image in your story in invisible img tags in the Start passage:

<html>
<img src="  [url of an image ] " style="display:none;" >
...
</html>

...but of course, that requires you to manually list every image yourself. Here is my recommendation: use this JavaScript that will do it automatically, when the story starts. Just put this in a passage tagged with "script".

(function(){var r="";var s=Wikifier.formatters;for(var j=0;j<s.length;j++){if(s[j].name=="image"){r=s[j].lookahead;
break;}}var div=document.getElementById("storeArea").firstChild.nextSibling;while(div){if(r){k(new RegExp(r,"mg"),4);
}var b=String.fromCharCode(92);var u=b+"s*['"+'"]?([^"'+"']+(jpe?g|a?png|gif|bmp))['"+'"]?'+b+"s*";
k(new RegExp("url"+b+"("+u+b+")","mig"),1);k(new RegExp("src"+b+"s*="+u,"mig"),1);
div=div.nextSibling;}function k(c,e){do{var d=c.exec(div.innerHTML);if(d){var i=new Image();
i.src=d[e];}}while(d);}}());

That's all.

Update 17/2/13: This now works with images in HTML <img> tags as well.

Update 12/2/13: This will now also preload images used in CSS url( ... ) values. It will search for such values in every passage, include the stylesheet passages, script passages, and inline JavaScript.

Version history:

  1. 17/2/13 - Now works for image files specified in HTML src="..." attributes.
  2. 13/2/13 - CSS preloading now only loads JPEG, JPG, PNG, APNG, GIF and BMP files (that is to say, not font files).
  3. 12/2/13 - Now preloads images specified by CSS URL values too.
  4. 19/1/13 - Initial.
+b+

Twine: storing images inside the HTML file

Update: embedded images in Twine 1.4 already use this technology automatically.

One aspect of Twine that I very much like is that the entire game is included in a single HTML file. It provides a great convenience and advantage for maintaining and archiving the game. Some Twine games include images in the text, and in most all cases there are in the form of hyperlinks to external image files, which, being separate from the game file, are vulnerable to being lost or broken.

There is a way to store image files (among other files) inside HTML using Base64 encoding, which converts binary files into streams of ASCII. Simply use an online Base64 encoder to convert your image into a Base64 data URI, and paste that wherever you would paste a URL to an image. This works not only for the HTML img tag, but also for Twine's img markup (although it probably won't be syntax-highlighted correctly in the passage editor).

Here is an example:
[img[data:image/png;base64,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]]

(If you paste the entire data URI between the braces into your browser URL bar, you can see the image.)

Some advantages:
- Stores all the game's resources in one file.
- Images are preloaded when the game starts.

Some disadvantages:
- Base64 encoding increases the size of the images by 33%.
- Can't easily use the same image file for multiple passages - each passage must have a full copy of the file. (You can work around this with some JavaScript preprocessing, if you really need to.)
- Doesn't work with IE versions < 9... go figure.

Syndicate content
pensive-mosquitoes