first commit

This commit is contained in:
Christian Lawson-Perfect 2025-02-09 19:58:41 +00:00
commit 9a575ba8ec
17 changed files with 1303 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
.make.*
.DS_Store
output/

275
aperiodical-logo.svg Normal file
View file

@ -0,0 +1,275 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="30cm"
height="30cm"
id="svg3055"
version="1.1"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
inkscape:export-filename="/home/christian/Pictures/t-shirts/aperiodical-logo.png"
inkscape:export-xdpi="500.04132"
inkscape:export-ydpi="500.04132"
sodipodi:docname="aperiodical-logo.svg">
<defs
id="defs3057">
<clipPath
id="clipPath6473"
clipPathUnits="userSpaceOnUse">
<path
inkscape:connector-curvature="0"
id="path6471"
d="M 14,13.755905 H 179 V 178.7559 H 14 Z" />
</clipPath>
<mask
id="mask6475"
height="1"
width="1"
y="0"
x="0"
maskUnits="userSpaceOnUse">
<g
id="g6481">
<g
id="g6479"
clip-path="url(#clipPath6473)">
<path
inkscape:connector-curvature="0"
id="path6477"
style="fill:#000000;fill-opacity:0.98999999;fill-rule:nonzero;stroke:none"
d="M 14,13.755905 H 179 V 178.75591 H 14 Z" />
</g>
</g>
</mask>
<clipPath
id="clipPath6489"
clipPathUnits="userSpaceOnUse">
<path
inkscape:connector-curvature="0"
id="path6487"
d="M 14,13.755905 H 179 V 178.7559 H 14 Z" />
</clipPath>
<clipPath
id="clipPath6493"
clipPathUnits="userSpaceOnUse">
<path
inkscape:connector-curvature="0"
id="path6491"
d="M 14,14 H 179 V 179 H 14 Z" />
</clipPath>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.48821792"
inkscape:cx="766.91375"
inkscape:cy="418.92832"
inkscape:current-layer="g6802"
showgrid="true"
inkscape:document-units="cm"
inkscape:grid-bbox="true"
borderlayer="true"
inkscape:window-width="1853"
inkscape:window-height="1016"
inkscape:window-x="67"
inkscape:window-y="27"
inkscape:window-maximized="1"
units="cm" />
<metadata
id="metadata3060">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer"
transform="translate(0,1069.8582)">
<g
transform="matrix(1.0666667,0,0,1.0666667,140.35172,-1149.7625)"
id="layer1-7"
inkscape:label="Layer 1">
<path
sodipodi:nodetypes="csc"
inkscape:connector-curvature="0"
id="circle4362"
d="m 296.42131,912.47121 c 0,41.94782 23.22002,77.83443 63.74699,88.65949 55.63769,14.8615 115.75463,-26.94246 131.25687,-45.49392"
style="opacity:0.98999999;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.33398107;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<g
id="g6802"
transform="translate(21.752994,-20.17165)">
<g
aria-label="aperiodical.com"
transform="matrix(5.5903503,0,0,5.5903503,-1700.6466,-4656.1358)"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:21.19848442px;line-height:0%;font-family:montserrat;-inkscape-font-specification:'montserrat, Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="text4338">
<path
d="m 292.83429,932.37217 c -1.10093,0.0554 -3.05483,0.87544 -2.94718,3.01378 0.065,1.29148 0.46466,2.90571 1.584,4.0592 -0.69014,0.20455 -1.22249,0.59218 -1.11591,2.70934 l 0.0298,0.59281 c 0.0224,0.44461 0.15794,0.60758 0.66606,0.582 l 0.35991,-0.0181 c 0.50812,-0.0256 0.58109,-0.26274 0.5619,-0.64383 -0.0396,-1.20785 0.0546,-1.44606 0.54044,-1.49175 l 4.42488,-0.22276 c 2.837,-0.14282 3.4163,-2.54922 3.30865,-4.68756 -0.0618,-1.22795 -0.23938,-3.49012 -1.89077,-3.40699 -0.65632,0.033 -1.07641,0.54237 -1.04657,1.13518 0.0192,0.38109 0.28725,1.06803 1.24104,1.04124 0.0361,0.29534 0.0765,0.67536 0.0999,1.14114 0.032,0.63515 0.0449,1.31371 -0.26506,1.9024 -0.42528,0.82797 -1.07947,0.90335 -1.56642,0.92787 l -0.48695,0.0245 c -0.14815,-2.94286 -0.97834,-6.78531 -3.49777,-6.65847 z m 0.14146,1.96683 c 0.76219,-0.0384 1.88457,0.75414 2.1707,4.75133 l -1.5667,0.0789 c -1.58788,0.0799 -1.97488,-1.70474 -2.01964,-2.59395 -0.0789,-1.5667 0.80167,-2.20534 1.41564,-2.23625 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'CMU Concrete';-inkscape-font-specification:'CMU Concrete Bold'"
id="path892"
inkscape:connector-curvature="0" />
<path
d="m 288.00037,944.77432 -1.589,0.26585 0.84653,5.05971 1.589,-0.26585 -0.26235,-1.5681 3.63797,-0.60866 c -0.53255,1.05629 -0.89068,2.38431 -0.62482,3.9733 0.38478,2.29987 2.24004,4.26774 5.8571,3.66258 2.90619,-0.48623 4.77311,-2.17414 4.38482,-4.49491 -0.19589,-1.17084 -0.70238,-3.04198 -1.88818,-4.47706 l 1.10811,-0.1854 -0.80716,-3.41132 -1.589,0.26585 0.0385,0.22999 c 0.1854,1.10811 0.11584,1.33468 -0.48699,1.45704 l -9.95215,1.66507 z m 6.752,2.45969 2.9271,-0.48973 c 0.14636,-0.0245 0.31362,-0.0525 0.45306,0.0102 0.48454,0.19835 1.47485,1.10732 1.7512,2.75904 0.32182,1.92352 -0.76459,3.39487 -2.83446,3.74118 -2.21624,0.3708 -3.71907,-0.90378 -4.0374,-2.8064 -0.27285,-1.63081 0.86237,-3.06734 1.7405,-3.21426 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'CMU Concrete';-inkscape-font-specification:'CMU Concrete Bold'"
id="path894"
inkscape:connector-curvature="0" />
<path
sodipodi:type="inkscape:offset"
inkscape:radius="0"
inkscape:original="M 299.94922 956.58398 C 299.2853 956.52897 298.5784 956.60299 297.8457 956.81641 C 294.69103 957.73527 293.54297 960.54351 294.38477 963.43359 C 295.17321 966.1405 297.72032 966.70122 298.31055 966.5293 C 298.6769 966.4226 298.59701 966.07029 298.48438 965.68359 L 298.43164 965.5 C 298.39014 965.35753 298.34243 965.19404 298.20508 965.10156 C 297.92449 964.89626 296.64982 965.0691 296.19336 963.50195 C 295.17372 960.00128 297.56828 959.128 298.2832 958.85352 L 300.19141 965.40625 C 300.31847 965.76667 300.4404 965.95289 300.94922 965.80469 C 302.92343 965.22966 305.15594 963.65199 304.17188 960.27344 C 303.53164 958.07534 301.94097 956.74903 299.94922 956.58398 z M 299.61523 958.57617 C 301.88268 958.40148 302.49235 960.18763 302.64648 960.7168 C 303.23336 962.73172 301.87959 963.54526 301.16211 963.88672 L 299.61523 958.57617 z "
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'CMU Concrete';-inkscape-font-specification:'CMU Concrete Bold'"
id="path896"
d="m 299.94922,956.58398 c -0.66392,-0.055 -1.37082,0.019 -2.10352,0.23243 -3.15467,0.91886 -4.30273,3.7271 -3.46093,6.61718 0.78844,2.70691 3.33555,3.26763 3.92578,3.09571 0.36635,-0.1067 0.28646,-0.45901 0.17383,-0.84571 L 298.43164,965.5 c -0.0415,-0.14247 -0.0892,-0.30596 -0.22656,-0.39844 -0.28059,-0.2053 -1.55526,-0.0325 -2.01172,-1.59961 -1.01964,-3.50067 1.37492,-4.37395 2.08984,-4.64843 l 1.90821,6.55273 c 0.12706,0.36042 0.24899,0.54664 0.75781,0.39844 1.97421,-0.57503 4.20672,-2.1527 3.22266,-5.53125 -0.64024,-2.1981 -2.23091,-3.52441 -4.22266,-3.68946 z m -0.33399,1.99219 c 2.26745,-0.17469 2.87712,1.61146 3.03125,2.14063 0.58688,2.01492 -0.76689,2.82846 -1.48437,3.16992 z" />
<path
d="m 296.19102,968.84316 2.05225,5.06957 1.47371,-0.59658 -0.73976,-1.82741 2.55443,-1.03407 c 0.13755,-0.0557 0.28679,-0.13897 0.42433,-0.19465 1.86671,-0.75568 3.41111,-0.44322 4.33378,0.87561 -0.76165,0.49129 -0.60161,1.11259 -0.51411,1.32873 0.20682,0.51089 0.77902,0.85099 1.38815,0.60441 0.84493,-0.34205 1.10033,-1.40596 0.75033,-2.27054 l -0.008,-0.0196 c -0.23864,-0.58949 -0.88058,-1.61032 -2.62195,-1.95738 l 0.67978,-0.29806 0.88422,-0.35795 -1.4809,-3.03677 -1.49336,0.60454 0.0716,0.17685 c 0.47727,1.17897 0.42675,1.33664 -0.24134,1.60709 l -5.4429,2.20338 -0.59659,-1.47371 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'CMU Concrete';-inkscape-font-specification:'CMU Concrete Bold'"
id="path898"
inkscape:connector-curvature="0" />
<path
d="m 312.20874,972.50179 c -0.64265,0.32631 -0.99796,1.12486 -0.61407,1.88092 0.32632,0.64265 1.14377,0.98837 1.88093,0.61407 0.73716,-0.3743 0.96888,-1.22897 0.63297,-1.89052 -0.3743,-0.73716 -1.25718,-0.93079 -1.89983,-0.60447 z m -12.24442,5.29002 2.22661,4.38514 1.41761,-0.71981 -0.64303,-1.26639 7.61729,-3.86777 -1.7531,-2.93756 -1.43652,0.7294 0.0768,0.15122 c 0.54706,1.07738 0.49123,1.24837 -0.13252,1.56509 l -5.2357,2.65848 -0.71981,-1.41761 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'CMU Concrete';-inkscape-font-specification:'CMU Concrete Bold'"
id="path900"
inkscape:connector-curvature="0" />
<path
d="m 306.96975,980.99314 c -2.52423,1.63997 -3.05806,4.51475 -1.26795,7.27007 1.8132,2.79087 4.6453,3.45355 7.16953,1.81358 2.63088,-1.70926 3.24205,-4.65957 1.46349,-7.39711 -1.80166,-2.77309 -4.73419,-3.39581 -7.36507,-1.68654 z m 1.3629,1.59193 c 1.52876,-0.99322 3.21377,-1.27901 4.6805,0.97857 1.44363,2.22203 0.46844,3.63927 -1.04254,4.62095 -1.63542,1.06251 -3.37998,1.41227 -4.84672,-0.84531 -1.45518,-2.2398 -0.40888,-3.70324 1.20876,-4.75421 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'CMU Concrete';-inkscape-font-specification:'CMU Concrete Bold'"
id="path902"
inkscape:connector-curvature="0" />
<path
d="m 313.74193,991.21445 c -2.92175,2.61605 -2.23626,5.18048 -1.01347,6.56111 1.18062,1.33301 2.49637,1.92336 3.77041,2.21084 l -1.01563,0.8995 2.42654,2.5799 1.19019,-1.0541 -0.16866,-0.1905 c -0.78708,-0.8886 -0.77439,-1.0981 -0.26657,-1.5479 l 9.22002,-8.16592 -2.47959,-2.44795 -1.20606,1.06818 0.0843,0.0952 c 0.78708,0.88867 0.89227,1.13532 0.39851,1.60095 l -2.30104,2.03798 c -0.24572,-1.39647 -1.00377,-2.76393 -1.94546,-3.82717 -1.95364,-2.20582 -4.63052,-1.64729 -6.69352,0.17986 z m 1.36153,1.56924 c 1.36475,-1.20873 3.11397,-1.8235 4.92707,0.22362 1.02602,1.15846 0.987,2.29739 0.95299,2.61069 -0.034,0.3133 -0.073,0.4611 -0.37452,0.72815 l -2.18995,1.93959 c -0.72999,0.64653 -2.65919,0.51456 -3.85387,-0.83433 -1.04007,-1.17432 -1.55646,-2.81246 0.53828,-4.66772 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'CMU Concrete';-inkscape-font-specification:'CMU Concrete Bold'"
id="path904"
inkscape:connector-curvature="0" />
<path
d="m 328.8858,995.29632 c -0.47936,0.53823 -0.51894,1.41137 0.11427,1.97532 0.53823,0.47937 1.42547,0.50312 1.97532,-0.11426 0.54986,-0.61738 0.45389,-1.4977 -0.10017,-1.99116 -0.61738,-0.54986 -1.51006,-0.40813 -1.98942,0.1301 z m -9.47149,9.39158 3.67262,3.2709 1.05741,-1.1873 -1.06062,-0.9446 5.68186,-6.37958 -2.70376,-2.09579 -1.07152,1.2031 0.12664,0.11279 c 0.90233,0.80364 0.91272,0.98321 0.44745,1.50558 l -3.90539,4.385 -1.18727,-1.0574 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'CMU Concrete';-inkscape-font-specification:'CMU Concrete Bold'"
id="path906"
inkscape:connector-curvature="0" />
<path
d="m 327.33674,1005.0768 c -1.85964,2.5792 -1.22912,5.4381 1.24699,7.2234 2.63086,1.8968 4.98755,0.042 5.13632,-0.1645 0.13638,-0.1892 0.006,-0.3352 -0.21712,-0.4963 l -0.22354,-0.1612 c -0.44707,-0.3223 -0.48146,-0.3471 -0.60463,-0.3575 -0.35231,-0.019 -1.27653,1.118 -2.61776,0.151 -1.27244,-0.9174 -2.63442,-2.7618 -1.02273,-4.9972 1.81005,-2.5105 3.69354,-1.5706 4.86281,-0.7276 0.34391,0.248 0.67541,0.5131 1.00692,0.7783 l 0.0172,0.012 c -0.45952,0.8185 0.004,1.3356 0.24467,1.5092 0.48147,0.3471 1.1497,0.254 1.53403,-0.2791 0.96702,-1.3412 -0.85287,-2.7317 -1.91897,-3.5004 -3.50782,-2.5291 -6.22922,-0.6757 -7.44419,1.0095 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'CMU Concrete';-inkscape-font-specification:'CMU Concrete Bold'"
id="path908"
inkscape:connector-curvature="0" />
<path
d="m 334.80675,1012.9089 c -0.52252,0.9706 -0.83244,3.0668 1.05279,4.0817 1.1386,0.6129 2.72658,1.1067 4.29294,0.7461 -0.18227,0.6964 -0.12622,1.3525 1.74035,2.3574 l 0.52263,0.2813 c 0.39198,0.211 0.6016,0.1794 0.84276,-0.2685 l 0.17082,-0.3174 c 0.24116,-0.4479 0.076,-0.6331 -0.25995,-0.814 -1.05389,-0.5914 -1.20897,-0.7953 -0.99652,-1.2346 l 2.10012,-3.9012 c 1.34648,-2.5012 -0.41247,-4.2425 -2.2977,-5.2574 -1.0826,-0.5828 -3.10997,-1.602 -3.89375,-0.1461 -0.3115,0.5786 -0.0932,1.2017 0.42943,1.4831 0.33599,0.1809 1.06248,0.3071 1.53333,-0.5228 0.27136,0.122 0.61739,0.2842 1.02804,0.5053 0.55997,0.3015 1.14722,0.6417 1.49041,1.2116 0.48823,0.7925 0.21406,1.3912 -0.0171,1.8206 l -0.23111,0.4293 c -2.59452,-1.3968 -6.31178,-2.6757 -7.50754,-0.4544 z m 1.756,0.8971 c 0.36174,-0.6719 1.62083,-1.2219 5.18883,0.6025 l -0.74358,1.3813 c -0.75363,1.3999 -2.48089,0.8071 -3.26485,0.3851 -1.38125,-0.7436 -1.47181,-1.8276 -1.1804,-2.3689 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'CMU Concrete';-inkscape-font-specification:'CMU Concrete Bold'"
id="path910"
inkscape:connector-curvature="0" />
<path
d="m 344.02905,1020.9695 4.70793,1.9286 0.60268,-1.4712 -1.47122,-0.6027 4.98223,-12.1621 -3.32509,-1.1101 -0.61073,1.4908 0.21578,0.088 c 1.05928,0.4339 1.18762,0.624 0.97419,1.2009 l -4.00185,9.7689 -1.47123,-0.6027 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'CMU Concrete';-inkscape-font-specification:'CMU Concrete Bold'"
id="path912"
inkscape:connector-curvature="0" />
<path
d="m 351.76642,1022.4491 c -0.22365,0.6851 0.10339,1.5278 0.90948,1.7909 0.66502,0.217 1.51422,-0.1302 1.77075,-0.9161 0.25654,-0.7859 -0.21073,-1.5405 -0.8959,-1.7642 -0.68518,-0.2236 -1.5278,0.1034 -1.78433,0.8894 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'CMU Concrete';-inkscape-font-specification:'CMU Concrete Bold'"
id="path914"
inkscape:connector-curvature="0" />
<path
d="m 357.76823,1020.6593 c -0.70514,3.1006 0.99145,5.4865 3.96803,6.1635 3.16262,0.7192 4.6081,-1.9087 4.66451,-2.1567 0.0517,-0.2274 -0.12493,-0.3111 -0.39364,-0.3722 l -0.26872,-0.061 c -0.53744,-0.1222 -0.57878,-0.1316 -0.69624,-0.093 -0.3317,0.1202 -0.73874,1.5277 -2.35106,1.161 -1.52963,-0.3478 -3.50358,-1.5142 -2.89246,-4.2014 0.68634,-3.0179 2.78728,-2.8879 4.19289,-2.5683 0.41341,0.094 0.82212,0.2088 1.23084,0.3234 h 0.0207 c -0.10349,0.9331 0.52506,1.2282 0.81445,1.294 0.57878,0.1316 1.15763,-0.215 1.30336,-0.8558 0.36667,-1.6123 -1.85166,-2.182 -3.13324,-2.4735 -4.21682,-0.959 -5.9987,1.8098 -6.45939,3.8355 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'CMU Concrete';-inkscape-font-specification:'CMU Concrete Bold'"
id="path916"
inkscape:connector-curvature="0" />
<path
d="m 367.63255,1022.9958 c -0.32106,2.993 1.57189,5.2215 4.83892,5.5719 3.30917,0.355 5.61065,-1.4235 5.93172,-4.4165 0.33463,-3.1195 -1.5681,-5.4556 -4.81404,-5.8038 -3.2881,-0.3528 -5.62196,1.5289 -5.9566,4.6484 z m 2.095,-0.053 c 0.19445,-1.8126 1.03625,-3.3 3.7131,-3.0129 2.63469,0.2827 3.11852,1.9336 2.92633,3.7252 -0.20802,1.9391 -1.03778,3.5131 -3.71463,3.2259 -2.65577,-0.2849 -3.13056,-2.0201 -2.9248,-3.9382 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'CMU Concrete';-inkscape-font-specification:'CMU Concrete Bold'"
id="path918"
inkscape:connector-curvature="0" />
<path
d="m 379.55366,1029.3426 5.12524,-0.2217 -0.0687,-1.5884 -1.5884,0.069 -0.17409,-4.0239 c -0.10078,-2.3297 1.31463,-2.9638 2.45828,-3.0133 2.05525,-0.068 2.28159,0.75 2.32648,1.7878 l 0.21807,5.0405 -1.5884,0.069 0.0687,1.5884 5.12524,-0.2217 -0.0687,-1.5884 -1.5884,0.069 -0.17408,-4.0239 c -0.10079,-2.3297 1.33581,-2.9647 2.45828,-3.0133 1.97053,-0.064 2.25582,0.6451 2.3053,1.7887 l 0.21806,5.0405 -1.5884,0.069 0.0687,1.5884 5.12524,-0.2217 -0.0687,-1.5884 -1.5884,0.069 -0.21989,-5.0828 c -0.0623,-1.4402 -0.27642,-3.4467 -3.45322,-3.3092 -2.20258,0.095 -3.20563,1.433 -3.56669,1.9154 -0.30739,-0.7294 -0.90201,-1.7221 -3.21049,-1.6222 -2.13904,0.092 -3.16418,1.4099 -3.48563,1.827 l -0.0724,-1.6731 -3.42086,0.3814 0.0696,1.6096 0.19061,-0.01 c 1.27072,-0.055 1.40237,0.045 1.43352,0.7655 l 0.2538,5.8665 -1.5884,0.069 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'CMU Concrete';-inkscape-font-specification:'CMU Concrete Bold'"
id="path920"
inkscape:connector-curvature="0" />
</g>
<g
id="g4352"
transform="matrix(4.5334558,-0.15331679,0.15331679,4.5334558,-251.17595,-3633.3261)">
<circle
id="path3958"
style="fill:#ffffff;fill-opacity:1;stroke:#000080;stroke-width:3.38588524;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
cx="391.24078"
cy="854.32007"
r="90.433044"
transform="rotate(17.239481)" />
<path
sodipodi:end="2.6410428"
sodipodi:start="0.56114361"
d="M 147.49559,948.64735 A 30.697403,30.697403 0 0 1 120.57583,962.9949 30.697403,30.697403 0 0 1 94.574313,947.04351 l 26.931407,-14.73193 z"
sodipodi:ry="30.697403"
sodipodi:rx="30.697403"
sodipodi:cy="932.31158"
sodipodi:cx="121.50572"
id="path3970"
style="fill:#ffcc00;stroke:none"
sodipodi:type="arc" />
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path3966"
d="M 40.853279,976.2691 124.10437,931.28934"
style="fill:none;stroke:#000080;stroke-width:3.30837727;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.12878418;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 123.30841,929.82718 0.0405,3.72615 72.48488,47.34908 1.80202,-2.77381 z"
id="path3968"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc" />
<path
inkscape:connector-curvature="0"
id="path3962"
d="M 42.058202,975.48597 197.695,979.70377"
style="fill:none;stroke:#000000;stroke-width:3.30837727;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:6.61675437, 3.30837718;stroke-dashoffset:0;stroke-opacity:1" />
<path
sodipodi:type="arc"
style="fill:#ff0000;stroke:none"
id="path3975"
sodipodi:cx="104.40775"
sodipodi:cy="843.73779"
sodipodi:rx="35.758743"
sodipodi:ry="35.758743"
d="m 123.86203,873.74144 a 35.758743,35.758743 0 0 1 -33.947564,2.6863 l 14.493284,-32.68995 z"
sodipodi:start="0.99554875"
sodipodi:end="1.9881115" />
<path
inkscape:transform-center-y="26.582871"
inkscape:transform-center-x="15.139863"
sodipodi:end="2.0160023"
sodipodi:start="0.99554875"
transform="rotate(22.319341)"
d="m 504.37304,759.55705 a 35.758743,35.758743 0 0 1 -34.85356,2.26941 l 15.39928,-32.27305 z"
sodipodi:ry="35.758743"
sodipodi:rx="35.758743"
sodipodi:cy="729.55341"
sodipodi:cx="484.91876"
id="path3979"
style="fill:#000080;stroke:none"
sodipodi:type="arc" />
<path
inkscape:connector-curvature="0"
id="path3964"
d="M 42.048374,975.54047 104.05984,844.7342 196.42961,978.86021 171.54466,859.91828 Z"
style="fill:none;stroke:#000000;stroke-width:1.98502636;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="ccccc" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 22 KiB

