The <<hoverrevise>>
macro is an extension of the <<revision>> / <<revise>>
macros. It lets you make a span of passage text briefly appear or disappear whenever you mouseover another span of text in the same passage.
Code
Just install the latest version of <<revision>>
(1.1.0 or later). It's included already.
CSS
You may want a "dissolve" type of transition to be applied to the text that appears or disappears. For that, give the <<insertion>>
or <<removal>>
macro a name that, say, contains "hover", and then use this CSS to exclusively target it:
.revision-span-in[class*=hover] { opacity: 0; } .revision-span[class*=hover] { transition: 1s; -webkit-transition: 1s; } .revision-span-out[class*=hover] { position:absolute; opacity: 0; }
<<revise>>
macros, you could omit "[class*=hover]" to make every <<revision>>
span have this transition.
Usage examples
Use this in conjunction with <<revision>>
, <<insertion>>
or <<removal>>
spans, as you would with <<revise>>
. The <<hoverrevise>>
macro covers a full span of text, ending with <<endhoverrevise>>
To make the text "Aah! A ghost!" appear when you mouseover a span:
<<insertion hoverghost>>Aah! A ghost!<<endinsertion>> <<hoverrevise hoverghost>>You'd better [[go to the chapel|chapel]]<<endhoverrevise>>
To make the text "Don't leave me" disappear when you mouseover a span:
<<removal words>>"Don't leave me"<<endremoval>> <<hoverrevise words>>[[Go to the beach|beach]]<<endhoverrevise>>
<<revision hovermetal>>Cold<<becomes>>Warm<<endrevision>> <<hoverrevise hovermetal>>[img[gold.png]]<<endhoverrevise>>
These are just a few of the possibilities allowed by this macro.
Notes:
This wraps the contained text in a <span>
with the class "hoverrevise" as well as "hoverrevise_" suffixed with the identifier you use (for instance, "hoverrevise_boo" for <<hoverrevise boo>>
").
Feel free to report any bugs to @webbedspace.
The <<revision>>
macro is a more powerful variation of the <<replace>>
macro. It lets you alter a span of passage text by clicking a link from anywhere in the passage.
Install my <<Replace>> Macro Set to use these macros.
Basic usage
First, use the <<revision>>
macro to define two different versions of some text:
You see here <<revision tome>>a closed book.<<becomes>>an open book.<<endrevision>>
Each section of text separated by <<becomes>>
is a different "version" of the text. The name "tome" in the macro is an identifier. You can use any single word you want as an identifier.
Then, you can create a link using the <<revise>>
like so:
<<revise tome "Open the book.">>
This creates a link reading "Open the book" that changes any "tome" revision in the passage into the next version - in this case, changing "a closed book" into "an open book".
(Notice that the word "revise" is the verb form of "revision", reflecting the fact that the hyperlink serves as a verb for the player to perform.)
Running code in revisions
Just like with <<replace>>
, any macros inside a revision are run as soon as they are made to appear:
<<revision button>>You see a button.<<becomes>>The button is pushed.<<set $button=true>><<endrevision>> <<revise button "Push it.">>
Multiple versions
You can have many versions of a span of text:
You see here <<revision books>>a closed book.<<becomes>>a chewed book.<<becomes>>paper scraps.<<endrevision>> <<revise books "Chew book">>
Back and forth
The <<revert>>
macro functions to reverse the effects of the <<revise>>
macro.
<<revision box>>Here is a closed box.<<becomes>>Here is an open box.<<endrevision>> <<revise box "Open the box.">> <<revert box "Close the box.">>
The <<revert>>
macro's link won't be displayed if it's at the first revision, just as the <<revise>>
macro won't be visible if it's at the last revision.
Cycling link text options (New)
The <<revise>>
and <<revert>>
macros have a number of additional options similar to the <<cyclinglink>>
macro. You can supply additional link text strings after the first, and it will change to the next string after you click it.
<<revise wall "Smash wall" "Smash it again" "Keep smashing">>
If the second parameter begins with the "$" sigil, then it will interpret it as a variable to be altered by clicking the link. When the player clicks the link, the variable will be changed to match the text of the link.
<<revise heat $warmth "Set it to warm" "Set it to hot" "Set it to boiling">>
If the last parameter is the word "end", then it will disable the link at the parameter before last, leaving just the text. If the last parameter is the word "out", then it will vanish altogether once it's reached the final string.
<<revise roast "Devour roast" "Munch some more" "You're too full now" end>>
<<revise amphora "Drink wine" out>>
<<hoverrevise>>
Click here to read about <<hoverrevise>>
.
<<becomes>> vs. <<gains>>
Just as the <<replace>>
macro has a variation, <<insert>>
, so too does <<becomes>>
.
The vase contains:<<revision vase>>Two roses<<gains>>, an orchid<<gains>>, a pencil<<gains>>, a straw<<endrevision>>. <<revise vase "Put something in the vase">>.
You can mix and match <<gains>>
and <<becomes>>
at will:
<<revision count>>One<<becomes>>Two<<gains>>and a half<<becomes>>Three!<<endrevision>>
<<cycle>>
Normally, when a <<revision>>
macro reaches the end of its revisions, its links disappear. If you change "revision" to "cycle", then you can keep clicking the link to return to the first revision.
You see here <<cycle pet>>a dog<<becomes>>a cat<<endcycle>>. <<revise pet "Change pet">>
<<insertion>> and <<removal>>
Two other variations exist, which are roughly analogous to <<timedinsert>>
and <<timedremove>>
.
You look at the plate. <<insertion grain>>1 grain.<<becomes>>2 grains.<<becomes>>3 grains.<<endinsertion>>
For <<insertion>>
, the first version "1 grain" is initially invisible. Clicking a <<revise>>
link will make it visible. Then, it functions as normal.
You look at the plate. <<removal seed>>3 seeds.<<becomes>>2 seeds.<<becomes>>1 seed.<<endremoval>>
For <<removal>>
, the <<revise>>
link will remain when you get to "1 seed". Clicking it a final time will remove the text altogether.
Example program
http://www.glorioustrainwrecks.com/files/TwineMacro-RevisionTest.html
Technical details
<<revision>>
macro is a <span>
classed with "revision-span" as well as the kind of version they are ("becomes", "gains"). When they appear, they are given the class "revision-span-in" and their display is set to "inline". When they disappear, they gain the class "revision-span-out" and, after 1 second, their display is set to "none". All of these are inside a container <span>
classed with the name of the macro ("insertion", "removal", "cycle", "revision") and the name of the identifier ("book" or whatever the author set it to be).Version history:
<<revision>>
macros were being called twice on passage load.<<revision>>
macros were still taking effect while they were animated being removed by other macros.<<revision>>
macros with the same ID weren't all being revised at the same time.<<revise>>
.<<hoverrevise>>
and support for <<randomise>>
.This macro is similar to <<timedreplace>>
, but instead of replacing one block of text with another, it just re-runs a passage section, re-drawing the text and running the contained macros again.
http://www.glorioustrainwrecks.com/files/TwineMacro-TimedLoop-1.1.0.txt
Much like <<timedreplace>>
, the transition between each rewrite is handled by CSS. Here's a default "fade-in" transition:
.timedloop.replacement-in { opacity: 0; } .timedloop { transition: 1s; -webkit-transition: 1s; } .timedloop.replacement-out { display:none; }You can easily modify this CSS. If you want an "instant" transition, for instance, change "opacity: 0;" to "display:none;"
New: This now takes CSS time values, which are decimal numbers ending in "s" (for seconds) or "ms" (for milliseconds).
Here's a usage example:
<<set $red=1>> <<timedloop 1s>>You have <<set $red += 1>><<print $red>> seed pods.<<endtimedloop>>The text will initially read "You have 2 seed pods.", then change to "You have 3 seed pods." after 1 second, then "You have 4 seed pods." after another second, and so forth until you leave the passage. The time value is in half-seconds, like
<<timedreplace>>
.
Known bug: When you click a link to leave a passage, the loop will still run while the passage is transitioning out. This may cause unexpected behaviour (if, for instance, a <<timedgoto>>
is inside the looped code).
Implementation details:
* 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 <<timedloop>>
tag after the first iteration will be forgotten if you use the Back or Forward browser buttons. This means that if you put long-term variable changes that affect future passages inside one, you should disable the Back button.
Version history:
* 11-4-2013: Fixed bug where the timeout wouldn't expire if you clicked a "refresher" link to the same passage.
* 5-4-2013: Initial.
Feel free to report any bugs to @webbedspace.
These are some CSS snippets that alter the Sugarcane format. Insert them directly into a "stylesheet" passage in your Twine games to use them. They probably won't work with Jonah, though.
I don't consider myself a graphic designer or web designer, so these are to be regarded solely as amateur efforts.
Click here for live previews of all these stylesheets!
"Sodden Tome"
This requires Twine 1.4. Designed for slimy gloomy or spooky stories. "Wow! It really looks like it fell in a swamp!" - Marsh Boggart 341, Death Quadrant 9.
html { background-image: linear-gradient(to bottom, hsl(70, 39%, 30%), black); background-image: -webkit-linear-gradient(top, hsl(70, 39%, 30%), black); background-attachment: fixed; background-color: black; } body { background-color: transparent; margin: 2% 0 0 0; font-size: 100%; } #passages { background-image: linear-gradient(-135deg, beige, #c6c66c); background-image: -webkit-linear-gradient(-45deg, beige, #c6c66c); background-color: beige; width: 40%; margin:auto; margin-bottom: 5%; padding: 2em; box-shadow: inset 0 0 2em olive; } .passage { margin: 0px; padding: 2em; /* Text formatting */ color: black; font-family: "Times New Roman", serif; text-align:justify; } .passage br + .char { margin-left: 1.6em; } .char.e + .char, .char.t + .char, .char.a + .char, .char.o + .char, .char.i + .char, .char.n + .char, .char:nth-child(8n) { text-shadow: 0em 3px 0.3em hsla(82, 39%, 20%, .25); color: rgba(0,0,0,0.7); } /* No sidebar */ #sidebar { display:none; } hr { border: 0; height: 2px !important; background-color: black; box-shadow: 0 0 1em darkolivegreen; } /* Links */ .passage a .char { color: hsla(82, 39%, 20%, .75); text-shadow: 0em 3px 0.2em hsla(82, 39%, 20%, .25), 0em 6px 0.4em hsla(82, 39%, 20%, .25); } .passage a:visited .char { color: hsla(37, 39%, 20%, .75); text-shadow: 0em 3px 0.2em hsla(37, 39%, 20%, .25), 0em 6px 0.4em hsla(37, 39%, 20%, .25); } .passage a:hover { text-decoration: none; } .passage a:hover .char { color: darkgreen !important; } .passage a:visited:hover .char { color: darkred !important; }
@import url(http://fonts.googleapis.com/css?family=Alegreya+Sans:300|Oxygen:400,700&subset=latin,latin-ext); html { background: [img[background]] fixed, dodgerblue; background-size: cover; min-height: 100%; height:100%; } body { background-color: rgba(0,0,0,0.4); background-attachment: fixed; margin: 0; padding: 0% 15% 0% 5em; font-size: 100%; font-family: Oxygen, "Century Gothic", sans-serif !important; font-weight: 300; min-height: 100%; } #sidebar, #passages { padding-top: 4em; padding-bottom: 0em; } #passages { margin-left: 60%; width: 50%; min-height: 100%; padding-bottom: 0; margin-bottom: 0; border: 0; } #passages::before { border-left: 3px solid rgba(255,255,255,0.2); position:fixed; height:100%; left: 50%; top:0; content:''; } .passage { margin: 0px; color: white; opacity: 0.7; font-size: 100%; text-align:justify; margin:auto; padding: 0px 0px 5em 0px; } .passage a { color: #cde8ff; opacity: 1; } .passage a:hover { color: white; text-decoration: none; border-bottom: 1px white solid; } .passage a:visited { color: #d7ffcd; } #sidebar { left: 1em; width: 50%; font-family: "Alegreya Sans","Century Gothic",sans-serif !important; } #sidebar li { color: white !important; text-align: center; opacity: 0.3; } #sidebar #storyTitle { font-size: 4em; line-height:0.8em; } #sidebar > :not(.storyElement) { display:none; } /* Shrink the page when viewed on devices with a low screen width */ @media screen and (max-width: 1600px) { .passage, #sidebar { font-size: 90%; } } @media screen and (max-width: 1200px) { #sidebar { font-size: 70%;} .passage { font-size: 85%;} } @media screen and (max-width: 960px) { #passages::before { display:none; } #sidebar { position: relative; width: 100%;} #passages { margin: 0; padding: 5%; width: 90%; border: 0; } }
"Simple Centered"
The "simple" stylesheets are designed to be shorter and more customisable than the others. This one simply consists of passage text, centered horizontally AND vertically inside the window, along with a colour gradient background. See the code comments for places where colours and values can easily be changed.
html { width: 100%; /* Vertical colour gradient */ background-image: linear-gradient(to bottom, black, midnightblue); background-image: -webkit-linear-gradient(top, black, midnightblue); background-attachment: fixed; /* Fallback colour */ background-color: midnightblue; /* Vertical centering */ height: 100%; display: table; } body { /* Remove default styles */ font-size: 100%; background-color: transparent; margin: 0; /* Vertical centering */ height:100%; display:table-cell; vertical-align: middle; } #passages { border-left: 0px; margin: 0; /* Keep a gap at the top and bottom of the page, when the text is longer than the window's height. */ padding: 5% 0; } .passage { /* Passage width */ width: 60%; /* Horizontal centering */ margin: 0 auto; /* Text formatting */ color: white; font-size: 100%; text-align:center; } /* No sidebar */ #sidebar { display:none; } /* Links */ a.internalLink, a.externalLink { color: cornflowerblue; } a.internalLink:hover, a.externalLink:hover { color: lightskyblue; text-decoration: none; } /* Shrink the page when viewed on devices with a low screen width */ @media screen and (max-width: 960px) { .passage { font-size: 90%; width: 70%; } } @media screen and (max-width: 840px) { .passage { font-size: 87.5%; width: 80%; } } @media screen and (max-width: 720px) { .passage { font-size: 75%; width: 90%; } }
html { /* Vertical colour gradient */ background-image: linear-gradient(to bottom, gainsboro, silver); background-image: -webkit-linear-gradient(top, gainsboro, silver); background-attachment: fixed; /* Fallback colour */ background-color: silver; } body { /* Remove default styles */ background-color: transparent; margin: 10% 0 0 0; font-size: 100%; /* Used to center the box */ text-align: center; } #passages { /* Box background (white with 70% opacity) */ background-color: rgba(255, 255, 255, 0.7); /* Border */ border: 2px solid white; /* Rounded corners */ border-radius: 1em; /* Box width */ width: 60%; /* Center the box */ display: inline-block; min-height: 40%; margin:auto; margin-bottom: 5%; padding: 0px; } .passage { margin: 0px; /* Inner margin within the box */ padding: 2em; /* Text formatting */ color: black; font-size: 100%; text-align:justify; } /* No sidebar */ #sidebar { display:none; } /* Links */ a.internalLink, a.externalLink { color: royalblue; } a.internalLink:hover, a.externalLink:hover { color: deepskyblue; text-decoration: none; } /* Shrink the page when viewed on devices with a low screen width */ @media screen and (max-width: 960px) { .passage { font-size: 90%;} #passages { width: 70%; } } @media screen and (max-width: 840px) { .passage { font-size: 87.5%; } #passages { width: 80%; } } @media screen and (max-width: 720px) { .passage { font-size: 75%; } #passages { width: 90%; } }
"Squillions"
"It's a Twine game that you can imagine James Bond playing." ? Ian Bogost.
head { box-shadow: inset 0px 0px 30em #bbb; width:100%; height:100%; display:block; position:fixed; } head * { display:none; } body { background-color:#fff; margin: 0; text-align:center; } #passages { border-left: 0; margin: 0; padding: 0; line-height:100vh; } .passage { position:absolute; top: 0; bottom: 0; left: 0; right: 0; width: 75%; height: 75%; margin:auto; font: bold 6em/1.25em Helvetica, "Helvetica Neue", Arial, sans-serif; color: #000; letter-spacing: -0.05em; text-align:center; } #sidebar { display:table; position:fixed; top: 0; left: 0; width:100%; height:100%; } #sidebar #title { display:table-cell; vertical-align:middle; text-align:center; } #sidebar #title #storyTitle { font: bold 12em/1.25em Helvetica, "Helvetica Neue", Arial, sans-serif; letter-spacing: -0.05em; color:rgba(0,0,0,0.15); } #title :not(#storyTitle){ display:none; } #storymenu, #snapback, #restart, #share, #credits { display:none; } a.internalLink:hover, a.externalLink:hover { color:#de0000 !important; text-decoration: none; } a.internalLink:nth-child(3n), a.externalLink:nth-child(3n) { color: #666; } a.internalLink:nth-child(3n+1), a.externalLink:nth-child(3n+1) { color: #777; } a.internalLink:nth-child(3n+2), a.externalLink:nth-child(3n+2) { color: #888; } @media screen and (max-width: 960px) { body { font-size: 50%; } } @media screen and (max-width: 840px) { body { font-size: 40%; } } @media screen and (max-width: 720px) { body { font-size: 30%; } }
"The Earth's Story Illustrated"
Best used with a dissolve transition.
This stylesheet is capable of displaying a 480-pixels-tall scene image above every passage's text! You can set scene images using Tag CSS. To assign, say, the image "classroom_afternoon" to the tag "classroom", simply create a stylesheet tagged "stylesheet classroom" and put this in it:
.passage .header { background-image: [img[classroom]]; }And then tag various passages with "classroom" to use the image.
.passage .header { background-image: [img[character-funnycry]], [img[classroom]]; }That's how you do it!
#sidebar { display:none; } body { margin: 0; padding: 0; height:100%; } #passages { margin:0; padding: 0; height:100%; } #passages * { box-sizing: border-box; -moz-box-sizing: border-box; } .passage { position:relative; width: 60%; font-size:2em; font-family: "Lucida Sans Typewriter", Consolas, Monaco, monospace; margin: 2em auto 0 auto; } .passage .header { width:100%; height:480px; min-height: 480px; border: #fff double 0.5em; border-radius: 1em; margin: 0 auto 1.5em auto; padding: 0; background-position: center; background-repeat: no-repeat; } .passage .content { top: 500px; width:100%; border: #fff double 0.5em; border-radius: 1em; padding: 1em; } a.internalLink, a.externalLink { border-bottom: solid #fff 1px; color:#eee; font-weight:normal; } a.internalLink:hover, a.externalLink:hover { text-decoration:none; border-bottom: none; color:#000; background-color:#fff; font-weight:normal; padding-left: 0; } a.internalLink:active, a.externalLink:active { border-bottom: 0; } @media screen and (max-width: 960px) { .passage { font-size: 1.5em; width: 75%; } } @media screen and (max-width: 640px) { .passage { font-size: 1.25em; width: 95%; } }
"Remembering the 90's"
Best used with an instant passage transition.
body { background:LightGrey; color: #000; font: medium "Times New Roman", Times, serif; margin: auto; padding: 8px; } #passages { margin: auto; border: 0; padding: 0; } .header, .footer { border: 1px inset; margin: 0.5rem auto; } .passage { font: inherit; line-height: inherit; margin: auto; } .passage ul { padding: 0; text-align: left; } .passage li { display:inherit; margin: 0; } a, #sidebar #snapback, #sidebar #restart, #sidebar #share, .menu div { font-weight:inherit !important; text-decoration: underline !important; color: #00F !important; } a.visited { color: #7F007F; } #sidebar { font: inherit !important; position:static; width: auto; list-style: disc outside; } #sidebar ul { padding: inherit; } #sidebar li { color: inherit; text-align:inherit; margin:inherit; display:inline; } #sidebar #titleSeparator { display:none; } #sidebar #title, #sidebar #title:hover { color:inherit; } #sidebar #storyTitle, #sidebar #storyTitle:hover { font-size: 2rem; margin: .67rem 0; font-weight:bold; } #sidebar #storySubtitle { font-size: inherit; font-weight:bold; } #sidebar #storyAuthor::before { content: "by "; } #sidebar #storyAuthor { font-size:medium; display:block; font-weight:bold !important; } #sidebar #credits { display:block; font-size: smaller; padding: inherit; } #snapbackMenu::before { content: "Rewind to:"; font-weight:bold; } #shareMenu::before { content: "Share this story at:"; font-weight:bold; } .menu::before { content: "Rewind to:"; font-weight:bold; } .menu, .menu div:hover { position: static; background-color:inherit; color:inherit; opacity:1; border:0; font:inherit; line-height:inherit; } .menu div { margin: 0 1.12rem; display: list-item; list-style:disc outside; }
"Porthole"
Features: Very roughly inspired by the title screen of "The Sea Will Claim Everything"; Designed for games with brief passage text. No sidebar.
body { width: 100%; margin-left: 0; text-align:center; } #passages { position:relative; display:inline-block; font-size: 1.5em; background-color:skyblue; background-image: -webkit-linear-gradient(top, #87ceeb 0%,#87ceeb 75%,#008eed 75%,#008eed 100%); background-image: linear-gradient(to bottom, #87ceeb 0%,#87ceeb 75%,#008eed 75%,#008eed 100%); width: 60em; height: 60em; border-radius: 30em; border: darkgoldenrod 1em solid; margin-left: 0; padding-left: 0; } .passage { position: absolute; text-align:center; top: 20em; bottom: 0; margin: -10em 5em auto 5em; display:inline-block; width: 40em; height: 40em; overflow-y: hidden; box-shadow: 0 0 2.5em 2.5em; } a.internalLink, a.externalLink { color: white; font-size: 1.2em; } a.internalLink:hover, a.externalLink:hover { color: white; text-decoration: none; text-shadow: 0 0 0.5em white; } .passage .body { color: white; } #sidebar { display:none; } @media screen and (max-width: 960px) { body { font-size: 55%; } } @media screen and (max-width: 840px) { body { font-size: 45%; } } @media screen and (max-width: 720px) { body { font-size: 40%; } }
"The Earth's Story 1.0"
Features: No sidebar. Similar to ZX Spectwine, below. Reminiscent of early 90s Nintendo RPGs - hence its gray sensibilities. Use Tag CSS and tag your passages "mint", "strawberry", "banana" or "peanut" to get different coloured borders for specific passages!
body { margin: 0; background-color:#000; } #passages { margin: 0; padding: 0; border: 0; } .passage { width: 40%; min-width: 26em; height: 33%; min-height: 33%; font-family: Helvetica, "Helvetica Neue", Arial, sans-serif; letter-spacing: 0.1em; padding: 1.5em; padding-left: 2em; font-size:1.8em; background-color:#000; color:#eee; margin: 10%; border: silver ridge 0.8em; border-radius: 1em; } .passage[data-tags~=mint] { border-color: MediumAquaMarine; } .passage[data-tags~=strawberry] { border-color: HotPink; } .passage[data-tags~=banana] { border-color: Gold; } .passage[data-tags~=peanut] { border-color: Peru; } .content::before { content: '•'; position: relative; left: -0.6em; margin-right: -0.4em; } a.internalLink, a.externalLink { border-bottom: solid #fff 1px; color:#eee; font-weight:normal; } a.internalLink:hover, a.externalLink:hover { text-decoration:none; border-bottom: solid #fff 2px; color:#fff; font-weight:normal; padding-left: 0; } a.internalLink:active, a.externalLink:active { border-bottom: 0; } #sidebar { display:none; }
"Hypercane 1.1"
As used in Myriad
Features: Sidebar is converted to a top bar (minus Share); vaguely resembles System 6, but nowhere near enough for true aficionados.
body { margin: 10% 0 10% 0; } #passages{ margin: 0; padding: 0; border: 0; } .passage, #sidebar * { font-family: Geneva, "Helvetica Neue", Helvetica, sans-serif; color:#000; text-align:left; } .passage { border-radius: 0.2em; width: 60%; margin: auto; padding: 2em; font-size:1.5rem; background-color:#fff; border: solid #000 0.05em; box-shadow: #000 0.5em 0.5em 0; } a.internalLink, a.externalLink { border: solid #000 0.05em; white-space: nowrap; padding: 0.1em 0.2em 0.1em 0.2em; border-radius: 0.5em; color:#000; } a.internalLink:hover, a.externalLink:hover { text-decoration: none; box-shadow: #000 0 0 0 0.1em; color:#000; } a.internalLink:active, a.externalLink:active { color: #fff; background-color:#000; } body { background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAEAQMAAACTPww9AAAABGdBTUEAALGPC/xhBQAAAAZQTFRFAAAA////pdmf3QAAAA5JREFUeF5jOACEBgwGAAnIAeGomjuiAAAAAElFTkSuQmCC); } #sidebar { position:absolute; top:0px; left:0px; width:99.2%; overflow-x:hidden; background-color:#fff; border-bottom: solid #000 1px; } #sidebar * { color: #000 !important; font-size:1.5rem; background-color:clear !important; display:inline !important; } #sidebar a:hover, #sidebar #snapback:hover, #sidebar #restart:hover { text-decoration:underline !important; } #sidebar a:active, #sidebar #snapback:active, #sidebar #restart:active { color:#fff !important; font-size:1.5rem; background-color:#000 !important; display:inline !important; } #sidebar li, #sidebar li > span { margin-left: 1rem; margin-right: 1rem; } #sidebar a, #sidebar a:hover { border: 0 !important; box-shadow: none; } .menu { background-color:#fff; color:#000; opacity:1; font-size: 1.5rem; border: solid #000 1px; box-shadow: #000 0.1em 0.1em 0; } .menu div:hover { background-color:#000; color:#fff; } #credits, #share, #titleSeparator, #sidebar li br { display:none !important; }
"Closed In"
As used in Solitary
Features: No sidebar; Enormous screen-filling font size designed for very terse stories; Font should scale down on mobile devices.
body { margin: 2%; } #passages{ margin: 0; padding: 0; border: 0; width:96%; margin: auto; } .passage { font-size:6em; color: #888; text-shadow: #888 0 0 0.05em; } @media screen and (max-width: 960px) { .passage { font-size: 4em; } } @media screen and (max-width: 640px) { .passage { font-size: 3em; } } a.internalLink, a.externalLink { color: #eee; text-shadow: #eee 0 0 0.07em; } a.internalLink:hover, a.externalLink:hover { color: #fff; text-decoration: none; text-shadow: #fff 0 0 0.09em; } #sidebar { display:none; }
"Orange Highlight"
As used in To my Grandma
Features: No sidebar; External links are a different colour to internal links.
body { margin: 5% 0 0 0; background-color:#000; } #passages{ margin: 0; padding: 0; border: 0; } .passage { font-family: "Times New Roman",serif; text-align:left; color:#000; width: 40em; padding: 2em; font-size:3em; background-color:#fff; background: -webkit-linear-gradient(left, #aaa 0%,#fff 19%); background: linear-gradient(to right, #aaa 0%,#fff 19%); } a.internalLink { color:#620; background-color:hsla(48, 100%, 50%, 0.5); } a.externalLink { color:#602; background-color:hsla(320, 100%, 50%, 0.5); } a.internalLink:hover { color:#410; background-color:hsla(40, 100%, 50%, 0.85); text-decoration: none; } a.externalLink:hover { color:#401; background-color:hsla(320, 100%, 50%, 0.85); text-decoration: none; } a.internalLink:active { color:#140; background-color:hsla(80, 100%, 50%, 0.85); text-decoration: none; } a.externalLink:active { color:#104; background-color:hsla(280, 100%, 50%, 0.85); text-decoration: none; } #sidebar { display:none; }
"Warm Cabin"
Features: no sidebar; denotes a vague memory of varnished wood as approximated by the MS PowerPoint designers.
body { margin: 0; padding: 0; } #passages { margin: 0 5% 0 5%; padding: 2.5% 0 5% 0; border-left: saddlebrown solid 1.5em; height:auto; background: #3d1d08; background: -webkit-radial-gradient(center, ellipse cover, #3d1d08 0%,#000000 80%); background: radial-gradient(ellipse at center, #3d1d08 0%,#000000 80%); } .header { width: 25%; border-top: saddlebrown solid 0.1em; margin: auto; padding: 0 0 2.5% 0; } .passage { margin: 2em; font-family: "Georgia", serif; font-size:2.2em; color: peru; text-shadow: sienna 0.05em 0.05em 0.05em; } a.internalLink, a.externalLink { color: burlywood; text-shadow: peru 0.05em 0.05em 0.05em; } a.internalLink:hover, a.externalLink:hover { color: cornsilk; text-decoration: none; text-shadow: peru 0.05em 0.05em 0.05em; } #sidebar { display:none; }
Feel free to report any bugs to @webbedspace.
While CSS passage transitions give you a good degree of creativity with regards to transition animations, a good many potential effects require Javascript. One such effect is the oft vaunted "typewriter" transition:
(function(){postrender.typewriter = function (b) { if(this.tags){var r=new RegExp("t8n.typewriter.([0-9]+)(?:[^0-9]|$)","g");var t=r.exec(this.tags.toString()); if(t){typeout(b,t[1]+0);}}return b;};var typeout=function(c,t){var Furl=function(current){this.n=current; this.out=false;this.data=current.nodeValue;current.nodeValue="";this.kids=[];var cn=current.childNodes; if(current.style && current.style.display=="none"){return;}while(cn.length>0){var f=new Furl(cn[0]); current.removeChild(cn[0]);f.out=true;this.kids.push(f);}};var nodes=new Furl(c); var unfurl=function(furled,d){var n=furled.n;if(furled.out){d.appendChild(n);furled.out=false; }if(furled.data){n.nodeValue+=furled.data[0];furled.data=furled.data.slice(1);return true; }for(var j=0;j<furled.kids.length;j++){var ret=unfurl(furled.kids[j],n);if(ret){return true; }}return false;};var title=state.history[0].passage.title;var intr=setInterval(function(){if(state.history[0].passage.title==title&&unfurl(nodes,null)){return; }clearInterval(intr);},t);};}());
To use it for a passage, tag the passage with "t8n-typewriter-" followed by the number of milliseconds between each character appearing - for instance "t8n-typewriter-10" or "t8n-typewriter-2". If you have a lot of text, a low number is highly recommended.
("t8n" is a numeronym for the word "transition". For this and any future transitions of mine, "t8n" will denote a tag that invokes that transition.)
This can be combined with CSS transitions! See this example to see a combination of the two.
Caveats:
<<replace>>
macro - replaced text will simply appear normally.<<timedreplace>>
macro. If the <<timedreplace>>
macro triggers before it has been fully typed out, then it will not function correctly. Simply make sure that the <<timedreplace>>
is timed to trigger only after it appears.These caveats may be improved in future versions. Other possible improvements may include a macro designed to alter typing speed within the text itself, so that the full range of pauses, staccato, blurt-outs, etc. as seen in various RPGs, can be replicated in Twine.
Version history:
Update: this has become the default behaviour in Twine 1.4, so this is no longer necessary.
This code will alter the Jonah format such that:
* All passages are now added to the bottom, regardless of where you click the link.
* Clicking a link to a previously displayed passage will display a new version of the passage at the bottom, instead of scrolling up to the old version.
This is based on a bit of code used by E. Turner in some of his Twine games. Unlike in those games, this code also preserves the "Rewind to Here" link's functionality.
Obsolete script removed: use Twine 1.4
Remember, this goes in a disconnected passage with the tag "script".
Feel free to report any bugs to @webbedspace.
This is inspired by the Timer script used in Panic! by Astrid Bin and Stefano Russo. That script implements a sophisticated timer entity which can count down throughout the entire game, can draw itself in graph form in a canvas element, and can be paused and resumed. However, it requires multiple macros to set it up for each use, and it can be a bit cumbersome if you simply want the game to advance to a new passage after a delay.
I felt like writing a similar macro that would be shorter and more specific. This one, <<timedgoto>>
, just automatically (and invisibly) goes to the given passage after the given amount of time has passed. Each use of the macro only functions within the passage that uses it - if you're using Sugarcane or single-passage Jonah and leave the passage by a normal link, it will be disengaged. Feel free to consider this a counterpart to <<timedreplace>>
.
version.extensions.timedgotoMacro={major:1,minor:2,revision:0}; macros["goto"]=macros.timedgoto={timer:null,handler:function(a,b,c,d){function cssTimeUnit(s){if(typeof s=="string"){if(s.slice(-2).toLowerCase()=="ms"){return +(s.slice(0,-2))||0 }else{if(s.slice(-1).toLowerCase()=="s"){return +(s.slice(0,-1))*1000||0 }}}throwError(a,s+" isn't a CSS time unit");return 0}var t,d,m,s; t=c[c.length-1];d=d.fullArgs();m=0;if(b!="goto"){d=d.slice(0,d.lastIndexOf(t)); m=cssTimeUnit(t)}d=eval(Wikifier.parse(d));if(d+""&&state&&state.init){if(macros["goto"].timer){clearTimeout(macros["goto"].timer) }s=state.history[0].passage.title;macros["goto"].timer=setTimeout(function(){if(state.history[0].passage.title==s){state.display(d,a) }},m)}}};
New: This now takes CSS time values, which are decimal numbers ending in "s" (for seconds) or "ms" (for milliseconds). You can use fractions of seconds as well as whole seconds.
Usage examples:
* <<timedgoto "underwater" 2.5s >>
goes to the "underwater" passage if you stay at the current passage for 2 and 1/2 seconds.
* <<timedgoto $deathpassage 5s >>
goes to the passage whose name is in the $deathpassage variable if you stay at the current passage for 5 seconds.
* <<timedgoto $playerType + "pit" 1s >>
goes to the $playerType + "pit" passage if you stay at the current passage for 1 second.
* <<goto "Throne">>
- This shorter version functions the same as <<timedgoto "Throne" 0s>>
.
Notes:
* This uses code parameters (that is, it accepts "strings", $variables, and "various"+$combinations of both, and its parameters are interpreted as Javascript), except that the final parameter (the time value) is separated from the rest and interpreted as a literal parameter.
Version history:
Update: this is now built into Twine 1.4, so this script is no longer necessary. You can enable it in Twine 1.4 by writing "Undo: off" in StorySettings.
If you want to remove the browser's Back button functionality in Sugarcane, to prevent the player from rewinding or undoing moves, here is some script code that can do that.
Obsolete script removed: use Twine 1.4
Version history:
I've modified my previous Sugarcane passage transition code to allow you to define your own transitions with CSS.
Update: the Javascript that was on this page is now installed in Twine 1.4.
What this does is remove the default JavaScript transition from Sugarcane, and cause the outgoing passage to gain a "transition-out" class, and the incoming passage to instantly gain and lose a "transition-in" class. This enables you to define new passage transitions by styling these 3 classes using CSS.
CSS Examples
In order for there to be a transition, you must specify the transition
property for your passage elements. A very basic example is as follows:
.passage { transition: 0.25s linear; -webkit-transition: 0.25s linear; }The
transition
property causes elements to transition between styles when they gain and lose them, in a specific way. In this case, it's a quarter-second, purely linear transition. (Other options than "linear" are listed here.) The maximum duration time before the departing passage is removed is 1 second.
The current versions of Opera, Firefox and IE support the plain transition property, but you need to also include the -webkit variant in order to work in Chrome and Safari (you can also include -khtml-transition
for Konqueror users, few though they may be).
Now that you've defined the transition itself, you can define the styles that the passages can transition from and to.
A very basic example:
.transition-in { position:absolute; opacity:0; }When a new passage arrives, its style will transition from the "transition-in" style to the normal passage style. In this case, it starts invisible ("opacity:0") and fades in to visibility. Note the "position:absolute" - both "transition-in" and "transition-out" should have either position:absolute or position:fixed if you want them to neatly appear above or below each other.
.transition-out { position:absolute; opacity:0; }
A broad number of CSS attributes can be used with this technique. Experiment!
Combining with tag-based CSS
If you use my tag-based passage CSS code, you can tie transitions to passages tagged with a particular tag. For instance, to bind the "dissolve" transition to passages tagged with "t8n-dissolve":
body[data-tags~=t8n-dissolve] .transition-in { position:absolute; opacity:0; } body[data-tags~=t8n-dissolve] .passage { transition:1s; -webkit-transition: 1s; } body[data-tags~=t8n-dissolve] .transition-out { position:absolute; opacity:0; }I recommend prefixing all transition-based tags with something identical, like "t8n" (a numeronym of "transition"). This way, you can then define a default transition to be used only when no "t8n" tags are present:
body:not([data-tags*=t8n]) .transition-in { opacity:0; transform: translate(0,3rem); -webkit-transform: translate(0,3rem); position:absolute; } body:not([data-tags*=t8n]) .passage { transition: 2s; -webkit-transition: 2s; } body:not([data-tags*=t8n]) .transition-out { opacity:0; transform: translate(0,-3rem); -webkit-transform: translate(0,-3rem); position:absolute; }
[data-tags*=t8n]
selects elements whose tags contain "t8n", and :not()
selects elements that do not fulfill the requirement in the brackets.)
It's important that if you use multiple transitions, the default transition should be guarded using body:not([data-tags*=t8n])
, so that its code doesn't interfere with the other transitions' code.
To see an example where the first four passages are tagged "t8n-dissolve" and the rest are not, see here.
Live examples
All examples are "The Sky in the Room" by Porpentine.
Fade in (the default transition)
.transition-in { opacity:0; position:absolute; } .passage:not(.transition-out) { transition: 1s; -webkit-transition: 1s; } .transition-out { opacity:0; position:absolute; }
:not(.transition-out)
means that the transition is applied only for the incoming passage - the departing one instantly gets opacity:0. Note that specifying passage.transition-in
would not do anything, since it is by removing the transition-in class that the transition is initiated.
Dissolve
.transition-in { position:absolute; opacity:0; } .passage { transition: 1s; -webkit-transition: 1s; } .transition-out { position:absolute; opacity:0; }
.transition-in { opacity:0; position:absolute; } .passage:not(.transition-out) { transition: 0.8s steps(3); -webkit-transition: 0.8s steps(3); } .transition-out { opacity:0; position:absolute; }
.transition-in { opacity:0; position:absolute; } .passage:not(.transition-out) { transition: 0s 1s; -webkit-transition: 0s 1s; } .transition-out { transition: 1s; -webkit-transition: 1s; opacity:0; position:absolute; }
Vertical wipe
Sadly, this currently only works if you give the passage div a fixed width and height.
If you use this, replace all instances of "1200px" and "800px" with dimensions appropriate for your story.
.transition-in { clip: rect(0px, 1200px, 0px, 0px) !important; } .passage { clip: rect(0px, 1200px, 800px, 0px); width: 1200px; height: 800px; position: absolute; transition: 1s linear; -webkit-transition: 1s linear; } .transition-out { clip: rect(800px, 1200px, 800px, 0px); }
.transition-in { opacity:0; transform: scale(0.8,0.8); -webkit-transform: scale(0.8,0.8); position:absolute; } .passage { width: calc(100% - 12em); // Necessary to keep the zoom origin point consistent. width: -webkit-calc(100% - 12em); transition: 0.5s ease-out; -webkit-transition: 0.5s ease-out; } .transition-out { opacity:0; transform: scale(2,2); -webkit-transform: scale(2,2); position:absolute; }
Fast scroll up
Ideally you would use "vh" (viewport height) units instead of "rem" (root em) units, but not enough browsers support it yet. ;_;
You can also make it a slow fading scroll if you change the lengths to something short (like 3rem).
.transition-in { opacity:0; transform: translate(0,-100rem); -webkit-transform: translate(0,-100rem); position:absolute; } .passage { transition: 1s; -webkit-transition: 1s; } .transition-out { opacity:0; transform: translate(0,100rem); -webkit-transform: translate(0,100rem); position:absolute; }
Garage door
This would work well with a story whose passage divs have a fixed width and height, but unlike the vertical wipe it isn't strictly necessary.
.transition-in { opacity:0; position:absolute; } .passage { background-color: #000; transition: 1s ease-in; -webkit-transition: 1s ease-in; } .transition-out { position:absolute; z-index:3; transform: translate(0,-200%); -webkit-transform: translate(0,-200%); }
.transition-in { color:transparent; text-shadow: #fff 0 0 1em; position:absolute; } .passage:not(.transition-out) { transition: 1s; -webkit-transition: 1s; } .transition-out { opacity:0; position:absolute; }
.transition-in { color:transparent; text-shadow: #fff -4em 0 1em, #fff 4em 0 1em; position:absolute; } .passage:not(.transition-out) { transition: 1s; -webkit-transition: 1s; } .transition-out { opacity:0; position:absolute; }
This is an optional appendix to my Twine code shortcuts article. Most of the things here are entirely valid, but aren't as intuitive, and don't really save that much in the way of typing or expression. Nonetheless, they do reveal some underlying concepts to JavaScript that may be enlightening.
Reducing strings and numbers to true and false
In JavaScript, using strings or numbers in place of true, false, or conditional statements such as "x > y" means that the computer will try and convert the strings or numbers into either true or false. The rule for those two types of values is: the number 0 and a string with zero characters in it, "", are converted to false - everything else is converted to true. (0 and "" are called "falsy" values because they're equivalent to false - other values are "truthy").
(If you're familiar with Game Maker, you of course know that Game Maker considers all negative numbers to be falsy as well - but not so in JavaScript!)
This is not really that useful, except for explaining the behaviour of later examples, but it does mean that you could shorten certain <<if>>
comparisons:
<<if $hp neq 0>>
can be shortened to
<<if $hp>>
You can also use Not to express the opposite.
<<if $hp eq 0>>
can be validly rewritten as
<<if not $hp>>
false is equivalent to 0, true is equivalent to 1
With regards to numbers, the process of true/false coercion also works symmetrically - the value false, when used in arithmetic, is converted to 0. <<set $red = 3 + false>>
is valid, but useless. Nevertheless, this understanding is necessary for the next part.
As a corollary, the raw value true, when used in arithmetic, is converted to 1. This means that <<set $dead += (not $hp)>>
will add 1 to $dead if $hp is 0.
And and Or
The way And and Or is implemented in JavaScript is interesting: "x and y" means "if x is false, the value of this statement is x (i.e false), otherwise the value of this statement is y". And, as discussed previously, since such statements are usually used for true/false comparisons, then y is reduced to its true/false value - as one expects.
However, what if you used it in a situation where a plain value was expected? Essentially, it can be used in a manner similar to the ternary operator:
<<set $liveammo = ($gun and $bullets)>>
This line, given the rule above, sets $liveammo to equal $bullets, but only if $gun is true - if not, it's set to false, i.e. 0 (or whatever falsy value $gun has). It may look counterintuitive, especially since it relies on the aforementioned understanding of true/false reduction, but it's equivalent to <<set $liveammo = ($gun ? 0 : $bullets)>>
Or is implemented in JavaScript this way: "x or y" means "if x is true, the value of this statement is x (i.e true), otherwise the value of this statement is y". This means you could use it like this:
<<set $samusenergy = ($energytank or $reservetank)>>
That line only sets $samusenergy to be $reservetank if the $energytank variable is 0. It's equivalent to <<set $samusenergy = ($energytank ? $energytank : $reservetank)>>
It can also be used, in a way, to specify a "default" value if the first value is 0 or an empty string:
<<set $name = ($name or prompt(“What’s your name?”))>>
This line of code will only prompt the player if $name isn't an empty string (i.e. a name was already given).