refactor: new structure
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
{
|
||||
"file": "space-route-1.html",
|
||||
"pages": [
|
||||
{
|
||||
"page": 1,
|
||||
"objects": [
|
||||
{ "nodeId": 44, "type": "dest", "src": "asteroid5.png", "left": "157.0mm", "top": "183.5mm", "w": "30mm", "h": "30mm", "rotate": 5, "flipH": false },
|
||||
{ "nodeId": 4, "type": "deco", "src": "asteroid3.png", "left": "133.5mm", "top": "54.5mm", "w": "20mm", "h": "20mm", "rotate": 0, "flipH": false },
|
||||
{ "nodeId": 15, "type": "deco", "src": "asteroid7.png", "left": "81.0mm", "top": "97.0mm", "w": "54mm", "h": "52mm", "rotate": -20, "flipH": false },
|
||||
{ "nodeId": 17, "type": "deco", "src": "asteroid11.png", "left": "130.5mm", "top": "97.5mm", "w": "20mm", "h": "20mm", "rotate": 0, "flipH": false },
|
||||
{ "nodeId": 23, "type": "deco", "src": "pod1.png", "left": "115.5mm", "top": "122.0mm", "w": "34mm", "h": "22mm", "rotate": 0, "flipH": false },
|
||||
{ "nodeId": 29, "type": "deco", "src": "pod2.png", "left": "109.0mm", "top": "146.5mm", "w": "38mm", "h": "24mm", "rotate": 50, "flipH": false },
|
||||
{ "nodeId": 42, "type": "deco", "src": "freighter1.png", "left": "104.0mm", "top": "191.5mm", "w": "50mm", "h": "26mm", "rotate": -5, "flipH": false }
|
||||
],
|
||||
"nodes": [
|
||||
{ "nodeId": 0, "left": "53.0mm", "top": "48.0mm" }, { "nodeId": 1, "left": "54.0mm", "top": "69.5mm" }, { "nodeId": 2, "left": "84.0mm", "top": "59.0mm" }, { "nodeId": 3, "left": "108.5mm", "top": "56.5mm" }, { "nodeId": 4, "left": "137.5mm", "top": "59.5mm" }, { "nodeId": 5, "left": "160.0mm", "top": "59.5mm" }, { "nodeId": 6, "left": "190.0mm", "top": "78.0mm" }, { "nodeId": 7, "left": "26.0mm", "top": "72.5mm" }, { "nodeId": 8, "left": "53.5mm", "top": "91.0mm" }, { "nodeId": 9, "left": "96.5mm", "top": "73.5mm" }, { "nodeId": 10, "left": "115.7mm", "top": "74.1mm" }, { "nodeId": 11, "left": "144.4mm", "top": "81.4mm" }, { "nodeId": 12, "left": "167.5mm", "top": "83.5mm" }, { "nodeId": 13, "left": "16.0mm", "top": "107.0mm" }, { "nodeId": 14, "left": "47.5mm", "top": "112.0mm" }, { "nodeId": 15, "left": "80.0mm", "top": "96.5mm" }, { "nodeId": 16, "left": "108.5mm", "top": "97.7mm" }, { "nodeId": 17, "left": "131.2mm", "top": "103.4mm" }, { "nodeId": 18, "left": "155.9mm", "top": "95.4mm" }, { "nodeId": 19, "left": "189.0mm", "top": "114.5mm" }, { "nodeId": 20, "left": "29.0mm", "top": "125.0mm" }, { "nodeId": 21, "left": "70.0mm", "top": "118.5mm" }, { "nodeId": 22, "left": "91.6mm", "top": "117.7mm" }, { "nodeId": 23, "left": "113.5mm", "top": "119.8mm" }, { "nodeId": 24, "left": "142.3mm", "top": "117.4mm" }, { "nodeId": 25, "left": "174.0mm", "top": "125.0mm" }, { "nodeId": 26, "left": "23.5mm", "top": "142.1mm" }, { "nodeId": 27, "left": "50.0mm", "top": "145.1mm" }, { "nodeId": 28, "left": "78.0mm", "top": "149.0mm" }, { "nodeId": 29, "left": "103.2mm", "top": "146.3mm" }, { "nodeId": 30, "left": "135.0mm", "top": "139.5mm" }, { "nodeId": 31, "left": "158.9mm", "top": "143.9mm" }, { "nodeId": 32, "left": "177.7mm", "top": "148.2mm" }, { "nodeId": 33, "left": "36.9mm", "top": "166.8mm" }, { "nodeId": 34, "left": "62.6mm", "top": "162.8mm" }, { "nodeId": 35, "left": "100.5mm", "top": "169.0mm" }, { "nodeId": 36, "left": "129.5mm", "top": "170.5mm" }, { "nodeId": 37, "left": "146.0mm", "top": "155.0mm" }, { "nodeId": 38, "left": "166.1mm", "top": "160.7mm" }, { "nodeId": 39, "left": "23.8mm", "top": "191.2mm" }, { "nodeId": 40, "left": "55.3mm", "top": "183.2mm" }, { "nodeId": 41, "left": "78.0mm", "top": "176.0mm" }, { "nodeId": 42, "left": "102.0mm", "top": "199.5mm" }, { "nodeId": 43, "left": "139.5mm", "top": "195.0mm" }, { "nodeId": 44, "left": "157.5mm", "top": "176.0mm" }, { "nodeId": 45, "left": "180.8mm", "top": "190.7mm" }, { "nodeId": 46, "left": "36.3mm", "top": "208.1mm" }, { "nodeId": 47, "left": "57.5mm", "top": "209.0mm" }, { "nodeId": 48, "left": "79.5mm", "top": "215.0mm" }, { "nodeId": 49, "left": "121.0mm", "top": "209.0mm" }, { "nodeId": 50, "left": "146.0mm", "top": "211.5mm" }, { "nodeId": 51, "left": "169.5mm", "top": "206.0mm" }, { "nodeId": 52, "left": "24.3mm", "top": "235.9mm" }, { "nodeId": 53, "left": "57.2mm", "top": "230.9mm" }, { "nodeId": 54, "left": "82.5mm", "top": "232.7mm" }, { "nodeId": 55, "left": "102.5mm", "top": "227.0mm" }, { "nodeId": 56, "left": "125.6mm", "top": "227.0mm" }, { "nodeId": 57, "left": "157.3mm", "top": "233.1mm" }, { "nodeId": 58, "left": "181.5mm", "top": "226.4mm" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"page": 2,
|
||||
"objects": [
|
||||
{ "nodeId": 44, "type": "dest", "src": "pod6.png", "left": "174.5mm", "top": "179.5mm", "w": "20mm", "h": "19mm", "rotate": 0, "flipH": false },
|
||||
{ "nodeId": 5, "type": "deco", "src": "asteroid9.png", "left": "157.2mm", "top": "56.0mm", "w": "54mm", "h": "52mm", "rotate": 55, "flipH": false },
|
||||
{ "nodeId": 11, "type": "deco", "src": "asteroid14.png", "left": "146.9mm", "top": "82.1mm", "w": "16mm", "h": "15mm", "rotate": 0, "flipH": false },
|
||||
{ "nodeId": 21, "type": "deco", "src": "asteroid2.png", "left": "60.5mm", "top": "122.0mm", "w": "24mm", "h": "24mm", "rotate": 0, "flipH": false },
|
||||
{ "nodeId": 28, "type": "deco", "src": "pod4.png", "left": "85.0mm", "top": "142.5mm", "w": "24mm", "h": "15mm", "rotate": 0, "flipH": false },
|
||||
{ "nodeId": 34, "type": "deco", "src": "pod5.png", "left": "62.0mm", "top": "168.0mm", "w": "24mm", "h": "15mm", "rotate": 0, "flipH": false },
|
||||
{ "nodeId": 43, "type": "deco", "src": "freighter2.png", "left": "130.5mm", "top": "194.5mm", "w": "58mm", "h": "30mm", "rotate": -5, "flipH": false }
|
||||
],
|
||||
"nodes": [
|
||||
{ "nodeId": 0, "left": "44.5mm", "top": "47.5mm" }, { "nodeId": 1, "left": "62.0mm", "top": "58.5mm" }, { "nodeId": 2, "left": "93.0mm", "top": "69.5mm" }, { "nodeId": 3, "left": "101.0mm", "top": "54.5mm" }, { "nodeId": 4, "left": "127.1mm", "top": "52.9mm" }, { "nodeId": 5, "left": "152.5mm", "top": "58.0mm" }, { "nodeId": 6, "left": "185.0mm", "top": "54.5mm" }, { "nodeId": 7, "left": "19.5mm", "top": "76.5mm" }, { "nodeId": 8, "left": "63.0mm", "top": "78.5mm" }, { "nodeId": 9, "left": "104.0mm", "top": "86.5mm" }, { "nodeId": 10, "left": "120.9mm", "top": "75.2mm" }, { "nodeId": 11, "left": "146.9mm", "top": "82.1mm" }, { "nodeId": 12, "left": "166.6mm", "top": "75.0mm" }, { "nodeId": 13, "left": "12.0mm", "top": "106.0mm" }, { "nodeId": 14, "left": "31.0mm", "top": "100.5mm" }, { "nodeId": 15, "left": "62.5mm", "top": "98.5mm" }, { "nodeId": 16, "left": "106.8mm", "top": "103.6mm" }, { "nodeId": 17, "left": "128.7mm", "top": "102.1mm" }, { "nodeId": 18, "left": "155.9mm", "top": "99.8mm" }, { "nodeId": 19, "left": "180.8mm", "top": "96.9mm" }, { "nodeId": 20, "left": "22.0mm", "top": "124.5mm" }, { "nodeId": 21, "left": "59.0mm", "top": "120.0mm" }, { "nodeId": 22, "left": "89.5mm", "top": "119.0mm" }, { "nodeId": 23, "left": "121.5mm", "top": "119.5mm" }, { "nodeId": 24, "left": "141.9mm", "top": "124.3mm" }, { "nodeId": 25, "left": "168.5mm", "top": "118.0mm" }, { "nodeId": 26, "left": "14.0mm", "top": "155.0mm" }, { "nodeId": 27, "left": "50.8mm", "top": "142.4mm" }, { "nodeId": 28, "left": "85.5mm", "top": "141.0mm" }, { "nodeId": 29, "left": "110.3mm", "top": "147.9mm" }, { "nodeId": 30, "left": "128.0mm", "top": "153.5mm" }, { "nodeId": 31, "left": "162.3mm", "top": "144.4mm" }, { "nodeId": 32, "left": "188.0mm", "top": "129.0mm" }, { "nodeId": 33, "left": "36.2mm", "top": "163.9mm" }, { "nodeId": 34, "left": "66.0mm", "top": "167.5mm" }, { "nodeId": 35, "left": "90.1mm", "top": "164.2mm" }, { "nodeId": 36, "left": "113.3mm", "top": "169.1mm" }, { "nodeId": 37, "left": "140.5mm", "top": "170.5mm" }, { "nodeId": 38, "left": "187.5mm", "top": "153.5mm" }, { "nodeId": 39, "left": "15.0mm", "top": "184.0mm" }, { "nodeId": 40, "left": "51.0mm", "top": "188.5mm" }, { "nodeId": 41, "left": "69.5mm", "top": "185.5mm" }, { "nodeId": 42, "left": "93.0mm", "top": "189.5mm" }, { "nodeId": 43, "left": "137.5mm", "top": "196.0mm" }, { "nodeId": 44, "left": "172.0mm", "top": "177.5mm" }, { "nodeId": 45, "left": "197.0mm", "top": "173.5mm" }, { "nodeId": 46, "left": "26.0mm", "top": "202.5mm" }, { "nodeId": 47, "left": "66.0mm", "top": "202.0mm" }, { "nodeId": 48, "left": "81.5mm", "top": "207.5mm" }, { "nodeId": 49, "left": "107.5mm", "top": "211.0mm" }, { "nodeId": 50, "left": "160.0mm", "top": "213.5mm" }, { "nodeId": 51, "left": "181.5mm", "top": "222.5mm" }, { "nodeId": 52, "left": "36.5mm", "top": "236.0mm" }, { "nodeId": 53, "left": "49.0mm", "top": "214.0mm" }, { "nodeId": 54, "left": "76.5mm", "top": "223.5mm" }, { "nodeId": 55, "left": "100.5mm", "top": "232.0mm" }, { "nodeId": 56, "left": "134.0mm", "top": "222.5mm" }, { "nodeId": 57, "left": "156.2mm", "top": "233.5mm" }, { "nodeId": 58, "left": "200.5mm", "top": "214.0mm" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"page": 3,
|
||||
"objects": [
|
||||
{ "nodeId": 51, "type": "dest", "src": "freighter8.png", "left": "160.0mm", "top": "217.5mm", "w": "52mm", "h": "32mm", "rotate": -15, "flipH": false },
|
||||
{ "nodeId": 2, "type": "deco", "src": "asteroid1.png", "left": "67.5mm", "top": "67.5mm", "w": "20mm", "h": "20mm", "rotate": 0, "flipH": false },
|
||||
{ "nodeId": 9, "type": "deco", "src": "asteroid10.png", "left": "93.5mm", "top": "74.0mm", "w": "26mm", "h": "26mm", "rotate": 0, "flipH": false },
|
||||
{ "nodeId": 19, "type": "deco", "src": "asteroid6.png", "left": "187.6mm", "top": "100.3mm", "w": "16mm", "h": "15mm", "rotate": 0, "flipH": false },
|
||||
{ "nodeId": 23, "type": "deco", "src": "pod8.png", "left": "113.1mm", "top": "126.1mm", "w": "20mm", "h": "12mm", "rotate": -25, "flipH": false },
|
||||
{ "nodeId": 26, "type": "deco", "src": "pod10.png", "left": "19.5mm", "top": "143.0mm", "w": "36mm", "h": "23mm", "rotate": 0, "flipH": false },
|
||||
{ "nodeId": 35, "type": "deco", "src": "freighter5.png", "left": "86.5mm", "top": "168.5mm", "w": "58mm", "h": "30mm", "rotate": 0, "flipH": false }
|
||||
],
|
||||
"nodes": [
|
||||
{ "nodeId": 0, "left": "41.5mm", "top": "46.5mm" }, { "nodeId": 1, "left": "48.5mm", "top": "77.0mm" }, { "nodeId": 2, "left": "67.0mm", "top": "67.0mm" }, { "nodeId": 3, "left": "110.1mm", "top": "57.1mm" }, { "nodeId": 4, "left": "134.4mm", "top": "59.9mm" }, { "nodeId": 5, "left": "158.2mm", "top": "52.6mm" }, { "nodeId": 6, "left": "185.4mm", "top": "49.6mm" }, { "nodeId": 7, "left": "25.0mm", "top": "63.0mm" }, { "nodeId": 8, "left": "57.0mm", "top": "96.5mm" }, { "nodeId": 9, "left": "93.0mm", "top": "74.0mm" }, { "nodeId": 10, "left": "116.9mm", "top": "73.4mm" }, { "nodeId": 11, "left": "140.3mm", "top": "80.8mm" }, { "nodeId": 12, "left": "165.0mm", "top": "81.8mm" }, { "nodeId": 13, "left": "17.0mm", "top": "107.0mm" }, { "nodeId": 14, "left": "39.0mm", "top": "103.5mm" }, { "nodeId": 15, "left": "82.5mm", "top": "97.4mm" }, { "nodeId": 16, "left": "108.9mm", "top": "100.6mm" }, { "nodeId": 17, "left": "133.1mm", "top": "93.6mm" }, { "nodeId": 18, "left": "156.3mm", "top": "100.2mm" }, { "nodeId": 19, "left": "187.6mm", "top": "100.3mm" }, { "nodeId": 20, "left": "34.7mm", "top": "124.1mm" }, { "nodeId": 21, "left": "70.7mm", "top": "123.2mm" }, { "nodeId": 22, "left": "90.0mm", "top": "117.0mm" }, { "nodeId": 23, "left": "113.1mm", "top": "126.1mm" }, { "nodeId": 24, "left": "141.7mm", "top": "125.1mm" }, { "nodeId": 25, "left": "175.2mm", "top": "117.0mm" }, { "nodeId": 26, "left": "23.5mm", "top": "141.5mm" }, { "nodeId": 27, "left": "54.0mm", "top": "135.5mm" }, { "nodeId": 28, "left": "74.5mm", "top": "139.5mm" }, { "nodeId": 29, "left": "94.5mm", "top": "145.5mm" }, { "nodeId": 30, "left": "146.5mm", "top": "141.5mm" }, { "nodeId": 31, "left": "166.5mm", "top": "134.5mm" }, { "nodeId": 32, "left": "187.9mm", "top": "139.2mm" }, { "nodeId": 33, "left": "23.5mm", "top": "166.5mm" }, { "nodeId": 34, "left": "48.0mm", "top": "165.5mm" }, { "nodeId": 35, "left": "89.0mm", "top": "167.0mm" }, { "nodeId": 36, "left": "128.5mm", "top": "153.0mm" }, { "nodeId": 37, "left": "149.0mm", "top": "160.5mm" }, { "nodeId": 38, "left": "168.7mm", "top": "168.2mm" }, { "nodeId": 39, "left": "28.6mm", "top": "188.9mm" }, { "nodeId": 40, "left": "50.8mm", "top": "185.4mm" }, { "nodeId": 41, "left": "68.0mm", "top": "194.0mm" }, { "nodeId": 42, "left": "108.0mm", "top": "191.5mm" }, { "nodeId": 43, "left": "130.0mm", "top": "187.1mm" }, { "nodeId": 44, "left": "151.5mm", "top": "181.5mm" }, { "nodeId": 45, "left": "185.4mm", "top": "184.6mm" }, { "nodeId": 46, "left": "21.0mm", "top": "219.0mm" }, { "nodeId": 47, "left": "46.5mm", "top": "208.0mm" }, { "nodeId": 48, "left": "90.1mm", "top": "205.9mm" }, { "nodeId": 49, "left": "110.0mm", "top": "211.5mm" }, { "nodeId": 50, "left": "134.0mm", "top": "205.5mm" }, { "nodeId": 51, "left": "167.0mm", "top": "215.5mm" }, { "nodeId": 52, "left": "54.5mm", "top": "243.0mm" }, { "nodeId": 53, "left": "59.5mm", "top": "223.0mm" }, { "nodeId": 54, "left": "83.5mm", "top": "225.5mm" }, { "nodeId": 55, "left": "108.0mm", "top": "243.0mm" }, { "nodeId": 56, "left": "127.5mm", "top": "230.0mm" }, { "nodeId": 57, "left": "161.0mm", "top": "242.5mm" }, { "nodeId": 58, "left": "199.5mm", "top": "235.5mm" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"page": 4,
|
||||
"objects": [
|
||||
{ "nodeId": 44, "type": "dest", "src": "pod16.png", "left": "157.5mm", "top": "189.5mm", "w": "58mm", "h": "49mm", "rotate": 0, "flipH": false },
|
||||
{ "nodeId": 2, "type": "deco", "src": "asteroid12.png", "left": "73.5mm", "top": "69.5mm", "w": "20mm", "h": "20mm", "rotate": -60, "flipH": false },
|
||||
{ "nodeId": 10, "type": "deco", "src": "asteroid4.png", "left": "122.0mm", "top": "78.5mm", "w": "16mm", "h": "15mm", "rotate": -35, "flipH": false },
|
||||
{ "nodeId": 14, "type": "deco", "src": "asteroid15.png", "left": "38.0mm", "top": "103.5mm", "w": "20mm", "h": "20mm", "rotate": -35, "flipH": false },
|
||||
{ "nodeId": 27, "type": "deco", "src": "pod11.png", "left": "49.0mm", "top": "139.8mm", "w": "30mm", "h": "19mm", "rotate": 0, "flipH": false },
|
||||
{ "nodeId": 35, "type": "deco", "src": "pod13.png", "left": "91.1mm", "top": "166.1mm", "w": "32mm", "h": "20mm", "rotate": 0, "flipH": false },
|
||||
{ "nodeId": 40, "type": "deco", "src": "freighter3.png", "left": "52.5mm", "top": "190.0mm", "w": "58mm", "h": "34mm", "rotate": 0, "flipH": false }
|
||||
],
|
||||
"nodes": [
|
||||
{ "nodeId": 0, "left": "40.0mm", "top": "49.0mm" }, { "nodeId": 1, "left": "43.0mm", "top": "84.5mm" }, { "nodeId": 2, "left": "74.0mm", "top": "70.0mm" }, { "nodeId": 3, "left": "99.6mm", "top": "49.7mm" }, { "nodeId": 4, "left": "133.4mm", "top": "53.4mm" }, { "nodeId": 5, "left": "155.4mm", "top": "52.9mm" }, { "nodeId": 6, "left": "178.0mm", "top": "62.5mm" }, { "nodeId": 7, "left": "20.0mm", "top": "65.0mm" }, { "nodeId": 8, "left": "73.5mm", "top": "87.5mm" }, { "nodeId": 9, "left": "93.3mm", "top": "77.4mm" }, { "nodeId": 10, "left": "122.5mm", "top": "79.0mm" }, { "nodeId": 11, "left": "146.3mm", "top": "81.5mm" }, { "nodeId": 12, "left": "166.5mm", "top": "81.0mm" }, { "nodeId": 13, "left": "14.0mm", "top": "84.0mm" }, { "nodeId": 14, "left": "38.5mm", "top": "104.0mm" }, { "nodeId": 15, "left": "81.9mm", "top": "100.0mm" }, { "nodeId": 16, "left": "101.1mm", "top": "96.3mm" }, { "nodeId": 17, "left": "135.6mm", "top": "97.4mm" }, { "nodeId": 18, "left": "152.0mm", "top": "99.0mm" }, { "nodeId": 19, "left": "180.4mm", "top": "100.8mm" }, { "nodeId": 20, "left": "26.5mm", "top": "117.5mm" }, { "nodeId": 21, "left": "66.3mm", "top": "126.0mm" }, { "nodeId": 22, "left": "86.5mm", "top": "123.1mm" }, { "nodeId": 23, "left": "117.3mm", "top": "120.7mm" }, { "nodeId": 24, "left": "149.4mm", "top": "117.2mm" }, { "nodeId": 25, "left": "166.6mm", "top": "123.3mm" }, { "nodeId": 26, "left": "18.0mm", "top": "139.0mm" }, { "nodeId": 27, "left": "48.0mm", "top": "137.5mm" }, { "nodeId": 28, "left": "81.6mm", "top": "143.1mm" }, { "nodeId": 29, "left": "102.5mm", "top": "141.1mm" }, { "nodeId": 30, "left": "127.0mm", "top": "144.8mm" }, { "nodeId": 31, "left": "158.0mm", "top": "137.9mm" }, { "nodeId": 32, "left": "181.3mm", "top": "142.7mm" }, { "nodeId": 33, "left": "27.0mm", "top": "160.0mm" }, { "nodeId": 34, "left": "68.0mm", "top": "166.8mm" }, { "nodeId": 35, "left": "91.5mm", "top": "168.0mm" }, { "nodeId": 36, "left": "131.5mm", "top": "161.0mm" }, { "nodeId": 37, "left": "154.0mm", "top": "154.0mm" }, { "nodeId": 38, "left": "172.1mm", "top": "163.0mm" }, { "nodeId": 39, "left": "9.5mm", "top": "175.0mm" }, { "nodeId": 40, "left": "62.0mm", "top": "190.0mm" }, { "nodeId": 41, "left": "99.0mm", "top": "195.5mm" }, { "nodeId": 42, "left": "111.5mm", "top": "176.5mm" }, { "nodeId": 43, "left": "133.5mm", "top": "181.0mm" }, { "nodeId": 44, "left": "158.5mm", "top": "192.0mm" }, { "nodeId": 45, "left": "184.1mm", "top": "184.2mm" }, { "nodeId": 46, "left": "10.5mm", "top": "199.0mm" }, { "nodeId": 47, "left": "72.0mm", "top": "218.5mm" }, { "nodeId": 48, "left": "98.0mm", "top": "220.5mm" }, { "nodeId": 49, "left": "122.0mm", "top": "207.5mm" }, { "nodeId": 50, "left": "145.6mm", "top": "207.9mm" }, { "nodeId": 51, "left": "168.9mm", "top": "207.7mm" }, { "nodeId": 52, "left": "35.5mm", "top": "241.0mm" }, { "nodeId": 53, "left": "40.5mm", "top": "221.0mm" }, { "nodeId": 54, "left": "82.7mm", "top": "234.9mm" }, { "nodeId": 55, "left": "112.0mm", "top": "242.0mm" }, { "nodeId": 56, "left": "127.5mm", "top": "226.9mm" }, { "nodeId": 57, "left": "160.6mm", "top": "226.6mm" }, { "nodeId": 58, "left": "178.5mm", "top": "235.8mm" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"page": 5,
|
||||
"objects": [
|
||||
{ "nodeId": 44, "type": "dest", "src": "asteroid16.png", "left": "148.5mm", "top": "182.0mm", "w": "24mm", "h": "24mm", "rotate": 0, "flipH": false },
|
||||
{ "nodeId": 4, "type": "deco", "src": "asteroid8.png", "left": "132.5mm", "top": "55.5mm", "w": "28mm", "h": "28mm", "rotate": -10, "flipH": false },
|
||||
{ "nodeId": 9, "type": "deco", "src": "asteroid13.png", "left": "91.0mm", "top": "81.0mm", "w": "18mm", "h": "17mm", "rotate": -20, "flipH": false },
|
||||
{ "nodeId": 13, "type": "deco", "src": "asteroid3.png", "left": "16.5mm", "top": "104.0mm", "w": "20mm", "h": "20mm", "rotate": 0, "flipH": false },
|
||||
{ "nodeId": 24, "type": "deco", "src": "pod7.png", "left": "146.5mm", "top": "125.0mm", "w": "40mm", "h": "25mm", "rotate": 10, "flipH": false },
|
||||
{ "nodeId": 38, "type": "deco", "src": "pod9.png", "left": "179.5mm", "top": "159.5mm", "w": "26mm", "h": "17mm", "rotate": 15, "flipH": false },
|
||||
{ "nodeId": 50, "type": "deco", "src": "freighter6.png", "left": "147.0mm", "top": "221.5mm", "w": "60mm", "h": "31mm", "rotate": 0, "flipH": false }
|
||||
],
|
||||
"nodes": [
|
||||
{ "nodeId": 0, "left": "37.5mm", "top": "47.0mm" }, { "nodeId": 1, "left": "48.0mm", "top": "64.0mm" }, { "nodeId": 2, "left": "76.0mm", "top": "58.2mm" }, { "nodeId": 3, "left": "103.3mm", "top": "60.2mm" }, { "nodeId": 4, "left": "132.5mm", "top": "57.0mm" }, { "nodeId": 5, "left": "155.6mm", "top": "59.8mm" }, { "nodeId": 6, "left": "185.8mm", "top": "55.4mm" }, { "nodeId": 7, "left": "13.0mm", "top": "68.0mm" }, { "nodeId": 8, "left": "57.0mm", "top": "80.5mm" }, { "nodeId": 9, "left": "91.5mm", "top": "81.5mm" }, { "nodeId": 10, "left": "118.4mm", "top": "79.0mm" }, { "nodeId": 11, "left": "142.3mm", "top": "75.3mm" }, { "nodeId": 12, "left": "170.3mm", "top": "81.6mm" }, { "nodeId": 13, "left": "15.0mm", "top": "104.0mm" }, { "nodeId": 14, "left": "49.2mm", "top": "96.4mm" }, { "nodeId": 15, "left": "78.6mm", "top": "99.7mm" }, { "nodeId": 16, "left": "102.2mm", "top": "103.0mm" }, { "nodeId": 17, "left": "129.1mm", "top": "100.7mm" }, { "nodeId": 18, "left": "154.4mm", "top": "99.9mm" }, { "nodeId": 19, "left": "193.5mm", "top": "103.0mm" }, { "nodeId": 20, "left": "27.0mm", "top": "126.5mm" }, { "nodeId": 21, "left": "69.9mm", "top": "117.8mm" }, { "nodeId": 22, "left": "92.8mm", "top": "121.9mm" }, { "nodeId": 23, "left": "113.9mm", "top": "116.1mm" }, { "nodeId": 24, "left": "146.5mm", "top": "123.5mm" }, { "nodeId": 25, "left": "180.0mm", "top": "119.5mm" }, { "nodeId": 26, "left": "16.0mm", "top": "152.0mm" }, { "nodeId": 27, "left": "45.5mm", "top": "143.0mm" }, { "nodeId": 28, "left": "76.6mm", "top": "146.3mm" }, { "nodeId": 29, "left": "99.5mm", "top": "137.9mm" }, { "nodeId": 30, "left": "122.5mm", "top": "138.5mm" }, { "nodeId": 31, "left": "160.6mm", "top": "144.4mm" }, { "nodeId": 32, "left": "187.5mm", "top": "138.5mm" }, { "nodeId": 33, "left": "43.3mm", "top": "163.2mm" }, { "nodeId": 34, "left": "62.0mm", "top": "162.4mm" }, { "nodeId": 35, "left": "87.4mm", "top": "161.9mm" }, { "nodeId": 36, "left": "115.5mm", "top": "166.0mm" }, { "nodeId": 37, "left": "143.5mm", "top": "154.0mm" }, { "nodeId": 38, "left": "179.0mm", "top": "163.5mm" }, { "nodeId": 39, "left": "31.0mm", "top": "187.2mm" }, { "nodeId": 40, "left": "52.0mm", "top": "185.4mm" }, { "nodeId": 41, "left": "78.0mm", "top": "191.1mm" }, { "nodeId": 42, "left": "105.5mm", "top": "189.6mm" }, { "nodeId": 43, "left": "126.9mm", "top": "188.7mm" }, { "nodeId": 44, "left": "150.5mm", "top": "183.5mm" }, { "nodeId": 45, "left": "194.5mm", "top": "184.0mm" }, { "nodeId": 46, "left": "14.5mm", "top": "216.5mm" }, { "nodeId": 47, "left": "46.5mm", "top": "205.5mm" }, { "nodeId": 48, "left": "73.0mm", "top": "212.0mm" }, { "nodeId": 49, "left": "104.0mm", "top": "208.5mm" }, { "nodeId": 50, "left": "163.5mm", "top": "221.5mm" }, { "nodeId": 51, "left": "196.0mm", "top": "217.5mm" }, { "nodeId": 52, "left": "45.0mm", "top": "244.5mm" }, { "nodeId": 53, "left": "40.5mm", "top": "224.0mm" }, { "nodeId": 54, "left": "75.1mm", "top": "231.5mm" }, { "nodeId": 55, "left": "102.0mm", "top": "232.0mm" }, { "nodeId": 56, "left": "130.5mm", "top": "245.0mm" }, { "nodeId": 57, "left": "169.0mm", "top": "252.5mm" }, { "nodeId": 58, "left": "200.0mm", "top": "251.5mm" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"page": 6,
|
||||
"objects": [
|
||||
{ "nodeId": 51, "type": "dest", "src": "freighter2.png", "left": "174.5mm", "top": "211.0mm", "w": "60mm", "h": "40mm", "rotate": 0, "flipH": false },
|
||||
{ "nodeId": 4, "type": "deco", "src": "freighter9.png", "left": "133.5mm", "top": "48.5mm", "w": "52mm", "h": "27mm", "rotate": 0, "flipH": false },
|
||||
{ "nodeId": 10, "type": "deco", "src": "freighter10.png", "left": "104.0mm", "top": "84.5mm", "w": "54mm", "h": "28mm", "rotate": -5, "flipH": false },
|
||||
{ "nodeId": 13, "type": "deco", "src": "pod14.png", "left": "20.5mm", "top": "106.5mm", "w": "48mm", "h": "30mm", "rotate": -20, "flipH": false },
|
||||
{ "nodeId": 24, "type": "deco", "src": "pod15.png", "left": "140.0mm", "top": "114.5mm", "w": "28mm", "h": "18mm", "rotate": 0, "flipH": false },
|
||||
{ "nodeId": 32, "type": "deco", "src": "pod17.png", "left": "182.5mm", "top": "142.0mm", "w": "38mm", "h": "24mm", "rotate": 0, "flipH": false },
|
||||
{ "nodeId": 35, "type": "deco", "src": "asteroid3.png", "left": "77.5mm", "top": "161.0mm", "w": "20mm", "h": "20mm", "rotate": 0, "flipH": false },
|
||||
{ "nodeId": 42, "type": "deco", "src": "asteroid11.png", "left": "101.0mm", "top": "191.5mm", "w": "20mm", "h": "20mm", "rotate": 0, "flipH": false }
|
||||
],
|
||||
"nodes": [
|
||||
{ "nodeId": 0, "left": "35.5mm", "top": "49.5mm" }, { "nodeId": 1, "left": "53.5mm", "top": "68.0mm" }, { "nodeId": 2, "left": "82.1mm", "top": "54.4mm" }, { "nodeId": 3, "left": "102.5mm", "top": "59.5mm" }, { "nodeId": 4, "left": "132.0mm", "top": "53.5mm" }, { "nodeId": 5, "left": "164.5mm", "top": "49.0mm" }, { "nodeId": 6, "left": "189.5mm", "top": "52.0mm" }, { "nodeId": 7, "left": "18.5mm", "top": "80.0mm" }, { "nodeId": 8, "left": "60.9mm", "top": "80.5mm" }, { "nodeId": 9, "left": "84.5mm", "top": "70.5mm" }, { "nodeId": 10, "left": "101.5mm", "top": "85.5mm" }, { "nodeId": 11, "left": "155.0mm", "top": "75.5mm" }, { "nodeId": 12, "left": "189.5mm", "top": "80.0mm" }, { "nodeId": 13, "left": "26.5mm", "top": "103.5mm" }, { "nodeId": 14, "left": "56.0mm", "top": "99.8mm" }, { "nodeId": 15, "left": "78.0mm", "top": "104.0mm" }, { "nodeId": 16, "left": "107.7mm", "top": "103.0mm" }, { "nodeId": 17, "left": "133.0mm", "top": "93.8mm" }, { "nodeId": 18, "left": "158.8mm", "top": "97.4mm" }, { "nodeId": 19, "left": "188.1mm", "top": "104.0mm" }, { "nodeId": 20, "left": "40.0mm", "top": "123.0mm" }, { "nodeId": 21, "left": "62.0mm", "top": "131.5mm" }, { "nodeId": 22, "left": "92.1mm", "top": "121.0mm" }, { "nodeId": 23, "left": "113.5mm", "top": "123.5mm" }, { "nodeId": 24, "left": "141.0mm", "top": "114.5mm" }, { "nodeId": 25, "left": "174.8mm", "top": "122.5mm" }, { "nodeId": 26, "left": "20.0mm", "top": "140.5mm" }, { "nodeId": 27, "left": "49.1mm", "top": "138.9mm" }, { "nodeId": 28, "left": "74.5mm", "top": "144.0mm" }, { "nodeId": 29, "left": "107.5mm", "top": "142.0mm" }, { "nodeId": 30, "left": "127.9mm", "top": "142.6mm" }, { "nodeId": 31, "left": "146.5mm", "top": "146.5mm" }, { "nodeId": 32, "left": "177.0mm", "top": "140.0mm" }, { "nodeId": 33, "left": "23.0mm", "top": "162.5mm" }, { "nodeId": 34, "left": "53.5mm", "top": "157.5mm" }, { "nodeId": 35, "left": "77.5mm", "top": "160.0mm" }, { "nodeId": 36, "left": "119.0mm", "top": "160.5mm" }, { "nodeId": 37, "left": "141.5mm", "top": "173.5mm" }, { "nodeId": 38, "left": "174.0mm", "top": "168.5mm" }, { "nodeId": 39, "left": "30.0mm", "top": "186.9mm" }, { "nodeId": 40, "left": "53.7mm", "top": "181.6mm" }, { "nodeId": 41, "left": "73.6mm", "top": "190.8mm" }, { "nodeId": 42, "left": "101.0mm", "top": "192.5mm" }, { "nodeId": 43, "left": "121.0mm", "top": "186.0mm" }, { "nodeId": 44, "left": "153.7mm", "top": "187.6mm" }, { "nodeId": 45, "left": "182.3mm", "top": "189.9mm" }, { "nodeId": 46, "left": "13.5mm", "top": "210.5mm" }, { "nodeId": 47, "left": "42.5mm", "top": "207.0mm" }, { "nodeId": 48, "left": "79.5mm", "top": "209.5mm" }, { "nodeId": 49, "left": "115.9mm", "top": "208.0mm" }, { "nodeId": 50, "left": "132.5mm", "top": "212.5mm" }, { "nodeId": 51, "left": "176.5mm", "top": "212.5mm" }, { "nodeId": 52, "left": "44.5mm", "top": "243.5mm" }, { "nodeId": 53, "left": "46.0mm", "top": "224.0mm" }, { "nodeId": 54, "left": "81.5mm", "top": "225.9mm" }, { "nodeId": 55, "left": "96.5mm", "top": "242.5mm" }, { "nodeId": 56, "left": "124.5mm", "top": "243.0mm" }, { "nodeId": 57, "left": "147.5mm", "top": "230.0mm" }, { "nodeId": 58, "left": "180.0mm", "top": "240.0mm" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"page": 7,
|
||||
"objects": [
|
||||
{ "nodeId": 43, "type": "dest", "src": "pod18.png", "left": "121.0mm", "top": "202.5mm", "w": "32mm", "h": "28mm", "rotate": 0, "flipH": false },
|
||||
{ "nodeId": 4, "type": "deco", "src": "asteroid7.png", "left": "132.0mm", "top": "42.5mm", "w": "46mm", "h": "44mm", "rotate": -10, "flipH": false },
|
||||
{ "nodeId": 12, "type": "deco", "src": "asteroid15.png", "left": "175.5mm", "top": "71.0mm", "w": "20mm", "h": "20mm", "rotate": 0, "flipH": false },
|
||||
{ "nodeId": 23, "type": "deco", "src": "asteroid5.png", "left": "111.0mm", "top": "113.0mm", "w": "22mm", "h": "22mm", "rotate": -10, "flipH": false },
|
||||
{ "nodeId": 31, "type": "deco", "src": "pod3.png", "left": "157.0mm", "top": "142.5mm", "w": "40mm", "h": "25mm", "rotate": 0, "flipH": false },
|
||||
{ "nodeId": 29, "type": "deco", "src": "pod12.png", "left": "96.5mm", "top": "139.0mm", "w": "28mm", "h": "18mm", "rotate": 0, "flipH": false },
|
||||
{ "nodeId": 44, "type": "deco", "src": "freighter4.png", "left": "159.5mm", "top": "189.5mm", "w": "58mm", "h": "30mm", "rotate": 0, "flipH": false }
|
||||
],
|
||||
"nodes": [
|
||||
{ "nodeId": 0, "left": "39.0mm", "top": "48.0mm" }, { "nodeId": 1, "left": "48.5mm", "top": "65.5mm" }, { "nodeId": 2, "left": "83.4mm", "top": "54.6mm" }, { "nodeId": 3, "left": "100.0mm", "top": "53.3mm" }, { "nodeId": 4, "left": "132.5mm", "top": "42.0mm" }, { "nodeId": 5, "left": "165.5mm", "top": "44.5mm" }, { "nodeId": 6, "left": "194.5mm", "top": "59.5mm" }, { "nodeId": 7, "left": "15.0mm", "top": "62.5mm" }, { "nodeId": 8, "left": "52.5mm", "top": "83.5mm" }, { "nodeId": 9, "left": "90.9mm", "top": "75.7mm" }, { "nodeId": 10, "left": "122.8mm", "top": "80.9mm" }, { "nodeId": 11, "left": "144.8mm", "top": "72.0mm" }, { "nodeId": 12, "left": "175.5mm", "top": "71.0mm" }, { "nodeId": 13, "left": "16.5mm", "top": "96.5mm" }, { "nodeId": 14, "left": "41.5mm", "top": "95.0mm" }, { "nodeId": 15, "left": "78.3mm", "top": "97.0mm" }, { "nodeId": 16, "left": "99.5mm", "top": "97.5mm" }, { "nodeId": 17, "left": "132.2mm", "top": "100.3mm" }, { "nodeId": 18, "left": "155.8mm", "top": "96.1mm" }, { "nodeId": 19, "left": "195.0mm", "top": "93.0mm" }, { "nodeId": 20, "left": "30.5mm", "top": "118.0mm" }, { "nodeId": 21, "left": "65.5mm", "top": "118.4mm" }, { "nodeId": 22, "left": "85.5mm", "top": "123.0mm" }, { "nodeId": 23, "left": "111.0mm", "top": "113.0mm" }, { "nodeId": 24, "left": "141.0mm", "top": "123.5mm" }, { "nodeId": 25, "left": "168.0mm", "top": "116.0mm" }, { "nodeId": 26, "left": "18.0mm", "top": "148.0mm" }, { "nodeId": 27, "left": "53.5mm", "top": "140.0mm" }, { "nodeId": 28, "left": "81.7mm", "top": "142.4mm" }, { "nodeId": 29, "left": "97.5mm", "top": "138.5mm" }, { "nodeId": 30, "left": "129.5mm", "top": "145.0mm" }, { "nodeId": 31, "left": "157.0mm", "top": "142.5mm" }, { "nodeId": 32, "left": "189.8mm", "top": "136.1mm" }, { "nodeId": 33, "left": "30.0mm", "top": "168.5mm" }, { "nodeId": 34, "left": "54.5mm", "top": "162.0mm" }, { "nodeId": 35, "left": "89.4mm", "top": "160.7mm" }, { "nodeId": 36, "left": "117.5mm", "top": "166.5mm" }, { "nodeId": 37, "left": "139.0mm", "top": "172.5mm" }, { "nodeId": 38, "left": "173.0mm", "top": "158.5mm" }, { "nodeId": 39, "left": "22.0mm", "top": "192.0mm" }, { "nodeId": 40, "left": "52.8mm", "top": "190.0mm" }, { "nodeId": 41, "left": "73.0mm", "top": "186.0mm" }, { "nodeId": 42, "left": "98.0mm", "top": "199.5mm" }, { "nodeId": 43, "left": "123.0mm", "top": "197.5mm" }, { "nodeId": 44, "left": "160.0mm", "top": "190.0mm" }, { "nodeId": 45, "left": "186.3mm", "top": "184.2mm" }, { "nodeId": 46, "left": "43.0mm", "top": "214.5mm" }, { "nodeId": 47, "left": "70.0mm", "top": "207.0mm" }, { "nodeId": 48, "left": "82.5mm", "top": "221.5mm" }, { "nodeId": 49, "left": "109.0mm", "top": "219.5mm" }, { "nodeId": 50, "left": "140.0mm", "top": "212.5mm" }, { "nodeId": 51, "left": "167.0mm", "top": "215.5mm" }, { "nodeId": 52, "left": "21.5mm", "top": "237.0mm" }, { "nodeId": 53, "left": "52.5mm", "top": "230.5mm" }, { "nodeId": 54, "left": "78.5mm", "top": "238.5mm" }, { "nodeId": 55, "left": "102.0mm", "top": "242.0mm" }, { "nodeId": 56, "left": "129.5mm", "top": "238.5mm" }, { "nodeId": 57, "left": "160.0mm", "top": "240.0mm" }, { "nodeId": 58, "left": "190.0mm", "top": "241.0mm" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
# Space Route 1
|
||||
|
||||
**Title:** Проложи Маршрут
|
||||
|
||||
## Notes
|
||||
|
||||
9-page space route worksheet. Player and enemy navigate a hex graph.
|
||||
Generated by `scripts/generate-route.mjs` with per-page seed configs.
|
||||
|
||||
See `scripts/generate-route.mjs` PAGES array for full page configs including:
|
||||
- diffRange (difficulty), routeLength, seeds
|
||||
- Ship/enemy images, destination objects, decorative objects
|
||||
- flipEnemy/flipPlayer flags for ship orientation
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,687 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700;800;900&display=swap" rel="stylesheet">
|
||||
<title>Проложи Маршрут</title>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: { nunito: ['Nunito', 'sans-serif'] },
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
@page { size: A4; margin: 0; }
|
||||
body { font-family: 'Nunito', sans-serif; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-200 font-nunito">
|
||||
|
||||
<script>
|
||||
// ============================================================
|
||||
// CONFIGURATION PER PAGE
|
||||
// ============================================================
|
||||
// diffRange: [minNodeValue, maxSpread]
|
||||
const PAGES = [
|
||||
{
|
||||
enemyShipImg: '../../assets/hero-images/spaceship4.jpeg',
|
||||
enemyLabel: 'Пиратский крейсер',
|
||||
playerShipImg: '../../assets/hero-images/spaceship2.png',
|
||||
footerImg: '../../assets/footers/planet3.jpeg',
|
||||
destObjectImg: '../../assets/icons/pack3-asteroids/asteroid5.png',
|
||||
decoObjects: [
|
||||
{ img: '../../assets/icons/pack3-asteroids/asteroid3.png' },
|
||||
{ img: '../../assets/icons/pack3-asteroids/asteroid7.png' },
|
||||
{ img: '../../assets/icons/pack3-asteroids/asteroid11.png' },
|
||||
],
|
||||
diffRange: [3, 6],
|
||||
routeLength: 5,
|
||||
seed: 42001,
|
||||
flipEnemy: false, // spaceship4 faces right
|
||||
flipPlayer: true, // spaceship2 faces left
|
||||
},
|
||||
{
|
||||
enemyShipImg: '../../assets/hero-images/spaceship6.jpeg',
|
||||
enemyLabel: 'Патрульный разведчик',
|
||||
playerShipImg: '../../assets/hero-images/spaceship7.png',
|
||||
footerImg: '../../assets/footers/planet5.jpeg',
|
||||
destObjectImg: '../../assets/icons/pack3-asteroids/asteroid9.png',
|
||||
decoObjects: [
|
||||
{ img: '../../assets/icons/pack3-asteroids/asteroid14.png' },
|
||||
{ img: '../../assets/icons/pack3-asteroids/asteroid2.png' },
|
||||
{ img: '../../assets/icons/pack3-asteroids/asteroid16.png' },
|
||||
{ img: '../../assets/icons/pack3-asteroids/asteroid4.png' },
|
||||
],
|
||||
diffRange: [4, 10],
|
||||
routeLength: 5,
|
||||
seed: 42002,
|
||||
flipEnemy: true, // spaceship6 faces left
|
||||
flipPlayer: true, // spaceship7 faces left
|
||||
},
|
||||
{
|
||||
enemyShipImg: '../../assets/hero-images/spaceship3.jpeg',
|
||||
enemyLabel: 'Контрабандист',
|
||||
playerShipImg: '../../assets/hero-images/spaceship9.png',
|
||||
footerImg: '../../assets/footers/planet1.jpeg',
|
||||
destObjectImg: '../../assets/icons/pack3-asteroids/asteroid1.png',
|
||||
decoObjects: [
|
||||
{ img: '../../assets/icons/pack3-asteroids/asteroid10.png' },
|
||||
{ img: '../../assets/icons/pack3-asteroids/asteroid6.png' },
|
||||
{ img: '../../assets/icons/pack3-asteroids/asteroid12.png' },
|
||||
],
|
||||
diffRange: [2, 18],
|
||||
routeLength: 6,
|
||||
seed: 42003,
|
||||
flipEnemy: false, // spaceship3 faces right
|
||||
flipPlayer: true, // spaceship9 faces left
|
||||
},
|
||||
];
|
||||
|
||||
const PLAYER_ROUTE_CELLS = 10;
|
||||
const GRID_COLS = 6;
|
||||
const GRID_ROWS = 7;
|
||||
|
||||
// ============================================================
|
||||
// SEEDED PRNG (mulberry32) — stable output across reloads
|
||||
// ============================================================
|
||||
let _seed = 0;
|
||||
function seedRng(s) { _seed = s | 0; }
|
||||
function seededRandom() {
|
||||
_seed |= 0; _seed = _seed + 0x6D2B79F5 | 0;
|
||||
let t = Math.imul(_seed ^ _seed >>> 15, 1 | _seed);
|
||||
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
||||
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// GRAPH ALGORITHM
|
||||
// ============================================================
|
||||
const NODE_R_MM = 5;
|
||||
const MAP_TOP = 44;
|
||||
const MAP_BOTTOM = 242;
|
||||
const MAP_LEFT = 14;
|
||||
const MAP_RIGHT = 196;
|
||||
|
||||
function rand(min, max) {
|
||||
return Math.floor(seededRandom() * (max - min + 1)) + min;
|
||||
}
|
||||
function randF(min, max) {
|
||||
return min + seededRandom() * (max - min);
|
||||
}
|
||||
function distN(a, b) {
|
||||
return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
|
||||
}
|
||||
|
||||
function generateNodes() {
|
||||
const nodes = [];
|
||||
const cellW = (MAP_RIGHT - MAP_LEFT) / GRID_COLS;
|
||||
const cellH = (MAP_BOTTOM - MAP_TOP) / GRID_ROWS;
|
||||
const jitter = Math.min(cellW, cellH) * 0.25;
|
||||
|
||||
let id = 0;
|
||||
for (let row = 0; row < GRID_ROWS; row++) {
|
||||
for (let col = 0; col < GRID_COLS; col++) {
|
||||
const hexOffset = (row % 2 === 1) ? cellW * 0.5 : 0;
|
||||
const baseX = MAP_LEFT + col * cellW + cellW / 2 + hexOffset;
|
||||
const baseY = MAP_TOP + row * cellH + cellH / 2;
|
||||
|
||||
if (baseX < MAP_LEFT + 5 || baseX > MAP_RIGHT - 5) continue;
|
||||
|
||||
const x = baseX + randF(-jitter, jitter);
|
||||
const y = baseY + randF(-jitter, jitter);
|
||||
|
||||
const cx = Math.max(MAP_LEFT + NODE_R_MM, Math.min(MAP_RIGHT - NODE_R_MM, x));
|
||||
const cy = Math.max(MAP_TOP + NODE_R_MM, Math.min(MAP_BOTTOM - NODE_R_MM, y));
|
||||
|
||||
nodes.push({ x: cx, y: cy, id: id++, value: 0, neighbors: [] });
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
function generateEdges(nodes) {
|
||||
const edges = [];
|
||||
const maxEdgeDist = 45;
|
||||
|
||||
const candidates = [];
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
for (let j = i + 1; j < nodes.length; j++) {
|
||||
const d = distN(nodes[i], nodes[j]);
|
||||
if (d <= maxEdgeDist) candidates.push({ i, j, d });
|
||||
}
|
||||
}
|
||||
candidates.sort((a, b) => a.d - b.d);
|
||||
|
||||
const MAX_DEGREE = 5;
|
||||
for (const { i, j } of candidates) {
|
||||
if (nodes[i].neighbors.length >= MAX_DEGREE || nodes[j].neighbors.length >= MAX_DEGREE) continue;
|
||||
let crosses = false;
|
||||
for (const e of edges) {
|
||||
if (e.i === i || e.j === i || e.i === j || e.j === j) continue;
|
||||
if (segmentsIntersect(nodes[i], nodes[j], nodes[e.i], nodes[e.j])) { crosses = true; break; }
|
||||
}
|
||||
if (!crosses) {
|
||||
edges.push({ i, j });
|
||||
nodes[i].neighbors.push(j);
|
||||
nodes[j].neighbors.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
ensureConnected(nodes, edges);
|
||||
|
||||
// Ensure min degree 2
|
||||
for (const n of nodes) {
|
||||
if (n.neighbors.length < 2) {
|
||||
let best = null, bestDist = Infinity;
|
||||
for (const other of nodes) {
|
||||
if (other.id === n.id || n.neighbors.includes(other.id)) continue;
|
||||
const d = distN(n, other);
|
||||
if (d < bestDist) { bestDist = d; best = other; }
|
||||
}
|
||||
if (best) {
|
||||
edges.push({ i: n.id, j: best.id });
|
||||
n.neighbors.push(best.id);
|
||||
best.neighbors.push(n.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return edges;
|
||||
}
|
||||
|
||||
// Trim a node's edges down to exactly targetDegree, keeping nearest neighbors
|
||||
function trimNodeDegree(nodes, edges, nodeId, targetDegree) {
|
||||
const n = nodes[nodeId];
|
||||
while (n.neighbors.length > targetDegree) {
|
||||
// Remove the farthest neighbor
|
||||
let farthestNb = -1, farthestDist = 0;
|
||||
for (const nb of n.neighbors) {
|
||||
const d = distN(n, nodes[nb]);
|
||||
if (d > farthestDist) { farthestDist = d; farthestNb = nb; }
|
||||
}
|
||||
// Remove edge
|
||||
n.neighbors = n.neighbors.filter(nb => nb !== farthestNb);
|
||||
nodes[farthestNb].neighbors = nodes[farthestNb].neighbors.filter(nb => nb !== nodeId);
|
||||
// Remove from edges array
|
||||
for (let ei = edges.length - 1; ei >= 0; ei--) {
|
||||
const e = edges[ei];
|
||||
if ((e.i === nodeId && e.j === farthestNb) || (e.j === nodeId && e.i === farthestNb)) {
|
||||
edges.splice(ei, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ensureConnected(nodes, edges) {
|
||||
const component = new Array(nodes.length).fill(-1);
|
||||
let compId = 0;
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
if (component[i] !== -1) continue;
|
||||
const queue = [i];
|
||||
component[i] = compId;
|
||||
while (queue.length) {
|
||||
const cur = queue.shift();
|
||||
for (const nb of nodes[cur].neighbors) {
|
||||
if (component[nb] === -1) { component[nb] = compId; queue.push(nb); }
|
||||
}
|
||||
}
|
||||
compId++;
|
||||
}
|
||||
for (let c = 1; c < compId; c++) {
|
||||
let best = null, bestDist = Infinity;
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
if (component[i] !== 0) continue;
|
||||
for (let j = 0; j < nodes.length; j++) {
|
||||
if (component[j] !== c) continue;
|
||||
const d = distN(nodes[i], nodes[j]);
|
||||
if (d < bestDist) { bestDist = d; best = { i, j }; }
|
||||
}
|
||||
}
|
||||
if (best) {
|
||||
edges.push(best);
|
||||
nodes[best.i].neighbors.push(best.j);
|
||||
nodes[best.j].neighbors.push(best.i);
|
||||
for (let k = 0; k < nodes.length; k++) {
|
||||
if (component[k] === c) component[k] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function segmentsIntersect(p1, p2, p3, p4) {
|
||||
const d1 = crossP(p3, p4, p1), d2 = crossP(p3, p4, p2);
|
||||
const d3 = crossP(p1, p2, p3), d4 = crossP(p1, p2, p4);
|
||||
return ((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) &&
|
||||
((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0));
|
||||
}
|
||||
function crossP(pi, pj, pk) {
|
||||
return (pk.x - pi.x) * (pj.y - pi.y) - (pj.x - pi.x) * (pk.y - pi.y);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// VALUE ASSIGNMENT
|
||||
// ============================================================
|
||||
|
||||
function assignValues(nodes, minVal, maxSpread) {
|
||||
const maxVal = minVal + Math.ceil(maxSpread * 1.3);
|
||||
const order = [...Array(nodes.length).keys()];
|
||||
order.sort((a, b) => nodes[b].neighbors.length - nodes[a].neighbors.length);
|
||||
|
||||
for (let attempt = 0; attempt < 300; attempt++) {
|
||||
if (tryAssign(nodes, order, minVal, maxSpread, maxVal)) {
|
||||
if (validateGraph(nodes, minVal, maxSpread)) return true;
|
||||
}
|
||||
for (let i = order.length - 1; i > 0; i--) {
|
||||
if (Math.random() < 0.4) {
|
||||
const j = rand(0, i);
|
||||
[order[i], order[j]] = [order[j], order[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function tryAssign(nodes, order, minVal, maxSpread, maxVal) {
|
||||
for (const n of nodes) n.value = -1;
|
||||
|
||||
for (const idx of order) {
|
||||
const candidates = [];
|
||||
for (let v = minVal; v <= maxVal; v++) {
|
||||
let valid = true;
|
||||
const signedDiffs = [];
|
||||
|
||||
for (const nb of nodes[idx].neighbors) {
|
||||
if (nodes[nb].value === -1) continue;
|
||||
const absDiff = Math.abs(v - nodes[nb].value);
|
||||
if (absDiff < 1 || absDiff > maxSpread) { valid = false; break; }
|
||||
const sd = v - nodes[nb].value;
|
||||
if (signedDiffs.includes(sd)) { valid = false; break; }
|
||||
signedDiffs.push(sd);
|
||||
}
|
||||
if (!valid) continue;
|
||||
|
||||
for (const nb of nodes[idx].neighbors) {
|
||||
if (nodes[nb].value === -1) continue;
|
||||
const myDiff = v - nodes[nb].value;
|
||||
for (const nb2 of nodes[nb].neighbors) {
|
||||
if (nb2 === idx || nodes[nb2].value === -1) continue;
|
||||
if (nodes[nb2].value - nodes[nb].value === myDiff) { valid = false; break; }
|
||||
}
|
||||
if (!valid) break;
|
||||
}
|
||||
if (valid) candidates.push(v);
|
||||
}
|
||||
if (candidates.length === 0) return false;
|
||||
const center = minVal + maxSpread * 0.5;
|
||||
candidates.sort((a, b) => Math.abs(a - center) - Math.abs(b - center));
|
||||
const pick = Math.min(candidates.length - 1, Math.floor(Math.random() * Math.min(candidates.length, 4)));
|
||||
nodes[idx].value = candidates[pick];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function validateGraph(nodes, minVal, maxSpread) {
|
||||
for (const n of nodes) {
|
||||
if (n.value < minVal) return false;
|
||||
const diffs = [];
|
||||
for (const nb of n.neighbors) {
|
||||
const d = nodes[nb].value - n.value;
|
||||
const ad = Math.abs(d);
|
||||
if (ad < 1 || ad > maxSpread) return false;
|
||||
if (diffs.includes(d)) return false;
|
||||
diffs.push(d);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// ROUTE FINDING — directional, avoids loops
|
||||
// ============================================================
|
||||
|
||||
// Find corner node: top-left or bottom-left
|
||||
function findCornerNode(nodes, corner) {
|
||||
let sorted;
|
||||
if (corner === 'top-left') {
|
||||
sorted = [...nodes].sort((a, b) => (a.x + a.y * 2) - (b.x + b.y * 2));
|
||||
} else {
|
||||
// bottom-left: low x, high y
|
||||
sorted = [...nodes].sort((a, b) => (a.x - a.y * 2) - (b.x - b.y * 2));
|
||||
}
|
||||
return sorted[0];
|
||||
}
|
||||
|
||||
// Directional route: prefer moving toward a target area, avoid backtracking
|
||||
function findDirectionalRoute(nodes, startId, length, targetArea) {
|
||||
// targetArea: {x, y} — general direction to aim for
|
||||
const best = { path: null, score: -Infinity };
|
||||
|
||||
for (let trial = 0; trial < 30; trial++) {
|
||||
const path = [startId];
|
||||
if (buildDirectionalPath(nodes, path, length, targetArea)) {
|
||||
const score = routeQualityScore(nodes, path, targetArea);
|
||||
if (score > best.score) {
|
||||
best.path = [...path];
|
||||
best.score = score;
|
||||
}
|
||||
}
|
||||
}
|
||||
return best.path;
|
||||
}
|
||||
|
||||
function buildDirectionalPath(nodes, path, targetLen, target) {
|
||||
if (path.length === targetLen + 1) return true;
|
||||
const current = nodes[path[path.length - 1]];
|
||||
const nbs = [...current.neighbors].filter(nb => !path.includes(nb));
|
||||
if (nbs.length === 0) return false;
|
||||
|
||||
// Score each neighbor: prefer those closer to target, penalize going backward
|
||||
const scored = nbs.map(nb => {
|
||||
const n = nodes[nb];
|
||||
const distToTarget = Math.sqrt((n.x - target.x) ** 2 + (n.y - target.y) ** 2);
|
||||
// Progress: how much closer we get vs current position
|
||||
const curDist = Math.sqrt((current.x - target.x) ** 2 + (current.y - target.y) ** 2);
|
||||
const progress = curDist - distToTarget;
|
||||
// Small random factor for variety
|
||||
return { nb, score: progress + randF(-15, 15) };
|
||||
});
|
||||
scored.sort((a, b) => b.score - a.score);
|
||||
|
||||
for (const { nb } of scored) {
|
||||
path.push(nb);
|
||||
if (buildDirectionalPath(nodes, path, targetLen, target)) return true;
|
||||
path.pop();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Score route: higher = more directional, less loopy
|
||||
function routeQualityScore(nodes, path, target) {
|
||||
const start = nodes[path[0]];
|
||||
const end = nodes[path[path.length - 1]];
|
||||
|
||||
// Distance from end to target
|
||||
const endDist = Math.sqrt((end.x - target.x) ** 2 + (end.y - target.y) ** 2);
|
||||
|
||||
// Straightness: total path length vs straight-line distance
|
||||
let totalDist = 0;
|
||||
for (let i = 1; i < path.length; i++) {
|
||||
totalDist += distN(nodes[path[i - 1]], nodes[path[i]]);
|
||||
}
|
||||
const straightLine = distN(start, end);
|
||||
const straightness = straightLine / (totalDist + 1);
|
||||
|
||||
return -endDist * 0.5 + straightness * 50 + straightLine * 0.3;
|
||||
}
|
||||
|
||||
function findShortestPath(nodes, fromId, toId) {
|
||||
const prev = new Array(nodes.length).fill(-1);
|
||||
const visited = new Set([fromId]);
|
||||
const queue = [fromId];
|
||||
while (queue.length) {
|
||||
const cur = queue.shift();
|
||||
if (cur === toId) break;
|
||||
for (const nb of nodes[cur].neighbors) {
|
||||
if (!visited.has(nb)) { visited.add(nb); prev[nb] = cur; queue.push(nb); }
|
||||
}
|
||||
}
|
||||
if (!visited.has(toId)) return null;
|
||||
const path = [];
|
||||
let cur = toId;
|
||||
while (cur !== -1) { path.unshift(cur); cur = prev[cur]; }
|
||||
return path;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PAGE GENERATION
|
||||
// ============================================================
|
||||
|
||||
function generatePage(config, pageIndex) {
|
||||
seedRng(config.seed); // deterministic output per page
|
||||
let nodes, edges, enemyRoute, playerRoute;
|
||||
let success = false;
|
||||
|
||||
for (let attempt = 0; attempt < 80; attempt++) {
|
||||
nodes = generateNodes();
|
||||
edges = generateEdges(nodes);
|
||||
|
||||
// Find corner starts
|
||||
const enemyStartNode = findCornerNode(nodes, 'top-left');
|
||||
const playerStartNode = findCornerNode(nodes, 'bottom-left');
|
||||
|
||||
// Trim start nodes to exactly 2 edges
|
||||
trimNodeDegree(nodes, edges, enemyStartNode.id, 2);
|
||||
trimNodeDegree(nodes, edges, playerStartNode.id, 2);
|
||||
|
||||
const [minVal, maxSpread] = config.diffRange;
|
||||
if (!assignValues(nodes, minVal, maxSpread)) continue;
|
||||
|
||||
// Enemy route: from top-left corner toward bottom-right area
|
||||
const targetArea = { x: MAP_RIGHT - 30, y: MAP_BOTTOM - 40 };
|
||||
enemyRoute = findDirectionalRoute(nodes, enemyStartNode.id, config.routeLength, targetArea);
|
||||
if (!enemyRoute) continue;
|
||||
|
||||
const dest = enemyRoute[enemyRoute.length - 1];
|
||||
|
||||
// Player route: from bottom-left corner to destination
|
||||
if (playerStartNode.id === dest || enemyRoute.includes(playerStartNode.id)) continue;
|
||||
playerRoute = findShortestPath(nodes, playerStartNode.id, dest);
|
||||
if (playerRoute && playerRoute.length >= 3) {
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
return `<div class="w-[210mm] h-[297mm] mx-auto bg-white flex items-center justify-center text-red-500 text-xl">Page ${pageIndex + 1}: Generation failed after 80 attempts</div>`;
|
||||
}
|
||||
|
||||
return renderPage(nodes, edges, enemyRoute, playerRoute, config, pageIndex);
|
||||
}
|
||||
|
||||
function renderPage(nodes, edges, enemyRoute, playerRoute, config, pageIndex) {
|
||||
const dest = enemyRoute[enemyRoute.length - 1];
|
||||
const enemyStart = enemyRoute[0];
|
||||
const playerStart = playerRoute[0];
|
||||
|
||||
// Pick nodes for decorative objects (not start/dest nodes, spread across map)
|
||||
const decoNodeIds = [];
|
||||
const usedIds = new Set([enemyStart, playerStart, dest, ...enemyRoute, ...playerRoute]);
|
||||
const availableNodes = nodes.filter(n => !usedIds.has(n.id));
|
||||
// Spread them out by picking from different y-regions
|
||||
const sortedByRegion = [...availableNodes].sort((a, b) => a.y - b.y);
|
||||
const regionSize = Math.floor(sortedByRegion.length / (config.decoObjects.length + 1));
|
||||
for (let di = 0; di < config.decoObjects.length; di++) {
|
||||
const regionStart = regionSize * (di + 0.5);
|
||||
const idx = Math.min(Math.floor(regionStart + randF(-2, 2)), sortedByRegion.length - 1);
|
||||
if (idx >= 0 && !decoNodeIds.includes(sortedByRegion[idx].id)) {
|
||||
decoNodeIds.push(sortedByRegion[Math.max(0, idx)].id);
|
||||
}
|
||||
}
|
||||
|
||||
// Build SVG for edges
|
||||
let edgeSvg = '';
|
||||
for (const e of edges) {
|
||||
const n1 = nodes[e.i], n2 = nodes[e.j];
|
||||
edgeSvg += `<line x1="${n1.x}mm" y1="${n1.y}mm" x2="${n2.x}mm" y2="${n2.y}mm"
|
||||
stroke="#a5b4fc" stroke-width="0.7" stroke-dasharray="2.5 2" stroke-linecap="round" opacity="0.45"/>`;
|
||||
}
|
||||
|
||||
// Build node circles
|
||||
const asteroidNodeIds = new Set([dest, ...decoNodeIds]);
|
||||
let nodesHtml = '';
|
||||
for (const n of nodes) {
|
||||
const isEnemyStart = n.id === enemyStart;
|
||||
const isPlayerStart = n.id === playerStart;
|
||||
const isStartNode = isEnemyStart || isPlayerStart;
|
||||
const hasAsteroid = asteroidNodeIds.has(n.id);
|
||||
|
||||
const borderWidth = isStartNode ? '2.5px' : '1.5px';
|
||||
const borderColor = isStartNode ? 'border-indigo-600' : 'border-indigo-300';
|
||||
const bgColor = isStartNode ? 'bg-indigo-50' : 'bg-white';
|
||||
const fontSize = isStartNode ? '13px' : '11px';
|
||||
const shadow = isStartNode ? 'shadow-md shadow-indigo-200' : 'shadow-sm';
|
||||
const baseSize = isStartNode ? NODE_R_MM * 2.4 : NODE_R_MM * 2;
|
||||
const size = hasAsteroid ? baseSize / 1.5 : baseSize;
|
||||
const offset = size / 2;
|
||||
|
||||
nodesHtml += `
|
||||
<div class="absolute flex items-center justify-center rounded-full ${borderColor} ${bgColor} ${shadow}"
|
||||
style="left:${n.x}mm;top:${n.y}mm;width:${size}mm;height:${size}mm;margin-left:-${offset}mm;margin-top:-${offset}mm;z-index:15;border-width:${borderWidth};">
|
||||
<span class="font-extrabold text-indigo-950 leading-none" style="font-size:${fontSize};">${n.value}</span>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// Destination object — large asteroid under the destination node
|
||||
const destNode = nodes[dest];
|
||||
let destObjHtml = `
|
||||
<img src="${config.destObjectImg}" class="absolute object-contain"
|
||||
style="left:${destNode.x}mm;top:${destNode.y}mm;width:30mm;height:30mm;margin-left:-15mm;margin-top:-15mm;z-index:4;" alt="">`;
|
||||
|
||||
// Decorative objects — placed under specific nodes, large
|
||||
let decosHtml = '';
|
||||
for (let di = 0; di < config.decoObjects.length && di < decoNodeIds.length; di++) {
|
||||
const dn = nodes[decoNodeIds[di]];
|
||||
decosHtml += `
|
||||
<img src="${config.decoObjects[di].img}" class="absolute object-contain"
|
||||
style="left:${dn.x}mm;top:${dn.y}mm;width:24mm;height:24mm;margin-left:-12mm;margin-top:-12mm;z-index:4;" alt="">`;
|
||||
}
|
||||
|
||||
// Decorative stars — bigger, darker, more
|
||||
let starsHtml = '';
|
||||
for (let i = 0; i < 70; i++) {
|
||||
const sx = randF(6, 204);
|
||||
const sy = randF(30, 270);
|
||||
const size = randF(10, 22);
|
||||
const opacity = randF(0.12, 0.35);
|
||||
const symbols = ['✦', '✧', '⋆', '·', '∗', '⊹'];
|
||||
starsHtml += `<span class="absolute text-indigo-300" style="left:${sx}mm;top:${sy}mm;font-size:${size}px;opacity:${opacity};z-index:2;pointer-events:none;">${symbols[rand(0, symbols.length - 1)]}</span>`;
|
||||
}
|
||||
|
||||
// Enemy route: circle with start value + diff cells
|
||||
const enemyDiffs = [];
|
||||
for (let i = 1; i < enemyRoute.length; i++) {
|
||||
enemyDiffs.push(nodes[enemyRoute[i]].value - nodes[enemyRoute[i - 1]].value);
|
||||
}
|
||||
|
||||
let enemyRouteCells = `<div class="inline-flex items-center justify-center rounded-full border-[2px] border-indigo-500 bg-indigo-50 shrink-0" style="width:8mm;height:8mm;">
|
||||
<span class="text-[12px] font-extrabold text-indigo-950">${nodes[enemyStart].value}</span>
|
||||
</div>`;
|
||||
for (const diff of enemyDiffs) {
|
||||
const diffStr = (diff > 0 ? '+' : '') + diff;
|
||||
enemyRouteCells += `<div class="inline-flex items-center justify-center shrink-0 border-[1.5px] border-indigo-300 rounded bg-white" style="width:9mm;height:7mm;margin-left:1mm;">
|
||||
<span class="text-[11px] font-bold text-indigo-950">${diffStr}</span>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// Player route: circle + 10 empty cells
|
||||
const cellCount = Math.max(PLAYER_ROUTE_CELLS, playerRoute.length - 1);
|
||||
let playerRouteCells = `<div class="inline-flex items-center justify-center rounded-full border-[2px] border-indigo-500 bg-indigo-50 shrink-0" style="width:8mm;height:8mm;">
|
||||
<span class="text-[12px] font-extrabold text-indigo-950">${nodes[playerStart].value}</span>
|
||||
</div>`;
|
||||
for (let i = 0; i < cellCount; i++) {
|
||||
playerRouteCells += `<div class="inline-flex items-center justify-center shrink-0 border-[1.5px] border-indigo-200 border-dashed rounded bg-white" style="width:9mm;height:7mm;margin-left:1mm;">
|
||||
</div>`;
|
||||
}
|
||||
|
||||
const isLastPage = pageIndex === PAGES.length - 1;
|
||||
const pageBreak = isLastPage ? '' : 'break-after: page;';
|
||||
|
||||
const [minVal, maxSpread] = config.diffRange;
|
||||
const nodeValues = nodes.map(n => n.value);
|
||||
const actualMin = Math.min(...nodeValues);
|
||||
const actualMax = Math.max(...nodeValues);
|
||||
|
||||
return `
|
||||
<div class="w-[210mm] h-[297mm] relative overflow-hidden mx-auto bg-white" style="${pageBreak}">
|
||||
|
||||
<!-- Footer with planet -->
|
||||
<div class="absolute bottom-0 left-0 right-0 h-[80mm] z-0">
|
||||
<div class="absolute top-0 left-0 right-0 h-full z-10" style="background: linear-gradient(to bottom, white 0%, rgba(255,255,255,0.6) 25%, transparent 50%);"></div>
|
||||
<img src="${config.footerImg}" class="w-full h-full object-cover object-top" alt="">
|
||||
</div>
|
||||
|
||||
<!-- Map content -->
|
||||
<div class="absolute inset-0" style="z-index:3;">
|
||||
|
||||
<!-- Stars -->
|
||||
${starsHtml}
|
||||
|
||||
<!-- Destination object -->
|
||||
${destObjHtml}
|
||||
|
||||
<!-- Decorative objects -->
|
||||
${decosHtml}
|
||||
|
||||
<!-- Edges SVG -->
|
||||
<svg class="absolute inset-0 w-full h-full" style="z-index:5;" xmlns="http://www.w3.org/2000/svg">
|
||||
${edgeSvg}
|
||||
</svg>
|
||||
|
||||
<!-- Nodes -->
|
||||
${nodesHtml}
|
||||
|
||||
<!-- Header with enemy ship -->
|
||||
<div class="absolute top-0 left-0 right-0 z-30">
|
||||
<div class="flex items-start">
|
||||
<!-- Enemy ship image — 40% of header -->
|
||||
<img src="${config.enemyShipImg}" class="shrink-0 object-contain" style="width:48%;max-height:50mm;z-index:4;${config.flipEnemy ? 'transform:scaleX(-1);' : ''}" alt="">
|
||||
<!-- Title — right side -->
|
||||
<div class="flex-1 flex flex-col items-center justify-center text-center pt-[5mm]">
|
||||
<h1 class="text-[1.4rem] font-extrabold leading-tight tracking-tight text-indigo-950">Проложи Маршрут</h1>
|
||||
<p class="text-[0.75rem] font-medium text-indigo-300 mt-0">Найди путь в космосе!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Enemy route card (right side, under title) -->
|
||||
<div class="absolute z-30" style="top:19mm;right:8mm;">
|
||||
<div class="flex items-center gap-[2mm] px-[3mm] py-[1.5mm] rounded-lg border-[1.5px] border-indigo-100" style="background:rgba(255,255,255,0.75);">
|
||||
<div class="text-[11px] font-bold text-indigo-900 whitespace-nowrap">${config.enemyLabel}:</div>
|
||||
<div class="flex items-center flex-nowrap">${enemyRouteCells}</div>
|
||||
</div>
|
||||
<div class="text-[10px] text-indigo-400 mt-[0.5mm] text-right pr-[2mm]">📍 Проследи маршрут — куда он летит?</div>
|
||||
</div>
|
||||
|
||||
<!-- Player ship image (over footer, left side) -->
|
||||
<img src="${config.playerShipImg}" class="absolute object-contain" style="bottom:8mm;left:0;width:48%;max-height:60mm;z-index:4;${config.flipPlayer ? 'transform:scaleX(-1);' : ''}" alt="">
|
||||
|
||||
<!-- Player route card (right side) -->
|
||||
<div class="absolute z-30" style="bottom:14mm;right:8mm;">
|
||||
<div class="text-[12px] font-semibold text-gray-800 mb-[1mm] text-right pr-[2mm]">Рассчитай маршрут до цели перехвата</div>
|
||||
<div class="flex items-center gap-[2mm] px-[3mm] py-[1.5mm] rounded-lg border-[1.5px] border-indigo-100" style="background:rgba(255,255,255,0.75);">
|
||||
<div class="text-[11px] font-bold text-indigo-900 whitespace-nowrap">Твой истребитель:</div>
|
||||
<div class="flex items-center flex-nowrap">${playerRouteCells}</div>
|
||||
</div>
|
||||
<div class="text-[12px] font-semibold text-gray-800 mt-[1mm] text-right pr-[2mm]">✏️ Запиши разницу чисел, чтобы добраться до цели!</div>
|
||||
</div>
|
||||
|
||||
<!-- Debug info -->
|
||||
<div class="absolute z-40 text-[7px] text-indigo-300" style="bottom:3mm;left:3mm;">
|
||||
[${minVal},${maxSpread}] | ${nodes.length} nodes | values ${actualMin}-${actualMax} | enemy ${enemyRoute.length} steps | player shortest ${playerRoute.length - 1} steps
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// GENERATE ALL PAGES
|
||||
// ============================================================
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
let html = '';
|
||||
for (let i = 0; i < PAGES.length; i++) {
|
||||
html += generatePage(PAGES[i], i);
|
||||
}
|
||||
document.body.innerHTML = html;
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user