130
index.html Normal file
View file

@ -0,0 +1,130 @@
<!doctype html>
<html lang="en">
<head>
<title>Design an aperiodic monotile thing</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<script src="script.js" defer></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<section id="drawing">
<svg viewBox="-100 -100 200 200" xmlns="http://www.w3.org/2000/svg">
<defs>
<path id="spectre" class="spectre"
d="m 1.5,-0.8660254 c 0.5735127,0.00665 0.2925127,0.4933532 0.8660254,0.5 0.281,0.5 -0.281,0.5 0,1 0.5,-0.281 0.5,0.281 1,0 0.4933531,0.2925127 0.00665,0.5735127 0.5,0.8660254 C 3.5735127,1.9933532 3.2925127,1.50665 3,2.0000001 2.4264873,1.9933501 2.7074873,1.5066468 2.1339746,1.5 c -0.00665,0.5735128 -0.4933532,0.2925128 -0.5,0.8660255 -0.5,0.281 -0.5,-0.281 -1,0 -0.5,0.281 -0.5,-0.281 -1,0 C -0.8593785,2.0735128 -0.3726754,1.7925128 -0.8660254,1.5 -0.5735127,1.0066468 -0.2925127,1.49335 0,1 -0.281,0.5 0.281,0.5 0,0 0.5,-0.281 0.5,0.281 0.99999998,0 1.00665,-0.5735127 1.4933531,-0.2925127 1.5,-0.8660254 Z"
stroke="black"
stroke-width="0.1"
stroke-opacity="0.5"
stroke-linejoin="round"
fill="currentColor"></path>
</defs>
<g id="display">
<g id="board" transform="translate(0 -120) rotate(90)"></g>
<g id="guides">
<line x1="-1000" x2="1000" stroke="black" style="transform: translate(0, calc(1px * var(--y0-hue)))"/>
<line x1="-1000" x2="1000" stroke="black" style="transform: translate(0, calc(1px * var(--y1-hue)))"/>
<line x1="-1000" x2="1000" stroke="black" style="transform: translate(0, calc(1px * var(--y0-lum)))"/>
<line x1="-1000" x2="1000" stroke="black" style="transform: translate(0, calc(1px * var(--y1-lum)))"/>
</g>
<g id="draggables">
<image class="draggable template hoodie" id="hoodie-back" href="template/hoodie_back_template.png" x="14" y="-71" />
<image class="draggable template hoodie" id="hoodie-front" href="template/hoodie_front_template.png" x="54" y="-71" />
<image class="draggable template hoodie" id="hoodie-front-pocket" href="template/hoodie_front_pocket_template.png" x="-17" y="-79" />
<image class="draggable template hoodie" id="hoodie-hood" href="template/hoodie_hood_template.png" x="28" y="-111" />
<image class="draggable template hoodie" id="hoodie-label-panel" href="template/hoodie_label_panel_template.png" x="71" y="-96" />
<image class="draggable template hoodie" id="hoodie-left-sleeve" href="template/hoodie_left_sleeve_template.png" x="90" y="-70" />
<image class="draggable template hoodie" id="hoodie-right-sleeve" href="template/hoodie_right_sleeve_template.png" x="121" y="-70" />
<image class="draggable template tshirt" id="tshirt-right-sleeve" href="template/TShirt_Right_Sleeve.png" x="83" y="-60" />
<image class="draggable template tshirt" id="tshirt-left-sleeve" href="template/TShirt_Left_Sleeve.png" x="42" y="-60" />
<image class="draggable template tshirt" id="tshirt-front" href="template/TShirt_Front.png" x="2" y="-60" />
<image class="draggable template tshirt" id="tshirt-back" href="template/TShirt_Back.png" x="-39" y="-60" />
<circle class="draggable" id="y0-lum" cx="0" cy="30" r="5"/>
<circle class="draggable" id="y1-lum" cx="0" cy="-150" r="5"/>
<circle class="draggable" id="y0-hue" cx="30" cy="-100" r="5"/>
<circle class="draggable" id="y1-hue" cx="30" cy="-30" r="5"/>
</g>
</g>
</svg>
</section>
<section id="controls">
<details open>
<summary>Instructions</summary>
<p>Use the controls to change the colours.</p>
<p>Drag the circles around to change the gradients of hue and lightness.</p>
<p>Drag the templates for the pieces of the garment that will be cut out.</p>
<p>When you're done, click the <em>Finish</em> button and <a href="mailto:christian+clothes@lawson-perfect.uk?subject=Please+make+this+for+me">send the file to me</a>.</p>
</details>
<form>
<fieldset>
<legend>View</legend>
<label for="zoom">
Zoom
</label>
<input type="range" min="0.2" max="10" value="1" step="0.01" id="zoom">
</fieldset>
<fieldset>
<legend>Colours</legend>
<label for="min-hue">
Hue 1
</label>
<div class="colour-input-wrapper">
<input type="range" name="min-hue" id="min-hue" value="150" min="0" max="360">
</div>
<output for="min-hue"></output>
<label for="max-hue">
Hue 2
</label>
<div class="colour-input-wrapper">
<input type="range" id="max-hue" value="260" min="0" max="360">
</div>
<output for="max-hue"></output>
<label for="interpolation-long">
Long way round
</label>
<input type="radio" name="hue-interpolation" value="longer hue" id="interpolation-long">
<label for="interpolation-short">
Short way round
</label>
<input type="radio" checked name="hue-interpolation" value="shorter hue" id="interpolation-short">
<label for="sat-scale">
Saturation scale:
</label>
<input type="range" min="0" max="5" step="0.001" id="sat-scale" value="1.5">
<output for="sat-scale"></output>
<label for="randomisation">
Randomisation:
</label>
<input type="range" min="0" max="1" step="0.01" id="randomisation" value="0.2">
<output for="randomisation"></output>
</fieldset>
<fieldset>
<legend>Tiling</legend>
<label for="num_iterations">Number of iterations</label>
<input type="number" min="1" max="5" value="4" id="num_iterations">
</fieldset>
<fieldset>
<legend>Pieces</legend>
<label for="template-name">Template</label>
<select name="template-name" id="template-name">
<option value="hoodie" selected>Hoodie</option>
<option value="tshirt">T-shirt</option>
</select>
<label for="template-scale">Template scale</label>
<input type="range" min="0.2" max="5" value="2" step="0.01" id="template-scale">
</fieldset>
<button type="button" id="finish">Finish</button>
<a id="link" download="aperiodic-monotile-design.svg">Download</a>
</form>
</section>
</body>
</html>

