Export the current FetLife event to a calendar file
// ==UserScript==
// @name FL Tools Calendar
// @namespace https://gitlab.com/fl-tools-lite/fl-tools-calendar
// @version 0.1.0
// @description Export the current FetLife event to a calendar file
// @author TypicalBits
// @icon data:image/png;base64,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
// @icon64 data:image/png;base64,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
// @license PolyForm-Noncommercial-1.0.0
// @match https://fetlife.com/*
// @run-at document-end
// @noframes
// @grant GM.getValue
// @grant GM.setValue
// @homepageURL https://gitlab.com/fl-tools-lite/fl-tools-calendar
// @supportURL https://gitlab.com/fl-tools-lite/fl-tools-calendar/-/issues
// ==/UserScript==
// Copyright 2026 TypicalBits.
(function(){
'use strict';
// dock.js
// Copyright 2026 TypicalBits. Shared FL Tools Lite coordination.
const LITE_ORDER = ['view','read','calendar','notes','return'];
const LITE_CORNERS = ['bottom-left','bottom-right','top-left','top-right'];
const LITE_COLORS = {view:'#62A8DE',read:'#B9A0F5',calendar:'#65D8EB',notes:'#F39BC8',return:'#CCD4E0'};
const LITE_REPAIR = 'flt-lite:repair-v1';
function claimProduct(document,id) {
if (!LITE_ORDER.includes(id) || document.querySelector(`[data-flt-lite-claim="${id}"]`)) return null;
const claim=document.createElement('span');claim.hidden=true;claim.dataset.fltLiteClaim=id;
document.documentElement.append(claim);
return {ensure(){if(!claim.isConnected)document.documentElement.append(claim);},release(){claim.remove();}};
}
function setLiteCorner(document,host,corner) {
host.dataset.corner=corner; document.documentElement.dataset.fltLiteCorner=corner;
for(const panel of document.querySelectorAll('[data-flt-lite-panel]'))panel.dataset.fltLiteCorner=corner;
}
function ownLiteDock(document,host,storage,restoreSaved=true) {
const w=document.defaultView; const shadow=host.attachShadow({mode:'open'});
const style=document.createElement('style');style.textContent=`
:host{position:fixed;bottom:16px;left:16px;z-index:2147483001;color-scheme:dark;font:12px/1.4 system-ui,sans-serif}
:host([data-corner="bottom-right"]){left:auto;right:16px}
:host([data-corner="top-left"]){bottom:auto;top:16px}
:host([data-corner="top-right"]){left:auto;right:16px;bottom:auto;top:16px}
:host([data-fallback="true"]){transform:translateY(-58px)}:host([data-fallback="true"][data-corner^="top"]){transform:translateY(58px)}
.dock{display:flex;flex-wrap:wrap;gap:5px;max-width:calc(100vw - 32px)}
button{display:flex;align-items:center;justify-content:center;padding:3px;width:46px;height:46px;border:1px solid var(--accent);border-radius:8px;background:#17171a;cursor:pointer;color:var(--accent);font:inherit}
button:focus-visible{outline:2px solid var(--accent);outline-offset:2px}button[aria-expanded="true"]{box-shadow:0 0 0 1px var(--accent)}img{width:38px;height:38px;display:block}
`;const bar=document.createElement('nav');bar.className='dock';bar.setAttribute('aria-label','FL Tools Lite');shadow.append(style,bar);
let cornerRevision=0;
let current=null;const buttons=new Map(); let disposed=false;let writes=Promise.resolve();
function ports(){return [...host.children].filter(x=>x.hasAttribute('data-flt-lite-port'));}
function close(port,focus=false){port?.dispatchEvent(new w.Event('flt-lite:close'));buttons.get(port)?.setAttribute('aria-expanded','false');if(current===port)current=null;if(focus)buttons.get(port)?.focus();}
function open(port){if(current===port){close(port,true);return;}if(current)close(current);current=port;buttons.get(port)?.setAttribute('aria-expanded','true');port.dispatchEvent(new w.Event('flt-lite:open'));}
function render(){
const active=ports().sort((a,b)=>LITE_ORDER.indexOf(a.dataset.product)-LITE_ORDER.indexOf(b.dataset.product));
for(const [port,button]of buttons)if(!active.includes(port)){if(current===port)close(port);button.remove();buttons.delete(port);}
for(const port of active){
let button=buttons.get(port);
if(!button){port.dispatchEvent(new w.Event('flt-lite:close'));button=document.createElement('button');button.type='button';button.dataset.product=port.dataset.product;button.setAttribute('aria-label','Open FL Tools '+port.dataset.label);button.setAttribute('aria-expanded','false');button.style.setProperty('--accent',LITE_COLORS[port.dataset.product]);
if(port.dataset.icon){const image=document.createElement('img');image.src=port.dataset.icon;image.alt='';button.append(image);}else button.textContent=port.dataset.label;
button.addEventListener('click',()=>open(port));buttons.set(port,button);
}
if(bar.children[active.indexOf(port)]!==button)bar.append(button);
}
setLiteCorner(document,host,host.dataset.corner||'bottom-left');
}
const route=event=>{const port=event.target;if(port.parentNode!==host||!port.hasAttribute('data-flt-lite-port'))return;
if(event.type==='flt-lite:register')render();
if(event.type==='flt-lite:toggle')open(port);
if(event.type==='flt-lite:hide')close(port,true);
if(event.type==='flt-lite:focus')buttons.get(port)?.focus();
if(event.type==='flt-lite:corner'&&LITE_CORNERS.includes(port.dataset.corner)){
cornerRevision++;const corner=port.dataset.corner;setLiteCorner(document,host,corner);
if(storage)writes=writes.then(()=>storage.setValue('flt-lite:dock:v1',corner)).catch(()=>{});
}
};
for(const name of ['register','toggle','hide','focus','corner'])host.addEventListener('flt-lite:'+name,route);
const escape=event=>{if(current&&event.key==='Escape'){close(current,true);event.stopPropagation();}};
document.addEventListener('keydown',escape);
const observer=new w.MutationObserver(()=>{if(!disposed)render();});observer.observe(host,{childList:true});render();
if(storage&&restoreSaved)Promise.resolve().then(()=>storage.getValue('flt-lite:dock:v1','bottom-left')).then(corner=>{if(!disposed&&cornerRevision===0&&host.isConnected&&LITE_CORNERS.includes(corner))setLiteCorner(document,host,corner);}).catch(()=>{});
return {destroy(){disposed=true;observer.disconnect();document.removeEventListener('keydown',escape);for(const name of ['register','toggle','hide','focus','corner'])host.removeEventListener('flt-lite:'+name,route);},render};
}
function registerDock(document,{id,label,icon,onOpen=()=>{},onClose=()=>{},storage}) {
const w=document.defaultView;const port=document.createElement('span');port.hidden=true;port.dataset.fltLitePort='';port.dataset.product=id;port.dataset.label=label;port.dataset.icon=icon;
port.addEventListener('flt-lite:open',onOpen);port.addEventListener('flt-lite:close',onClose);
let owner=null,ownedHost=null,destroyed=false,scheduled=false;
function ensure(){
if(destroyed||!document.body)return;
let host=document.querySelector('[data-flt-lite-dock="1"]');
if(!host){const restoreSaved=!document.documentElement.dataset.fltLiteCorner;owner?.destroy();host=document.createElement('div');host.dataset.fltLiteDock='1';host.dataset.corner=document.documentElement.dataset.fltLiteCorner||ownedHost?.dataset.corner||'bottom-left';host.dataset.fallback=String(!!document.querySelector('[data-flt-lite-dock]:not([data-flt-lite-dock="1"])'));document.body.append(host);ownedHost=host;owner=ownLiteDock(document,host,storage,restoreSaved);}
if(port.parentNode!==host){host.append(port);port.dispatchEvent(new w.Event('flt-lite:register',{bubbles:true}));}
owner?.render();
}
const schedule=()=>{if(scheduled||destroyed)return;scheduled=true;w.queueMicrotask(()=>{scheduled=false;ensure();});};
const observer=new w.MutationObserver(records=>{if(!port.isConnected||!document.querySelector('[data-flt-lite-dock="1"]'))schedule();});observer.observe(document.documentElement,{childList:true,subtree:true});
document.addEventListener(LITE_REPAIR,schedule);ensure();
const send=name=>{ensure();port.dispatchEvent(new w.Event('flt-lite:'+name,{bubbles:true}));};
return {
ensure,open(){send('toggle');},close(){send('hide');},focus(){send('focus');},
setCorner(corner){if(!LITE_CORNERS.includes(corner))return;port.dataset.corner=corner;send('corner');},
get corner(){return port.parentNode?.dataset.corner||'bottom-left';},
destroy(){if(destroyed)return;destroyed=true;observer.disconnect();document.removeEventListener(LITE_REPAIR,schedule);onClose();const host=port.parentNode;port.remove();
if(owner&&host===ownedHost){const survivors=[...host.children];owner.destroy();for(const survivor of survivors)document.body.append(survivor);host.remove();document.dispatchEvent(new w.Event(LITE_REPAIR));}
else if(host&&!host.querySelector('[data-flt-lite-port]'))host.remove();
port.removeEventListener('flt-lite:open',onOpen);port.removeEventListener('flt-lite:close',onClose);
}
};
}
// storage.js
// Copyright 2026 TypicalBits.
function createRecordStore(storage,key,normalize,locks=globalThis.navigator?.locks) {
let value={},loaded=false,error='',queue=Promise.resolve();
return {
get value(){return structuredClone(value);},get error(){return error;},
async load(){try{value=normalize(await storage.getValue(key,{}));loaded=true;error='';}catch{error='Saved data could not be read. Reload before saving.';}return structuredClone(value);},
transact(change){const job=queue.then(async()=>{
if(!loaded)throw Error(error||'Saved data has not loaded.');
if(!locks?.request){error='This browser cannot safely save across tabs. Use a browser with Web Locks support.';throw Error(error);}
const controller=new AbortController();const timeout=setTimeout(()=>controller.abort(),10000);
try{return await locks.request('flt-lite:storage:'+key,{mode:'exclusive',signal:controller.signal},async()=>{
let latest;try{latest=normalize(await storage.getValue(key,{}));}catch{error='Saved data could not be read. Nothing was overwritten.';throw Error(error);}
const next=normalize(change(structuredClone(latest)));
try{await storage.setValue(key,next);value=next;error='';return structuredClone(value);}catch{error='Could not save. Your previous saved data is unchanged.';throw Error(error);}
});}catch(problem){if(controller.signal.aborted){error='Another tab is busy saving. Please try again.';throw Error(error);}throw problem;}finally{clearTimeout(timeout);}
});queue=job.catch(()=>{});return job;}
};
}
function pageKey(input) {
const url=new URL(input);
if(url.protocol!=='https:'||url.hostname!=='fetlife.com'||url.username||url.password||url.port)throw Error('This is not a FetLife page.');
url.hash='';return url.href;
}
// panel.js
// Copyright 2026 TypicalBits.
function liteElement(document,tag,attributes={},text='') {
const element=document.createElement(tag);for(const [key,value]of Object.entries(attributes))element.setAttribute(key,String(value));if(text)element.textContent=text;return element;
}
function createLitePanel(document,{id,name,accent,icon,version,storage,onOpen=()=>{}}) {
const e=(tag,attrs,text)=>liteElement(document,tag,attrs,text);
const host=e('div',{'data-flt-lite-panel':id,'data-flt-lite-corner':'bottom-left'});const shadow=host.attachShadow({mode:'open'});
const style=e('style');style.textContent=`
:host{all:initial;position:fixed;left:16px;bottom:16px;z-index:2147483002;font:12px/1.5 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#eeeef4;color-scheme:dark;--accent:${accent}}
:host([data-flt-lite-corner$="right"]){left:auto;right:16px}:host([data-flt-lite-corner^="top"]){bottom:auto;top:16px}
*,*::before,*::after{box-sizing:border-box}[hidden]{display:none!important}button,input,textarea,select{font:inherit;color:inherit}button{cursor:pointer}button:disabled{opacity:.5;cursor:default}
button,input,textarea,select{background:#222228;border:1px solid #4b4b59;border-radius:6px;padding:6px 8px;min-width:0}button{min-height:32px}button:focus-visible,input:focus-visible,textarea:focus-visible,summary:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.panel{position:absolute;bottom:54px;left:0;width:min(320px,calc(100vw - 32px));max-height:calc(100dvh - 90px);overflow:auto;padding:12px;background:#131318;border:1px solid var(--accent);border-radius:10px;box-shadow:0 8px 26px #0008}
:host([data-flt-lite-corner$="right"]) .panel{left:auto;right:0}:host([data-flt-lite-corner^="top"]) .panel{bottom:auto;top:54px}
.heading{display:flex;align-items:center;gap:8px;margin-bottom:12px}.heading strong{flex:1;font-size:14px}.heading img{width:30px;height:30px}.version{font-size:10px;color:#b5b5c4}.close{width:30px;min-height:30px;padding:0}
p{margin:0 0 10px}fieldset{border:0;padding:0;margin:0 0 12px;min-width:0}legend,label{font-weight:600;padding:0;margin-bottom:6px;display:block}input,textarea,select{display:block;width:100%;margin-bottom:10px}textarea{min-height:160px;resize:vertical}
.choices,.actions{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:10px}.choices button{flex:1}.choices button[aria-pressed=true],.primary{background:var(--accent);border-color:var(--accent);color:#101015;font-weight:600}
.row{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:12px}.row label{margin:0;min-width:0}.switch{position:relative;flex:none;width:34px;min-height:20px;height:20px;padding:0;border-radius:5px}.switch::after{content:"";position:absolute;top:3px;left:3px;width:12px;height:12px;background:#b5b5c4;border-radius:3px}.switch[aria-checked=true]{background:var(--accent)}.switch[aria-checked=true]::after{left:17px;background:#131318}
.note,.status,.page{font-size:11px;color:#b5b5c4;overflow-wrap:anywhere}.status{border-top:1px solid #3d3d46;padding-top:8px;margin:10px 0 0}.status:empty{display:none}details{border-top:1px solid #3d3d46;margin-top:10px;padding-top:8px}summary{cursor:pointer;color:var(--accent);font-weight:600;margin-bottom:8px}.record{border-top:1px solid #3d3d46;padding:8px 0;overflow-wrap:anywhere}.record strong{display:block;margin-bottom:4px}.confirmation{border:1px solid var(--accent);padding:8px;border-radius:6px;margin-bottom:10px}
`;
const panel=e('section',{class:'panel','aria-label':'FL Tools '+name});panel.hidden=true;
const heading=e('div',{class:'heading'});if(icon)heading.append(e('img',{src:icon,alt:''}));heading.append(e('strong',{},'FL Tools '+name),e('span',{class:'version'},version));const close=e('button',{class:'close',type:'button','aria-label':'Close '+name},'×');heading.append(close);
const body=e('div',{'data-content':''}),settings=e('details');settings.append(e('summary',{},'Settings'));const corners=e('div',{class:'choices','aria-label':'Dock position'});
let dock;for(const corner of ['bottom-left','bottom-right','top-left','top-right']){const button=e('button',{type:'button','aria-label':corner.replace('-',' '),'aria-pressed':'false'},corner.startsWith('top')?'↑ '+(corner.endsWith('left')?'Left':'Right'):'↓ '+(corner.endsWith('left')?'Left':'Right'));button.addEventListener('click',()=>{dock.setCorner(corner);for(const b of corners.children)b.setAttribute('aria-pressed',String(b===button));});corners.append(button);}settings.append(e('p',{class:'note'},'Dock position'),corners);
const status=e('p',{class:'status',role:'status','data-status':''});panel.append(heading,body,settings,status);shadow.append(style,panel);document.body.append(host);
dock=registerDock(document,{id,label:name,icon,storage,onOpen:()=>{panel.hidden=false;onOpen();close.focus();},onClose:()=>{panel.hidden=true;}});close.addEventListener('click',()=>dock.close());
return {host,shadow,panel,body,settings,dock,e,setStatus(text){if(status.textContent!==text)status.textContent=text;},ensure(){if(!host.isConnected)document.body.append(host);dock.ensure();},destroy(){dock.destroy();host.remove();}};
}
function watchLitePage(document,callback) {
const window=document.defaultView;let queued=false,destroyed=false;
const owned=node=>{const element=node.nodeType===1?node:node.parentElement;const host=element?.getRootNode()?.host;return !!element?.closest('[data-flt-lite-panel],[data-flt-lite-dock],[data-flt-lite-port],[data-flt-lite-claim],[data-flt-lite-style],[data-flt-view-root],[data-flt-view-style]')||(typeof host?.hasAttribute==='function'&&host.hasAttribute('data-flt-lite-panel'));};
const schedule=()=>{if(queued||destroyed)return;queued=true;window.queueMicrotask(()=>{queued=false;if(!destroyed)callback();});};
const observer=new window.MutationObserver(records=>{if(records.some(record=>!owned(record.target)&&[...record.addedNodes,...record.removedNodes].some(node=>!owned(node))))schedule();});observer.observe(document.documentElement,{childList:true,subtree:true});
const events=['popstate','pageshow','turbo:load','turbo:render','turbolinks:load'];for(const event of events)window.addEventListener(event,schedule);
return ()=>{destroyed=true;observer.disconnect();for(const event of events)window.removeEventListener(event,schedule);};
}
// bootstrap.js
// Copyright 2026 TypicalBits.
async function bootstrapLite({document,window,storage,product,setup}) {
const claim=claimProduct(document,product.id);if(!claim)return {duplicate:true,destroy(){}};
let feature,stop=()=>{},ui;
try {
const refresh=()=>{claim.ensure();ui.ensure();feature?.refresh?.();};
ui=createLitePanel(document,{...product,storage,onOpen:refresh});ui.setStatus('Loading saved settings…');
feature=await setup({ui,document,window,storage});stop=watchLitePage(document,refresh);refresh();
return {refresh,destroy(){stop();feature?.destroy?.();ui.destroy();claim.release();}};
}catch(error){stop();feature?.destroy?.();ui?.destroy();claim.release();throw error;}
}
function liteButton(ui,label,attributes={},handler=()=>{}) {
const button=ui.e('button',{type:'button',...attributes},label);button.addEventListener('click',handler);return button;
}
function liteField(ui,name,label,value='') {
const id='flt-'+ui.host.dataset.fltLitePanel+'-'+name;const node=ui.e('input',{id,type:'text','data-field':name});node.value=value;
ui.body.append(ui.e('label',{for:id},label),node);return node;
}
// icon.js
const PRODUCT_ICON = "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%20role%3D%22img%22%20aria-labelledby%3D%22title%20desc%22%3E%0A%20%20%3Ctitle%20id%3D%22title%22%3EFL%20Tools%20Calendar%20badge%3C%2Ftitle%3E%0A%20%20%3Cdesc%20id%3D%22desc%22%3EFL%20Tools%20Calendar%20badge.%20Artwork%20is%20border-free%3B%20CSS%20supplies%20the%20launcher%20and%20menu%20frames.%3C%2Fdesc%3E%0A%20%20%3Cdefs%3E%0A%20%20%20%20%3CradialGradient%20id%3D%22surface%22%20cx%3D%2238%25%22%20cy%3D%2228%25%22%20r%3D%2285%25%22%3E%0A%20%20%20%20%20%20%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2325242a%22%2F%3E%0A%20%20%20%20%20%20%3Cstop%20offset%3D%22.58%22%20stop-color%3D%22%2317171a%22%2F%3E%0A%20%20%20%20%20%20%3Cstop%20offset%3D%221%22%20stop-color%3D%22%230e0e10%22%2F%3E%0A%20%20%20%20%3C%2FradialGradient%3E%0A%20%20%20%20%3Cfilter%20id%3D%22labelGlow%22%20x%3D%22-40%25%22%20y%3D%22-60%25%22%20width%3D%22180%25%22%20height%3D%22220%25%22%3E%0A%20%20%20%20%20%20%3CfeGaussianBlur%20in%3D%22SourceGraphic%22%20stdDeviation%3D%22.85%22%20result%3D%22blur%22%2F%3E%0A%20%20%20%20%20%20%3CfeFlood%20flood-color%3D%22%2365D8EB%22%20flood-opacity%3D%22.42%22%20result%3D%22glowColor%22%2F%3E%0A%20%20%20%20%20%20%3CfeComposite%20in%3D%22glowColor%22%20in2%3D%22blur%22%20operator%3D%22in%22%20result%3D%22softGlow%22%2F%3E%0A%20%20%20%20%20%20%3CfeMerge%3E%0A%20%20%20%20%20%20%20%20%3CfeMergeNode%20in%3D%22softGlow%22%2F%3E%0A%20%20%20%20%20%20%20%20%3CfeMergeNode%20in%3D%22SourceGraphic%22%2F%3E%0A%20%20%20%20%20%20%3C%2FfeMerge%3E%0A%20%20%20%20%3C%2Ffilter%3E%0A%20%20%3C%2Fdefs%3E%0A%20%20%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22url%28%23surface%29%22%2F%3E%0A%20%20%3Cimage%20href%3D%22data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAE90lEQVR4AcyZW4hWVRTH%2FzM5NamVGXQhrKjHLCqsJgeMLhBGQaCkZEqXyWns60JBRUJkD0UQZMVoWSFJt4dIKOqpHoIQqgepXkpKRcuKKCu0LK%2B%2F5ezvzNnn7HPxm%2FMdz8daZ%2B%2B199prrf86%2B%2By9z%2Fl6VfB7SZpfoNKV7tXSqa9IVxcZzwWAkYsx8BBcOxHYrgPS2lFpap5z9MLdB6WeHmmVpD64djpewr3OmiQ9rpxfb1Yft%2B9GLAzAJ2fpdLP9P6nt915mwplZvoIALPvwCjfoNFfWWuyXTncO%2B4%2BRHnH1VBEE8Kp0FZoXwUZTmEftbJhcF0dZJ5l3viCdGHIcBMDDsySuTAYujct11Jm6cZ%2BT%2BzNWwxSAl6XJBDgPjtPlcaGm%2BkDCz%2BKEfFhMAQD5bHqmwhHRdkMk1FAhiSfhxuKgGCOm0SDtltyxBndNAUAxlW3aLmPwBW5M1wsStggnrKRcx6mP6iWwRykA9KYA0GbUsku3%2BQlpEgkbCfkBWHJaqTegeE6gzZqGOFbMsko3%2BQzpbuzPhFMEsLOTjSEAJySVnNxLBlaztc8wZlk7zrVPuFgn2VJ9LpvnhRh7Es4ieza8vhCA4HrrRs1ia99mTPR7eC5%2BhNetkebZrXc6hcVaaRpjlsEfMv6vf6VdLNU%2FsHx%2FxeDMPYcEpmILATgWI2XJNpvF3Np3ufWbCaaVB4QNcjrTcOX%2F0nbGjMLX4ygVFG1Z1JfsCAH4I6lUUp6B3osA2eimAuI4EfgCjgebyOL9tHrLNHIpAnAqthCAbaWsZSvNZCp8zvSwQMV06efOPE%2Fg7zDkFLhjwsbW5OAUAJQ2J5U6kPvJ1koCX7VX%2Bpjx98ETJhKTis0DYIc2HFe5645gb3DCkTsDJPc2V40KDwArgW0g06Pe5lXmcFe9jdYDQLwL4abTLfEAIwA8bNPoOB%2FOoMY0e4e8CAAPmx0hIrkx4aYDOS%2FeFAXMGj0l3tHgureHRAB4gP9scNDx0HbGhQgAe%2FRPdLDUcm0wsSxvj4cXAbhdsjvwrRr%2BI%2BAN8RCR46Le96QGCkyRD%2BJheQDY6VhNhU5cpVH1Lb9In8Qj8gAslTbR%2BSbcVFrBcd1LsAfAot4nLaf8G24UMTu%2B4E3njWRQKQD38LKBUiWnR%2BxURbtJ7JKbJbYr32QKgHUPS69T8k2V69Gng2R%2FaJn0XSiUIACnaC8kR31VIviHeTbtZciF5ReZALgLHI80n41jvT%2BkVmk5wT%2Bb5zETgA0yEGRgAfX34FoJv4%2Fh%2F6kip7kAbDBG7E4sxOBrJtfAe%2FG1lMw%2FXcZXIQAzYiAwOIThB5C9dRi5StrJlJ2LLz5slDNbCkDbFIbt64L9a7m73VZh%2BT1r5BV8V%2FR22iL7RwTAjAFiPYPsrWiLyRXxp5yGZ2ctlXk%2BiCWvO9x3l%2FQ1D4b9g2KfTMJK5VvX%2FCxde4f0W%2Fkh45odAbDhLel3HM%2Bl%2FhzcCfFJVIt4voY537DRdmJC6hiAuTPHw9KD1O0PiT2UY1R83dEjXcnYt4pV8zUmBKBt2gIhoGuQf4WLaCPpHuBZ%2BrJIsUx%2FJQDMEQFtYH21f1C%2BMTnEgHyb5WvQHRhDKkfcVhkA8zwibeXb%2FBzqn8FJGt0h3cp8s7mf7OtYrhSAReHera8j2x%2BZbEz9GaZZi2eGm2Qt1XHlACw0gv2HHfUm6vYC0mJ6PUq9K3QIAAD%2F%2F1wKF9MAAAAGSURBVAMAjeXXP35naSYAAAAASUVORK5CYII%3D%22%20x%3D%2216%22%20y%3D%226%22%20width%3D%2232%22%20height%3D%2232%22%20preserveAspectRatio%3D%22xMidYMid%20meet%22%2F%3E%0A%20%20%3Ctext%20x%3D%2232%22%20y%3D%2254%22%20fill%3D%22%2365D8EB%22%20font-family%3D%22Arial%2C%20sans-serif%22%20font-size%3D%228.2%22%20font-weight%3D%22800%22%20letter-spacing%3D%22.1%22%20text-anchor%3D%22middle%22%20filter%3D%22url%28%23labelGlow%29%22%3ECALENDAR%3C%2Ftext%3E%0A%3C%2Fsvg%3E%0A";
// calendar.js
// Copyright 2026 TypicalBits.
function dateParts(input) {
const match=/^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2})(?::(\d{2}))?)?(Z|[+-]\d{2}:\d{2})?$/.exec(input);
if(!match||(+match[1]<1000)||(!match[4]&&match[7]))throw Error('Use YYYY-MM-DD, or YYYY-MM-DDTHH:mm with a timezone.');
const values=match.slice(1,7).map(x=>+(x||0));const [year,month,day,hour,minute,second]=values;
const wall=Date.UTC(year,month-1,day,hour,minute,second);const parsed=new Date(wall);
if(parsed.getUTCFullYear()!==year||parsed.getUTCMonth()!==month-1||parsed.getUTCDate()!==day||hour>23||minute>59||second>59)throw Error('The event date or time is invalid.');
return {wall,values,allDay:!match[4],offset:match[7]||''};
}
function zonedParts(time,timezone) {
const formatter=new Intl.DateTimeFormat('en-US',{timeZone:timezone,year:'numeric',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit',second:'2-digit',hourCycle:'h23'});
const fields=Object.fromEntries(formatter.formatToParts(time).map(x=>[x.type,x.value]));return ['year','month','day','hour','minute','second'].map(x=>+fields[x]);
}
function calendarInstant(input,timezone) {
const data=dateParts(input);if(data.allDay)return {allDay:true,time:data.wall};
if(data.offset){const offset=data.offset==='Z'?0:(()=>{const hours=+data.offset.slice(1,3),minutes=+data.offset.slice(4);if(hours>23||minutes>59)throw Error('Invalid UTC offset.');return (hours*60+minutes)*(data.offset[0]==='-'?-1:1);})();return {allDay:false,time:data.wall-offset*60000};}
if(!timezone?.trim())throw Error('Supply a timezone or include an explicit UTC offset.');
const offsets=new Set();try{
for(let h=-36;h<=36;h+=6){const sample=data.wall+h*3600000;const p=zonedParts(sample,timezone);offsets.add(Date.UTC(p[0],p[1]-1,p[2],p[3],p[4],p[5])-sample);}
}catch{throw Error('Use a valid timezone such as America/Los_Angeles, or an explicit UTC offset.');}
const candidates=[...offsets].map(offset=>data.wall-offset).filter(time=>zonedParts(time,timezone).every((v,i)=>v===data.values[i]));
if(candidates.length!==1)throw Error('This local time is missing or ambiguous at a clock change. Include an explicit UTC offset.');
return {allDay:false,time:candidates[0]};
}
function calendarText(input) {return String(input).replaceAll('\\','\\\\').replace(/\r\n|\r|\n/g,'\\n').replaceAll(';','\\;').replaceAll(',','\\,').replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/g,'');}
function foldCalendar(line) {
const encoder=new TextEncoder();let output='',part='',size=0;
for(const char of line){const n=encoder.encode(char).length;if(size+n>75){output+=part+'\r\n';part=' ';size=1;}part+=char;size+=n;}return output+part;
}
function calendarTime(time,allDay=false){const iso=new Date(time).toISOString();return allDay?iso.slice(0,10).replaceAll('-',''):iso.replace(/[-:]/g,'').slice(0,15)+'Z';}
function eventUID(url){let a=2166136261,b=5381;for(const char of url){a=Math.imul(a^char.charCodeAt(0),16777619);b=Math.imul(b,33)^char.charCodeAt(0);}return (a>>>0).toString(16)+(b>>>0).toString(16)+'@fl-tools-lite';}
function buildCalendar(fields,now=new Date()) {
const title=String(fields.title||'').trim();if(!title)throw Error('Enter the event title.');if(title.length>1000||String(fields.location||'').length>2000)throw Error('The title or location is too long.');
const url=pageKey(fields.url),start=calendarInstant(String(fields.start||'').trim(),fields.timezone),end=fields.end?calendarInstant(String(fields.end).trim(),fields.timezone):null;
if(end&&(end.allDay!==start.allDay||end.time<=start.time))throw Error('End must be after start and use the same date format. For all-day events, end is the first day after the event.');
if(!Number.isFinite(now.getTime()))throw Error('Invalid export date.');
const lines=['BEGIN:VCALENDAR','VERSION:2.0','PRODID:-//TypicalBits//FL Tools Calendar//EN','CALSCALE:GREGORIAN','BEGIN:VEVENT','UID:'+eventUID(url),'DTSTAMP:'+calendarTime(now.getTime()),`DTSTART${start.allDay?';VALUE=DATE':''}:`+calendarTime(start.time,start.allDay)];
if(end)lines.push(`DTEND${end.allDay?';VALUE=DATE':''}:`+calendarTime(end.time,end.allDay));
lines.push('SUMMARY:'+calendarText(title));if(fields.location)lines.push('LOCATION:'+calendarText(fields.location));lines.push('URL:'+url,'END:VEVENT','END:VCALENDAR');
return lines.map(foldCalendar).join('\r\n')+'\r\n';
}
function readEvent(document,input) {
const url=pageKey(input),path=new URL(url).pathname;
if(!/^\/events\/(?:\d+|\d{4}\/\d{2}\/\d{2}\/[^/]+)\/?$/.test(path))return {available:false,title:'',start:'',end:'',timezone:'',location:'',url};
const nodes=[];function collect(node){if(Array.isArray(node)){node.forEach(collect);return;}if(!node||typeof node!=='object')return;if([node['@type']].flat().includes('Event'))nodes.push(node);if(node['@graph'])collect(node['@graph']);}
for(const script of document.querySelectorAll('script[type="application/ld+json"]')){try{if(script.textContent.length<200000)collect(JSON.parse(script.textContent));}catch{}}
const matching=nodes.filter(node=>{try{return node.url&&pageKey(node.url)===url;}catch{return false;}});const event=matching.length===1?matching[0]:nodes.length===1&&!nodes[0].url?nodes[0]:null;
const main=document.querySelector('main');const property=name=>{const matches=main?.querySelectorAll(`[itemprop="${name}"]`);return matches?.length===1?(matches[0].getAttribute('content')||matches[0].getAttribute('datetime')||matches[0].textContent.trim()):'';};
const location=event?.location;const address=location?.address;const textLocation=typeof location==='string'?location:[location?.name,typeof address==='string'?address:address?.addressLocality].filter(Boolean).join(', ');
return {available:true,title:String(event?.name||main?.querySelector('h1')?.textContent?.trim()||''),start:String(event?.startDate||property('startDate')||''),end:String(event?.endDate||property('endDate')||''),timezone:'',location:String(textLocation||property('location')||''),url};
}
// main.js
// Copyright 2026 TypicalBits.
function downloadCalendar(window,document,text) {
const blob=new window.Blob([text],{type:'text/calendar;charset=utf-8'});const url=window.URL.createObjectURL(blob);const link=document.createElement('a');link.href=url;link.download='fetlife-event.ics';link.hidden=true;document.body.append(link);link.click();link.remove();window.setTimeout(()=>window.URL.revokeObjectURL(url),1000);
}
function startCalendar({document,window,storage,download=text=>downloadCalendar(window,document,text)}) {
return bootstrapLite({document,window,storage,product:{id:'calendar',name:'Calendar',version:'0.1.0',icon:PRODUCT_ICON,accent:'#65D8EB'},setup:async({ui})=>{
let current='',dirty=false,available=false,message='';const fields={};
ui.body.append(ui.e('p',{class:'note'},'Preview the current event before exporting. All-day end is the first day after the event.'));
for(const [key,label]of [['title','Title'],['start','Start'],['end','End (optional)'],['timezone','Timezone (for times without an offset)'],['location','Location'],['url','Event link']]){fields[key]=liteField(ui,key,label);fields[key].addEventListener('input',()=>{dirty=true;message='';validate();});}
fields.start.placeholder='2026-10-03T18:00:00-07:00';fields.end.placeholder='Optional';fields.timezone.placeholder='America/Los_Angeles';fields.url.readOnly=true;
const actions=ui.e('div',{class:'actions'});const exportButton=liteButton(ui,'Export .ics',{class:'primary','data-export':''},()=>{try{if(!available)throw Error('Open an individual event first.');download(buildCalendar(values()));message='Calendar file exported.';}catch(error){message=error.message;}validate();});
const reload=liteButton(ui,'Read page again',{},()=>{if(dirty){confirmation.hidden=false;return;}load();});actions.append(exportButton,reload);ui.body.append(actions);
const confirmation=ui.e('div',{class:'confirmation'});confirmation.hidden=true;confirmation.append(ui.e('p',{},'Replace your edited preview with this page’s event?'),liteButton(ui,'Replace preview',{},()=>{confirmation.hidden=true;dirty=false;load();}),liteButton(ui,'Keep edits',{},()=>confirmation.hidden=true));ui.body.append(confirmation);
function values(){return Object.fromEntries(Object.entries(fields).map(([key,node])=>[key,node.value]));}
function validate(){let error='';try{if(!available)throw Error('Open an individual event to export its calendar file.');buildCalendar(values());}catch(problem){error=problem.message;}exportButton.disabled=!!error;ui.setStatus(message||error||'Ready to export. Review the event details first.');}
function load(){const data=readEvent(document,window.location.href);current=window.location.href;available=data.available;for(const [key,node]of Object.entries(fields))node.value=data[key]||'';message='';dirty=false;validate();}
load();return {refresh(){if(window.location.href!==current){if(dirty){message='Page changed. Your edited preview is retained. Use Read page again to replace it.';available=false;validate();}else load();}else if(!dirty)load();}};
}});
}
void startCalendar({document,window,storage:GM}).catch(()=>{});
})();