I’m a Haudenosaunee author from Windsor, Ontario. I write stories about kids finding their people, getting into trouble, and figuring out what’s worth standing up for.My debut middle-grade novel, The Riverside Rink Rats, is coming from Orca Book Publishers in 2028. It brings together hockey, mystery, and the city I call home.When I’m not writing, I’m usually designing things, making music, building guitars, or gasping for breath on the bench during beer-league hockey. I live with my wife, our two daughters, and a couple of pets who contribute very little toward the mortgage.Represented by Lisa Rambert @ Transatlantic Agency
Stay in the loopWant to see the Rink Rats cover when it’s ready? Sign up for book news, occasional updates from my writing desk, and a heads-up when preorders open.
Work with meWriting feedback, Indigenous perspectives, and someone to bounce ideas off.Have a story in progress, a character you’re unsure about, or an idea you can’t quite get onto the page? I offer honest, practical feedback to help you figure out your next step.Story & manuscript feedbackMaybe the opening isn’t landing, a character feels flat, or something in the middle has gone sideways. I can read your work, help identify what’s getting in the way, and suggest ways forward while keeping your voice at the centre.Indigenous representationI offer feedback on Indigenous characters, dialogue, and story choices, drawing on my Mohawk perspective and lived experience. My interest is in characters with full lives: humour, friendships, complicated families, ambitions, and flaws.I don’t speak for every Indigenous person or Nation. We’ll discuss whether my background fits your project and where more specific expertise may be needed.Talk it throughYou don’t need a finished manuscript. Bring the half-formed idea, the plot problem, or the three different endings you can’t choose between. We can talk through the possibilities and work out what to try next.Tell me what you’re working on.Send a short description of your project, what you’d like help with, and your timeline. Include a budget if you have one in mind. We’ll agree on the scope and cost before any work begins.Let’s talk
Rink Rats / Player recruitment
Your number. Your team. Your story.
Pick a side, take the tryout, and get your own hockey card.
Who’s hitting the ice?
Build your player. No hockey experience required.
Use a nickname if you like. Your answers stay on your device.
Roster confirmed
You made the team.
On mobile, if saving doesn’t start, press and hold the image shown below.
;(()=>{ function initRinkCard(){ 'use strict'; const root=document.getElementById('rr-builder'); if(!root||root.dataset.ready)return;root.dataset.ready='true'; const BOOK_URL='https://ericjacobson.ca'; const TEAMS={rats:{name:'RINK RATS',full:'Riverside Rink Rats',ink:'#10243f',accent:'#c63842',light:'#f4b5b8'},thunder:{name:'TECUMSEH THUNDER',full:'Tecumseh Thunder',ink:'#17151c',accent:'#7544b5',light:'#d8bdf8'}}; const $=s=>root.querySelector(s);const all=s=>[...root.querySelectorAll(s)]; const canvas=$('#rr-card'),ctx=canvas.getContext('2d'); let stage='build',q=0,answers=[null,null,null],result=null,side='front'; const QUESTIONS=[ {title:'Thirty seconds left.',question:'You’re down a goal. What’s your move?',options:[['Chase the puck like my skates are on fire.','speed'],['Find the teammate nobody’s watching.','brain'],['Park in front of the net. I’m not moving.','grit'],['Get everyone fired up. Loudly.','heart']]}, {title:'Something’s up at the rink.',question:'Your team finds a mysterious note. You’re the one who…',options:[['Runs off to see where it leads.','speed'],['Checks every detail for a clue.','brain'],['Insists we find out who left it.','grit'],['Makes sure nobody investigates alone.','heart']]}, {title:'The dressing-room test.',question:'A new teammate is having a rough first day. You…',options:[['Challenge them to a race. Winner gets bragging rights.','speed'],['Show them a trick that took you forever to learn.','brain'],['Stay after practice until they get it.','grit'],['Make room beside you. They’re one of us now.','heart']]} ]; const TYPES={ speed:{name:'THE SPARK PLUG',role:'First to the puck. First into trouble.',note:'Gets to the puck before everyone else. Occasionally gets there before the plan. Has never walked calmly into a dressing room.',ability:'AFTERBURNERS',detail:'One burst of speed can change the whole shift.',base:[8,6,6,6],position:'FORWARD'}, brain:{name:'THE PLAYMAKER',role:'Sees the play. Spots the clue.',note:'Sees a passing lane before anyone else knows there is a lane. Has a backup plan for the backup plan. Cannot locate their own water bottle.',ability:'SIXTH SENSE',detail:'Spots the opening everyone else skated past.',base:[6,8,6,6],position:'DEFENCE'}, grit:{name:'THE ANCHOR',role:'Still going when the others stop.',note:'First into the corner. Last to admit they are tired. Would block a shot with a lunchbox if the equipment bag went missing.',ability:'NOT BUDGING',detail:'Makes the hard play when the team needs it.',base:[6,6,8,6],position:'DEFENCE'}, heart:{name:'THE GLUE',role:'Every team needs someone like you.',note:'Knows when a teammate needs a pep talk and when they need half a granola bar. Celebrates an assist like they won the Stanley Cup.',ability:'BENCH BOOST',detail:'Gets the whole team believing again.',base:[6,6,6,8],position:'FORWARD'} }; function player(){const t=$('input[name="rr-team"]:checked').value;return {team:TEAMS[t],teamId:t,name:($('#rr-name').value.trim()||'Rookie'),number:Math.max(0,Math.min(99,Math.round(Number($('#rr-number').value)||0))),position:$('#rr-position').value,jersey:$('#rr-jersey').value,skin:$('#rr-skin').value,hair:$('#rr-hair').value,style:$('#rr-style').value,helmet:$('#rr-helmet').value};} function score(){const counts={speed:0,brain:0,grit:0,heart:0};answers.forEach((a,i)=>counts[QUESTIONS[i].options[a][1]]++);const keys=Object.keys(counts),max=Math.max(...Object.values(counts));const preferred=QUESTIONS[0].options[answers[0]][1];const key=counts[preferred]===max?preferred:keys.find(k=>counts[k]===max);const type=TYPES[key];return {...type,key,stats:type.base.map((n,i)=>Math.min(10,n+counts[keys[i]]))};} function pos(p){return p.position==='auto'?(result?result.position:'ROOKIE'):p.position;} function show(which,focus=true){stage=which;all('[data-panel]').forEach(el=>el.hidden=el.dataset.panel!==which);all('.rr-step').forEach((el,i)=>el.classList.toggle('rr-on',which==='result'||(which==='quiz'?q+1:0)>=i));$('[data-action="flip"]').hidden=!result;if(focus)$('[data-panel="'+which+'"] h3').focus({preventScroll:true});render();} function quiz(){const question=QUESTIONS[q];$('#rr-qcount').textContent='TRYOUT '+(q+1)+' / 3';$('#rr-qtitle').textContent=question.title;$('#rr-question').textContent=question.question;const box=$('#rr-answers');box.replaceChildren();question.options.forEach(([label],i)=>{const b=document.createElement('button');b.type='button';b.className='rr-answer';b.textContent=label;b.setAttribute('aria-pressed',String(answers[q]===i));b.onclick=()=>{answers[q]=i;[...box.children].forEach((e,j)=>e.setAttribute('aria-pressed',String(j===i)));$('#rr-error').textContent='';};box.append(b);});$('#rr-error').textContent='';$('[data-action="next"]').textContent=q===2?'Draft me! →':'Next →';show('quiz');} function finish(){result=score();side='front';show('result');canvas.classList.remove('rr-reveal');void canvas.offsetWidth;canvas.classList.add('rr-reveal');$('#rr-status').textContent='You made the '+player().team.full+'. Your card is ready.';} function text(c,str,x,y,size,color='#f5f0e5',align='left',weight='bold',maxWidth){c.fillStyle=color;c.textAlign=align;let s=size;c.font=weight+' '+s+'px Arial, sans-serif';while(maxWidth&&c.measureText(str).width>maxWidth&&s>13){s--;c.font=weight+' '+s+'px Arial, sans-serif';}c.fillText(str,x,y);} function rect(c,x,y,w,h,color){c.fillStyle=color;c.fillRect(x,y,w,h);} function wrap(c,str,x,y,width,size=26,line=37,color='#10243f'){c.font='normal '+size+'px Arial, sans-serif';c.textAlign='left';c.fillStyle=color;let row='';for(const word of str.split(' ')){const test=row?row+' '+word:word;if(c.measureText(test).width>width&&row){c.fillText(row,x,y);y+=line;row=word;}else row=test;}if(row)c.fillText(row,x,y);return y+line;} function emblem(c,p,x,y,s){c.save();c.translate(x,y);c.fillStyle=p.team.accent;if(p.teamId==='thunder'){c.beginPath();c.moveTo(s*.55,0);c.lineTo(s*.1,s*.55);c.lineTo(s*.43,s*.55);c.lineTo(s*.3,s);c.lineTo(s*.9,s*.36);c.lineTo(s*.57,s*.36);c.closePath();c.fill();}else{c.fillRect(s*.1,s*.2,s*.8,s*.6);text(c,'RR',s*.5,s*.66,s*.4,'white','center');}c.restore();} function avatar(c,p){const unit=12;const ox=172,oy=254;const R=(x,y,w,h,col)=>rect(c,ox+x*unit,oy+y*unit,w*unit,h*unit,col);const dark=p.team.ink,accent=p.team.accent,shirt=p.jersey==='away'?'#f5f0e5':dark,outline='#070c17',hair=p.hair;const goalie=pos(p)==='GOALIE'; R(0,22,28,1,'#d5dfde');R(3,23,23,1,'#a7b9c0'); if(p.style==='flow'||p.style==='braid'){R(9,5,10,8,hair);if(p.style==='braid'){R(19,7,2,8,hair);R(20,14,2,3,hair);R(20,16,2,1,accent);}} R(10,1,8,8,outline);R(10,2,8,6,p.skin);R(11,8,6,2,p.skin);if(p.style!=='none'){R(10,1,8,2,hair);R(10,3,1,2,hair);R(17,3,1,2,hair);} R(11,4,1,1,outline);R(16,4,1,1,outline);R(13,6,3,1,'#fff3de'); if(p.helmet==='helmet'){R(9,0,10,3,dark);R(10,-1,8,1,dark);R(9,1,10,1,accent);R(9,3,1,3,dark);R(18,3,1,3,dark);if(goalie){R(10,3,8,1,'#ddd');R(10,5,8,1,'#ddd');R(12,3,1,4,'#ddd');R(15,3,1,4,'#ddd');R(10,7,8,1,'#ddd');}} R(7,10,14,9,outline);R(8,10,12,8,shirt);R(7,11,2,6,shirt);R(19,11,2,6,shirt);R(5,11,3,6,shirt);R(21,11,3,6,shirt);R(5,14,3,2,accent);R(21,14,3,2,accent);R(5,17,3,2,outline);R(21,17,3,2,outline);R(8,16,12,2,accent);R(8,15,12,1,'#f5f0e5');R(12,10,4,1,accent); text(c,String(p.number).padStart(2,'0'),ox+14*unit,oy+15*unit,44,p.jersey==='away'?dark:'#fff','center'); R(9,18,10,3,dark);R(9,20,4,5,dark);R(15,20,4,5,dark);R(9,22,4,2,accent);R(15,22,4,2,accent);R(8,25,5,2,outline);R(15,25,5,2,outline);R(7,27,6,1,'#d9e5eb');R(15,27,6,1,'#d9e5eb'); if(goalie){R(8,20,6,6,'#eee9dc');R(15,20,6,6,'#eee9dc');R(9,21,1,4,accent);R(19,21,1,4,accent);R(8,23,6,1,accent);R(15,23,6,1,accent);R(3,16,5,4,'#eee9dc');}else{for(let i=0;10>i;i++)R(23,18+i,1,1,'#a4845c');R(18,27,6,1,outline);} } function draw(c,p,face){const t=p.team;c.clearRect(0,0,700,1000);rect(c,0,0,700,1000,t.ink);rect(c,12,12,676,976,'#f5f0e5');rect(c,23,23,654,954,t.ink);rect(c,23,23,654,12,t.accent);text(c,'RINK RATS / ROOKIE SERIES',49,65,15,'#f5f0e5','left','bold');text(c,'No. '+String(p.number).padStart(2,'0'),650,65,15,t.light,'right'); if(face==='front'){ text(c,t.name,350,116,32,'#fff','center','bold',592);rect(c,43,140,614,80,t.accent);text(c,p.name.toLocaleUpperCase(),350,194,44,'#fff','center','bold',570); rect(c,43,237,614,370,'#e0e8e7');for(let x=65;650>x;x+=55)rect(c,x,237,1,370,'#c3d1d2');rect(c,43,500,614,5,t.accent);rect(c,43,550,614,57,'#d5dfe0');text(c,String(p.number).padStart(2,'0'),610,376,110,'#c1cecf','right');emblem(c,p,66,264,65);avatar(c,p); text(c,pos(p),350,643,18,t.light,'center');text(c,result?result.name:'YOUR STORY STARTS HERE',350,686,32,'#fff','center','bold',590); rect(c,43,714,614,207,'#f5f0e5');const labels=['SPEED','HOCKEY IQ','GRIT','TEAM SPIRIT'];labels.forEach((l,i)=>{let y=751+i*44;text(c,l,62,y,18,t.ink);for(let j=0;10>j;j++)rect(c,270+j*32,y-20,25,23,result&&result.stats[i]>j?t.accent:'#d9d3c7');text(c,result?String(result.stats[i]):'?',630,y,19,t.ink,'right');}); }else{ text(c,'THE SCOUTING REPORT',49,123,31,'#fff','left','bold',600);text(c,p.name.toLocaleUpperCase(),49,181,43,'#fff','left','bold',595);text(c,t.name+' / '+pos(p),49,218,18,t.light,'left','bold',595); rect(c,43,244,614,641,'#f5f0e5');text(c,result?result.name:'ROOKIE',65,292,28,t.ink,'left','bold',560);wrap(c,result?result.role:'Ready for the tryout.',65,333,560,22,31,t.ink);rect(c,65,369,570,3,t.accent);text(c,'SCOUT’S NOTES',65,411,16,t.accent);let y=wrap(c,result?result.note:'Take the tryout to reveal your scouting report.',65,450,557,26,37,t.ink);y=Math.max(635,y+28);rect(c,65,y,570,132,t.ink);text(c,'SIGNATURE ABILITY',85,y+30,14,t.light);text(c,result?(pos(p)==='GOALIE'?({speed:'FLASH PADS',brain:'READ THE SHOT',grit:'BRICK WALL',heart:'CALM IN THE CREASE'}[result.key]):result.ability):'TO BE DISCOVERED',85,y+65,24,'#fff','left','bold',530);wrap(c,result?result.detail:'Every rookie has something special.',85,y+94,525,18,24,'#fff');text(c,'THERE’S ROOM FOR YOU ON THE ROSTER.',65,854,16,t.ink,'left','bold',565);text(c,'A hockey story. A mystery. Your next team.',350,923,18,t.light,'center'); } text(c,'RINK RATS • ERICJACOBSON.CA',350,958,18,'#f5f0e5','center');} function render(){const p=player();root.style.setProperty('--rr-ink',p.team.ink);root.style.setProperty('--rr-accent',p.team.accent);draw(ctx,p,side);$('#rr-preview-label').textContent=result?(side==='front'?'YOUR OFFICIAL ROOKIE CARD':'SCOUTING REPORT'):'YOUR ROOKIE CARD';$('[data-action="flip"]').textContent=side==='front'?'Flip to scouting report ↻':'Flip to player card ↻';$('#rr-preview-note').textContent=result?'Two sides. One new teammate.':'Your player updates as you make your picks.';$('#rr-report').textContent=result?result.note:'';$('#rr-result-sub').textContent=p.team.full+' • #'+p.number+' • '+pos(p);canvas.setAttribute('aria-label',p.name+', '+p.team.full+', number '+p.number+', '+pos(p)+(result?'. '+result.name+'. '+result.note+'. Speed '+result.stats[0]+', hockey IQ '+result.stats[1]+', grit '+result.stats[2]+', team spirit '+result.stats[3]+' out of 10.':'. Complete the tryout to reveal ratings.'));} function download(both){try{const p=player(),out=document.createElement('canvas');out.width=both?1440:700;out.height=1000;const c=out.getContext('2d');if(both){rect(c,0,0,1440,1000,'#f5f0e5');draw(c,p,'front');c.save();c.translate(740,0);draw(c,p,'back');c.restore();}else draw(c,p,side);const url=out.toDataURL('image/png'),a=document.createElement('a');a.href=url;a.download=(p.name.replace(/[^a-z0-9_-]/gi,'-')||'Rookie')+'-'+p.teamId+'-'+(both?'both-sides':side)+'.png';root.append(a);a.click();a.remove();const holder=$('#rr-save-fallback');holder.replaceChildren();const image=new Image();image.src=url;image.alt='Your downloadable '+(both?'two-sided hockey card':side+' of your hockey card');image.style.cssText='width:100%;height:auto;margin-top:14px;border-radius:6px;';holder.append(image);holder.hidden=false;$('#rr-status').textContent='Card image ready. If the download did not start, press and hold the saved image.';}catch(e){$('#rr-status').textContent='Could not create the image. Please try again.';}} all('input,select').forEach(el=>el.addEventListener('input',()=>{render();$('#rr-save-fallback').hidden=true;})); root.addEventListener('click',e=>{const b=e.target.closest('[data-action]');if(!b)return;switch(b.dataset.action){case 'start':if(!$('#rr-number').checkValidity()){$('#rr-number').reportValidity();return;}if(result){show('result');}else{q=0;quiz();}break;case 'next':if(answers[q]===null){$('#rr-error').textContent='Pick the answer that sounds most like you.';return;}if(2>q){q++;quiz();}else finish();break;case 'back':if(q>0){q--;quiz();}else show('build');break;case 'flip':side=side==='front'?'back':'front';render();break;case 'edit':side='front';show('build');$('[data-action="start"]').textContent='Update my card →';break;case 'retry':q=0;answers=[null,null,null];result=null;side='front';$('#rr-save-fallback').hidden=true;$('[data-action="start"]').textContent='Head to tryouts →';quiz();break;case 'download':download(false);break;case 'both':download(true);break;}}); $('#rr-book-link').href=BOOK_URL;render(); } if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',initRinkCard,{once:true});}else{setTimeout(initRinkCard,0);} })();
Get in touchHave a question about my writing, interested in working together, or just want to say hello? Send me a message below.For consulting inquiries, tell me a little about your project, what you’d like help with, and your timeline. You don’t need a polished pitch, we can figure that out together! Lets do rad things.