<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>dispatch yourself — dispatches</title><style>
:root{--ink:#111;--paper:#ded9cd;--red:#a8322a}*{box-sizing:border-box}body{margin:0;background:#eeece7;color:var(--ink);font:16px/1.4 system-ui,-apple-system,"Segoe UI",sans-serif}.wrap{width:min(100% - 32px,560px);margin:0 auto;padding:26px 0 18px}h1{font:800 24px/1.05 system-ui,sans-serif;letter-spacing:-.04em;margin:0 0 6px} .intro{margin:0 0 18px;color:#58534c;font-size:14px}.controls{display:grid;gap:11px;margin-bottom:20px}label{font-size:12px;letter-spacing:.08em;text-transform:lowercase;color:#625d55}input[type=text]{display:block;width:100%;margin-top:5px;padding:11px 12px;border:1px solid #bdb5a8;background:#fffdf8;border-radius:2px;font:16px system-ui;color:var(--ink)}input[type=file]{display:none}.upload{display:block;padding:11px 12px;border:1px solid #bdb5a8;background:#fffdf8;text-align:center;cursor:pointer;font-size:14px}.preview{width:100%;display:block;height:auto;border:1px solid #ddd3c3;box-shadow:0 8px 24px #21170e16;background:#ded9cd}.buttons{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:14px}button{padding:13px 10px;border:1px solid var(--ink);background:var(--ink);color:#fff;font:600 14px system-ui;cursor:pointer}button.secondary{background:transparent;color:var(--ink)}button:active{transform:translateY(1px)}footer{text-align:center;color:#777166;font-size:12px;margin-top:17px}@media(min-width:520px){.wrap{padding-top:36px}.controls{grid-template-columns:1fr 1fr}.upload{grid-column:1/-1}}
</style></head><body><main class="wrap"><h1>dispatch yourself</h1><p class="intro">make yourself the front page. then send it to someone.</p><section class="controls"><label class="upload" for="photo">choose a photo</label><input id="photo" type="file" accept="image/*"><label>your name<input id="name" type="text" maxlength="32" placeholder="e.g. sam" value="Sam"></label><label>place, if you like<input id="place" type="text" maxlength="48" placeholder="e.g. mumbai, india"></label></section><canvas class="preview" id="cover" width="1080" height="1920" aria-label="your dispatches front page"></canvas><div class="buttons"><button class="secondary" id="again">give me another</button><button id="download">download</button></div><footer>made with dispatches</footer></main><script>
(()=>{const c=document.getElementById('cover'),x=c.getContext('2d'),nameEl=document.getElementById('name'),placeEl=document.getElementById('place');let photo=null,seed=0,last=-1;const moods=['HAUNTING','KINDNESS','GRATEFUL','INGENIOUS','CHARMING','STEADY','WILD','DEVOTION','BRAVE','DEFIANT','FEROCIOUS','UNKILLABLE','TENDER','PATIENT','UNFINISHED','MAGNIFICENT','QUIET','FEARLESS'];const heads=[['The person who','apologised to the sea'],['The one who','taught a city to whistle'],['The person who','kept the moon waiting'],['The one who','declared war on small spoons'],['The person who','built a home for lost buttons'],['The one who','asked the mountain for directions'],['The person who','made peace with every pigeon'],['The one who','put a chair in the middle of the road'],['The person who','wrote a thank-you note to Tuesday'],['The one who','tried to teach a river manners'],['The person who','rescued a very ordinary afternoon'],['The one who','invented a new way to be late'],['The person who','refused to let the kettle feel lonely'],['The one who','planted a forest of impossible plans'],['The person who','made a treaty with the wind'],['The one who','gave the neighbourhood a second chance'],['The person who','kept a spare key for the stars'],['The one who','put the whole town in a good mood'],['The person who','went looking for the end of worry'],['The one who','taught a lighthouse to wink'],['The person who','saved the last slice for a stranger'],['The one who','started a very small revolution'],['The person who','asked the clouds to stay for tea'],['The one who','found a shortcut through the century']];const subs=[['Has anyone seen my coat? It was here a minute ago and now the world has grown large and cold.'],['They say the morning belongs to the early bird, but the afternoon is ours if we want it.'],['Some days you lead the parade; other days you just watch the dogs go by. Both count.'],['An investigation into why the kettle sounds so urgent when nobody is rushing.'],['Notes on a life lived mostly in the margins, where the handwriting is better.'],['It is later than we think, but also earlier than we deserve. Please advise.'],['A small gathering was held in the kitchen to discuss the rising price of silence.'],['We are standing on the corner waiting for a change in the weather and the plot.'],['The authorities have confirmed that nobody knows what they are doing, which is reassuring.']],quotes=[['"I meant to do today what I am about to do tomorrow."','— OVERHEARD'],['"If you cannot be elegant, be loud. If you cannot be loud, bring cake."','— THE ARCHIVE'],['"The map is mostly blank because we got bored and went swimming."','— FIELD NOTES'],['"Do not look back unless you dropped your sandwich."','— DAILY ADVICE'],['"We have nothing to lose except our composure."','— GENERAL LEDGER']],authors=[['G. V. R.','Senior Correspondent'],['M. K. T.','Investigations'],['S. O. N.','Special Bureau'],['A. L. E.','Weekend Desk'],['J. Z. Q.','Foreign Affairs'],['N. V. R.','Local Color']];function pick(arr,exclude){let i;do{i=Math.floor(Math.random()*arr.length)}while(i===exclude&&arr.length>1);return[arr[i],i]}
function getDateStr(){const d=new Date();const days=['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];const months=['January','February','March','April','May','June','July','August','September','October','November','December'];return `${days[d.getDay()]}, ${months[d.getMonth()] && months[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`.toUpperCase()}
function roll(){const [m,im]=pick(moods,last);const [h,ih]=pick(heads);const [s]=pick(subs);const [q]=pick(quotes);const [a]=pick(authors);seed={mood:m,head1:h[0],head2:h[1],sub:s[0],quote:q[0],quoteSrc:q[1],authorName:a[0],authorRole:a[1]};last=im;draw()}
function draw(){const w=1080,h=1920;x.fillStyle='#ded9cd';x.fillRect(0,0,w,h);x.fillStyle='#c8c1b3';x.fillRect(60,60,w-120,h-120);x.fillStyle='#ded9cd';x.fillRect(68,68,w-136,h-136);x.fillStyle='#111';x.font='700 38px monospace';x.fillText('VOL. XIV NO. 209',100,140);const placeTxt=placeEl.value.trim().toUpperCase();x.textAlign='right';x.fillText(placeTxt||'EVERYWHERE',w-100,140);x.textAlign='left';x.font='500 24px system-ui';x.fillText(getDateStr(),100,185);x.textAlign='right';x.fillText(seed.mood,w-100,185);x.textAlign='left';x.strokeStyle='#111';x.lineWidth=4;x.beginPath();x.moveTo(100,215);x.lineTo(w-100,215);x.stroke();x.lineWidth=2;x.beginPath();x.moveTo(100,224);x.lineTo(w-100,224);x.stroke();const pw=880,ph=820,px=100,py=265;x.save();x.beginPath();x.rect(px,py,pw,ph);x.clip();x.fillStyle='#b8b0a2';x.fillRect(px,py,pw,ph);if(photo){const iw=photo.width,ih=photo.height;const scale=Math.max(pw/iw,ph/ih);const nw=iw*scale,nh=ih*scale;const nx=px+(pw-nw)/2,ny=py+(ph-nh)/2;x.drawImage(photo,nx,ny,nw,nh)}else{x.fillStyle='#111';x.font='italic 32px Georgia';x.textAlign='center';x.textBaseline='middle';x.fillText('upload your photo above',px+pw/2,py+ph/2);x.textBaseline='alphabetic';x.textAlign='left'}x.restore();x.strokeStyle='#111';x.lineWidth=4;x.strokeRect(px,py,pw,ph);const nameTxt=(nameEl.value.trim()||'SAM').toUpperCase();const tagY=py+ph+55;x.font='700 32px system-ui';x.fillStyle='#111';x.fillText('DISPATCHES PROFILE:',100,tagY);x.font='800 48px system-ui';x.fillStyle='#a8322a';x.fillText(nameTxt,100,tagY+55);x.strokeStyle='#111';x.lineWidth=2;x.beginPath();x.moveTo(100,tagY+85);x.lineTo(w-100,tagY+85);x.stroke();const colTop=tagY+120;const colW=412,colGap=56,col1X=100,col2X=col1X+colW+colGap;x.font='700 46px Georgia';x.fillStyle='#111';wrapText(seed.head1,col1X,colTop,colW,52);wrapText(seed.head2,col1X,colTop+104,colW,52);x.font='18px system-ui';x.fillStyle='#333';wrapText(seed.sub,col1X,colTop+230,colW,26);x.font='italic 22px Georgia';x.fillStyle='#111';wrapText(seed.quote,col2X,colTop,colW,32);x.font='700 16px system-ui';x.fillStyle='#625d55';x.fillText(seed.quoteSrc,col2X,colTop+140);x.lineWidth=1;x.beginPath();x.moveTo(col2X,colTop+170);x.lineTo(col2X+colW,colTop+170);x.stroke();x.font='700 20px system-ui';x.fillStyle='#111';x.fillText(seed.authorName,col2X,colTop+210);x.font='16px system-ui';x.fillStyle='#625d55';x.fillText(seed.authorRole,col2X,colTop+235)}
function wrapText(text,xPos,yPos,maxWidth,lineHeight){const words=text.split(' ');let line='';let y=yPos;for(let n=0;n<words.length;n++){const testLine=line+words[n]+' ';const metrics=x.measureText(testLine);const testWidth=metrics.width;if(testWidth>maxWidth && n>0){x.fillText(line,xPos,y);line=words[n]+' ';y+=lineHeight}else{line=testLine}}x.fillText(line,xPos,y)}
document.getElementById('photo').addEventListener('change',e=>{const file=e.target.files[0];if(!file)return;const reader=new FileReader();reader.onload=ev=>{const img=new Image();img.onload=()=>{photo=img;draw()};img.src=ev.target.result};reader.readAsDataURL(file)});nameEl.addEventListener('input',draw);placeEl.addEventListener('input',draw);document.getElementById('again').addEventListener('click',roll);document.getElementById('download').addEventListener('click',()=>{const a=document.createElement('a');a.download='dispatch-front-page.png';a.href=c.toDataURL('image/png');a.click()});roll();})();
</script></body></html>