760
script.js Normal file
View file

@ -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 = `<use href="#spectre" transform="matrix(${matS.join(',')})"/>`;
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(`<g transform="matrix(${matS.join(',')})">`);
const quad_points = this.quad.map(({x,y}) => `${x},${y}`).join(' ');
// stream.push(`<polygon fill="blue" fill-opacity="0.2" points="${quad_points}"></polygon>`);
for( let g of this.geoms ) {
g.geom.streamSVG( g.xform, stream );
}
stream.push('</g>');
}
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<settings.num_iterations;i++) {
sys = buildSupertiles( sys );
}
sys = sys['Delta'];
window.sys = sys;
const drawing = [];
const board = svg.querySelector('#board');
board.innerHTML = '';
sys.streamSVG(ident, drawing);
board.innerHTML = drawing.join(' ');
const viewbox = svg.getBoundingClientRect();
const rule = new Function('t','x','y', settings.colouring_rule);
function visit(g, t) {
for(let c of g.children) {
visit(c, t+Math.random()*10-5);
}
if(g.tagName.toLowerCase()=='use') {
const e = document.querySelector('defs .spectre').cloneNode(true);
const b = g.getBoundingClientRect();
const point = svg.createSVGPoint();
point.x = b.x;
point.y = b.y;
const position = point.matrixTransform(svg.querySelector('#display').getScreenCTM().inverse());
e.style.setProperty('--t',t);
e.style.setProperty('--r',Math.random());
e.style.setProperty('--x',position.x);
e.style.setProperty('--y',position.y);
e.removeAttribute('id');
g.replaceWith(e);
e.setAttribute('transform', g.getAttribute('transform'));
}
}
visit(board,30);
make_download();
}
function finish() {
const svg = document.querySelector('svg');
const viewbox = svg.getBoundingClientRect();
make_download();
document.getElementById('link').click();
}
function setup() {
const settings = get_settings();
const svg = document.querySelector('svg');
const raw_zoom = document.getElementById('zoom').valueAsNumber;
const zoom = raw_zoom;
//const bounds = sys.bounds(ident);
svg.querySelector('#display').setAttribute('transform',`scale(${zoom}) translate(${pan.x} ${pan.y})`);
svg.style.setProperty('--zoom', zoom);
svg.style.setProperty('--pan-x', pan.x);
svg.style.setProperty('--pan-y', pan.y);
if(settings.num_iterations != last_num_iterations) {
rebuild();
last_num_iterations = settings.num_iterations;
}
}
for(let i of document.querySelectorAll('input')) {
i.addEventListener('input', setup);
i.addEventListener('change', setup)
}
setup();
document.getElementById('finish').addEventListener('click', () => 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();

33
spectre.svg Normal file
View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
id="svg312810"
sodipodi:docname="spectre.svg"
xml:space="preserve"
inkscape:version="1.2.1 (9c6d41e, 2022-07-14)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs312814" /><sodipodi:namedview
id="namedview312812"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="109.19085"
inkscape:cx="1.657648"
inkscape:cy="0.98451471"
inkscape:window-width="1379"
inkscape:window-height="847"
inkscape:window-x="61"
inkscape:window-y="25"
inkscape:window-maximized="1"
inkscape:current-layer="svg312810" /><path
id="tile"
d="m 1.5,-0.8660254 c 0.5735127,0.00665 0.2925127,0.4933532 0.8660254,0.5 0.281,0.5 -0.281,0.5 0,1 0.5,-0.281 0.5,0.281 1,0 0.4933531,0.2925127 0.00665,0.5735127 0.5,0.8660254 C 3.5735127,1.9933532 3.2925127,1.50665 3,2.0000001 2.4264873,1.9933501 2.7074873,1.5066468 2.1339746,1.5 c -0.00665,0.5735128 -0.4933532,0.2925128 -0.5,0.8660255 -0.5,0.281 -0.5,-0.281 -1,0 -0.5,0.281 -0.5,-0.281 -1,0 C -0.8593785,2.0735128 -0.3726754,1.7925128 -0.8660254,1.5 -0.5735127,1.0066468 -0.2925127,1.49335 0,1 -0.281,0.5 0.281,0.5 0,0 0.5,-0.281 0.5,0.281 0.99999998,0 1.00665,-0.5735127 1.4933531,-0.2925127 1.5,-0.8660254 Z"
style="fill:#ff0000" /></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

102
style.css Normal file
View file

@ -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;
}

BIN
template/TShirt_Back.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

BIN
template/TShirt_Front.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 KiB