commit 9a575ba8ec81c9bf19a3ec80c7d48927e3b8ea68 Author: Christian Lawson-Perfect Date: Sun Feb 9 19:58:41 2025 +0000 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a8c7ab0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.make.* +.DS_Store +output/ \ No newline at end of file diff --git a/aperiodical-logo.svg b/aperiodical-logo.svg new file mode 100644 index 0000000..23bfe84 --- /dev/null +++ b/aperiodical-logo.svg @@ -0,0 +1,275 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/index.html b/index.html new file mode 100644 index 0000000..db1cc64 --- /dev/null +++ b/index.html @@ -0,0 +1,130 @@ + + + + Design an aperiodic monotile thing + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ Instructions +

Use the controls to change the colours.

+

Drag the circles around to change the gradients of hue and lightness.

+

Drag the templates for the pieces of the garment that will be cut out.

+

When you're done, click the Finish button and send the file to me.

+
+
+
+ View + + +
+
+ Colours + +
+ +
+ + + +
+ +
+ + + + + + + + + + + + + + + + +
+
+ Tiling + + +
+
+ Pieces + + + + +
+ + + Download +
+
+ + + \ No newline at end of file diff --git a/script.js b/script.js new file mode 100644 index 0000000..a63e9cd --- /dev/null +++ b/script.js @@ -0,0 +1,760 @@ +// derived from https://cs.uwaterloo.ca/~csk/spectre/spectre.js + + +const {PI, cos, sin} = Math; + +const ident = [1,0,0,0,1,0]; + +let pan = {x: -100, y: 100}; + +function radians(degrees) { + return degrees * PI / 180; +} + +let to_screen = [20, 0, 0, 0, -20, 0]; +let lw_scale = 1; + +let sys; + +let scale_centre; +let scale_start; +let scale_ts; + +let reset_but; +let tile_sel; +let shape_sel; +let colscheme_sel; + +let subst_button; +let translate_button; +let scale_button; +let dragging = false; +let uibox = true; + +const spectre = [ + pt(0, 0), + pt(1.0, 0.0), + pt(1.5, -0.8660254037844386), + pt(2.366025403784439, -0.36602540378443865), + pt(2.366025403784439, 0.6339745962155614), + pt(3.366025403784439, 0.6339745962155614), + pt(3.866025403784439, 1.5), + pt(3.0, 2.0), + pt(2.133974596215561, 1.5), + pt(1.6339745962155614, 2.3660254037844393), + pt(0.6339745962155614, 2.3660254037844393), + pt(-0.3660254037844386, 2.3660254037844393), + pt(-0.866025403784439, 1.5), + pt(0.0, 1.0) +]; + +const base_quad = [spectre[3], spectre[5], spectre[7], spectre[11]]; + + +const tile_names = [ + 'Gamma', 'Delta', 'Theta', 'Lambda', 'Xi', + 'Pi', 'Sigma', 'Phi', 'Psi' ]; + +const svg_point = ({x,y}) => `${x.toFixed(3)},${y.toFixed(3)}`; + +function lerp(a,b,t) { + return t*b + (1-t)*a; +} + +function pt( x, y ) +{ + return { x : x, y : y }; +} + +// Affine matrix inverse +function inv( T ) { + const det = T[0]*T[4] - T[1]*T[3]; + return [T[4]/det, -T[1]/det, (T[1]*T[5]-T[2]*T[4])/det, + -T[3]/det, T[0]/det, (T[2]*T[3]-T[0]*T[5])/det]; +}; + +// Affine matrix multiply +function mul( A, B ) +{ + return [A[0]*B[0] + A[1]*B[3], + A[0]*B[1] + A[1]*B[4], + A[0]*B[2] + A[1]*B[5] + A[2], + + A[3]*B[0] + A[4]*B[3], + A[3]*B[1] + A[4]*B[4], + A[3]*B[2] + A[4]*B[5] + A[5]]; +} + +function padd( p, q ) +{ + return { x : p.x + q.x, y : p.y + q.y }; +} + +function psub( p, q ) +{ + return { x : p.x - q.x, y : p.y - q.y }; +} + +function pframe( o, p, q, a, b ) +{ + return { x : o.x + a*p.x + b*q.x, y : o.y + a*p.y + b*q.y }; +} + +// Rotation matrix +function trot( ang ) +{ + const c = cos( ang ); + const s = sin( ang ); + return [c, -s, 0, s, c, 0]; +} + +//Scale matrix +function tscale(x,y) { + return [x,0,0,0,y,0]; +} + +// Translation matrix +function ttrans( tx, ty ) +{ + return [1, 0, tx, 0, 1, ty]; +} + +// Translation matrix moving p to q +function transTo( p, q ) +{ + return ttrans( q.x - p.x, q.y - p.y ); +} + + +// Matrix * point +function transPt( M, P ) +{ + return pt(M[0]*P.x + M[1]*P.y + M[2], M[3]*P.x + M[4]*P.y + M[5]); +} + +class Shape { + constructor( pts, quad) { + this.pts = pts; + this.quad = quad; + + let blah = true; + + this.pts = [pts[pts.length-1]]; + for( const p of pts ) { + const prev = this.pts[this.pts.length-1]; + const v = psub( p, prev ); + const w = pt( -v.y, v.x ); + if( blah ) { + this.pts.push( pframe( prev, v, w, 0.33, 0.6 ) ); + this.pts.push( pframe( prev, v, w, 0.67, 0.6 ) ); + } else { + this.pts.push( pframe( prev, v, w, 0.33, -0.6 ) ); + this.pts.push( pframe( prev, v, w, 0.67, -0.6 ) ); + } + blah = !blah; + this.pts.push( p ); + } + } + + streamSVG( S, stream ) { + const tpts = this.pts.map(p => transPt( S, p )); + + const [a,c,e,b,d,f] = S; + const matS = [a,b,c,d,e,f].map(p=>p.toFixed(3)); + const s = ``; + + stream.push( s ); + } + + bounds(S) { + const points = this.pts.map(p => transPt(S,p)); + return { + minx: Math.min(...points.map(p => p.x)), + miny: Math.min(...points.map(p => p.y)), + maxx: Math.max(...points.map(p => p.x)), + maxy: Math.max(...points.map(p => p.y)), + }; + } + + * flatten(S) { + const points = this.pts.map(p => transPt(S,p)); + const ymax = Math.max(...points.map(p => p.y)); + yield {points, ymax, shape: this}; + } +} + +class Meta +{ + constructor() + { + this.geoms = []; + this.quad = []; + } + + addChild( g, T ) + { + this.geoms.push( { geom : g, xform: T } ); + } + + draw( S ) + { + for( let g of this.geoms ) { + g.geom.draw( mul( S, g.xform ) ); + } + } + + streamSVG( S, stream ) + { + const {minx,miny,maxx,maxy} = this.bounds(S); + + const [a,c,e,b,d,f] = S; + const matS = [a,b,c,d,e,f].map(p=>p.toFixed(3)); + stream.push(``); + const quad_points = this.quad.map(({x,y}) => `${x},${y}`).join(' '); + // stream.push(``); + for( let g of this.geoms ) { + g.geom.streamSVG( g.xform, stream ); + } + stream.push(''); + + } + + bounds(S) { + const sub_bounds = this.geoms.map(g => g.geom.bounds(mul(S,g.xform))); + return { + minx: Math.min(...sub_bounds.map(b=>b.minx)), + miny: Math.min(...sub_bounds.map(b=>b.miny)), + maxx: Math.max(...sub_bounds.map(b=>b.maxx)), + maxy: Math.max(...sub_bounds.map(b=>b.maxy)), + }; + } + + * flatten(S) { + for(let g of this.geoms) { + yield* g.geom.flatten(mul(S, g.xform)); + } + } +} + +function tiles(level, label) { + let quad; + let out = []; + let transform; + if(level == 0) { + transform = ident; + quad = base_quad; + switch(label) { + case 'Delta': + case 'Theta': + case 'Lambda': + case 'Xi': + case 'Pi': + case 'Sigma': + case 'Phi': + case 'Psi': + out.push(ident); + case 'Gamma': + const mystic = new Meta(); + out.push(ident); + out.push(mul( ttrans( spectre[8].x, spectre[8].y ), trot( PI / 6 ) )); + } + } else { + /* + * Each of the subtiles is identical, but rotated and translated. + * + * Produce transformation matrices Ts for each of the subtiles: they're formed by rotating the quad and then matching up a pair of points. + * + * The whole thing is then reflected. + * + * The layout of subtiles depends on the larger tile. + * + */ + const labels = ['Delta', 'Theta', 'Lambda', 'Xi', + 'Pi', 'Sigma', 'Phi', 'Psi']; + + const sublevels = Object.fromEntries(labels.map(label => tiles(level-1, label))); + const subquad = sublevels['Delta'].quad; + + const reflection = tscale(-1,1); + + + // How to get from each subtile to the next. + const t_rules = [ + [60, 3, 1], [0, 2, 0], [60, 3, 1], [60, 3, 1], + [0, 2, 0], [60, 3, 1], [-120, 3, 3] ]; + + + let Ts = [ident]; + let total_ang = 0; + let rot = ident; + let tquad = [...subquad]; + for( const [ang,from,to] of t_rules ) { + total_ang += ang; + if( ang != 0 ) { + rot = trot( radians( total_ang ) ); + tquad = subquad.map(q => transPt(rot,q)); + } + + const ttt = transTo( tquad[to], transPt( Ts[Ts.length-1], subquad[from] ) ); + Ts.push( mul( ttt, rot ) ); + } + + Ts = Ts.map(t => mul(reflection, t)); + + + // Now build the actual supertiles, labelling appropriately. + const super_rules = { + 'Gamma' : ['Pi','Delta','null','Theta','Sigma','Xi','Phi','Gamma'], + 'Delta' : ['Xi','Delta','Xi','Phi','Sigma','Pi','Phi','Gamma'], + 'Theta' : ['Psi','Delta','Pi','Phi','Sigma','Pi','Phi','Gamma'], + 'Lambda' : ['Psi','Delta','Xi','Phi','Sigma','Pi','Phi','Gamma'], + 'Xi' : ['Psi','Delta','Pi','Phi','Sigma','Psi','Phi','Gamma'], + 'Pi' : ['Psi','Delta','Xi','Phi','Sigma','Psi','Phi','Gamma'], + 'Sigma' : ['Xi','Delta','Xi','Phi','Sigma','Pi','Lambda','Gamma'], + 'Phi' : ['Psi','Delta','Psi','Phi','Sigma','Pi','Phi','Gamma'], + 'Psi' : ['Psi','Delta','Psi','Phi','Sigma','Psi','Phi','Gamma'] }; + const super_quad = [ + transPt( Ts[6], subquad[2] ), + transPt( Ts[5], subquad[1] ), + transPt( Ts[3], subquad[2] ), + transPt( Ts[0], subquad[1] ) ]; + } + return {quad, tiles: out}; +} + + +function buildSpectreBase() { + const ret = {}; + + for( lab of ['Delta', 'Theta', 'Lambda', 'Xi', + 'Pi', 'Sigma', 'Phi', 'Psi'] ) { + ret[lab] = new Shape( spectre, base_quad, lab ); + } + + const mystic = new Meta(); + mystic.addChild( new Shape( spectre, base_quad, 'Gamma1' ), ident ); + mystic.addChild( new Shape( spectre, base_quad, 'Gamma2' ), + mul( ttrans( spectre[8].x, spectre[8].y ), trot( PI / 6 ) ) ); + mystic.quad = base_quad; + ret['Gamma'] = mystic; + + return ret; +} + +function buildHatTurtleBase( hat_dominant ) +{ + const r3 = 1.7320508075688772; + const hr3 = 0.8660254037844386; + + function hexPt( x, y ) + { + return pt( x + 0.5*y, -hr3*y ); + } + + function hexPt2( x, y ) + { + return pt( x + hr3*y, -0.5*y ); + } + + const hat = [ + hexPt(-1, 2), hexPt(0, 2), hexPt(0, 3), hexPt(2, 2), hexPt(3, 0), + hexPt(4, 0), hexPt(5,-1), hexPt(4,-2), hexPt(2,-1), hexPt(2,-2), + hexPt( 1, -2), hexPt(0,-2), hexPt(-1,-1), hexPt(0, 0) ]; + + const turtle = [ + hexPt(0,0), hexPt(2,-1), hexPt(3,0), hexPt(4,-1), hexPt(4,-2), + hexPt(6,-3), hexPt(7,-5), hexPt(6,-5), hexPt(5,-4), hexPt(4,-5), + hexPt(2,-4), hexPt(0,-3), hexPt(-1,-1), hexPt(0,-1) + ]; + + const hat_keys = [ + hat[3], hat[5], hat[7], hat[11] + ]; + const turtle_keys = [ + turtle[3], turtle[5], turtle[7], turtle[11] + ]; + + const ret = {}; + + if( hat_dominant ) { + for( lab of ['Delta', 'Theta', 'Lambda', 'Xi', + 'Pi', 'Sigma', 'Phi', 'Psi'] ) { + ret[lab] = new Shape( hat, hat_keys, lab ); + } + + const mystic = new Meta(); + mystic.addChild( new Shape( hat, hat_keys, 'Gamma1' ), ident ); + mystic.addChild( new Shape( turtle, turtle_keys, 'Gamma2' ), + ttrans( hat[8].x, hat[8].y ) ); + mystic.quad = hat_keys; + ret['Gamma'] = mystic; + } else { + for( lab of ['Delta', 'Theta', 'Lambda', 'Xi', + 'Pi', 'Sigma', 'Phi', 'Psi'] ) { + ret[lab] = new Shape( turtle, turtle_keys, lab ); + } + + const mystic = new Meta(); + mystic.addChild( new Shape( turtle, turtle_keys, 'Gamma1' ), ident ); + mystic.addChild( new Shape( hat, hat_keys, 'Gamma2' ), + mul( ttrans( turtle[9].x, turtle[9].y ), trot( PI/3 ) ) ); + mystic.quad = turtle_keys; + ret['Gamma'] = mystic; + } + + return ret; +} + +function buildHexBase() +{ + const hr3 = 0.8660254037844386; + + const hex = [ + pt(0, 0), + pt(1.0, 0.0), + pt(1.5, hr3), + pt(1, 2*hr3), + pt(0, 2*hr3), + pt(-0.5, hr3) + ]; + + const hex_keys = [ hex[1], hex[2], hex[3], hex[5] ]; + + const ret = {}; + + for( lab of ['Gamma', 'Delta', 'Theta', 'Lambda', 'Xi', + 'Pi', 'Sigma', 'Phi', 'Psi'] ) { + ret[lab] = new Shape( hex, hex_keys, lab ); + } + + return ret; +} + +function buildSupertiles( sys ) +{ + // First, use any of the nine-unit tiles in sys to obtain + // a list of transformation matrices for placing tiles within + // supertiles. + + const quad = sys['Delta'].quad; + const R = [-1,0,0,0,1,0]; + + const t_rules = [ + [60, 3, 1], [0, 2, 0], [60, 3, 1], [60, 3, 1], + [0, 2, 0], [60, 3, 1], [-120, 3, 3] ]; + + const Ts = [ident]; + let total_ang = 0; + let rot = ident; + const tquad = [...quad]; + for( const [ang,from,to] of t_rules ) { + total_ang += ang; + if( ang != 0 ) { + rot = trot( radians( total_ang ) ); + for( i = 0; i < 4; ++i ) { + tquad[i] = transPt( rot, quad[i] ); + } + } + + const ttt = transTo( tquad[to], + transPt( Ts[Ts.length-1], quad[from] ) ); + Ts.push( mul( ttt, rot ) ); + } + + for( let idx = 0; idx < Ts.length; ++idx ) { + Ts[idx] = mul( R, Ts[idx] ); + } + + // Now build the actual supertiles, labelling appropriately. + const super_rules = { + 'Gamma' : ['Pi','Delta','null','Theta','Sigma','Xi','Phi','Gamma'], + 'Delta' : ['Xi','Delta','Xi','Phi','Sigma','Pi','Phi','Gamma'], + 'Theta' : ['Psi','Delta','Pi','Phi','Sigma','Pi','Phi','Gamma'], + 'Lambda' : ['Psi','Delta','Xi','Phi','Sigma','Pi','Phi','Gamma'], + 'Xi' : ['Psi','Delta','Pi','Phi','Sigma','Psi','Phi','Gamma'], + 'Pi' : ['Psi','Delta','Xi','Phi','Sigma','Psi','Phi','Gamma'], + 'Sigma' : ['Xi','Delta','Xi','Phi','Sigma','Pi','Lambda','Gamma'], + 'Phi' : ['Psi','Delta','Psi','Phi','Sigma','Pi','Phi','Gamma'], + 'Psi' : ['Psi','Delta','Psi','Phi','Sigma','Psi','Phi','Gamma'] }; + const super_quad = [ + transPt( Ts[6], quad[2] ), + transPt( Ts[5], quad[1] ), + transPt( Ts[3], quad[2] ), + transPt( Ts[0], quad[1] ) ]; + + const ret = {}; + + for( const [lab, subs] of Object.entries( super_rules ) ) { + const sup = new Meta(); + for( let idx = 0; idx < 8; ++idx ) { + if( subs[idx] == 'null' ) { + continue; + } + sup.addChild( sys[subs[idx]], Ts[idx] ); + } + sup.quad = super_quad; + + ret[lab] = sup; + } + + return ret; +} + +/* modified from https://css-tricks.com/converting-color-spaces-in-javascript/ + */ +function hexToHSL(H) { + const [r,g,b] = [0,1,2].map(i=>H.slice(2*i+1,2*i+3)).map(n=>parseInt(n,16)/255); + let cmin = Math.min(r,g,b), + cmax = Math.max(r,g,b), + delta = cmax - cmin, + h = 0, + s = 0, + l = 0; + + if (delta == 0) + h = 0; + else if (cmax == r) + h = ((g - b) / delta) % 6; + else if (cmax == g) + h = (b - r) / delta + 2; + else + h = (r - g) / delta + 4; + + h = Math.round(h * 60); + + if (h < 0) + h += 360; + + l = (cmax + cmin) / 2; + s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1)); + s = +(s * 100).toFixed(1); + l = +(l * 100).toFixed(1); + + return {h,s,l}; +} + +let last_num_iterations; + +function get_settings() { + return Object.fromEntries( + Array.from(document.querySelectorAll('input,textarea')).map(i => [i.id, i.type=='number' ? i.valueAsNumber : i.value]) + ); +} + +function rebuild() { + const svg = document.querySelector('svg'); + const settings = get_settings(); + let sys = buildSpectreBase(false); + + for(let i=0;i finish()); + +function make_download() { + const svg = document.querySelector('svg').cloneNode(true); + document.body.append(svg); + Array.from(svg.querySelectorAll('.spectre')).forEach(s => { + s.setAttribute('fill', getComputedStyle(s).fill); + }); + for(let e of svg.querySelectorAll('#guides, #draggables circle')) { + e.parentElement.removeChild(e); + } + for(let img of svg.querySelectorAll('image.template')) { + img.setAttribute('width', getComputedStyle(img).width); + } + document.body.removeChild(svg); + const f = new File([svg.outerHTML],'aperiodic-monotile-clothes.svg',{type:'image/svg+xml'}); + const url = URL.createObjectURL(f); + document.getElementById('link').setAttribute('href',url); +} + +function set_colours() { + const svg = document.querySelector('svg'); + const fd = new FormData(document.querySelector('form')); + console.log(fd); + + svg.style.setProperty('--y0-lum', document.getElementById('y0-lum').cy.baseVal.value); + svg.style.setProperty('--y1-lum', document.getElementById('y1-lum').cy.baseVal.value); + svg.style.setProperty('--y0-hue', document.getElementById('y0-hue').cy.baseVal.value); + svg.style.setProperty('--y1-hue', document.getElementById('y1-hue').cy.baseVal.value); + + 'back front front-pocket hood label-panel left-sleeve right-sleeve'.split(' ').forEach(w => { + const img = document.getElementById(`hoodie-${w}`); + svg.style.setProperty(`--hoodie-${w}-x`, img.x.baseVal.value); + svg.style.setProperty(`--hoodie-${w}-y`, img.y.baseVal.value); + }); + + svg.style.setProperty('--hue-interpolation', fd.get('hue-interpolation')); + + svg.dataset.template = fd.get('template-name'); + + 'min-hue max-hue sat-scale randomisation template-scale'.split(' ').forEach(w => { + const n = document.getElementById(w).valueAsNumber; + svg.style.setProperty(`--${w}`, n); + const o = document.querySelector(`output[for="${w}"]`); + if(o) { + o.textContent = n; + } + }); +} + +Array.from(document.querySelectorAll('form :is(input,select)')).map(i => { + i.addEventListener('input', set_colours); +}); +set_colours(); + +function getsvg(event) { + let t = event.target; + while(t && t.tagName.toLowerCase() != 'svg') { + t = t.parentElement; + } + return t; +} + +function getcoords(svg, event) { + const point = svg.createSVGPoint(); + point.x = event.clientX; + point.y = event.clientY; + const position = point.matrixTransform(svg.querySelector('#display').getScreenCTM().inverse()); + return position; +} + +function init_draggables() { + let dragging = false; + let off = null; + const svg = document.querySelector('svg'); + + function coord_attributes(element) { + switch(element.tagName.toLowerCase()) { + case 'circle': + return ['cx', 'cy']; + default: + return ['x', 'y']; + } + } + + function get_element_centre(element) { + const [xattr, yattr] = coord_attributes(element); + return {x: element[xattr].baseVal.value, y: element[yattr].baseVal.value}; + } + + function set_element_centre(element, p) { + const [xattr, yattr] = coord_attributes(element); + element.setAttribute(xattr, p.x - off.x); + element.setAttribute(yattr, p.y - off.y); + } + + svg.addEventListener('pointerdown', e => { + if(e.buttons == 1 && e.target.classList.contains('draggable')) { + dragging = e.target; + const p = getcoords(svg, e); + const {x,y} = get_element_centre(dragging); + off = {x: p.x - x, y: p.y - y}; + } else { + dragging = svg; + const z = svg.querySelector('#display').getScreenCTM().a; + off = {x: e.clientX - pan.x*z, y: e.clientY - pan.y*z}; + } + }); + + document.body.addEventListener('pointermove', e => { + if(!dragging) { + return; + } + + if(dragging.classList.contains('draggable')) { + const p = getcoords(svg, e); + set_element_centre(dragging, p); + set_colours(); + } else { + const z = svg.querySelector('#display').getScreenCTM().a; + pan.x = (e.clientX - off.x) / z; + pan.y = (e.clientY - off.y) / z; + setup(); + } + }); + document.body.addEventListener('pointerup', () => { + dragging = false; + }); + + svg.addEventListener('wheel', e => { + const dy = e.deltaY / 1000; + const zoom_input = document.getElementById('zoom'); + zoom_input.value = zoom_input.valueAsNumber - dy; + setup(); + e.preventDefault(); + }); + + set_colours(); +} + +init_draggables(); \ No newline at end of file diff --git a/spectre.svg b/spectre.svg new file mode 100644 index 0000000..40d8c99 --- /dev/null +++ b/spectre.svg @@ -0,0 +1,33 @@ + + diff --git a/style.css b/style.css new file mode 100644 index 0000000..bc88fcf --- /dev/null +++ b/style.css @@ -0,0 +1,102 @@ +:root { + --min-hue: 150; + --max-hue: 260; + --sat-scale: 1.5; + --y0-lum: 800; + --y1-lum: 0; + --y0-hue: 800; + --y1-hue: 0; + --scale-y: 0.01; + --randomisation: 0.2; + --template-scale: 1; + --hue-interpolation: longer hue; +} + + +svg { + max-width: 100%; + max-height: 100%; + border: 1px solid black; +} +body { + + padding: 0; + margin: 0; + display: grid; + grid-template: + "drawing controls" / 1fr 1fr; + gap: 1em; +} +fieldset { + display: grid; + gap: 1em; + grid-template-columns: auto 1fr auto; + + & label { + justify-self: end; + grid-column: 1; + } + & input { + grid-column: 2; + } + & output { + justify-self: start; + grid-column: 3; + } +} +.colour-input-wrapper { + display: inline-block; + background-image: linear-gradient(in hsl longer hue to right, hsl(0, 100%, 50%), hsl(360, 100%, 50%)); + + & input { + width: 100%; + } +} +.spectre { + --sat: calc(var(--t) * var(--sat-scale) * 1%); + --min-r: calc(1 - var(--randomisation)); + --max-r: calc(1 + var(--randomisation)); + --random: calc(var(--min-r) + (var(--max-r) - var(--min-r)) * var(--r)); + --lum: calc(100% * ((var(--y) - var(--y0-lum)) / (var(--y1-lum) - var(--y0-lum))) * var(--random)); + --mix: calc(100% * ((var(--y) - var(--y0-hue)) / (var(--y1-hue) - var(--y0-hue)))); + + --col1: hsl(var(--min-hue), var(--sat), var(--lum)); + --col2: hsl(var(--max-hue), var(--sat), var(--lum)); + fill: color-mix(in hsl var(--hue-interpolation), var(--col1), var(--col2) var(--mix)); +} + +#draggables circle { + r: calc(5px / var(--zoom)); + stroke-width: calc(0.5px / var(--zoom)); + stroke: white; + fill: black; + + &#y1-lum { + fill: white; + stroke: black; + } + + &#y0-hue { + fill: hsl(var(--min-hue), 100%, 50%); + } + + &#y1-hue { + fill: hsl(var(--max-hue), 100%, 50%); + } +} + +#draggables image { + width: calc(20px * var(--template-scale)); +} + +svg:not([data-template="hoodie"]) #draggables image.template.hoodie { + display: none; +} + +svg:not([data-template="tshirt"]) #draggables image.template.tshirt { + display: none; +} + +#link { + display: none; +} \ No newline at end of file diff --git a/template/TShirt_Back.png b/template/TShirt_Back.png new file mode 100644 index 0000000..d5976ff Binary files /dev/null and b/template/TShirt_Back.png differ diff --git a/template/TShirt_Front.png b/template/TShirt_Front.png new file mode 100644 index 0000000..5fde41f Binary files /dev/null and b/template/TShirt_Front.png differ diff --git a/template/TShirt_Left_Sleeve.png b/template/TShirt_Left_Sleeve.png new file mode 100644 index 0000000..b129c81 Binary files /dev/null and b/template/TShirt_Left_Sleeve.png differ diff --git a/template/TShirt_Right_Sleeve.png b/template/TShirt_Right_Sleeve.png new file mode 100644 index 0000000..26fb427 Binary files /dev/null and b/template/TShirt_Right_Sleeve.png differ diff --git a/template/hoodie_back_template.png b/template/hoodie_back_template.png new file mode 100644 index 0000000..a8555fc Binary files /dev/null and b/template/hoodie_back_template.png differ diff --git a/template/hoodie_front_pocket_template.png b/template/hoodie_front_pocket_template.png new file mode 100644 index 0000000..5eaceb3 Binary files /dev/null and b/template/hoodie_front_pocket_template.png differ diff --git a/template/hoodie_front_template.png b/template/hoodie_front_template.png new file mode 100644 index 0000000..b757bec Binary files /dev/null and b/template/hoodie_front_template.png differ diff --git a/template/hoodie_hood_template.png b/template/hoodie_hood_template.png new file mode 100644 index 0000000..3218b3d Binary files /dev/null and b/template/hoodie_hood_template.png differ diff --git a/template/hoodie_label_panel_template.png b/template/hoodie_label_panel_template.png new file mode 100644 index 0000000..fb97bb4 Binary files /dev/null and b/template/hoodie_label_panel_template.png differ diff --git a/template/hoodie_left_sleeve_template.png b/template/hoodie_left_sleeve_template.png new file mode 100644 index 0000000..8d60f8c Binary files /dev/null and b/template/hoodie_left_sleeve_template.png differ diff --git a/template/hoodie_right_sleeve_template.png b/template/hoodie_right_sleeve_template.png new file mode 100644 index 0000000..c340c24 Binary files /dev/null and b/template/hoodie_right_sleeve_template.png differ