window.CGP_PAGE={"pages": "https://charliesgnarlyparty.com/wp-content/uploads/2026/09/", "ext": "webp", "merch": "https://charliesgnarlyparty.com/wp-content/plugins/charlies-gnarly-party/assets/merch/", "cart": "", "shop": "https://charliesgnarlyparty.com/shop/"};
document.documentElement.classList.add("cgp-landing");
if(document.body){document.body.classList.add("cgp-landing");}
else{document.addEventListener("DOMContentLoaded",function(){document.body.classList.add("cgp-landing");});}
(function(){
"use strict";
/* =========================================================
1. THE BOOK. A real open book: odd page left, even right.
========================================================= */
var TOTAL = 35;
// PDF page 1 is the front cover and page 2 is the BACK cover, so page 2
// belongs at the end. Pages 3 and 4 are the copyright and title pages:
// front matter nobody wants to click through, so the story opens straight
// onto printed page 2 at PDF page 5. From there the parity holds all the
// way down, odd on the left and even on the right, and page 35 (the end)
// has no partner.
var FRONT = 1, BACK = 2, STORY = 5, LAST = 35;
function spreadViews(){
var v = [[FRONT]];
for(var i=STORY;i<=33;i+=2) v.push([i,i+1]);
v.push([LAST]);
v.push([BACK]);
return v;
}
function singleViews(){
var v = [[FRONT]];
for(var i=STORY;i<=LAST;i++) v.push([i]);
v.push([BACK]);
return v;
}
// the number printed on the page itself, which runs three behind the PDF
function printed(n){ return n - 3; }
var wide = window.matchMedia('(min-width:860px)');
var views = wide.matches ? spreadViews() : singleViews();
var vi = 0;
var book = document.getElementById('book');
var leafL = document.getElementById('leafL');
var leafR = document.getElementById('leafR');
var imgL = document.getElementById('imgL');
var imgR = document.getElementById('imgR');
var prevBtn = document.getElementById('prev');
var nextBtn = document.getElementById('next');
var bar = document.getElementById('bar');
var flipper = document.getElementById('flipper');
var flipFront = document.getElementById('flipFront');
var flipBack = document.getElementById('flipBack');
var readerBox = document.getElementById('readerBox');
var reduce = window.matchMedia('(prefers-reduced-motion: reduce)');
var FLIP_MS = 620;
var busy = false, lastPair = null;
var PAGES = (window.CGP_PAGE && CGP_PAGE.pages) || 'pages/';
var PAGE_EXT = (window.CGP_PAGE && CGP_PAGE.ext) || 'jpg';
function src(n){ return PAGES + 'p-' + String(n).padStart(2,'0') + '.' + PAGE_EXT; }
// Warm the next couple of views so turning feels instant, without
// pulling all 35 pages down on first paint.
function prefetch(){
for(var k=vi+1;k<=Math.min(vi+2,views.length-1);k++){
views[k].forEach(function(n){ new Image().src = src(n); });
}
}
function paintLeaves(pair){
book.classList.toggle('single', pair.length === 1);
imgL.src = src(pair[0]);
imgL.alt = 'Charlie’s Gnarly Party, page ' + printed(pair[0]);
if(pair.length > 1){
leafR.hidden = false;
imgR.src = src(pair[1]);
imgR.alt = 'Charlie’s Gnarly Party, page ' + printed(pair[1]);
}else{
leafR.hidden = true;
imgR.removeAttribute('src');
imgR.alt = '';
}
}
function cue(cls){
[leafL,leafR].forEach(function(l){
l.classList.remove('soft','slide'); void l.offsetWidth; l.classList.add(cls);
});
}
// A spread turning into another spread gets the real thing: the leaf you
// grabbed rotates on the spine, its back face carrying the page underneath.
function turnLeaf(from, to, dir){
var fwd = dir > 0;
flipFront.src = src(fwd ? from[1] : from[0]);
flipBack.src = src(fwd ? to[0] : to[1]);
// the stationary half holds still while the far half is already revealed
book.classList.remove('single');
leafR.hidden = false;
imgL.src = src(fwd ? from[0] : to[0]);
imgR.src = src(fwd ? to[1] : from[1]);
busy = true;
flipper.hidden = false;
flipper.classList.toggle('rev', !fwd);
flipper.classList.remove('run'); void flipper.offsetWidth;
flipper.classList.add('run');
setTimeout(function(){
flipper.classList.remove('run');
flipper.hidden = true;
paintLeaves(to);
busy = false;
}, FLIP_MS);
}
function render(dir){
var pair = views[vi];
var from = lastPair;
lastPair = pair;
book.classList.toggle('back', dir < 0);
var canFlip = from && from.length === 2 && pair.length === 2 && !reduce.matches;
if(canFlip){
turnLeaf(from, pair, dir);
}else{
paintLeaves(pair);
if(from) cue(views.length > 20 ? 'slide' : 'soft');
}
bar.style.width = ((vi+1) / views.length * 100) + '%';
bar.parentNode.setAttribute('aria-valuenow', vi+1);
bar.parentNode.setAttribute('aria-valuemax', views.length);
prevBtn.disabled = vi === 0;
nextBtn.disabled = vi === views.length - 1;
prefetch();
if(vi === views.length - 1) showEnd();
}
function go(d){
if(busy) return;
var n = vi + d;
if(n < 0 || n >= views.length) return;
vi = n; render(d);
}
prevBtn.addEventListener('click', function(){ go(-1); });
nextBtn.addEventListener('click', function(){ go(1); });
book.addEventListener('click', function(e){
var r = book.getBoundingClientRect();
go(e.clientX - r.left < r.width/2 ? -1 : 1);
});
document.addEventListener('keydown', function(e){
if(document.getElementById('funnel').classList.contains('open')) return;
if(document.getElementById('contact').classList.contains('open')) return;
if(e.key === 'ArrowLeft') go(-1);
if(e.key === 'ArrowRight') go(1);
});
var x0=null,y0=null;
book.addEventListener('touchstart', function(e){
x0 = e.touches[0].clientX; y0 = e.touches[0].clientY;
}, {passive:true});
book.addEventListener('touchend', function(e){
if(x0===null) return;
var dx = e.changedTouches[0].clientX - x0, dy = e.changedTouches[0].clientY - y0;
if(Math.abs(dx) > 45 && Math.abs(dx) > Math.abs(dy)) go(dx < 0 ? 1 : -1);
x0 = y0 = null;
}, {passive:true});
// Keep the reader's place when the layout flips between one and two pages.
function relayout(){
var current = views[vi][0];
views = wide.matches ? spreadViews() : singleViews();
lastPair = null;
vi = 0;
for(var i=0;iThat is the part you are actually buying. Their name goes inside the front cover, by hand.
A funny book gets read once. A funny book with their name in the front gets shown to everyone who comes round.
' +
'
' +
photo(SHOT.personalized, 'A personalized copy, signed inside the front cover') +
'
' +
'Hand-written inscription' +
'
Their name, your line, David’s signature. Inside the front cover.
' +
'
+' + money(PRICE.upgrade) + '' +
'Only sold here
' +
'
' +
'
';
fnBody.appendChild(actions(
btn('Yes, put their name in it', 'btn-primary btn-block', function(){
cart.edition = 'personalized'; draw();
}),
quiet('No, keep it plain', next)
));
}
}
/* ---------- step 3 ---------- */
function drawQty(){
var three = PRICE[cart.edition] + 2*PRICE.extraCopy;
var rrp = PRICE[cart.edition] + 2*PRICE.extraCopyRrp;
fnBody.innerHTML =
'
Nobody buys one
' +
'
You hand it over, it never comes back, and by Friday you have thought of two more people. Extra copies are ' + money(PRICE.extraCopy) + ' instead of ' + money(PRICE.extraCopyRrp) + '.
' +
'
' +
photo(SHOT.hardcover, 'The hardcover edition') +
'
';
box.appendChild(m);
var slot = fnBody.querySelector('#bumpSlot');
if(slot){
slot.innerHTML = '';
if(!cart.stickers && stickersAvailable()){
var lab = document.createElement('label');
lab.className = 'bump';
lab.innerHTML = '' +
'Add the sticker pack for ' + money(PRICE.stickers) +
' ' + money(PRICE.stickersRrp) + '' +
'Same envelope, no extra postage.';
lab.querySelector('input').addEventListener('change', function(){
cart.stickers = this.checked; paintSummary(); refreshCta();
});
slot.appendChild(lab);
}
}
fnTot.textContent = money(total());
}
var CGP_IDS = {hardcover:2158, signed:2508, personalized:2596, stickers:2274,
kevin:2370, molly:2376, lucy:2396};
function addNative(id, qty, then){
var u = '/?add-to-cart=' + id + '&quantity=' + (qty || 1);
fetch(u, {credentials:'same-origin'}).then(then).catch(then);
}
function fillCartFallback(done){
var jobs = [[CGP_IDS[cart.edition] || CGP_IDS.hardcover, cart.qty]];
if(cart.stickers) jobs.push([CGP_IDS.stickers, 1]);
var i = 0;
(function step(){
if(i >= jobs.length){ done('/checkout/'); return; }
var j = jobs[i++];
addNative(j[0], j[1], step);
})();
}
function fillCart(done){
var api = (window.CGP_PAGE && CGP_PAGE.cart) || '';
if(!api){ fillCartFallback(function(u){ done(u); }); return; }
var body = {
edition: cart.edition,
qty: cart.qty,
stickers: cart.stickers,
shirt: cart.shirt ? { style: cart.shirt.style, size: cart.shirt.size } : null
};
var xhr = new XMLHttpRequest();
xhr.open('POST', api, true);
xhr.setRequestHeader('Content-Type', 'application/json');
if(window.CGP_PAGE && CGP_PAGE.nonce) xhr.setRequestHeader('X-WP-Nonce', CGP_PAGE.nonce);
xhr.onload = function(){
try{
var r = JSON.parse(xhr.responseText);
if(xhr.status >= 200 && xhr.status < 300 && r.checkout){ done(r.checkout); return; }
done(null, r.message || null);
}catch(e){ done(null, null); }
};
xhr.onerror = function(){ done(null, null); };
xhr.send(JSON.stringify(body));
}
function refreshCta(){
var b = fnBody.querySelector('.fn-actions .btn');
if(!b) return;
if(b.textContent.indexOf('Pay ') === 0) b.textContent = 'Pay ' + money(total());
}
/* ---------- step 6: delivery, and the gift levers ---------- */
function drawDelivery(){
var s = cart.ship;
fnBody.innerHTML =
'
Where is it going?
' +
'
Posted from the author, not a warehouse.
' +
'' +
'' +
'' +
'
' +
'' +
'' +
'' +
'
' +
'' +
'
Because it is a gift
' +
'';
[['sName','name'],['sA1','a1'],['sA2','a2'],['sCity','city'],['sReg','region'],['sPost','post'],['sCountry','country']]
.forEach(function(pair){
var el = fnBody.querySelector('#' + pair[0]);
el.addEventListener('input', function(){ cart.ship[pair[1]] = el.value; el.style.borderColor = ''; });
});
var g = fnBody.querySelector('#giftOpts');
function sw(id, on, title, desc, price, cb){
var l = document.createElement('label');
l.className = 'switch' + (on ? ' on' : '');
l.innerHTML = '' +
'' + title + '' + desc + '' +
'' + price + '';
l.querySelector('input').addEventListener('change', function(){
l.classList.toggle('on', this.checked);
cb(this.checked);
fnTot.textContent = money(total());
});
g.appendChild(l);
return l;
}
sw('gDirect', cart.direct, 'Send it straight to them',
'We post it to the recipient and leave the price off the slip.', 'Free',
function(v){ cart.direct = v; });
sw('gWrap', cart.wrap, 'Wrap it',
'Black paper, done by hand before it goes in the box.', '+' + money(PRICE.giftWrap),
function(v){ cart.wrap = v; });
fnBody.appendChild(actions(
btn('Continue to payment', 'btn-primary btn-block', function(){
var need = [['sName','name'],['sA1','a1'],['sCity','city'],['sPost','post']];
for(var i=0;i' +
'' +
'';
var rc = fnBody.querySelector('#recap');
var html = '';
lines().forEach(function(l){ html += '
All thirty-four pages, free, yours to keep. We will send it now and leave you alone.
' +
'
' +
'
' +
'';
var xe = fnBody.querySelector('#xEmail');
var note = fnBody.querySelector('#xNote');
fnBody.appendChild(actions(
btn('Send me the ebook', 'btn-lemon btn-block', function(){
if(!xe.value.trim() || xe.value.indexOf('@') === -1){
xe.focus(); xe.style.borderColor = 'var(--punch)'; return;
}
cart.email = xe.value.trim();
note.innerHTML = '
Sent. Check your inbox.
';
setTimeout(closeFunnel, 1100);
}),
quiet('Back to my order', function(){ pending = null; draw(); })
));
var d = document.createElement('div');
d.className = 'fn-actions';
d.style.marginTop = '0';
d.appendChild(quiet('No thanks, close', closeFunnel));
fnBody.appendChild(d);
}
/* =========================================================
3. CONTACT
========================================================= */
var ct = document.getElementById('contact');
var ctBody = document.getElementById('ctBody');
var ctFocus = null;
var CT_FORM = 2224; // Contact Form 7: "Contact form 1"
var TOPICS = ['An order I have placed',
'Personalising or signing','Wholesale and stockists','Something else'];
function ctForm(){
ctBody.innerHTML =
'
Say something
' +
'
It goes to David. He answers them himself, usually the same day.
' +
'' +
'
' +
'' +
'' +
'
' +
'
' +
'
';
var name = ctBody.querySelector('#ctName');
var mail = ctBody.querySelector('#ctEmail');
var msg = ctBody.querySelector('#ctMsg');
[name,mail,msg].forEach(function(f){
f.addEventListener('input', function(){ f.style.borderColor = ''; });
});
var send = document.createElement('button');
send.type = 'button';
send.className = 'btn btn-primary btn-block';
send.textContent = 'Send it';
var topic = ctBody.querySelector('#ctTopic');
// If the site cannot send the mail, never leave the visitor at a dead
// end: hand them the address so the message still reaches David.
function ctFallback(){
var w = ctBody.querySelector('.ct-warn');
if(!w){
w = document.createElement('p');
w.className = 'ct-warn';
w.style.cssText = 'color:var(--dim);font-size:14.5px;margin:14px 0 0;text-align:center;line-height:1.5';
ctBody.appendChild(w);
}
w.innerHTML = 'That did not go through. Email David directly at ' +
'charliesgnarlyparty@gmail.com';
}
send.addEventListener('click', function(){
if(!name.value.trim()){ name.focus(); name.style.borderColor = 'var(--punch)'; return; }
if(!mail.value.trim() || mail.value.indexOf('@') === -1){
mail.focus(); mail.style.borderColor = 'var(--punch)'; return;
}
if(!msg.value.trim()){ msg.focus(); msg.style.borderColor = 'var(--punch)'; return; }
send.disabled = true;
send.textContent = 'Sending';
// Posts to the site's existing Contact Form 7 form, so the message
// routes through whatever mail setup the store already uses rather
// than anything new.
var fd = new FormData();
fd.append('_wpcf7', CT_FORM);
fd.append('_wpcf7_version', '6.1.7');
fd.append('_wpcf7_locale', 'en_US');
fd.append('_wpcf7_unit_tag', 'wpcf7-f' + CT_FORM + '-o1');
fd.append('_wpcf7_container_post', '0');
fd.append('your-name', name.value.trim());
fd.append('your-email', mail.value.trim());
fd.append('your-subject', topic ? topic.value : 'Website enquiry');
fd.append('your-message', msg.value.trim());
fetch('/wp-json/contact-form-7/v1/contact-forms/' + CT_FORM + '/feedback', {
method: 'POST', body: fd, credentials: 'same-origin'
})
.then(function(r){ return r.json(); })
.then(function(r){
var st = r && r.status;
// Every submission is stored in the site admin, so the message has
// reached David whether or not the notification email went out.
// Only a validation problem or an unreachable server is the
// visitor's business.
if(st === 'mail_sent' || st === 'mail_failed'){ ctSent(name.value.trim()); return; }
send.disabled = false; send.textContent = 'Send it';
if(st === 'validation_failed'){
var w = ctBody.querySelector('.ct-warn');
if(!w){
w = document.createElement('p');
w.className = 'ct-warn';
w.style.cssText = 'color:var(--punch);font-size:14px;margin:12px 0 0;text-align:center';
ctBody.appendChild(w);
}
w.textContent = r.message || 'Please check the fields above.';
return;
}
ctFallback();
})
.catch(function(){
send.disabled = false; send.textContent = 'Send it';
ctFallback();
});
});
var wrap = document.createElement('div');
wrap.className = 'fn-actions';
wrap.appendChild(send);
ctBody.appendChild(wrap);
}
function ctSent(who){
ctBody.innerHTML =
'
' +
'' +
'
Sent
' +
'
Thanks ' + who +
'. David reads these himself, so give him a day or two.
' +
'
';
var wrap = document.createElement('div');
wrap.className = 'fn-actions';
var b = document.createElement('button');
b.type = 'button'; b.className = 'btn btn-primary btn-block'; b.textContent = 'Close';
b.addEventListener('click', closeContact);
wrap.appendChild(b);
ctBody.appendChild(wrap);
}
function openContact(){
ctFocus = document.activeElement;
ctForm();
ct.classList.add('open');
document.body.style.overflow = 'hidden';
ct.scrollTop = 0;
}
function closeContact(){
ct.classList.remove('open');
document.body.style.overflow = '';
if(ctFocus && ctFocus.focus) ctFocus.focus();
}
document.addEventListener('click', function(e){
if(e.target.closest('.js-contact')){ e.preventDefault(); openContact(); }
});
document.getElementById('ctClose').addEventListener('click', closeContact);
ct.addEventListener('click', function(e){ if(e.target === ct) closeContact(); });
document.addEventListener('keydown', function(e){
if(e.key === 'Escape' && ct.classList.contains('open')) closeContact();
});
/* ---- chrome ---- */
var topbar = document.getElementById('topbar');
var mbar = document.getElementById('mobilebar');
window.addEventListener('scroll', function(){
var y = window.scrollY;
topbar.classList.toggle('solid', y > 20);
mbar.classList.toggle('on', y > 700);
}, {passive:true});
})();