<header></header>
<main id='passages'></main>
<footer>
<div id='portrait'>
<img src='idle.png'>
<div id='playerSay'></div>
</div>
<div id='bottomBar'></div>
</footer>
<div id='background'>
<div id='bg4'></div>
<div id='bg3'></div>
<div id='bg2'></div>
<div id='bg1'></div>
</div><h1 id='kissmatic'>KISSMATIC</h1>
<<but [[New game|intro1]]>><</but>>
<<but 'Settings'>>
<<diag 'Settings'>><<include [[Settings]]>>
<</but>>
<<but 'About'>>
<<diag 'About'>><<include [[About]]>>
<</but>><h3 class='date'>February 13th</h3>
<p>Has it been a year already?</p>
<p>Seems so. 364 days -- a lot of seconds. These passed in a blur.</p>
<p>A grey monotonous blur. Countless carbon copies of the same colorless day.</p>
<p>Since the <i>accident</i>.</p>
<p>Still, tomorrow will be [[different|intro2]].</p>
<h5>A game by Maliface.</h5>
<p>This is a short demo for a game that may or may not ever be completed...</p>
<div class='choice'>
<p>Discord : maliface</p>
<p>GitHub : <a href='https://github.com/MalifaciousGames'>MalifaciousGames</a></p>
</div><div id='suitcase'>
<<hover `{class : 'size2'}`>>
Shotgun
<<tip>>The trusty 12 gauge.
<</hover>>
<<hover >>
Shotgun shells x 18
<<tip>>Loaded with love.
<</hover>>
<<hover>>
Heart chocolates
<<tip>>Heals all wounds
<</hover>>
<<hover >>
Brass knuckles
<<tip>>The finishing touch.
<</hover>>
<<hover `{class : 'size3'}`>>
Baseball bat
<<tip>>Blunt.
<</hover>>
<<hover >>
Lil snubby
<<tip>>Size matters, and this one hits just right.
<</hover>>
<<hover `{class : 'size2'}`>>
Vodka (75ml)
<<tip>>To take the edge off.
<</hover>>
<<hover >>
9mm ammo x 60
<<tip>>Enough to last all night long.
<</hover>>
</div>(()=>{let curScroll=0,tipVis=false;const $tip=$('<div>').attr({id:'macro-hover-tip','aria-live':'polite'});const summonTip=(txt,$cont,dir)=>{tipVis=true;curScroll=$(document).scrollTop();$cont.append($tip);$tip.removeClass().wiki(txt);const{height:h,width:w}=$tip[0].getBoundingClientRect(),mrg=4,contRect=$cont[0].getBoundingClientRect();contRect.center={y:contRect.top+contRect.height/2,x:contRect.left+contRect.width/2};const positions={up:{at:[contRect.center.x,contRect.top],stickout:{main: -(h+mrg),sec:w/2}},down:{at:[contRect.center.x,contRect.bottom],stickout:{main:h+mrg,sec:w/2}},left:{at:[contRect.left,contRect.center.y],stickout:{main: -(w+mrg),sec:h/2}},right:{at:[contRect.right,contRect.center.y],stickout:{main:w+mrg,sec:h/2}},over:{at:[contRect.center.x,contRect.center.y],stickout:{main:0,sec:0}}};const checkPos=dir=>{const pos=positions[dir],checked=[];switch(dir){case 'up':case 'down':const vert=pos.at[1]+pos.stickout.main;checked.push((vert>0&&vert<window.innerHeight),(pos.at[0]-pos.stickout.sec>0),(pos.at[0]+pos.stickout.sec<window.innerWidth));break;case 'left':case 'right':const hori=pos.at[0]+pos.stickout.main;checked.push((hori>0&&hori<window.innerWidth),(pos.at[1]-pos.stickout.sec>0),(pos.at[1]+pos.stickout.sec<window.innerHeight));break;default:checked.push(true)}return checked.every(e=>e)};dir=dir.find(d=>checkPos(d));if(!dir){dir=['up','right','down','left','over'].find(d=>checkPos(d))}let[x,y]=positions[dir].at,{main,sec}=positions[dir].stickout;switch(dir){case 'up':y+=main;x-=sec;break;case 'down':y+=mrg;x-=sec;break;case 'left':y-=sec;x+=main;break;case 'right':y-=sec;x+=mrg;break;default:x-=w/2;y-=h/2}$tip.attr({'aria-hidden':false}).css({top:y,left:x}).addClass('visible '+dir)};const hideTip=()=>{tipVis=false;$tip.attr({'aria-hidden':true}).removeClass().empty()};$(document).on('scroll',e=>{if(!tipVis){return}const scr=$(document).scrollTop(),off=scr-curScroll;const pos=Number($tip.css('top').slice(0,-2));$tip.css({top:pos-off});curScroll=scr}).on(':passageinit',hideTip);Macro.add('hover',{tags:['swap','tip'],handler(){const attr=this.args.find(a=>typeof a==='object')??{},pay={},$wrap=$('<span>'),inner=this.payload[0].contents,callbacks={in:[],out:[]};let active=false;attr.tabindex=0;$wrap.attr(attr);this.payload.slice(1).forEach((p,i)=>{pay[p.name]=p;$wrap.addClass(p.name)});if(pay.swap){callbacks.in.push(e=>{$wrap.empty().wiki(pay.swap.contents)});callbacks.out.push(e=>{$wrap.empty().wiki(inner)})}if(pay.tip){let dir=[];if(pay.tip.args.length){dir=pay.tip.args.map(a=>a.split(' ')).flat()}$wrap.attr('role','tooltip');callbacks.in.push(summonTip.bind(null,pay.tip.contents,$wrap,dir));callbacks.out.push(hideTip)}$wrap.on('mouseenter focus',e=>{if(active){return}active=true;callbacks.in.forEach(f=>f.call())}).on('mouseleave focusout',e=>{active=false;callbacks.out.forEach(f=>f.call())}).wiki(inner).addClass(`macro-${this.name }`).appendTo($(this.output))}})})();
/* Important styles, these ensure proper display, should'nt be messed with */
#macro-hover-tip {
position: fixed;
z-index: 100000;
display: grid;
place-items: center;
opacity: 0;
user-select: none;
}
#macro-hover-tip.visible {
opacity: 1;
user-select: auto;
transition: opacity .3s;
}
/* Cosmetic styling, can be changed without issues! */
.macro-hover.tip {
cursor: help;
}
#macro-hover-tip {
border: .15em solid;
border-radius: .2em;
background-color: #111;
width: fit-content;
padding-inline: .5em;
}
/* The small arrow */
#macro-hover-tip::before {
content: '';
height: .5em;
width: .5em;
display: block;
background-color: inherit;
border: inherit;
position: absolute;
border-left: none;
border-top: none;
}
/* Orient the arrow properly based on tip direction */
#macro-hover-tip.over::before {display: none}
#macro-hover-tip.up::before {
bottom: -.4em;
rotate: 45deg;
}
#macro-hover-tip.down::before {
top: -.4em;
rotate: 225deg;
}
#macro-hover-tip.left::before {
right: -.4em;
rotate: 315deg;
}
#macro-hover-tip.right::before {
left: -.4em;
rotate: 135deg;
}<h1 class='floorName'><span style='font-size: 1.5em;'>ζ̅</span>th heaven</h1>
<p>The carpet-covered stairs reach the seventh and final floor.</p>
<p>A vending machine stands in a corner. vending machine macro.</p>
<p>Purple swinging doors block your way. They bear lascivious brass embellishments and the twin handles resemble spread legs.</p>
<p>This is the dreaded yet desired point of no return.</p>
[[Push them appart.|topCorridor]]<p>The heavy fire-proof doors swing out of the way.</p>
<p>A corridor stretches in front of you. Rows of rooms on either sides.</p>
<p>The floor here is covered in purple carpet, yet it appears to gradually turn white up ahead.</p>
<<adel 'Advance.'>>
<<app>>
<p>White petals lie on the ground. Rare at first, they quickly form an even layer.</p>
<p>Thousands of slender petals, rounded at one end.</p>
<p>The light floral layer gives under your feet uncomfortably.</p>
<p class='i'>Just how many?</p>
[[Keep going.|topEnd]]
<</adel>><h1 class='floorName'>α̅th heaven</h1><h1 class='floorName'><span style='font-size: 1.5em;'>β̅</span>th heaven</h1><h1 class='floorName'><span style='font-size: 1.5em;'>γ̅</span>th heaven</h1><h1 class='floorName'><span style='font-size: 1.5em;'>δ̅</span>th heaven</h1><h1 class='floorName'><span style='font-size: 1.5em;'>ε̅</span>th heaven</h1><h1 class='floorName'><span style='font-size: 1.5em;'>6</span>th heaven</h1><p>The current game features themes some players might find disturbing.</p>
<ul class='warning'>
<li>Swearing : <span>◼◼◼◼◼</span></li>
<li>Sex : <span>◼◻◻◻◻</span></li>
<li style='justify-content: center;'>Mentions of:</li>
<li>Depression : <span>◼◼◼◻◻</span></li>
<li>Trauma : <span>◼◼◻◻◻</span></li>
<li>Suicide : <span>◼◼◻◻◻</span></li>
</ul>
<p>I tried not to dwell on the heavier undertones, out of respect.</p>
<p>If I have failed I apologize.</p>
<p>Reach the end of the corridor in a dream-like state.</p>
<p>The final set of doors.</p><p>The hot air hits you like a wave, engulfs you.</p>
<p>That and the smell, it is a pungent mix of perfume, sweat, spices, candy, leather.</p>
<p>Memories come flooding in, everything down to the most minute detail is the exact same as you remember.</p>
<p>A hard lump forms in your throat. Cold sweat.</p>
<p>It's not about the memories you have, it's about the ones you lack...</p>
<<adel 'Breath in, swallow.'>>
<<after '' t8n>>
<p>Nice and deep.</p>
<p>The lobby is square with lounge areas on either sides and a circular front desk. Pink lighting strips run down the wall inset under dark soundproof panels, plunging the space in a cozy semi-darkness.</p>
<p>Laughter and quiet discussions come from the few clients lying on the deep purple couches. These are a merry and scantily clad crowd.</p>
<<say "I don't belong here.">>
<p>You remove the now unbearably hot coat and advance toward the [[front desk|intro8]], suitcase in hand.</p>
<</adel>>
; (() => {
const keyListeners = [], defaultT8n = true;
$(document).on('keyup.macro-a', e => {
keyListeners.filter(i => i.key === e.key || i.key === e.code).forEach(i => i.elem.trigger('click'));
}).on(':passageinit', e => {
keyListeners.forEach(i => {
if (!$.contains(document.body, i.elem[0])) keyListeners.delete(i);
});
});
const sugEquivalents = {
prep: 'prepend',
app: 'append',
rep: 'replace'
};
const parseArgs = args => {
let obj = {}, i = 0;
while (i < args.length) {
const arg = args[i];
if (typeof arg === 'object') {
Array.isArray(arg) ? args.splice(i--, 1, ...arg) : Object.assign(obj, arg);
} else {
const val = args[i += 1];
if (val === undefined) {
throw new Error('Uneven number of arguments.');
} else if (typeof arg !== 'string') {
throw new Error(`Attribute key must be a string, reading: '${arg}'.`);
};
obj[arg.toLowerCase()] = val;
}
i++;
}
return obj;
};
const getReturnValue = exp => {
const ty = typeof exp;
if (ty === 'string') return Scripting.evalTwineScript(exp);
if (ty === 'function') return exp.call();
return exp;
};
const processEvents = events => {
if (typeof events === 'string') return events.split(',').map(v => v.trim());
if (!Array.isArray(events)) return [events];
return clone(events);
};
Macro.add(['a', 'adel', 'but', 'butdel'], {
isAsync: true,
tags: ['rep', 'prep', 'app', 'diag', 'after', 'before'],
handler() {
let [linkText, ...attributes] = this.args, [main, ...payloads] = this.payload,
passage, img,
onClick = [e => $.wiki(main.contents)], postClick = [], selfCheck = [],
deleteSelf = this.name.includes('del'),
count = 0;
attributes = parseArgs(attributes);
const type = this.name[0] === 'b' ? 'button' : 'link', $link = $(`<${type === 'button' ? type : 'a'}>`);
//Process bracket syntax
if (typeof linkText === 'object') ({ text: linkText, link: passage, source: img } = linkText);
//Handle passage argument
if (attributes.hasOwnProperty('goto')) {
passage = typeof attributes.goto === 'object' ? attributes.goto.link : attributes.goto;
delete attributes.goto;
}
if (passage) {
postClick.push(e => Engine.play(passage));
$link.attr('data-passage', passage);
if (Config.addVisitedLinkClass && State.hasPlayed(passage)) {
$link.addClass('link-visited');
} else if (!Story.has(passage)) {
$link.addClass('link-broken');
}
};
//Trigger callback
if (attributes.hasOwnProperty('trigger')) {
const events = processEvents(attributes.trigger);
postClick.push(e => events.forEach(ev => $(document).trigger(ev)));
delete attributes.trigger;
};
//Max clicks
if (attributes.hasOwnProperty('count')) {
const max = attributes.count - 1;
postClick.push(e => count === max ? $link.remove() : $link.attr('data-count', count));
$link.attr('data-count', count);
delete attributes.count;
};
//Exclusive choices
if (attributes.hasOwnProperty('choice')) {
const choiceGroups = processEvents(attributes.choice);
postClick.push(e => choiceGroups.forEach(c => $('[data-choice]').not($link).trigger(':choiceCheck', c)));
$link.attr('data-choice', true).on(':choiceCheck', (e, c) => {
if (choiceGroups.includes(c)) $link.remove();
});
delete attributes.choice;
};
//Key bindings
if (attributes.hasOwnProperty('key')) {
const keys = processEvents(attributes.key);
keys.forEach(k => keyListeners.push({
key: k,
elem: $link
}));
delete attributes.key;
};
//The 2 selfCheck attributes
if (attributes.hasOwnProperty('condition')) {
const exp = clone(attributes.condition);
delete attributes.condition;
selfCheck.push(e => $link[getReturnValue(exp) ? 'show' : 'hide']());
}
if (attributes.hasOwnProperty('disabled')) {
const exp = clone(attributes.disabled);
delete attributes.disabled;
selfCheck.push(e => $link.ariaDisabled(getReturnValue(exp)));
}
if (selfCheck.length) {
selfCheck.forEach(c => c.call());
$link.attr('data-checkself', true).on(':checkSelf', e => selfCheck.forEach(c => c.call()));
}
//Add payload callbacks
payloads.forEach(pay => {
let callback;
if (pay.name === 'diag') {
callback = _ => {
Dialog.setup(pay.args[0] || '', pay.args[1] || '');
Dialog.wiki(pay.contents).open();
}
return onClick.push(callback);
}
const attr = pay.args.find(a => typeof a === 'object' && !(a instanceof jQuery)) || {},
t8n = pay.args.includesAny('transition', 't8n') || defaultT8n;
callback = _ => {
let $trg = pay.args[0] ? $(pay.args[0]) : (['before', 'after'].includes(pay.name) ? $link : $link.parent()),
cName = sugEquivalents[pay.name] ?? pay.name;
const $insert = $('<span>').attr(attr).addClass(`macro-${cName}-insert`).wiki(pay.contents).reveal();
switch (pay.name) {
case 'prep': $trg.prepend($insert); break;
case 'rep': $trg.empty();
case 'app': $trg.append($insert); break;
default: $trg[pay.name]($insert);
}
};
onClick.push(callback)
});
if (deleteSelf) postClick.push(e => $link.remove());
$link
.wikiWithOptions({
profile: 'core'
}, img ? `<img src='${img}' class='link-image'>` : linkText)
.attr(attributes)
.addClass(`macro-${this.name} link-${attributes.href ? 'external' : 'internal'}`);
$link.ariaClick({
namespace: '.macros',
role: type,
one: !!passage || deleteSelf
},
this.createShadowWrapper(
e => {
const oldThis = State.temporary.this;
State.temporary.this = { event: e, self: $link, count };
try {
onClick.forEach(callback => callback.call(null, e));
} finally {
State.temporary.this = oldThis;
}
},
e => {
count++;
postClick.forEach(callback => callback.call(null, e));
setTimeout(() => $('[data-checkself]').trigger(':checkSelf', 40));
}
)
);
this.output.appendChild($link.get(0));
}
});
})();
<p>The room is small, metal lockers stretch along the right wall, brooms and buckets are stacked in a corner.</p>
<<reveal 'Get up.'>>
<p>You push against the wall to stand up.</p>
<p>The tasteless pink fuzz of the handcuffs itches, adds insult to injury.</p>
<<adel 'Examine the lockers.' condition 'Player.strength < 5'>>
<<say "Won't open without a key.">>
<</adel>>
<<adel 'Smash the lockers.' condition 'Player.strength === 5 && _free'>>
<<after>>
<<say "Not in a mood for a cocktail anymore...">>
<p>Your punch caves the sheet metal in, revealing a colorful assortment of cleaning supplies.</p>
<</adel>>
<p class='choice'>
<<a 'Think about the situation.' count 6>>
<<run _this.self.css('filter', `blur(${ _this.count*2 }px)`)>>
<<rep '#th'>><<= ['You rack your brain to find a way out.','The fuck did you expect?','There you are, just where you wanted to be.','What\'s the plan now? Concentrate.','Breath slowly. Don\'t panic. There must be a way.','Fuck it, panic.'][_this.count]>>
<</a>>
<p id='th'/>
<<a 'Slam your head against the wall.' condition 'Player.strength < 5'>>
<<run Player.dmg(3)>>
<<shake>>
<<rep '#th'>><<= ['Pain spreads across your forehead, deep into your skull.','The wall makes a dull sound.','Blood drips down your face. That\'ll definitely leave a mark...','You feel weak and dizzy. The wall is slick with blood.','There we go.'][_this.count]>>
<</a>>
</p>
<<adel 'Free yourself.' condition 'Player.strength === 5'>>
<<after>>
<p>Bend over, tense every muscle in your arms, shoulders and back.</p>
<p>The taught chain slowly stretches as the funzy cuffs dig into your wrists.</p>
<<reveal 'Break the handcuffs.'>>
<<say "There you go.">>
<<set _free = true>>
<p>With a last, sharp tug, the small links snap.</p>
<em>Free.</em>
<p>Better make the most of this strength while it lasts.</p>
<<step 'Deal with the door.'>><<shake>>
<p>Taking a step back you kick it with all your might.</p>
<p>It shakes on its hinges, the wood creaks.</p>
<<step 'Again.'>><<say "Just one more.">><<shake>>
<p>The lock rips clean out of the splintered fibers.</p>
<p>The door flings open on [[the empty corridor.|hotelEscape2]]</p>
<</reveal>>
<</adel>>
<</reveal>><<combat 'bouncer' 'scion' 'bouncer' 'bouncer'>><p>You died.</p>
<p>Add different deaths...</p>
<<button 'Restart'>>
<<run UI.restart()>>
<</button>>The second floor archon is actually waiting for you in his room.
Had heard of your arrival and gotten curious.
Mentions the earlier experience one year ago, says that it is common to come back, for one reason or another.
The missing suitcase gear can be found after defeating him, including the shotgun.The floor 3 archon is giving a lengthy lecture on gnostic values to a small crowd.
This can be interrupted at any moment but clicking 'cut the crap'. If not the archon will finally notice the player.<p>Is everything ready?</p>
<p>You know it is, you checked already.</p>
<p>The off-white suitcase sits on the bed, closed, neatly packed.</p>
<div class='choice'>
<<a 'Check it one more time.'>>
<<rep '' 't8n'>>
<<include [[suitcase]]>>
<<say 'Everything the body needs.'>>
<<app '#p' 't8n'>>It is late already. You should [[sleep|intro3]].
<</a>>
<<a 'It\'s fine, don\'t sweat it.'>>
<<rep '' 't8n'>>
<p>Everything's there, you know it.</p>
<p>You should take some rest instead.</p>
<<app '#p' 't8n'>>It is late already. You should [[sleep|intro3]].
<</a>>
</div>
<p id='p'/><p>You twist and turn in bed.</p>
<p>Slipping into dreamless oblivion has not been an issue for the past year. 12 hours -- 14 sometimes -- spent in thoughtless abandonment.</p>
<p>How much time does one really spend living?</p>
<p>It's been a year already. Felt like yesterday, still feels like it.</p>
<p>You <i>really</i> should get some sleep.</p>
<div class='choice'>
[[Reach for the pills.|intro4]]
<<a 'Don\'t trust the drugs, sleep will come eventually'>>
<<rep '' 't8n'>>
<p>You twist and turn some more. Rustle in the sheets.</p>
<p>Get up. Have a glass of water.</p>
<p>Try to read. Words.</p>
<<app '#p' t8n>>
<p>A pale glow rises in the east. Sleeping would do little good at this point.</p>
<p>Might as well [[get up|intro4]].</p>
<</a>>
</div>
<span id='p'/><h3 class='date'>February 14th</h3>
<p>You carry the heavy suitcase on your way to the bus stop.</p>
<p>An opaque winter fog blankets the small country road. Frost-covered grass crunches under your steps.</p>
<p>The bus is empty at this early hour, the drowsy driver mumbles as you get in.</p>
<p class='choice'>
<<a '"Really is cold today, hey?"'>><<say '. . .'>><<rep '' 't8n'>>Nah, nevermind...<</a>>
<<a '"Guess you don\'t see many people at this hour."'>><<say '. . .'>><<rep '' 't8n'>>Nah, nevermind...<</a>>
<<a '"Hey, don\'t go falling asleep on the wheel, I can\'t drive that thing."'>><<say '. . .'>><<rep '' 't8n'>>Nah, nevermind...<</a>>
</p>
<p>Detached houses spread far and wide, off-white, dark garden, tall hedges.</p>
<p>Then the brick terraced houses, endlessly identical.</p>
<p>The bus stops [[near a shody square|intro5]].</p><<vending 'condom' 'ropes' 'panties' 'chocolates' 'ballgag' 'cuffs' 'blinddrops'>>
<<a "I ain't got time for this shit.">>
<<vendSay 'I don\'t see what you mean...'>>
<</a>><p>The building stands tall and dark under the white February sky.</p>
<p>The neon front sign spells :</p>
<h1>Grand Pleroma Hotel</h1>
<p>Initiates refer to it as the GPH -- brothel, love hotel, whatever you want to call it.</p>
<p>Despite the early hour you know the inside is as lively as ever. It always is.</p>
<p>The Hotel is 24/7, it has its own time, it is a separate world where one does not think about the outside.</p>
<<adel 'Check your wallet'>>
<<after '' t8n>>
<p>It even has its own currency, you examine the <span class='money'>54</span>.</p>
<</adel>>
<p>Those nicely curtained windows are just for show. Daylight does not reach inside, you know it. Warm, pink artificial lighting, at all hours of the day -- and night.</p>
[[Go in.|intro6]]/* Mali's <<arrowbox>> macro */
Macro.add("arrowbox",{arrows:{up:{symbol:"🞁",key:"ArrowUp"},down:{symbol:"🞃",key:"ArrowDown"},left:{symbol:"🞀",key:"ArrowLeft"},right:{symbol:"🞂",key:"ArrowRight"}},arrowElementType:"button",wrapperDesc:"Arrow keys or scroll to cycle",cycleCooldown:400,tags:["option","optionsfrom"],skipArgs:["optionsfrom"],isAsync:!0,addOptions:function(e,t,a){a&&(this.selected=this.length);const s={label:e,value:t??e};this.push(s)},runAnimation:function(e,t,a){e.addClass(t).one("animationend",(()=>{e.removeClass(t),a&&a()}))},handler(){const e=this.args[0].trim(),t=[],a=this.self.addOptions.bind(t),s=this.self.runAnimation,i={direction:this.args.find((e=>"horizontal"===e||"vertical"===e))??"horizontal",cycleWrap:this.args.includes("wrap"),autofocus:this.args.includes("autofocus"),animations:!this.args.includes("no-animations")||!0,centerInput:this.args.includes("type-in")};switch(this.payload.filter((e=>"option"===e.name)).forEach((e=>a(e.args[0],e.args[1],e.args.includes("selected")))),this.payload.filter((e=>"optionsfrom"===e.name)).forEach((e=>{const t=e.arguments.trim(),s=Scripting.evalTwineScript("{"===t[0]?`(${t})`:t);if(Array.isArray(s)||s instanceof Set)s.forEach((e=>a(e)));else if(s instanceof Map)s.forEach(((e,t)=>a(t,e)));else{if("object"!=typeof s)return this.error("The contents of the 'optionsfrom' tag should evaluate to a valid collection.");$.each(s,((e,t)=>a(e,t)))}})),t.length){case 0:return this.error(`The ${this.name} macro cannot be called without any options.`);case 1:i.cycleWrap=!1}let r=0,n=!0;if(t.hasOwnProperty("selected"))i.startValue=t[t.selected],r=t.selected;else{const a=State.getVar(e);void 0!==a&&t.find((e=>e.value===a))?(i.startValue=t.find((e=>e.value===a)),r=t.indexOf(i.startValue)):i.startValue=t[0]}State.setVar(e,i.startValue.value);const o=[],l=$("<form>").attr({id:`${this.name}-${Util.slugify(e)}`,class:`macro-${this.name} ${i.direction}`,title:(i.centerInput?"Crtl + ":"")+this.self.wrapperDesc,role:"group",tabindex:i.centerInput?"-1":"0"}),c=$(`<${i.centerInput?"input type='text'":"div"}>`).attr({class:`${this.name}-value`}).appendTo(l),p=$("<div>").addClass("anim-wrapper").attr("aria-live","polite").appendTo(c);function u(){i.cycleWrap?(r%=t.length,r<0&&(r=t.length-1)):(r?o[0].is(":disabled")&&o[0].ariaDisabled(!1):o[0].ariaDisabled(!0),r===t.length-1?o[1].ariaDisabled(!0):o[1].is(":disabled")&&o[1].ariaDisabled(!1))}const d=(t,a,r)=>{if(n=!1,setTimeout((()=>{n=!0}),this.self.cycleCooldown),i.centerInput)c.val(t.label),c[0].style.width=.9*String(t.label).length+"ch",a&&c.focus();else if(i.animations&&r){s(p,`slide${r}`,(()=>{p.empty().wiki(t.label)}))}else p.empty().wiki(t.label);State.setVar(e,t.value)};({horizontal:["left","right"],vertical:["up","down"]})[i.direction].forEach(((e,a)=>{const s=this.self.arrows[e],p=$(`<${this.self.arrowElementType}>`).append(s.symbol).attr({class:`${this.name}-arrow-${e}`,"data-key":s.key}),h=i.centerInput?c:l;p.ariaClick({namespace:".macros",label:a?"Next":"Previous","aria-label":a?"Next":"Previous",role:"button"},(()=>{h.focus(),r+=a?1:-1,u(),d(t[r],!0,e)})).attr("tabindex",i.centerInput?"0":"-1"),h.on("keydown",(e=>{const t=!i.centerInput||e.ctrlKey;if(n&&t&&e.key===s.key)return p.click(),e.preventDefault(),!1})),o.push(p),l[a?"append":"prepend"](p)})),i.centerInput&&c.on("input",(e=>{t[r]={label:c.val(),value:c.val()},d(t[r])})),l.on("wheel",(e=>{n&&o[e.originalEvent.deltaY<0?1:0].click()})),d(i.startValue),u(),i.autofocus&&setTimeout((()=>l.focus()),200),$(this.output).append(l)}});/* Mali's <<arrowbox>> macro */
.macro-arrowbox {
display: inline-flex;
align-items: center;
gap: .25em;
}
.macro-arrowbox > input {
min-width: 3em;
text-align: center;
padding: 0;
width: fit-content !important;
}
.arrowbox-value {
border: 1px solid #444;
user-select: none;
padding: 0 .5em;
}
.macro-arrowbox:focus > .arrowbox-value {
border-color:seashell;
}
.macro-arrowbox > :first-child, .macro-arrowbox > :last-child {
line-height: 1em;
padding: .2em;
z-index: 1;
}
.macro-arrowbox:focus, .macro-arrowbox:hover {
outline: 1px solid #444;
}
.macro-arrowbox.vertical {
flex-direction: column;
gap: 0;
margin: 1em 0;
position: relative;
}
.macro-arrowbox.vertical > :first-child, .macro-arrowbox.vertical > :last-child {
position: absolute;
width: 100%;
line-height: .4em;
}
.arrowbox-arrow-up {top: -1em}
.arrowbox-arrow-down {bottom: -1em}
.anim-wrapper {
position: relative;
animation-duration: .4s;
}
@keyframes arrowbox-slideup {from {translate: 0 0;opacity: 1} to {translate: 0 2em;opacity: 0}}
.anim-wrapper.slideup {animation-name: arrowbox-slideup}
@keyframes arrowbox-slidedown {from {translate: 0 0;opacity: 1} to {translate: 0 -2em;opacity: 0}}
.anim-wrapper.slidedown {animation-name: arrowbox-slidedown}
@keyframes arrowbox-slideleft {from {translate: 0 0;opacity: 1} to {translate: 2em 0;opacity: 0}}
.anim-wrapper.slideleft {animation-name: arrowbox-slideleft}
@keyframes arrowbox-slideright {from {translate: 0 0;opacity: 1} to {translate: -2em 0;opacity: 0}}
.anim-wrapper.slideright {animation-name: arrowbox-slideright}
Floor 4 is a labyrinth of tight corridors and curtained dead-ends. <p>You push the heavy door which swings silently shut behind you.</p>
<p>The antechamber is carpetted from floor to ceiling with plush dark red carpet. You realize just how quiet this place is compared to the outside.</p>
<p>The elegantly-dressed bouncer stares you down and asks : "Business or pleasure?"</p>
<p class='choice'>
<<a '"Business."'>>
<<say 'Business.'>>
<<rep '' 't8n'>>
"Pleasurable business I hope." he laughs.
<<app '#p' 't8n'>>
<p>The bouncer swiftly opens the door in front of you. Holding it ajar.</p>
<p>You can feel the heat, see the warm pink light inside, hear the muffled music.</p>
<p>"Please [[come in|intro7]]."</p>
<</a>>
<<a '"Pleasure."'>>
<<say 'Pleasure.'>>
<<rep '' 't8n'>>
"Very well. Have a good time." he whispers.
<<app '#p' 't8n'>>
<p>The bouncer swiftly opens the door in front of you. Holding it ajar.</p>
<p>You can feel the heat, see the warm pink light inside, hear the muffled music.</p>
<p>"Please [[come in|intro7]]."</p>
<</a>>
</p>
<span id='p'/><p>A woman wearing glasses and a Pleroma-branded suit turns to you with a smile.</p>
<p>"Welcome to the GPH."</p>
<p>"First time here?"</p>
<p class='choice'>
<<adel '"It is."'>>
<<say 'It is.'>>
<<rep '' 't8n'>>
<p>The woman stares at you over her glasses and smiles.</p>
<p>"In this case I wish you a great first experience. The GPH is a place like no other, you will see!"</p>
<i>No shit...</i>
<</adel>>
<<adel '"Nah, second."'>>
<<say 'Nah, second.'>>
<<rep '' 't8n'>>
<p>"Coming back for more I see. Love to hear it!"</p>
<p>The woman smiles radiantly.</p>
<</adel>>
</p>
<p>"Please let me take your coat. The suitcase too?"</p>
<<adel "\"I'll keep the suitcase.\"">>
<<say "I'll keep the suitcase.">>
<<after '' t8n>>
<p>She gives you an understanding nod as she reaches for the coat. Her expert hand sticks a pink paper tag.</p>
<p>"Your name is?"</p>
"[[Daisy.|intro9]]"
<</adel>>
Skipping the intro means entering the GPH through a back entrance and getting into combat directly with the whole arsenal.<<set _names = ['Bologna, Sophia', 'Equin, Georges', 'Shopmann, William', 'Berry, Tran', 'Gilda', 'Valensky, Vincent', 'Rasputin', 'Tastic, Gwen', 'Corinth', 'Murakami, Ryu', 'SloopyFeel', 'Kossuth, Lajos', 'Amora, Manon' ]>>
<<arrowbox '$name' wrap type-in>>
<<optionsfrom _names>>
<</arrowbox>><p>The woman stares at you over her glasses and smiles.</p>
<p>"In this case I wish you a great first experience. The GPH is a place like no other!"</p>
<i>No shit...</i>
<p>"Coming back for more I see. Love to hear it!"</p>
<p>The woman smile radiantly.</p><p>Her walk is brisk, she leads you down a flight of carpeted stairs toward a dimly lit corridor.</p>
<p>"Showers and dressing rooms are here on the right, in case you want to get ready." She eyes the heavy suitcase.</p>
<p>The heat seems to increase as you dive deeper inside the secluded world. The cold winter sky so far out of view in the plush darkness.</p>
<p>"Please wait here. I will come back to you as soon as your partner arrives."</p>
<p>She stands aside to let you in a small cinnamon-scented room. Two deep armchairs and a transparent coffee table are crammed in the tight space.</p>
<p>"Please let me know if you need anything."</p>
<p class='choice'>
<<adel '"How long have you been working here?"'>>
<<say "How long have you been working here?">>
<<rep '' t8n>>
<p>"I have been doing this for almost four years now.", she seems lost in thoughts for a second.</p>
<p>"Time flies."</p>
<i>Do you remember her from last time? Impossible to say.</i>
<</adel>>
<<adel '"What is it about this place?"'>>
<<say "What is it about this place?">>
<<rep '' t8n>>
<p>Once again, the woman smile.</p>
<p>"The Grand Pleroma is not any club or love hotel. It is a way of life. We are open to anyone, at any time, for anything... for the right price of course."</p>
<p>"And I hope this stay convinces you of it."</p>
<</adel>>
</p>
<p>The elegant host takes her leave.</p>
<p>A pink neon right on the ceiling bathes the room in its kitch hue. On the wall, you notice a poster bearing the word "Ascend". The lighting and the heavily reclined chair make you drowsy.</p>
[[Open the suitcase, get ready.|intro11]]<<set _names = ['Bologna, Sophia', 'Equin, Georges', 'Shopmann, William', 'Berry, Tran', 'Gilda', 'Valensky, Vincent', 'Rasputin', 'Tastic, Gwen', 'Corinth', 'Murakami, Ryu', 'SloopyFeel', 'Kossuth, Lajos', 'Amora, Manon' ]>>
<p>She pauses for a second before swiftly scribbling your name on the tag. The coat vanishes in the dark storage room dehind the desk.</p>
<<say 'Did she figure it out?'>>
<p>The elegant receptionist returns with the usual smile.</p>
<p>"Are you meeting someone?"</p>
<<adel '"Yes, I am."'>><<say "Yes, I am.">>
<<after '' t8n>>
<p>"Name is <<arrowbox '$name' wrap type-in>><<optionsfrom _names>><</arrowbox>>."</p>
<p>The woman leans down toward an inset computer screen.</p>
<p>"I am sorry, they have not arrived yet. Follow me, I will show you where the [[waiting room|intro10]] is."</p>
<</adel>><p>You hoist the suitcase on the small table and unzip it.</p>
<<include [[suitcase]]>>
<<reveal 'The door opens.'>>
<<run $('#suitcase').remove(); $('body').removeClass('intro')>>
<<shake>>
<p>A man rushes in, knocks the table over.</p>
<<step 'The f...'>><<say 'The f...'>><<shake>>
<p>Adrenaline. Fight.</p>
<p>Weapons on the ground. Fuck.</p>
<<step 'Headbutt.'>><<shake>>
<p>Broken nose.</p>
<p>Is the revolver loaded? Should have checked.</p>
<<step 'Duck.'>><<say 'Piece of...'>><<shake>>
<<set Player.hp -= 2>>
<p>Pain.</p>
<p>Dizziness.</p>
<<step 'The ground.'>><<shake>>
<p>. . .</p>
<p>Your head spins as you're lifted from the ground.</p>
<p>Handcuffs.</p>
<<step 'Fuck.'>><<say 'Fuck'>>
<p>You are dragged back through the [[corridor|intro12]].</p>
<</reveal>>
<p>Doors on either side pass by. How many? Which way?</p>
<<say "Hold on, don't vomit.">>
<p>"You knew yet you came back."</p>
<p>Focus.</p>
<span class='choice'>
<<adel '"Fuck off."'>>
<<say "Fuck off.">>
<<rep>>
<p>Is someone walking behind you?</p>
<p>The receptionist?</p>
<</adel>>
<<adel '"Got no memories from... last time..."'>>
<<say "Got no memories from... last time...">>
<<rep>>
<p>"It's better than way."</p>
<p>The man snorts noisily. Broken nose, no doubt.</p>
<p>"Should have kept out of it."</p>
<</adel>>
</span>
<p>You stop in front of a small broom-closet-looking room.</p>
<p>"Just wait in there, your partner will arrive soon."</p>
<p>You are forcibly pushed inside, meeting the tile floor as the [[door locks behind you|hotelEscape1]].</p><<done>><<say "So, which way?">><</done>>
<p>You stumble out into the dark corridor.</p>
<p>Pink neon light rains down from a long recessed fixture in the ceiling.</p>
<span class='choice'>
<<a 'Go left.' trigger 'rev'>><<rep>>
<p>Left it is.</p>
<</a>>
<<a 'Go right.' trigger 'rev'>><<rep>>
<p>Right it is.</p>
<</a>>
</span>
<<on 'rev' '' hidden>>
<p>Doors, all similar, line up on either sides at regular interval. Like rooms in an hotel, or cells in a prison.</p>
<<reveal 'Try to open one.'>><<say 'No luck.'>>
<p>Locked.</p>
<p>What are those anyway?</p>
<<step 'Another one?'>><<say 'Son of a...'>>
<p>Same deal.</p>
<p>Broom lockers? Waiting rooms similar to the one you saw before?</p>
<<step 'Third time the charm?'>><<say 'Piece of shit!'>>
<p>Closed.</p>
<p>Don't waste time, keep going. Gotta retrieve the suitcase.</p>
<<step 'Ok, last one...'>><<say 'At last.'>>
<p>The door open on a [[small room|chocoRoom]] which holds a pink sofa and an elegant pedestal table.</p>
<p>Ain't got time for that, [[keep going|hotelEscape3]]</p>
<</reveal>>
<</on>><p>The space is small, just wide enough for the length of the sofa. A smell of cold tobacco hangs in the still air.</p>
<p>A rectangular velvet-covered box lies on the tall octogonal table.</p>
<<reveal 'Open it.'>>
<<say 'Not bad...'>>
<<set $player.consummables.chocolates = 5>>
<p>Chocolates. The box holds five heart-shaped chocolates.</p>
<<step 'Eat one.'>><<run Item.chocolates.use()>>
<p>Tasty.</p>
<<step 'Another one.'>><<run Item.chocolates.use()>>
<p>Invigorating.</p>
<<step 'One more.'>><<run Item.chocolates.use()>>
<p>Better not to overdo it.</p>
<p>You pocket the rest, ready to [[leave|hotelEscape3]].</p>
<</reveal>>
//Clean unused listeners
$(document).on(':passageend', () => {
let events = Macro.get('on').activeListeners, i = events.length;
while(i--) {
const e = events[i];
//Check if update wrapper is still on the page
if (!$(`[data-id='${e.split('.').last()}']`).length){
$(document).off(events.delete(e)[0]);
}
};
});
Macro.add('on', {
isAsync : true,
tags : null,
activeListeners : [],
counter : 0,
handler() {
if (!this.args[0]) {
return this.error(`Missing event type.`);
} else if (typeof this.args[0] !== 'string'){
return this.error(`Event name must be a string, reading: ${typeof this.args[0]}.`);
};
const def = this.self, id = def.counter++, payload = this.payload[0].contents,
wrapper = $(document.createElement(this.args[1] || 'span')).attr('data-id',`macro-on-${id}`), attributes = this.args.find(o => typeof o === 'object'),
events = this.args[0].split(',').map(e => `${e.trim()}.macro-on-${id}`),
t8n = this.args.includesAny('t8n','transition'), startHidden = this.args.includesAny('startHidden','hidden');
if (attributes){wrapper.attr(attributes)};
if (!startHidden){wrapper.wiki(payload)};
// Apply listener for each event name
events.forEach(event => {
def.activeListeners.push(event);
$(document).on(event, this.createShadowWrapper((e) => {
const oldE = State.temporary.event;
State.temporary.event = e;
try {
wrapper.empty().wiki(payload).reveal();
} finally {
oldE === undefined? delete State.temporary.event : State.temporary.event = oldE;
}
}));
});
wrapper.addClass(`macro-${this.name}`).appendTo(this.output);
}
});
// Triggers custom events
Macro.add('trigger', {
handler() {
let trig = this.args[0];
if (!['string','object'].includes(typeof trig)) {
return this.error(`Invalid event type, reading :'${typeof trig}'.`);
}
if (typeof trig === 'string'){ //Comma-separated string of events
trig = trig.split(',').map(event => event.trim());
} else if (typeof trig === 'object' && !Array.isArray(trig)){ //A single event object
trig = [trig];
}//Do nothing if trig is already an array, it's fine
// Triggers each event supplied
trig.forEach(event => {
$(this.args[1] ?? document).trigger(event);
});
}
});
<<say 'Time for payback.'>>
<p>A man wearing a suit exits an adjacent room, carrying an off-white suitcase.</p>
<p>Nose covered in white surgical tape.</p>
<p>"Wha..."</p>
<<adel "Fight.">>
<<after>>
<p>And... this is where the demo ends...</p>
<p>The fight system still needs a lot of polish.</p>
<p><b>Thanks for playing!</b></p>
<</adel>>
<<say 'Still breathing...'>>
<p>You step over the man's immobile body.</p>
<<adel 'Search him.'>>
<<after>>
<p>That broken nose is the least of his worries now...</p>
<p>You swipe a dark leather wallet from his front pocket.</p>
<p>Item found : keycards</p>
<p>Item found : <<money 45>></p>
<</adel>>
<p>The white suitcase's contents lie scattered on the carpeted floor.</p>
<p>Baseball bat, chocolates, revolver, ammunitions...</p>
<<adel 'Gather your stuff.'>>
<<after>>
<i>Items retrieved.</i>
<p>The shotgun is missing however.</p>
<<say 'Shit!'>>
[[Check the adjacent room.|adjRoom]]
<</adel>>
[[Gotta get going.|hotelEscape5]]<p>You stumble back, ready for another blow.</p>
<<reveal 'Nothing.'>>
<p>No one in your narrowed field of view.</p>
<<step 'Breath.'>>
<p>Your lungs are on fire.</p>
<p>Your whole body aches.</p>
<<step 'Focus.'>>
<<say 'Focus'>>
<p>Your head hurts and you realize just how narrow your field of vision is.</p>
<p>Like wearing a motorcycle helmet.</p>
<<step 'Come on.'>>
<p>Someone else might come, gotta hurry.</p>
<p>At least [[retrieve your weapons|hotelEscape4]] before they do.</p>
<</reveal>>
See the items!<p>You recognize the waiting room. The two armchairs, the "ascend" poster on the wall.</p>
<p>A keychain lies on the transparent coffee table.</p>
<<adel 'Snatch it.'>>
<<after>>
<<say 'Let\'s see what this opens.'>>
<p>Item : Keychain</p>
<</adel>>
<p>Maybe if you wait here the "partner" will show up? This one too will get a piece of your mind.</p>
<p>Better [[get going|hotelEscape5]], this place holds all matters of tricks.</p>What if the bouncer gets up? What if he calls for help?
How much time do you have before the situation becomes very dire?
Put a bullet in his head, while you're near.
Feel the weight of the revolver.
Can you kill a man?
What if he doesn't call for help? Then the gunshot would be more noisy.
Are you capable of killing?
Do not shy away from the question, face it.
---
Set out to find a way up.
Stairs, not elevator.
Look around for a disguise : [[findDisguise]].
Or don't, go in guns blazing.
[[Climb the stairs.|floor1]]You hurry <p>Nothing to see yet.</p>