@toneflix/flipbook 0.2.4 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/flipbook.iife.js +4 -4
- package/dist/index.cjs +158 -3
- package/dist/index.d.cts +39 -2
- package/dist/index.d.mts +39 -2
- package/dist/index.mjs +159 -5
- package/dist/style.css +25 -1
- package/package.json +2 -2
package/dist/flipbook.iife.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
var Flipbook=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});let t=(e,t)=>({x:e.x-t.x,y:e.y-t.y}),n=(e,t)=>e.x*t.x+e.y*t.y,r=e=>Math.hypot(e.x,e.y);var i=class e{a;b;c;d;e;f;constructor(e=1,t=0,n=0,r=1,i=0,a=0){this.a=e,this.b=t,this.c=n,this.d=r,this.e=i,this.f=a}static translate(t,n){return new e(1,0,0,1,t,n)}static rotate(t){let n=Math.cos(t),r=Math.sin(t);return new e(n,r,-r,n,0,0)}static scale(t,n=t){return new e(t,0,0,n,0,0)}static reflect(t,r){let{x:i,y:a}=r,o=2*n(t,r);return new e(1-2*i*i,-2*i*a,-2*i*a,1-2*a*a,o*i,o*a)}after(t){let{a:n,b:r,c:i,d:a,e:o,f:s}=this;return new e(n*t.a+i*t.b,r*t.a+a*t.b,n*t.c+i*t.d,r*t.c+a*t.d,n*t.e+i*t.f+o,r*t.e+a*t.f+s)}invert(){let{a:t,b:n,c:r,d:i,e:a,f:o}=this,s=t*i-n*r;return new e(i/s,-n/s,-r/s,t/s,(r*o-i*a)/s,(n*a-t*o)/s)}apply(e){return{x:this.a*e.x+this.c*e.y+this.e,y:this.b*e.x+this.d*e.y+this.f}}toCss(){return`matrix(${this.a}, ${this.b}, ${this.c}, ${this.d}, ${this.e}, ${this.f})`}place(e,t){let n=this.apply({x:e/2,y:t/2}),r=this.a*this.d-this.b*this.c<0,i=r?Math.atan2(-this.b,-this.a):Math.atan2(this.b,this.a);return{left:n.x-e/2,top:n.y-t/2,width:e,height:t,angle:i,mirror:r}}},a=class e{width;height;origin;point;center;normal;progress;flat;folded;constructor(n,i,a,o){this.width=n,this.height=i,this.origin=a,this.point=e.attach(o,a,i);let s=t(a,this.point),c=r(s),l=[{x:0,y:0},{x:n,y:0},{x:n,y:i},{x:0,y:i}];this.center={x:(a.x+this.point.x)/2,y:(a.y+this.point.y)/2},this.progress=Math.min(1,c/Math.max(1,2*a.x)),c<.5?(this.normal={x:1,y:0},this.flat=l,this.folded=[]):(this.normal={x:s.x/c,y:s.y/c},this.flat=this.clip(l,-1),this.folded=this.clip(l,1))}static attach(e,n,i){let a=[{x:0,y:0},{x:0,y:i}],o=e;for(let e=0;e<3;e++)for(let e of a){let i=r(t(n,e)),a=t(o,e),s=r(a);s>i&&(o={x:e.x+a.x*i/s,y:e.y+a.y*i/s})}return o}static hinge(e,t,n){let r=Math.max(-1,Math.min(1,n*t.x/Math.max(1,e.x)));return-Math.acos(r)*180/Math.PI}get shade(){return Math.min(1,this.progress*8)*Math.min(1,(1-this.progress)*6)}reflect(e){let r=2*n(t(e,this.center),this.normal);return{x:e.x-r*this.normal.x,y:e.y-r*this.normal.y}}get reflection(){return i.reflect(this.center,this.normal)}get flap(){return this.folded.map(e=>this.reflect(e))}flapMatrix(e){let{x:t,y:n}=this.normal,r=e===1?-1:1,i=e*(1-2*t*t)*r,a=-2*t*n*r,o=-2*e*t*n,s=1-2*n*n,c=this.reflect({x:e===1?this.width:0,y:0});return`matrix(${i}, ${a}, ${o}, ${s}, ${e*c.x}, ${c.y})`}clip(e,r){let i=[],a=e=>r*n(t(e,this.center),this.normal)>=0;return e.forEach((o,s)=>{let c=e[(s+e.length-1)%e.length],l=r*n(t(c,this.center),this.normal),u=r*n(t(o,this.center),this.normal);if(a(o)!==a(c)){let e=l/(l-u);i.push({x:c.x+(o.x-c.x)*e,y:c.y+(o.y-c.y)*e})}a(o)&&i.push(o)}),i}};let o={fall:.0075,auto:.0055,peek:.02,flick:170,lift:.12},s={now:()=>performance.now(),request:e=>requestAnimationFrame(e),cancel:e=>cancelAnimationFrame(e)},c=()=>typeof matchMedia==`function`&&matchMedia(`(prefers-reduced-motion: reduce)`).matches;var l=class{listener;physics;mode=`idle`;origin={x:0,y:0};point={x:0,y:0};stiff=!1;size={width:1,height:1};position={x:0,y:0};velocity={x:0,y:0};target={x:0,y:0};omega=o.fall;lift=0;turned=!1;trail=[];handle=0;last=0;scheduler;reducedMotion;constructor(e,t={}){this.listener=e,this.physics={...o,...t.physics},this.scheduler=t.scheduler??s,this.reducedMotion=t.reducedMotion??c}get active(){return this.mode!==`idle`}get dragging(){return this.mode===`drag`}get peeking(){return this.mode===`peek`}get frame(){return{origin:this.origin,point:this.point,stiff:this.stiff}}grab(e,t,n,r){this.size=n,this.origin=e,this.stiff=r,this.mode=`drag`,this.trail=[],this.stop(),this.moveTo(t)}drag(e){this.mode===`drag`&&this.moveTo(e)}release(){if(this.mode!==`drag`)return!1;let e=this.speed(),t=this.point.x+e.x*this.physics.flick;return this.fall(t<0,e,this.physics.fall),this.turned}turn(e,t,n){this.size=t,this.origin=e,this.stiff=n,this.point={...e},this.fall(!0,{x:0,y:0},this.physics.auto)}peek(e,t,n,r){this.mode!==`drag`&&this.mode!==`fall`&&((this.mode===`idle`||this.origin.x!==e.x||this.origin.y!==e.y)&&(this.size=n,this.origin=e,this.stiff=r,this.point={...e},this.position={...this.point},this.velocity={x:0,y:0},this.lift=0),this.mode=`peek`,this.omega=this.physics.peek,this.target=t,this.turned=t.x<0,this.run())}unpeek(){this.mode===`peek`&&(this.mode=`fall`,this.target=this.turned?{x:-this.origin.x,y:this.origin.y}:{...this.origin},this.run())}finish(){if(this.mode===`idle`)return;let e=this.mode===`fall`?this.turned:this.point.x<0;this.stop(),this.mode=`idle`,this.listener.settle(e)}cancel(){this.stop(),this.mode=`idle`}moveTo(e){let t=this.stiff?{x:Math.max(-this.origin.x,Math.min(this.origin.x,e.x)),y:this.origin.y}:a.attach(e,this.origin,this.size.height),n=this.scheduler.now();this.point=t,this.trail.push({p:t,t:n}),this.trail=this.trail.filter(e=>n-e.t<100),this.listener.frame(this.frame)}speed(){if(this.trail.length<2)return{x:0,y:0};let e=this.trail[0],t=this.trail[this.trail.length-1],n=Math.max(1,t.t-e.t);return{x:(t.p.x-e.p.x)/n,y:(t.p.y-e.p.y)/n}}fall(e,t,n){this.reducedMotion()?(this.stop(),this.mode=`idle`,this.listener.settle(e)):(this.mode=`fall`,this.turned=e,this.omega=n,this.target=e?{x:-this.origin.x,y:this.origin.y}:{...this.origin},this.lift=this.stiff?0:this.size.height*this.physics.lift*(this.origin.y>this.size.height/2?-1:1),this.position={x:this.point.x,y:this.point.y-this.arc(this.point.x)},this.velocity=t,this.run())}arc(e){let t=Math.max(1,this.origin.x),n=Math.min(1,Math.abs(e)/t);return this.lift*(1-n*n)}run(){this.handle||=(this.last=this.scheduler.now(),this.scheduler.request(this.tick))}stop(){this.handle&&this.scheduler.cancel(this.handle),this.handle=0}tick=e=>{let n=Math.min(48,e-this.last),i=this.omega;this.last=e;for(let e=0;e<n;e+=4){let t=Math.min(4,n-e);for(let e of[`x`,`y`]){let n=i*i*(this.target[e]-this.position[e])-2*i*this.velocity[e];this.velocity[e]+=n*t,this.position[e]+=this.velocity[e]*t}}let o={x:this.position.x,y:this.position.y+(this.mode===`fall`?this.arc(this.position.x):0)};this.point=this.stiff?{x:o.x,y:this.origin.y}:a.attach(o,this.origin,this.size.height),this.listener.frame(this.frame);let s=r(t(this.target,this.position))<.5&&r(this.velocity)<.02;s&&this.mode===`fall`?(this.handle=0,this.mode=`idle`,this.listener.settle(this.turned)):this.handle=s?0:this.scheduler.request(this.tick)}};let u={single:{label:`Full bleed`,slots:1},framed:{label:`Framed`,slots:1},pair:{label:`Pair`,slots:2},grid:{label:`Grid`,slots:4},collage:{label:`Collage`,slots:3},text:{label:`Words`,slots:0},triptych:{label:`Triptych`,slots:3},mosaic:{label:`Mosaic`,slots:3},strip:{label:`Film strip`,slots:3},contact:{label:`Contact sheet`,slots:6},scatter:{label:`Loose prints`,slots:3},story:{label:`Story`,slots:1}},d=(e,t,n,r=7,i=108,a=3)=>{let o=(i-a*(n-1))/n;return Array.from({length:n},(n,i)=>({x:e,y:r+i*(o+a),width:t,height:o}))},f={x:7,y:118,width:86,height:10},p={triptych:{slots:[7,36.67,66.33].map(e=>({x:e,y:14,width:26.67,height:94})),caption:{...f,y:114}},mosaic:{slots:[{x:7,y:7,width:51,height:108},...d(61,32,2)],caption:f},strip:{slots:d(7,86,3),caption:f},contact:{slots:d(7,41.5,3).flatMap((e,t)=>[e,d(51.5,41.5,3)[t]]),caption:f},scatter:{slots:[{x:9,y:9,width:54,height:41,rotate:-4},{x:38,y:43,width:54,height:41,rotate:3},{x:11,y:80,width:48,height:35,rotate:-2}],caption:{...f,x:63,y:92,width:30,height:28},loose:!0},story:{slots:[{x:7,y:7,width:86,height:66}],caption:{x:9,y:80,width:82,height:45},prose:!0}},m={classic:{label:`Classic`,cloth:`#5a1d22`,ink:`#e2c58f`,stage:[`#4a2a27`,`#22140f`,`#0d0807`]},linen:{label:`Linen`,cloth:`#e6dcc6`,ink:`#3d3226`,stage:[`#4a4134`,`#241f18`,`#0e0c09`]},midnight:{label:`Midnight`,cloth:`#141b2e`,ink:`#d5dbe8`,stage:[`#27324d`,`#121827`,`#07090f`]},leather:{label:`Tan leather`,cloth:`#6b3f24`,ink:`#ecc77d`,stage:[`#4d3121`,`#24170f`,`#0d0805`]},sage:{label:`Sage`,cloth:`#5d6f5c`,ink:`#f1e9d2`,stage:[`#3c4a3c`,`#1c241c`,`#0a0d0a`]},blush:{label:`Blush`,cloth:`#d8b4a8`,ink:`#5a2f2a`,stage:[`#5a4440`,`#2a1f1d`,`#100b0a`]}},h={classic:{label:`Classic`,description:`A ruled panel with the title over an inset photo`},gilt:{label:`Gilt`,description:`Gold rules and corner flourishes around an oval portrait`},window:{label:`Window`,description:`The photo set into a window pressed into the board`},label:{label:`Tipped-in print`,description:`A print held by photo corners over a paper title label`},deco:{label:`Art deco`,description:`Gold rays rising to an arched photo`},image:{label:`Full image`,description:`Your photo across the whole cover, finished like the board`}},g={ivory:{label:`Ivory`,swatch:`#fbf8f1`},white:{label:`White`,swatch:`#ffffff`},kraft:{label:`Kraft`,swatch:`#d8c4a2`},charcoal:{label:`Charcoal`,swatch:`#2a2826`}},_={none:{label:`Plain`,description:`The photo printed straight onto the page`},hairline:{label:`Hairline`,description:`A fine printed rule around the photo`},double:{label:`Double rule`,description:`Two printed rules, like a classic album keyline`},mat:{label:`Gallery mat`,description:`A wide printed mat with a thin frame line`},corners:{label:`Photo corners`,description:`Printed album corners holding the photo`},deckle:{label:`Deckled edge`,description:`A vintage print with a scalloped border`},plate:{label:`Plate mark`,description:`The photo sits in a plate pressed into the paper`},feather:{label:`Soft edge`,description:`The photo fades into the paper at its edges`},oval:{label:`Oval`,description:`An oval cameo inside a fine printed ring`},botanical:{label:`Botanical`,description:`Line-drawn sprigs of leaves around a fine rule`},watercolor:{label:`Watercolour`,description:`A wash of soft colour printed around a mat`},arcs:{label:`Rainbow arcs`,description:`A printed border of little rainbow arcs around a mat`},gilt:{label:`Gilt`,description:`An engraved border printed in gold ink`},woodgrain:{label:`Woodgrain`,description:`A printed woodgrain border around a mat`}},v={watercolor:3,arcs:3,gilt:2.6,woodgrain:3};function y(e){return e.coverText??e.coverDesign!==`image`}function b(e,t,n=`none`){return e.layout===`single`?`none`:t?.frame??x(e,n)}function x(e,t=`none`){return e.layout===`single`?`none`:e.frame??t}var S=class e{pageCount;faces;leaves;spread;stiffCovers;constructor(t,{spread:n,stiffCovers:r=!0}){this.pageCount=t;let i=Array.from({length:t},(e,t)=>({kind:`page`,index:t})),a=n&&t%2==1?[{kind:`blank`}]:[];this.spread=n,this.stiffCovers=r,this.faces=[{kind:`cover`},{kind:`title`},...i,...a,{kind:`colophon`},{kind:`back`}],this.leaves=n?e.pair(this.faces):this.faces.map(e=>({front:e,back:{kind:`blank`}}))}static pair(e){let t=[];for(let n=0;n<e.length;n+=2)t.push({front:e[n],back:e[n+1]});return t}get length(){return this.leaves.length}flippedForFace(e){return this.spread?Math.ceil(e/2):e}flippedForPage(e){return!e||e<1||e>this.pageCount?0:this.flippedForFace(e+1)}convert(e,t){return t.spread===this.spread?Math.min(e,this.length):this.spread?Math.ceil(e/2):Math.max(0,e*2-1)}isStiff(e){let{front:t,back:n}=this.leaves[e]??{};return this.stiffCovers&&(t?.kind===`cover`||t?.kind===`back`||n?.kind===`back`)}visible(e){return e<=0?[this.leaves[0].front]:e>=this.length?[this.spread?this.leaves[this.length-1].back:this.faces[this.faces.length-1]]:this.spread?[this.leaves[e-1].back,this.leaves[e].front]:[this.leaves[e].front]}stops(){if(!this.spread)return this.faces.map((e,t)=>({flipped:t,faces:[e]}));let e=[{flipped:0,faces:[this.faces[0]]}];for(let t=1;t<this.faces.length-1;t+=2)e.push({flipped:this.flippedForFace(t),faces:[this.faces[t],this.faces[t+1]]});return e.push({flipped:this.length,faces:[this.faces[this.faces.length-1]]}),e}label(t){if(t<=0)return`Cover`;if(t>=this.length)return`Back cover`;let n=e.pageNumbers(this.visible(t));return n.length===0?this.visible(t).some(e=>e.kind===`title`)?`Title page`:`The end`:n.length===1?`Page ${n[0]} of ${this.pageCount}`:`Pages ${n[0]}–${n[1]} of ${this.pageCount}`}static describe(t){let n=e.pageNumbers(t);return n.length?`page ${n.join(` and `)}`:t[0].kind===`back`?`the back cover`:t[0].kind===`cover`?`the cover`:`the title page`}static pageNumbers(e){return e.filter(e=>e.kind===`page`).map(e=>e.index+1)}};let C={edgeX:.15,edgeY:.08,peek:!0};var w=class{events;flipped;active=null;layout;width=0;height=0;grip;engine;press=null;grabStart={x:0,y:0};grabDir=1;grabReach=1;constructor(e,t,n={}){this.events=t,this.layout=e,this.flipped=Math.max(0,Math.min(e.length,n.flipped??0)),this.grip={...C,...n.grip},this.engine=new l({frame:e=>this.active&&this.events.frame(e,this.active),settle:e=>this.settle(e)},n)}get size(){return{width:this.width,height:this.height}}get atStart(){return this.flipped===0}get atEnd(){return this.flipped===this.layout.length}get settled(){return!this.active||this.active.peek}get label(){return this.layout.label(this.flipped)}configure({layout:e,width:t,height:n}){let r=e&&e!==this.layout,i=t!==void 0&&t!==this.width||n!==void 0&&n!==this.height;(r||i)&&(e&&r&&(this.flipped=e.convert(this.flipped,this.layout),this.layout=e),this.width=t??this.width,this.height=n??this.height,this.engine.cancel(),this.active=null,this.press=null,this.events.change())}go(e){let t=Math.max(0,Math.min(this.layout.length,e));if(t===this.flipped||this.width===0)return;if(this.engine.active&&!this.engine.peeking){this.engine.finish();return}if(this.engine.cancel(),this.events.flip(),Math.abs(t-this.flipped)!==1){this.active=null,this.flipped=t,this.events.change();return}let n=Math.min(t,this.flipped),r=this.layout.isStiff(n);this.begin({leaf:n,dir:t>this.flipped?1:-1,stiff:r,peek:!1}),this.engine.turn({x:this.width,y:r?this.height/2:this.height},this.size,r)}next(){this.go(this.flipped+1)}prev(){this.go(this.flipped-1)}hover(e){if(this.press||this.engine.dragging)return;let t=this.grip.peek?this.cornerAt(e):null;if(!t){this.engine.unpeek();return}let{width:n,height:r}=this,i=t.x>0?1:-1,a=i===1?this.flipped:this.flipped-1,o=this.layout.isStiff(a),s={x:n,y:o?r/2:t.y},c={x:i*(e.x-t.x),y:e.y-t.y},l=Math.hypot(c.x,c.y)||1,u=Math.max(n*.12,Math.min(n*.2,l*2)),d={x:n+c.x/l*u,y:o?s.y:t.y+c.y/l*u};(this.active?.leaf!==a||this.active.dir!==i)&&this.begin({leaf:a,dir:i,stiff:o,peek:!0}),this.engine.peek(s,d,this.size,o)}leave(){this.engine.unpeek()}down(e,t){this.press={at:e,id:t,grabbed:!1}}move(e,t){let n=this.press;if(!n||n.id!==t)return!1;let r=e.x-n.at.x,i=e.y-n.at.y;if(!n.grabbed){if(Math.hypot(r,i)<6||this.engine.active&&!this.engine.peeking)return!1;let e=(this.engine.peeking&&this.active?{...this.active,origin:this.engine.frame.origin,start:this.engine.frame.point}:null)??this.holdAt(n.at);if(!e)return!1;n.grabbed=!0,this.grabStart=e.start,this.grabDir=e.dir,this.grabReach=!this.layout.spread&&e.dir===-1?2:1,this.begin({leaf:e.leaf,dir:e.dir,stiff:e.stiff,peek:!1}),this.engine.grab(e.origin,e.start,this.size,e.stiff)}return this.engine.drag({x:this.grabStart.x+this.grabDir*this.grabReach*r,y:this.grabStart.y+i}),!0}up(e,t){let n=this.press;return!n||n.id!==t?null:(this.press=null,n.grabbed?(this.engine.release()&&this.events.flip(),`turn`):Math.abs(e.x-n.at.x)<8&&Math.abs(e.y-n.at.y)<8?`tap`:null)}cancel(){this.press=null,this.engine.dragging&&this.engine.release()}destroy(){this.engine.cancel()}begin(e){this.active=e,this.events.change()}settle(e){let t=this.active;if(!t)return;let n=t.dir===1?e:!e;this.flipped=n?t.leaf+1:t.leaf,this.active=null,this.events.change()}holdAt(e){let{width:t,height:n,layout:r}=this,i=r.spread?e.x>=0:e.x>=t*.3,a=i?this.flipped:this.flipped-1;if(a<0||a>=r.length)return null;let o=i?e:{x:r.spread?-e.x:t,y:e.y},s=o.x>t*(1-this.grip.edgeX),c=o.y<n*this.grip.edgeY,l=o.y>n*(1-this.grip.edgeY),u=r.isStiff(a),d={x:s||!(c||l)?t:Math.max(1,o.x),y:u?n/2:c?0:l?n:Math.max(0,Math.min(n,o.y))};return{leaf:a,dir:i?1:-1,origin:d,stiff:u,start:d}}cornerAt(e){let{width:t,height:n,layout:r,flipped:i}=this,a=Math.max(36,t*.12);return[...i<r.length?[{x:t,y:0},{x:t,y:n}]:[],...r.spread&&i>0?[{x:-t,y:0},{x:-t,y:n}]:[]].find(t=>Math.hypot(e.x-t.x,e.y-t.y)<a)??null}};let T={previous:`Previous page`,next:`Next page`,soundOn:`Turn page sound on`,soundOff:`Turn page sound off`,fullscreen:`Full screen`,exitFullscreen:`Exit full screen`,fullscreenPrompt:`Tap to open full screen`,byline:e=>`by ${e}`,rotateTitle:`Turn your phone sideways`,rotateBody:`Photo books open across two pages, just like the real thing.`,rotateFullscreen:`Open full screen`,rotateSingle:`Read one page at a time`},ee={viewBox:`0 0 40 40`,strokes:[{d:`M3 37V12Q3 3 12 3H37`,width:1},{d:`M7 37V16Q7 7 16 7H37`,width:.6},{d:`M12 12C16 10 20 12 20 16C20 19 17 20 15.5 18.5`,width:.8}],fills:[`M16 7C18 10 21 11 24 10C22 8 19 7 16 7Z`,`M7 16C10 18 11 21 10 24C8 22 7 19 7 16Z`,`M10.7 12a1.3 1.3 0 1 0 2.6 0a1.3 1.3 0 1 0 -2.6 0Z`]},E={x:50,y:124},D={viewBox:`0 0 100 133.33`,strokes:[...Array.from({length:17},(e,t)=>{let n=12+t/16*76,r=(E.y-88)/(E.y-16);return{d:`M${(E.x+(n-E.x)*r).toFixed(2)} 88L${n.toFixed(2)} 16`,width:.25,opacity:.3}}),{d:`M5 5H95V128.33H5Z`,width:.5},{d:`M11 8H89L92 11V122.33L89 125.33H11L8 122.33V11Z`,width:.35}],fills:[`M50 8.5L52 11L50 13.5L48 11Z`,`M50 119.83L52 122.33L50 124.83L48 122.33Z`]};function O(e,t={},...n){let r=document.createElement(e);for(let[e,n]of Object.entries(t))e===`style`?Object.assign(r.style,n):n!=null&&n!==!1&&r.setAttribute(e,n===!0?``:String(n));return te(r,n),r}function te(e,t){for(let n of t)n&&e.append(n)}function k(e){let t=document.createElement(`template`);return t.innerHTML=e.trim(),t.content.firstElementChild}async function A(e){try{!document.fullscreenElement&&e.requestFullscreen&&await e.requestFullscreen({navigationUI:`hide`})}catch{}try{let e=screen.orientation;if(e?.lock)return await e.lock(`landscape`),!0}catch{}return!1}function ne(e){let t=matchMedia(`(orientation: portrait)`),n=matchMedia(`(pointer: coarse)`),r=()=>e({portrait:t.matches,touch:n.matches});return r(),t.addEventListener(`change`,r),n.addEventListener(`change`,r),window.addEventListener(`resize`,r),()=>{t.removeEventListener(`change`,r),n.removeEventListener(`change`,r),window.removeEventListener(`resize`,r)}}let re=[`tl`,`tr`,`bl`,`br`];function j(e,t){let n=e.strokes.map(({d:e,width:t,opacity:n=1})=>`<path d="${e}" fill="none" stroke="currentColor" stroke-width="${t}" stroke-opacity="${n}"/>`).join(``),r=e.fills.map(e=>`<path d="${e}" fill="currentColor"/>`).join(``);return`<svg viewBox="${e.viewBox}" class="${t}" preserveAspectRatio="none" aria-hidden="true">${n}${r}</svg>`}let M=()=>re.map(e=>k(j(ee,`fb-fleuron is-${e}`))),N=(e,t,n)=>e.cover&&O(`div`,{class:t},n?H(e.cover,`fb-photo`):O(`div`,{class:`fb-placeholder`})),P=e=>O(`h1`,{class:`fb-serif fb-cover-title`},e.title),F=e=>e.kicker&&O(`p`,{class:`fb-kicker`},e.kicker),I=e=>e.subtitle&&O(`p`,{class:`fb-serif fb-italic fb-cover-subtitle`},e.subtitle);function ie(e,t){let n=y(e);switch(e.coverDesign??`classic`){case`gilt`:return[O(`div`,{class:`fb-cover-frame`}),...M(),O(`div`,{class:`fb-cover-stack`},n&&F(e),n&&P(e),N(e,`fb-cover-photo is-oval`,t),n&&I(e))];case`window`:return[N(e,`fb-cover-window`,t)||O(`div`,{class:`fb-cover-window is-empty`},V()),n&&O(`div`,{class:`fb-cover-stamp`},F(e),P(e),I(e))];case`label`:return[e.cover&&O(`div`,{class:`fb-cover-print`},N(e,`fb-cover-print-photo`,t),...re.map(e=>O(`span`,{class:`fb-print-frame-corner is-${e}`,"aria-hidden":`true`}))),n&&O(`div`,{class:`fb-cover-label ${e.cover?``:`is-alone`}`},F(e),P(e),I(e))];case`deco`:return[k(j(D,`fb-cover-rays`)),O(`div`,{class:`fb-cover-stack is-deco`},n&&F(e),N(e,`fb-cover-photo is-arch`,t),n&&P(e),n&&I(e))];case`image`:return[e.cover&&O(`div`,{class:`fb-cover-art`},t?H(e.cover,`fb-photo`):O(`div`,{class:`fb-placeholder`})),e.cover&&O(`div`,{class:`fb-cover-tint`}),n&&O(`div`,{class:`fb-cover-caption ${e.cover?``:`is-plain`}`},F(e),P(e),I(e))];default:return[O(`div`,{class:`fb-cover-panel`},n&&F(e),n&&P(e),N(e,`fb-cover-photo`,t)||n&&I(e))]}}let L=(e,t,...n)=>O(`div`,{class:`fb-cover fb-cover-${e.theme??`classic`} fb-design-${e.coverDesign??`classic`}`},...n,O(`div`,{class:`fb-hinge is-${t}`}),O(`div`,{class:`fb-cover-finish`,"aria-hidden":`true`}));function R(e,t=!0){return L(e,`left`,...ie(e,t))}function z(e){let t=e.coverDesign===`gilt`;return L(e,`right`,t&&O(`div`,{class:`fb-cover-frame`}),...t?M():[],O(`div`,{class:`fb-imprint`},V(),e.imprint&&O(`p`,{class:`fb-kicker`,style:{marginTop:`2cqw`,fontSize:`2.4cqw`,opacity:`0.6`}},e.imprint)))}let ae=[14,24,34,44,54,64,74].map((e,t)=>({x:e,angle:t%2?38:-38,length:26-t*2})),B=e=>k(`<svg viewBox="0 0 100 40" class="fb-print-frame-sprig is-${e}" fill="none" stroke="currentColor" stroke-linecap="round" aria-hidden="true">
|
|
1
|
+
var Flipbook=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});let t=(e,t)=>({x:e.x-t.x,y:e.y-t.y}),n=(e,t)=>e.x*t.x+e.y*t.y,r=e=>Math.hypot(e.x,e.y);var i=class e{a;b;c;d;e;f;constructor(e=1,t=0,n=0,r=1,i=0,a=0){this.a=e,this.b=t,this.c=n,this.d=r,this.e=i,this.f=a}static translate(t,n){return new e(1,0,0,1,t,n)}static rotate(t){let n=Math.cos(t),r=Math.sin(t);return new e(n,r,-r,n,0,0)}static scale(t,n=t){return new e(t,0,0,n,0,0)}static reflect(t,r){let{x:i,y:a}=r,o=2*n(t,r);return new e(1-2*i*i,-2*i*a,-2*i*a,1-2*a*a,o*i,o*a)}after(t){let{a:n,b:r,c:i,d:a,e:o,f:s}=this;return new e(n*t.a+i*t.b,r*t.a+a*t.b,n*t.c+i*t.d,r*t.c+a*t.d,n*t.e+i*t.f+o,r*t.e+a*t.f+s)}invert(){let{a:t,b:n,c:r,d:i,e:a,f:o}=this,s=t*i-n*r;return new e(i/s,-n/s,-r/s,t/s,(r*o-i*a)/s,(n*a-t*o)/s)}apply(e){return{x:this.a*e.x+this.c*e.y+this.e,y:this.b*e.x+this.d*e.y+this.f}}toCss(){return`matrix(${this.a}, ${this.b}, ${this.c}, ${this.d}, ${this.e}, ${this.f})`}place(e,t){let n=this.apply({x:e/2,y:t/2}),r=this.a*this.d-this.b*this.c<0,i=r?Math.atan2(-this.b,-this.a):Math.atan2(this.b,this.a);return{left:n.x-e/2,top:n.y-t/2,width:e,height:t,angle:i,mirror:r}}},a=class e{width;height;origin;point;center;normal;progress;flat;folded;constructor(n,i,a,o){this.width=n,this.height=i,this.origin=a,this.point=e.attach(o,a,i);let s=t(a,this.point),c=r(s),l=[{x:0,y:0},{x:n,y:0},{x:n,y:i},{x:0,y:i}];this.center={x:(a.x+this.point.x)/2,y:(a.y+this.point.y)/2},this.progress=Math.min(1,c/Math.max(1,2*a.x)),c<.5?(this.normal={x:1,y:0},this.flat=l,this.folded=[]):(this.normal={x:s.x/c,y:s.y/c},this.flat=this.clip(l,-1),this.folded=this.clip(l,1))}static attach(e,n,i){let a=[{x:0,y:0},{x:0,y:i}],o=e;for(let e=0;e<3;e++)for(let e of a){let i=r(t(n,e)),a=t(o,e),s=r(a);s>i&&(o={x:e.x+a.x*i/s,y:e.y+a.y*i/s})}return o}static hinge(e,t,n){let r=Math.max(-1,Math.min(1,n*t.x/Math.max(1,e.x)));return-Math.acos(r)*180/Math.PI}get shade(){return Math.min(1,this.progress*8)*Math.min(1,(1-this.progress)*6)}reflect(e){let r=2*n(t(e,this.center),this.normal);return{x:e.x-r*this.normal.x,y:e.y-r*this.normal.y}}get reflection(){return i.reflect(this.center,this.normal)}get flap(){return this.folded.map(e=>this.reflect(e))}flapMatrix(e){let{x:t,y:n}=this.normal,r=e===1?-1:1,i=e*(1-2*t*t)*r,a=-2*t*n*r,o=-2*e*t*n,s=1-2*n*n,c=this.reflect({x:e===1?this.width:0,y:0});return`matrix(${i}, ${a}, ${o}, ${s}, ${e*c.x}, ${c.y})`}clip(e,r){let i=[],a=e=>r*n(t(e,this.center),this.normal)>=0;return e.forEach((o,s)=>{let c=e[(s+e.length-1)%e.length],l=r*n(t(c,this.center),this.normal),u=r*n(t(o,this.center),this.normal);if(a(o)!==a(c)){let e=l/(l-u);i.push({x:c.x+(o.x-c.x)*e,y:c.y+(o.y-c.y)*e})}a(o)&&i.push(o)}),i}};let o={fall:.0075,auto:.0055,peek:.02,flick:170,lift:.12},s={now:()=>performance.now(),request:e=>requestAnimationFrame(e),cancel:e=>cancelAnimationFrame(e)},c=()=>typeof matchMedia==`function`&&matchMedia(`(prefers-reduced-motion: reduce)`).matches;var l=class{listener;physics;mode=`idle`;origin={x:0,y:0};point={x:0,y:0};stiff=!1;size={width:1,height:1};position={x:0,y:0};velocity={x:0,y:0};target={x:0,y:0};omega=o.fall;lift=0;turned=!1;trail=[];handle=0;last=0;scheduler;reducedMotion;constructor(e,t={}){this.listener=e,this.physics={...o,...t.physics},this.scheduler=t.scheduler??s,this.reducedMotion=t.reducedMotion??c}get active(){return this.mode!==`idle`}get dragging(){return this.mode===`drag`}get peeking(){return this.mode===`peek`}get frame(){return{origin:this.origin,point:this.point,stiff:this.stiff}}grab(e,t,n,r){this.size=n,this.origin=e,this.stiff=r,this.mode=`drag`,this.trail=[],this.stop(),this.moveTo(t)}drag(e){this.mode===`drag`&&this.moveTo(e)}release(){if(this.mode!==`drag`)return!1;let e=this.speed(),t=this.point.x+e.x*this.physics.flick;return this.fall(t<0,e,this.physics.fall),this.turned}turn(e,t,n){this.size=t,this.origin=e,this.stiff=n,this.point={...e},this.fall(!0,{x:0,y:0},this.physics.auto)}peek(e,t,n,r){this.mode!==`drag`&&this.mode!==`fall`&&((this.mode===`idle`||this.origin.x!==e.x||this.origin.y!==e.y)&&(this.size=n,this.origin=e,this.stiff=r,this.point={...e},this.position={...this.point},this.velocity={x:0,y:0},this.lift=0),this.mode=`peek`,this.omega=this.physics.peek,this.target=t,this.turned=t.x<0,this.run())}unpeek(){this.mode===`peek`&&(this.mode=`fall`,this.target=this.turned?{x:-this.origin.x,y:this.origin.y}:{...this.origin},this.run())}finish(){if(this.mode===`idle`)return;let e=this.mode===`fall`?this.turned:this.point.x<0;this.stop(),this.mode=`idle`,this.listener.settle(e)}cancel(){this.stop(),this.mode=`idle`}moveTo(e){let t=this.stiff?{x:Math.max(-this.origin.x,Math.min(this.origin.x,e.x)),y:this.origin.y}:a.attach(e,this.origin,this.size.height),n=this.scheduler.now();this.point=t,this.trail.push({p:t,t:n}),this.trail=this.trail.filter(e=>n-e.t<100),this.listener.frame(this.frame)}speed(){if(this.trail.length<2)return{x:0,y:0};let e=this.trail[0],t=this.trail[this.trail.length-1],n=Math.max(1,t.t-e.t);return{x:(t.p.x-e.p.x)/n,y:(t.p.y-e.p.y)/n}}fall(e,t,n){this.reducedMotion()?(this.stop(),this.mode=`idle`,this.listener.settle(e)):(this.mode=`fall`,this.turned=e,this.omega=n,this.target=e?{x:-this.origin.x,y:this.origin.y}:{...this.origin},this.lift=this.stiff?0:this.size.height*this.physics.lift*(this.origin.y>this.size.height/2?-1:1),this.position={x:this.point.x,y:this.point.y-this.arc(this.point.x)},this.velocity=t,this.run())}arc(e){let t=Math.max(1,this.origin.x),n=Math.min(1,Math.abs(e)/t);return this.lift*(1-n*n)}run(){this.handle||=(this.last=this.scheduler.now(),this.scheduler.request(this.tick))}stop(){this.handle&&this.scheduler.cancel(this.handle),this.handle=0}tick=e=>{let n=Math.min(48,e-this.last),i=this.omega;this.last=e;for(let e=0;e<n;e+=4){let t=Math.min(4,n-e);for(let e of[`x`,`y`]){let n=i*i*(this.target[e]-this.position[e])-2*i*this.velocity[e];this.velocity[e]+=n*t,this.position[e]+=this.velocity[e]*t}}let o={x:this.position.x,y:this.position.y+(this.mode===`fall`?this.arc(this.position.x):0)};this.point=this.stiff?{x:o.x,y:this.origin.y}:a.attach(o,this.origin,this.size.height),this.listener.frame(this.frame);let s=r(t(this.target,this.position))<.5&&r(this.velocity)<.02;s&&this.mode===`fall`?(this.handle=0,this.mode=`idle`,this.listener.settle(this.turned)):this.handle=s?0:this.scheduler.request(this.tick)}};let u={single:{label:`Full bleed`,slots:1},framed:{label:`Framed`,slots:1},pair:{label:`Pair`,slots:2},grid:{label:`Grid`,slots:4},collage:{label:`Collage`,slots:3},text:{label:`Words`,slots:0},triptych:{label:`Triptych`,slots:3},mosaic:{label:`Mosaic`,slots:3},strip:{label:`Film strip`,slots:3},contact:{label:`Contact sheet`,slots:6},scatter:{label:`Loose prints`,slots:3},story:{label:`Story`,slots:1}},d=(e,t,n,r=7,i=108,a=3)=>{let o=(i-a*(n-1))/n;return Array.from({length:n},(n,i)=>({x:e,y:r+i*(o+a),width:t,height:o}))},f={x:7,y:118,width:86,height:10},p={triptych:{slots:[7,36.67,66.33].map(e=>({x:e,y:14,width:26.67,height:94})),caption:{...f,y:114}},mosaic:{slots:[{x:7,y:7,width:51,height:108},...d(61,32,2)],caption:f},strip:{slots:d(7,86,3),caption:f},contact:{slots:d(7,41.5,3).flatMap((e,t)=>[e,d(51.5,41.5,3)[t]]),caption:f},scatter:{slots:[{x:9,y:9,width:54,height:41,rotate:-4},{x:38,y:43,width:54,height:41,rotate:3},{x:11,y:80,width:48,height:35,rotate:-2}],caption:{...f,x:63,y:92,width:30,height:28},loose:!0},story:{slots:[{x:7,y:7,width:86,height:66}],caption:{x:9,y:80,width:82,height:45},prose:!0}},m={classic:{label:`Classic`,cloth:`#5a1d22`,ink:`#e2c58f`,stage:[`#4a2a27`,`#22140f`,`#0d0807`]},linen:{label:`Linen`,cloth:`#e6dcc6`,ink:`#3d3226`,stage:[`#4a4134`,`#241f18`,`#0e0c09`]},midnight:{label:`Midnight`,cloth:`#141b2e`,ink:`#d5dbe8`,stage:[`#27324d`,`#121827`,`#07090f`]},leather:{label:`Tan leather`,cloth:`#6b3f24`,ink:`#ecc77d`,stage:[`#4d3121`,`#24170f`,`#0d0805`]},sage:{label:`Sage`,cloth:`#5d6f5c`,ink:`#f1e9d2`,stage:[`#3c4a3c`,`#1c241c`,`#0a0d0a`]},blush:{label:`Blush`,cloth:`#d8b4a8`,ink:`#5a2f2a`,stage:[`#5a4440`,`#2a1f1d`,`#100b0a`]}},h={classic:{label:`Classic`,description:`A ruled panel with the title over an inset photo`},gilt:{label:`Gilt`,description:`Gold rules and corner flourishes around an oval portrait`},window:{label:`Window`,description:`The photo set into a window pressed into the board`},label:{label:`Tipped-in print`,description:`A print held by photo corners over a paper title label`},deco:{label:`Art deco`,description:`Gold rays rising to an arched photo`},image:{label:`Full image`,description:`Your photo across the whole cover, finished like the board`}},g={ivory:{label:`Ivory`,swatch:`#fbf8f1`},white:{label:`White`,swatch:`#ffffff`},kraft:{label:`Kraft`,swatch:`#d8c4a2`},charcoal:{label:`Charcoal`,swatch:`#2a2826`}},_={none:{label:`Plain`,description:`The photo printed straight onto the page`},hairline:{label:`Hairline`,description:`A fine printed rule around the photo`},double:{label:`Double rule`,description:`Two printed rules, like a classic album keyline`},mat:{label:`Gallery mat`,description:`A wide printed mat with a thin frame line`},corners:{label:`Photo corners`,description:`Printed album corners holding the photo`},deckle:{label:`Deckled edge`,description:`A vintage print with a scalloped border`},plate:{label:`Plate mark`,description:`The photo sits in a plate pressed into the paper`},feather:{label:`Soft edge`,description:`The photo fades into the paper at its edges`},oval:{label:`Oval`,description:`An oval cameo inside a fine printed ring`},botanical:{label:`Botanical`,description:`Line-drawn sprigs of leaves around a fine rule`},watercolor:{label:`Watercolour`,description:`A wash of soft colour printed around a mat`},arcs:{label:`Rainbow arcs`,description:`A printed border of little rainbow arcs around a mat`},gilt:{label:`Gilt`,description:`An engraved border printed in gold ink`},woodgrain:{label:`Woodgrain`,description:`A printed woodgrain border around a mat`}},v={watercolor:3,arcs:3,gilt:2.6,woodgrain:3};function y(e){return e.coverText??e.coverDesign!==`image`}function b(e,t,n=`none`){return e.layout===`single`?`none`:t?.frame??x(e,n)}function x(e,t=`none`){return e.layout===`single`?`none`:e.frame??t}var S=class e{pageCount;faces;leaves;spread;stiffCovers;constructor(t,{spread:n,stiffCovers:r=!0}){this.pageCount=t;let i=Array.from({length:t},(e,t)=>({kind:`page`,index:t})),a=n&&t%2==1?[{kind:`blank`}]:[];this.spread=n,this.stiffCovers=r,this.faces=[{kind:`cover`},{kind:`title`},...i,...a,{kind:`colophon`},{kind:`back`}],this.leaves=n?e.pair(this.faces):this.faces.map(e=>({front:e,back:{kind:`blank`}}))}static pair(e){let t=[];for(let n=0;n<e.length;n+=2)t.push({front:e[n],back:e[n+1]});return t}get length(){return this.leaves.length}flippedForFace(e){return this.spread?Math.ceil(e/2):e}flippedForPage(e){return!e||e<1||e>this.pageCount?0:this.flippedForFace(e+1)}convert(e,t){return t.spread===this.spread?Math.min(e,this.length):this.spread?Math.ceil(e/2):Math.max(0,e*2-1)}isStiff(e){let{front:t,back:n}=this.leaves[e]??{};return this.stiffCovers&&(t?.kind===`cover`||t?.kind===`back`||n?.kind===`back`)}visible(e){return e<=0?[this.leaves[0].front]:e>=this.length?[this.spread?this.leaves[this.length-1].back:this.faces[this.faces.length-1]]:this.spread?[this.leaves[e-1].back,this.leaves[e].front]:[this.leaves[e].front]}stops(){if(!this.spread)return this.faces.map((e,t)=>({flipped:t,faces:[e]}));let e=[{flipped:0,faces:[this.faces[0]]}];for(let t=1;t<this.faces.length-1;t+=2)e.push({flipped:this.flippedForFace(t),faces:[this.faces[t],this.faces[t+1]]});return e.push({flipped:this.length,faces:[this.faces[this.faces.length-1]]}),e}label(t){if(t<=0)return`Cover`;if(t>=this.length)return`Back cover`;let n=e.pageNumbers(this.visible(t));return n.length===0?this.visible(t).some(e=>e.kind===`title`)?`Title page`:`The end`:n.length===1?`Page ${n[0]} of ${this.pageCount}`:`Pages ${n[0]}–${n[1]} of ${this.pageCount}`}static describe(t){let n=e.pageNumbers(t);return n.length?`page ${n.join(` and `)}`:t[0].kind===`back`?`the back cover`:t[0].kind===`cover`?`the cover`:`the title page`}static pageNumbers(e){return e.filter(e=>e.kind===`page`).map(e=>e.index+1)}};let C={edgeX:.15,edgeY:.08,peek:!0};var w=class{events;flipped;active=null;layout;width=0;height=0;grip;engine;press=null;grabStart={x:0,y:0};grabDir=1;grabReach=1;constructor(e,t,n={}){this.events=t,this.layout=e,this.flipped=Math.max(0,Math.min(e.length,n.flipped??0)),this.grip={...C,...n.grip},this.engine=new l({frame:e=>this.active&&this.events.frame(e,this.active),settle:e=>this.settle(e)},n)}get size(){return{width:this.width,height:this.height}}get atStart(){return this.flipped===0}get atEnd(){return this.flipped===this.layout.length}get settled(){return!this.active||this.active.peek}get label(){return this.layout.label(this.flipped)}configure({layout:e,width:t,height:n}){let r=e&&e!==this.layout,i=t!==void 0&&t!==this.width||n!==void 0&&n!==this.height;(r||i)&&(e&&r&&(this.flipped=e.convert(this.flipped,this.layout),this.layout=e),this.width=t??this.width,this.height=n??this.height,this.engine.cancel(),this.active=null,this.press=null,this.events.change())}go(e){let t=Math.max(0,Math.min(this.layout.length,e));if(t===this.flipped||this.width===0)return;if(this.engine.active&&!this.engine.peeking){this.engine.finish();return}if(this.engine.cancel(),this.events.flip(),Math.abs(t-this.flipped)!==1){this.active=null,this.flipped=t,this.events.change();return}let n=Math.min(t,this.flipped),r=this.layout.isStiff(n);this.begin({leaf:n,dir:t>this.flipped?1:-1,stiff:r,peek:!1}),this.engine.turn({x:this.width,y:r?this.height/2:this.height},this.size,r)}next(){this.go(this.flipped+1)}prev(){this.go(this.flipped-1)}hover(e){if(this.press||this.engine.dragging)return;let t=this.grip.peek?this.cornerAt(e):null;if(!t){this.engine.unpeek();return}let{width:n,height:r}=this,i=t.x>0?1:-1,a=i===1?this.flipped:this.flipped-1,o=this.layout.isStiff(a),s={x:n,y:o?r/2:t.y},c={x:i*(e.x-t.x),y:e.y-t.y},l=Math.hypot(c.x,c.y)||1,u=Math.max(n*.12,Math.min(n*.2,l*2)),d={x:n+c.x/l*u,y:o?s.y:t.y+c.y/l*u};(this.active?.leaf!==a||this.active.dir!==i)&&this.begin({leaf:a,dir:i,stiff:o,peek:!0}),this.engine.peek(s,d,this.size,o)}leave(){this.engine.unpeek()}down(e,t){this.press={at:e,id:t,grabbed:!1}}move(e,t){let n=this.press;if(!n||n.id!==t)return!1;let r=e.x-n.at.x,i=e.y-n.at.y;if(!n.grabbed){if(Math.hypot(r,i)<6||this.engine.active&&!this.engine.peeking)return!1;let e=(this.engine.peeking&&this.active?{...this.active,origin:this.engine.frame.origin,start:this.engine.frame.point}:null)??this.holdAt(n.at);if(!e)return!1;n.grabbed=!0,this.grabStart=e.start,this.grabDir=e.dir,this.grabReach=!this.layout.spread&&e.dir===-1?2:1,this.begin({leaf:e.leaf,dir:e.dir,stiff:e.stiff,peek:!1}),this.engine.grab(e.origin,e.start,this.size,e.stiff)}return this.engine.drag({x:this.grabStart.x+this.grabDir*this.grabReach*r,y:this.grabStart.y+i}),!0}up(e,t){let n=this.press;return!n||n.id!==t?null:(this.press=null,n.grabbed?(this.engine.release()&&this.events.flip(),`turn`):Math.abs(e.x-n.at.x)<8&&Math.abs(e.y-n.at.y)<8?`tap`:null)}cancel(){this.press=null,this.engine.dragging&&this.engine.release()}destroy(){this.engine.cancel()}begin(e){this.active=e,this.events.change()}settle(e){let t=this.active;if(!t)return;let n=t.dir===1?e:!e;this.flipped=n?t.leaf+1:t.leaf,this.active=null,this.events.change()}holdAt(e){let{width:t,height:n,layout:r}=this,i=r.spread?e.x>=0:e.x>=t*.3,a=i?this.flipped:this.flipped-1;if(a<0||a>=r.length)return null;let o=i?e:{x:r.spread?-e.x:t,y:e.y},s=o.x>t*(1-this.grip.edgeX),c=o.y<n*this.grip.edgeY,l=o.y>n*(1-this.grip.edgeY),u=r.isStiff(a),d={x:s||!(c||l)?t:Math.max(1,o.x),y:u?n/2:c?0:l?n:Math.max(0,Math.min(n,o.y))};return{leaf:a,dir:i?1:-1,origin:d,stiff:u,start:d}}cornerAt(e){let{width:t,height:n,layout:r,flipped:i}=this,a=Math.max(36,t*.12);return[...i<r.length?[{x:t,y:0},{x:t,y:n}]:[],...r.spread&&i>0?[{x:-t,y:0},{x:-t,y:n}]:[]].find(t=>Math.hypot(e.x-t.x,e.y-t.y)<a)??null}};let T={previous:`Previous page`,next:`Next page`,soundOn:`Turn page sound on`,soundOff:`Turn page sound off`,fullscreen:`Full screen`,exitFullscreen:`Exit full screen`,fullscreenPrompt:`Tap to open full screen`,byline:e=>`by ${e}`,rotateTitle:`Turn your phone sideways`,rotateBody:`Photo books open across two pages, just like the real thing.`,rotateFullscreen:`Open full screen`,rotateSingle:`Read one page at a time`},ee={viewBox:`0 0 40 40`,strokes:[{d:`M3 37V12Q3 3 12 3H37`,width:1},{d:`M7 37V16Q7 7 16 7H37`,width:.6},{d:`M12 12C16 10 20 12 20 16C20 19 17 20 15.5 18.5`,width:.8}],fills:[`M16 7C18 10 21 11 24 10C22 8 19 7 16 7Z`,`M7 16C10 18 11 21 10 24C8 22 7 19 7 16Z`,`M10.7 12a1.3 1.3 0 1 0 2.6 0a1.3 1.3 0 1 0 -2.6 0Z`]},E={x:50,y:124},te={viewBox:`0 0 100 133.33`,strokes:[...Array.from({length:17},(e,t)=>{let n=12+t/16*76,r=(E.y-88)/(E.y-16);return{d:`M${(E.x+(n-E.x)*r).toFixed(2)} 88L${n.toFixed(2)} 16`,width:.25,opacity:.3}}),{d:`M5 5H95V128.33H5Z`,width:.5},{d:`M11 8H89L92 11V122.33L89 125.33H11L8 122.33V11Z`,width:.35}],fills:[`M50 8.5L52 11L50 13.5L48 11Z`,`M50 119.83L52 122.33L50 124.83L48 122.33Z`]},D={min:1,max:4},O=(e,t,n)=>Math.min(n,Math.max(t,e)),k=(e,t)=>Math.round(e*10**t)/10**t;var A=class e{static CENTRE=new e;x;y;zoom;constructor(e=50,t=50,n=1){this.x=O(Number.isFinite(e)?e:50,0,100),this.y=O(Number.isFinite(t)?t:50,0,100),this.zoom=O(Number.isFinite(n)?n:1,D.min,D.max)}static of(t){return t?new e(t.x,t.y,t.zoom):e.CENTRE}get isCentred(){return this.x===50&&this.y===50&&this.zoom===1}rect(e,t){let n=t?.width&&t.height?t.width/t.height:e.width/(e.height||1),r=Math.max(e.width/n,e.height)*this.zoom,i=r*n;return{left:(e.width-i)*this.x/100,top:(e.height-r)*this.y/100,width:i,height:r}}pan(t,n,r,i){let{width:a,height:o}=this.rect(r,i),s=a-r.width,c=o-r.height,l=s>0?this.x-t/s*100:this.x,u=c>0?this.y-n/c*100:this.y;return new e(l,u,this.zoom)}zoomTo(t){return new e(this.x,this.y,t)}movable(e,t){let{width:n,height:r}=this.rect(e,t);return{x:n-e.width>.5,y:r-e.height>.5}}get css(){let e=`${this.x}% ${this.y}%`;return{objectPosition:e,transform:this.zoom===1?``:`scale(${this.zoom})`,transformOrigin:e}}equals(t){let n=e.of(t);return this.x===n.x&&this.y===n.y&&this.zoom===n.zoom}toJSON(){return{x:k(this.x,1),y:k(this.y,1),zoom:k(this.zoom,2)}}};function j(e,t={},...n){let r=document.createElement(e);for(let[e,n]of Object.entries(t))e===`style`?Object.assign(r.style,n):n!=null&&n!==!1&&r.setAttribute(e,n===!0?``:String(n));return ne(r,n),r}function ne(e,t){for(let n of t)n&&e.append(n)}function M(e){let t=document.createElement(`template`);return t.innerHTML=e.trim(),t.content.firstElementChild}async function N(e){try{!document.fullscreenElement&&e.requestFullscreen&&await e.requestFullscreen({navigationUI:`hide`})}catch{}try{let e=screen.orientation;if(e?.lock)return await e.lock(`landscape`),!0}catch{}return!1}function P(e){let t=matchMedia(`(orientation: portrait)`),n=matchMedia(`(pointer: coarse)`),r=()=>e({portrait:t.matches,touch:n.matches});return r(),t.addEventListener(`change`,r),n.addEventListener(`change`,r),window.addEventListener(`resize`,r),()=>{t.removeEventListener(`change`,r),n.removeEventListener(`change`,r),window.removeEventListener(`resize`,r)}}let re=[`tl`,`tr`,`bl`,`br`];function F(e,t){let n=e.strokes.map(({d:e,width:t,opacity:n=1})=>`<path d="${e}" fill="none" stroke="currentColor" stroke-width="${t}" stroke-opacity="${n}"/>`).join(``),r=e.fills.map(e=>`<path d="${e}" fill="currentColor"/>`).join(``);return`<svg viewBox="${e.viewBox}" class="${t}" preserveAspectRatio="none" aria-hidden="true">${n}${r}</svg>`}let ie=()=>re.map(e=>M(F(ee,`fb-fleuron is-${e}`))),I=(e,t,n)=>e.cover&&j(`div`,{class:t},n?W(e.cover,`fb-photo`):j(`div`,{class:`fb-placeholder`})),L=e=>j(`h1`,{class:`fb-serif fb-cover-title`},e.title),R=e=>e.kicker&&j(`p`,{class:`fb-kicker`},e.kicker),z=e=>e.subtitle&&j(`p`,{class:`fb-serif fb-italic fb-cover-subtitle`},e.subtitle);function ae(e,t){let n=y(e);switch(e.coverDesign??`classic`){case`gilt`:return[j(`div`,{class:`fb-cover-frame`}),...ie(),j(`div`,{class:`fb-cover-stack`},n&&R(e),n&&L(e),I(e,`fb-cover-photo is-oval`,t),n&&z(e))];case`window`:return[I(e,`fb-cover-window`,t)||j(`div`,{class:`fb-cover-window is-empty`},U()),n&&j(`div`,{class:`fb-cover-stamp`},R(e),L(e),z(e))];case`label`:return[e.cover&&j(`div`,{class:`fb-cover-print`},I(e,`fb-cover-print-photo`,t),...re.map(e=>j(`span`,{class:`fb-print-frame-corner is-${e}`,"aria-hidden":`true`}))),n&&j(`div`,{class:`fb-cover-label ${e.cover?``:`is-alone`}`},R(e),L(e),z(e))];case`deco`:return[M(F(te,`fb-cover-rays`)),j(`div`,{class:`fb-cover-stack is-deco`},n&&R(e),I(e,`fb-cover-photo is-arch`,t),n&&L(e),n&&z(e))];case`image`:return[e.cover&&j(`div`,{class:`fb-cover-art`},t?W(e.cover,`fb-photo`):j(`div`,{class:`fb-placeholder`})),e.cover&&j(`div`,{class:`fb-cover-tint`}),n&&j(`div`,{class:`fb-cover-caption ${e.cover?``:`is-plain`}`},R(e),L(e),z(e))];default:return[j(`div`,{class:`fb-cover-panel`},n&&R(e),n&&L(e),I(e,`fb-cover-photo`,t)||n&&z(e))]}}let B=(e,t,...n)=>j(`div`,{class:`fb-cover fb-cover-${e.theme??`classic`} fb-design-${e.coverDesign??`classic`}`},...n,j(`div`,{class:`fb-hinge is-${t}`}),j(`div`,{class:`fb-cover-finish`,"aria-hidden":`true`}));function oe(e,t=!0){return B(e,`left`,...ae(e,t))}function se(e){let t=e.coverDesign===`gilt`;return B(e,`right`,t&&j(`div`,{class:`fb-cover-frame`}),...t?ie():[],j(`div`,{class:`fb-imprint`},U(),e.imprint&&j(`p`,{class:`fb-kicker`,style:{marginTop:`2cqw`,fontSize:`2.4cqw`,opacity:`0.6`}},e.imprint)))}let V={pan:2,zoom:.1};var ce=class{el;options;crop;pointers=/* @__PURE__ */ new Map;gesture=null;abort=new AbortController;settle;constructor(e,t){this.el=e,this.options=t,this.crop=A.of(t.position);let n=this.abort.signal;e.classList.add(`fb-positioning`),e.tabIndex=0,e.setAttribute(`aria-roledescription`,`photo position`),e.setAttribute(`aria-keyshortcuts`,`ArrowLeft ArrowRight ArrowUp ArrowDown + -`),e.addEventListener(`pointerdown`,e=>this.down(e),{signal:n}),e.addEventListener(`pointermove`,e=>this.move(e),{signal:n}),e.addEventListener(`pointerup`,e=>this.up(e),{signal:n}),e.addEventListener(`pointercancel`,e=>this.up(e),{signal:n}),e.addEventListener(`wheel`,e=>this.wheel(e),{signal:n,passive:!1}),e.addEventListener(`keydown`,e=>this.key(e),{signal:n}),e.addEventListener(`dragstart`,e=>e.preventDefault(),{signal:n})}get position(){return this.crop.toJSON()}set position(e){this.crop=A.of(e),this.paint()}destroy(){clearTimeout(this.settle),this.abort.abort(),this.el.classList.remove(`fb-positioning`,`is-moving`),this.el.removeAttribute(`tabindex`),this.el.removeAttribute(`aria-roledescription`),this.el.removeAttribute(`aria-keyshortcuts`)}get image(){return this.el.querySelector(`img`)}get box(){let e=this.image;return{width:e?.clientWidth??0,height:e?.clientHeight??0}}get natural(){let e=this.image;return e?.naturalWidth?{width:e.naturalWidth,height:e.naturalHeight}:null}paint(){let e=this.image;e&&Object.assign(e.style,this.crop.css)}commit(){this.options.onChange(this.crop.toJSON())}spread(){let[e,t]=[...this.pointers.values()];return e&&t?Math.hypot(e.x-t.x,e.y-t.y):0}down(e){e.button===0&&this.image&&(e.stopPropagation(),this.el.setPointerCapture(e.pointerId),this.el.classList.add(`is-moving`),this.pointers.set(e.pointerId,{x:e.clientX,y:e.clientY}),this.gesture={crop:this.crop,spread:this.spread()})}move(e){let t=this.pointers.get(e.pointerId);if(t&&this.gesture){if(e.stopPropagation(),this.pointers.set(e.pointerId,{x:e.clientX,y:e.clientY}),this.pointers.size>1)this.gesture.spread&&(this.crop=this.crop.zoomTo(this.gesture.crop.zoom*this.spread()/this.gesture.spread));else{let n=this.el.getBoundingClientRect().width/(this.el.offsetWidth||1);this.crop=this.crop.pan((e.clientX-t.x)/n,(e.clientY-t.y)/n,this.box,this.natural)}this.paint()}}up(e){this.pointers.delete(e.pointerId)&&(e.stopPropagation(),this.gesture=this.pointers.size?{crop:this.crop,spread:this.spread()}:null,this.pointers.size||(this.el.classList.remove(`is-moving`),this.commit()))}wheel(e){e.preventDefault(),this.crop=this.crop.zoomTo(this.crop.zoom*Math.exp(-e.deltaY/500)),this.paint(),clearTimeout(this.settle),this.settle=setTimeout(()=>this.commit(),250)}key(e){let t={ArrowLeft:()=>new A(this.crop.x+V.pan,this.crop.y,this.crop.zoom),ArrowRight:()=>new A(this.crop.x-V.pan,this.crop.y,this.crop.zoom),ArrowUp:()=>new A(this.crop.x,this.crop.y+V.pan,this.crop.zoom),ArrowDown:()=>new A(this.crop.x,this.crop.y-V.pan,this.crop.zoom),"+":()=>this.crop.zoomTo(this.crop.zoom+V.zoom),"=":()=>this.crop.zoomTo(this.crop.zoom+V.zoom),"-":()=>this.crop.zoomTo(this.crop.zoom-V.zoom),0:()=>A.CENTRE}[e.key]?.();t&&(e.preventDefault(),e.stopPropagation(),this.crop=t,this.paint(),this.commit())}};let le=[14,24,34,44,54,64,74].map((e,t)=>({x:e,angle:t%2?38:-38,length:26-t*2})),H=e=>M(`<svg viewBox="0 0 100 40" class="fb-print-frame-sprig is-${e}" fill="none" stroke="currentColor" stroke-linecap="round" aria-hidden="true">
|
|
2
2
|
<path d="M4 20 Q 50 17, 92 20" stroke-width="1.2"/>
|
|
3
|
-
${
|
|
4
|
-
</svg>`),
|
|
3
|
+
${le.map(({x:e,angle:t,length:n})=>`<g transform="translate(${e} 20) rotate(${t}) scale(${n/20})"><path d="M0 0 C 5 -5, 14 -5, 20 0 C 14 5, 5 5, 0 0 Z" stroke-width="1"/><path d="M2 0 L 16 0" stroke-width="0.6"/></g>`).join(``)}
|
|
4
|
+
</svg>`),U=()=>M(`<svg viewBox="0 0 40 40" class="fb-ornament" aria-hidden="true">
|
|
5
5
|
<circle cx="20" cy="20" r="18.5" fill="none" stroke="currentColor" stroke-opacity="0.6"/>
|
|
6
6
|
<path d="M20 9 L 27 20 L 20 31 L 13 20 Z" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/>
|
|
7
7
|
<circle cx="20" cy="20" r="2.2" fill="currentColor"/>
|
|
8
|
-
</svg>`),H=(e,t)=>O(`img`,{src:e.src,alt:e.alt??``,draggable:`false`,decoding:`async`,width:e.width??void 0,height:e.height??void 0,class:t}),U=({x:e,y:t,width:n,height:r,rotate:i})=>({left:`${e}cqw`,top:`${t}cqw`,width:`${n}cqw`,height:`${r}cqw`,...i?{transform:`rotate(${i}deg)`}:{}}),W=e=>O(`div`,{class:`fb-page-surface ${e?`is-${e}`:``}`,"aria-hidden":`true`},O(`div`,{class:`fb-page-surface-grain`}),e&&O(`div`,{class:`fb-page-surface-gutter`}),e&&O(`div`,{class:`fb-page-surface-sheen`})),G=(e,t,n=!0,r=!0)=>O(`p`,{class:`fb-serif ${n?`fb-italic`:``} ${r?`fb-center`:``}`,style:t},e);function K(e,t,n=`cover`,r=!0,i=1){let a=e?.width&&e.height?e.width/e.height:4/3,o=O(`div`,{class:`fb-print-frame-box`,style:{fontSize:`${i}cqw`}}),s=O(`div`,{class:`fb-print-frame fb-frame-${t} ${n===`contain`?`is-contained`:``}`});if(n===`contain`&&s.style.setProperty(`--ratio`,String(a)),v[t]&&s.append(O(`span`,{class:`fb-print-frame-band`,"aria-hidden":`true`})),s.append(e&&r?H(e,`fb-print-photo fb-print-frame-photo`):O(`div`,{class:`fb-print-frame-photo fb-placeholder`})),t===`corners`)for(let e of[`tl`,`tr`,`bl`,`br`])s.append(O(`span`,{class:`fb-print-frame-corner is-${e}`,"aria-hidden":`true`}));return t===`botanical`&&s.append(B(`tl`),B(`br`)),o.append(s),o}function q(e,t,n={}){let{layout:r,caption:i,photos:a}=e,{number:o,side:s,showImages:c=!0,onSlotClick:l,activeSlot:u}=n,d=p[r],f=O(`div`,{class:`fb-paper fb-paper-${t.paper} fb-page`}),m=(n,r=``,i=!1,o)=>{let s=a[n]??null,d=b(e,s,t.frame),f=O(`div`,{class:[`fb-slot`,r,i&&`is-framed`,l&&`is-editable`,l&&u===n&&`is-active`].filter(Boolean).join(` `),role:l?`button`:void 0,"aria-label":l?`Photo slot ${n+1}`:void 0,style:o});return l&&f.addEventListener(`click`,()=>l(n)),f.append(s?K(s,d,i?`contain`:`cover`,c):O(`div`,{class:`fb-slot-empty`},l?`Add a photo`:``)),f},h=(e,...t)=>O(`div`,{class:`fb-column`,style:e},...t),g=i?G(i,{fontSize:`3.8cqw`}):null;switch(r){case`single`:f.append(m(0,`fb-bleed`)),i&&f.append(O(`div`,{class:`fb-caption-over`},i));break;case`framed`:f.append(h({padding:`10cqw 9cqw 6cqw`},O(`div`,{class:`fb-grow`},m(0,`fb-fill`,!0)),G(i??``,{marginTop:`5cqw`,fontSize:`4.2cqw`,minHeight:`6cqw`,lineHeight:`1.35`})));break;case`pair`:f.append(h({padding:`7cqw`,gap:`4cqw`},m(0,`fb-grow`),m(1,`fb-grow`),g));break;case`grid`:f.append(h({padding:`7cqw`,gap:`3cqw`},O(`div`,{class:`fb-grid fb-grow`,style:{gap:`3cqw`}},...[0,1,2,3].map(e=>m(e))),g));break;case`collage`:f.append(h({padding:`7cqw`,gap:`3cqw`},m(0,`fb-grow-large`),O(`div`,{class:`fb-row fb-grow`,style:{gap:`3cqw`}},m(1,`fb-grow`),m(2,`fb-grow`)),g));break;case`text`:f.append(O(`div`,{class:`fb-column fb-center`,style:{alignItems:`center`,justifyContent:`center`,padding:`14cqw 12cqw`}},O(`span`,{class:`fb-serif`,style:{fontSize:`18cqw`,lineHeight:`0.6`,opacity:`0.3`},"aria-hidden":`true`},`“`),G(i||(l?`Write something in the caption box below.`:``),{marginTop:`4cqw`,fontSize:`6cqw`,lineHeight:`1.4`,textWrap:`balance`},!0,!1)))}return d&&(f.append(...d.slots.map((e,t)=>m(t,`fb-placed ${d.loose?`is-loose`:``}`,!1,U(e)))),i&&f.append(O(`p`,{class:`fb-placed-caption fb-serif ${d.prose?`is-prose`:`fb-italic`}`,style:U(d.caption)},i))),o!==void 0&&r!==`single`&&f.append(O(`span`,{class:`fb-page-number`,style:s===`left`?{left:`0`}:{right:`0`}},String(o))),f.append(W(s)),f}function oe(e,t,n){let r=[e.author,e.year].filter(Boolean).join(` · `);return O(`div`,{class:`fb-paper fb-paper-${t.paper} fb-page fb-center`,style:{alignItems:`center`,justifyContent:`center`,padding:`12cqw`}},V(),O(`h2`,{class:`fb-serif`,style:{marginTop:`6cqw`,fontSize:`7.5cqw`,fontWeight:`400`,lineHeight:`1.1`,textWrap:`balance`}},e.title),e.subtitle&&G(e.subtitle,{marginTop:`4cqw`,fontSize:`4cqw`,lineHeight:`1.45`},!0,!1),O(`div`,{style:{marginTop:`7cqw`,height:`1px`,width:`20cqw`,background:`currentColor`,opacity:`0.3`}}),r&&O(`p`,{style:{marginTop:`5cqw`,fontSize:`2.6cqw`,letterSpacing:`0.2em`,textTransform:`uppercase`}},r),W(n))}function se(e,t,n){return O(`div`,{class:`fb-paper fb-paper-${t.paper} fb-page fb-center`,style:{alignItems:`center`,justifyContent:`flex-end`,padding:`12cqw`,opacity:`0.9`}},e.colophon&&G(e.colophon,{fontSize:`3.6cqw`},!0,!1),W(n))}function ce(e,t){return O(`div`,{class:`fb-paper fb-paper-${e.paper} fb-page`},W(t))}function J(e,t,n,r=!0){let i={paper:t.paper??`ivory`,frame:t.frame??`none`};switch(e.kind){case`cover`:return R(t);case`back`:return z(t);case`title`:return oe(t,i,n);case`colophon`:return se(t,i,n);case`blank`:return ce(i,n);case`page`:return q(t.pages[e.index],i,{number:r?e.index+1:void 0,side:n})}}let Y=null;function X(e=.18){try{Y??=new AudioContext;let t=Y.createBuffer(1,Math.floor(Y.sampleRate*.45),Y.sampleRate),n=t.getChannelData(0);for(let e=0;e<n.length;e++){let t=e/n.length,r=Math.sin(Math.PI*Math.min(1,t*2.2))*(1-t)**1.5;n[e]=(Math.random()*2-1)*r*(.6+.4*Math.sin(t*40))}let r=Y.createBufferSource(),i=Y.createBiquadFilter(),a=Y.createGain();r.buffer=t,i.type=`bandpass`,i.frequency.value=2200,i.Q.value=.6,a.gain.value=e,r.connect(i).connect(a).connect(Y.destination),r.start()}catch{}}let le=3/4,ue=`flipbook:sound`,de={layout:`auto`,controls:!0,autoHide:3200,thumbnails:!0,soundButton:!0,sound:!1,fullscreenButton:!0,fullscreenPrompt:!1,landscape:!0,keyboard:!0,stiffCovers:!0,shading:1,labels:T},fe=[{name:`under`,z:2},{name:`bend`,z:4},{name:`shadow`,z:5},{name:`curl`,z:7}],Z=(e,t=0,n=0)=>e.length?`polygon(${e.map(e=>`${(e.x+t).toFixed(2)}px ${(e.y+n).toFixed(2)}px`).join(`, `)})`:`polygon(0 0)`,Q=e=>`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="${e}" stroke-linecap="round" stroke-linejoin="round"/></svg>`,$={prev:Q(`M15 6l-6 6 6 6`),next:Q(`M9 6l6 6-6 6`),expand:Q(`M4 9V4h5M20 9V4h-5M4 15v5h5M20 15v5h-5`),collapse:Q(`M9 4v5H4M15 4v5h5M9 20v-5H4M15 20v-5h5`),soundOn:Q(`M4 10v4h3l5 4V6L7 10H4zM16 9a4 4 0 0 1 0 6M18.5 6.5a8 8 0 0 1 0 11`),soundOff:Q(`M4 10v4h3l5 4V6L7 10H4zM17 10l4 4M21 10l-4 4`),grid:`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><rect x="3" y="5" width="8" height="14" rx="1"/><rect x="13" y="5" width="8" height="14" rx="1"/></svg>`,phone:`<svg viewBox="0 0 64 64" class="fb-rotate-hint" aria-hidden="true"><rect x="20" y="6" width="24" height="52" rx="4" fill="none" stroke="currentColor" stroke-width="2.5"/><circle cx="32" cy="51" r="2" fill="currentColor"/></svg>`},pe=()=>{try{return localStorage.getItem(ue)}catch{return null}};var me=class e{container;root;reader;settings;layout;size={width:0,height:0};orientation={portrait:!1,touch:!1};portraitChoice=`ask`;promptAnswered=!1;soundOn;stripOpen=!1;chromeVisible=!0;hideTimer;lastPlace=``;rendering=!1;dirty=!1;faces=/* @__PURE__ */ new Map;overlays=/* @__PURE__ */ new Map;cleanups=[];els;constructor(t,n){this.container=t,this.settings=e.resolve(n),this.soundOn=this.settings.sound,this.layout=this.buildLayout(),this.reader=new w(this.layout,{frame:(e,t)=>this.paint(e,t),change:()=>this.render(),flip:()=>{this.soundOn&&X(),this.settings.onFlip?.()}},{flipped:this.layout.flippedForPage(this.settings.startPage),physics:n.physics,grip:n.grip}),this.root=O(`div`,{class:`fb-root`}),this.els=this.build(),t.append(this.root);let r=pe();this.settings.soundButton&&r&&(this.soundOn=r===`on`),this.listen(),this.poke(),this.render()}update(t){let n=t.book&&t.book!==this.settings.book;t.fullscreenPrompt&&!this.settings.fullscreenPrompt&&(this.promptAnswered=!1),this.settings=e.resolve({...this.settings,...t,labels:{...this.settings.labels,...t.labels}}),t.sound!==void 0&&(this.soundOn=t.sound),t.physics&&(this.reader.engine.physics={...this.reader.engine.physics,...t.physics}),t.grip&&(this.reader.grip={...this.reader.grip,...t.grip}),(n||t.stiffCovers!==void 0)&&this.clearFaces(),this.els.title.textContent=this.settings.book.title,this.poke(),this.render()}next(){this.reader.next()}prev(){this.reader.prev()}go(e){this.reader.go(e)}goToPage(e){this.reader.go(this.reader.layout.flippedForPage(e))}destroy(){this.reader.destroy(),clearTimeout(this.hideTimer),this.cleanups.forEach(e=>e()),this.root.remove()}static resolve(e){let t=Object.fromEntries(Object.entries(e).filter(([,e])=>e!==void 0));return{...de,...t,labels:{...T,...e.labels}}}get spread(){let{layout:e,landscape:t}=this.settings,n=t&&this.orientation.touch&&this.orientation.portrait,r=this.els?.stage.getBoundingClientRect(),i=!!r&&r.width>0&&r.width/r.height<.95;return e===`auto`?!(n&&this.portraitChoice===`single`)&&!i:e===`spread`}buildLayout(){return new S(this.settings.book.pages.length,{spread:this.spread,stiffCovers:this.settings.stiffCovers})}build(){let e=(e,t,n)=>{let r=O(`button`,{type:`button`,class:`fb-button`,"aria-label":e,title:e});return r.append(k(t)),r.addEventListener(`click`,n),r},t=O(`h1`,{class:`fb-title`},this.settings.book.title),n=O(`p`,{class:`fb-byline`}),r=e(``,$.soundOff,()=>this.toggleSound()),i=e(``,$.expand,()=>this.toggleFullscreen()),a=O(`div`,{class:`fb-bar-group`},r,i),o=O(`div`,{class:`fb-bar fb-bar-top`},O(`div`,{class:`fb-bar-group`},O(`div`,{class:`fb-heading`},t,n)),a),s=O(`div`,{class:`fb-book-shadow`}),c=O(`div`,{class:`fb-edge`}),l=O(`div`,{class:`fb-edge`}),u=O(`div`,{class:`fb-spine`}),d=O(`div`,{class:`fb-book`},s,c,l,u),f=O(`div`,{class:`fb-stage`},d),p=e(``,$.prev,()=>this.reader.prev()),m=e(``,$.next,()=>this.reader.next()),h=O(`button`,{type:`button`,class:`fb-pages-button`,"aria-live":`polite`}),g=O(`div`,{class:`fb-bar fb-bar-bottom`},p,h,m);h.addEventListener(`click`,()=>{this.settings.thumbnails&&(this.stripOpen=!this.stripOpen,this.render())});for(let{name:e,z:t}of fe){let n=O(`div`,{class:`fb-light`}),r=O(`div`,{class:`fb-overlay`,style:{zIndex:String(t)}},n);this.overlays.set(e,{clip:r,light:n})}return this.root.append(o,f,g),{top:o,title:t,byline:n,topRight:a,sound:r,fullscreen:i,stage:f,book:d,shadow:s,leftEdge:c,rightEdge:l,spine:u,bottom:g,prev:p,pages:h,next:m,strip:null,rotate:null,prompt:null}}listen(){let{stage:e}=this.els,t=(e,t,n)=>{e.addEventListener(t,n),this.cleanups.push(()=>e.removeEventListener(t,n))},n=new ResizeObserver(()=>this.render());n.observe(e),this.cleanups.push(()=>n.disconnect()),this.cleanups.push(ne(e=>{this.orientation=e,this.render()})),t(e,`pointerdown`,e=>{e.button===0&&this.reader.down(this.toBook(e),e.pointerId)}),t(e,`pointermove`,t=>{let n=this.toBook(t);this.poke(),t.pointerType===`mouse`&&this.reader.hover(n),this.reader.move(n,t.pointerId)&&!e.hasPointerCapture(t.pointerId)&&e.setPointerCapture(t.pointerId)}),t(e,`pointerup`,t=>{if(this.reader.up(this.toBook(t),t.pointerId)!==`tap`)return;let n=e.getBoundingClientRect(),r=t.clientX-n.left;r>n.width*2/3?this.reader.next():r<n.width/3&&this.reader.prev()}),t(e,`pointerleave`,()=>this.reader.leave()),t(e,`pointercancel`,()=>this.reader.cancel()),t(this.root,`keydown`,e=>this.settings.keyboard===!0&&this.onKey(e)),t(window,`keydown`,e=>this.els.prompt&&this.answerPrompt(e.key!==`Escape`)),t(window,`keydown`,e=>this.settings.keyboard===`global`&&this.onKey(e)),t(document,`fullscreenchange`,()=>{document.fullscreenElement===this.root&&(this.promptAnswered=!0),this.render()})}onKey(e){e.target instanceof HTMLInputElement||e.target instanceof HTMLTextAreaElement||e.target instanceof HTMLSelectElement||([`ArrowRight`,`PageDown`,` `].includes(e.key)?(e.preventDefault(),this.reader.next()):[`ArrowLeft`,`PageUp`].includes(e.key)?(e.preventDefault(),this.reader.prev()):e.key===`Home`?this.reader.go(0):e.key===`End`?this.reader.go(this.reader.layout.length):e.key===`Escape`&&this.stripOpen&&(this.stripOpen=!1,this.render()))}toBook(e){let t=this.els.book.getBoundingClientRect();return{x:e.clientX-(t.left+(this.layout.spread?t.width/2:0)),y:e.clientY-t.top}}poke(){let{autoHide:e}=this.settings;clearTimeout(this.hideTimer),this.chromeVisible||(this.chromeVisible=!0,this.renderChrome()),e!==!1&&(this.hideTimer=setTimeout(()=>{this.chromeVisible=!1,this.renderChrome()},e))}toggleSound(){this.soundOn=!this.soundOn,this.soundOn&&X();try{localStorage.setItem(ue,this.soundOn?`on`:`off`)}catch{}this.renderChrome()}async toggleFullscreen(){document.fullscreenElement?await document.exitFullscreen().catch(()=>{}):await A(this.root)}answerPrompt(e){this.promptAnswered=!0,this.render(),e&&A(this.root)}clearFaces(){this.faces.forEach(e=>e.remove()),this.faces.clear()}render(){if(this.rendering)this.dirty=!0;else{this.rendering=!0;do this.dirty=!1,this.renderOnce();while(this.dirty);this.rendering=!1}}renderOnce(){let{settings:t,reader:n,els:r}=this,{book:i,controls:a}=t,o=r.stage.getBoundingClientRect(),s=this.spread;(s!==this.layout.spread||i.pages.length!==this.layout.pageCount||t.stiffCovers!==this.layout.stiffCovers)&&(this.layout=this.buildLayout(),this.clearFaces());let c=s?2:1,l=!a||o.height<500?16:Math.min(150,o.height*.16),u=Math.max(0,o.width-(s?64:24)),d=Math.max(0,o.height-l),f=Math.floor(Math.min(d*le,u/c)),p=Math.floor(f/le);this.size={width:f,height:p},n.configure({layout:this.layout,width:f,height:p});let{flipped:m,active:h,settled:g,atStart:_,atEnd:v}=n,y=this.layout.leaves,b=0;s&&_&&g&&(b=-f/2),s&&v&&g&&(b=f/2),this.root.style.background=t.background??e.stageTint(i),this.root.tabIndex=t.keyboard===!0?0:-1,r.book.hidden=f<=0,Object.assign(r.book.style,{width:`${f*c}px`,height:`${p}px`,transform:`translateX(${b}px)`,cursor:n.engine.dragging?`grabbing`:`grab`,clipPath:s?``:`inset(-50% -50% -50% 0)`}),Object.assign(r.shadow.style,{left:s&&_&&g?`52%`:`2%`,right:s&&v&&g?`52%`:`2%`});let x=s&&!_&&!v,S=e=>Math.min(10,Math.max(0,e)*.8),C=x&&m>1?S(m-1):0,w=(x||s&&_)&&m<y.length-1?S(y.length-m-1):0;Object.assign(r.leftEdge.style,{display:C?``:`none`,left:`${-C}px`,width:`${C}px`}),Object.assign(r.rightEdge.style,{display:w?``:`none`,right:`${-w}px`,width:`${w}px`}),r.spine.style.left=`${s?f:0}px`,this.renderFaces(h,m,f,p),this.renderOverlays(h,f,p),this.renderChrome(),this.renderStrip(),this.renderRotatePrompt(),this.renderFullscreenPrompt();let T=`${s}:${m}`;T!==this.lastPlace&&(this.lastPlace=T,t.onPageChange?.({flipped:m,label:n.label,faces:this.layout.visible(m)}))}renderFaces(e,t,n,r){let i=this.layout.leaves,a=e?.leaf??null,o=e?.dir??1,s=a===null?t:a+1,c=a===null?t-1:a-1,l=/* @__PURE__ */ new Set;for(let u=Math.max(0,t-2);u<=Math.min(i.length-1,t+1);u++)for(let t of[`front`,`back`]){let d=`${u}:${t}`,f=u===a?t===`front`==(o===1)?`turning`:`flap`:t===`front`?u===s?`right`:`spare`:u===c&&this.layout.spread?`left`:`spare`,p=f===`left`||f===`turning`&&o===-1&&!e?.stiff,m=this.faces.get(d);m||(m=O(`div`,{class:`fb-face`}),m.append(J(t===`front`?i[u].front:i[u].back,this.settings.book,t===`front`?`right`:`left`)),this.faces.set(d,m),this.els.spine.append(m)),l.add(d),Object.assign(m.style,{left:`${p?-n:0}px`,width:`${n}px`,height:`${r}px`,zIndex:String({right:1,left:1,turning:3,flap:6,spare:0}[f]),visibility:f===`spare`?`hidden`:`visible`}),m.setAttribute(`aria-hidden`,String(f!==`left`&&f!==`right`)),u!==a&&Object.assign(m.style,{transform:``,transformOrigin:``,clipPath:``})}for(let[e,t]of this.faces)l.has(e)||(t.remove(),this.faces.delete(e));e&&this.paint(this.reader.engine.frame,e)}renderOverlays(e,t,n){let r=!!e&&!e.stiff,i=e=>`${(t*e).toFixed(1)}px`,a={under:`linear-gradient(to right, rgb(0 0 0 / 0.42), rgb(0 0 0 / 0.2) ${i(.05)}, rgb(0 0 0 / 0.06) ${i(.16)}, transparent ${i(.3)})`,bend:`linear-gradient(to right, rgb(0 0 0 / 0.16), rgb(0 0 0 / 0.04) ${i(.05)}, transparent ${i(.1)})`,shadow:`rgb(0 0 0 / 0.55)`,curl:`linear-gradient(to right, rgb(0 0 0 / 0.3), rgb(0 0 0 / 0.1) ${i(.035)}, rgb(255 255 255 / 0.2) ${i(.11)}, rgb(255 255 255 / 0.06) ${i(.25)}, rgb(0 0 0 / 0.05) ${i(.6)}, rgb(0 0 0 / 0.12) ${i(1)})`};for(let[e,{clip:i,light:o}]of this.overlays){if(!r){i.remove();continue}let s=e===`shadow`;Object.assign(i.style,{left:`${-t}px`,top:`${-n}px`,width:`${t*2}px`,height:`${n*3}px`,filter:s?`blur(${Math.max(3,t*.012)}px)`:``}),Object.assign(o.style,s?{inset:`0`,background:a[e]}:{width:`${t*3}px`,height:`${n*4}px`,background:a[e]}),i.isConnected||this.els.spine.append(i)}r&&e&&this.paint(this.reader.engine.frame,e)}paint(e,t){let{width:n,height:r}=this.size,i=t.dir,o=this.faces.get(`${t.leaf}:front`),s=this.faces.get(`${t.leaf}:back`);if(e.stiff){let t=`perspective(${Math.max(1800,n*5)}px) rotateY(${a.hinge(e.origin,e.point,i)}deg)`;o&&Object.assign(o.style,{transformOrigin:`0 50%`,transform:t,clipPath:``}),s&&Object.assign(s.style,{transformOrigin:`0 50%`,transform:`${t} translateX(${n}px) rotateY(180deg)`,clipPath:``});return}let c=i===1?o:s,l=i===1?s:o,u=new a(n,r,e.origin,e.point),d=u.shade*this.settings.shading;c&&Object.assign(c.style,{transformOrigin:``,transform:``,clipPath:Z(u.flat.map(e=>({x:i===1?e.x:n-e.x,y:e.y})))}),l&&Object.assign(l.style,{transformOrigin:`0 0`,transform:u.flapMatrix(i),clipPath:Z(u.folded.map(e=>({x:i===1?n-e.x:e.x,y:e.y})))});let f=(e,t,a,o)=>{let s=this.overlays.get(e);if(!s)return;let c=Z(t.map(e=>({x:i*e.x,y:e.y})),n,r);if(s.clip.style.opacity=String(o),e===`shadow`){s.light.style.clipPath=c;return}let l=Math.atan2(a*u.normal.y,i*a*u.normal.x);s.clip.style.clipPath=c,s.light.style.transform=`translate(${i*u.center.x+n}px, ${u.center.y+r}px) rotate(${l}rad) translateY(-50%)`};f(`under`,u.folded,1,d),f(`bend`,u.flat,-1,d),f(`shadow`,u.flap,-1,d*.6),f(`curl`,u.flap,-1,d)}renderChrome(){let{els:e,settings:t,reader:n}=this,{labels:r}=t,i=this.chromeVisible||this.stripOpen,a=document.fullscreenElement===this.root,o=(e,t,n)=>{e.setAttribute(`aria-label`,t),e.title=t,n&&e.replaceChildren(k(n))};e.top.hidden=!t.controls,e.bottom.hidden=!t.controls,e.top.classList.toggle(`is-hidden`,!i),e.bottom.classList.toggle(`is-hidden`,!i),e.title.textContent=t.book.title,e.byline.textContent=t.book.author?r.byline(t.book.author):``,e.byline.hidden=!t.book.author,e.sound.hidden=!t.soundButton,e.fullscreen.hidden=!t.fullscreenButton,o(e.sound,this.soundOn?r.soundOff:r.soundOn,this.soundOn?$.soundOn:$.soundOff),o(e.fullscreen,a?r.exitFullscreen:r.fullscreen,a?$.collapse:$.expand),o(e.prev,r.previous),o(e.next,r.next),e.prev.disabled=n.atStart,e.next.disabled=n.atEnd,e.pages.replaceChildren(...t.thumbnails?[k($.grid)]:[],n.label),e.pages.setAttribute(`aria-expanded`,String(this.stripOpen))}renderStrip(){let e=this.stripOpen&&this.settings.thumbnails,{book:t}=this.settings;if(!e){this.els.strip?.remove(),this.els.strip=null;return}let n=O(`div`,{class:`fb-strip-list`});for(let e of this.layout.stops()){let r=O(`button`,{type:`button`,class:`fb-strip-stop`,"aria-current":String(e.flipped===this.reader.flipped),"aria-label":`Go to ${S.describe(e.faces)}`});for(let n of e.faces)r.append(O(`div`,{class:`fb-strip-face`},J(n,t,void 0,!1)));r.addEventListener(`click`,()=>{this.stripOpen=!1,this.reader.go(e.flipped),this.render()}),n.append(r)}let r=O(`div`,{class:`fb-strip`},n);this.els.strip?.remove(),this.els.strip=r,this.els.bottom.before(r),n.querySelector(`[aria-current='true']`)?.scrollIntoView({inline:`center`,block:`nearest`})}renderRotatePrompt(){let{landscape:e,layout:t,labels:n}=this.settings;if(!(e&&t!==`single`&&this.orientation.touch&&this.orientation.portrait&&this.portraitChoice===`ask`)){this.els.rotate?.remove(),this.els.rotate=null;return}if(this.els.rotate)return;let r=O(`button`,{type:`button`,class:`fb-rotate-primary`},n.rotateFullscreen),i=O(`button`,{type:`button`,class:`fb-rotate-secondary`},n.rotateSingle);r.addEventListener(`click`,()=>A(this.root)),i.addEventListener(`click`,()=>{this.portraitChoice=`single`,this.render()}),this.els.rotate=O(`div`,{class:`fb-rotate`},k($.phone),O(`div`,{},O(`p`,{class:`fb-rotate-title`},n.rotateTitle),O(`p`,{class:`fb-rotate-body`},n.rotateBody)),r,i),this.root.append(this.els.rotate)}renderFullscreenPrompt(){let{fullscreenPrompt:e,labels:t}=this.settings;if(!e||this.promptAnswered||!document.fullscreenEnabled||document.fullscreenElement||this.els.rotate){this.els.prompt?.remove(),this.els.prompt=null;return}if(this.els.prompt)return;let n=O(`button`,{type:`button`,class:`fb-fullscreen-prompt`}),r=O(`span`,{class:`fb-fullscreen-prompt-label`},t.fullscreenPrompt);r.prepend(k($.expand)),n.append(r),n.addEventListener(`click`,()=>this.answerPrompt(!0)),this.els.prompt=n,this.root.append(n)}static stageTint(e){let[t,n,r]=m[e.theme??`classic`].stage;return`radial-gradient(ellipse at 50% 40%, ${t} 0%, ${n} 55%, ${r} 100%)`}};return e.Affine=i,e.BANDED_FRAMES=v,e.BookLayout=S,e.COVER_DESIGNS=h,e.COVER_THEMES=m,e.DECO=D,e.DEFAULT_GRIP=C,e.DEFAULT_LABELS=T,e.DEFAULT_PHYSICS=o,e.FLEURON=ee,e.FRAME_STYLES=_,e.Flipbook=me,e.FlipbookReader=w,e.Fold=a,e.PAGE_LAYOUTS=u,e.PAPER_COLORS=g,e.PLACED_LAYOUTS=p,e.PageTurn=l,e.coverArtMarkup=j,e.coverHasText=y,e.dot=n,e.enterLandscape=A,e.frameOf=x,e.length=r,e.photoFrame=b,e.playPageTurn=X,e.printedPhoto=K,e.renderBackCover=z,e.renderBlank=ce,e.renderColophon=se,e.renderCover=R,e.renderFace=J,e.renderPage=q,e.renderTitlePage=oe,e.sub=t,e.watchOrientation=ne,e})({});
|
|
8
|
+
</svg>`),W=(e,t)=>j(`img`,{src:e.src,alt:e.alt??``,draggable:`false`,decoding:`async`,width:e.width??void 0,height:e.height??void 0,class:t,style:A.of(e.position).css}),ue=({x:e,y:t,width:n,height:r,rotate:i})=>({left:`${e}cqw`,top:`${t}cqw`,width:`${n}cqw`,height:`${r}cqw`,...i?{transform:`rotate(${i}deg)`}:{}}),G=e=>j(`div`,{class:`fb-page-surface ${e?`is-${e}`:``}`,"aria-hidden":`true`},j(`div`,{class:`fb-page-surface-grain`}),e&&j(`div`,{class:`fb-page-surface-gutter`}),e&&j(`div`,{class:`fb-page-surface-sheen`})),K=(e,t,n=!0,r=!0)=>j(`p`,{class:`fb-serif ${n?`fb-italic`:``} ${r?`fb-center`:``}`,style:t},e);function q(e,t,n=`cover`,r=!0,i=1){let a=e?.width&&e.height?e.width/e.height:4/3,o=j(`div`,{class:`fb-print-frame-box`,style:{fontSize:`${i}cqw`}}),s=j(`div`,{class:`fb-print-frame fb-frame-${t} ${n===`contain`?`is-contained`:``}`});if(n===`contain`&&s.style.setProperty(`--ratio`,String(a)),v[t]&&s.append(j(`span`,{class:`fb-print-frame-band`,"aria-hidden":`true`})),s.append(e&&r?j(`div`,{class:`fb-print-photo fb-print-frame-photo`},W(e,`fb-photo`)):j(`div`,{class:`fb-print-frame-photo fb-placeholder`})),t===`corners`)for(let e of[`tl`,`tr`,`bl`,`br`])s.append(j(`span`,{class:`fb-print-frame-corner is-${e}`,"aria-hidden":`true`}));return t===`botanical`&&s.append(H(`tl`),H(`br`)),o.append(s),o}function de(e,t,n={}){let{layout:r,caption:i,photos:a}=e,{number:o,side:s,showImages:c=!0,onSlotClick:l,activeSlot:u,onPhotoPosition:d}=n,f=p[r],m=j(`div`,{class:`fb-paper fb-paper-${t.paper} fb-page`}),h=(n,r=``,i=!1,o)=>{let s=a[n]??null,f=b(e,s,t.frame),p=j(`div`,{class:[`fb-slot`,r,i&&`is-framed`,l&&`is-editable`,l&&u===n&&`is-active`].filter(Boolean).join(` `),role:l?`button`:void 0,"aria-label":l?`Photo slot ${n+1}`:void 0,style:o});return l&&p.addEventListener(`click`,()=>l(n)),p.append(s?q(s,f,i?`contain`:`cover`,c):j(`div`,{class:`fb-slot-empty`},l?`Add a photo`:``)),s&&c&&d&&u===n&&new ce(p,{position:s.position,onChange:e=>d(n,e)}),p},g=(e,...t)=>j(`div`,{class:`fb-column`,style:e},...t),_=i?K(i,{fontSize:`3.8cqw`}):null;switch(r){case`single`:m.append(h(0,`fb-bleed`)),i&&m.append(j(`div`,{class:`fb-caption-over`},i));break;case`framed`:m.append(g({padding:`10cqw 9cqw 6cqw`},j(`div`,{class:`fb-grow`},h(0,`fb-fill`,!0)),K(i??``,{marginTop:`5cqw`,fontSize:`4.2cqw`,minHeight:`6cqw`,lineHeight:`1.35`})));break;case`pair`:m.append(g({padding:`7cqw`,gap:`4cqw`},h(0,`fb-grow`),h(1,`fb-grow`),_));break;case`grid`:m.append(g({padding:`7cqw`,gap:`3cqw`},j(`div`,{class:`fb-grid fb-grow`,style:{gap:`3cqw`}},...[0,1,2,3].map(e=>h(e))),_));break;case`collage`:m.append(g({padding:`7cqw`,gap:`3cqw`},h(0,`fb-grow-large`),j(`div`,{class:`fb-row fb-grow`,style:{gap:`3cqw`}},h(1,`fb-grow`),h(2,`fb-grow`)),_));break;case`text`:m.append(j(`div`,{class:`fb-column fb-center`,style:{alignItems:`center`,justifyContent:`center`,padding:`14cqw 12cqw`}},j(`span`,{class:`fb-serif`,style:{fontSize:`18cqw`,lineHeight:`0.6`,opacity:`0.3`},"aria-hidden":`true`},`“`),K(i||(l?`Write something in the caption box below.`:``),{marginTop:`4cqw`,fontSize:`6cqw`,lineHeight:`1.4`,textWrap:`balance`},!0,!1)))}return f&&(m.append(...f.slots.map((e,t)=>h(t,`fb-placed ${f.loose?`is-loose`:``}`,!1,ue(e)))),i&&m.append(j(`p`,{class:`fb-placed-caption fb-serif ${f.prose?`is-prose`:`fb-italic`}`,style:ue(f.caption)},i))),o!==void 0&&r!==`single`&&m.append(j(`span`,{class:`fb-page-number`,style:s===`left`?{left:`0`}:{right:`0`}},String(o))),m.append(G(s)),m}function fe(e,t,n){let r=[e.author,e.year].filter(Boolean).join(` · `);return j(`div`,{class:`fb-paper fb-paper-${t.paper} fb-page fb-center`,style:{alignItems:`center`,justifyContent:`center`,padding:`12cqw`}},U(),j(`h2`,{class:`fb-serif`,style:{marginTop:`6cqw`,fontSize:`7.5cqw`,fontWeight:`400`,lineHeight:`1.1`,textWrap:`balance`}},e.title),e.subtitle&&K(e.subtitle,{marginTop:`4cqw`,fontSize:`4cqw`,lineHeight:`1.45`},!0,!1),j(`div`,{style:{marginTop:`7cqw`,height:`1px`,width:`20cqw`,background:`currentColor`,opacity:`0.3`}}),r&&j(`p`,{style:{marginTop:`5cqw`,fontSize:`2.6cqw`,letterSpacing:`0.2em`,textTransform:`uppercase`}},r),G(n))}function pe(e,t,n){return j(`div`,{class:`fb-paper fb-paper-${t.paper} fb-page fb-center`,style:{alignItems:`center`,justifyContent:`flex-end`,padding:`12cqw`,opacity:`0.9`}},e.colophon&&K(e.colophon,{fontSize:`3.6cqw`},!0,!1),G(n))}function me(e,t){return j(`div`,{class:`fb-paper fb-paper-${e.paper} fb-page`},G(t))}function J(e,t,n,r=!0){let i={paper:t.paper??`ivory`,frame:t.frame??`none`};switch(e.kind){case`cover`:return oe(t);case`back`:return se(t);case`title`:return fe(t,i,n);case`colophon`:return pe(t,i,n);case`blank`:return me(i,n);case`page`:return de(t.pages[e.index],i,{number:r?e.index+1:void 0,side:n})}}let Y=null;function X(e=.18){try{Y??=new AudioContext;let t=Y.createBuffer(1,Math.floor(Y.sampleRate*.45),Y.sampleRate),n=t.getChannelData(0);for(let e=0;e<n.length;e++){let t=e/n.length,r=Math.sin(Math.PI*Math.min(1,t*2.2))*(1-t)**1.5;n[e]=(Math.random()*2-1)*r*(.6+.4*Math.sin(t*40))}let r=Y.createBufferSource(),i=Y.createBiquadFilter(),a=Y.createGain();r.buffer=t,i.type=`bandpass`,i.frequency.value=2200,i.Q.value=.6,a.gain.value=e,r.connect(i).connect(a).connect(Y.destination),r.start()}catch{}}let he=3/4,ge=`flipbook:sound`,_e={layout:`auto`,controls:!0,autoHide:3200,thumbnails:!0,soundButton:!0,sound:!1,fullscreenButton:!0,fullscreenPrompt:!1,landscape:!0,keyboard:!0,stiffCovers:!0,shading:1,labels:T},ve=[{name:`under`,z:2},{name:`bend`,z:4},{name:`shadow`,z:5},{name:`curl`,z:7}],Z=(e,t=0,n=0)=>e.length?`polygon(${e.map(e=>`${(e.x+t).toFixed(2)}px ${(e.y+n).toFixed(2)}px`).join(`, `)})`:`polygon(0 0)`,Q=e=>`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="${e}" stroke-linecap="round" stroke-linejoin="round"/></svg>`,$={prev:Q(`M15 6l-6 6 6 6`),next:Q(`M9 6l6 6-6 6`),expand:Q(`M4 9V4h5M20 9V4h-5M4 15v5h5M20 15v5h-5`),collapse:Q(`M9 4v5H4M15 4v5h5M9 20v-5H4M15 20v-5h5`),soundOn:Q(`M4 10v4h3l5 4V6L7 10H4zM16 9a4 4 0 0 1 0 6M18.5 6.5a8 8 0 0 1 0 11`),soundOff:Q(`M4 10v4h3l5 4V6L7 10H4zM17 10l4 4M21 10l-4 4`),grid:`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><rect x="3" y="5" width="8" height="14" rx="1"/><rect x="13" y="5" width="8" height="14" rx="1"/></svg>`,phone:`<svg viewBox="0 0 64 64" class="fb-rotate-hint" aria-hidden="true"><rect x="20" y="6" width="24" height="52" rx="4" fill="none" stroke="currentColor" stroke-width="2.5"/><circle cx="32" cy="51" r="2" fill="currentColor"/></svg>`},ye=()=>{try{return localStorage.getItem(ge)}catch{return null}};var be=class e{container;root;reader;settings;layout;size={width:0,height:0};orientation={portrait:!1,touch:!1};portraitChoice=`ask`;promptAnswered=!1;soundOn;stripOpen=!1;chromeVisible=!0;hideTimer;lastPlace=``;rendering=!1;dirty=!1;faces=/* @__PURE__ */ new Map;overlays=/* @__PURE__ */ new Map;cleanups=[];els;constructor(t,n){this.container=t,this.settings=e.resolve(n),this.soundOn=this.settings.sound,this.layout=this.buildLayout(),this.reader=new w(this.layout,{frame:(e,t)=>this.paint(e,t),change:()=>this.render(),flip:()=>{this.soundOn&&X(),this.settings.onFlip?.()}},{flipped:this.layout.flippedForPage(this.settings.startPage),physics:n.physics,grip:n.grip}),this.root=j(`div`,{class:`fb-root`}),this.els=this.build(),t.append(this.root);let r=ye();this.settings.soundButton&&r&&(this.soundOn=r===`on`),this.listen(),this.poke(),this.render()}update(t){let n=t.book&&t.book!==this.settings.book;t.fullscreenPrompt&&!this.settings.fullscreenPrompt&&(this.promptAnswered=!1),this.settings=e.resolve({...this.settings,...t,labels:{...this.settings.labels,...t.labels}}),t.sound!==void 0&&(this.soundOn=t.sound),t.physics&&(this.reader.engine.physics={...this.reader.engine.physics,...t.physics}),t.grip&&(this.reader.grip={...this.reader.grip,...t.grip}),(n||t.stiffCovers!==void 0)&&this.clearFaces(),this.els.title.textContent=this.settings.book.title,this.poke(),this.render()}next(){this.reader.next()}prev(){this.reader.prev()}go(e){this.reader.go(e)}goToPage(e){this.reader.go(this.reader.layout.flippedForPage(e))}destroy(){this.reader.destroy(),clearTimeout(this.hideTimer),this.cleanups.forEach(e=>e()),this.root.remove()}static resolve(e){let t=Object.fromEntries(Object.entries(e).filter(([,e])=>e!==void 0));return{..._e,...t,labels:{...T,...e.labels}}}get spread(){let{layout:e,landscape:t}=this.settings,n=t&&this.orientation.touch&&this.orientation.portrait,r=this.els?.stage.getBoundingClientRect(),i=!!r&&r.width>0&&r.width/r.height<.95;return e===`auto`?!(n&&this.portraitChoice===`single`)&&!i:e===`spread`}buildLayout(){return new S(this.settings.book.pages.length,{spread:this.spread,stiffCovers:this.settings.stiffCovers})}build(){let e=(e,t,n)=>{let r=j(`button`,{type:`button`,class:`fb-button`,"aria-label":e,title:e});return r.append(M(t)),r.addEventListener(`click`,n),r},t=j(`h1`,{class:`fb-title`},this.settings.book.title),n=j(`p`,{class:`fb-byline`}),r=e(``,$.soundOff,()=>this.toggleSound()),i=e(``,$.expand,()=>this.toggleFullscreen()),a=j(`div`,{class:`fb-bar-group`},r,i),o=j(`div`,{class:`fb-bar fb-bar-top`},j(`div`,{class:`fb-bar-group`},j(`div`,{class:`fb-heading`},t,n)),a),s=j(`div`,{class:`fb-book-shadow`}),c=j(`div`,{class:`fb-edge`}),l=j(`div`,{class:`fb-edge`}),u=j(`div`,{class:`fb-spine`}),d=j(`div`,{class:`fb-book`},s,c,l,u),f=j(`div`,{class:`fb-stage`},d),p=e(``,$.prev,()=>this.reader.prev()),m=e(``,$.next,()=>this.reader.next()),h=j(`button`,{type:`button`,class:`fb-pages-button`,"aria-live":`polite`}),g=j(`div`,{class:`fb-bar fb-bar-bottom`},p,h,m);h.addEventListener(`click`,()=>{this.settings.thumbnails&&(this.stripOpen=!this.stripOpen,this.render())});for(let{name:e,z:t}of ve){let n=j(`div`,{class:`fb-light`}),r=j(`div`,{class:`fb-overlay`,style:{zIndex:String(t)}},n);this.overlays.set(e,{clip:r,light:n})}return this.root.append(o,f,g),{top:o,title:t,byline:n,topRight:a,sound:r,fullscreen:i,stage:f,book:d,shadow:s,leftEdge:c,rightEdge:l,spine:u,bottom:g,prev:p,pages:h,next:m,strip:null,rotate:null,prompt:null}}listen(){let{stage:e}=this.els,t=(e,t,n)=>{e.addEventListener(t,n),this.cleanups.push(()=>e.removeEventListener(t,n))},n=new ResizeObserver(()=>this.render());n.observe(e),this.cleanups.push(()=>n.disconnect()),this.cleanups.push(P(e=>{this.orientation=e,this.render()})),t(e,`pointerdown`,e=>{e.button===0&&this.reader.down(this.toBook(e),e.pointerId)}),t(e,`pointermove`,t=>{let n=this.toBook(t);this.poke(),t.pointerType===`mouse`&&this.reader.hover(n),this.reader.move(n,t.pointerId)&&!e.hasPointerCapture(t.pointerId)&&e.setPointerCapture(t.pointerId)}),t(e,`pointerup`,t=>{if(this.reader.up(this.toBook(t),t.pointerId)!==`tap`)return;let n=e.getBoundingClientRect(),r=t.clientX-n.left;r>n.width*2/3?this.reader.next():r<n.width/3&&this.reader.prev()}),t(e,`pointerleave`,()=>this.reader.leave()),t(e,`pointercancel`,()=>this.reader.cancel()),t(this.root,`keydown`,e=>this.settings.keyboard===!0&&this.onKey(e)),t(window,`keydown`,e=>this.els.prompt&&this.answerPrompt(e.key!==`Escape`)),t(window,`keydown`,e=>this.settings.keyboard===`global`&&this.onKey(e)),t(document,`fullscreenchange`,()=>{document.fullscreenElement===this.root&&(this.promptAnswered=!0),this.render()})}onKey(e){e.target instanceof HTMLInputElement||e.target instanceof HTMLTextAreaElement||e.target instanceof HTMLSelectElement||([`ArrowRight`,`PageDown`,` `].includes(e.key)?(e.preventDefault(),this.reader.next()):[`ArrowLeft`,`PageUp`].includes(e.key)?(e.preventDefault(),this.reader.prev()):e.key===`Home`?this.reader.go(0):e.key===`End`?this.reader.go(this.reader.layout.length):e.key===`Escape`&&this.stripOpen&&(this.stripOpen=!1,this.render()))}toBook(e){let t=this.els.book.getBoundingClientRect();return{x:e.clientX-(t.left+(this.layout.spread?t.width/2:0)),y:e.clientY-t.top}}poke(){let{autoHide:e}=this.settings;clearTimeout(this.hideTimer),this.chromeVisible||(this.chromeVisible=!0,this.renderChrome()),e!==!1&&(this.hideTimer=setTimeout(()=>{this.chromeVisible=!1,this.renderChrome()},e))}toggleSound(){this.soundOn=!this.soundOn,this.soundOn&&X();try{localStorage.setItem(ge,this.soundOn?`on`:`off`)}catch{}this.renderChrome()}async toggleFullscreen(){document.fullscreenElement?await document.exitFullscreen().catch(()=>{}):await N(this.root)}answerPrompt(e){this.promptAnswered=!0,this.render(),e&&N(this.root)}clearFaces(){this.faces.forEach(e=>e.remove()),this.faces.clear()}render(){if(this.rendering)this.dirty=!0;else{this.rendering=!0;do this.dirty=!1,this.renderOnce();while(this.dirty);this.rendering=!1}}renderOnce(){let{settings:t,reader:n,els:r}=this,{book:i,controls:a}=t,o=r.stage.getBoundingClientRect(),s=this.spread;(s!==this.layout.spread||i.pages.length!==this.layout.pageCount||t.stiffCovers!==this.layout.stiffCovers)&&(this.layout=this.buildLayout(),this.clearFaces());let c=s?2:1,l=!a||o.height<500?16:Math.min(150,o.height*.16),u=Math.max(0,o.width-(s?64:24)),d=Math.max(0,o.height-l),f=Math.floor(Math.min(d*he,u/c)),p=Math.floor(f/he);this.size={width:f,height:p},n.configure({layout:this.layout,width:f,height:p});let{flipped:m,active:h,settled:g,atStart:_,atEnd:v}=n,y=this.layout.leaves,b=0;s&&_&&g&&(b=-f/2),s&&v&&g&&(b=f/2),this.root.style.background=t.background??e.stageTint(i),this.root.tabIndex=t.keyboard===!0?0:-1,r.book.hidden=f<=0,Object.assign(r.book.style,{width:`${f*c}px`,height:`${p}px`,transform:`translateX(${b}px)`,cursor:n.engine.dragging?`grabbing`:`grab`,clipPath:s?``:`inset(-50% -50% -50% 0)`}),Object.assign(r.shadow.style,{left:s&&_&&g?`52%`:`2%`,right:s&&v&&g?`52%`:`2%`});let x=s&&!_&&!v,S=e=>Math.min(10,Math.max(0,e)*.8),C=x&&m>1?S(m-1):0,w=(x||s&&_)&&m<y.length-1?S(y.length-m-1):0;Object.assign(r.leftEdge.style,{display:C?``:`none`,left:`${-C}px`,width:`${C}px`}),Object.assign(r.rightEdge.style,{display:w?``:`none`,right:`${-w}px`,width:`${w}px`}),r.spine.style.left=`${s?f:0}px`,this.renderFaces(h,m,f,p),this.renderOverlays(h,f,p),this.renderChrome(),this.renderStrip(),this.renderRotatePrompt(),this.renderFullscreenPrompt();let T=`${s}:${m}`;T!==this.lastPlace&&(this.lastPlace=T,t.onPageChange?.({flipped:m,label:n.label,faces:this.layout.visible(m)}))}renderFaces(e,t,n,r){let i=this.layout.leaves,a=e?.leaf??null,o=e?.dir??1,s=a===null?t:a+1,c=a===null?t-1:a-1,l=/* @__PURE__ */ new Set;for(let u=Math.max(0,t-2);u<=Math.min(i.length-1,t+1);u++)for(let t of[`front`,`back`]){let d=`${u}:${t}`,f=u===a?t===`front`==(o===1)?`turning`:`flap`:t===`front`?u===s?`right`:`spare`:u===c&&this.layout.spread?`left`:`spare`,p=f===`left`||f===`turning`&&o===-1&&!e?.stiff,m=this.faces.get(d);m||(m=j(`div`,{class:`fb-face`}),m.append(J(t===`front`?i[u].front:i[u].back,this.settings.book,t===`front`?`right`:`left`)),this.faces.set(d,m),this.els.spine.append(m)),l.add(d),Object.assign(m.style,{left:`${p?-n:0}px`,width:`${n}px`,height:`${r}px`,zIndex:String({right:1,left:1,turning:3,flap:6,spare:0}[f]),visibility:f===`spare`?`hidden`:`visible`}),m.setAttribute(`aria-hidden`,String(f!==`left`&&f!==`right`)),u!==a&&Object.assign(m.style,{transform:``,transformOrigin:``,clipPath:``})}for(let[e,t]of this.faces)l.has(e)||(t.remove(),this.faces.delete(e));e&&this.paint(this.reader.engine.frame,e)}renderOverlays(e,t,n){let r=!!e&&!e.stiff,i=e=>`${(t*e).toFixed(1)}px`,a={under:`linear-gradient(to right, rgb(0 0 0 / 0.42), rgb(0 0 0 / 0.2) ${i(.05)}, rgb(0 0 0 / 0.06) ${i(.16)}, transparent ${i(.3)})`,bend:`linear-gradient(to right, rgb(0 0 0 / 0.16), rgb(0 0 0 / 0.04) ${i(.05)}, transparent ${i(.1)})`,shadow:`rgb(0 0 0 / 0.55)`,curl:`linear-gradient(to right, rgb(0 0 0 / 0.3), rgb(0 0 0 / 0.1) ${i(.035)}, rgb(255 255 255 / 0.2) ${i(.11)}, rgb(255 255 255 / 0.06) ${i(.25)}, rgb(0 0 0 / 0.05) ${i(.6)}, rgb(0 0 0 / 0.12) ${i(1)})`};for(let[e,{clip:i,light:o}]of this.overlays){if(!r){i.remove();continue}let s=e===`shadow`;Object.assign(i.style,{left:`${-t}px`,top:`${-n}px`,width:`${t*2}px`,height:`${n*3}px`,filter:s?`blur(${Math.max(3,t*.012)}px)`:``}),Object.assign(o.style,s?{inset:`0`,background:a[e]}:{width:`${t*3}px`,height:`${n*4}px`,background:a[e]}),i.isConnected||this.els.spine.append(i)}r&&e&&this.paint(this.reader.engine.frame,e)}paint(e,t){let{width:n,height:r}=this.size,i=t.dir,o=this.faces.get(`${t.leaf}:front`),s=this.faces.get(`${t.leaf}:back`);if(e.stiff){let t=`perspective(${Math.max(1800,n*5)}px) rotateY(${a.hinge(e.origin,e.point,i)}deg)`;o&&Object.assign(o.style,{transformOrigin:`0 50%`,transform:t,clipPath:``}),s&&Object.assign(s.style,{transformOrigin:`0 50%`,transform:`${t} translateX(${n}px) rotateY(180deg)`,clipPath:``});return}let c=i===1?o:s,l=i===1?s:o,u=new a(n,r,e.origin,e.point),d=u.shade*this.settings.shading;c&&Object.assign(c.style,{transformOrigin:``,transform:``,clipPath:Z(u.flat.map(e=>({x:i===1?e.x:n-e.x,y:e.y})))}),l&&Object.assign(l.style,{transformOrigin:`0 0`,transform:u.flapMatrix(i),clipPath:Z(u.folded.map(e=>({x:i===1?n-e.x:e.x,y:e.y})))});let f=(e,t,a,o)=>{let s=this.overlays.get(e);if(!s)return;let c=Z(t.map(e=>({x:i*e.x,y:e.y})),n,r);if(s.clip.style.opacity=String(o),e===`shadow`){s.light.style.clipPath=c;return}let l=Math.atan2(a*u.normal.y,i*a*u.normal.x);s.clip.style.clipPath=c,s.light.style.transform=`translate(${i*u.center.x+n}px, ${u.center.y+r}px) rotate(${l}rad) translateY(-50%)`};f(`under`,u.folded,1,d),f(`bend`,u.flat,-1,d),f(`shadow`,u.flap,-1,d*.6),f(`curl`,u.flap,-1,d)}renderChrome(){let{els:e,settings:t,reader:n}=this,{labels:r}=t,i=this.chromeVisible||this.stripOpen,a=document.fullscreenElement===this.root,o=(e,t,n)=>{e.setAttribute(`aria-label`,t),e.title=t,n&&e.replaceChildren(M(n))};e.top.hidden=!t.controls,e.bottom.hidden=!t.controls,e.top.classList.toggle(`is-hidden`,!i),e.bottom.classList.toggle(`is-hidden`,!i),e.title.textContent=t.book.title,e.byline.textContent=t.book.author?r.byline(t.book.author):``,e.byline.hidden=!t.book.author,e.sound.hidden=!t.soundButton,e.fullscreen.hidden=!t.fullscreenButton,o(e.sound,this.soundOn?r.soundOff:r.soundOn,this.soundOn?$.soundOn:$.soundOff),o(e.fullscreen,a?r.exitFullscreen:r.fullscreen,a?$.collapse:$.expand),o(e.prev,r.previous),o(e.next,r.next),e.prev.disabled=n.atStart,e.next.disabled=n.atEnd,e.pages.replaceChildren(...t.thumbnails?[M($.grid)]:[],n.label),e.pages.setAttribute(`aria-expanded`,String(this.stripOpen))}renderStrip(){let e=this.stripOpen&&this.settings.thumbnails,{book:t}=this.settings;if(!e){this.els.strip?.remove(),this.els.strip=null;return}let n=j(`div`,{class:`fb-strip-list`});for(let e of this.layout.stops()){let r=j(`button`,{type:`button`,class:`fb-strip-stop`,"aria-current":String(e.flipped===this.reader.flipped),"aria-label":`Go to ${S.describe(e.faces)}`});for(let n of e.faces)r.append(j(`div`,{class:`fb-strip-face`},J(n,t,void 0,!1)));r.addEventListener(`click`,()=>{this.stripOpen=!1,this.reader.go(e.flipped),this.render()}),n.append(r)}let r=j(`div`,{class:`fb-strip`},n);this.els.strip?.remove(),this.els.strip=r,this.els.bottom.before(r),n.querySelector(`[aria-current='true']`)?.scrollIntoView({inline:`center`,block:`nearest`})}renderRotatePrompt(){let{landscape:e,layout:t,labels:n}=this.settings;if(!(e&&t!==`single`&&this.orientation.touch&&this.orientation.portrait&&this.portraitChoice===`ask`)){this.els.rotate?.remove(),this.els.rotate=null;return}if(this.els.rotate)return;let r=j(`button`,{type:`button`,class:`fb-rotate-primary`},n.rotateFullscreen),i=j(`button`,{type:`button`,class:`fb-rotate-secondary`},n.rotateSingle);r.addEventListener(`click`,()=>N(this.root)),i.addEventListener(`click`,()=>{this.portraitChoice=`single`,this.render()}),this.els.rotate=j(`div`,{class:`fb-rotate`},M($.phone),j(`div`,{},j(`p`,{class:`fb-rotate-title`},n.rotateTitle),j(`p`,{class:`fb-rotate-body`},n.rotateBody)),r,i),this.root.append(this.els.rotate)}renderFullscreenPrompt(){let{fullscreenPrompt:e,labels:t}=this.settings;if(!e||this.promptAnswered||!document.fullscreenEnabled||document.fullscreenElement||this.els.rotate){this.els.prompt?.remove(),this.els.prompt=null;return}if(this.els.prompt)return;let n=j(`button`,{type:`button`,class:`fb-fullscreen-prompt`}),r=j(`span`,{class:`fb-fullscreen-prompt-label`},t.fullscreenPrompt);r.prepend(M($.expand)),n.append(r),n.addEventListener(`click`,()=>this.answerPrompt(!0)),this.els.prompt=n,this.root.append(n)}static stageTint(e){let[t,n,r]=m[e.theme??`classic`].stage;return`radial-gradient(ellipse at 50% 40%, ${t} 0%, ${n} 55%, ${r} 100%)`}};return e.Affine=i,e.BANDED_FRAMES=v,e.BookLayout=S,e.COVER_DESIGNS=h,e.COVER_THEMES=m,e.DECO=te,e.DEFAULT_GRIP=C,e.DEFAULT_LABELS=T,e.DEFAULT_PHYSICS=o,e.FLEURON=ee,e.FRAME_STYLES=_,e.Flipbook=be,e.FlipbookReader=w,e.Fold=a,e.PAGE_LAYOUTS=u,e.PAPER_COLORS=g,e.PHOTO_ZOOM=D,e.PLACED_LAYOUTS=p,e.PageTurn=l,e.PhotoCrop=A,e.PhotoPositioner=ce,e.coverArtMarkup=F,e.coverHasText=y,e.dot=n,e.enterLandscape=N,e.frameOf=x,e.length=r,e.photoFrame=b,e.playPageTurn=X,e.printedPhoto=q,e.renderBackCover=se,e.renderBlank=me,e.renderColophon=pe,e.renderCover=oe,e.renderFace=J,e.renderPage=de,e.renderTitlePage=fe,e.sub=t,e.watchOrientation=P,e})({});
|
package/dist/index.cjs
CHANGED
|
@@ -115,6 +115,155 @@ function renderBackCover(book) {
|
|
|
115
115
|
}
|
|
116
116
|
}, book.imprint)));
|
|
117
117
|
}
|
|
118
|
+
// src/positioner.ts
|
|
119
|
+
/** How far one arrow key press moves the photo (it moves the way the arrow points), and how much one zoom step enlarges it. */
|
|
120
|
+
const STEP = {
|
|
121
|
+
pan: 2,
|
|
122
|
+
zoom: .1
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Lets people choose which part of a photo shows inside the element that crops it: drag or use the arrow
|
|
126
|
+
* keys to move it, and pinch, scroll or press + and − to zoom. The element must hold the photo's `<img>`.
|
|
127
|
+
*/
|
|
128
|
+
var PhotoPositioner = class {
|
|
129
|
+
el;
|
|
130
|
+
options;
|
|
131
|
+
crop;
|
|
132
|
+
pointers = /* @__PURE__ */ new Map();
|
|
133
|
+
gesture = null;
|
|
134
|
+
abort = new AbortController();
|
|
135
|
+
settle;
|
|
136
|
+
constructor(el, options) {
|
|
137
|
+
this.el = el;
|
|
138
|
+
this.options = options;
|
|
139
|
+
this.crop = _toneflix_flipbook_core.PhotoCrop.of(options.position);
|
|
140
|
+
const signal = this.abort.signal;
|
|
141
|
+
el.classList.add("fb-positioning");
|
|
142
|
+
el.tabIndex = 0;
|
|
143
|
+
el.setAttribute("aria-roledescription", "photo position");
|
|
144
|
+
el.setAttribute("aria-keyshortcuts", "ArrowLeft ArrowRight ArrowUp ArrowDown + -");
|
|
145
|
+
el.addEventListener("pointerdown", (e) => this.down(e), { signal });
|
|
146
|
+
el.addEventListener("pointermove", (e) => this.move(e), { signal });
|
|
147
|
+
el.addEventListener("pointerup", (e) => this.up(e), { signal });
|
|
148
|
+
el.addEventListener("pointercancel", (e) => this.up(e), { signal });
|
|
149
|
+
el.addEventListener("wheel", (e) => this.wheel(e), {
|
|
150
|
+
signal,
|
|
151
|
+
passive: false
|
|
152
|
+
});
|
|
153
|
+
el.addEventListener("keydown", (e) => this.key(e), { signal });
|
|
154
|
+
el.addEventListener("dragstart", (e) => e.preventDefault(), { signal });
|
|
155
|
+
}
|
|
156
|
+
get position() {
|
|
157
|
+
return this.crop.toJSON();
|
|
158
|
+
}
|
|
159
|
+
/** Moves the photo without reporting it, e.g. when the position changes elsewhere. */
|
|
160
|
+
set position(position) {
|
|
161
|
+
this.crop = _toneflix_flipbook_core.PhotoCrop.of(position);
|
|
162
|
+
this.paint();
|
|
163
|
+
}
|
|
164
|
+
destroy() {
|
|
165
|
+
clearTimeout(this.settle);
|
|
166
|
+
this.abort.abort();
|
|
167
|
+
this.el.classList.remove("fb-positioning", "is-moving");
|
|
168
|
+
this.el.removeAttribute("tabindex");
|
|
169
|
+
this.el.removeAttribute("aria-roledescription");
|
|
170
|
+
this.el.removeAttribute("aria-keyshortcuts");
|
|
171
|
+
}
|
|
172
|
+
get image() {
|
|
173
|
+
return this.el.querySelector("img");
|
|
174
|
+
}
|
|
175
|
+
get box() {
|
|
176
|
+
const img = this.image;
|
|
177
|
+
return {
|
|
178
|
+
width: img?.clientWidth ?? 0,
|
|
179
|
+
height: img?.clientHeight ?? 0
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
get natural() {
|
|
183
|
+
const img = this.image;
|
|
184
|
+
return img?.naturalWidth ? {
|
|
185
|
+
width: img.naturalWidth,
|
|
186
|
+
height: img.naturalHeight
|
|
187
|
+
} : null;
|
|
188
|
+
}
|
|
189
|
+
paint() {
|
|
190
|
+
const img = this.image;
|
|
191
|
+
if (img) Object.assign(img.style, this.crop.css);
|
|
192
|
+
}
|
|
193
|
+
commit() {
|
|
194
|
+
this.options.onChange(this.crop.toJSON());
|
|
195
|
+
}
|
|
196
|
+
spread() {
|
|
197
|
+
const [a, b] = [...this.pointers.values()];
|
|
198
|
+
return a && b ? Math.hypot(a.x - b.x, a.y - b.y) : 0;
|
|
199
|
+
}
|
|
200
|
+
down(e) {
|
|
201
|
+
if (e.button !== 0 || !this.image) return;
|
|
202
|
+
e.stopPropagation();
|
|
203
|
+
this.el.setPointerCapture(e.pointerId);
|
|
204
|
+
this.el.classList.add("is-moving");
|
|
205
|
+
this.pointers.set(e.pointerId, {
|
|
206
|
+
x: e.clientX,
|
|
207
|
+
y: e.clientY
|
|
208
|
+
});
|
|
209
|
+
this.gesture = {
|
|
210
|
+
crop: this.crop,
|
|
211
|
+
spread: this.spread()
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
move(e) {
|
|
215
|
+
const last = this.pointers.get(e.pointerId);
|
|
216
|
+
if (!last || !this.gesture) return;
|
|
217
|
+
e.stopPropagation();
|
|
218
|
+
this.pointers.set(e.pointerId, {
|
|
219
|
+
x: e.clientX,
|
|
220
|
+
y: e.clientY
|
|
221
|
+
});
|
|
222
|
+
if (this.pointers.size > 1) {
|
|
223
|
+
if (this.gesture.spread) this.crop = this.crop.zoomTo(this.gesture.crop.zoom * this.spread() / this.gesture.spread);
|
|
224
|
+
} else {
|
|
225
|
+
const scale = this.el.getBoundingClientRect().width / (this.el.offsetWidth || 1);
|
|
226
|
+
this.crop = this.crop.pan((e.clientX - last.x) / scale, (e.clientY - last.y) / scale, this.box, this.natural);
|
|
227
|
+
}
|
|
228
|
+
this.paint();
|
|
229
|
+
}
|
|
230
|
+
up(e) {
|
|
231
|
+
if (!this.pointers.delete(e.pointerId)) return;
|
|
232
|
+
e.stopPropagation();
|
|
233
|
+
this.gesture = this.pointers.size ? {
|
|
234
|
+
crop: this.crop,
|
|
235
|
+
spread: this.spread()
|
|
236
|
+
} : null;
|
|
237
|
+
if (this.pointers.size) return;
|
|
238
|
+
this.el.classList.remove("is-moving");
|
|
239
|
+
this.commit();
|
|
240
|
+
}
|
|
241
|
+
wheel(e) {
|
|
242
|
+
e.preventDefault();
|
|
243
|
+
this.crop = this.crop.zoomTo(this.crop.zoom * Math.exp(-e.deltaY / 500));
|
|
244
|
+
this.paint();
|
|
245
|
+
clearTimeout(this.settle);
|
|
246
|
+
this.settle = setTimeout(() => this.commit(), 250);
|
|
247
|
+
}
|
|
248
|
+
key(e) {
|
|
249
|
+
const next = {
|
|
250
|
+
ArrowLeft: () => new _toneflix_flipbook_core.PhotoCrop(this.crop.x + STEP.pan, this.crop.y, this.crop.zoom),
|
|
251
|
+
ArrowRight: () => new _toneflix_flipbook_core.PhotoCrop(this.crop.x - STEP.pan, this.crop.y, this.crop.zoom),
|
|
252
|
+
ArrowUp: () => new _toneflix_flipbook_core.PhotoCrop(this.crop.x, this.crop.y + STEP.pan, this.crop.zoom),
|
|
253
|
+
ArrowDown: () => new _toneflix_flipbook_core.PhotoCrop(this.crop.x, this.crop.y - STEP.pan, this.crop.zoom),
|
|
254
|
+
"+": () => this.crop.zoomTo(this.crop.zoom + STEP.zoom),
|
|
255
|
+
"=": () => this.crop.zoomTo(this.crop.zoom + STEP.zoom),
|
|
256
|
+
"-": () => this.crop.zoomTo(this.crop.zoom - STEP.zoom),
|
|
257
|
+
"0": () => _toneflix_flipbook_core.PhotoCrop.CENTRE
|
|
258
|
+
}[e.key]?.();
|
|
259
|
+
if (!next) return;
|
|
260
|
+
e.preventDefault();
|
|
261
|
+
e.stopPropagation();
|
|
262
|
+
this.crop = next;
|
|
263
|
+
this.paint();
|
|
264
|
+
this.commit();
|
|
265
|
+
}
|
|
266
|
+
};
|
|
118
267
|
// src/pages.ts
|
|
119
268
|
const LEAVES = [
|
|
120
269
|
14,
|
|
@@ -145,7 +294,8 @@ const image = (photo, className) => h("img", {
|
|
|
145
294
|
decoding: "async",
|
|
146
295
|
width: photo.width ?? void 0,
|
|
147
296
|
height: photo.height ?? void 0,
|
|
148
|
-
class: className
|
|
297
|
+
class: className,
|
|
298
|
+
style: _toneflix_flipbook_core.PhotoCrop.of(photo.position).css
|
|
149
299
|
});
|
|
150
300
|
const boxStyle = ({ x, y, width, height, rotate }) => ({
|
|
151
301
|
left: `${x}cqw`,
|
|
@@ -176,7 +326,7 @@ function printedPhoto(photo, frame, fit = "cover", show = true, scale = 1) {
|
|
|
176
326
|
class: "fb-print-frame-band",
|
|
177
327
|
"aria-hidden": "true"
|
|
178
328
|
}));
|
|
179
|
-
print.append(photo && show ?
|
|
329
|
+
print.append(photo && show ? h("div", { class: "fb-print-photo fb-print-frame-photo" }, image(photo, "fb-photo")) : h("div", { class: "fb-print-frame-photo fb-placeholder" }));
|
|
180
330
|
if (frame === "corners") for (const c of [
|
|
181
331
|
"tl",
|
|
182
332
|
"tr",
|
|
@@ -193,7 +343,7 @@ function printedPhoto(photo, frame, fit = "cover", show = true, scale = 1) {
|
|
|
193
343
|
/** The inside of one book page. The parent must have `container-type: size`. */
|
|
194
344
|
function renderPage(page, look, options = {}) {
|
|
195
345
|
const { layout, caption, photos } = page;
|
|
196
|
-
const { number, side, showImages = true, onSlotClick, activeSlot } = options;
|
|
346
|
+
const { number, side, showImages = true, onSlotClick, activeSlot, onPhotoPosition } = options;
|
|
197
347
|
const placed = _toneflix_flipbook_core.PLACED_LAYOUTS[layout];
|
|
198
348
|
const root = h("div", { class: `fb-paper fb-paper-${look.paper} fb-page` });
|
|
199
349
|
const slot = (index, className = "", framed = false, style) => {
|
|
@@ -213,6 +363,10 @@ function renderPage(page, look, options = {}) {
|
|
|
213
363
|
});
|
|
214
364
|
if (onSlotClick) el.addEventListener("click", () => onSlotClick(index));
|
|
215
365
|
el.append(photo ? printedPhoto(photo, frame, framed ? "contain" : "cover", showImages) : h("div", { class: "fb-slot-empty" }, onSlotClick ? "Add a photo" : ""));
|
|
366
|
+
if (photo && showImages && onPhotoPosition && activeSlot === index) new PhotoPositioner(el, {
|
|
367
|
+
position: photo.position,
|
|
368
|
+
onChange: (position) => onPhotoPosition(index, position)
|
|
369
|
+
});
|
|
216
370
|
return el;
|
|
217
371
|
};
|
|
218
372
|
const column = (style, ...children) => h("div", {
|
|
@@ -1090,6 +1244,7 @@ var Flipbook = class Flipbook {
|
|
|
1090
1244
|
}
|
|
1091
1245
|
};
|
|
1092
1246
|
exports.Flipbook = Flipbook;
|
|
1247
|
+
exports.PhotoPositioner = PhotoPositioner;
|
|
1093
1248
|
exports.coverArtMarkup = coverArtMarkup;
|
|
1094
1249
|
exports.enterLandscape = enterLandscape;
|
|
1095
1250
|
exports.playPageTurn = playPageTurn;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CoverArt, Face, FlipbookBook, FlipbookLabels, FlipbookPage, FlipbookPhoto, FlipbookReader, FrameStyle, GripOptions, PaperColor, TurnPhysics } from "@toneflix/flipbook-core";
|
|
1
|
+
import { CoverArt, Face, FlipbookBook, FlipbookLabels, FlipbookPage, FlipbookPhoto, FlipbookReader, FrameStyle, GripOptions, PaperColor, PhotoPosition, TurnPhysics } from "@toneflix/flipbook-core";
|
|
2
2
|
export * from "@toneflix/flipbook-core";
|
|
3
3
|
interface FlipbookPlace {
|
|
4
4
|
/** How many leaves lie on the left: 0 is the closed front cover. */
|
|
@@ -119,6 +119,8 @@ interface PageOptions {
|
|
|
119
119
|
/** Editor: makes each photo slot clickable. */
|
|
120
120
|
onSlotClick?: (slot: number) => void;
|
|
121
121
|
activeSlot?: number | null;
|
|
122
|
+
/** Editor: lets people move and zoom the photo in the active slot. */
|
|
123
|
+
onPhotoPosition?: (slot: number, position: PhotoPosition) => void;
|
|
122
124
|
}
|
|
123
125
|
/** A photo printed on a page inside its frame. */
|
|
124
126
|
export declare function printedPhoto(photo: FlipbookPhoto | null, frame: FrameStyle, fit?: 'cover' | 'contain', show?: boolean, scale?: number): HTMLDivElement;
|
|
@@ -129,6 +131,41 @@ export declare function renderColophon(book: FlipbookBook, look: PageLook, side?
|
|
|
129
131
|
export declare function renderBlank(look: PageLook, side?: Side): HTMLDivElement;
|
|
130
132
|
/** Any face of a book. Page numbers and the spine curve need `side`. */
|
|
131
133
|
export declare function renderFace(face: Face, book: FlipbookBook, side?: Side, numbered?: boolean): HTMLDivElement;
|
|
134
|
+
interface PhotoPositionerOptions {
|
|
135
|
+
/** Where the photo starts. */
|
|
136
|
+
position?: Partial<PhotoPosition> | null;
|
|
137
|
+
/** Called once a drag, pinch, scroll or key press has moved the photo. */
|
|
138
|
+
onChange: (position: PhotoPosition) => void;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Lets people choose which part of a photo shows inside the element that crops it: drag or use the arrow
|
|
142
|
+
* keys to move it, and pinch, scroll or press + and − to zoom. The element must hold the photo's `<img>`.
|
|
143
|
+
*/
|
|
144
|
+
export declare class PhotoPositioner {
|
|
145
|
+
private readonly el;
|
|
146
|
+
private readonly options;
|
|
147
|
+
private crop;
|
|
148
|
+
private readonly pointers;
|
|
149
|
+
private gesture;
|
|
150
|
+
private readonly abort;
|
|
151
|
+
private settle?;
|
|
152
|
+
constructor(el: HTMLElement, options: PhotoPositionerOptions);
|
|
153
|
+
get position(): PhotoPosition;
|
|
154
|
+
/** Moves the photo without reporting it, e.g. when the position changes elsewhere. */
|
|
155
|
+
set position(position: Partial<PhotoPosition> | null | undefined);
|
|
156
|
+
destroy(): void;
|
|
157
|
+
private get image();
|
|
158
|
+
private get box();
|
|
159
|
+
private get natural();
|
|
160
|
+
private paint;
|
|
161
|
+
private commit;
|
|
162
|
+
private spread;
|
|
163
|
+
private down;
|
|
164
|
+
private move;
|
|
165
|
+
private up;
|
|
166
|
+
private wheel;
|
|
167
|
+
private key;
|
|
168
|
+
}
|
|
132
169
|
/** SVG markup for a piece of cover line art, drawn in the current ink. */
|
|
133
170
|
export declare function coverArtMarkup(art: CoverArt, className: string): string;
|
|
134
171
|
/** The front cover, laid out by the book's `coverDesign`. */
|
|
@@ -150,4 +187,4 @@ export declare function watchOrientation(onChange: (state: {
|
|
|
150
187
|
portrait: boolean;
|
|
151
188
|
touch: boolean;
|
|
152
189
|
}) => void): () => void;
|
|
153
|
-
export type { FlipbookOptions, FlipbookPlace, PageLook, PageOptions, Side };
|
|
190
|
+
export type { FlipbookOptions, FlipbookPlace, PageLook, PageOptions, PhotoPositionerOptions, Side };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CoverArt, Face, FlipbookBook, FlipbookLabels, FlipbookPage, FlipbookPhoto, FlipbookReader, FrameStyle, GripOptions, PaperColor, TurnPhysics } from "@toneflix/flipbook-core";
|
|
1
|
+
import { CoverArt, Face, FlipbookBook, FlipbookLabels, FlipbookPage, FlipbookPhoto, FlipbookReader, FrameStyle, GripOptions, PaperColor, PhotoPosition, TurnPhysics } from "@toneflix/flipbook-core";
|
|
2
2
|
export * from "@toneflix/flipbook-core";
|
|
3
3
|
interface FlipbookPlace {
|
|
4
4
|
/** How many leaves lie on the left: 0 is the closed front cover. */
|
|
@@ -119,6 +119,8 @@ interface PageOptions {
|
|
|
119
119
|
/** Editor: makes each photo slot clickable. */
|
|
120
120
|
onSlotClick?: (slot: number) => void;
|
|
121
121
|
activeSlot?: number | null;
|
|
122
|
+
/** Editor: lets people move and zoom the photo in the active slot. */
|
|
123
|
+
onPhotoPosition?: (slot: number, position: PhotoPosition) => void;
|
|
122
124
|
}
|
|
123
125
|
/** A photo printed on a page inside its frame. */
|
|
124
126
|
export declare function printedPhoto(photo: FlipbookPhoto | null, frame: FrameStyle, fit?: 'cover' | 'contain', show?: boolean, scale?: number): HTMLDivElement;
|
|
@@ -129,6 +131,41 @@ export declare function renderColophon(book: FlipbookBook, look: PageLook, side?
|
|
|
129
131
|
export declare function renderBlank(look: PageLook, side?: Side): HTMLDivElement;
|
|
130
132
|
/** Any face of a book. Page numbers and the spine curve need `side`. */
|
|
131
133
|
export declare function renderFace(face: Face, book: FlipbookBook, side?: Side, numbered?: boolean): HTMLDivElement;
|
|
134
|
+
interface PhotoPositionerOptions {
|
|
135
|
+
/** Where the photo starts. */
|
|
136
|
+
position?: Partial<PhotoPosition> | null;
|
|
137
|
+
/** Called once a drag, pinch, scroll or key press has moved the photo. */
|
|
138
|
+
onChange: (position: PhotoPosition) => void;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Lets people choose which part of a photo shows inside the element that crops it: drag or use the arrow
|
|
142
|
+
* keys to move it, and pinch, scroll or press + and − to zoom. The element must hold the photo's `<img>`.
|
|
143
|
+
*/
|
|
144
|
+
export declare class PhotoPositioner {
|
|
145
|
+
private readonly el;
|
|
146
|
+
private readonly options;
|
|
147
|
+
private crop;
|
|
148
|
+
private readonly pointers;
|
|
149
|
+
private gesture;
|
|
150
|
+
private readonly abort;
|
|
151
|
+
private settle?;
|
|
152
|
+
constructor(el: HTMLElement, options: PhotoPositionerOptions);
|
|
153
|
+
get position(): PhotoPosition;
|
|
154
|
+
/** Moves the photo without reporting it, e.g. when the position changes elsewhere. */
|
|
155
|
+
set position(position: Partial<PhotoPosition> | null | undefined);
|
|
156
|
+
destroy(): void;
|
|
157
|
+
private get image();
|
|
158
|
+
private get box();
|
|
159
|
+
private get natural();
|
|
160
|
+
private paint;
|
|
161
|
+
private commit;
|
|
162
|
+
private spread;
|
|
163
|
+
private down;
|
|
164
|
+
private move;
|
|
165
|
+
private up;
|
|
166
|
+
private wheel;
|
|
167
|
+
private key;
|
|
168
|
+
}
|
|
132
169
|
/** SVG markup for a piece of cover line art, drawn in the current ink. */
|
|
133
170
|
export declare function coverArtMarkup(art: CoverArt, className: string): string;
|
|
134
171
|
/** The front cover, laid out by the book's `coverDesign`. */
|
|
@@ -150,4 +187,4 @@ export declare function watchOrientation(onChange: (state: {
|
|
|
150
187
|
portrait: boolean;
|
|
151
188
|
touch: boolean;
|
|
152
189
|
}) => void): () => void;
|
|
153
|
-
export type { FlipbookOptions, FlipbookPlace, PageLook, PageOptions, Side };
|
|
190
|
+
export type { FlipbookOptions, FlipbookPlace, PageLook, PageOptions, PhotoPositionerOptions, Side };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BANDED_FRAMES, BookLayout, COVER_THEMES, DECO, DEFAULT_LABELS, FLEURON, FlipbookReader, Fold, PLACED_LAYOUTS, coverHasText, photoFrame } from "@toneflix/flipbook-core";
|
|
1
|
+
import { BANDED_FRAMES, BookLayout, COVER_THEMES, DECO, DEFAULT_LABELS, FLEURON, FlipbookReader, Fold, PLACED_LAYOUTS, PhotoCrop, coverHasText, photoFrame } from "@toneflix/flipbook-core";
|
|
2
2
|
export * from "@toneflix/flipbook-core";
|
|
3
3
|
// src/dom.ts
|
|
4
4
|
/** Creates an element. Strings become text, so book content is never parsed as HTML. */
|
|
@@ -115,6 +115,155 @@ function renderBackCover(book) {
|
|
|
115
115
|
}
|
|
116
116
|
}, book.imprint)));
|
|
117
117
|
}
|
|
118
|
+
// src/positioner.ts
|
|
119
|
+
/** How far one arrow key press moves the photo (it moves the way the arrow points), and how much one zoom step enlarges it. */
|
|
120
|
+
const STEP = {
|
|
121
|
+
pan: 2,
|
|
122
|
+
zoom: .1
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Lets people choose which part of a photo shows inside the element that crops it: drag or use the arrow
|
|
126
|
+
* keys to move it, and pinch, scroll or press + and − to zoom. The element must hold the photo's `<img>`.
|
|
127
|
+
*/
|
|
128
|
+
var PhotoPositioner = class {
|
|
129
|
+
el;
|
|
130
|
+
options;
|
|
131
|
+
crop;
|
|
132
|
+
pointers = /* @__PURE__ */ new Map();
|
|
133
|
+
gesture = null;
|
|
134
|
+
abort = new AbortController();
|
|
135
|
+
settle;
|
|
136
|
+
constructor(el, options) {
|
|
137
|
+
this.el = el;
|
|
138
|
+
this.options = options;
|
|
139
|
+
this.crop = PhotoCrop.of(options.position);
|
|
140
|
+
const signal = this.abort.signal;
|
|
141
|
+
el.classList.add("fb-positioning");
|
|
142
|
+
el.tabIndex = 0;
|
|
143
|
+
el.setAttribute("aria-roledescription", "photo position");
|
|
144
|
+
el.setAttribute("aria-keyshortcuts", "ArrowLeft ArrowRight ArrowUp ArrowDown + -");
|
|
145
|
+
el.addEventListener("pointerdown", (e) => this.down(e), { signal });
|
|
146
|
+
el.addEventListener("pointermove", (e) => this.move(e), { signal });
|
|
147
|
+
el.addEventListener("pointerup", (e) => this.up(e), { signal });
|
|
148
|
+
el.addEventListener("pointercancel", (e) => this.up(e), { signal });
|
|
149
|
+
el.addEventListener("wheel", (e) => this.wheel(e), {
|
|
150
|
+
signal,
|
|
151
|
+
passive: false
|
|
152
|
+
});
|
|
153
|
+
el.addEventListener("keydown", (e) => this.key(e), { signal });
|
|
154
|
+
el.addEventListener("dragstart", (e) => e.preventDefault(), { signal });
|
|
155
|
+
}
|
|
156
|
+
get position() {
|
|
157
|
+
return this.crop.toJSON();
|
|
158
|
+
}
|
|
159
|
+
/** Moves the photo without reporting it, e.g. when the position changes elsewhere. */
|
|
160
|
+
set position(position) {
|
|
161
|
+
this.crop = PhotoCrop.of(position);
|
|
162
|
+
this.paint();
|
|
163
|
+
}
|
|
164
|
+
destroy() {
|
|
165
|
+
clearTimeout(this.settle);
|
|
166
|
+
this.abort.abort();
|
|
167
|
+
this.el.classList.remove("fb-positioning", "is-moving");
|
|
168
|
+
this.el.removeAttribute("tabindex");
|
|
169
|
+
this.el.removeAttribute("aria-roledescription");
|
|
170
|
+
this.el.removeAttribute("aria-keyshortcuts");
|
|
171
|
+
}
|
|
172
|
+
get image() {
|
|
173
|
+
return this.el.querySelector("img");
|
|
174
|
+
}
|
|
175
|
+
get box() {
|
|
176
|
+
const img = this.image;
|
|
177
|
+
return {
|
|
178
|
+
width: img?.clientWidth ?? 0,
|
|
179
|
+
height: img?.clientHeight ?? 0
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
get natural() {
|
|
183
|
+
const img = this.image;
|
|
184
|
+
return img?.naturalWidth ? {
|
|
185
|
+
width: img.naturalWidth,
|
|
186
|
+
height: img.naturalHeight
|
|
187
|
+
} : null;
|
|
188
|
+
}
|
|
189
|
+
paint() {
|
|
190
|
+
const img = this.image;
|
|
191
|
+
if (img) Object.assign(img.style, this.crop.css);
|
|
192
|
+
}
|
|
193
|
+
commit() {
|
|
194
|
+
this.options.onChange(this.crop.toJSON());
|
|
195
|
+
}
|
|
196
|
+
spread() {
|
|
197
|
+
const [a, b] = [...this.pointers.values()];
|
|
198
|
+
return a && b ? Math.hypot(a.x - b.x, a.y - b.y) : 0;
|
|
199
|
+
}
|
|
200
|
+
down(e) {
|
|
201
|
+
if (e.button !== 0 || !this.image) return;
|
|
202
|
+
e.stopPropagation();
|
|
203
|
+
this.el.setPointerCapture(e.pointerId);
|
|
204
|
+
this.el.classList.add("is-moving");
|
|
205
|
+
this.pointers.set(e.pointerId, {
|
|
206
|
+
x: e.clientX,
|
|
207
|
+
y: e.clientY
|
|
208
|
+
});
|
|
209
|
+
this.gesture = {
|
|
210
|
+
crop: this.crop,
|
|
211
|
+
spread: this.spread()
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
move(e) {
|
|
215
|
+
const last = this.pointers.get(e.pointerId);
|
|
216
|
+
if (!last || !this.gesture) return;
|
|
217
|
+
e.stopPropagation();
|
|
218
|
+
this.pointers.set(e.pointerId, {
|
|
219
|
+
x: e.clientX,
|
|
220
|
+
y: e.clientY
|
|
221
|
+
});
|
|
222
|
+
if (this.pointers.size > 1) {
|
|
223
|
+
if (this.gesture.spread) this.crop = this.crop.zoomTo(this.gesture.crop.zoom * this.spread() / this.gesture.spread);
|
|
224
|
+
} else {
|
|
225
|
+
const scale = this.el.getBoundingClientRect().width / (this.el.offsetWidth || 1);
|
|
226
|
+
this.crop = this.crop.pan((e.clientX - last.x) / scale, (e.clientY - last.y) / scale, this.box, this.natural);
|
|
227
|
+
}
|
|
228
|
+
this.paint();
|
|
229
|
+
}
|
|
230
|
+
up(e) {
|
|
231
|
+
if (!this.pointers.delete(e.pointerId)) return;
|
|
232
|
+
e.stopPropagation();
|
|
233
|
+
this.gesture = this.pointers.size ? {
|
|
234
|
+
crop: this.crop,
|
|
235
|
+
spread: this.spread()
|
|
236
|
+
} : null;
|
|
237
|
+
if (this.pointers.size) return;
|
|
238
|
+
this.el.classList.remove("is-moving");
|
|
239
|
+
this.commit();
|
|
240
|
+
}
|
|
241
|
+
wheel(e) {
|
|
242
|
+
e.preventDefault();
|
|
243
|
+
this.crop = this.crop.zoomTo(this.crop.zoom * Math.exp(-e.deltaY / 500));
|
|
244
|
+
this.paint();
|
|
245
|
+
clearTimeout(this.settle);
|
|
246
|
+
this.settle = setTimeout(() => this.commit(), 250);
|
|
247
|
+
}
|
|
248
|
+
key(e) {
|
|
249
|
+
const next = {
|
|
250
|
+
ArrowLeft: () => new PhotoCrop(this.crop.x + STEP.pan, this.crop.y, this.crop.zoom),
|
|
251
|
+
ArrowRight: () => new PhotoCrop(this.crop.x - STEP.pan, this.crop.y, this.crop.zoom),
|
|
252
|
+
ArrowUp: () => new PhotoCrop(this.crop.x, this.crop.y + STEP.pan, this.crop.zoom),
|
|
253
|
+
ArrowDown: () => new PhotoCrop(this.crop.x, this.crop.y - STEP.pan, this.crop.zoom),
|
|
254
|
+
"+": () => this.crop.zoomTo(this.crop.zoom + STEP.zoom),
|
|
255
|
+
"=": () => this.crop.zoomTo(this.crop.zoom + STEP.zoom),
|
|
256
|
+
"-": () => this.crop.zoomTo(this.crop.zoom - STEP.zoom),
|
|
257
|
+
"0": () => PhotoCrop.CENTRE
|
|
258
|
+
}[e.key]?.();
|
|
259
|
+
if (!next) return;
|
|
260
|
+
e.preventDefault();
|
|
261
|
+
e.stopPropagation();
|
|
262
|
+
this.crop = next;
|
|
263
|
+
this.paint();
|
|
264
|
+
this.commit();
|
|
265
|
+
}
|
|
266
|
+
};
|
|
118
267
|
// src/pages.ts
|
|
119
268
|
const LEAVES = [
|
|
120
269
|
14,
|
|
@@ -145,7 +294,8 @@ const image = (photo, className) => h("img", {
|
|
|
145
294
|
decoding: "async",
|
|
146
295
|
width: photo.width ?? void 0,
|
|
147
296
|
height: photo.height ?? void 0,
|
|
148
|
-
class: className
|
|
297
|
+
class: className,
|
|
298
|
+
style: PhotoCrop.of(photo.position).css
|
|
149
299
|
});
|
|
150
300
|
const boxStyle = ({ x, y, width, height, rotate }) => ({
|
|
151
301
|
left: `${x}cqw`,
|
|
@@ -176,7 +326,7 @@ function printedPhoto(photo, frame, fit = "cover", show = true, scale = 1) {
|
|
|
176
326
|
class: "fb-print-frame-band",
|
|
177
327
|
"aria-hidden": "true"
|
|
178
328
|
}));
|
|
179
|
-
print.append(photo && show ?
|
|
329
|
+
print.append(photo && show ? h("div", { class: "fb-print-photo fb-print-frame-photo" }, image(photo, "fb-photo")) : h("div", { class: "fb-print-frame-photo fb-placeholder" }));
|
|
180
330
|
if (frame === "corners") for (const c of [
|
|
181
331
|
"tl",
|
|
182
332
|
"tr",
|
|
@@ -193,7 +343,7 @@ function printedPhoto(photo, frame, fit = "cover", show = true, scale = 1) {
|
|
|
193
343
|
/** The inside of one book page. The parent must have `container-type: size`. */
|
|
194
344
|
function renderPage(page, look, options = {}) {
|
|
195
345
|
const { layout, caption, photos } = page;
|
|
196
|
-
const { number, side, showImages = true, onSlotClick, activeSlot } = options;
|
|
346
|
+
const { number, side, showImages = true, onSlotClick, activeSlot, onPhotoPosition } = options;
|
|
197
347
|
const placed = PLACED_LAYOUTS[layout];
|
|
198
348
|
const root = h("div", { class: `fb-paper fb-paper-${look.paper} fb-page` });
|
|
199
349
|
const slot = (index, className = "", framed = false, style) => {
|
|
@@ -213,6 +363,10 @@ function renderPage(page, look, options = {}) {
|
|
|
213
363
|
});
|
|
214
364
|
if (onSlotClick) el.addEventListener("click", () => onSlotClick(index));
|
|
215
365
|
el.append(photo ? printedPhoto(photo, frame, framed ? "contain" : "cover", showImages) : h("div", { class: "fb-slot-empty" }, onSlotClick ? "Add a photo" : ""));
|
|
366
|
+
if (photo && showImages && onPhotoPosition && activeSlot === index) new PhotoPositioner(el, {
|
|
367
|
+
position: photo.position,
|
|
368
|
+
onChange: (position) => onPhotoPosition(index, position)
|
|
369
|
+
});
|
|
216
370
|
return el;
|
|
217
371
|
};
|
|
218
372
|
const column = (style, ...children) => h("div", {
|
|
@@ -1089,4 +1243,4 @@ var Flipbook = class Flipbook {
|
|
|
1089
1243
|
return `radial-gradient(ellipse at 50% 40%, ${inner} 0%, ${middle} 55%, ${outer} 100%)`;
|
|
1090
1244
|
}
|
|
1091
1245
|
};
|
|
1092
|
-
export { Flipbook, coverArtMarkup, enterLandscape, playPageTurn, printedPhoto, renderBackCover, renderBlank, renderColophon, renderCover, renderFace, renderPage, renderTitlePage, watchOrientation };
|
|
1246
|
+
export { Flipbook, PhotoPositioner, coverArtMarkup, enterLandscape, playPageTurn, printedPhoto, renderBackCover, renderBlank, renderColophon, renderCover, renderFace, renderPage, renderTitlePage, watchOrientation };
|
package/dist/style.css
CHANGED
|
@@ -409,6 +409,29 @@
|
|
|
409
409
|
.fb-slot.is-active {
|
|
410
410
|
box-shadow: 0 0 0 0.8cqw var(--fb-accent);
|
|
411
411
|
}
|
|
412
|
+
/* The active slot of an editor that positions photos: drag to move, a thirds grid while moving. */
|
|
413
|
+
.fb-positioning {
|
|
414
|
+
cursor: grab;
|
|
415
|
+
touch-action: none;
|
|
416
|
+
outline: none;
|
|
417
|
+
}
|
|
418
|
+
.fb-positioning.is-moving {
|
|
419
|
+
cursor: grabbing;
|
|
420
|
+
}
|
|
421
|
+
.fb-positioning:focus-visible {
|
|
422
|
+
box-shadow: 0 0 0 1cqw var(--fb-accent);
|
|
423
|
+
}
|
|
424
|
+
.fb-positioning.is-moving .fb-print-frame-photo::after {
|
|
425
|
+
content: '';
|
|
426
|
+
position: absolute;
|
|
427
|
+
inset: 0;
|
|
428
|
+
pointer-events: none;
|
|
429
|
+
background:
|
|
430
|
+
linear-gradient(to right, transparent calc(100% / 3 - 0.5px), rgb(255 255 255 / 0.7) 0 calc(100% / 3 + 0.5px), transparent 0),
|
|
431
|
+
linear-gradient(to right, transparent calc(200% / 3 - 0.5px), rgb(255 255 255 / 0.7) 0 calc(200% / 3 + 0.5px), transparent 0),
|
|
432
|
+
linear-gradient(to bottom, transparent calc(100% / 3 - 0.5px), rgb(255 255 255 / 0.7) 0 calc(100% / 3 + 0.5px), transparent 0),
|
|
433
|
+
linear-gradient(to bottom, transparent calc(200% / 3 - 0.5px), rgb(255 255 255 / 0.7) 0 calc(200% / 3 + 0.5px), transparent 0);
|
|
434
|
+
}
|
|
412
435
|
.fb-slot[data-over='true'] {
|
|
413
436
|
box-shadow: 0 0 0 1cqw var(--fb-highlight);
|
|
414
437
|
}
|
|
@@ -566,7 +589,7 @@
|
|
|
566
589
|
inset: var(--frame-pad);
|
|
567
590
|
width: calc(100% - 2 * var(--frame-pad));
|
|
568
591
|
height: calc(100% - 2 * var(--frame-pad));
|
|
569
|
-
|
|
592
|
+
overflow: hidden;
|
|
570
593
|
}
|
|
571
594
|
|
|
572
595
|
.fb-frame-hairline {
|
|
@@ -1104,6 +1127,7 @@
|
|
|
1104
1127
|
.fb-cover-art {
|
|
1105
1128
|
position: absolute;
|
|
1106
1129
|
inset: 0;
|
|
1130
|
+
overflow: hidden;
|
|
1107
1131
|
}
|
|
1108
1132
|
.fb-cover-tint {
|
|
1109
1133
|
position: absolute;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toneflix/flipbook",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "An expressive flipbook for creating personalised and interactive reading and photo viewing experiences, for any web page, no framework needed.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://toneflix.github.io/flipbook/frameworks/vanilla",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"dist"
|
|
47
47
|
],
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@toneflix/flipbook-core": "^0.
|
|
49
|
+
"@toneflix/flipbook-core": "^0.3.4"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"tsdown": "^0.23.5",
|