← back to Wallpaper Paint Antics
series2/episodes/07-sample-before-specifying.html
215 lines
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=1080,height=1920"><title>Wallo presents — The value of a real sample</title><style>@font-face{font-family:Georgia;src:url('../assets/georgia.ttf')}@font-face{font-family:Verdana;src:url('../assets/verdana.ttf')}html,body{margin:0;width:1080px;height:1920px;overflow:hidden}#root{position:relative;width:1080px;height:1920px;overflow:hidden}.clip{position:absolute;inset:0;width:1080px;height:1920px}canvas{width:1080px;height:1920px}.preload{position:absolute;opacity:0;pointer-events:none}</style><script src="../assets/gsap.min.js"></script></head><body><div id="root" data-composition-id="main" data-start="0" data-duration="20" data-width="1080" data-height="1920" data-fps="30"><section id="material-scene" class="clip" data-start="0" data-duration="20" data-track-index="0"><canvas id="stage" width="1080" height="1920" aria-label="Wallo presenting The value of a real sample"></canvas><span class="preload" style="font-family:Georgia">Wallo</span><span class="preload" style="font-family:Verdana">Designer Wallcoverings</span></section><audio id="narrated-mix" src="../assets/mix-3.wav" data-start="0" data-duration="20" data-track-index="1"></audio></div><script>window.EPISODE={"slug":"07-sample-before-specifying","title":"See it. Feel it.\nThen specify it.","shortTitle":"The value of a real sample","kicker":"04 / SAMPLING","product":"topiary","note":"Topiary View / Thyme · Sanderson. Illustration not to scale.","takeaway":"A sample makes the decision real.","lines":["A screen helps you shortlist. A physical sample helps you decide.","Hold it beside your flooring, your fabrics, and your paint.","Check the color in daylight and under the room's lighting.","Confirm the pattern scale and repeat before planning the installation."],"captions":["Shortlist online. Decide with a sample.","Compare it with the room's other finishes.","Check daylight and the room's lighting.","Confirm scale and repeat."],"index":3,"audio":{"slug":"07-sample-before-specifying","provider":"Kokoro local","voice":"am_michael","duration":20,"segments":[{"index":0,"start":0.6,"end":4.827,"text":"A screen helps you shortlist. A physical sample helps you decide.","caption":"Shortlist online. Decide with a sample.","duration":4.227},{"index":1,"start":5.1,"end":8.385,"text":"Hold it beside your flooring, your fabrics, and your paint.","caption":"Compare it with the room's other finishes.","duration":3.285},{"index":2,"start":9.6,"end":12.843,"text":"Check the color in daylight and under the room's lighting.","caption":"Check daylight and the room's lighting.","duration":3.243},{"index":3,"start":14.1,"end":18.175,"text":"Confirm the pattern scale and repeat before planning the installation.","caption":"Confirm scale and repeat.","duration":4.075}],"energy":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.0014,0.0022,0.0015,0.0019,0.0025,0.5463,0.818,0.6078,0.5899,0.7306,0.7678,0.8636,0.7011,0.0936,0.0101,0.0573,0.0392,0.3234,0.6009,0.7328,0.849,0.916,0.9793,0.9384,0.895,0.8176,0.774,0.684,0.664,0.7179,0.6157,0.5665,0.5571,0.5117,0.3691,0.4505,0.5943,0.6677,0.6378,0.6586,0.6223,0.1551,0.0331,0.1041,0.1534,0.216,0.1506,0.0506,0.2725,0.3231,0.3622,0.3483,0.2679,0.2413,0.3407,0.3765,0.2875,0.2776,0.3049,0.2043,0.4944,0.8111,0.9393,1,1,1,0.7967,0.5085,0.2364,0.0494,0.0467,0.107,0.1432,0.1971,0.2595,0.1987,0.1264,0.1116,0.0626,0.0295,0.0467,0.1025,0.1438,0.2095,0.0784,0.0012,0.0234,0.0149,0.0022,0.0017,0.0018,0.0022,0.0027,0.003,0.0031,0.0039,0.0045,0.0054,0.0066,0.0083,0.0076,0.0097,0.0097,0.0088,0.0069,0.0161,1,1,0.3486,0.2444,0.1537,0.1303,0.1318,0.1237,0.9411,1,1,0.7285,0.3441,0.3915,0.8967,1,0.2822,0.0759,0.163,0.1926,0.7762,0.7846,0.5519,0.4852,0.2137,0.3372,0.3563,0.3329,0.2836,0.2421,0.2078,0.4724,0.8575,0.7684,0.6772,0.738,0.8087,0.7964,0.6664,0.4457,0.0994,0.0105,0.2872,0.5099,0.4037,0.4325,0.436,0.3565,0.307,0.222,0.2794,0.4267,0.5291,0.6441,0.5988,0.6754,0.4052,0.1067,0.019,0.1094,0.1162,0.1778,0.1603,0.0534,0.2256,0.3423,0.378,0.368,0.2237,0.1546,0.2687,0.4783,0.5406,0.3863,0.2849,0.3162,0.2678,0.2547,0.2036,0.188,0.0801,0.7898,0.7792,0.7591,0.7786,0.8492,0.986,0.8794,0.5905,0.394,0.2057,0.1336,0.0867,0.0812,0.0435,0.0503,0.0213,0.0375,0.0418,0.0266,0.0122,0.0032,0.0015,0.0012,0.0013,0.0007,0,0,0,0,0,0,0,0,0,0,0,0,0,0.0011,0.0024,0.0024,0.0029,0.0032,0.0233,0.0672,0.1971,1,1,1,1,1,1,0.5342,0.2458,0.5829,1,0.8136,0.1556,0.1234,0.108,0.1331,0.5486,0.6113,0.6192,0.8265,0.7506,0.6058,0.5084,0.5969,0.9348,0.6343,0.681,0.6075,0.5898,0.5346,0.6018,0.3019,0.219,0.2324,0.3543,0.4669,0.4539,0.2364,0.1705,0.1722,0.157,0.1515,0.1337,0.0897,0.2237,0.3781,0.5626,1,1,1,1,0.8846,0.9308,0.8059,0.736,0.5555,0.3743,0.2852,0.2415,0.1844,0.1308,0.1178,0.0921,0.0904,0.0488,0.0495,0.0071,0.0034,0.005,0.0061,0.007,0.0081,0.0074,0.0073,0.0065,0.0043,0.0029,0.006,0.0059,0.1962,0.3423,0.7129,0.9033,0.5194,0.3005,0.2627,0.2043,0.164,0.1461,0.1102,0.1117,1,1,0.8362,0.8382,0.9066,0.9393,1,1,0.3556,0.2235,0.2036,0.3877,0.5487,0.5743,0.4802,0.4249,0.1988,0.045,0.0909,0.2236,0.5214,0.5086,0.345,0.1509,0.2757,0.2858,0.39,0.5102,0.5927,0.6042,0.5317,0.3825,0.4023,0.4515,0.4744,0.4158,0.148,0.0327,0.0045,0.006,0.2076,0.2237,0.1889,0.157,0.1701,0.5779,0.6864,0.7251,0.6215,0.5967,0.5397,0.3584,0.2205,0.1176,0.089,0.0489,0.0432,0.0144,0.0044,0.0036,0.0138,0.0465,0.0129,0.0037,0.0015,0.0012,0.0006,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.0006,0.0481,0.2661,0.915,0.9709,0.7658,0.9489,1,1,0.4281,0.0399,0.0049,0.0238,0.0057,0.0249,0.0411,0.1567,0.7198,0.2802,0.0409,0.004,0.0022,0.2995,0.2947,0.2571,0.2997,0.3103,0.9399,1,1,0.8337,0.4778,0.4509,0.6073,0.497,0.552,0.496,0.4268,0.2843,0.1659,0.1826,0.2784,0.3495,0.4418,0.4967,0.4801,0.4348,0.3049,0.2041,0.1125,0.2589,0.7607,0.8046,0.5964,0.7033,0.6053,0.6243,0.6188,0.6099,0.3736,0.3748,0.3877,0.3881,0.3444,0.2618,0.2675,0.1921,0.1758,0.1439,0.12,0.0958,0.0603,0.0183,0.003,0.005,0.0021,0.0026,0.0035,0.0044,0.0051,0.0049,0.0049,0.0051,0.0057,0.0081,0.0057,0.0066,0.004,0.027,0.3331,0.5356,0.5952,0.6618,0.659,0.7863,0.7004,0.6776,0.4943,0.5434,0.3612,0.1945,0.3587,0.6035,0.5203,0.2418,0.139,0.1193,0.2507,0.5235,0.3342,0.2333,0.1963,0.1789,0.1722,0.1857,0.2926,0.4433,0.6064,0.7354,0.6695,0.6462,0.5722,0.5254,0.4503,0.4232,0.4576,0.4438,0.4135,0.3034,0.2666,0.4576,0.4512,0.1105,0.119,0.3009,0.366,0.4365,0.4852,0.5092,0.4508,0.5748,0.5667,0.6741,0.6098,0.5233,0.4997,0.3949,0.2821,0.1209,0.0668,0.128,0.0937,0.0715,0.0288,0.0287,0.0278,0.0213,0.0075,0.0048,0.0024,0.0013,0.0009,0.0003,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.0009,0.1437,0.1386,0.7233,0.8509,0.6767,0.5781,0.4463,0.2158,0.1849,0.1824,0.1966,0.1373,0.2526,1,1,1,1,1,1,0.705,0.7533,0.7073,0.596,0.3683,0.5485,0.75,0.1713,0.0382,0.01,0.2151,0.3748,0.4666,0.4397,0.6123,0.8094,0.947,0.8063,0.711,0.5648,0.1915,0.3336,0.4299,0.4436,0.483,0.4823,0.5406,0.528,0.3176,0.3453,0.6133,0.6139,0.6634,0.5212,0.1017,0.009,0.0024,0.1311,0.6499,0.8821,0.7096,0.674,0.6727,0.6453,0.637,0.5834,0.5171,0.4904,0.416,0.3423,0.3169,0.2792,0.299,0.2996,0.3667,0.3815,0.4323,0.4302,0.4229,0.4021,0.3568,0.4291,0.4905,0.4763,0.245,0.0594,0.0087,0.0033,0.005,0.2758,0.1012,0.0695,0.0547,0.3631,0.4844,0.4434,0.3566,0.3081,0.2052,0.1365,0.0827,0.047,0.0129,0.0049,0.0105,0.0033,0.002,0.0027,0.0032,0.0042,0.0046,0.0052,0.006,0.0069,0.0071,0.0076,0.0076,0.0053,0.0069,0.0098,0.0036,0.0015,0.0949,0.8455,0.8829,0.2737,0.2589,0.2624,0.1869,0.4589,1,0.9303,0.9021,0.1779,0.0526,0.006,0.0053,0.2445,0.1393,0.0847,0.0551,0.2522,0.6826,0.8463,0.731,0.7892,0.8757,0.9053,0.9774,0.8982,0.7934,0.6905,0.6659,0.5732,0.5046,0.4559,0.4698,0.4255,0.2772,0.3274,0.3964,0.3661,0.4327,0.4954,0.5044,0.5192,0.4659,0.3514,0.2462,0.648,0.5665,0.1295,0.0048,0.086,0.2478,0.4937,0.2934,0.3336,0.3388,0.5165,0.4373,0.4446,0.5044,0.4648,0.4507,0.3938,0.2174,0.5216,0.7116,0.5028,0.628,0.3144,0.0906,0.1781,0.0994,0.0563,0.0261,0.0294,0.0073,0.0183,0.0052,0.0029,0.0012,0.0012,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"peak":0.6511131525039673}};window.PRODUCTS=[{"id":"fairborn","title":"Fairborn Type 2 | Phillipe Romano","label":"Fairborn Type 2 · Phillipe Romano","url":"https://www.designerwallcoverings.com/products/prw-920735-rif-003","image_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/prw-920735-rif-003.jpg?v=1781716604","file":"assets/products/fairborn.jpg","description":"<p>When light rakes across the embossed surface, it reveals an organic grid-like pattern of broken lines. Although extremely durable — as you’d expect from a Type II vinyl wallcovering named for rock fissures — offering a soft, nature-inspired palette of creams, taupes, blues and browns. A small scale and intricate pattern appear almost seamless, and its 12 two-tone colorways resemble artisanal materials like mosaic</p><br><br>A captivating wallcovering featuring intricate detailing and refined textures, offering a sophisticated backdrop that elevates any interior space. The design evokes timeless elegance with a modern touch. \n#luxuryinteriors #modernwallcoverings #elegantdesign\n","retrieved_at":"2026-09-05","local":"../assets/products/fairborn.jpg"},{"id":"waterperry","title":"Waterperry Ivory/Stone | Sanderson","label":"Waterperry / Ivory–Stone · Sanderson","url":"https://www.designerwallcoverings.com/products/waterperry-ivory-stone-sanderson","image_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/DWAP216280_5340.jpg?v=1788582264","file":"assets/products/waterperry.jpg","description":"<div class=\"sanderson-product\">\n<p>Waterperry in Ivory/Stone is a Sanderson Wallcovering in a botanical/leaves and trees design. Part of the Sanderson heritage collection, this pattern reads in a palette of Alabaster, Linen, Mist. Supplied by the roll; a memo sample is available. An English design-house classic from Sanderson Design Group.</p>\n</div>","retrieved_at":"2026-09-05","local":"../assets/products/waterperry.jpg"},{"id":"wheaton","title":"Wheaton Type 2 | Phillipe Romano","label":"Wheaton Type 2 · Phillipe Romano","url":"https://www.designerwallcoverings.com/products/prw-920166-coa-004-1","image_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/d4639abbf9baa5237707.jpg?v=1785438111","file":"assets/products/wheaton.jpg","description":"<p>Weaving shimmering strands and beguiling texture, emulating the intricacy of our favored grasscloth Tide in the form of a durable Type II vinyl</p>\n","retrieved_at":"2026-09-05","local":"../assets/products/wheaton.jpg"},{"id":"topiary","title":"Topiary View Thyme | Sanderson","label":"Topiary View / Thyme · Sanderson","url":"https://www.designerwallcoverings.com/products/topiary-view-thyme-sanderson","image_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/DHIP217511_f500.jpg?v=1788582179","file":"assets/products/topiary.jpg","description":"<div class=\"sanderson-product\">\n<p>Topiary View in Thyme is a Sanderson Wallcovering in a landscapes design. Part of the Sanderson heritage collection, this pattern reads in a palette of Alabaster, Stone, Oatmeal. Supplied by the roll; a memo sample is available. An English design-house classic from Sanderson Design Group.</p>\n</div>","retrieved_at":"2026-09-05","local":"../assets/products/topiary.jpg"},{"id":"kaya","title":"Kaya - Cream Weave Wallcovering | Malibu Wallcovering","label":"Kaya / Cream Weave · Malibu","url":"https://www.designerwallcoverings.com/products/kaya-cream-weave-wallcovering-malibu-wallpaper","image_url":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/0086435_kaya-cream-weave_650.jpg?v=1776906632","file":"assets/products/kaya.jpg","description":"<p>Elevate your space with the luxurious Kaya Cream Weave wallcovering. This exquisite nonwoven vinyl design features a warm neutral color palette and stunning texture, perfect for creating an elegant ambiance.</p>","retrieved_at":"2026-09-05","local":"../assets/products/kaya.jpg"}];/* Wallo Material Notes: deterministic articulated character acting and real DW samples. */
(() => {
'use strict';
const canvas=document.getElementById('stage'), c=canvas.getContext('2d');
const E=window.EPISODE, P=window.PRODUCTS, ep=E.index;
const ink='#243a3b', chalk='#eeeae2', teal='#284f4b', clay='#a9634d', brass='#b7924f';
const images={}; let telemetry=[];
const clamp=(x,a=0,b=1)=>Math.max(a,Math.min(b,x));
const mix=(a,b,p)=>a+(b-a)*p;
const smooth=p=>{p=clamp(p);return p*p*(3-2*p);};
const ease=(t,a,b)=>smooth((t-a)/(b-a));
const pulse=(t,a,b,c,d)=>ease(t,a,b)*(1-ease(t,c,d));
const point=(x,y)=>({x,y});
const dist=(a,b)=>Math.hypot(a.x-b.x,a.y-b.y);
function group(x,y,r,s,fn){c.save();c.translate(x,y);c.rotate(r);c.scale(s,s);fn();c.restore();}
function path(fn,fill,stroke=null,w=2){c.beginPath();fn(c);if(fill){c.fillStyle=fill;c.fill();}if(stroke){c.lineWidth=w;c.strokeStyle=stroke;c.lineJoin='round';c.lineCap='round';c.stroke();}}
function line(x,y,u,v,col,w=2){path(q=>{q.moveTo(x,y);q.lineTo(u,v);},null,col,w);}
function rect(x,y,w,h,col){c.fillStyle=col;c.fillRect(x,y,w,h);}
function oval(x,y,rx,ry,col,stroke=null,w=2){path(q=>q.ellipse(x,y,rx,ry,0,0,Math.PI*2),col,stroke,w);}
function rr(x,y,w,h,r,col,stroke=null,lw=2){path(q=>q.roundRect(x,y,w,h,r),col,stroke,lw);}
function text(s,x,y,size=28,color=ink,font='Verdana',align='left'){c.font=`${size}px '${font}'`;c.textAlign=align;c.textBaseline='alphabetic';c.fillStyle=color;c.fillText(s,x,y);}
function wrap(s,x,y,max,size=36,color=ink,font='Verdana',lh=size*1.4){
c.font=`${size}px '${font}'`;let row='',lines=[];
for(const word of s.split(' ')){const test=row?row+' '+word:word;if(c.measureText(test).width>max&&row){lines.push(row);row=word;}else row=test;}
if(row)lines.push(row);lines.forEach((v,i)=>text(v,x,y+i*lh,size,color,font));return lines.length;
}
function imageCover(id,x,y,w,h){const im=images[id];if(!im||!im.complete)return;const k=Math.max(w/im.width,h/im.height);c.save();c.beginPath();c.rect(x,y,w,h);c.clip();c.drawImage(im,x+(w-im.width*k)/2,y+(h-im.height*k)/2,im.width*k,im.height*k);c.restore();}
function sample(id,x,y,w=126,h=164,r=0){group(x,y,r,1,()=>{rr(-w/2+7,-h/2+9,w,h,2,'#203a3a20');rect(-w/2-5,-h/2-5,w+10,h+10,'#f9f7f0');imageCover(id,-w/2,-h/2,w,h);line(-w/2,-h/2,w/2,-h/2,'#ffffff',2);});}
function bone(a,b,width,col,shade){
const angle=Math.atan2(b.y-a.y,b.x-a.x),d=dist(a,b);
group(a.x,a.y,angle,1,()=>{rr(-width*.18,-width/2,d+width*.36,width,width*.46,col);path(q=>{q.moveTo(5,width*.26);q.quadraticCurveTo(d*.5,width*.34,d-3,width*.22);},null,shade||'#00000018',2.3);});
}
function solveIK(root,target,l1,l2,pole){
const dx=target.x-root.x,dy=target.y-root.y,raw=Math.hypot(dx,dy),d=clamp(raw,Math.abs(l1-l2)+.01,l1+l2-.01);
const nx=raw?dx/raw:0,ny=raw?dy/raw:1,a=(l1*l1-l2*l2+d*d)/(2*d),h=Math.sqrt(Math.max(0,l1*l1-a*a));
const end=point(root.x+nx*d,root.y+ny*d),joint=point(root.x+nx*a-ny*h*pole,root.y+ny*a+nx*h*pole);
return {root,joint,end,l1,l2};
}
function rotatePoint(p,angle,origin=point(0,-258)){const x=p.x-origin.x,y=p.y-origin.y;return point(origin.x+x*Math.cos(angle)-y*Math.sin(angle),origin.y+x*Math.sin(angle)+y*Math.cos(angle));}
function key(t,frames){if(t<=frames[0][0])return frames[0].slice(1);for(let i=1;i<frames.length;i++)if(t<=frames[i][0]){const p=ease(t,frames[i-1][0],frames[i][0]);return frames[i].slice(1).map((v,j)=>mix(frames[i-1][j+1],v,p));}return frames.at(-1).slice(1);}
function gait(t,start,end,x0,x1){
const p=clamp((t-start)/(end-start));
const lift=(a,b)=>p>a&&p<b?Math.sin(Math.PI*(p-a)/(b-a))*44:0;
const left=mix(x0-30,(x0+x1)/2-30,smooth((p-.08)/.28))+mix(0,(x1-x0)/2,smooth((p-.65)/.3));
const right=mix(x0+30,x1+30,smooth((p-.35)/.3));
return {x:(left+right)/2,feet:[point(left,-lift(.08,.36)-lift(.65,.95)),point(right,-lift(.35,.65))],walking:p>0&&p<1,phase:p};
}
function voiceEnergy(t){const env=E.audio?.energy||[];return env[Math.min(env.length-1,Math.max(0,Math.floor(t*50)))]||0;}
function hand(x,y,r,pose='open',skin='#c89472',prop=null){
group(x,y,r,1,()=>{
if(prop==='sample')sample(E.product,8,-18,115,148,-.04);
if(prop==='fan')for(let k=0;k<3;k++)sample(['waterperry','topiary','kaya'][k],k*17-15,-43,91,130,(k-1)*.18);
if(prop==='report'){rr(-12,-134,135,176,4,'#f8f5e9',ink,2);rect(1,-117,108,5,teal);text('TEST',4,-87,22,ink);text('REPORT',4,-58,22,ink);for(let k=0;k<3;k++)line(4,-35+k*18,97,-35+k*18,'#a4aaa0',2);}
if(prop==='cloth'){path(q=>{q.moveTo(-28,-15);q.lineTo(30,-26);q.lineTo(43,35);q.lineTo(-21,44);q.closePath();},'#c8c5b5',ink,1);line(-17,-4,28,25,'#efeadb',3);}
if(prop==='roller'){line(0,-8,0,-72,ink,7);line(0,-72,37,-72,ink,7);line(37,-72,37,-108,ink,5);rr(-27,-132,127,38,11,'#8b9c8d',ink,2);rr(-9,-9,18,54,7,brass);}
if(prop==='tablet'){rr(-73,-76,145,170,8,ink);rr(-65,-67,129,151,4,'#f4f0e5');text('DW',-45,-33,27,teal,'Georgia');for(let i=0;i<3;i++)imageCover(['waterperry','fairborn','kaya'][i],-47+i*32,-6,27,60);}
if(pose==='point'){bone(point(-4,3),point(34,-18),13,skin);bone(point(0,9),point(16,15),21,skin);oval(-6,9,13,18,skin);}
else{rr(-14,-9,29,42,12,skin);oval(-15,5,7,13,skin);for(let i=0;i<3;i++)line(-7+i*6,14,-7+i*6,28,'#9e685446',1);}
});
}
function head(kind,x,y,t,look,mouth,tilt){
const male=kind==='wallo',skin=male?'#c89472':'#d5a185',dark=male?'#714f3b':'#834e39';
group(x,y,tilt,.82,()=>{
if(!male){oval(-4,-10,51,70,dark);oval(-39,-50,28,28,dark);path(q=>{q.moveTo(-57,-55);q.quadraticCurveTo(-81,-37,-59,-16);},null,'#a86b4b',7);}
// Tapered jaw, defined brow and modest nose maintain adult proportions.
path(q=>{q.moveTo(-41,-40);q.bezierCurveTo(-43,-77,43,-80,44,-34);q.lineTo(40,31);q.quadraticCurveTo(31,66,5,72);q.quadraticCurveTo(-26,65,-36,34);q.closePath();},skin);
path(q=>{q.moveTo(-38,-31);q.lineTo(-32,34);q.quadraticCurveTo(-24,52,-8,57);q.lineTo(-20,21);q.lineTo(-18,-46);q.closePath();},male?'#b58162':'#c39076');
oval(-40,3,9,16,skin);oval(42,2,8,15,skin);
if(male){path(q=>{q.moveTo(-39,-39);q.lineTo(-33,-18);q.lineTo(-30,-54);q.lineTo(31,-54);q.lineTo(43,-25);q.lineTo(42,-55);q.closePath();},dark);}
else{path(q=>{q.moveTo(-40,-27);q.quadraticCurveTo(-24,-63,2,-56);q.quadraticCurveTo(38,-37,43,-19);q.lineTo(38,-58);q.quadraticCurveTo(-4,-89,-37,-55);q.closePath();},dark);for(let i=0;i<4;i++)path(q=>{q.moveTo(-29+i*17,-56);q.quadraticCurveTo(-26+i*17,-43,-13+i*17,-39);},null,'#ae7150',2);}
const blink=(t+(male?.31:1.93))%4.4<.14;
for(const side of [-1,1]){const ex=side*18+look*3,ey=-8;path(q=>{q.moveTo(ex-10,ey);q.quadraticCurveTo(ex,ey-5,ex+10,ey);},null,ink,2.2);if(!blink){oval(ex+look*3,ey+1,3.5,4.3,ink);oval(ex+look*3+1,ey-.3,1.1,1.2,'#eee8dc');}line(ex-10,ey-15,ex+8,ey-17+(side===1?look*2:0),dark,2.5);}
path(q=>{q.moveTo(3+look*5,-5);q.lineTo(8+look*8,15);q.quadraticCurveTo(4+look*5,20,-1+look*5,17);},null,'#98644f',1.8);
if(male){path(q=>{q.moveTo(-20,29);q.quadraticCurveTo(-6,20,4,27);q.quadraticCurveTo(15,23,26,30);q.quadraticCurveTo(17,39,3,34);q.quadraticCurveTo(-9,37,-20,29);},dark);}
if(mouth>.12){oval(4,44,10+mouth*3,2+mouth*6,'#714735');if(mouth>.4)line(-2,41,11,41,'#efe6d7',2);}
else path(q=>{q.moveTo(-7,43);q.quadraticCurveTo(5,48,17,42);},null,'#714735',1.8);
if(male){path(q=>{q.moveTo(-49,-53);q.quadraticCurveTo(-48,-98,10,-95);q.quadraticCurveTo(47,-89,48,-53);q.closePath();},brass);path(q=>{q.moveTo(-49,-53);q.quadraticCurveTo(12,-64,72,-48);q.quadraticCurveTo(77,-35,39,-41);q.lineTo(-49,-43);q.closePath();},'#97773f');line(-27,-73,12,-84,'#d2b270',3);}
else oval(-39,14,3.5,5.5,brass);
});
}
function character(kind,t,settings={}){
const male=kind==='wallo',skin=male?'#c89472':'#d5a185',shirt=male?'#d6d0bc':clay,cloth=male?teal:'#c8c1af';
const walk=settings.walk||gait(t,0,1,settings.x,settings.x);let x=walk.x,y=settings.y||882;
const crouch=settings.crouch||0,lean=settings.lean||0,breath=Math.sin(t*2.1+(male?0:1.2))*1.7;
const hipY=-275+crouch,origin=point(0,hipY);
const bodyPoint=(u,v)=>rotatePoint(point(u,v+crouch+breath),lean,origin);
const leftShoulder=bodyPoint(-62,-469),rightShoulder=bodyPoint(62,-469);
const arms=[solveIK(leftShoulder,point(...settings.left),118,111,-1),solveIK(rightShoulder,point(...settings.right),118,111,1)];
const legs=walk.feet.map((foot,i)=>solveIK(point(i===0?-32:32,hipY),point(foot.x-x,foot.y-13),134,136,i===0?1:-1));
const mouth=male?voiceEnergy(t)*.88:0;
const joints={kind,x,y,arms,legs,lean,crouch,feet:walk.feet,walking:walk.walking,props:settings.props||[]};
telemetry.push(joints);
oval(x,y+6,94-crouch*.1,15,'#233a3b15');
group(x,y,0,settings.scale||1,()=>{
// Back arm and far leg precede torso so elbow overlaps read spatially.
const armDraw=(i)=>{const b=arms[i];bone(b.root,b.joint,39,shirt);bone(b.joint,b.end,27,skin);oval(b.joint.x,b.joint.y,16,16,skin);const sleeve=point(mix(b.root.x,b.joint.x,.77),mix(b.root.y,b.joint.y,.77));bone(b.root,sleeve,43,shirt);line(sleeve.x-8,sleeve.y,sleeve.x+9,sleeve.y,'#ffffff30',2);};
armDraw(0);
legs.forEach((b,i)=>{bone(b.root,b.joint,58,cloth);bone(b.joint,b.end,43,cloth);oval(b.joint.x,b.joint.y,23,23,cloth);line(b.root.x-8,b.root.y+30,b.joint.x-6,b.joint.y-16,male?'#41635b':'#dfd8c8',2);group(b.end.x,b.end.y+10,walk.walking&&walk.feet[i].y<0?-.12:0,1,()=>{path(q=>{q.moveTo(-24,-20);q.lineTo(20,-20);q.lineTo(27,-9);q.quadraticCurveTo(60,-5,56,14);q.lineTo(-24,14);q.closePath();},male?'#655545':'#626b62');line(-24,14,55,14,'#34413d',5);line(4,-8,25,-7,'#c6b69b',2);});});
group(0,hipY,lean,1,()=>{
const dy=crouch+breath-hipY;
path(q=>{q.moveTo(-63,-478+dy);q.quadraticCurveTo(-82,-460+dy,-71,-417+dy);q.lineTo(-60,-266+dy);q.quadraticCurveTo(0,-235+dy,60,-266+dy);q.lineTo(71,-417+dy);q.quadraticCurveTo(79,-460+dy,58,-479+dy);q.closePath();},shirt);
// Structured overall/apron silhouette, stitched seams, pockets and cloth folds.
path(q=>{q.moveTo(-50,-425+dy);q.lineTo(-46,-350+dy);q.lineTo(-61,-290+dy);q.lineTo(-60,-253+dy);q.lineTo(61,-253+dy);q.lineTo(61,-290+dy);q.lineTo(46,-350+dy);q.lineTo(48,-425+dy);q.closePath();},cloth);
for(const s of [-1,1]){line(s*44,-472+dy,s*38,-405+dy,cloth,17);oval(s*39,-414+dy,4.8,4.8,brass);line(s*49,-328+dy,s*41,-312+dy,male?'#59756b':'#a9a18f',2);}
rr(-27,-401+dy,54,56,4,male?'#254540':'#bab39f');path(q=>{q.moveTo(-23,-388+dy);q.quadraticCurveTo(0,-376+dy,24,-388+dy);},null,male?'#6a8778':'#e5dfd0',1.5);
line(-50,-266+dy,52,-266+dy,male?'#1e403b':'#aca590',3);
if(male){rr(-66,-321+dy,20,67,3,'#8d7452');line(-56,-342+dy,-56,-282+dy,brass,6);}
path(q=>{q.moveTo(-17,-509+dy);q.lineTo(-18,-474+dy);q.quadraticCurveTo(0,-454+dy,21,-474+dy);q.lineTo(18,-509+dy);q.closePath();},skin);
});
const hp=bodyPoint(0,-559);head(kind,hp.x,hp.y,t,settings.look||0,mouth,(settings.headTilt||0)+lean*.28);
armDraw(1);
arms.forEach((b,i)=>hand(b.end.x,b.end.y,(settings.wrists||[0,0])[i],(settings.gestures||['open','open'])[i],skin,(settings.props||[])[i]));
});
return joints;
}
function room(t){
rect(-350,-300,1800,1200,'#dfdbd1');rect(-350,765,1800,450,'#c9c2b1');
rect(20,0,240,706,'#e9e7dc');for(let i=0;i<4;i++)line(22+i*59,0,22+i*59,704,'#f7f4e9',7);
rect(318,26,647,727,'#c7bfae');rect(329,37,624,704,'#efede3');
if(ep===1){const a=ease(t,5.1,8);rect(343,49,596,678,'#aab7a4');c.save();c.beginPath();c.rect(343,49,596*a,678);c.clip();imageCover(E.product,343,49,596,678);c.restore();}
else imageCover(E.product,343,49,596,678);
line(340,743,943,743,'#faf7eb',4);line(-350,765,1450,765,'#a79f8d',2);
for(let i=0;i<6;i++)line(-200,814+i*54,1400,814+i*54,'#b6ad9a',1.4);
line(91,765,-170,1220,'#b6ad9a',1.4);line(880,765,1130,1220,'#b6ad9a',1.4);
// Light and shadow move gently with the camera, not with an arbitrary pulse.
path(q=>{q.moveTo(40,706);q.lineTo(263,706);q.lineTo(672,1120);q.lineTo(412,1120);q.closePath();},'#fcf7df28');
// A sample ledge ties the real products to the working material library.
rect(404,685,479,12,'#b29d7a');for(let i=0;i<3;i++)sample(P[(i+ep)%P.length].id,468+i*115,622,88,116,(i-1)*.045);
}
function documentPanel(t){
if(ep!==0)return;
const a=pulse(t,4.2,4.9,14.3,15.0);if(!a)return;
group(640,177+(1-a)*28,0,.88,()=>{
rr(-239,-168,478,336,2,'#f7f4e9',ink,1.5);text('BEFORE YOU SPECIFY',-210,-126,24,teal);
const labels=['PRODUCT / TEST REPORT','CLASSIFICATION','SUBSTRATE + ADHESIVE'];
labels.forEach((label,i)=>{const done=t>5.0+i*2.8;line(-209,-61+i*77,-182,-61+i*77,done?brass:'#bac0b5',3);text(label,-163,-52+i*77,21,ink);});
});
}
function swatchBoard(t){
if(ep!==3&&ep!==4)return;
const a=pulse(t,6.4,7.1,13.4,14);if(!a)return;
group(560,275+(1-a)*25,0,.95,()=>{
rr(-199,-139,398,278,2,'#f5f0e4',ink,1);sample(E.product,-83,-1,155,220,0);
rect(28,-111,137,83,'#c6bea9');rect(28,-16,137,69,'#8c9985');rect(28,64,137,43,'#b89672');
text(ep===3?'COMPARE YOUR FINISHES':'YOUR PROJECT SHORTLIST',-192,-162,23,ink);
});
}
function actionNotes(t){
if(ep!==2)return;const a=pulse(t,8,8.5,14.2,14.8);if(!a)return;
['ABRASION','CLEANABILITY','CARE'].forEach((s,i)=>{const show=ease(t,8+i*.7,8.4+i*.7);if(show){rect(440,120+i*77,440,58,'#f4f0e6');text(s,468,159+i*77,27,ink);}});
}
function performances(t){
let wwalk=gait(t,.2,2.9,126,253),pwalk=gait(t,.5,3.1,949,801);
const w={walk:wwalk,left:[-85,-279],right:[149,-396],wrists:[-.1,-.36],props:[],gestures:['open','open'],look:.6,lean:Math.sin(t*.8)*.012};
const p={walk:pwalk,left:[-130,-402],right:[86,-275],wrists:[.14,.08],props:['sample'],look:-.6,lean:Math.sin(t*.73+1)*.013};
// Presenter gestures are deliberately different for each spoken clause.
const gesture=key(t,[[0,100,-285],[2.8,145,-411],[4.4,165,-440],[5.2,120,-367],[8.7,161,-441],[9.6,149,-378],[12.8,180,-432],[14,125,-360],[17,152,-419],[19.8,126,-383]]);
w.right=gesture;w.wrists[1]=key(t,[[0,0],[3,-.5],[5.2,-.15],[8,-.42],[11,.1],[14,-.4],[18,-.2]])[0];
w.gestures[1]=t>4.5&&t<8.8?'point':'open';w.headTilt=Math.sin(t*.65)*.022;w.look=t>4&&t<9?1:.1;
if(t<3){w.right=[90-Math.sin(t*4)*40,-277];w.left=[-90+Math.sin(t*4)*40,-277];p.left=[-90+Math.sin(t*4)*24,-280];p.props=[];}
if(ep===0){p.props=['report'];p.left=key(t,[[0,-85,-280],[3,-130,-365],[6,-131,-420],[10,-153,-445],[14,-125,-384],[19,-108,-376]]);p.wrists[0]=-.14;p.right=key(t,[[0,90,-277],[8,98,-320],[11,-10,-377],[14,93,-278]]);}
if(ep===1){
p.props=t<8?['roller']:['sample'];p.left=key(t,[[0,-88,-283],[3,-129,-447],[4.5,-142,-495],[5.2,-143,-411],[6,-146,-495],[7,-145,-397],[8,-100,-367],[11,-139,-436],[15,-103,-361]]);p.wrists[0]=.07;
if(t>9.3){p.walk=gait(t,9.3,11.4,801,870);p.look=-1;p.lean=-.06*ease(t,10,11);}
w.props=['sample'];w.left=key(t,[[0,-87,-282],[4,-98,-291],[6,-39,-389],[8,-30,-433],[12,-59,-386],[17,-78,-320]]);w.wrists[0]=-.08;
}
if(ep===2){
p.crouch=pulse(t,4,5.6,8.8,10.2)*113;p.lean=-pulse(t,4,5.4,9,10.3)*.10;
const wipe=pulse(t,5,5.8,8.2,9);p.props=['cloth'];p.left=[-147-wipe*17,-389+p.crouch+wipe*Math.sin(t*6)*42];p.wrists[0]=-.5;
p.right=[85,-284+p.crouch];p.look=-1;
w.props=['sample'];w.left=[-45,-371];w.wrists[0]=-.18;
}
if(ep===3){
p.props=['sample'];p.left=key(t,[[0,-92,-280],[3,-100,-366],[6,-138,-440],[10,-144,-478],[14,-126,-418],[18,-93,-340]]);p.wrists[0]=key(t,[[0,0],[3,-.08],[7,.22],[10,-.18],[13,.16],[17,0]])[0];
w.props=['fan'];w.left=key(t,[[0,-90,-280],[4,-33,-365],[7,-44,-400],[12,-24,-389],[16,-44,-346]]);w.wrists[0]=-.13;
}
if(ep===4){
w.props=['tablet'];w.left=[-43,-341];w.right=key(t,[[0,101,-280],[3,143,-413],[6,158,-444],[8.5,65,-361],[10,34,-340],[11.6,20,-356],[13,144,-411],[17,160,-444],[19,112,-374]]);w.gestures[1]=t>8&&t<12?'point':'open';
p.props=['fan'];p.left=[-129,-400];p.right=key(t,[[0,89,-280],[6,86,-280],[9,-18,-384],[12,76,-285],[17,100,-382],[19,86,-297]]);
}
// Camera-right actor is drawn first so the presenter remains the visual lead.
character('pippa',t,p);character('wallo',t,w);
}
function overlay(t){
rect(0,0,1080,437,chalk);rect(0,1437,1080,483,chalk);
text('DESIGNER WALLCOVERINGS',72,96,25,ink);text('WALLO PRESENTS',1008,96,21,teal,'Verdana','right');
line(72,124,1008,124,'#b9b6aa',1);text(E.kicker,72,178,24,teal);
E.title.split('\n').forEach((s,i)=>text(s,69,270+i*87,73,ink,'Georgia'));
text('MATERIAL NOTES',73,411,22,teal);text('WITH WALLO & PIPPA',1006,411,20,teal,'Verdana','right');
const product=P.find(p=>p.id===E.product);text(product.label,72,1481,25,ink);
line(72,1505,1008,1505,'#b9b6aa',1);
const segment=(E.audio?.segments||[]).filter(s=>t>=s.start).at(-1);
const caption=segment?E.captions[segment.index]:'A wallcovering presentation with Wallo.';
wrap(caption,72,1570,930,38,ink,'Verdana',55);
if(t>=16.8)text(E.takeaway,72,1738,37,teal,'Georgia');
wrap(E.note,72,1794,926,24,'#58635b','Verdana',34);
text('designerwallcoverings.com',72,1870,25,teal);
text(String(ep+1).padStart(2,'0')+' / 05',1008,1870,22,teal,'Verdana','right');
rect(72,1894,936,3,'#c9c7bb');rect(72,1894,936*clamp(t/20),3,teal);
}
function draw(t){
telemetry=[];rect(0,0,1080,1920,chalk);
c.save();c.beginPath();c.rect(0,437,1080,1000);c.clip();
const zoom=1+ease(t,3.3,5)*.08-ease(t,14.2,16)*.06;
c.translate(540,950);c.scale(zoom,zoom);c.translate(-540,-487);
room(t);documentPanel(t);swatchBoard(t);actionNotes(t);performances(t);
c.restore();overlay(t);window.__rigState=telemetry;
}
const driver={t:0};window.__timelines=window.__timelines||{};
window.__timelines.main=gsap.timeline({paused:true}).to(driver,{t:20,duration:20,ease:'none',onUpdate:()=>draw(driver.t)},0);
window.__draw=draw;
window.__ready=Promise.all(P.map(p=>new Promise((resolve,reject)=>{const im=new Image();images[p.id]=im;im.onload=resolve;im.onerror=()=>reject(new Error('Missing product '+p.id));im.src=p.local;}))).then(()=>document.fonts.ready).then(()=>{draw(driver.t);return true;});
draw(0);
})();
</script></body></html>