collin/mahjong · 82287db9
Two online modes over antics: pure online, and party with phone hands
Collin Richards · 2026-08-20 08:50 UTC · 82287db9c0f56beec27f127645ba58fc3e20c3bc · parent 323803dc · browse files
modifiedTODO.md+2 −0
| ⋯ 25 unchanged lines | |||
| 26 | 26 | - [ ] computer shouldn't be able to take the pung before the throw animation completes | |
| 27 | 27 | - [ ] weirdness around throwing the tiles in the center, it's like attracted to the center, even gets stopped by wall it keeps going | |
| 28 | 28 | - [ ] put the tumble back on a thrown tile — `TUMBLE` in `src/table/pool.ts` | |
| 29 | + | - [ ] fake 3d for the tiles (centered around controllign players pov) | |
| 30 | + | - [ ] consider switching physics engine to real 3d engine | |
modifiedpackage-lock.json+24 −1
| ⋯ 7 unchanged lines | |||
| 8 | 8 | "name": "mahjong", | |
| 9 | 9 | "version": "0.0.0", | |
| 10 | 10 | "dependencies": { | |
| 11 | + | "antics-sdk": "^0.3.0", | |
| 11 | 12 | "react": "^19.2.8", | |
| 12 | - | "react-dom": "^19.2.8" | |
| 13 | + | "react-dom": "^19.2.8", | |
| 14 | + | "uqr": "^0.1.3" | |
| 13 | 15 | }, | |
| 14 | 16 | "devDependencies": { | |
| 15 | 17 | "@types/node": "^24.13.3", | |
| ⋯ 834 unchanged lines | |||
| 850 | 852 | "url": "https://opencollective.com/vitest" | |
| 851 | 853 | } | |
| 852 | 854 | }, | |
| 855 | + | "node_modules/antics-protocol": { | |
| 856 | + | "version": "0.2.0", | |
| 857 | + | "resolved": "https://registry.npmjs.org/antics-protocol/-/antics-protocol-0.2.0.tgz", | |
| 858 | + | "integrity": "sha512-9kFogFzYF99ajcxCB9hWv4IRw3sgO1dF1jbw6cK7sx9EVc+rUuUh2xGhqDzdWoajZz3AmmeBhjmhfTfg2Hlq/Q==", | |
| 859 | + | "license": "SEE LICENSE IN LICENSE" | |
| 860 | + | }, | |
| 861 | + | "node_modules/antics-sdk": { | |
| 862 | + | "version": "0.3.0", | |
| 863 | + | "resolved": "https://registry.npmjs.org/antics-sdk/-/antics-sdk-0.3.0.tgz", | |
| 864 | + | "integrity": "sha512-GhgJP2HiJaZTH89MsUZyJsNiJCkslLy965BAAudCYpFGQStJMUu6Yrg4VB1famvccZYK4ghS2eKwFnCvRgWeXg==", | |
| 865 | + | "license": "SEE LICENSE IN LICENSE", | |
| 866 | + | "dependencies": { | |
| 867 | + | "antics-protocol": "^0.1.0 || ^0.2.0" | |
| 868 | + | } | |
| 869 | + | }, | |
| 853 | 870 | "node_modules/assertion-error": { | |
| 854 | 871 | "version": "2.0.1", | |
| 855 | 872 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", | |
| ⋯ 675 unchanged lines | |||
| 1531 | 1548 | "dev": true, | |
| 1532 | 1549 | "license": "MIT" | |
| 1533 | 1550 | }, | |
| 1551 | + | "node_modules/uqr": { | |
| 1552 | + | "version": "0.1.3", | |
| 1553 | + | "resolved": "https://registry.npmjs.org/uqr/-/uqr-0.1.3.tgz", | |
| 1554 | + | "integrity": "sha512-0rjE8iEJe4YmT9TOhwsZtqCMRLc5DXZUI2UEYUUg63ikBkqqE5EYWaI0etFe/5KUcmcYwLih2RND1kq+hrUJXA==", | |
| 1555 | + | "license": "MIT" | |
| 1556 | + | }, | |
| 1534 | 1557 | "node_modules/vite": { | |
| 1535 | 1558 | "version": "8.2.0", | |
| 1536 | 1559 | "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.0.tgz", | |
| ⋯ 184 unchanged lines | |||
modifiedpackage.json+3 −1
| ⋯ 10 unchanged lines | |||
| 11 | 11 | "test": "vitest run" | |
| 12 | 12 | }, | |
| 13 | 13 | "dependencies": { | |
| 14 | + | "antics-sdk": "^0.3.0", | |
| 14 | 15 | "react": "^19.2.8", | |
| 15 | - | "react-dom": "^19.2.8" | |
| 16 | + | "react-dom": "^19.2.8", | |
| 17 | + | "uqr": "^0.1.3" | |
| 16 | 18 | }, | |
| 17 | 19 | "devDependencies": { | |
| 18 | 20 | "@types/node": "^24.13.3", | |
| ⋯ 9 unchanged lines | |||
deletedpublic/tiles/back.pngbinary
Binary file
deletedpublic/tiles/tiles.svg+0 −563
| 1 | - | <?xml version="1.0" encoding="utf-8"?> | |
| 2 | - | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | |
| 3 | - | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="5504" height="352" viewBox="0 0 2752 176" version="1.1"> | |
| 4 | - | <defs> | |
| 5 | - | <g id="bt1"> | |
| 6 | - | <path d="M9,0C8.47,0,7.96,0.21,7.59,0.59l-7,7C0.21,7.96,0,8.47,0,9v77c0,1.1,0.9,2,2,2h53c0.53,0,1.04-0.21,1.41-0.59l7-7C63.79,80.04,64,79.53,64,79V2c0-1.1-0.9-2-2-2H9z"/> | |
| 7 | - | <path fill="#9F9F9F" d="M11.75,78.25l-9,9h9H55c0.33,0,0.65-0.13,0.88-0.37l7-7c0.23-0.23,0.37-1.3,0.37-1.63H11.75z"/> | |
| 8 | - | <path fill="#D7D7D7" d="M8.12,1.12l-7,7C0.88,8.35,0.75,8.67,0.75,9v66.25v1v9l2,2l7-7v-4v-1V0.75C9.42,0.75,8.35,0.88,8.12,1.12z"/> | |
| 9 | - | <path fill="#BFBFBF" d="M1.61,86.89l10.14-10.14v-0.5L1.34,86.66C1.42,86.75,1.51,86.82,1.61,86.89z"/> | |
| 10 | - | <path fill="#B7B7B7" d="M1.93,87.07l9.82-9.82v-0.5L1.61,86.89C1.71,86.96,1.82,87.02,1.93,87.07z"/> | |
| 11 | - | <path fill="#AFAFAF" d="M2.3,87.2l9.45-9.45v-0.5l-9.82,9.82C2.05,87.12,2.17,87.17,2.3,87.2z"/> | |
| 12 | - | <path fill="#A7A7A7" d="M11.75,77.75L2.3,87.2c0.14,0.03,0.29,0.05,0.45,0.05l9-9V77.75z"/> | |
| 13 | - | <path fill="#C7C7C7" d="M1.34,86.66l10.41-10.41h-0.5L1.11,86.39C1.18,86.49,1.25,86.58,1.34,86.66z"/> | |
| 14 | - | <path fill="#CDCDCD" d="M1.11,86.39l10.14-10.14h-0.5l-9.82,9.82C0.98,86.18,1.04,86.29,1.11,86.39z"/> | |
| 15 | - | <path fill="#D2D2D2" d="M0.93,86.07l9.82-9.82h-0.5L0.8,85.7C0.83,85.83,0.88,85.95,0.93,86.07z"/> | |
| 16 | - | <path fill="#D7D7D7" d="M10.25,76.25h-0.5l-9,9c0,0.15,0.02,0.3,0.05,0.45L10.25,76.25z"/> | |
| 17 | - | <path fill="#F6F6F6" d="M63.25,78.25V2.75c0-1.1-0.9-2-2-2h-50.5c-1.1,0-2,0.9-2,2v75.5c0,1.1,0.9,2,2,2h50.5C62.35,80.25,63.25,79.35,63.25,78.25z"/> | |
| 18 | - | <path fill="#FFFFFF" d="M8.75,78.25V2.75c0-1.1,0.9-2,2-2h-1c-1.1,0-2,0.9-2,2v75.5c0,1.1,0.9,2,2,2h1C9.65,80.25,8.75,79.35,8.75,78.25z"/> | |
| 19 | - | </g> | |
| 20 | - | <g id="bt2"> | |
| 21 | - | <path fill="#0000FF" d="M9,0C8.47,0,7.96,0.21,7.59,0.59l-7,7C0.21,7.96,0,8.47,0,9v77c0,1.1,0.9,2,2,2h53c0.53,0,1.04-0.21,1.41-0.59l7-7C63.79,80.04,64,79.53,64,79V2c0-1.1-0.9-2-2-2H9z"/> | |
| 22 | - | <path fill="#8F8FFF" d="M11.75,78.25l-9,9h9H55c0.33,0,0.65-0.13,0.88-0.37l7-7c0.23-0.23,0.37-1.3,0.37-1.63H11.75z"/> | |
| 23 | - | <path fill="#C7C7FF" d="M8.12,1.12l-7,7C0.88,8.35,0.75,8.67,0.75,9v66.25v1v9l2,2l7-7v-4v-1V0.75C9.42,0.75,8.35,0.88,8.12,1.12z"/> | |
| 24 | - | <path fill="#AFAFFF" d="M1.61,86.89l10.14-10.14v-0.5L1.34,86.66C1.42,86.75,1.51,86.82,1.61,86.89z"/> | |
| 25 | - | <path fill="#A7A7FF" d="M1.93,87.07l9.82-9.82v-0.5L1.61,86.89C1.71,86.96,1.82,87.02,1.93,87.07z"/> | |
| 26 | - | <path fill="#9F9FFF" d="M2.3,87.2l9.45-9.45v-0.5l-9.82,9.82C2.05,87.12,2.17,87.17,2.3,87.2z"/> | |
| 27 | - | <path fill="#9797FF" d="M11.75,77.75L2.3,87.2c0.14,0.03,0.29,0.05,0.45,0.05l9-9V77.75z"/> | |
| 28 | - | <path fill="#B7B7FF" d="M1.34,86.66l10.41-10.41h-0.5L1.11,86.39C1.18,86.49,1.25,86.58,1.34,86.66z"/> | |
| 29 | - | <path fill="#BDBDFF" d="M1.11,86.39l10.14-10.14h-0.5l-9.82,9.82C0.98,86.18,1.04,86.29,1.11,86.39z"/> | |
| 30 | - | <path fill="#C2C2FF" d="M0.93,86.07l9.82-9.82h-0.5L0.8,85.7C0.83,85.83,0.88,85.95,0.93,86.07z"/> | |
| 31 | - | <path fill="#C7C7FF" d="M10.25,76.25h-0.5l-9,9c0,0.15,0.02,0.3,0.05,0.45L10.25,76.25z"/> | |
| 32 | - | <path fill="#E3E3FF" d="M61.25,0.75H9.75c-1.1,0-2,0.9-2,2v75.5c0,1.1,0.9,2,2,2h51.5c1.1,0,2-0.9,2-2V2.75C63.25,1.65,62.35,0.75,61.25,0.75z"/> | |
| 33 | - | <path fill="#FFFFFF" stroke="#0000FF" stroke-width="3" d="M60,74.19c0,1.55-1.22,2.81-2.72,2.81H13.72c-1.5,0-2.72-1.26-2.72-2.81V6.81C11,5.26,12.22,4,13.72,4h43.56C58.78,4,60,5.26,60,6.81V74.19z"/> | |
| 34 | - | </g> | |
| 35 | - | <path id="bams" fill="#006C00" d="M11,13.5c0-0.92-1.25-1.71-3.06-2.09V4.44C9.75,4.06,11,3.27,11,2.35C11,1.05,8.54,0,5.5,0S0,1.05,0,2.35c0,0.92,1.25,1.71,3.06,2.09v6.97C1.25,11.79,0,12.58,0,13.5c0,0.92,1.25,1.71,3.06,2.09v6.96C1.25,22.94,0,23.73,0,24.65C0,25.95,2.46,27,5.5,27s5.5-1.05,5.5-2.35c0-0.92-1.25-1.71-3.06-2.09v-6.96C9.75,15.21,11,14.42,11,13.5z M5.5,1.47c1.35,0,2.44,0.39,2.44,0.88c0,0.41-0.78,0.75-1.83,0.85v5.94c0,0.46-0.28,0.84-0.61,0.84S4.89,9.6,4.89,9.14V3.2C3.84,3.1,3.06,2.76,3.06,2.35C3.06,1.86,4.15,1.47,5.5,1.47z M5.5,25.53c-1.35,0-2.44-0.39-2.44-0.88c0-0.41,0.78-0.75,1.83-0.85v-5.94c0-0.46,0.28-0.84,0.61-0.84s0.61,0.38,0.61,0.84v5.94c1.05,0.1,1.83,0.44,1.83,0.85C7.94,25.14,6.85,25.53,5.5,25.53z M5.5,14.38c-1.86,0-3.36-0.39-3.36-0.88c0-0.49,1.5-0.88,3.36-0.88s3.36,0.39,3.36,0.88C8.86,13.99,7.36,14.38,5.5,14.38z"/> | |
| 36 | - | <path id="bams2" fill="#9C0000" d="M11,13.5c0-0.92-1.25-1.71-3.06-2.09V4.44C9.75,4.06,11,3.27,11,2.35C11,1.05,8.54,0,5.5,0S0,1.05,0,2.35c0,0.92,1.25,1.71,3.06,2.09v6.97C1.25,11.79,0,12.58,0,13.5c0,0.92,1.25,1.71,3.06,2.09v6.96C1.25,22.94,0,23.73,0,24.65C0,25.95,2.46,27,5.5,27s5.5-1.05,5.5-2.35c0-0.92-1.25-1.71-3.06-2.09v-6.96C9.75,15.21,11,14.42,11,13.5z M5.5,1.47c1.35,0,2.44,0.39,2.44,0.88c0,0.41-0.78,0.75-1.83,0.85v5.94c0,0.46-0.28,0.84-0.61,0.84S4.89,9.6,4.89,9.14V3.2C3.84,3.1,3.06,2.76,3.06,2.35C3.06,1.86,4.15,1.47,5.5,1.47z M5.5,25.53c-1.35,0-2.44-0.39-2.44-0.88c0-0.41,0.78-0.75,1.83-0.85v-5.94c0-0.46,0.28-0.84,0.61-0.84s0.61,0.38,0.61,0.84v5.94c1.05,0.1,1.83,0.44,1.83,0.85C7.94,25.14,6.85,25.53,5.5,25.53z M5.5,14.38c-1.86,0-3.36-0.39-3.36-0.88c0-0.49,1.5-0.88,3.36-0.88s3.36,0.39,3.36,0.88C8.86,13.99,7.36,14.38,5.5,14.38z"/> | |
| 37 | - | <g id="dots"> | |
| 38 | - | <path fill="#10106C" d="M9,1.42c-4.18,0-7.58,3.4-7.58,7.58s3.4,7.58,7.58,7.58c4.18,0,7.58-3.4,7.58-7.58S13.18,1.42,9,1.42zM9,15.11c-1.36,0-2.49-0.97-2.75-2.25c0.78,0.56,1.72,0.89,2.75,0.89c1.03,0,1.97-0.34,2.75-0.89C11.49,14.14,10.36,15.11,9,15.11z M9,2.89c1.36,0,2.49,0.97,2.75,2.25C10.97,4.59,10.03,4.25,9,4.25c-1.03,0-1.97,0.34-2.75,0.89C6.51,3.86,7.64,2.89,9,2.89z M12.75,9c0,0.57-0.14,1.11-0.37,1.59C12.04,9.92,11.51,9.37,10.85,9c0.66-0.37,1.19-0.92,1.53-1.59C12.61,7.89,12.75,8.43,12.75,9z M5.25,9c0-0.57,0.14-1.11,0.37-1.59C5.96,8.08,6.49,8.63,7.15,9c-0.66,0.37-1.19,0.92-1.53,1.59C5.39,10.11,5.25,9.57,5.25,9z M11.59,11.7c-1.02-0.27-1.82-1.1-2.03-2.14c1.04,0.21,1.87,1.01,2.14,2.03C11.66,11.63,11.63,11.66,11.59,11.7z M6.3,11.59c0.27-1.02,1.1-1.82,2.15-2.03c-0.21,1.04-1.01,1.87-2.03,2.14C6.37,11.66,6.34,11.63,6.3,11.59z M6.41,6.3c1.02,0.27,1.82,1.1,2.03,2.15C7.4,8.23,6.57,7.43,6.3,6.41C6.34,6.37,6.37,6.34,6.41,6.3z M11.7,6.41c-0.27,1.02-1.1,1.82-2.14,2.03c0.21-1.05,1.01-1.87,2.03-2.15C11.63,6.34,11.66,6.37,11.7,6.41z M9,7.15C8.63,6.49,8.08,5.96,7.41,5.62C7.89,5.39,8.43,5.25,9,5.25s1.11,0.14,1.59,0.37C9.92,5.96,9.37,6.49,9,7.15z M9,10.85c0.37,0.66,0.92,1.19,1.59,1.53c-0.49,0.23-1.02,0.37-1.59,0.37s-1.11-0.14-1.59-0.37C8.08,12.04,8.63,11.51,9,10.85z M5.14,11.75C3.86,11.49,2.89,10.36,2.89,9s0.97-2.49,2.25-2.75C4.59,7.03,4.25,7.97,4.25,9C4.25,10.03,4.59,10.97,5.14,11.75z M12.86,6.25c1.28,0.26,2.25,1.39,2.25,2.75s-0.97,2.49-2.25,2.75c0.56-0.78,0.89-1.72,0.89-2.75C13.75,7.97,13.41,7.03,12.86,6.25z M15.01,6.33c-0.59-0.6-1.38-0.99-2.25-1.09c-0.11-0.88-0.49-1.66-1.09-2.25C13.15,3.65,14.35,4.85,15.01,6.33z M6.33,2.99c-0.6,0.59-0.98,1.38-1.09,2.25C4.36,5.35,3.58,5.73,2.99,6.33C3.65,4.85,4.85,3.65,6.33,2.99z M2.99,11.67c0.59,0.6,1.38,0.99,2.25,1.09c0.11,0.88,0.49,1.66,1.09,2.25C4.85,14.35,3.65,13.15,2.99,11.67z M11.67,15.01c0.6-0.59,0.99-1.38,1.09-2.25c0.88-0.11,1.66-0.49,2.25-1.09C14.35,13.15,13.15,14.35,11.67,15.01z"/> | |
| 39 | - | <path fill="#10106C" d="M9,0C4.04,0,0,4.04,0,9c0,4.96,4.04,9,9,9c4.96,0,9-4.04,9-9C18,4.04,13.96,0,9,0z M9,17c-4.41,0-8-3.59-8-8s3.59-8,8-8s8,3.59,8,8S13.41,17,9,17z"/> | |
| 40 | - | </g> | |
| 41 | - | <g id="dots2"> | |
| 42 | - | <path fill="#9C0000" d="M9,1.42c-4.18,0-7.58,3.4-7.58,7.58s3.4,7.58,7.58,7.58c4.18,0,7.58-3.4,7.58-7.58S13.18,1.42,9,1.42zM9,15.11c-1.36,0-2.49-0.97-2.75-2.25c0.78,0.56,1.72,0.89,2.75,0.89c1.03,0,1.97-0.34,2.75-0.89C11.49,14.14,10.36,15.11,9,15.11z M9,2.89c1.36,0,2.49,0.97,2.75,2.25C10.97,4.59,10.03,4.25,9,4.25c-1.03,0-1.97,0.34-2.75,0.89C6.51,3.86,7.64,2.89,9,2.89z M12.75,9c0,0.57-0.14,1.11-0.37,1.59C12.04,9.92,11.51,9.37,10.85,9c0.66-0.37,1.19-0.92,1.53-1.59C12.61,7.89,12.75,8.43,12.75,9z M5.25,9c0-0.57,0.14-1.11,0.37-1.59C5.96,8.08,6.49,8.63,7.15,9c-0.66,0.37-1.19,0.92-1.53,1.59C5.39,10.11,5.25,9.57,5.25,9z M11.59,11.7c-1.02-0.27-1.82-1.1-2.03-2.14c1.04,0.21,1.87,1.01,2.14,2.03C11.66,11.63,11.63,11.66,11.59,11.7z M6.3,11.59c0.27-1.02,1.1-1.82,2.15-2.03c-0.21,1.04-1.01,1.87-2.03,2.14C6.37,11.66,6.34,11.63,6.3,11.59z M6.41,6.3c1.02,0.27,1.82,1.1,2.03,2.15C7.4,8.23,6.57,7.43,6.3,6.41C6.34,6.37,6.37,6.34,6.41,6.3z M11.7,6.41c-0.27,1.02-1.1,1.82-2.14,2.03c0.21-1.05,1.01-1.87,2.03-2.15C11.63,6.34,11.66,6.37,11.7,6.41z M9,7.15C8.63,6.49,8.08,5.96,7.41,5.62C7.89,5.39,8.43,5.25,9,5.25s1.11,0.14,1.59,0.37C9.92,5.96,9.37,6.49,9,7.15z M9,10.85c0.37,0.66,0.92,1.19,1.59,1.53c-0.49,0.23-1.02,0.37-1.59,0.37s-1.11-0.14-1.59-0.37C8.08,12.04,8.63,11.51,9,10.85z M5.14,11.75C3.86,11.49,2.89,10.36,2.89,9s0.97-2.49,2.25-2.75C4.59,7.03,4.25,7.97,4.25,9C4.25,10.03,4.59,10.97,5.14,11.75z M12.86,6.25c1.28,0.26,2.25,1.39,2.25,2.75s-0.97,2.49-2.25,2.75c0.56-0.78,0.89-1.72,0.89-2.75C13.75,7.97,13.41,7.03,12.86,6.25z M15.01,6.33c-0.59-0.6-1.38-0.99-2.25-1.09c-0.11-0.88-0.49-1.66-1.09-2.25C13.15,3.65,14.35,4.85,15.01,6.33z M6.33,2.99c-0.6,0.59-0.98,1.38-1.09,2.25C4.36,5.35,3.58,5.73,2.99,6.33C3.65,4.85,4.85,3.65,6.33,2.99z M2.99,11.67c0.59,0.6,1.38,0.99,2.25,1.09c0.11,0.88,0.49,1.66,1.09,2.25C4.85,14.35,3.65,13.15,2.99,11.67z M11.67,15.01c0.6-0.59,0.99-1.38,1.09-2.25c0.88-0.11,1.66-0.49,2.25-1.09C14.35,13.15,13.15,14.35,11.67,15.01z"/> | |
| 43 | - | <path fill="#9C0000" d="M9,0C4.04,0,0,4.04,0,9c0,4.96,4.04,9,9,9c4.96,0,9-4.04,9-9C18,4.04,13.96,0,9,0z M9,17c-4.41,0-8-3.59-8-8s3.59-8,8-8s8,3.59,8,8S13.41,17,9,17z"/> | |
| 44 | - | </g> | |
| 45 | - | <g id="nums"> | |
| 46 | - | <path fill="#BA0000" d="M18.01,0.14c0,0-4.39,3.33-4.78,3.63c-0.32-0.33-3.2-3.27-3.2-3.27l2.22,8.92l0.6-0.03c0.75-4.76,4.35-6.63,5.33-7.05c0.14,0.2,0.26,0.42,0.26,0.72c0,0.09-0.01,0.18-0.02,0.28c-0.21,1.67-2.39,4.64-6.68,7.21l-0.31,0.18l0.81,1.01l0.2-0.06c0.06-0.02,6.59-2.06,10.36-1.88l1.14,0.05l-0.96-0.63c-0.08-0.05-1.95-1.25-3.93-1.03c0.28-0.63,2.26-2.56,4.15-4.16l0.31-0.26L18.19,0L18.01,0.14z"/> | |
| 47 | - | <path fill="#BA0000" d="M33.78,24.64c-0.17-0.17-4.21-4.28-5.63-5.01c-0.53-0.27-1.71-0.18-5.75,0.16c0,0-0.17,0.01-0.43,0.04c1.19-2.79,2.72-5.86,3.57-6.25l0.55-0.25l-2.13-2.09l-0.21,0.03c-5.11,0.87-13.08,2.09-14.11,1.9l-0.35-0.07l-0.12,0.33c-0.2,0.56,0.32,4.93,0.86,7.37C5.45,21.2,0,21.66,0,21.66l0.3,0.56l0.68,1.27l0.32-0.09c1.72-0.51,2.52-0.57,4.6-0.54c0.19,0.49,2.6,6.77,2.6,6.77l0.79-0.2c0,0-0.69-5.69-0.8-6.52c0.78,0.02,7.99,0.22,9.67,0.26c-1.09,0.92-3.56,3.02-3.56,3.02l0.4,0.71c0,0,5.15-1.76,5.41-1.85c0.25,0.1,4.3,1.65,4.3,1.65s-2.13-2.57-2.81-3.38c3.78,0.2,4.75,0.42,5.01,0.74c0.33,0.39,0.5,0.96,0.5,1.6c0,1.16-0.58,2.57-1.8,3.73c-1.82-0.46-5.17-2.34-5.21-2.36l-0.52,0.62L24.96,34l0.24-0.05c3.82-0.78,8.44-8.48,8.63-8.81L34,24.87L33.78,24.64z M21.02,16.65c-1.22,0.16-2.77,0.37-3.21,0.43c0-0.33,0-1.17,0-2c1.62-0.13,3.11-0.25,3.49-0.29C21.25,15.06,21.15,15.77,21.02,16.65z M16.09,15.22c0.02,0.84,0.04,1.75,0.05,2.06c-1.41,0.16-2.6,0.26-3.55,0.3c0.1-0.62,0.19-1.31,0.3-2.1C13.13,15.46,14.51,15.35,16.09,15.22z M12.03,20.65c0.08-0.35,0.17-0.76,0.26-1.24c1.3-0.24,2.61-0.46,3.89-0.63c0.01,0.48,0.03,1.1,0.04,1.52C15.97,20.31,14.26,20.46,12.03,20.65zM17.8,20.17c0-0.48,0-1.18,0-1.6c0.98-0.1,1.36-0.08,1.96-0.02l0.96,0.06c-0.07,0.46-0.14,0.92-0.2,1.35C19.43,20.04,18.19,20.14,17.8,20.17z"/> | |
| 48 | - | </g> | |
| 49 | - | <g id="flower"> | |
| 50 | - | <rect x="1" y="1" fill="#F8F6D8" stroke="#F8F6D8" width="43" height="67"/> | |
| 51 | - | <path fill="#D3BB54" d="M11.27,58.16l-0.71-8.35c1.76-0.67,2.92-1.76,2.83-2.86c-0.09-1.1-1.42-1.91-3.26-2.16l-0.71-8.36c1.76-0.67,2.92-1.76,2.83-2.86c-0.13-1.56-2.69-2.54-5.71-2.19c-3.02,0.35-5.37,1.89-5.23,3.44c0.09,1.1,1.42,1.91,3.25,2.16l0.71,8.36c-1.76,0.67-2.92,1.76-2.83,2.86c0.09,1.1,1.42,1.91,3.25,2.16l0.71,8.36c-1.76,0.67-2.92,1.76-2.83,2.86c0.13,1.55,2.69,2.53,5.71,2.19c3.02-0.35,5.37-1.89,5.23-3.44C14.43,59.22,13.11,58.41,11.27,58.16z M4.34,34.47c-0.05-0.58,1-1.18,2.34-1.34c1.34-0.15,2.47,0.2,2.52,0.78c0.04,0.49-0.7,0.99-1.74,1.23l0.61,7.13c0.05,0.56-0.19,1.04-0.52,1.08c-0.34,0.04-0.65-0.38-0.7-0.94l-0.61-7.13C5.19,35.28,4.38,34.96,4.34,34.47z M4.57,47.95c-0.05-0.58,1.41-1.23,3.25-1.44c1.85-0.21,3.39,0.09,3.43,0.67c0.05,0.58-1.41,1.23-3.25,1.44C6.15,48.84,4.62,48.54,4.57,47.95z M9.15,62c-1.34,0.15-2.47-0.19-2.52-0.78c-0.04-0.49,0.7-0.99,1.74-1.23l-0.61-7.13c-0.05-0.56,0.19-1.04,0.52-1.08c0.33-0.04,0.65,0.38,0.69,0.94l0.61,7.13c1.06,0,1.87,0.32,1.91,0.81C11.54,61.25,10.49,61.85,9.15,62z"/> | |
| 52 | - | <path fill="none" stroke="#7C5A2D" stroke-width="0.25" d="M11.27,58.16l-0.71-8.35c1.76-0.67,2.92-1.76,2.83-2.86c-0.09-1.1-1.42-1.91-3.26-2.16l-0.71-8.36c1.76-0.67,2.92-1.76,2.83-2.86c-0.13-1.56-2.69-2.54-5.71-2.19c-3.02,0.35-5.37,1.89-5.23,3.44c0.09,1.1,1.42,1.91,3.25,2.16l0.71,8.36c-1.76,0.67-2.92,1.76-2.83,2.86c0.09,1.1,1.42,1.91,3.25,2.16l0.71,8.36c-1.76,0.67-2.92,1.76-2.83,2.86c0.13,1.55,2.69,2.53,5.71,2.19c3.02-0.35,5.37-1.89,5.23-3.44C14.43,59.22,13.11,58.41,11.27,58.16z M4.34,34.47c-0.05-0.58,1-1.18,2.34-1.34c1.34-0.15,2.47,0.2,2.52,0.78c0.04,0.49-0.7,0.99-1.74,1.23l0.61,7.13c0.05,0.56-0.19,1.04-0.52,1.08c-0.34,0.04-0.65-0.38-0.7-0.94l-0.61-7.13C5.19,35.28,4.38,34.96,4.34,34.47z M4.57,47.95c-0.05-0.58,1.41-1.23,3.25-1.44c1.85-0.21,3.39,0.09,3.43,0.67c0.05,0.58-1.41,1.23-3.25,1.44C6.15,48.84,4.62,48.54,4.57,47.95z M9.15,62c-1.34,0.15-2.47-0.19-2.52-0.78c-0.04-0.49,0.7-0.99,1.74-1.23l-0.61-7.13c-0.05-0.56,0.19-1.04,0.52-1.08c0.33-0.04,0.65,0.38,0.69,0.94l0.61,7.13c1.06,0,1.87,0.32,1.91,0.81C11.54,61.25,10.49,61.85,9.15,62z"/> | |
| 53 | - | <path fill="#EADDA7" stroke="#7C5A2D" stroke-width="0.25" d="M5.23,67.99c2.49-8.18-3.33-18.78-3.33-18.78s10.4,10.59,7.48,18.78H5.23z"/> | |
| 54 | - | <path fill="#DBC879" d="M19.02,36.17c-2.92-0.98-5.59-0.56-5.97,0.94c-0.27,1.06,0.7,2.38,2.33,3.41l-2.03,8.06c-1.85-0.14-3.28,0.37-3.54,1.43c-0.27,1.06,0.7,2.38,2.33,3.41l-2.03,8.06c-1.84-0.14-3.27,0.37-3.54,1.43c-0.38,1.5,1.68,3.51,4.6,4.5c2.92,0.98,5.59,0.56,5.96-0.94c0.27-1.06-0.7-2.38-2.32-3.41l2.03-8.06c1.85,0.14,3.28-0.37,3.55-1.43c0.27-1.06-0.71-2.38-2.33-3.41l2.03-8.06c1.84,0.14,3.28-0.37,3.54-1.43C23.99,39.16,21.94,37.15,19.02,36.17z M14.41,57.04l-1.73,6.88c0.98,0.45,1.63,1.1,1.51,1.58c-0.14,0.56-1.31,0.66-2.6,0.23c-1.3-0.44-2.23-1.25-2.09-1.81c0.12-0.47,0.97-0.62,2.01-0.39l1.73-6.88c0.13-0.54,0.51-0.88,0.83-0.77C14.39,55.98,14.54,56.5,14.41,57.04z M18.32,52.88c-0.14,0.56-1.7,0.53-3.48-0.07c-1.78-0.6-3.11-1.54-2.97-2.11c0.14-0.56,1.7-0.53,3.48,0.07C17.13,51.37,18.46,52.32,18.32,52.88z M20.68,39.67c-0.12,0.47-0.97,0.62-2.01,0.39l-1.73,6.88c-0.13,0.54-0.51,0.88-0.83,0.77c-0.32-0.11-0.48-0.63-0.34-1.17l1.73-6.88c-0.98-0.45-1.63-1.1-1.51-1.58c0.14-0.56,1.31-0.67,2.6-0.23C19.89,38.3,20.82,39.11,20.68,39.67z"/> | |
| 55 | - | <path fill="none" stroke="#7C5A2D" stroke-width="0.25" d="M19.02,36.17c-2.92-0.98-5.59-0.56-5.97,0.94c-0.27,1.06,0.7,2.38,2.33,3.41l-2.03,8.06c-1.85-0.14-3.28,0.37-3.54,1.43c-0.27,1.06,0.7,2.38,2.33,3.41l-2.03,8.06c-1.84-0.14-3.27,0.37-3.54,1.43c-0.38,1.5,1.68,3.51,4.6,4.5c2.92,0.98,5.59,0.56,5.96-0.94c0.27-1.06-0.7-2.38-2.32-3.41l2.03-8.06c1.85,0.14,3.28-0.37,3.55-1.43c0.27-1.06-0.71-2.38-2.33-3.41l2.03-8.06c1.84,0.14,3.28-0.37,3.54-1.43C23.99,39.16,21.94,37.15,19.02,36.17z M14.41,57.04l-1.73,6.88c0.98,0.45,1.63,1.1,1.51,1.58c-0.14,0.56-1.31,0.66-2.6,0.23c-1.3-0.44-2.23-1.25-2.09-1.81c0.12-0.47,0.97-0.62,2.01-0.39l1.73-6.88c0.13-0.54,0.51-0.88,0.83-0.77C14.39,55.98,14.54,56.5,14.41,57.04z M18.32,52.88c-0.14,0.56-1.7,0.53-3.48-0.07c-1.78-0.6-3.11-1.54-2.97-2.11c0.14-0.56,1.7-0.53,3.48,0.07C17.13,51.37,18.46,52.32,18.32,52.88z M20.68,39.67c-0.12,0.47-0.97,0.62-2.01,0.39l-1.73,6.88c-0.13,0.54-0.51,0.88-0.83,0.77c-0.32-0.11-0.48-0.63-0.34-1.17l1.73-6.88c-0.98-0.45-1.63-1.1-1.51-1.58c0.14-0.56,1.31-0.67,2.6-0.23C19.89,38.3,20.82,39.11,20.68,39.67z"/> | |
| 56 | - | <path fill="#EADA9B" stroke="#7C5A2D" stroke-width="0.25" d="M19.85,67.7c-1.77-5.79,2.35-13.28,2.35-13.28s-7.35,7.49-5.29,13.28H19.85z"/> | |
| 57 | - | <path fill="#BFAA52" stroke="#7C5A2D" d="M30.33,52.2c-10.89,0-12.13-1.84-15.85-5.97c4.7-3.44,10.9-3.9,16.34,4.82L30.33,52.2z"/> | |
| 58 | - | <path fill="#DBC775" d="M27.52,50.68c-6.89,0-7.68-1.16-10.03-3.78c2.98-2.18,6.9-2.47,10.34,3.05L27.52,50.68z"/> | |
| 59 | - | <path fill="#BFAA52" stroke="#7C5A2D" d="M42.27,47.51c-0.36,2.52-6.85,4.67-9.62,5.14c-2.77,0.47,1.52-5.63,6.18-9.5C41.78,42.64,42.5,45.94,42.27,47.51z"/> | |
| 60 | - | <path fill="#DBC775" d="M37.59,49.31c-0.15,1.08-2.95,2.01-4.14,2.21c-1.19,0.2,0.66-2.42,2.66-4.09C37.38,47.21,37.69,48.63,37.59,49.31z"/> | |
| 61 | - | <path fill="#E5D389" stroke="#7C5A2D" stroke-width="0.75" d="M38.25,67.92c-7.82-3.7-5.44-11.92-5.44-18.72l-2.23-0.29c0,0-2.8,17.48,4.83,18.76L38.25,67.92z"/> | |
| 62 | - | <path fill="#BFAA52" stroke="#7C5A2D" d="M43.33,52.91v7.75c0,0-2.47,2.07-4.21,2.07c-1.73,0-6.93-9.55-6.44-10.59C33.19,51.1,42.84,50.85,43.33,52.91z"/> | |
| 63 | - | <path fill="#DBC775" d="M40.06,53.5c1.02,0.72,0,3.43,0,3.43s-1.33,0.91-2.27,0.91c-0.93,0-3.73-4.22-3.47-4.68S39.04,52.79,40.06,53.5z"/> | |
| 64 | - | <path fill="#BFAA52" stroke="#7C5A2D" d="M31.45,52.39c-12.87-1.29-10.89,4.39-14.11,10.07C27.49,65.3,28.23,57.04,31.45,52.39z"/> | |
| 65 | - | <path fill="#E2CF7F" d="M30.26,53.1c-8.21-0.82-6.95,2.8-9,6.42C27.74,61.34,28.21,56.07,30.26,53.1z"/> | |
| 66 | - | <path fill="#BFAA52" stroke="#7C5A2D" d="M32.93,50.42c0,0,3.69-9.06-0.25-11.11c-4.04-2.11-6.64-1.29-7.13,0c-0.5,1.29,3.91,12.41,5.65,12.66C32.93,52.23,32.93,50.42,32.93,50.42z"/> | |
| 67 | - | <path fill="#DBC775" d="M32.14,50.27c0,0,2.05-5.67-0.14-6.95c-2.24-1.32-3.68-0.81-3.96,0c-0.28,0.81,2.17,7.76,3.13,7.92C32.14,51.4,32.14,50.27,32.14,50.27z"/> | |
| 68 | - | <path fill="#EADDA7" stroke="#7C5A2D" stroke-width="0.25" d="M31.45,51.12c0,0-1.48-2.54-1.23-4.87"/> | |
| 69 | - | <path fill="#BFAA52" stroke="#7C5A2D" d="M30.33,52.6c0,0-4.13,5.57-2.01,5.48c2.41-0.11,2.6,0,5.1,0.46c2.57,0.47,0.2-5.55-1.6-6.32L30.33,52.6z"/> | |
| 70 | - | <path fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" d="M27.27,48.13c0,0,2.02,2.93,3.35,2.93c1.33,0,0.61,2.18,0,1.56C30,51.99,28.15,50.19,27.05,50C26.23,50,26.04,47.66,27.27,48.13z"/> | |
| 71 | - | <path fill="#D3BB54" stroke="#AF9A41" d="M44,12.51c-4.46,0-8.54,2.85-8.54,6.37c0,3.52,4.08,6.37,8.54,6.37V12.51z"/> | |
| 72 | - | <path fill="#D3BB54" stroke="#AF9A41" d="M33.33,7.64c0-3.52-3.62-6.23-8.08-6.23c-4.46,0-8.08,2.71-8.08,6.23s3.62,6.37,8.08,6.37C29.71,14.01,33.33,11.16,33.33,7.64z"/> | |
| 73 | - | <ellipse fill="#D3BB54" stroke="#AF9A41" cx="29.56" cy="17.54" rx="8.08" ry="6.37"/> | |
| 74 | - | <path fill="#DBC879" stroke="#AF9A41" d="M44,16.35V1.42H30.9c-2.93,1.56-3.79,4.49-3.79,7.49c0,4.79,4.93,8.68,11.02,8.68C40.21,17.58,42.34,17.13,44,16.35z"/> | |
| 75 | - | <path fill="#D3BB54" stroke="#AF9A41" d="M31.94,15.47c0,0-0.72,1.82-3.03,1.82"/> | |
| 76 | - | <path fill="#D3BB54" stroke="#AF9A41" d="M27.19,6.62c0,0-1.73,0.57-3.17-1.02"/> | |
| 77 | - | <path fill="#663333" d="M44,0H0v69h45V0H44z M43,2c0,1.91,0,63.09,0,65c-1.86,0-39.14,0-41,0C2,65.09,2,3.91,2,2C3.86,2,41.14,2,43,2z"/> | |
| 78 | - | <path fill="#E2CF7F" d="M31.13,52.9c0,0-1.71,2.31-0.83,2.27c1-0.04,1.08,0,2.11,0.19c1.06,0.2,0.08-2.3-0.66-2.62L31.13,52.9z"/> | |
| 79 | - | </g> | |
| 80 | - | <g id="season"> | |
| 81 | - | <rect x="1" y="1" fill="#E3E4FF" width="43" height="67"/> | |
| 82 | - | <polygon fill="#CAE0FF" stroke="#99B9FF" points="4.69,42.38 9.02,39.76 10.2,44.33 6.26,48.26 8.62,49.89 10.2,48.26 10.98,51.52 14.91,51.19 14.52,47.93 17.27,49.24 18.85,47.28 14.13,44.01 13.34,39.44 19.24,41.4 20.81,45.97 23.56,44.99 22.77,42.7 26.71,44.33 28.28,41.07 23.96,39.44 27.11,38.45 25.92,36.17 20.42,37.8 14.91,35.84 20.03,32.9 25.92,33.88 26.31,31.27 23.17,30.94 26.31,28.98 23.96,26.37 20.81,28 20.81,26.04 18.06,25.71 17.27,30.61 12.55,33.23 11.77,28.66 15.7,24.41 12.95,23.1 11.38,25.06 10.58,21.47 7.05,22.12 7.45,25.39 4.69,24.08 3.12,25.71 7.45,28.66 8.23,33.55 3.12,31.92 1,27.81 1,34.92 1.16,34.86 6.26,36.82 1.55,39.76 1,39.66 1,45.06 1.16,44.99 1,45.91 1,47.31 3.51,47.6"/> | |
| 83 | - | <path fill="#99B9FF" stroke="#678ED3" d="M40.63,26.67c1.1,0,2.17-0.13,3.21-0.35V1.5h-14.3c-2.44,2.67-3.95,6.22-3.95,10.13C25.59,19.94,32.32,26.67,40.63,26.67z"/> | |
| 84 | - | <polygon fill="#C9D3F2" stroke="#9EB3E8" stroke-width="0.5" points="22.61,14.9 22.3,14.59 20.96,16.9 23.27,18.23 23.39,17.81"/> | |
| 85 | - | <polygon fill="#C9D3F2" stroke="#9EB3E8" stroke-width="0.5" points="34.45,28.87 34.03,28.98 35.36,31.29 37.67,29.96 37.35,29.65"/> | |
| 86 | - | <polygon fill="#798CD8" points="43.83,30.1 43.83,29.71 43.59,29.96"/> | |
| 87 | - | <polygon fill="#C9D3F2" stroke="#9EB3E8" stroke-width="0.5" points="23.39,5.45 23.27,5.03 20.96,6.36 22.3,8.67 22.61,8.36"/> | |
| 88 | - | <polygon fill="#99B9FF" stroke="#678ED3" points="23.31,15.6 23.31,7.66 22.61,8.36 22.3,8.67 19.34,11.63 22.3,14.59 22.61,14.9"/> | |
| 89 | - | <polygon fill="#C9D3F2" stroke="#9EB3E8" stroke-width="0.5" points="26.66,23.47 26.23,23.36 26.23,26.02 28.9,26.02 28.78,25.6"/> | |
| 90 | - | <polygon fill="#99B9FF" stroke="#678ED3" points="37.35,29.65 37.67,29.96 40.63,32.92 43.59,29.96 43.83,29.71 43.83,28.95 36.66,28.95"/> | |
| 91 | - | <polygon fill="#99B9FF" stroke="#678ED3" points="35.41,28.61 28.53,24.64 28.78,25.6 28.9,26.02 29.98,30.07 34.03,28.98 34.45,28.87"/> | |
| 92 | - | <polygon fill="#99B9FF" stroke="#678ED3" points="27.61,23.73 23.64,16.85 23.39,17.81 23.27,18.23 22.19,22.27 26.23,23.36 26.66,23.47"/> | |
| 93 | - | <polygon fill="#99B9FF" stroke="#678ED3" points="23.39,5.45 23.64,6.41 26.48,1.5 22.33,1.5 23.27,5.03 "/> | |
| 94 | - | <path fill="#99B9FF" stroke="#678ED3" d="M30.67,46.67c0,0,2.32-3,7-3c5.67,0,5.67,9.33,5.67,9.33H19.67c0,0-0.33-6.67,7.67-6.67c3.67,0,6,4,6,4"/> | |
| 95 | - | <line fill="#99B9FF" stroke="#678ED3" x1="29.5" y1="57.27" x2="24.83" y2="65.36"/> | |
| 96 | - | <line fill="#99B9FF" stroke="#678ED3" x1="34.22" y1="55.68" x2="28.68" y2="65.95"/> | |
| 97 | - | <line fill="#99B9FF" stroke="#678ED3" x1="23.33" y1="61" x2="20.5" y2="65.91"/> | |
| 98 | - | <line fill="#99B9FF" stroke="#678ED3" x1="37.46" y1="54.63" x2="33.25" y2="62.59"/> | |
| 99 | - | <line fill="#99B9FF" stroke="#678ED3" x1="40.46" y1="62.17" x2="38.13" y2="66.21"/> | |
| 100 | - | <line fill="#99B9FF" stroke="#678ED3" x1="27.11" y1="54.52" x2="24.44" y2="59.14"/> | |
| 101 | - | <line fill="#99B9FF" stroke="#678ED3" x1="31.67" y1="54" x2="30.67" y2="55.73"/> | |
| 102 | - | <line fill="#99B9FF" stroke="#678ED3" x1="40.98" y1="55.6" x2="37.81" y2="61.09"/> | |
| 103 | - | <line fill="#99B9FF" stroke="#678ED3" x1="23.84" y1="55.04" x2="22.18" y2="57.92"/> | |
| 104 | - | <line fill="#99B9FF" stroke="#678ED3" x1="21.13" y1="55.63" x2="16.91" y2="63.59"/> | |
| 105 | - | <line fill="#99B9FF" stroke="#678ED3" x1="36" y1="63.6" x2="35" y2="65.33"/> | |
| 106 | - | <path fill="#678ED3" d="M44,0H0v69h45V0H44z M43,2c0,1.91,0,63.09,0,65c-1.86,0-39.14,0-41,0C2,65.09,2,3.91,2,2C3.86,2,41.14,2,43,2z"/> | |
| 107 | - | </g> | |
| 108 | - | <!-- 一索 is a bird, not a stick: a sparrow on a perch, drawn line-art in | |
| 109 | - | the sheet's own green, red and blue. Origin is the glyph's top-left; it | |
| 110 | - | occupies a 44x56 box that centers in the tile face. --> | |
| 111 | - | <g id="yitiao" fill="none" stroke-linecap="round" stroke-linejoin="round"> | |
| 112 | - | <!-- perch, with a knob at each end --> | |
| 113 | - | <g stroke="#006C00" fill="#006C00"> | |
| 114 | - | <path stroke-width="1.4" d="M4.5,35H39.5"/> | |
| 115 | - | <circle cx="3.6" cy="35" r="1.9"/> | |
| 116 | - | <circle cx="40.4" cy="35" r="1.9"/> | |
| 117 | - | </g> | |
| 118 | - | <!-- tail plumes --> | |
| 119 | - | <g stroke="#006C00" stroke-width="0.9"> | |
| 120 | - | <path d="M19.6,30.5C16,37,14.2,44,14,51.5"/> | |
| 121 | - | <path d="M20.8,31C18,38,17,45.5,17.5,53.5"/> | |
| 122 | - | <path d="M22,31.4C20.2,38.5,20.2,46.5,21.5,55"/> | |
| 123 | - | <path d="M23.2,31.4C22.6,38.5,23.6,46.5,25.8,55.5"/> | |
| 124 | - | <path d="M24.3,31C24.8,38,26.4,45.5,29,54"/> | |
| 125 | - | <path d="M25.2,30.3C26.4,36.5,28.6,43.5,31.6,51"/> | |
| 126 | - | </g> | |
| 127 | - | <!-- far wing --> | |
| 128 | - | <path stroke="#294D8E" stroke-width="1.2" d="M25.5,19.5C30,20.5,34.8,24.5,38.8,31C34.5,29.2,30,28.4,27,28.7"/> | |
| 129 | - | <!-- long flight feather, sweeping past the perch --> | |
| 130 | - | <path stroke="#294D8E" stroke-width="1.2" d="M23,23.5C17.5,27.5,10.8,34.5,5,44.5C11.5,38.5,17.8,32.5,22,29"/> | |
| 131 | - | <!-- near wing, folded over the breast --> | |
| 132 | - | <path stroke="#294D8E" stroke-width="1.2" d="M24,17.8C17.4,18.6,10.6,23.4,6.2,31.2C12,27.4,17.6,25.6,21.4,26.2C22.8,23.4,23.6,20.6,24,17.8Z"/> | |
| 133 | - | <g stroke="#294D8E" stroke-width="0.6"> | |
| 134 | - | <path d="M19,19.6C18.2,21.8,17.4,24.2,16.6,26.4"/> | |
| 135 | - | <path d="M16.6,21C15.8,23.2,14.8,25.4,14,27.4"/> | |
| 136 | - | <path d="M14.2,22.6C13.4,24.6,12.4,26.6,11.6,28.6"/> | |
| 137 | - | <path d="M11.8,24.6C11,26.4,10.2,28.2,9.4,29.8"/> | |
| 138 | - | </g> | |
| 139 | - | <!-- head, beak, back and breast --> | |
| 140 | - | <g stroke="#9C0000" stroke-width="1.3"> | |
| 141 | - | <path d="M20,8.6C17.2,7.8,14.4,9.4,13.2,11.8L7.4,13.9L13.2,15.9C12.6,17.6,12.4,19.4,12.7,21"/> | |
| 142 | - | <path d="M20.8,9.2C24.4,12,26.4,16.2,26.8,21.4C27.2,26.4,26.2,30,24.4,32"/> | |
| 143 | - | <path d="M13.4,17.2C14.2,22.4,16.4,27.4,19.8,31.2"/> | |
| 144 | - | </g> | |
| 145 | - | <g stroke="#9C0000" stroke-width="0.7"> | |
| 146 | - | <path d="M15.4,18.4C16,22,17.2,25.4,19,28.6"/> | |
| 147 | - | <path d="M18.4,19.2C18.8,22.8,19.8,26.2,21.4,29.6"/> | |
| 148 | - | <path d="M21.6,20C21.8,23.6,22.6,26.8,24,30.2"/> | |
| 149 | - | </g> | |
| 150 | - | <!-- swept-back crest --> | |
| 151 | - | <g stroke="#006C00" stroke-width="1.2"> | |
| 152 | - | <path d="M13.8,10.6C13.2,5.6,16.2,2.4,20.2,2.8C23.4,3.2,24.4,5.4,22.4,6.8"/> | |
| 153 | - | <path d="M16.6,8.8C17.8,7.2,20,6.2,22.4,6.8"/> | |
| 154 | - | </g> | |
| 155 | - | <path stroke="#9C0000" stroke-width="0.7" d="M8.8,14.6L13,14.2"/> | |
| 156 | - | <circle fill="#9C0000" stroke="none" cx="16.8" cy="11.9" r="1.4"/> | |
| 157 | - | </g> | |
| 158 | - | </defs> | |
| 159 | - | <use x="0" y="0" xlink:href="#bt1"/> | |
| 160 | - | <use x="64" y="0" xlink:href="#bt1"/> | |
| 161 | - | <use x="128" y="0" xlink:href="#bt1"/> | |
| 162 | - | <use x="192" y="0" xlink:href="#bt1"/> | |
| 163 | - | <use x="256" y="0" xlink:href="#bt1"/> | |
| 164 | - | <use x="320" y="0" xlink:href="#bt1"/> | |
| 165 | - | <use x="384" y="0" xlink:href="#bt1"/> | |
| 166 | - | <use x="448" y="0" xlink:href="#bt1"/> | |
| 167 | - | <use x="512" y="0" xlink:href="#bt1"/> | |
| 168 | - | <use x="576" y="0" xlink:href="#bt1"/> | |
| 169 | - | <use x="640" y="0" xlink:href="#bt1"/> | |
| 170 | - | <use x="704" y="0" xlink:href="#bt1"/> | |
| 171 | - | <use x="768" y="0" xlink:href="#bt1"/> | |
| 172 | - | <use x="832" y="0" xlink:href="#bt1"/> | |
| 173 | - | <use x="896" y="0" xlink:href="#bt1"/> | |
| 174 | - | <use x="960" y="0" xlink:href="#bt1"/> | |
| 175 | - | <use x="1024" y="0" xlink:href="#bt1"/> | |
| 176 | - | <use x="1088" y="0" xlink:href="#bt1"/> | |
| 177 | - | <use x="1152" y="0" xlink:href="#bt1"/> | |
| 178 | - | <use x="1216" y="0" xlink:href="#bt1"/> | |
| 179 | - | <use x="1280" y="0" xlink:href="#bt1"/> | |
| 180 | - | <use x="1344" y="0" xlink:href="#bt1"/> | |
| 181 | - | <use x="1408" y="0" xlink:href="#bt1"/> | |
| 182 | - | <use x="1472" y="0" xlink:href="#bt1"/> | |
| 183 | - | <use x="1536" y="0" xlink:href="#bt1"/> | |
| 184 | - | <use x="1600" y="0" xlink:href="#bt1"/> | |
| 185 | - | <use x="1664" y="0" xlink:href="#bt1"/> | |
| 186 | - | <use x="1728" y="0" xlink:href="#bt1"/> | |
| 187 | - | <use x="1792" y="0" xlink:href="#bt1"/> | |
| 188 | - | <use x="1856" y="0" xlink:href="#bt1"/> | |
| 189 | - | <use x="1920" y="0" xlink:href="#bt1"/> | |
| 190 | - | <use x="1984" y="0" xlink:href="#bt1"/> | |
| 191 | - | <use x="2048" y="0" xlink:href="#bt1"/> | |
| 192 | - | <use x="2112" y="0" xlink:href="#bt1"/> | |
| 193 | - | <use x="2176" y="0" xlink:href="#bt1"/> | |
| 194 | - | <use x="2240" y="0" xlink:href="#bt1"/> | |
| 195 | - | <use x="2304" y="0" xlink:href="#bt1"/> | |
| 196 | - | <use x="2368" y="0" xlink:href="#bt1"/> | |
| 197 | - | <use x="2432" y="0" xlink:href="#bt1"/> | |
| 198 | - | <use x="2496" y="0" xlink:href="#bt1"/> | |
| 199 | - | <use x="2560" y="0" xlink:href="#bt1"/> | |
| 200 | - | <use x="2624" y="0" xlink:href="#bt1"/> | |
| 201 | - | <use x="2688" y="0" xlink:href="#bt1"/> | |
| 202 | - | <use x="0" y="88" xlink:href="#bt2"/> | |
| 203 | - | <use x="64" y="88" xlink:href="#bt2"/> | |
| 204 | - | <use x="128" y="88" xlink:href="#bt2"/> | |
| 205 | - | <use x="192" y="88" xlink:href="#bt2"/> | |
| 206 | - | <use x="256" y="88" xlink:href="#bt2"/> | |
| 207 | - | <use x="320" y="88" xlink:href="#bt2"/> | |
| 208 | - | <use x="384" y="88" xlink:href="#bt2"/> | |
| 209 | - | <use x="448" y="88" xlink:href="#bt2"/> | |
| 210 | - | <use x="512" y="88" xlink:href="#bt2"/> | |
| 211 | - | <use x="576" y="88" xlink:href="#bt2"/> | |
| 212 | - | <use x="640" y="88" xlink:href="#bt2"/> | |
| 213 | - | <use x="704" y="88" xlink:href="#bt2"/> | |
| 214 | - | <use x="768" y="88" xlink:href="#bt2"/> | |
| 215 | - | <use x="832" y="88" xlink:href="#bt2"/> | |
| 216 | - | <use x="896" y="88" xlink:href="#bt2"/> | |
| 217 | - | <use x="960" y="88" xlink:href="#bt2"/> | |
| 218 | - | <use x="1024" y="88" xlink:href="#bt2"/> | |
| 219 | - | <use x="1088" y="88" xlink:href="#bt2"/> | |
| 220 | - | <use x="1152" y="88" xlink:href="#bt2"/> | |
| 221 | - | <use x="1216" y="88" xlink:href="#bt2"/> | |
| 222 | - | <use x="1280" y="88" xlink:href="#bt2"/> | |
| 223 | - | <use x="1344" y="88" xlink:href="#bt2"/> | |
| 224 | - | <use x="1408" y="88" xlink:href="#bt2"/> | |
| 225 | - | <use x="1472" y="88" xlink:href="#bt2"/> | |
| 226 | - | <use x="1536" y="88" xlink:href="#bt2"/> | |
| 227 | - | <use x="1600" y="88" xlink:href="#bt2"/> | |
| 228 | - | <use x="1664" y="88" xlink:href="#bt2"/> | |
| 229 | - | <use x="1728" y="88" xlink:href="#bt2"/> | |
| 230 | - | <use x="1792" y="88" xlink:href="#bt2"/> | |
| 231 | - | <use x="1856" y="88" xlink:href="#bt2"/> | |
| 232 | - | <use x="1920" y="88" xlink:href="#bt2"/> | |
| 233 | - | <use x="1984" y="88" xlink:href="#bt2"/> | |
| 234 | - | <use x="2048" y="88" xlink:href="#bt2"/> | |
| 235 | - | <use x="2112" y="88" xlink:href="#bt2"/> | |
| 236 | - | <use x="2176" y="88" xlink:href="#bt2"/> | |
| 237 | - | <use x="2240" y="88" xlink:href="#bt2"/> | |
| 238 | - | <use x="2304" y="88" xlink:href="#bt2"/> | |
| 239 | - | <use x="2368" y="88" xlink:href="#bt2"/> | |
| 240 | - | <use x="2432" y="88" xlink:href="#bt2"/> | |
| 241 | - | <use x="2496" y="88" xlink:href="#bt2"/> | |
| 242 | - | <use x="2560" y="88" xlink:href="#bt2"/> | |
| 243 | - | <use x="2624" y="88" xlink:href="#bt2"/> | |
| 244 | - | <use x="2688" y="88" xlink:href="#bt2"/> | |
| 245 | - | <use x="979" y="41" xlink:href="#nums"/> | |
| 246 | - | <use x="1043" y="41" xlink:href="#nums"/> | |
| 247 | - | <use x="1107" y="41" xlink:href="#nums"/> | |
| 248 | - | <use x="1171" y="41" xlink:href="#nums"/> | |
| 249 | - | <use x="1235" y="41" xlink:href="#nums"/> | |
| 250 | - | <use x="1299" y="41" xlink:href="#nums"/> | |
| 251 | - | <use x="1363" y="41" xlink:href="#nums"/> | |
| 252 | - | <use x="1427" y="41" xlink:href="#nums"/> | |
| 253 | - | <use x="1491" y="41" xlink:href="#nums"/> | |
| 254 | - | <use x="979" y="129" xlink:href="#nums"/> | |
| 255 | - | <use x="1043" y="129" xlink:href="#nums"/> | |
| 256 | - | <use x="1107" y="129" xlink:href="#nums"/> | |
| 257 | - | <use x="1171" y="129" xlink:href="#nums"/> | |
| 258 | - | <use x="1235" y="129" xlink:href="#nums"/> | |
| 259 | - | <use x="1299" y="129" xlink:href="#nums"/> | |
| 260 | - | <use x="1363" y="129" xlink:href="#nums"/> | |
| 261 | - | <use x="1427" y="129" xlink:href="#nums"/> | |
| 262 | - | <use x="1491" y="129" xlink:href="#nums"/> | |
| 263 | - | <use x="2125" y="6" xlink:href="#season"/> | |
| 264 | - | <use x="2189" y="6" xlink:href="#season"/> | |
| 265 | - | <use x="2253" y="6" xlink:href="#season"/> | |
| 266 | - | <use x="2317" y="6" xlink:href="#season"/> | |
| 267 | - | <use x="2125" y="94" xlink:href="#season"/> | |
| 268 | - | <use x="2189" y="94" xlink:href="#season"/> | |
| 269 | - | <use x="2253" y="94" xlink:href="#season"/> | |
| 270 | - | <use x="2317" y="94" xlink:href="#season"/> | |
| 271 | - | <use x="2445" y="6" xlink:href="#flower"/> | |
| 272 | - | <use x="2509" y="6" xlink:href="#flower"/> | |
| 273 | - | <use x="2573" y="6" xlink:href="#flower"/> | |
| 274 | - | <use x="2637" y="6" xlink:href="#flower"/> | |
| 275 | - | <use x="2445" y="94" xlink:href="#flower"/> | |
| 276 | - | <use x="2509" y="94" xlink:href="#flower"/> | |
| 277 | - | <use x="2573" y="94" xlink:href="#flower"/> | |
| 278 | - | <use x="2637" y="94" xlink:href="#flower"/> | |
| 279 | - | <use transform="translate(88.5,16.5) scale(1.22)" xlink:href="#dots"/> | |
| 280 | - | <use transform="translate(88.5,42.5) scale(1.22)" xlink:href="#dots"/> | |
| 281 | - | <use transform="translate(88.5,104.5) scale(1.22)" xlink:href="#dots"/> | |
| 282 | - | <use transform="translate(88.5,130.5) scale(1.22)" xlink:href="#dots"/> | |
| 283 | - | <use transform="translate(144.5,12.5)" xlink:href="#dots"/> | |
| 284 | - | <use transform="translate(144.5,100.5)" xlink:href="#dots"/> | |
| 285 | - | <use transform="translate(154.5,31.5)" xlink:href="#dots2"/> | |
| 286 | - | <use transform="translate(154.5,119.5)" xlink:href="#dots2"/> | |
| 287 | - | <use transform="translate(164.5,50.5)" xlink:href="#dots"/> | |
| 288 | - | <use transform="translate(164.5,138.5)" xlink:href="#dots"/> | |
| 289 | - | <use transform="translate(209,15) scale(.94)" xlink:href="#dots"/> | |
| 290 | - | <use transform="translate(209,49) scale(.94)" xlink:href="#dots"/> | |
| 291 | - | <use transform="translate(209,103) scale(.94)" xlink:href="#dots"/> | |
| 292 | - | <use transform="translate(209,137) scale(.94)" xlink:href="#dots"/> | |
| 293 | - | <use transform="translate(229,15) scale(.94)" xlink:href="#dots"/> | |
| 294 | - | <use transform="translate(229,49) scale(.94)" xlink:href="#dots"/> | |
| 295 | - | <use transform="translate(229,103) scale(.94)" xlink:href="#dots"/> | |
| 296 | - | <use transform="translate(229,137) scale(.94)" xlink:href="#dots"/> | |
| 297 | - | <use transform="translate(273.5,13.5) scale(.88)" xlink:href="#dots"/> | |
| 298 | - | <use transform="translate(273.5,51.5) scale(.88)" xlink:href="#dots"/> | |
| 299 | - | <use transform="translate(273.5,101.5) scale(.88)" xlink:href="#dots"/> | |
| 300 | - | <use transform="translate(273.5,139.5) scale(.88)" xlink:href="#dots"/> | |
| 301 | - | <use transform="translate(283.5,32.5) scale(.88)" xlink:href="#dots2"/> | |
| 302 | - | <use transform="translate(283.5,120.5) scale(.88)" xlink:href="#dots2"/> | |
| 303 | - | <use transform="translate(293.5,13.5) scale(.88)" xlink:href="#dots"/> | |
| 304 | - | <use transform="translate(293.5,51.5) scale(.88)" xlink:href="#dots"/> | |
| 305 | - | <use transform="translate(293.5,101.5) scale(.88)" xlink:href="#dots"/> | |
| 306 | - | <use transform="translate(293.5,139.5) scale(.88)" xlink:href="#dots"/> | |
| 307 | - | <use transform="translate(340,14) scale(.72)" xlink:href="#dots"/> | |
| 308 | - | <use transform="translate(340,37) scale(.72)" xlink:href="#dots2"/> | |
| 309 | - | <use transform="translate(340,54) scale(.72)" xlink:href="#dots2"/> | |
| 310 | - | <use transform="translate(340,102) scale(.72)" xlink:href="#dots"/> | |
| 311 | - | <use transform="translate(340,125) scale(.72)" xlink:href="#dots2"/> | |
| 312 | - | <use transform="translate(340,142) scale(.72)" xlink:href="#dots2"/> | |
| 313 | - | <use transform="translate(358,14) scale(.72)" xlink:href="#dots"/> | |
| 314 | - | <use transform="translate(358,37) scale(.72)" xlink:href="#dots2"/> | |
| 315 | - | <use transform="translate(358,54) scale(.72)" xlink:href="#dots2"/> | |
| 316 | - | <use transform="translate(358,102) scale(.72)" xlink:href="#dots"/> | |
| 317 | - | <use transform="translate(358,125) scale(.72)" xlink:href="#dots2"/> | |
| 318 | - | <use transform="translate(358,142) scale(.72)" xlink:href="#dots2"/> | |
| 319 | - | <use transform="translate(399,10) scale(.72)" xlink:href="#dots"/> | |
| 320 | - | <use transform="translate(404,41) scale(.72)" xlink:href="#dots2"/> | |
| 321 | - | <use transform="translate(404,58) scale(.72)" xlink:href="#dots2"/> | |
| 322 | - | <use transform="translate(413,17) scale(.72)" xlink:href="#dots"/> | |
| 323 | - | <use transform="translate(427,24) scale(.72)" xlink:href="#dots"/> | |
| 324 | - | <use transform="translate(404,129) scale(.72)" xlink:href="#dots2"/> | |
| 325 | - | <use transform="translate(404,146) scale(.72)" xlink:href="#dots2"/> | |
| 326 | - | <use transform="translate(399,98) scale(.72)" xlink:href="#dots"/> | |
| 327 | - | <use transform="translate(422,41) scale(.72)" xlink:href="#dots2"/> | |
| 328 | - | <use transform="translate(422,58) scale(.72)" xlink:href="#dots2"/> | |
| 329 | - | <use transform="translate(413,105) scale(.72)" xlink:href="#dots"/> | |
| 330 | - | <use transform="translate(427,112) scale(.72)" xlink:href="#dots"/> | |
| 331 | - | <use transform="translate(422,129) scale(.72)" xlink:href="#dots2"/> | |
| 332 | - | <use transform="translate(422,146) scale(.72)" xlink:href="#dots2"/> | |
| 333 | - | <use transform="translate(469,10) scale(.72)" xlink:href="#dots"/> | |
| 334 | - | <use transform="translate(469,26) scale(.72)" xlink:href="#dots"/> | |
| 335 | - | <use transform="translate(469,42) scale(.72)" xlink:href="#dots"/> | |
| 336 | - | <use transform="translate(469,58) scale(.72)" xlink:href="#dots"/> | |
| 337 | - | <use transform="translate(469,98) scale(.72)" xlink:href="#dots"/> | |
| 338 | - | <use transform="translate(469,114) scale(.72)" xlink:href="#dots"/> | |
| 339 | - | <use transform="translate(469,130) scale(.72)" xlink:href="#dots"/> | |
| 340 | - | <use transform="translate(469,146) scale(.72)" xlink:href="#dots"/> | |
| 341 | - | <use transform="translate(485,10) scale(.72)" xlink:href="#dots"/> | |
| 342 | - | <use transform="translate(485,26) scale(.72)" xlink:href="#dots"/> | |
| 343 | - | <use transform="translate(485,42) scale(.72)" xlink:href="#dots"/> | |
| 344 | - | <use transform="translate(485,58) scale(.72)" xlink:href="#dots"/> | |
| 345 | - | <use transform="translate(485,98) scale(.72)" xlink:href="#dots"/> | |
| 346 | - | <use transform="translate(485,114) scale(.72)" xlink:href="#dots"/> | |
| 347 | - | <use transform="translate(485,130) scale(.72)" xlink:href="#dots"/> | |
| 348 | - | <use transform="translate(485,146) scale(.72)" xlink:href="#dots"/> | |
| 349 | - | <use transform="translate(526,14) scale(.72)" xlink:href="#dots"/> | |
| 350 | - | <use transform="translate(526,34) scale(.72)" xlink:href="#dots2"/> | |
| 351 | - | <use transform="translate(526,54) scale(.72)" xlink:href="#dots"/> | |
| 352 | - | <use transform="translate(526,102) scale(.72)" xlink:href="#dots"/> | |
| 353 | - | <use transform="translate(526,122) scale(.72)" xlink:href="#dots2"/> | |
| 354 | - | <use transform="translate(526,142) scale(.72)" xlink:href="#dots"/> | |
| 355 | - | <use transform="translate(541,14) scale(.72)" xlink:href="#dots"/> | |
| 356 | - | <use transform="translate(541,34) scale(.72)" xlink:href="#dots2"/> | |
| 357 | - | <use transform="translate(541,54) scale(.72)" xlink:href="#dots"/> | |
| 358 | - | <use transform="translate(541,102) scale(.72)" xlink:href="#dots"/> | |
| 359 | - | <use transform="translate(541,122) scale(.72)" xlink:href="#dots2"/> | |
| 360 | - | <use transform="translate(541,142) scale(.72)" xlink:href="#dots"/> | |
| 361 | - | <use transform="translate(556,14) scale(.72)" xlink:href="#dots"/> | |
| 362 | - | <use transform="translate(556,34) scale(.72)" xlink:href="#dots2"/> | |
| 363 | - | <use transform="translate(556,54) scale(.72)" xlink:href="#dots"/> | |
| 364 | - | <use transform="translate(556,102) scale(.72)" xlink:href="#dots"/> | |
| 365 | - | <use transform="translate(556,122) scale(.72)" xlink:href="#dots2"/> | |
| 366 | - | <use transform="translate(556,142) scale(.72)" xlink:href="#dots"/> | |
| 367 | - | <use transform="translate(1550.5,11.9)" xlink:href="#yitiao"/> | |
| 368 | - | <use transform="translate(1550.5,99.9)" xlink:href="#yitiao"/> | |
| 369 | - | <use transform="translate(1630,9.5)" xlink:href="#bams"/> | |
| 370 | - | <use transform="translate(1630,44.5)" xlink:href="#bams"/> | |
| 371 | - | <use transform="translate(1630,97.5)" xlink:href="#bams"/> | |
| 372 | - | <use transform="translate(1630,132.5)" xlink:href="#bams"/> | |
| 373 | - | <use transform="translate(1682,44.5)" xlink:href="#bams"/> | |
| 374 | - | <use transform="translate(1682,132.5)" xlink:href="#bams"/> | |
| 375 | - | <use transform="translate(1694,9.5)" xlink:href="#bams"/> | |
| 376 | - | <use transform="translate(1694,97.5)" xlink:href="#bams"/> | |
| 377 | - | <use transform="translate(1705,44.5)" xlink:href="#bams"/> | |
| 378 | - | <use transform="translate(1705,132.5)" xlink:href="#bams"/> | |
| 379 | - | <use transform="translate(1746,9.5)" xlink:href="#bams"/> | |
| 380 | - | <use transform="translate(1746,44.5)" xlink:href="#bams"/> | |
| 381 | - | <use transform="translate(1746,97.5)" xlink:href="#bams"/> | |
| 382 | - | <use transform="translate(1746,132.5)" xlink:href="#bams"/> | |
| 383 | - | <use transform="translate(1770,9.5)" xlink:href="#bams"/> | |
| 384 | - | <use transform="translate(1770,44.5)" xlink:href="#bams"/> | |
| 385 | - | <use transform="translate(1770,97.5)" xlink:href="#bams"/> | |
| 386 | - | <use transform="translate(1770,132.5)" xlink:href="#bams"/> | |
| 387 | - | <use transform="translate(1810,10.5) scale(.91,.925)" xlink:href="#bams"/> | |
| 388 | - | <use transform="translate(1810,45.5) scale(.91,.925)" xlink:href="#bams"/> | |
| 389 | - | <use transform="translate(1810,98.5) scale(.91,.925)" xlink:href="#bams"/> | |
| 390 | - | <use transform="translate(1810,133.5) scale(.91,.925)" xlink:href="#bams"/> | |
| 391 | - | <use transform="translate(1822.5,116) scale(.91,.925)" xlink:href="#bams2"/> | |
| 392 | - | <use transform="translate(1822.5,28) scale(.91,.925)" xlink:href="#bams2"/> | |
| 393 | - | <use transform="translate(1835,10.5) scale(.91,.925)" xlink:href="#bams"/> | |
| 394 | - | <use transform="translate(1835,45.5) scale(.91,.925)" xlink:href="#bams"/> | |
| 395 | - | <use transform="translate(1835,98.5) scale(.91,.925)" xlink:href="#bams"/> | |
| 396 | - | <use transform="translate(1835,133.5) scale(.91,.925)" xlink:href="#bams"/> | |
| 397 | - | <use transform="translate(1875,12.5) scale(.82,.85)" xlink:href="#bams"/> | |
| 398 | - | <use transform="translate(1875,45.5) scale(.82,.85)" xlink:href="#bams"/> | |
| 399 | - | <use transform="translate(1875,100.5) scale(.82,.85)" xlink:href="#bams"/> | |
| 400 | - | <use transform="translate(1875,133.5) scale(.82,.85)" xlink:href="#bams"/> | |
| 401 | - | <use transform="translate(1887,12.5) scale(.82,.85)" xlink:href="#bams"/> | |
| 402 | - | <use transform="translate(1887,45.5) scale(.82,.85)" xlink:href="#bams"/> | |
| 403 | - | <use transform="translate(1887,100.5) scale(.82,.85)" xlink:href="#bams"/> | |
| 404 | - | <use transform="translate(1887,133.5) scale(.82,.85)" xlink:href="#bams"/> | |
| 405 | - | <use transform="translate(1899,12.5) scale(.82,.85)" xlink:href="#bams"/> | |
| 406 | - | <use transform="translate(1899,45.5) scale(.82,.85)" xlink:href="#bams"/> | |
| 407 | - | <use transform="translate(1899,100.5) scale(.82,.85)" xlink:href="#bams"/> | |
| 408 | - | <use transform="translate(1899,133.5) scale(.82,.85)" xlink:href="#bams"/> | |
| 409 | - | <use transform="translate(1939.5,31.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 410 | - | <use transform="translate(1951.5,31.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 411 | - | <use transform="translate(1963.5,31.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 412 | - | <use transform="translate(1939.5,50.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 413 | - | <use transform="translate(1951.5,50.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 414 | - | <use transform="translate(1963.5,50.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 415 | - | <use transform="translate(1939.5,119.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 416 | - | <use transform="translate(1951.5,119.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 417 | - | <use transform="translate(1963.5,119.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 418 | - | <use transform="translate(1939.5,138.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 419 | - | <use transform="translate(1951.5,138.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 420 | - | <use transform="translate(1963.5,138.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 421 | - | <use transform="translate(1951.5,12.5) scale(.73,.66)" xlink:href="#bams2"/> | |
| 422 | - | <use transform="translate(1951.5,100.5) scale(.73,.66)" xlink:href="#bams2"/> | |
| 423 | - | <use transform="translate(2003.5,13.5) scale(.73,.77)" xlink:href="#bams"/> | |
| 424 | - | <use transform="translate(2003.5,46.5) scale(.73,.77)" xlink:href="#bams"/> | |
| 425 | - | <use transform="translate(2003.5,101.5) scale(.73,.77)" xlink:href="#bams"/> | |
| 426 | - | <use transform="translate(2003.5,134.5) scale(.73,.77)" xlink:href="#bams"/> | |
| 427 | - | <use transform="translate(2027.5,13.5) scale(.73,.77)" xlink:href="#bams"/> | |
| 428 | - | <use transform="translate(2027.5,46.5) scale(.73,.77)" xlink:href="#bams"/> | |
| 429 | - | <use transform="translate(2027.5,101.5) scale(.73,.77)" xlink:href="#bams"/> | |
| 430 | - | <use transform="translate(2027.5,134.5) scale(.73,.77)" xlink:href="#bams"/> | |
| 431 | - | <use transform="translate(2067.5,12.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 432 | - | <use transform="translate(2067.5,31.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 433 | - | <use transform="translate(2067.5,50.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 434 | - | <use transform="translate(2067.5,100.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 435 | - | <use transform="translate(2067.5,119.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 436 | - | <use transform="translate(2067.5,138.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 437 | - | <use transform="translate(2079.5,12.5) scale(.73,.66)" xlink:href="#bams2"/> | |
| 438 | - | <use transform="translate(2079.5,31.5) scale(.73,.66)" xlink:href="#bams2"/> | |
| 439 | - | <use transform="translate(2079.5,50.5) scale(.73,.66)" xlink:href="#bams2"/> | |
| 440 | - | <use transform="translate(2079.5,100.5) scale(.73,.66)" xlink:href="#bams2"/> | |
| 441 | - | <use transform="translate(2079.5,119.5) scale(.73,.66)" xlink:href="#bams2"/> | |
| 442 | - | <use transform="translate(2079.5,138.5) scale(.73,.66)" xlink:href="#bams2"/> | |
| 443 | - | <use transform="translate(2091.5,12.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 444 | - | <use transform="translate(2091.5,31.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 445 | - | <use transform="translate(2091.5,50.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 446 | - | <use transform="translate(2091.5,100.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 447 | - | <use transform="translate(2091.5,119.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 448 | - | <use transform="translate(2091.5,138.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 449 | - | <path fill="#006C00" d="M2031.75,29.81c-0.41-0.59-1.51-0.57-2.75-0.06l-3.11-4.44c0.9-1,1.3-2.02,0.89-2.61c-0.41-0.58-1.51-0.57-2.75-0.06l-3.11-4.44c0.9-1,1.3-2.02,0.89-2.61c-0.58-0.83-2.51-0.47-4.32,0.8s-2.81,2.96-2.23,3.79c0.41,0.59,1.51,0.57,2.75,0.06l3.11,4.44c-0.9,1-1.3,2.02-0.89,2.61c0.41,0.59,1.51,0.57,2.75,0.06l3.11,4.44c-0.9,1-1.3,2.02-0.89,2.61c0.58,0.83,2.51,0.47,4.32-0.8S2032.33,30.64,2031.75,29.81z M2021.19,22.48l-2.65-3.79c-0.67,0.38-1.29,0.49-1.47,0.22c-0.22-0.31,0.26-1.02,1.06-1.58c0.8-0.56,1.63-0.77,1.85-0.46c0.18,0.26-0.13,0.8-0.71,1.3l2.65,3.79c0.21,0.3,0.21,0.65,0.01,0.79C2021.73,22.9,2021.4,22.77,2021.19,22.48z M2021.5,26.4c-0.22-0.31,0.5-1.19,1.61-1.96c1.11-0.78,2.18-1.15,2.4-0.84c0.22,0.31-0.5,1.19-1.61,1.96C2022.79,26.33,2021.71,26.71,2021.5,26.4z M2028.87,32.67c-0.8,0.56-1.63,0.77-1.85,0.46c-0.18-0.26,0.13-0.8,0.71-1.3l-2.65-3.79c-0.21-0.29-0.21-0.65-0.01-0.79c0.2-0.14,0.53-0.02,0.74,0.28l2.65,3.79c0.67-0.38,1.29-0.49,1.47-0.22C2030.15,31.39,2029.67,32.1,2028.87,32.67z"/> | |
| 450 | - | <path fill="#006C00" d="M2021.52,16.4c-1.81-1.27-3.75-1.62-4.32-0.8c-0.41,0.58-0.02,1.61,0.89,2.61l-3.11,4.44c-1.25-0.51-2.35-0.53-2.75,0.06c-0.41,0.59-0.02,1.61,0.89,2.61l-3.11,4.44c-1.25-0.51-2.34-0.53-2.75,0.06c-0.58,0.83,0.42,2.52,2.23,3.79c1.81,1.27,3.75,1.63,4.32,0.8c0.41-0.59,0.02-1.61-0.89-2.61l3.11-4.44c1.25,0.51,2.34,0.53,2.75-0.06c0.41-0.58,0.02-1.61-0.89-2.61l3.11-4.44c1.25,0.51,2.34,0.53,2.75-0.06C2024.33,19.36,2023.33,17.67,2021.52,16.4z M2013.92,28.03l-2.65,3.79c0.58,0.5,0.9,1.04,0.71,1.31c-0.22,0.31-1.04,0.11-1.85-0.46s-1.28-1.27-1.06-1.58c0.18-0.26,0.8-0.15,1.47,0.22l2.65-3.79c0.21-0.3,0.54-0.42,0.74-0.28C2014.13,27.38,2014.13,27.74,2013.92,28.03z M2017.5,26.4c-0.22,0.31-1.29-0.07-2.4-0.84c-1.11-0.77-1.83-1.65-1.61-1.96c0.22-0.31,1.29,0.07,2.4,0.84C2017,25.21,2017.72,26.09,2017.5,26.4z M2021.93,18.91c-0.18,0.26-0.8,0.15-1.47-0.22l-2.65,3.79c-0.21,0.3-0.54,0.42-0.74,0.28c-0.2-0.14-0.2-0.49,0.01-0.79l2.65-3.79c-0.58-0.5-0.9-1.04-0.71-1.3c0.22-0.31,1.04-0.11,1.85,0.46S2022.15,18.6,2021.93,18.91z"/> | |
| 451 | - | <path fill="#006C00" d="M2031.75,117.81c-0.41-0.59-1.51-0.57-2.75-0.06l-3.11-4.44c0.9-1,1.3-2.02,0.89-2.61c-0.41-0.58-1.51-0.57-2.75-0.06l-3.11-4.44c0.9-1,1.3-2.02,0.89-2.61c-0.58-0.83-2.51-0.47-4.32,0.8c-1.81,1.27-2.81,2.96-2.23,3.79c0.41,0.59,1.51,0.57,2.75,0.06l3.11,4.44c-0.9,1-1.3,2.02-0.89,2.61c0.41,0.58,1.51,0.56,2.75,0.06l3.11,4.44c-0.9,1-1.3,2.02-0.89,2.61c0.58,0.83,2.51,0.47,4.32-0.8C2031.33,120.33,2032.33,118.64,2031.75,117.81z M2021.19,110.48l-2.65-3.79c-0.67,0.38-1.29,0.49-1.47,0.22c-0.22-0.31,0.26-1.02,1.06-1.58s1.63-0.77,1.85-0.46c0.18,0.26-0.13,0.8-0.71,1.3l2.65,3.79c0.21,0.3,0.21,0.65,0.01,0.79C2021.73,110.9,2021.4,110.77,2021.19,110.48z M2021.5,114.4c-0.22-0.31,0.5-1.19,1.61-1.96c1.11-0.78,2.18-1.15,2.4-0.84c0.22,0.31-0.5,1.19-1.61,1.96C2022.79,114.34,2021.71,114.71,2021.5,114.4z M2028.87,120.67c-0.8,0.56-1.63,0.77-1.85,0.46c-0.18-0.26,0.13-0.8,0.71-1.31l-2.65-3.79c-0.21-0.3-0.21-0.65-0.01-0.79c0.2-0.14,0.53-0.02,0.74,0.28l2.65,3.79c0.67-0.38,1.29-0.49,1.47-0.22C2030.15,119.39,2029.67,120.1,2028.87,120.67z"/> | |
| 452 | - | <path fill="#006C00" d="M2021.52,104.4c-1.81-1.27-3.75-1.63-4.32-0.8c-0.41,0.59-0.02,1.61,0.89,2.61l-3.11,4.44c-1.25-0.51-2.35-0.53-2.75,0.06c-0.41,0.59-0.02,1.61,0.89,2.61l-3.11,4.44c-1.25-0.51-2.34-0.53-2.75,0.06c-0.58,0.83,0.42,2.52,2.23,3.79c1.81,1.27,3.75,1.62,4.32,0.8c0.41-0.58,0.02-1.61-0.89-2.61l3.11-4.44c1.25,0.51,2.34,0.53,2.75-0.06c0.41-0.59,0.02-1.61-0.89-2.61l3.11-4.44c1.25,0.51,2.34,0.53,2.75-0.06C2024.33,107.36,2023.33,105.67,2021.52,104.4z M2013.92,116.03l-2.65,3.79c0.58,0.5,0.9,1.04,0.71,1.31c-0.22,0.31-1.04,0.11-1.85-0.46c-0.8-0.56-1.28-1.27-1.06-1.58c0.18-0.26,0.8-0.15,1.47,0.22l2.65-3.79c0.21-0.29,0.54-0.42,0.74-0.28C2014.13,115.38,2014.13,115.74,2013.92,116.03z M2017.5,114.4c-0.22,0.31-1.29-0.07-2.4-0.84c-1.11-0.77-1.83-1.65-1.61-1.96c0.22-0.31,1.29,0.07,2.4,0.84C2017,113.21,2017.72,114.09,2017.5,114.4z M2021.93,106.91c-0.18,0.26-0.8,0.15-1.47-0.22l-2.65,3.79c-0.21,0.3-0.54,0.42-0.74,0.28c-0.2-0.14-0.2-0.49,0.01-0.79l2.65-3.79c-0.58-0.5-0.9-1.04-0.71-1.3c0.22-0.31,1.04-0.11,1.85,0.46C2021.67,105.9,2022.15,106.61,2021.93,106.91z"/> | |
| 453 | - | <path fill="#006C00" d="M2023.75,148.81c-0.41-0.59-1.51-0.57-2.75-0.06l-3.11-4.44c0.9-1,1.3-2.02,0.89-2.61c-0.41-0.59-1.51-0.57-2.75-0.06l-3.11-4.44c0.9-1,1.3-2.02,0.89-2.61c-0.58-0.83-2.51-0.47-4.32,0.8s-2.81,2.96-2.23,3.79c0.41,0.59,1.51,0.57,2.75,0.06l3.11,4.44c-0.9,1-1.3,2.02-0.89,2.61c0.41,0.58,1.51,0.56,2.75,0.06l3.11,4.44c-0.9,1-1.3,2.02-0.89,2.61c0.58,0.83,2.51,0.47,4.32-0.8C2023.33,151.33,2024.33,149.64,2023.75,148.81z M2013.19,141.48l-2.65-3.79c-0.67,0.38-1.29,0.49-1.47,0.22c-0.22-0.31,0.26-1.02,1.06-1.58s1.63-0.77,1.85-0.46c0.18,0.26-0.13,0.8-0.71,1.3l2.65,3.79c0.21,0.29,0.21,0.65,0.01,0.79C2013.73,141.9,2013.4,141.77,2013.19,141.48z M2013.5,145.4c-0.22-0.31,0.5-1.19,1.61-1.96c1.11-0.78,2.18-1.15,2.4-0.84c0.22,0.31-0.5,1.19-1.61,1.96S2013.71,145.71,2013.5,145.4zM2020.87,151.67c-0.8,0.56-1.63,0.77-1.85,0.46c-0.18-0.26,0.13-0.8,0.71-1.3l-2.65-3.79c-0.21-0.3-0.21-0.65-0.01-0.79c0.2-0.14,0.53-0.02,0.74,0.28l2.65,3.79c0.67-0.38,1.29-0.49,1.47-0.22C2022.15,150.4,2021.67,151.1,2020.87,151.67z"/> | |
| 454 | - | <path fill="#006C00" d="M2029.52,135.4c-1.81-1.27-3.75-1.63-4.32-0.8c-0.41,0.59-0.02,1.61,0.89,2.61l-3.11,4.44c-1.25-0.51-2.34-0.53-2.75,0.06c-0.41,0.59-0.02,1.61,0.89,2.61l-3.11,4.44c-1.25-0.51-2.34-0.53-2.75,0.06c-0.58,0.83,0.42,2.52,2.23,3.79c1.81,1.27,3.75,1.62,4.32,0.8c0.41-0.58,0.02-1.61-0.89-2.61l3.11-4.44c1.25,0.51,2.35,0.53,2.75-0.06c0.41-0.58,0.02-1.61-0.89-2.61l3.11-4.44c1.25,0.51,2.34,0.53,2.75-0.06C2032.33,138.36,2031.33,136.67,2029.52,135.4z M2021.92,147.03l-2.65,3.79c0.58,0.5,0.9,1.04,0.71,1.3c-0.22,0.31-1.04,0.11-1.85-0.46c-0.8-0.56-1.28-1.27-1.06-1.58c0.18-0.26,0.8-0.15,1.47,0.22l2.65-3.79c0.21-0.3,0.54-0.42,0.74-0.28C2022.13,146.39,2022.13,146.74,2021.92,147.03z M2025.5,145.4c-0.22,0.31-1.29-0.07-2.4-0.84c-1.11-0.77-1.83-1.65-1.61-1.96c0.22-0.31,1.29,0.07,2.4,0.84C2025,144.21,2025.72,145.09,2025.5,145.4z M2029.93,137.92c-0.18,0.26-0.8,0.15-1.47-0.22l-2.65,3.79c-0.21,0.29-0.54,0.42-0.74,0.28c-0.2-0.14-0.2-0.49,0.01-0.79l2.65-3.79c-0.58-0.5-0.9-1.04-0.71-1.3c0.22-0.31,1.04-0.11,1.85,0.46C2029.67,136.9,2030.15,137.61,2029.93,137.92z"/> | |
| 455 | - | <path fill="#006C00" d="M2023.75,60.81c-0.41-0.59-1.51-0.57-2.75-0.06l-3.11-4.44c0.9-1,1.3-2.02,0.89-2.61c-0.41-0.59-1.51-0.56-2.75-0.06l-3.11-4.44c0.9-1,1.3-2.02,0.89-2.61c-0.58-0.83-2.51-0.47-4.32,0.8c-1.81,1.27-2.81,2.96-2.23,3.79c0.41,0.59,1.51,0.57,2.75,0.06l3.11,4.44c-0.9,1-1.3,2.02-0.89,2.61c0.41,0.58,1.51,0.56,2.75,0.06l3.11,4.44c-0.9,1-1.3,2.02-0.89,2.61c0.58,0.83,2.51,0.47,4.32-0.8C2023.33,63.33,2024.33,61.64,2023.75,60.81z M2013.19,53.48l-2.65-3.79c-0.67,0.38-1.29,0.49-1.47,0.22 c-0.22-0.31,0.26-1.02,1.06-1.58s1.63-0.77,1.85-0.46c0.18,0.26-0.13,0.8-0.71,1.3l2.65,3.79c0.21,0.29,0.21,0.65,0.01,0.79C2013.73,53.9,2013.4,53.77,2013.19,53.48z M2013.5,57.4c-0.22-0.31,0.5-1.19,1.61-1.96c1.11-0.78,2.18-1.15,2.4-0.84c0.22,0.31-0.5,1.19-1.61,1.96C2014.79,57.34,2013.71,57.71,2013.5,57.4z M2020.87,63.67c-0.8,0.56-1.63,0.77-1.85,0.46c-0.18-0.26,0.13-0.8,0.71-1.3l-2.65-3.79c-0.21-0.3-0.21-0.65-0.01-0.79c0.2-0.14,0.53-0.02,0.74,0.28l2.65,3.79c0.67-0.38,1.29-0.49,1.47-0.22C2022.15,62.4,2021.67,63.1,2020.87,63.67z"/> | |
| 456 | - | <path fill="#006C00" d="M2029.52,47.4c-1.81-1.27-3.75-1.63-4.32-0.8c-0.41,0.59-0.02,1.61,0.89,2.61l-3.11,4.44c-1.25-0.51-2.34-0.53-2.75,0.06c-0.41,0.59-0.02,1.61,0.89,2.61l-3.11,4.44c-1.25-0.51-2.34-0.53-2.75,0.06c-0.58,0.83,0.42,2.52,2.23,3.79c1.81,1.27,3.75,1.62,4.32,0.8c0.41-0.58,0.02-1.61-0.89-2.61l3.11-4.44c1.25,0.51,2.35,0.53,2.75-0.06c0.41-0.59,0.02-1.61-0.89-2.61l3.11-4.44c1.25,0.51,2.34,0.53,2.75-0.06C2032.33,50.36,2031.33,48.67,2029.52,47.4z M2021.92,59.03l-2.65,3.79c0.58,0.5,0.9,1.04,0.71,1.3c-0.22,0.31-1.04,0.11-1.85-0.46c-0.8-0.56-1.28-1.27-1.06-1.58c0.18-0.26,0.8-0.15,1.47,0.22l2.65-3.79c0.21-0.3,0.54-0.42,0.74-0.28C2022.13,58.39,2022.13,58.74,2021.92,59.03z M2025.5,57.4c-0.22,0.31-1.29-0.07-2.4-0.84c-1.11-0.77-1.83-1.65-1.61-1.96c0.22-0.31,1.29,0.07,2.4,0.84C2025,56.21,2025.72,57.09,2025.5,57.4z M2029.93,49.92c-0.18,0.26-0.8,0.15-1.47-0.22l-2.65,3.79c-0.21,0.29-0.54,0.42-0.74,0.28c-0.2-0.14-0.2-0.49,0.01-0.79l2.65-3.79c-0.58-0.5-0.9-1.04-0.71-1.3c0.22-0.31,1.04-0.11,1.85,0.46C2029.67,48.9,2030.15,49.61,2029.93,49.92z"/> | |
| 457 | - | <path fill="#20208C" d="M1171.2,12.1l6.8,19.4l0.3-0.1c3.6-1.2,13.3-4.1,15.3-2.6c0.2,0.2,0.3,0.4,0.3,0.7l-0.1,0.8l0.6-0.5c6.8-5.3,9.4-14,9.4-14.1l0-0.2l-0.1-0.1c-4.9-7.3-24.9-1.3-25.7-1l-0.2,0.1l0,0.2c0,0-0.2,1.7-0.2,2.4c-0.4-1.5-1.1-4.1-1.1-4.1l-5.6-1.3L1171.2,12.1z M1178.1,17.8c1.2-0.3,2.6-0.7,4-1c0,0,0,0,0,0s1.9,9.3,2,9.9c-0.5,0.1-3,0.8-3.6,1c-0.1-0.4-1.4-5.6-2.5-9.9C1178,17.9,1178.1,17.8,1178.1,17.8z M1189.8,15.5c0,0.1-0.4,9.6-0.4,10.2c-0.4,0.1-2.1,0.4-2.6,0.5c-0.1-0.6-1.8-9.6-1.8-9.9C1186.7,15.9,1188.3,15.7,1189.8,15.5z M1197.7,16.4c-0.2,2.3-1.1,8.1-2.6,9.9c-0.1-0.1,0-0.2-0.1-0.3c-0.5-0.5-1.6-0.7-3.3-0.6c0.3-4.5,1-7.5,1.9-7.9l0.7-0.3c0,0-3.5-1.7-3.8-1.8C1194.3,15,1197.3,15.1,1197.7,16.4z"/> | |
| 458 | - | <path fill="#20208C" d="M1003.8,18.1c-0.3,0.2-0.4,0.5-0.4,0.8c0,0.3,0.1,0.5,0.1,0.7c-2.6,0.2-19.7,1.8-23.9,1.4l-0.4,0l1.7,6l0.3-0.1c3.3-1.1,24.1-2.4,29-1.4c0.8,0.1,1.2,0,1.4-0.4c0.5-1-1.9-3.5-3.4-4.8C1007.3,19.4,1004.8,17.5,1003.8,18.1z"/> | |
| 459 | - | <path fill="#20208C" d="M1067.9,23.9c-0.3,0.2-0.4,0.5-0.4,0.9c0,0.2,0,0.5,0.1,0.7c-2.8,0.3-19.9,1.9-24.1,1.4l-0.5-0.1l1.7,6.2l0.4-0.1c3.3-1.2,24.3-2.4,29.3-1.4c0.8,0.2,1.3,0,1.5-0.4c0.7-1.3-3-4.6-3.4-4.9C1071.5,25.2,1069,23.2,1067.9,23.9z"/> | |
| 460 | - | <path fill="#20208C" d="M1060.1,12.9c-2.6,0.9-6.2,2.2-12.3,2.2l-0.7,0l2.9,4.2l16.7-2.2l0.5-3.1l-0.1-0.1C1064.7,11.2,1063,11.8,1060.1,12.9z"/> | |
| 461 | - | <path fill="#20208C" d="M1131.7,26.3c-0.3,0.2-0.4,0.5-0.4,0.9c0,0.3,0,0.5,0.1,0.7c-2.7,0.3-19.3,1.9-23.4,1.4l-0.5-0.1l1.7,6.2l0.3-0.1c3.2-1.2,23.6-2.4,28.4-1.4c0.8,0.2,1.3,0,1.5-0.4c0.6-1.3-2.9-4.6-3.3-5C1135.1,27.7,1132.7,25.6,1131.7,26.3z"/> | |
| 462 | - | <polygon fill="#20208C" points="1125.3,19 1114,20.4 1115.6,24.7 1127.6,23.3 1125.6,19 "/> | |
| 463 | - | <path fill="#20208C" d="M1122.9,8.6c-3,1.1-7,2.6-14,2.6l-0.6,0l3.2,4.9l18.9-2.6l0.6-3.6l-0.1-0.1C1128.2,6.6,1126.3,7.3,1122.9,8.6z"/> | |
| 464 | - | <path fill="#20208C" d="M1245.8,10.3l-10.3,10.5l0.4,0.5c0,0,5.6-3.2,7-4c-0.6,1.5-4.3,11.6-4.3,11.6l3.3,3.5c0,0,2.4-15.6,2.4-15.9c0.3-0.2,5.1-2.9,5.1-2.9l-3.4-3.7L1245.8,10.3z"/> | |
| 465 | - | <path fill="#20208C" d="M1258.6,10.2h-1.9c0,0-0.6,1.3-0.9,1.9c0,0-0.1-0.1-0.1-0.1l-5.9,3.5l0.2,0.7c0,0,3-0.2,3.9-0.2c-0.4,0.8-2.2,4.5-2.3,4.8c-0.4,0-6.3,0.5-6.3,0.5l-0.1,0.7l4,1.6c0,0,0.7-0.3,1.3-0.5c-0.6,1.2-2.5,5.2-2.7,5.5c-1.3,0.1-2.3,0.1-2.9,0l-0.4,0l0.5,3.7l0.3-0.1c1.4-0.2,12.2-1.6,14.1-1.7c1.8-0.1,7.3,2.1,7.4,2.1l0.7,0.3l-0.2-0.7c-0.1-0.4-1.3-3.9-2.2-4.8l-0.1-0.1l-0.1,0c-0.8-0.2-3.6,0-7.5,0.4c0.9-1,4.3-4.6,4.3-4.6l-3.2-3.4c0,0-2.9,7.8-3,8.2c-0.3,0-2.7,0.3-3.5,0.3c0.4-0.9,2.5-6.5,2.6-6.7c0.3-0.1,3.2-1.2,3.2-1.2l-0.2-0.7c0,0-1.7,0.3-2.5,0.4c0.3-0.9,1.6-4.1,1.7-4.4c0.2,0,1.2-0.1,1.2-0.1s-0.6-0.9-0.7-1.1c0.1-0.3,1.7-4.4,1.7-4.4H1258.6z"/> | |
| 466 | - | <path fill="#20208C" d="M1313.2,9.8c0,0.3,0,0.6,0,1c0,1.6-0.2,3.7-0.7,6.2c-5.9,0.7-11.1,1.5-11.2,1.5l-0.3,0V21c0,0,9.9-0.4,10.7-0.5c-0.2,0.7-1,4-1,4l0.6,0.3c0,0,2.9-4.1,3.1-4.4c11.7-0.3,14.4,0.2,15,0.5l0.6,0.4l-0.6-3.5c-0.3-1-2-2.1-12.2-1.3c0.6-0.9,3.2-4.6,3.2-4.6l-7.2-2.7L1313.2,9.8z"/> | |
| 467 | - | <path fill="#20208C" d="M1307.6,24.8c0,0,0.8,4.7-1.6,8.5l0.5,0.5c4.4-2.9,5.9-6.6,5.9-6.7l0.1-0.3l-5-2.6L1307.6,24.8z"/> | |
| 468 | - | <path fill="#20208C" d="M1317.1,23.7c0.2,0.3,5.9,7.7,7.9,9.6l0.1,0.1l0.2,0c0.4,0,0.8-0.3,1.1-0.7c0.8-1.1,1.3-3.2,1-4.3c-0.4-1.3-7.6-4.3-9.8-5.2l-1.2-0.5L1317.1,23.7z"/> | |
| 469 | - | <path fill="#20208C" d="M1387.2,11.7L1387.2,11.7c-0.2,0.4-1.5,1.9-8.3,7.6c-0.1-0.7-0.2-1.6-0.2-1.6c-0.2-1.9-0.4-4.1-0.8-6.5l-0.1-0.3l-3.2,0.3l0,0.3c0,0.6,0,1.1,0,1.7c0,3.3,0.3,6.2,0.9,8.9c-0.3,0.2-10,8.2-10,8.2l0.3,0.3l1.9,1.8c0,0,7.9-6.9,8.6-7.6c0.7,2.2,1.6,4,2.7,5c2,2,4.2,1.7,7,1.3c2-0.3,4.3-0.6,7-0.2l0.5,0.1l-0.6-2.8c0-0.6-0.3-1.1-0.7-1.5c-1.3-0.9-3.8-0.4-6.5,0.1c-1.3,0.3-2.7,0.5-3.9,0.6c-1.3-1.2-2.1-2.8-2.6-5.2c7.5-6.2,10.3-7.8,11.3-8.1l0.2-0.1l0-0.3c0-0.2,0-0.3,0-0.4c0-1.8-0.9-2.8-1.7-3C1388.5,10.4,1387.7,10.6,1387.2,11.7z"/> | |
| 470 | - | <path fill="#20208C" d="M1432.4,19c0,3.7-4.5,9.2-4.7,9.4l-0.7,0.9l1.1-0.3c5.2-1.4,8.2-5,8.3-5.1l0.2-0.2l-4.2-5.7V19z"/> | |
| 471 | - | <path fill="#20208C" d="M1437.9,11.3l-2,0.3l0.3,0.5c0.1,0.2,9.3,15.7,10.6,19.3l0.1,0.3l0.3-0.1c2.2-0.4,4-0.2,5.8,0c2.2,0.2,4.3,0.4,6.7-0.4l0.4-0.1l-0.2-0.4c-1.2-2-2.5-2.4-4.2-2.9c-3.2-0.9-8-2.3-17.3-16.3l-0.1-0.2L1437.9,11.3z"/> | |
| 472 | - | <path fill="#20208C" d="M1520.6,23.6c0,0-2.9,3.2-3.1,3.4c-3.2,0.7-8.4,1.2-8.8-0.4c-0.5-1.9,3.4-10.4,4.9-13.6l0.2-0.3l-5.6-2.8l-0.1,0.4c-0.1,0.2-0.4,1.3-0.9,3c-2,0-4.2,0-4.5,0c0-0.4-0.1-1.3-0.1-1.3l0,0c-1.4-2.3-7.4-0.7-8.1-0.6l-0.4,0.1l0.2,0.4c0.2,0.4,0.4,0.9,0.5,1.4c-0.8,0-3.8,0-3.8,0l1.9,3.6c0,0,1.8-0.3,2.4-0.4c0,0.2,0,0.5,0,0.7c0,6.2-2.7,14.1-2.7,14.2l0.6,0.3c0.3-0.4,7.9-9.5,9.3-16.4c0.2,0,2.4-0.4,4.4-0.7c-1.2,4.7-2.6,12.1-0.3,14.8c3.4,4,16.6,3.7,17.1,3.5l0.3-0.1L1520.6,23.6z"/> | |
| 473 | - | <path fill="#20208C" d="M1171.2,100.1l6.8,19.4l0.3-0.1c3.6-1.2,13.3-4.1,15.3-2.6c0.2,0.2,0.3,0.4,0.3,0.7l-0.1,0.8l0.6-0.5c6.8-5.3,9.4-14,9.4-14.1l0-0.2l-0.1-0.1c-4.9-7.3-24.9-1.3-25.7-1l-0.2,0.1l0,0.2c0,0-0.2,1.7-0.2,2.4c-0.4-1.5-1.1-4.1-1.1-4.1l-5.6-1.3L1171.2,100.1z M1178.1,105.8c1.2-0.3,2.6-0.7,4-1c0,0,0,0,0,0s1.9,9.3,2,9.9c-0.5,0.1-3,0.8-3.6,1c-0.1-0.4-1.4-5.6-2.5-9.9C1178,105.9,1178.1,105.8,1178.1,105.8z M1189.8,103.5c0,0.1-0.4,9.6-0.4,10.2c-0.4,0.1-2.1,0.4-2.6,0.5c-0.1-0.6-1.8-9.6-1.8-9.9C1186.7,103.9,1188.3,103.7,1189.8,103.5z M1197.7,104.4c-0.2,2.3-1.1,8.1-2.6,9.9c-0.1-0.1,0-0.2-0.1-0.3c-0.5-0.5-1.6-0.7-3.3-0.6c0.3-4.5,1-7.5,1.9-7.9l0.7-0.3c0,0-3.5-1.7-3.8-1.8C1194.3,103,1197.3,103.1,1197.7,104.4z"/> | |
| 474 | - | <path fill="#20208C" d="M1003.8,106.1c-0.3,0.2-0.4,0.5-0.4,0.8c0,0.3,0.1,0.5,0.1,0.7c-2.6,0.2-19.7,1.8-23.9,1.4l-0.4,0l1.7,6l0.3-0.1c3.3-1.1,24.1-2.4,29-1.4c0.8,0.1,1.2,0,1.4-0.4c0.5-1-1.9-3.5-3.4-4.8C1007.3,107.4,1004.8,105.5,1003.8,106.1z"/> | |
| 475 | - | <path fill="#20208C" d="M1067.9,111.9c-0.3,0.2-0.4,0.5-0.4,0.9c0,0.2,0,0.5,0.1,0.7c-2.8,0.3-19.9,1.9-24.1,1.4l-0.5-0.1l1.7,6.2l0.4-0.1c3.3-1.2,24.3-2.4,29.3-1.4c0.8,0.2,1.3,0,1.5-0.4c0.7-1.3-3-4.6-3.4-4.9C1071.5,113.2,1069,111.2,1067.9,111.9z"/> | |
| 476 | - | <path fill="#20208C" d="M1060.1,100.9c-2.6,0.9-6.2,2.2-12.3,2.2l-0.7,0l2.9,4.2l16.7-2.2l0.5-3.1l-0.1-0.1C1064.7,99.2,1063,99.8,1060.1,100.9z"/> | |
| 477 | - | <path fill="#20208C" d="M1131.7,114.3c-0.3,0.2-0.4,0.5-0.4,0.9c0,0.3,0,0.5,0.1,0.7c-2.7,0.3-19.3,1.9-23.4,1.4l-0.5-0.1l1.7,6.2l0.3-0.1c3.2-1.2,23.6-2.4,28.4-1.4c0.8,0.2,1.3,0,1.5-0.4c0.6-1.3-2.9-4.6-3.3-5C1135.1,115.7,1132.7,113.6,1131.7,114.3z"/> | |
| 478 | - | <polygon fill="#20208C" points="1125.3,107 1114,108.4 1115.6,112.7 1127.6,111.3 1125.6,107 "/> | |
| 479 | - | <path fill="#20208C" d="M1122.9,96.6c-3,1.1-7,2.6-14,2.6l-0.6,0l3.2,4.9l18.9-2.6l0.6-3.6l-0.1-0.1C1128.2,94.6,1126.3,95.3,1122.9,96.6z"/> | |
| 480 | - | <path fill="#20208C" d="M1245.8,98.3l-10.3,10.5l0.4,0.5c0,0,5.6-3.2,7-4c-0.6,1.5-4.3,11.6-4.3,11.6l3.3,3.5c0,0,2.4-15.6,2.4-15.9c0.3-0.2,5.1-2.9,5.1-2.9l-3.4-3.7L1245.8,98.3z"/> | |
| 481 | - | <path fill="#20208C" d="M1258.6,98.2h-1.9c0,0-0.6,1.3-0.9,1.9c0,0-0.1-0.1-0.1-0.1l-5.9,3.5l0.2,0.7c0,0,3-0.2,3.9-0.2c-0.4,0.8-2.2,4.5-2.3,4.8c-0.4,0-6.3,0.5-6.3,0.5l-0.1,0.7l4,1.6c0,0,0.7-0.3,1.3-0.5c-0.6,1.2-2.5,5.2-2.7,5.5c-1.3,0.1-2.3,0.1-2.9,0l-0.4,0l0.5,3.7l0.3-0.1c1.4-0.2,12.2-1.6,14.1-1.7c1.8-0.1,7.3,2.1,7.4,2.1l0.7,0.3l-0.2-0.7c-0.1-0.4-1.3-3.9-2.2-4.8l-0.1-0.1l-0.1,0c-0.8-0.2-3.6,0-7.5,0.4c0.9-1,4.3-4.6,4.3-4.6l-3.2-3.4c0,0-2.9,7.8-3,8.2c-0.3,0-2.7,0.3-3.5,0.3c0.4-0.9,2.5-6.5,2.6-6.7c0.3-0.1,3.2-1.2,3.2-1.2l-0.2-0.7c0,0-1.7,0.3-2.5,0.4c0.3-0.9,1.6-4.1,1.7-4.4c0.2,0,1.2-0.1,1.2-0.1s-0.6-0.9-0.7-1.1c0.1-0.3,1.7-4.4,1.7-4.4H1258.6z"/> | |
| 482 | - | <path fill="#20208C" d="M1313.2,97.8c0,0.3,0,0.6,0,1c0,1.6-0.2,3.7-0.7,6.2c-5.9,0.7-11.1,1.5-11.2,1.5l-0.3,0v2.5c0,0,9.9-0.4,10.7-0.5c-0.2,0.7-1,4-1,4l0.6,0.3c0,0,2.9-4.1,3.1-4.4c11.7-0.3,14.4,0.2,15,0.5l0.6,0.4l-0.6-3.5c-0.3-1-2-2.1-12.2-1.3c0.6-0.9,3.2-4.6,3.2-4.6l-7.2-2.7L1313.2,97.8z"/> | |
| 483 | - | <path fill="#20208C" d="M1307.6,112.8c0,0,0.8,4.7-1.6,8.5l0.5,0.5c4.4-2.9,5.9-6.6,5.9-6.7l0.1-0.3l-5-2.6L1307.6,112.8z"/> | |
| 484 | - | <path fill="#20208C" d="M1317.1,111.7c0.2,0.3,5.9,7.7,7.9,9.6l0.1,0.1l0.2,0c0.4,0,0.8-0.3,1.1-0.7c0.8-1.1,1.3-3.2,1-4.3c-0.4-1.3-7.6-4.3-9.8-5.2l-1.2-0.5L1317.1,111.7z"/> | |
| 485 | - | <path fill="#20208C" d="M1387.2,99.7L1387.2,99.7c-0.2,0.4-1.5,1.9-8.3,7.6c-0.1-0.7-0.2-1.6-0.2-1.6c-0.2-1.9-0.4-4.1-0.8-6.5l-0.1-0.3l-3.2,0.3l0,0.3c0,0.6,0,1.1,0,1.7c0,3.3,0.3,6.2,0.9,8.9c-0.3,0.2-10,8.2-10,8.2l0.3,0.3l1.9,1.8c0,0,7.9-6.9,8.6-7.6c0.7,2.2,1.6,4,2.7,5c2,2,4.2,1.7,7,1.3c2-0.3,4.3-0.6,7-0.2l0.5,0.1l-0.6-2.8c0-0.6-0.3-1.1-0.7-1.5c-1.3-0.9-3.8-0.4-6.5,0.1c-1.3,0.3-2.7,0.5-3.9,0.6c-1.3-1.2-2.1-2.8-2.6-5.2c7.5-6.2,10.3-7.8,11.3-8.1l0.2-0.1l0-0.3c0-0.2,0-0.3,0-0.4c0-1.8-0.9-2.8-1.7-3C1388.5,98.4,1387.7,98.6,1387.2,99.7z"/> | |
| 486 | - | <path fill="#20208C" d="M1432.4,107c0,3.7-4.5,9.2-4.7,9.4l-0.7,0.9l1.1-0.3c5.2-1.4,8.2-5,8.3-5.1l0.2-0.2l-4.2-5.7V107z"/> | |
| 487 | - | <path fill="#20208C" d="M1437.9,99.3l-2,0.3l0.3,0.5c0.1,0.2,9.3,15.7,10.6,19.3l0.1,0.3l0.3-0.1c2.2-0.4,4-0.2,5.8,0c2.2,0.2,4.3,0.4,6.7-0.4l0.4-0.1l-0.2-0.4c-1.2-2-2.5-2.4-4.2-2.9c-3.2-0.9-8-2.3-17.3-16.3l-0.1-0.2L1437.9,99.3z"/> | |
| 488 | - | <path fill="#20208C" d="M1520.6,111.6c0,0-2.9,3.2-3.1,3.4c-3.2,0.7-8.4,1.2-8.8-0.4c-0.5-1.9,3.4-10.4,4.9-13.6l0.2-0.3l-5.6-2.8l-0.1,0.4c-0.1,0.2-0.4,1.3-0.9,3c-2,0-4.2,0-4.5,0c0-0.4-0.1-1.3-0.1-1.3l0,0c-1.4-2.3-7.4-0.7-8.1-0.6l-0.4,0.1l0.2,0.4c0.2,0.4,0.4,0.9,0.5,1.4c-0.8,0-3.8,0-3.8,0l1.9,3.6c0,0,1.8-0.3,2.4-0.4c0,0.2,0,0.5,0,0.7c0,6.2-2.7,14.1-2.7,14.2l0.6,0.3c0.3-0.4,7.9-9.5,9.3-16.4c0.2,0,2.4-0.4,4.4-0.7c-1.2,4.7-2.6,12.1-0.3,14.8c3.4,4,16.6,3.7,17.1,3.5l0.3-0.1L1520.6,111.6z"/> | |
| 489 | - | <path fill="#101040" d="M632.3,142.7c-2-0.9-11.8,3.3-11.8,3.3l0.5-20.7c0,0,5.3-4.3,9.6-1.7l-1.2-5c-1.4-0.2-8.4,5.5-8.4,5.5s-0.6-18.8,4.8-19.9c0,0-8.9-2.6-9.7-2.2c-0.4,0.2-0.4,13.8-0.2,27.1c-3.2,3.1-5.9,6.2-7.1,7.3l0.4-28.5c0,0-4.7-2.6-5.8-1.4l0.2,14.8c-0.3-0.5-0.7-0.9-1-1.3c-1-1.3-6.1-4.2-6.1-4.2s1.7,5.6,2.5,6.6c0.7,0.9,3,2.9,4.6,2.2l0.2,16.5c-3.4,3-7.9,6.8-8.5,5.4c-1.5-3.2-4.7-3-5.2-1.5c-0.5,1.6,4.2,10.2,5.8,10.1c0.8-0.1,4.4-4.9,8-10l0.1,5.3c1.6,2.1,3.8,0.5,4.6-0.7l0.3-11c1.1-1.6,4-4.5,7.1-7.9c0.2,12.1,0.3,22.8,0.3,22.8c4.9-5.2,16.7-4,16.7-4L632.3,142.7z"/> | |
| 490 | - | <path fill="#101040" d="M688.4,14.3c-8.5,5.7-21.5,6.9-28.8,5.9l-0.9,2c11.7,3.8,26.4,1,31.2-3.2L688.4,14.3z"/> | |
| 491 | - | <path fill="#101040" d="M692.5,39.2c-3.6-0.9-7.3-1.4-10.8-1.7c0.8-2.9,1.2-6,1-9.2l-7.8,2c0.6,1.7,0.6,4.3,0.2,7c-1.6,0-3.1,0.1-4.5,0.3c0.2-2.7,0.4-3.3-0.5-6l-4.7,1.3c1,1.8,1.5,2.3,1.8,5.1c-3.4,1-5.7,1.8-6.9,3.2c-5.1,6.1,0,23.2,0,23.2s-0.2-15.4,2-17c2.2-1.6,4.7-1.8,4.7-1.8c0.1,1.5-1.6,6.7-1.6,6.7c2.2-2.7,3.2-3.6,4.3-7.2c1.6-0.5,2.5-0.8,4.1-1c-1.6,6.1-4.5,10.9-4.5,10.9l7.4,2c-4.1-0.1-13.3,5.1-13.3,5.1s16.2-4,20.8-1.1l1.2-2.9c-3.4-2.7-10.5-4.9-10.5-4.9s2.2-4.1,4.6-9.4c2.3,0.1,5.6,0.3,7.7,0.9c9.2,4.1-1.7,22.4-1.7,22.4S705.7,45.9,692.5,39.2z"/> | |
| 492 | - | <path fill="#101040" d="M735.3,14.2c0.4,0.5,0.9,2.3,0.9,8.6c0,0.9,0,1.8,0,2.8c-3.4,0.5-6.3,1-6.8,1.2l-0.6,0.2l2.8,2.7l0.2-0.1c1.1-0.3,2.6-0.5,4.2-0.7c0,0.7-0.4,7.3-0.4,7.9c-4.9,0.4-9.2,1.1-12.7,1.9c-3,0.8-4.8,2.6-5.2,5.4c-0.1,0.4-0.1,0.9-0.1,1.4c0,7.1,6.9,16.9,7,16.9l0.8,0.5l-0.3-0.9c0-0.1-2.8-9.4-2.8-15.6c0-1.8,0.2-3.3,0.8-4.3c0.4-0.6,0.8-0.9,1.4-1c1.3-0.2,2.7-0.4,4-0.6c0,0,3.1,4,3.7,4.7c-0.5,0.1-1.4,0.3-1.4,0.3l0,0.3l0.2,2.2l0.4-0.1c0.6-0.2,1.8-0.5,3.2-0.7c0,0.6,0.1,2.4,0.2,2.9c-1.7,0.5-3.2,1.1-3.8,1.4l-0.4,0.2l1.5,1.6l0.2-0.1c0.7-0.3,1.7-0.6,2.7-0.9c0,0.7,0.3,5.6,0.3,5.6l0.7,0c0,0,1.1-5.5,1.2-6c1.8-0.2,3.5-0.1,4.8,0.2l0.4,0.1v-3l-0.2-0.1c-0.8-0.4-2.3-0.4-4.5,0.1c0.1-0.7,0.4-2.1,0.5-2.5c1.6-0.1,2.9-0.2,3.8-0.1l0.5,0.1l-0.7-2.6c0,0-0.9,0-1.4,0.1c0.8-0.9,3.5-4.4,3.7-4.6c0.3,0,3.9,0.1,3.9,0.1c1.6,0.1,3,0.7,4.1,2c1.3,1.5,2,3.9,2,6.3c0,0.5,0,1.1-0.1,1.6l-0.1,0.7c-0.7,5.1-1.3,8.8-3.1,9.6c-1.2,0.5-3.1-0.3-5.9-2.4l-0.5,0.5l5.5,9.6l0.3-0.1c6.7-2.4,7.7-3.8,10.4-15.4c1.2-5.1,0.8-9.1-1.3-11.8c-2.5-3.3-6.3-3.7-6.5-3.7c-0.1,0-6.7-0.3-14.6,0.2c0.2-0.8,2-7.5,2.1-7.9c2.5,0,4.7,0.1,5.7,0.5l0.4,0.1l0.4-4.7l-0.4,0c-1.1,0-2.8,0.1-5.1,0.3c1.3-4.4,1.9-5.8,2.1-6.2l0.2-0.3l-8.1-4.7L735.3,14.2z M739.7,39.5c-0.1,0.7-0.7,4.3-0.7,4.7c0,0-0.1,0-0.1,0c0-0.1,0.1-0.4,0.1-0.4l-4.4,0.4c0,0,0,0.5,0,0.6c-0.4,0.1-1,0.2-1.4,0.2c0-0.7,0-5,0-5.1C735.4,39.8,737.5,39.6,739.7,39.5z"/> | |
| 493 | - | <path fill="#101040" d="M804.5,41.3l0.1-6.4l4.4-0.3l-0.3-2.4l-4.1,1.2l0-5.7c2.4,0.3,5,0.8,7.1,0.9c4.3,0.3-1.2,14.1-1.2,14.1s16.3-14.4,2.6-18.1c-1.4,0-4.9,0.2-8.4,0.4l0-3.9l7.5-1.9l-7.5-0.1l0-4l-4.6-3.4l0.5,8.7l-4,2.7l4.1-1l0.2,3.2c-1.8,0.2-3.4,0.3-4.3,0.5c-11.5,2.2,1,17.2,1,17.2s-4-14,0.5-15.3c0.9-0.3,1.8-0.3,2.9-0.3l0.4,7l-3,0.9l3-0.2l0.2,4.1c-9.2,12.1-19.7,16.6-19.7,16.6s10.2-1.1,20-11.9l1,18.7c-3.7-1.5-7-3.6-7-3.6s2.4,7.1,7.5,10.1l4.3-5.1c-1-0.2-2.1-0.5-3.2-0.9l0.2-18.9c2.7,3.2,9,10.5,11.7,11l9-3C821.9,51.6,807.7,43.3,804.5,41.3z"/> | |
| 494 | - | <path fill="#101040" d="M688.4,102.3c-8.5,5.7-21.5,6.9-28.8,5.9l-0.9,2c11.7,3.8,26.4,1,31.2-3.2L688.4,102.3z"/> | |
| 495 | - | <path fill="#101040" d="M692.5,127.2c-3.6-0.9-7.3-1.4-10.8-1.7c0.8-2.9,1.2-6,1-9.2l-7.8,2c0.6,1.7,0.6,4.3,0.2,7c-1.6,0-3.1,0.1-4.5,0.3c0.2-2.7,0.4-3.3-0.5-6l-4.7,1.3c1,1.8,1.5,2.3,1.8,5.1c-3.4,1-5.7,1.8-6.9,3.2c-5.1,6.1,0,23.2,0,23.2s-0.2-15.4,2-17c2.2-1.6,4.7-1.8,4.7-1.8c0.1,1.5-1.6,6.7-1.6,6.7c2.2-2.7,3.2-3.6,4.3-7.2c1.6-0.5,2.5-0.8,4.1-1c-1.6,6.1-4.5,10.9-4.5,10.9l7.4,2c-4.1-0.1-13.3,5.1-13.3,5.1s16.2-4,20.8-1.1l1.2-2.9c-3.4-2.7-10.5-4.9-10.5-4.9s2.2-4.1,4.6-9.4c2.3,0.1,5.6,0.3,7.7,0.9c9.2,4.1-1.7,22.4-1.7,22.4S705.7,133.9,692.5,127.2z"/> | |
| 496 | - | <path fill="#101040" d="M735.3,102.2c0.4,0.5,0.9,2.3,0.9,8.6c0,0.8,0,1.8,0,2.8c-3.4,0.5-6.3,1-6.8,1.2l-0.6,0.2l2.8,2.7l0.2-0.1c1.1-0.3,2.6-0.5,4.2-0.7c0,0.7-0.4,7.3-0.4,7.9c-4.9,0.4-9.2,1.1-12.7,1.9c-3,0.8-4.8,2.6-5.2,5.4c-0.1,0.4-0.1,0.9-0.1,1.4c0,7.1,6.9,16.9,7,16.9l0.8,0.5l-0.3-0.9c0-0.1-2.8-9.4-2.8-15.6c0-1.8,0.2-3.3,0.8-4.3c0.4-0.6,0.8-0.9,1.4-1c1.3-0.2,2.7-0.4,4-0.6c0,0,3.1,4,3.7,4.7c-0.5,0.1-1.4,0.3-1.4,0.3l0,0.3l0.2,2.2l0.4-0.1c0.6-0.2,1.8-0.5,3.2-0.7c0,0.6,0.1,2.4,0.2,2.9c-1.7,0.5-3.2,1.1-3.8,1.4l-0.4,0.2l1.5,1.6l0.2-0.1c0.7-0.3,1.7-0.6,2.7-0.9c0,0.7,0.3,5.6,0.3,5.6l0.7,0c0,0,1.1-5.5,1.2-6c1.8-0.2,3.5-0.1,4.8,0.2l0.4,0.1v-3l-0.2-0.1c-0.8-0.4-2.3-0.4-4.5,0.1c0.1-0.7,0.4-2.1,0.5-2.5c1.6-0.1,2.9-0.2,3.8-0.1l0.5,0.1l-0.7-2.6c0,0-0.9,0-1.4,0.1c0.8-0.9,3.5-4.4,3.7-4.6c0.3,0,3.9,0.1,3.9,0.1c1.6,0.1,3,0.7,4.1,2c1.3,1.5,2,3.9,2,6.3c0,0.5,0,1.1-0.1,1.6l-0.1,0.7c-0.7,5.1-1.3,8.8-3.1,9.6c-1.2,0.5-3.1-0.3-5.9-2.4l-0.5,0.5l5.5,9.6l0.3-0.1c6.7-2.4,7.7-3.8,10.4-15.4c1.2-5.1,0.8-9.1-1.3-11.8c-2.5-3.3-6.3-3.7-6.5-3.7c-0.1,0-6.7-0.3-14.6,0.2c0.2-0.8,2-7.5,2.1-7.9c2.5,0,4.7,0.1,5.7,0.5l0.4,0.1l0.4-4.7l-0.4,0c-1.1,0-2.8,0.1-5.1,0.3c1.3-4.4,1.9-5.8,2.1-6.2l0.2-0.3l-8.1-4.7L735.3,102.2z M739.7,127.5c-0.1,0.7-0.7,4.3-0.7,4.7c0,0-0.1,0-0.1,0c0-0.1,0.1-0.4,0.1-0.4l-4.4,0.4c0,0,0,0.5,0,0.6c-0.4,0.1-1,0.2-1.4,0.2c0-0.7,0-5,0-5.1C735.4,127.8,737.5,127.6,739.7,127.5z"/> | |
| 497 | - | <path fill="#101040" d="M804.5,129.3l0.1-6.4l4.4-0.3l-0.3-2.4l-4.1,1.2l0-5.7c2.4,0.3,5,0.8,7.1,0.9c4.3,0.3-1.2,14.1-1.2,14.1s16.3-14.4,2.6-18.1c-1.4,0-4.9,0.2-8.4,0.4l0-3.9l7.5-1.9l-7.5-0.1l0-4l-4.6-3.4l0.5,8.7l-4,2.7l4.1-1l0.2,3.2c-1.8,0.2-3.4,0.3-4.3,0.5c-11.5,2.2,1,17.2,1,17.2s-4-14,0.5-15.3c0.9-0.3,1.8-0.3,2.9-0.3l0.4,7l-3,0.9l3-0.2l0.2,4.1c-9.2,12.1-19.7,16.6-19.7,16.6s10.2-1.1,20-11.9l1,18.7c-3.7-1.5-7-3.6-7-3.6s2.4,7.1,7.5,10.1l4.3-5.1c-1-0.2-2.1-0.5-3.2-0.9l0.2-18.9c2.7,3.2,9,10.5,11.7,11l9-3C821.9,139.6,807.7,131.3,804.5,129.3z"/> | |
| 498 | - | <path fill="none" stroke="#10106C" stroke-width="0.75" d="M49.4,40.5c0,7.7-6.2,13.9-13.9,13.9c-7.7,0-13.9-6.2-13.9-13.9c0-7.7,6.2-13.9,13.9-13.9C43.2,26.6,49.4,32.8,49.4,40.5z"/> | |
| 499 | - | <circle fill="none" stroke="#10106C" stroke-width="1.6" cx="42" cy="40.5" r="6.5"/> | |
| 500 | - | <circle fill="none" stroke="#10106C" stroke-width="1.6" cx="29" cy="40.5" r="6.5"/> | |
| 501 | - | <circle fill="none" stroke="#10106C" stroke-width="1.6" cx="35.5" cy="34" r="6.5"/> | |
| 502 | - | <circle fill="none" stroke="#10106C" stroke-width="1.6" cx="35.5" cy="47" r="6.5"/> | |
| 503 | - | <path fill="none" stroke="#10106C" stroke-width="0.75" d="M44.8,40.5c0,5.1-4.2,9.3-9.3,9.3s-9.3-4.2-9.3-9.3c0-5.1,4.2-9.3,9.3-9.3S44.8,35.4,44.8,40.5z"/> | |
| 504 | - | <path fill="none" stroke="#BA0000" stroke-width="2" d="M52.2,40.5c0,9.2-7.5,16.7-16.7,16.7c-9.2,0-16.7-7.5-16.7-16.7c0-9.2,7.5-16.7,16.7-16.7C44.7,23.8,52.2,31.3,52.2,40.5z"/> | |
| 505 | - | <path fill="none" stroke="#10106C" stroke-width="0.75" d="M55,40.5C55,51.3,46.3,60,35.5,60C24.7,60,16,51.3,16,40.5C16,29.7,24.7,21,35.5,21C46.3,21,55,29.7,55,40.5z"/> | |
| 506 | - | <path fill="none" stroke="#10106C" stroke-width="0.75" d="M49.4,128.5c0,7.7-6.2,13.9-13.9,13.9c-7.7,0-13.9-6.2-13.9-13.9s6.2-13.9,13.9-13.9C43.2,114.6,49.4,120.8,49.4,128.5z"/> | |
| 507 | - | <circle fill="none" stroke="#10106C" stroke-width="1.6" cx="42" cy="128.5" r="6.5"/> | |
| 508 | - | <circle fill="none" stroke="#10106C" stroke-width="1.6" cx="29" cy="128.5" r="6.5"/> | |
| 509 | - | <circle fill="none" stroke="#10106C" stroke-width="1.6" cx="35.5" cy="122" r="6.5"/> | |
| 510 | - | <circle fill="none" stroke="#10106C" stroke-width="1.6" cx="35.5" cy="135" r="6.5"/> | |
| 511 | - | <path fill="none" stroke="#10106C" stroke-width="0.75" d="M44.8,128.5c0,5.1-4.2,9.3-9.3,9.3s-9.3-4.2-9.3-9.3c0-5.1,4.2-9.3,9.3-9.3S44.8,123.4,44.8,128.5z"/> | |
| 512 | - | <path fill="none" stroke="#BA0000" stroke-width="2" d="M52.2,128.5c0,9.2-7.5,16.7-16.7,16.7c-9.2,0-16.7-7.5-16.7-16.7c0-9.2,7.5-16.7,16.7-16.7C44.7,111.8,52.2,119.3,52.2,128.5z"/> | |
| 513 | - | <path fill="none" stroke="#10106C" stroke-width="0.75" d="M55,128.5c0,10.8-8.7,19.5-19.5,19.5c-10.8,0-19.5-8.7-19.5-19.5c0-10.8,8.7-19.5,19.5-19.5C46.3,109,55,117.7,55,128.5z"/> | |
| 514 | - | <path fill="#BA0000" d="M861.8,10.2c-0.4,0.4-0.5,1-0.5,1.7c0,1.3,0.4,2.9,0.6,3.5c0,0,0.3,7,0.4,7.8c-0.9,0.1-2.5,0.2-3.1,0.4l-0.8,0c-1.7,0.1-3.3,0.2-4,0.9l-0.3,0.3l0.3,0.4c0.5,0.6,0.7,2.3,0.9,4.1c0.5,3.7,1.1,8.3,4.1,10.1l0.6,0.3L860,39c0,0,0.1-0.3,0.1-0.7c0-0.3-0.1-0.8-0.2-1.5c0.8,0.1,2.5,0.3,3.1,0.3c0.1,0.8,2.7,33.4,2.7,33.4l1,0c0,0,1.1-32.2,1.1-33.2c0.7,0,3.8,0.1,4.7,0.2c-0.2,0.6-1,3.1-1,3.1l1.4-1.3c0.4-0.3,9.1-8.2,7.9-13.4c-0.4-1.7-1.8-2.9-4.1-3.5l-0.1,0h-0.1c-1.3,0-5.3,0.1-8.3,0.3c0.1-2.6,0.1-4,0.1-4.7c0-0.6,0-0.7,0-0.8c0,0,0-0.1,0-0.1c0-0.5,0.5-1.2,0.9-1.7c0.4-0.6,0.9-1.1,0.9-1.7c0-0.2-0.1-0.4-0.2-0.6c-0.6-0.9-5-3.2-6.9-3.4C862.4,9.8,862,9.9,861.8,10.2z M861.9,26.6c0,0,0.2,0,0.5,0c0,0.9,0.3,7.2,0.4,8.1c-0.8-0.1-2.7-0.3-3.3-0.3c-0.3-1.3-0.4-2.7-0.5-4.1c-0.1-1.2-0.2-2.4-0.4-3.5C859.5,26.8,861.9,26.6,861.9,26.6z M868,29.7c0,0,0.1-2.4,0.1-3.2c1.3,0.2,3,0.3,4.6,0.3c0,0,1.2,0,1.8,0c0,0.4-0.1,0.9-0.1,0.9c-0.2,2.2-0.5,4.8-1.4,7.4c-0.6,0-4.3-0.2-5.1-0.3C867.9,34.1,868,29.7,868,29.7z"/> | |
| 515 | - | <path fill="#BA0000" d="M861.8,98.2c-0.4,0.4-0.5,1-0.5,1.7c0,1.3,0.4,2.9,0.6,3.5c0,0,0.3,7,0.4,7.8c-0.9,0.1-2.5,0.2-3.1,0.4l-0.8,0c-1.7,0.1-3.3,0.2-4,0.9l-0.3,0.3l0.3,0.4c0.5,0.6,0.7,2.3,0.9,4.1c0.5,3.7,1.1,8.3,4.1,10.1l0.6,0.3l0.2-0.6c0,0,0.1-0.3,0.1-0.7c0-0.3-0.1-0.8-0.2-1.5c0.8,0.1,2.5,0.3,3.1,0.3c0.1,0.8,2.7,33.4,2.7,33.4l1,0c0,0,1.1-32.2,1.1-33.2c0.7,0,3.8,0.1,4.7,0.2c-0.2,0.6-1,3.1-1,3.1l1.4-1.3c0.4-0.3,9.1-8.2,7.9-13.4c-0.4-1.7-1.8-2.9-4.1-3.5l-0.1,0h-0.1c-1.3,0-5.3,0.1-8.3,0.3c0.1-2.6,0.1-4,0.1-4.7c0-0.6,0-0.7,0-0.8c0,0,0-0.1,0-0.1c0-0.5,0.5-1.2,0.9-1.7c0.4-0.6,0.9-1.1,0.9-1.7c0-0.2-0.1-0.4-0.2-0.6c-0.6-0.9-5-3.2-6.9-3.4C862.4,97.8,862,97.9,861.8,98.2z M861.9,114.6c0,0,0.2,0,0.5,0c0,0.9,0.3,7.2,0.4,8.1c-0.8-0.1-2.7-0.3-3.3-0.3c-0.3-1.3-0.4-2.7-0.5-4.1c-0.1-1.2-0.2-2.4-0.4-3.5C859.5,114.8,861.9,114.6,861.9,114.6z M868,117.7c0,0,0.1-2.4,0.1-3.2c1.3,0.2,3,0.3,4.6,0.3c0,0,1.2,0,1.8,0c0,0.4-0.1,0.9-0.1,0.9c-0.2,2.2-0.5,4.8-1.4,7.4c-0.6,0-4.3-0.2-5.1-0.3C867.9,122.1,868,117.7,868,117.7z"/> | |
| 516 | - | <path fill="#004C00" d="M925,47.8c0,0.1-1.4,5.1-5.1,7.1c-0.5-0.6-3.1-4.2-3.1-4.2l-0.9,0.5l4.4,11.4l1-0.2c0-2.2,1.1-3.5,2.4-4.9c1.8-2.1,3.8-4.5,2.3-9.7l-0.5-1.7L925,47.8z"/> | |
| 517 | - | <path fill="#004C00" d="M929.5,37c0,0-0.5,4.9-1.9,7.8l-0.3,0.7l0.7,0.1c1.8,0.1,3.5-3.7,4.1-5.4c0.4,0,1.4,0.1,2,0.1c-0.3,1.2-0.7,2.8-0.7,3.8c0,0.4,0,0.7,0.2,0.9l0.5,0.3c0.8,0.1,5.2-0.2,6.1-0.3l0-1c-1.5-0.2-3.3-0.7-3.5-1.2c-0.2-0.6,0.6-3,1.5-5l0.3-0.7l-9-0.6L929.5,37z"/> | |
| 518 | - | <path fill="#004C00" d="M942,46.6L927.3,47c0,0,4.9,6.3,5.4,6.9c-2.6,2.4-4.9,4.2-7,5.4l-0.6,0.4l0.6,0.5c1.4,1.1,4.2-0.2,8.6-4.2c2.3,3.2,3.8,5.5,4.3,6.9l0.2,0.7l2.7-2.2l0-0.2l0-0.3c0-0.5,0-2.1-4.7-7.3c3-3.1,5.6-6,5.6-6.1l0.7-0.9L942,46.6z M937.3,49.4c-0.8,0.9-1.7,1.8-2.1,2.3c-0.4-0.4-1.2-1.2-1.9-1.9C934.4,49.6,936,49.5,937.3,49.4z"/> | |
| 519 | - | <path fill="#004C00" d="M931.1,25.2l-1.7,1.7l-4.5-5.2l-0.2,0c-0.7,0-4.3,0.1-5.2,1.4c-0.1,0.1-0.1,0.3-0.1,0.4c0,0.1,0,0.1,0,0.2c0.1,0.5,0.7,0.7,1.8,1.1c0.5,0.2,1.2,0.5,1.5,0.7c-1.1,2.3-2.3,4.4-3.7,6.4c-0.7-0.4-4.1-2.5-4.1-2.5l-0.6,0.8c0,0,2.8,3.1,3.3,3.6c-2.8,3.5-6.2,6.6-10.4,9.5l0.4,0.9c12.8-3.3,21.9-16.2,22.1-16.5l2.3,2.2c-3.2,2-6.2,3.8-6.2,3.8l0.5,0.9c2-0.8,4.8-1.9,7.6-3L945,42.4l10.8-3.8l-0.1-1c-5.1-0.2-12-4.4-17.1-7.9c2.9-1,5.3-1.7,6.4-1.6c0.7,0,0.9-0.3,1-0.5c0.4-1.1-3.5-4.4-11.4-9.7l-0.5-0.3l-0.2,0.6 M934.9,27c0.7-1.2,1.6-2.9,1.9-3.3c0.5,0.3,1.8,1.1,2.5,1.6c-0.4,0.2-1.9,1.1-3.7,2.3C935.4,27.4,935.2,27.2,934.9,27z"/> | |
| 520 | - | <path fill="#004C00" d="M926.9,35.4l-6.2,3.5l0.3,0.9c0,0,1.1-0.1,1.8-0.1c-0.2,0.7-0.6,1.8-1.1,3.2c-0.6,0-5-0.2-5-0.2l0.8,0.9c0.3,0.3,1.6,1.7,2.7,1.7c0,0,0.3-0.1,0.6-0.1c-0.8,2-1.7,3.7-2.6,5l0.7,0.7c1.5-1.2,3.2-3.4,4.8-6.4c0.3-0.1,1.2-0.4,1.2-0.4l-0.1-1c0,0-0.2,0-0.4,0c1.8-3.6,3.1-7.1,3.1-7.1l0.5-1.3L926.9,35.4z"/> | |
| 521 | - | <path fill="#004C00" d="M925,135.8c0,0.1-1.4,5.1-5.1,7.1c-0.5-0.6-3.1-4.2-3.1-4.2l-0.9,0.5l4.4,11.4l1-0.2c0-2.2,1.1-3.5,2.4-4.9c1.8-2.1,3.8-4.5,2.3-9.7l-0.5-1.7L925,135.8z"/> | |
| 522 | - | <path fill="#004C00" d="M929.5,125c0,0-0.5,4.9-1.9,7.8l-0.3,0.7l0.7,0.1c1.8,0.1,3.5-3.7,4.1-5.4c0.4,0,1.4,0.1,2,0.1c-0.3,1.2-0.7,2.8-0.7,3.8c0,0.4,0,0.7,0.2,0.9l0.5,0.3c0.8,0.1,5.2-0.2,6.1-0.3l0-1c-1.5-0.2-3.3-0.7-3.5-1.2c-0.2-0.6,0.6-3,1.5-5l0.3-0.7l-9-0.6L929.5,125z"/> | |
| 523 | - | <path fill="#004C00" d="M942,134.6l-14.7,0.4c0,0,4.9,6.3,5.4,6.9c-2.6,2.4-4.9,4.2-7,5.4l-0.6,0.4l0.6,0.5c1.4,1.1,4.2-0.2,8.6-4.2c2.3,3.2,3.8,5.5,4.3,6.9l0.2,0.7l2.7-2.3l0-0.2l0-0.3c0-0.5,0-2.1-4.7-7.3c3-3.1,5.6-6,5.6-6.1l0.7-0.9L942,134.6z M937.3,137.4c-0.8,0.9-1.7,1.8-2.1,2.3c-0.4-0.4-1.2-1.2-1.9-1.9C934.4,137.6,936,137.5,937.3,137.4z"/> | |
| 524 | - | <path fill="#004C00" d="M931.1,113.2l-1.7,1.7l-4.5-5.2l-0.2,0c-0.7,0-4.3,0.1-5.2,1.4c-0.1,0.1-0.1,0.3-0.1,0.4c0,0.1,0,0.1,0,0.2c0.1,0.5,0.7,0.7,1.8,1.1c0.5,0.2,1.2,0.5,1.5,0.7c-1.1,2.3-2.3,4.4-3.7,6.4c-0.7-0.4-4.1-2.5-4.1-2.5l-0.6,0.8c0,0,2.8,3.1,3.3,3.6c-2.8,3.5-6.2,6.6-10.4,9.5l0.4,0.9c12.8-3.3,21.9-16.2,22.1-16.5l2.3,2.2c-3.2,2-6.2,3.8-6.2,3.8l0.5,0.9c2-0.8,4.8-1.9,7.6-3l11.4,10.9l10.8-3.8l-0.1-1c-5.1-0.2-12-4.4-17.1-7.9c2.9-1,5.3-1.7,6.4-1.6c0.7,0,0.9-0.3,1-0.5c0.4-1.1-3.5-4.4-11.4-9.7l-0.5-0.3l-0.2,0.6 M934.9,115c0.7-1.2,1.6-2.9,1.9-3.3c0.5,0.3,1.8,1.1,2.5,1.6c-0.4,0.2-1.9,1.1-3.7,2.3C935.4,115.4,935.2,115.2,934.9,115z"/> | |
| 525 | - | <path fill="#004C00" d="M926.9,123.4l-6.2,3.5l0.3,0.9c0,0,1.1-0.1,1.8-0.1c-0.2,0.7-0.6,1.8-1.1,3.2c-0.6,0-5-0.2-5-0.2l0.8,0.9c0.3,0.3,1.6,1.7,2.7,1.7c0,0,0.3-0.1,0.6-0.1c-0.8,2-1.7,3.7-2.6,5l0.7,0.7c1.5-1.2,3.2-3.4,4.8-6.4c0.3-0.1,1.2-0.4,1.2-0.4l-0.1-1c0,0-0.2,0-0.4,0c1.8-3.6,3.1-7.1,3.1-7.1l0.5-1.3L926.9,123.4z"/> | |
| 526 | - | <rect x="2386" y="12" rx="5" fill="#294D8E" width="35" height="57"/> | |
| 527 | - | <rect x="2387" y="13" rx="4" fill="#F4F4F4" width="33" height="55"/> | |
| 528 | - | <rect x="2390" y="16" rx="3" fill="#294D8E" width="27" height="49"/> | |
| 529 | - | <rect x="2393" y="19" rx="7" fill="#F4F4F4" width="21" height="43"/> | |
| 530 | - | <rect x="2386" y="100" rx="5" fill="#294D8E" width="35" height="57"/> | |
| 531 | - | <rect x="2387" y="101" rx="4" fill="#FFFFFF" width="33" height="55"/> | |
| 532 | - | <rect x="2390" y="104" rx="3" fill="#294D8E" width="27" height="49"/> | |
| 533 | - | <rect x="2393" y="107" rx="7" fill="#FFFFFF" width="21" height="43"/> | |
| 534 | - | <path fill="#99B9FF" stroke="#678ED3" d="M2323.8,13.9l-0.1,1.3c0,0,8.9-2.4,9.4-0.6c0,0-0.4,4.1-1.4,4.9c0-0.5-0.7-0.6-1.7-0.5c0.1-1.1,0.3-3.7,1-4l-1.6-0.7l-0.2,4.8c-0.5,0.1-1,0.2-1.5,0.3l-0.9-4.8l-1,0.4l0.9,4.7c-1.1,0.3-2,0.5-2,0.5l-1.8-7l-2.2-0.5l3.1,8.9c0,0,7.7-2.7,7.5-0.7c3.2-2.5,4.3-6.5,4.3-6.5C2333.4,11,2323.8,13.9,2323.8,13.9z"/> | |
| 535 | - | <path fill="#99B9FF" stroke="#678ED3" d="M2267.6,19.9c-0.8-2.5,4.7,2.6,2.6,2.2c-2.1-0.4-10.6,0.1-11.9,0.6l-0.6-2.2C2259.5,20.7,2267.6,19.9,2267.6,19.9z"/> | |
| 536 | - | <path fill="#99B9FF" stroke="#678ED3" d="M2139.6,16.1c-0.8-2.5,4.7,2.6,2.6,2.2c-2.1-0.4-10.6,0.1-11.9,0.6l-0.6-2.2C2131.5,16.9,2139.6,16.1,2139.6,16.1z"/> | |
| 537 | - | <polygon fill="#99B9FF" stroke="#678ED3" points="2264.9,16.3 2265.7,17.8 2260.9,18.3 2260.4,16.8"/> | |
| 538 | - | <path fill="#99B9FF" stroke="#678ED3" d="M2267.1,12.5c-2-2.3-2.8,0.6-9,0.6l1.1,1.7l7.7-1L2267.1,12.5z"/> | |
| 539 | - | <path fill="#99B9FF" stroke="#678ED3" d="M2203.6,18.9c-0.8-2.5,4.7,2.6,2.6,2.2c-2.1-0.4-10.6,0.1-11.9,0.6l-0.6-2.2C2195.5,19.7,2203.6,18.9,2203.6,18.9z"/> | |
| 540 | - | <path fill="#99B9FF" stroke="#678ED3" d="M2203.1,14.5c-2-2.3-2.8,0.6-9,0.6l1.1,1.7l7.7-1L2203.1,14.5z"/> | |
| 541 | - | <path fill="#99B9FF" stroke="#678ED3" d="M2323.8,101.9l-0.1,1.3c0,0,8.9-2.4,9.4-0.6c0,0-0.4,4.1-1.4,4.9c0-0.5-0.7-0.6-1.7-0.5c0.1-1.1,0.3-3.7,1-4l-1.6-0.7l-0.2,4.8c-0.5,0.1-1,0.2-1.5,0.3l-0.9-4.8l-1,0.4l0.9,4.7c-1.1,0.3-2,0.5-2,0.5l-1.8-7l-2.2-0.5l3.1,8.9c0,0,7.7-2.7,7.5-0.7c3.2-2.5,4.3-6.5,4.3-6.5C2333.4,99,2323.8,101.9,2323.8,101.9z"/> | |
| 542 | - | <path fill="#99B9FF" stroke="#678ED3" d="M2267.6,107.9c-0.8-2.5,4.7,2.6,2.6,2.2c-2.1-0.4-10.6,0.1-11.9,0.6l-0.6-2.2C2259.5,108.7,2267.6,107.9,2267.6,107.9z"/> | |
| 543 | - | <path fill="#99B9FF" stroke="#678ED3" d="M2139.6,104.1c-0.8-2.5,4.7,2.6,2.6,2.2c-2.1-0.4-10.6,0.1-11.9,0.6l-0.6-2.2C2131.5,104.9,2139.6,104.1,2139.6,104.1z"/> | |
| 544 | - | <polygon fill="#99B9FF" stroke="#678ED3" points="2264.9,104.3 2265.7,105.8 2260.9,106.3 2260.4,104.8 "/> | |
| 545 | - | <path fill="#99B9FF" stroke="#678ED3" d="M2267.1,100.5c-2-2.3-2.8,0.6-9,0.6l1.1,1.7l7.7-1L2267.1,100.5z"/> | |
| 546 | - | <path fill="#99B9FF" stroke="#678ED3" d="M2203.6,106.9c-0.8-2.5,4.7,2.6,2.6,2.2c-2.1-0.4-10.6,0.1-11.9,0.6l-0.6-2.2C2195.5,107.7,2203.6,106.9,2203.6,106.9z"/> | |
| 547 | - | <path fill="#99B9FF" stroke="#678ED3" d="M2203.1,102.5c-2-2.3-2.8,0.6-9,0.6l1.1,1.7l7.7-1L2203.1,102.5z"/> | |
| 548 | - | <path fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" d="M2643.1,20.3l-0.1,1.3c0,0,8.9-2.4,9.4-0.6c0,0-0.4,4.1-1.4,4.9c0-0.5-0.7-0.6-1.7-0.5c0.1-1.1,0.3-3.7,1-4l-1.6-0.7l-0.2,4.8c-0.5,0.1-1,0.2-1.5,0.3l-0.9-4.8l-1,0.4l0.9,4.7c-1.1,0.3-2,0.5-2,0.5l-1.8-7L2640,19l3.1,8.9c0,0,7.7-2.7,7.5-0.7c3.2-2.5,4.3-6.5,4.3-6.5C2652.7,17.4,2643.1,20.3,2643.1,20.3z"/> | |
| 549 | - | <path fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" d="M2586.9,26.3c-0.8-2.5,4.7,2.6,2.6,2.2c-2.1-0.4-10.6,0.1-11.9,0.6l-0.6-2.2C2578.9,27.1,2586.9,26.3,2586.9,26.3z"/> | |
| 550 | - | <path fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" d="M2458.9,22.5c-0.8-2.5,4.7,2.6,2.6,2.2c-2.1-0.4-10.6,0.1-11.9,0.6l-0.6-2.2C2450.9,23.3,2458.9,22.5,2458.9,22.5z"/> | |
| 551 | - | <polygon fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" points="2584.3,22.6 2585,24.1 2580.3,24.7 2579.7,23.2 "/> | |
| 552 | - | <path fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" d="M2586.5,18.8c-2-2.3-2.8,0.6-9,0.6l1.1,1.7l7.7-1L2586.5,18.8z"/> | |
| 553 | - | <path fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" d="M2522.9,25.3c-0.8-2.5,4.7,2.6,2.6,2.2c-2.1-0.4-10.6,0.1-11.9,0.6l-0.6-2.2C2514.9,26.1,2522.9,25.3,2522.9,25.3z"/> | |
| 554 | - | <path fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" d="M2522.5,20.8c-2-2.3-2.8,0.6-9,0.6l1.1,1.7l7.7-1L2522.5,20.8z"/> | |
| 555 | - | <path fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" d="M2643.1,108.3l-0.1,1.3c0,0,8.9-2.4,9.4-0.6c0,0-0.4,4.1-1.4,4.9c0-0.5-0.7-0.6-1.7-0.5c0.1-1.1,0.3-3.7,1-4l-1.6-0.7l-0.2,4.8c-0.5,0.1-1,0.2-1.5,0.3l-0.9-4.8l-1,0.4l0.9,4.7c-1.1,0.3-2,0.5-2,0.5l-1.8-7l-2.2-0.5l3.1,8.9c0,0,7.7-2.7,7.5-0.7c3.2-2.5,4.3-6.5,4.3-6.5C2652.7,105.4,2643.1,108.3,2643.1,108.3z"/> | |
| 556 | - | <path fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" d="M2586.9,114.3c-0.8-2.5,4.7,2.6,2.6,2.2c-2.1-0.4-10.6,0.1-11.9,0.6l-0.6-2.2C2578.9,115.1,2586.9,114.3,2586.9,114.3z"/> | |
| 557 | - | <path fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" d="M2458.9,110.5c-0.8-2.5,4.7,2.6,2.6,2.2c-2.1-0.4-10.6,0.1-11.9,0.6l-0.6-2.2C2450.9,111.3,2458.9,110.5,2458.9,110.5z"/> | |
| 558 | - | <polygon fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" points="2584.3,110.6 2585,112.1 2580.3,112.7 2579.7,111.2 "/> | |
| 559 | - | <path fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" d="M2586.5,106.8c-2-2.3-2.8,0.6-9,0.6l1.1,1.7l7.7-1L2586.5,106.8z"/> | |
| 560 | - | <path fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" d="M2522.9,113.3c-0.8-2.5,4.7,2.6,2.6,2.2c-2.1-0.4-10.6,0.1-11.9,0.6l-0.6-2.2C2514.9,114.1,2522.9,113.3,2522.9,113.3z"/> | |
| 561 | - | <path fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" d="M2522.5,108.8c-2-2.3-2.8,0.6-9,0.6l1.1,1.7l7.7-1L2522.5,108.8z"/> | |
| 562 | - | <path fill="#101040" d="M632.3,54.7c-2-0.9-11.8,3.3-11.8,3.3l0.5-20.7c0,0,5.3-4.3,9.6-1.7l-1.2-5c-1.4-0.2-8.4,5.5-8.4,5.5s-0.6-18.8,4.8-19.9c0,0-8.9-2.6-9.7-2.2c-0.4,0.2-0.4,13.8-0.2,27.1c-3.2,3.1-5.9,6.2-7.1,7.3l0.4-28.5c0,0-4.7-2.6-5.8-1.4l0.2,14.8c-0.3-0.5-0.7-0.9-1-1.3c-1-1.3-6.1-4.2-6.1-4.2s1.7,5.6,2.5,6.6c0.7,0.9,3,2.9,4.6,2.2l0.2,16.5c-3.4,3-7.9,6.8-8.5,5.4c-1.5-3.2-4.7-3-5.2-1.5c-0.5,1.6,4.2,10.2,5.8,10.1c0.8-0.1,4.4-4.9,8-10l0.1,5.3c1.6,2.1,3.8,0.5,4.6-0.7l0.3-11c1.1-1.6,4-4.5,7.1-7.9c0.2,12.1,0.3,22.8,0.3,22.8c4.9-5.2,16.7-4,16.7-4L632.3,54.7z"/> | |
| 563 | - | </svg> |
addedscripts/antics-deploy.mjs+92 −0
| 1 | + | #!/usr/bin/env node | |
| 2 | + | // Deploy the built game to antics.gg as a multi-file bundle. | |
| 3 | + | // | |
| 4 | + | // npm run build && node scripts/antics-deploy.mjs # keyless: a fresh link each time | |
| 5 | + | // node scripts/antics-deploy.mjs --project <proj_id> # under a project: stable /p/ share link | |
| 6 | + | // | |
| 7 | + | // A keyless deploy needs no account at all. Deploying under a project needs a | |
| 8 | + | // token from `npx antics-cli login`, read from ANTICS_TOKEN or ~/.antics/token. | |
| 9 | + | ||
| 10 | + | import { readdir, readFile } from 'node:fs/promises'; | |
| 11 | + | import { homedir } from 'node:os'; | |
| 12 | + | import { join, relative, extname } from 'node:path'; | |
| 13 | + | ||
| 14 | + | const HOST = process.env.ANTICS_HOST || 'https://antics.gg'; | |
| 15 | + | const DIST = new URL('../dist', import.meta.url).pathname; | |
| 16 | + | ||
| 17 | + | // Anything not in this list rides along as a data: URI. | |
| 18 | + | const TEXT = new Set(['.html', '.js', '.css', '.svg', '.json', '.webmanifest', '.txt', '.map']); | |
| 19 | + | const MIME = { | |
| 20 | + | '.png': 'image/png', | |
| 21 | + | '.jpg': 'image/jpeg', | |
| 22 | + | '.webp': 'image/webp', | |
| 23 | + | '.mp3': 'audio/mpeg', | |
| 24 | + | '.woff2': 'font/woff2', | |
| 25 | + | '.ico': 'image/x-icon', | |
| 26 | + | }; | |
| 27 | + | ||
| 28 | + | async function* walk(dir) { | |
| 29 | + | for (const e of await readdir(dir, { withFileTypes: true })) { | |
| 30 | + | const p = join(dir, e.name); | |
| 31 | + | if (e.isDirectory()) yield* walk(p); | |
| 32 | + | else yield p; | |
| 33 | + | } | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | async function collect() { | |
| 37 | + | const files = {}; | |
| 38 | + | let bytes = 0; | |
| 39 | + | for await (const p of walk(DIST)) { | |
| 40 | + | const rel = relative(DIST, p); | |
| 41 | + | const ext = extname(rel); | |
| 42 | + | const buf = await readFile(p); | |
| 43 | + | bytes += buf.length; | |
| 44 | + | files[rel] = TEXT.has(ext) | |
| 45 | + | ? buf.toString('utf8') | |
| 46 | + | : `data:${MIME[ext] ?? 'application/octet-stream'};base64,${buf.toString('base64')}`; | |
| 47 | + | } | |
| 48 | + | return { files, bytes }; | |
| 49 | + | } | |
| 50 | + | ||
| 51 | + | async function token() { | |
| 52 | + | if (process.env.ANTICS_TOKEN) return process.env.ANTICS_TOKEN; | |
| 53 | + | try { | |
| 54 | + | return (await readFile(join(homedir(), '.antics', 'token'), 'utf8')).trim(); | |
| 55 | + | } catch { | |
| 56 | + | return null; | |
| 57 | + | } | |
| 58 | + | } | |
| 59 | + | ||
| 60 | + | const args = process.argv.slice(2); | |
| 61 | + | const projectAt = args.indexOf('--project'); | |
| 62 | + | const project = projectAt >= 0 ? args[projectAt + 1] : null; | |
| 63 | + | ||
| 64 | + | const { files, bytes } = await collect(); | |
| 65 | + | console.log(`deploying ${Object.keys(files).length} files, ${(bytes / 1024 / 1024).toFixed(1)} MB from dist/`); | |
| 66 | + | ||
| 67 | + | const body = { files }; | |
| 68 | + | const headers = { 'content-type': 'application/json' }; | |
| 69 | + | if (project) { | |
| 70 | + | body.project = project; | |
| 71 | + | const t = await token(); | |
| 72 | + | if (!t) { | |
| 73 | + | console.error('a --project deploy needs a token: run `npx antics-cli login`, or set ANTICS_TOKEN'); | |
| 74 | + | process.exit(1); | |
| 75 | + | } | |
| 76 | + | headers.authorization = `Bearer ${t}`; | |
| 77 | + | } | |
| 78 | + | ||
| 79 | + | const res = await fetch(`${HOST}/api/deploy`, { | |
| 80 | + | method: 'POST', | |
| 81 | + | headers, | |
| 82 | + | body: JSON.stringify(body), | |
| 83 | + | }); | |
| 84 | + | const out = await res.json().catch(() => ({})); | |
| 85 | + | if (!res.ok) { | |
| 86 | + | console.error(`deploy failed: ${res.status}`, out); | |
| 87 | + | process.exit(1); | |
| 88 | + | } | |
| 89 | + | console.log('deployed.'); | |
| 90 | + | if (out.playUrl) console.log(` play (this build): ${out.playUrl}`); | |
| 91 | + | if (out.shareUrl) console.log(` share (follows updates): ${out.shareUrl}`); | |
| 92 | + | if (out.hash) console.log(` hash: ${out.hash}`); |
modifiedsrc/App.tsx+187 −24
| 1 | 1 | import { useEffect, useRef, useState, useSyncExternalStore } from 'react'; | |
| 2 | 2 | import { AutoPlay } from './game/autoplay'; | |
| 3 | + | import type { TableCtl } from './game/ctl'; | |
| 3 | 4 | import { Game } from './game/engine'; | |
| 4 | 5 | import { loadPrefs, savePrefs, type Prefs } from './game/prefs'; | |
| 5 | 6 | import { loadSave } from './game/save'; | |
| 6 | 7 | import { play, setSoundEnabled, setVoiceEnabled, setVolume } from './game/sound'; | |
| 7 | 8 | import { WIND_EN, WIND_NAMES } from './game/tiles'; | |
| 8 | 9 | import type { SeatId } from './game/types'; | |
| 10 | + | import { roomFromUrl, seatLink } from './net/protocol'; | |
| 11 | + | import { NetSession } from './net/session'; | |
| 9 | 12 | import { Center } from './ui/Center'; | |
| 10 | 13 | import { useCompact } from './ui/compact'; | |
| 14 | + | import { Controller } from './ui/Controller'; | |
| 11 | 15 | import { FullscreenToggle } from './ui/FullscreenToggle'; | |
| 12 | 16 | import { enterFullscreen, useFullscreen } from './ui/fullscreen'; | |
| 13 | 17 | import { Help } from './ui/Help'; | |
| 18 | + | import { OnlineLobby } from './ui/OnlineLobby'; | |
| 14 | 19 | import { TablePool } from './table/pool'; | |
| 15 | 20 | import { Pool } from './ui/Pool'; | |
| 16 | - | import { Seat } from './ui/Seat'; | |
| 21 | + | import { Seat, type RemoteSeat } from './ui/Seat'; | |
| 17 | 22 | import { Settings } from './ui/Settings'; | |
| 18 | 23 | import './styles.css'; | |
| 19 | 24 | ||
| ⋯ 2 unchanged lines | |||
| 22 | 27 | const SOLO_SEATS = [false, true, true, true]; | |
| 23 | 28 | const HOTSEAT_SEATS = [false, false, false, false]; | |
| 24 | 29 | ||
| 30 | + | // One join per page, whoever asks first — the lobby button, or the ?room= the | |
| 31 | + | // antics page hands us. A promise rather than a flag, so a second caller waits | |
| 32 | + | // on the same join instead of starting another. | |
| 33 | + | let joining: Promise<NetSession> | null = null; | |
| 34 | + | const joinOnce = () => (joining ??= NetSession.join()); | |
| 35 | + | ||
| 36 | + | const subNothing = () => () => {}; | |
| 37 | + | const zero = () => 0; | |
| 38 | + | ||
| 25 | 39 | export default function App() { | |
| 26 | 40 | const [prefs, setPrefs] = useState<Prefs>(loadPrefs); | |
| 27 | 41 | // Phone-sized: everything reads upright for the one person holding it. | |
| ⋯ 12 unchanged lines | |||
| 40 | 54 | const [settingsOpen, setSettingsOpen] = useState(false); | |
| 41 | 55 | // Read once at startup; after that the live game is the source of truth. | |
| 42 | 56 | const [saved] = useState(() => loadSave()); | |
| 43 | - | const phase = game.state.phase; | |
| 44 | 57 | ||
| 58 | + | // ---- the room, if this page is in one ---------------------------------- | |
| 59 | + | const [session, setSession] = useState<NetSession | null>(null); | |
| 60 | + | const [netErr, setNetErr] = useState<string | null>(null); | |
| 61 | + | const [connecting, setConnecting] = useState(false); | |
| 62 | + | useSyncExternalStore(session?.subscribe ?? subNothing, session?.getSnapshot ?? zero); | |
| 63 | + | useSyncExternalStore(session?.table.subscribe ?? subNothing, session?.table.getSnapshot ?? zero); | |
| 64 | + | ||
| 65 | + | const connect = async (): Promise<NetSession | null> => { | |
| 66 | + | setConnecting(true); | |
| 67 | + | try { | |
| 68 | + | const s = await joinOnce(); | |
| 69 | + | setSession(s); | |
| 70 | + | setNetErr(null); | |
| 71 | + | return s; | |
| 72 | + | } catch (e) { | |
| 73 | + | setNetErr(e instanceof Error ? e.message : String(e)); | |
| 74 | + | joining = null; | |
| 75 | + | return null; | |
| 76 | + | } finally { | |
| 77 | + | setConnecting(false); | |
| 78 | + | } | |
| 79 | + | }; | |
| 80 | + | ||
| 81 | + | // Opened from a room link — the antics page passes ?room= down to us — so | |
| 82 | + | // everyone on that link shares one table. Join it before anyone taps | |
| 83 | + | // anything: presence is the first thing a shared room owes its visitors. | |
| 84 | + | const auto = useRef(false); | |
| 85 | + | useEffect(() => { | |
| 86 | + | if (auto.current || !roomFromUrl()) return; | |
| 87 | + | auto.current = true; | |
| 88 | + | void connect(); | |
| 89 | + | // eslint-disable-next-line react-hooks/exhaustive-deps | |
| 90 | + | }, []); | |
| 91 | + | ||
| 92 | + | // The full-size tables hand phone throws to the pool, and hold the computer | |
| 93 | + | // players while something is still skidding across it. | |
| 94 | + | useEffect(() => { | |
| 95 | + | if (!session) return; | |
| 96 | + | session.onThrowFx = (seat, t) => { | |
| 97 | + | pool.throwFromNet(seat, t); | |
| 98 | + | pool.wake(); | |
| 99 | + | }; | |
| 100 | + | session.setBusy(() => pool.busy); | |
| 101 | + | return () => { | |
| 102 | + | session.onThrowFx = () => {}; | |
| 103 | + | session.setBusy(() => false); | |
| 104 | + | }; | |
| 105 | + | }, [session, pool]); | |
| 106 | + | useEffect(() => session?.table.onSound(play), [session]); | |
| 107 | + | ||
| 108 | + | // Walking back into a running online game unseated — a reload, or a friend | |
| 109 | + | // arriving late — takes the first empty chair, deserted or computer-run. | |
| 110 | + | const reclaimed = useRef(false); | |
| 111 | + | useEffect(() => { | |
| 112 | + | if (!session || session.mode !== 'online' || !session.started) return; | |
| 113 | + | if (reclaimed.current || session.mySeat !== null) return; | |
| 114 | + | if (session.seats.some((s) => s.id === null)) { | |
| 115 | + | reclaimed.current = true; | |
| 116 | + | session.claimSeat(null); | |
| 117 | + | } | |
| 118 | + | }); | |
| 119 | + | ||
| 120 | + | const netMode = session?.mode ?? null; | |
| 121 | + | const mySeat = session?.mySeat ?? null; | |
| 122 | + | // The party table's common screen. Usually the device that started the game; | |
| 123 | + | // after a reload that flag is gone, so anything full-sized and unseated in a | |
| 124 | + | // party room shows the table rather than a phone's controller. | |
| 125 | + | const isScreen = session?.role === 'screen' || (netMode === 'party' && !compact && mySeat === null); | |
| 126 | + | const netActive = !!session && (netMode === 'party' ? true : netMode === 'online' && session.started); | |
| 127 | + | ||
| 128 | + | // Which engine this screen draws and drives. | |
| 129 | + | const ctl: TableCtl = netActive ? session!.table : game; | |
| 130 | + | const phase = ctl.state.phase; | |
| 131 | + | // Online the table is turned so your own seat is at the bottom edge. | |
| 132 | + | const viewSeat: SeatId = netMode === 'online' ? mySeat ?? 0 : 0; | |
| 133 | + | const posOf = (seat: SeatId): SeatId => (((seat - viewSeat + 4) % 4) as SeatId); | |
| 134 | + | ||
| 45 | 135 | // Names and stakes live in the game state as well as in the preferences — | |
| 46 | 136 | // saved games keep the settings they were played under. | |
| 47 | 137 | const commitSettings = (p: Prefs) => { | |
| 48 | 138 | setPrefs(p); | |
| 49 | - | game.applySettings(p.names, p.rules); | |
| 139 | + | ctl.applySettings(p.names, p.rules); | |
| 50 | 140 | setSettingsOpen(false); | |
| 51 | 141 | }; | |
| 52 | 142 | // The panel auditions sound changes as you make them, so cancelling has to | |
| ⋯ 10 unchanged lines | |||
| 63 | 153 | onSave={commitSettings} | |
| 64 | 154 | onClose={cancelSettings} | |
| 65 | 155 | stakesLocked={phase !== 'lobby' && phase !== 'gameEnd'} | |
| 66 | - | bots={game.state.bots} | |
| 156 | + | bots={ctl.state.bots} | |
| 67 | 157 | /> | |
| 68 | 158 | ); | |
| 69 | 159 | ||
| ⋯ 29 unchanged lines | |||
| 99 | 189 | game.restore(saved!.state); | |
| 100 | 190 | }; | |
| 101 | 191 | ||
| 192 | + | // The two networked ways to start. Whoever creates the room is its host and | |
| 193 | + | // may open a mode; a visitor who follows the link into a room where nothing | |
| 194 | + | // has started yet waits for the host's choice. | |
| 195 | + | const openOnline = async () => { | |
| 196 | + | const s = await connect(); | |
| 197 | + | if (s?.isHost && !s.mode) s.openOnlineLobby(); | |
| 198 | + | }; | |
| 199 | + | const openParty = async () => { | |
| 200 | + | const s = await connect(); | |
| 201 | + | if (s?.isHost && !s.mode) s.startParty(prefs.names, prefs.rules); | |
| 202 | + | }; | |
| 203 | + | ||
| 102 | 204 | // Preferences are written straight through, so a mute survives a refresh. | |
| 103 | 205 | useEffect(() => { | |
| 104 | 206 | savePrefs(prefs); | |
| ⋯ 3 unchanged lines | |||
| 108 | 210 | }, [prefs]); | |
| 109 | 211 | useEffect(() => game.onSound(play), [game]); | |
| 110 | 212 | // Watches the table for the whole session and only stirs when a seat the | |
| 111 | - | // computer holds is the one being waited on. | |
| 213 | + | // computer holds is the one being waited on. Idle in a networked game, where | |
| 214 | + | // whichever device is the room's host runs its own AutoPlay. | |
| 112 | 215 | // The computer waits for a thrown tile to land — and for whoever it landed on | |
| 113 | 216 | // to finish saying so — before it reaches for it. | |
| 114 | 217 | useEffect(() => new AutoPlay(game, () => pool.busy).start(), [game, pool]); | |
| 115 | 218 | ||
| 116 | - | if (phase === 'lobby') { | |
| 219 | + | // ---- routing ------------------------------------------------------------ | |
| 220 | + | // A phone at the party table holds a hand, not the table. | |
| 221 | + | if (session && netMode === 'party' && !isScreen) { | |
| 222 | + | return <Controller session={session} />; | |
| 223 | + | } | |
| 224 | + | if (session && netMode === 'online' && !session.started) { | |
| 225 | + | return <OnlineLobby session={session} prefs={prefs} />; | |
| 226 | + | } | |
| 227 | + | ||
| 228 | + | if (!netActive && phase === 'lobby') { | |
| 117 | 229 | const round = saved ? Math.floor(saved.state.rotations / 4) % 4 : 0; | |
| 118 | 230 | const savedSolo = saved?.state.bots.some(Boolean); | |
| 231 | + | const netIdle = !session?.mode; | |
| 119 | 232 | return ( | |
| 120 | 233 | <div className="lobby"> | |
| 121 | 234 | <h1>台灣麻將</h1> | |
| 122 | - | <p className="sub">Taiwanese Mahjong · 16 tiles · four players, one screen</p> | |
| 123 | - | <p className="hint"> | |
| 124 | - | 把擋板放在螢幕四邊,各自遮住自己的牌。 | |
| 125 | - | <br /> | |
| 126 | - | Stand a card over your edge of the screen to hide your hand. | |
| 127 | - | </p> | |
| 235 | + | <p className="sub">Taiwanese Mahjong · 16 tiles · four players</p> | |
| 128 | 236 | ||
| 129 | 237 | {saved && ( | |
| 130 | 238 | <div className="resume"> | |
| ⋯ 29 unchanged lines | |||
| 160 | 268 | 四人同桌 Hotseat | |
| 161 | 269 | <em>四個人共用一個螢幕 · four people, one screen</em> | |
| 162 | 270 | </button> | |
| 271 | + | {(!session || (session.isHost && netIdle)) && ( | |
| 272 | + | <> | |
| 273 | + | <button className="btn big-btn ghost" onClick={openOnline} disabled={connecting}> | |
| 274 | + | 線上對戰 Online game | |
| 275 | + | <em>各自用自己的裝置 · everyone joins from their own device</em> | |
| 276 | + | </button> | |
| 277 | + | <button className="btn big-btn ghost" onClick={openParty} disabled={connecting}> | |
| 278 | + | 手機派對 Party mode | |
| 279 | + | <em>這螢幕當桌,掃 QR 用手機拿牌 · this screen is the table, phones hold the hands</em> | |
| 280 | + | </button> | |
| 281 | + | </> | |
| 282 | + | )} | |
| 283 | + | {session && !session.isHost && netIdle && ( | |
| 284 | + | <p className="hint"> | |
| 285 | + | 已連線 {session.room.code} · {session.playerCount} 人在房,等房主選模式 | |
| 286 | + | <br /> | |
| 287 | + | connected — waiting for the host to pick a mode | |
| 288 | + | </p> | |
| 289 | + | )} | |
| 163 | 290 | </div> | |
| 164 | 291 | ||
| 292 | + | {(netErr || session?.err) && <p className="hint">{netErr ?? session?.err}</p>} | |
| 293 | + | ||
| 165 | 294 | <div className="lobby-extras"> | |
| 166 | 295 | <button className="btn ghost pill" onClick={() => setSettingsOpen(true)}> | |
| 167 | 296 | ⚙ 設定 Settings | |
| ⋯ 7 unchanged lines | |||
| 175 | 304 | ); | |
| 176 | 305 | } | |
| 177 | 306 | ||
| 307 | + | // What each seat is to this particular screen: whose buttons it shows, whose | |
| 308 | + | // tiles it may turn over, and what stands in for the seats that live on | |
| 309 | + | // other devices. | |
| 310 | + | const seatProps = (seat: SeatId): { local: boolean; remote?: RemoteSeat } => { | |
| 311 | + | if (!netActive) return { local: true }; | |
| 312 | + | const slot = session!.seats[seat]; | |
| 313 | + | if (netMode === 'party') { | |
| 314 | + | return { | |
| 315 | + | // A seat nobody has scanned is played right here, hotseat-style. | |
| 316 | + | local: slot.id === null, | |
| 317 | + | remote: { | |
| 318 | + | qrUrl: seatLink(session!.room.link, seat), | |
| 319 | + | holder: slot.id !== null ? slot.name || '手機 phone' : null, | |
| 320 | + | }, | |
| 321 | + | }; | |
| 322 | + | } | |
| 323 | + | return { | |
| 324 | + | local: seat === mySeat, | |
| 325 | + | // A deserted human seat: the host's screen offers it to the computer. | |
| 326 | + | remote: | |
| 327 | + | slot.id === null && !ctl.state.bots[seat] && session!.isHost | |
| 328 | + | ? { holder: null, onHandToBot: () => session!.handToBot(seat) } | |
| 329 | + | : undefined, | |
| 330 | + | }; | |
| 331 | + | }; | |
| 332 | + | ||
| 178 | 333 | return ( | |
| 179 | 334 | <div className="table"> | |
| 180 | - | {SEATS.map((s) => ( | |
| 181 | - | <Seat | |
| 182 | - | key={s} | |
| 183 | - | game={game} | |
| 184 | - | seat={s} | |
| 185 | - | compact={compact} | |
| 186 | - | pool={compact ? undefined : pool} | |
| 187 | - | onHelp={() => setHelpSeat(s)} | |
| 188 | - | /> | |
| 189 | - | ))} | |
| 335 | + | {SEATS.map((s) => { | |
| 336 | + | const sp = seatProps(s); | |
| 337 | + | return ( | |
| 338 | + | <Seat | |
| 339 | + | key={s} | |
| 340 | + | game={ctl} | |
| 341 | + | seat={s} | |
| 342 | + | pos={posOf(s)} | |
| 343 | + | local={sp.local} | |
| 344 | + | remote={sp.remote} | |
| 345 | + | compact={compact} | |
| 346 | + | pool={compact ? undefined : pool} | |
| 347 | + | onHelp={() => setHelpSeat(posOf(s))} | |
| 348 | + | /> | |
| 349 | + | ); | |
| 350 | + | })} | |
| 190 | 351 | {/* The tiles in the middle. Not on a phone: the compact layout has no | |
| 191 | 352 | centre square for them to be thrown into. */} | |
| 192 | - | {!compact && <Pool game={game} pool={pool} />} | |
| 353 | + | {!compact && <Pool game={ctl} pool={pool} viewSeat={viewSeat} />} | |
| 193 | 354 | <Center | |
| 194 | - | game={game} | |
| 355 | + | game={ctl} | |
| 195 | 356 | prefs={prefs} | |
| 196 | 357 | compact={compact} | |
| 358 | + | viewSeat={viewSeat} | |
| 359 | + | preferSeat={netMode === 'online' ? mySeat : null} | |
| 197 | 360 | onPrefs={setPrefs} | |
| 198 | 361 | onSettings={() => setSettingsOpen(true)} | |
| 199 | 362 | /> | |
| ⋯ 15 unchanged lines | |||
addedsrc/assets/back.pngbinary
Binary file
addedsrc/assets/tiles.svg+563 −0
| 1 | + | <?xml version="1.0" encoding="utf-8"?> | |
| 2 | + | <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> | |
| 3 | + | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="5504" height="352" viewBox="0 0 2752 176" version="1.1"> | |
| 4 | + | <defs> | |
| 5 | + | <g id="bt1"> | |
| 6 | + | <path d="M9,0C8.47,0,7.96,0.21,7.59,0.59l-7,7C0.21,7.96,0,8.47,0,9v77c0,1.1,0.9,2,2,2h53c0.53,0,1.04-0.21,1.41-0.59l7-7C63.79,80.04,64,79.53,64,79V2c0-1.1-0.9-2-2-2H9z"/> | |
| 7 | + | <path fill="#9F9F9F" d="M11.75,78.25l-9,9h9H55c0.33,0,0.65-0.13,0.88-0.37l7-7c0.23-0.23,0.37-1.3,0.37-1.63H11.75z"/> | |
| 8 | + | <path fill="#D7D7D7" d="M8.12,1.12l-7,7C0.88,8.35,0.75,8.67,0.75,9v66.25v1v9l2,2l7-7v-4v-1V0.75C9.42,0.75,8.35,0.88,8.12,1.12z"/> | |
| 9 | + | <path fill="#BFBFBF" d="M1.61,86.89l10.14-10.14v-0.5L1.34,86.66C1.42,86.75,1.51,86.82,1.61,86.89z"/> | |
| 10 | + | <path fill="#B7B7B7" d="M1.93,87.07l9.82-9.82v-0.5L1.61,86.89C1.71,86.96,1.82,87.02,1.93,87.07z"/> | |
| 11 | + | <path fill="#AFAFAF" d="M2.3,87.2l9.45-9.45v-0.5l-9.82,9.82C2.05,87.12,2.17,87.17,2.3,87.2z"/> | |
| 12 | + | <path fill="#A7A7A7" d="M11.75,77.75L2.3,87.2c0.14,0.03,0.29,0.05,0.45,0.05l9-9V77.75z"/> | |
| 13 | + | <path fill="#C7C7C7" d="M1.34,86.66l10.41-10.41h-0.5L1.11,86.39C1.18,86.49,1.25,86.58,1.34,86.66z"/> | |
| 14 | + | <path fill="#CDCDCD" d="M1.11,86.39l10.14-10.14h-0.5l-9.82,9.82C0.98,86.18,1.04,86.29,1.11,86.39z"/> | |
| 15 | + | <path fill="#D2D2D2" d="M0.93,86.07l9.82-9.82h-0.5L0.8,85.7C0.83,85.83,0.88,85.95,0.93,86.07z"/> | |
| 16 | + | <path fill="#D7D7D7" d="M10.25,76.25h-0.5l-9,9c0,0.15,0.02,0.3,0.05,0.45L10.25,76.25z"/> | |
| 17 | + | <path fill="#F6F6F6" d="M63.25,78.25V2.75c0-1.1-0.9-2-2-2h-50.5c-1.1,0-2,0.9-2,2v75.5c0,1.1,0.9,2,2,2h50.5C62.35,80.25,63.25,79.35,63.25,78.25z"/> | |
| 18 | + | <path fill="#FFFFFF" d="M8.75,78.25V2.75c0-1.1,0.9-2,2-2h-1c-1.1,0-2,0.9-2,2v75.5c0,1.1,0.9,2,2,2h1C9.65,80.25,8.75,79.35,8.75,78.25z"/> | |
| 19 | + | </g> | |
| 20 | + | <g id="bt2"> | |
| 21 | + | <path fill="#0000FF" d="M9,0C8.47,0,7.96,0.21,7.59,0.59l-7,7C0.21,7.96,0,8.47,0,9v77c0,1.1,0.9,2,2,2h53c0.53,0,1.04-0.21,1.41-0.59l7-7C63.79,80.04,64,79.53,64,79V2c0-1.1-0.9-2-2-2H9z"/> | |
| 22 | + | <path fill="#8F8FFF" d="M11.75,78.25l-9,9h9H55c0.33,0,0.65-0.13,0.88-0.37l7-7c0.23-0.23,0.37-1.3,0.37-1.63H11.75z"/> | |
| 23 | + | <path fill="#C7C7FF" d="M8.12,1.12l-7,7C0.88,8.35,0.75,8.67,0.75,9v66.25v1v9l2,2l7-7v-4v-1V0.75C9.42,0.75,8.35,0.88,8.12,1.12z"/> | |
| 24 | + | <path fill="#AFAFFF" d="M1.61,86.89l10.14-10.14v-0.5L1.34,86.66C1.42,86.75,1.51,86.82,1.61,86.89z"/> | |
| 25 | + | <path fill="#A7A7FF" d="M1.93,87.07l9.82-9.82v-0.5L1.61,86.89C1.71,86.96,1.82,87.02,1.93,87.07z"/> | |
| 26 | + | <path fill="#9F9FFF" d="M2.3,87.2l9.45-9.45v-0.5l-9.82,9.82C2.05,87.12,2.17,87.17,2.3,87.2z"/> | |
| 27 | + | <path fill="#9797FF" d="M11.75,77.75L2.3,87.2c0.14,0.03,0.29,0.05,0.45,0.05l9-9V77.75z"/> | |
| 28 | + | <path fill="#B7B7FF" d="M1.34,86.66l10.41-10.41h-0.5L1.11,86.39C1.18,86.49,1.25,86.58,1.34,86.66z"/> | |
| 29 | + | <path fill="#BDBDFF" d="M1.11,86.39l10.14-10.14h-0.5l-9.82,9.82C0.98,86.18,1.04,86.29,1.11,86.39z"/> | |
| 30 | + | <path fill="#C2C2FF" d="M0.93,86.07l9.82-9.82h-0.5L0.8,85.7C0.83,85.83,0.88,85.95,0.93,86.07z"/> | |
| 31 | + | <path fill="#C7C7FF" d="M10.25,76.25h-0.5l-9,9c0,0.15,0.02,0.3,0.05,0.45L10.25,76.25z"/> | |
| 32 | + | <path fill="#E3E3FF" d="M61.25,0.75H9.75c-1.1,0-2,0.9-2,2v75.5c0,1.1,0.9,2,2,2h51.5c1.1,0,2-0.9,2-2V2.75C63.25,1.65,62.35,0.75,61.25,0.75z"/> | |
| 33 | + | <path fill="#FFFFFF" stroke="#0000FF" stroke-width="3" d="M60,74.19c0,1.55-1.22,2.81-2.72,2.81H13.72c-1.5,0-2.72-1.26-2.72-2.81V6.81C11,5.26,12.22,4,13.72,4h43.56C58.78,4,60,5.26,60,6.81V74.19z"/> | |
| 34 | + | </g> | |
| 35 | + | <path id="bams" fill="#006C00" d="M11,13.5c0-0.92-1.25-1.71-3.06-2.09V4.44C9.75,4.06,11,3.27,11,2.35C11,1.05,8.54,0,5.5,0S0,1.05,0,2.35c0,0.92,1.25,1.71,3.06,2.09v6.97C1.25,11.79,0,12.58,0,13.5c0,0.92,1.25,1.71,3.06,2.09v6.96C1.25,22.94,0,23.73,0,24.65C0,25.95,2.46,27,5.5,27s5.5-1.05,5.5-2.35c0-0.92-1.25-1.71-3.06-2.09v-6.96C9.75,15.21,11,14.42,11,13.5z M5.5,1.47c1.35,0,2.44,0.39,2.44,0.88c0,0.41-0.78,0.75-1.83,0.85v5.94c0,0.46-0.28,0.84-0.61,0.84S4.89,9.6,4.89,9.14V3.2C3.84,3.1,3.06,2.76,3.06,2.35C3.06,1.86,4.15,1.47,5.5,1.47z M5.5,25.53c-1.35,0-2.44-0.39-2.44-0.88c0-0.41,0.78-0.75,1.83-0.85v-5.94c0-0.46,0.28-0.84,0.61-0.84s0.61,0.38,0.61,0.84v5.94c1.05,0.1,1.83,0.44,1.83,0.85C7.94,25.14,6.85,25.53,5.5,25.53z M5.5,14.38c-1.86,0-3.36-0.39-3.36-0.88c0-0.49,1.5-0.88,3.36-0.88s3.36,0.39,3.36,0.88C8.86,13.99,7.36,14.38,5.5,14.38z"/> | |
| 36 | + | <path id="bams2" fill="#9C0000" d="M11,13.5c0-0.92-1.25-1.71-3.06-2.09V4.44C9.75,4.06,11,3.27,11,2.35C11,1.05,8.54,0,5.5,0S0,1.05,0,2.35c0,0.92,1.25,1.71,3.06,2.09v6.97C1.25,11.79,0,12.58,0,13.5c0,0.92,1.25,1.71,3.06,2.09v6.96C1.25,22.94,0,23.73,0,24.65C0,25.95,2.46,27,5.5,27s5.5-1.05,5.5-2.35c0-0.92-1.25-1.71-3.06-2.09v-6.96C9.75,15.21,11,14.42,11,13.5z M5.5,1.47c1.35,0,2.44,0.39,2.44,0.88c0,0.41-0.78,0.75-1.83,0.85v5.94c0,0.46-0.28,0.84-0.61,0.84S4.89,9.6,4.89,9.14V3.2C3.84,3.1,3.06,2.76,3.06,2.35C3.06,1.86,4.15,1.47,5.5,1.47z M5.5,25.53c-1.35,0-2.44-0.39-2.44-0.88c0-0.41,0.78-0.75,1.83-0.85v-5.94c0-0.46,0.28-0.84,0.61-0.84s0.61,0.38,0.61,0.84v5.94c1.05,0.1,1.83,0.44,1.83,0.85C7.94,25.14,6.85,25.53,5.5,25.53z M5.5,14.38c-1.86,0-3.36-0.39-3.36-0.88c0-0.49,1.5-0.88,3.36-0.88s3.36,0.39,3.36,0.88C8.86,13.99,7.36,14.38,5.5,14.38z"/> | |
| 37 | + | <g id="dots"> | |
| 38 | + | <path fill="#10106C" d="M9,1.42c-4.18,0-7.58,3.4-7.58,7.58s3.4,7.58,7.58,7.58c4.18,0,7.58-3.4,7.58-7.58S13.18,1.42,9,1.42zM9,15.11c-1.36,0-2.49-0.97-2.75-2.25c0.78,0.56,1.72,0.89,2.75,0.89c1.03,0,1.97-0.34,2.75-0.89C11.49,14.14,10.36,15.11,9,15.11z M9,2.89c1.36,0,2.49,0.97,2.75,2.25C10.97,4.59,10.03,4.25,9,4.25c-1.03,0-1.97,0.34-2.75,0.89C6.51,3.86,7.64,2.89,9,2.89z M12.75,9c0,0.57-0.14,1.11-0.37,1.59C12.04,9.92,11.51,9.37,10.85,9c0.66-0.37,1.19-0.92,1.53-1.59C12.61,7.89,12.75,8.43,12.75,9z M5.25,9c0-0.57,0.14-1.11,0.37-1.59C5.96,8.08,6.49,8.63,7.15,9c-0.66,0.37-1.19,0.92-1.53,1.59C5.39,10.11,5.25,9.57,5.25,9z M11.59,11.7c-1.02-0.27-1.82-1.1-2.03-2.14c1.04,0.21,1.87,1.01,2.14,2.03C11.66,11.63,11.63,11.66,11.59,11.7z M6.3,11.59c0.27-1.02,1.1-1.82,2.15-2.03c-0.21,1.04-1.01,1.87-2.03,2.14C6.37,11.66,6.34,11.63,6.3,11.59z M6.41,6.3c1.02,0.27,1.82,1.1,2.03,2.15C7.4,8.23,6.57,7.43,6.3,6.41C6.34,6.37,6.37,6.34,6.41,6.3z M11.7,6.41c-0.27,1.02-1.1,1.82-2.14,2.03c0.21-1.05,1.01-1.87,2.03-2.15C11.63,6.34,11.66,6.37,11.7,6.41z M9,7.15C8.63,6.49,8.08,5.96,7.41,5.62C7.89,5.39,8.43,5.25,9,5.25s1.11,0.14,1.59,0.37C9.92,5.96,9.37,6.49,9,7.15z M9,10.85c0.37,0.66,0.92,1.19,1.59,1.53c-0.49,0.23-1.02,0.37-1.59,0.37s-1.11-0.14-1.59-0.37C8.08,12.04,8.63,11.51,9,10.85z M5.14,11.75C3.86,11.49,2.89,10.36,2.89,9s0.97-2.49,2.25-2.75C4.59,7.03,4.25,7.97,4.25,9C4.25,10.03,4.59,10.97,5.14,11.75z M12.86,6.25c1.28,0.26,2.25,1.39,2.25,2.75s-0.97,2.49-2.25,2.75c0.56-0.78,0.89-1.72,0.89-2.75C13.75,7.97,13.41,7.03,12.86,6.25z M15.01,6.33c-0.59-0.6-1.38-0.99-2.25-1.09c-0.11-0.88-0.49-1.66-1.09-2.25C13.15,3.65,14.35,4.85,15.01,6.33z M6.33,2.99c-0.6,0.59-0.98,1.38-1.09,2.25C4.36,5.35,3.58,5.73,2.99,6.33C3.65,4.85,4.85,3.65,6.33,2.99z M2.99,11.67c0.59,0.6,1.38,0.99,2.25,1.09c0.11,0.88,0.49,1.66,1.09,2.25C4.85,14.35,3.65,13.15,2.99,11.67z M11.67,15.01c0.6-0.59,0.99-1.38,1.09-2.25c0.88-0.11,1.66-0.49,2.25-1.09C14.35,13.15,13.15,14.35,11.67,15.01z"/> | |
| 39 | + | <path fill="#10106C" d="M9,0C4.04,0,0,4.04,0,9c0,4.96,4.04,9,9,9c4.96,0,9-4.04,9-9C18,4.04,13.96,0,9,0z M9,17c-4.41,0-8-3.59-8-8s3.59-8,8-8s8,3.59,8,8S13.41,17,9,17z"/> | |
| 40 | + | </g> | |
| 41 | + | <g id="dots2"> | |
| 42 | + | <path fill="#9C0000" d="M9,1.42c-4.18,0-7.58,3.4-7.58,7.58s3.4,7.58,7.58,7.58c4.18,0,7.58-3.4,7.58-7.58S13.18,1.42,9,1.42zM9,15.11c-1.36,0-2.49-0.97-2.75-2.25c0.78,0.56,1.72,0.89,2.75,0.89c1.03,0,1.97-0.34,2.75-0.89C11.49,14.14,10.36,15.11,9,15.11z M9,2.89c1.36,0,2.49,0.97,2.75,2.25C10.97,4.59,10.03,4.25,9,4.25c-1.03,0-1.97,0.34-2.75,0.89C6.51,3.86,7.64,2.89,9,2.89z M12.75,9c0,0.57-0.14,1.11-0.37,1.59C12.04,9.92,11.51,9.37,10.85,9c0.66-0.37,1.19-0.92,1.53-1.59C12.61,7.89,12.75,8.43,12.75,9z M5.25,9c0-0.57,0.14-1.11,0.37-1.59C5.96,8.08,6.49,8.63,7.15,9c-0.66,0.37-1.19,0.92-1.53,1.59C5.39,10.11,5.25,9.57,5.25,9z M11.59,11.7c-1.02-0.27-1.82-1.1-2.03-2.14c1.04,0.21,1.87,1.01,2.14,2.03C11.66,11.63,11.63,11.66,11.59,11.7z M6.3,11.59c0.27-1.02,1.1-1.82,2.15-2.03c-0.21,1.04-1.01,1.87-2.03,2.14C6.37,11.66,6.34,11.63,6.3,11.59z M6.41,6.3c1.02,0.27,1.82,1.1,2.03,2.15C7.4,8.23,6.57,7.43,6.3,6.41C6.34,6.37,6.37,6.34,6.41,6.3z M11.7,6.41c-0.27,1.02-1.1,1.82-2.14,2.03c0.21-1.05,1.01-1.87,2.03-2.15C11.63,6.34,11.66,6.37,11.7,6.41z M9,7.15C8.63,6.49,8.08,5.96,7.41,5.62C7.89,5.39,8.43,5.25,9,5.25s1.11,0.14,1.59,0.37C9.92,5.96,9.37,6.49,9,7.15z M9,10.85c0.37,0.66,0.92,1.19,1.59,1.53c-0.49,0.23-1.02,0.37-1.59,0.37s-1.11-0.14-1.59-0.37C8.08,12.04,8.63,11.51,9,10.85z M5.14,11.75C3.86,11.49,2.89,10.36,2.89,9s0.97-2.49,2.25-2.75C4.59,7.03,4.25,7.97,4.25,9C4.25,10.03,4.59,10.97,5.14,11.75z M12.86,6.25c1.28,0.26,2.25,1.39,2.25,2.75s-0.97,2.49-2.25,2.75c0.56-0.78,0.89-1.72,0.89-2.75C13.75,7.97,13.41,7.03,12.86,6.25z M15.01,6.33c-0.59-0.6-1.38-0.99-2.25-1.09c-0.11-0.88-0.49-1.66-1.09-2.25C13.15,3.65,14.35,4.85,15.01,6.33z M6.33,2.99c-0.6,0.59-0.98,1.38-1.09,2.25C4.36,5.35,3.58,5.73,2.99,6.33C3.65,4.85,4.85,3.65,6.33,2.99z M2.99,11.67c0.59,0.6,1.38,0.99,2.25,1.09c0.11,0.88,0.49,1.66,1.09,2.25C4.85,14.35,3.65,13.15,2.99,11.67z M11.67,15.01c0.6-0.59,0.99-1.38,1.09-2.25c0.88-0.11,1.66-0.49,2.25-1.09C14.35,13.15,13.15,14.35,11.67,15.01z"/> | |
| 43 | + | <path fill="#9C0000" d="M9,0C4.04,0,0,4.04,0,9c0,4.96,4.04,9,9,9c4.96,0,9-4.04,9-9C18,4.04,13.96,0,9,0z M9,17c-4.41,0-8-3.59-8-8s3.59-8,8-8s8,3.59,8,8S13.41,17,9,17z"/> | |
| 44 | + | </g> | |
| 45 | + | <g id="nums"> | |
| 46 | + | <path fill="#BA0000" d="M18.01,0.14c0,0-4.39,3.33-4.78,3.63c-0.32-0.33-3.2-3.27-3.2-3.27l2.22,8.92l0.6-0.03c0.75-4.76,4.35-6.63,5.33-7.05c0.14,0.2,0.26,0.42,0.26,0.72c0,0.09-0.01,0.18-0.02,0.28c-0.21,1.67-2.39,4.64-6.68,7.21l-0.31,0.18l0.81,1.01l0.2-0.06c0.06-0.02,6.59-2.06,10.36-1.88l1.14,0.05l-0.96-0.63c-0.08-0.05-1.95-1.25-3.93-1.03c0.28-0.63,2.26-2.56,4.15-4.16l0.31-0.26L18.19,0L18.01,0.14z"/> | |
| 47 | + | <path fill="#BA0000" d="M33.78,24.64c-0.17-0.17-4.21-4.28-5.63-5.01c-0.53-0.27-1.71-0.18-5.75,0.16c0,0-0.17,0.01-0.43,0.04c1.19-2.79,2.72-5.86,3.57-6.25l0.55-0.25l-2.13-2.09l-0.21,0.03c-5.11,0.87-13.08,2.09-14.11,1.9l-0.35-0.07l-0.12,0.33c-0.2,0.56,0.32,4.93,0.86,7.37C5.45,21.2,0,21.66,0,21.66l0.3,0.56l0.68,1.27l0.32-0.09c1.72-0.51,2.52-0.57,4.6-0.54c0.19,0.49,2.6,6.77,2.6,6.77l0.79-0.2c0,0-0.69-5.69-0.8-6.52c0.78,0.02,7.99,0.22,9.67,0.26c-1.09,0.92-3.56,3.02-3.56,3.02l0.4,0.71c0,0,5.15-1.76,5.41-1.85c0.25,0.1,4.3,1.65,4.3,1.65s-2.13-2.57-2.81-3.38c3.78,0.2,4.75,0.42,5.01,0.74c0.33,0.39,0.5,0.96,0.5,1.6c0,1.16-0.58,2.57-1.8,3.73c-1.82-0.46-5.17-2.34-5.21-2.36l-0.52,0.62L24.96,34l0.24-0.05c3.82-0.78,8.44-8.48,8.63-8.81L34,24.87L33.78,24.64z M21.02,16.65c-1.22,0.16-2.77,0.37-3.21,0.43c0-0.33,0-1.17,0-2c1.62-0.13,3.11-0.25,3.49-0.29C21.25,15.06,21.15,15.77,21.02,16.65z M16.09,15.22c0.02,0.84,0.04,1.75,0.05,2.06c-1.41,0.16-2.6,0.26-3.55,0.3c0.1-0.62,0.19-1.31,0.3-2.1C13.13,15.46,14.51,15.35,16.09,15.22z M12.03,20.65c0.08-0.35,0.17-0.76,0.26-1.24c1.3-0.24,2.61-0.46,3.89-0.63c0.01,0.48,0.03,1.1,0.04,1.52C15.97,20.31,14.26,20.46,12.03,20.65zM17.8,20.17c0-0.48,0-1.18,0-1.6c0.98-0.1,1.36-0.08,1.96-0.02l0.96,0.06c-0.07,0.46-0.14,0.92-0.2,1.35C19.43,20.04,18.19,20.14,17.8,20.17z"/> | |
| 48 | + | </g> | |
| 49 | + | <g id="flower"> | |
| 50 | + | <rect x="1" y="1" fill="#F8F6D8" stroke="#F8F6D8" width="43" height="67"/> | |
| 51 | + | <path fill="#D3BB54" d="M11.27,58.16l-0.71-8.35c1.76-0.67,2.92-1.76,2.83-2.86c-0.09-1.1-1.42-1.91-3.26-2.16l-0.71-8.36c1.76-0.67,2.92-1.76,2.83-2.86c-0.13-1.56-2.69-2.54-5.71-2.19c-3.02,0.35-5.37,1.89-5.23,3.44c0.09,1.1,1.42,1.91,3.25,2.16l0.71,8.36c-1.76,0.67-2.92,1.76-2.83,2.86c0.09,1.1,1.42,1.91,3.25,2.16l0.71,8.36c-1.76,0.67-2.92,1.76-2.83,2.86c0.13,1.55,2.69,2.53,5.71,2.19c3.02-0.35,5.37-1.89,5.23-3.44C14.43,59.22,13.11,58.41,11.27,58.16z M4.34,34.47c-0.05-0.58,1-1.18,2.34-1.34c1.34-0.15,2.47,0.2,2.52,0.78c0.04,0.49-0.7,0.99-1.74,1.23l0.61,7.13c0.05,0.56-0.19,1.04-0.52,1.08c-0.34,0.04-0.65-0.38-0.7-0.94l-0.61-7.13C5.19,35.28,4.38,34.96,4.34,34.47z M4.57,47.95c-0.05-0.58,1.41-1.23,3.25-1.44c1.85-0.21,3.39,0.09,3.43,0.67c0.05,0.58-1.41,1.23-3.25,1.44C6.15,48.84,4.62,48.54,4.57,47.95z M9.15,62c-1.34,0.15-2.47-0.19-2.52-0.78c-0.04-0.49,0.7-0.99,1.74-1.23l-0.61-7.13c-0.05-0.56,0.19-1.04,0.52-1.08c0.33-0.04,0.65,0.38,0.69,0.94l0.61,7.13c1.06,0,1.87,0.32,1.91,0.81C11.54,61.25,10.49,61.85,9.15,62z"/> | |
| 52 | + | <path fill="none" stroke="#7C5A2D" stroke-width="0.25" d="M11.27,58.16l-0.71-8.35c1.76-0.67,2.92-1.76,2.83-2.86c-0.09-1.1-1.42-1.91-3.26-2.16l-0.71-8.36c1.76-0.67,2.92-1.76,2.83-2.86c-0.13-1.56-2.69-2.54-5.71-2.19c-3.02,0.35-5.37,1.89-5.23,3.44c0.09,1.1,1.42,1.91,3.25,2.16l0.71,8.36c-1.76,0.67-2.92,1.76-2.83,2.86c0.09,1.1,1.42,1.91,3.25,2.16l0.71,8.36c-1.76,0.67-2.92,1.76-2.83,2.86c0.13,1.55,2.69,2.53,5.71,2.19c3.02-0.35,5.37-1.89,5.23-3.44C14.43,59.22,13.11,58.41,11.27,58.16z M4.34,34.47c-0.05-0.58,1-1.18,2.34-1.34c1.34-0.15,2.47,0.2,2.52,0.78c0.04,0.49-0.7,0.99-1.74,1.23l0.61,7.13c0.05,0.56-0.19,1.04-0.52,1.08c-0.34,0.04-0.65-0.38-0.7-0.94l-0.61-7.13C5.19,35.28,4.38,34.96,4.34,34.47z M4.57,47.95c-0.05-0.58,1.41-1.23,3.25-1.44c1.85-0.21,3.39,0.09,3.43,0.67c0.05,0.58-1.41,1.23-3.25,1.44C6.15,48.84,4.62,48.54,4.57,47.95z M9.15,62c-1.34,0.15-2.47-0.19-2.52-0.78c-0.04-0.49,0.7-0.99,1.74-1.23l-0.61-7.13c-0.05-0.56,0.19-1.04,0.52-1.08c0.33-0.04,0.65,0.38,0.69,0.94l0.61,7.13c1.06,0,1.87,0.32,1.91,0.81C11.54,61.25,10.49,61.85,9.15,62z"/> | |
| 53 | + | <path fill="#EADDA7" stroke="#7C5A2D" stroke-width="0.25" d="M5.23,67.99c2.49-8.18-3.33-18.78-3.33-18.78s10.4,10.59,7.48,18.78H5.23z"/> | |
| 54 | + | <path fill="#DBC879" d="M19.02,36.17c-2.92-0.98-5.59-0.56-5.97,0.94c-0.27,1.06,0.7,2.38,2.33,3.41l-2.03,8.06c-1.85-0.14-3.28,0.37-3.54,1.43c-0.27,1.06,0.7,2.38,2.33,3.41l-2.03,8.06c-1.84-0.14-3.27,0.37-3.54,1.43c-0.38,1.5,1.68,3.51,4.6,4.5c2.92,0.98,5.59,0.56,5.96-0.94c0.27-1.06-0.7-2.38-2.32-3.41l2.03-8.06c1.85,0.14,3.28-0.37,3.55-1.43c0.27-1.06-0.71-2.38-2.33-3.41l2.03-8.06c1.84,0.14,3.28-0.37,3.54-1.43C23.99,39.16,21.94,37.15,19.02,36.17z M14.41,57.04l-1.73,6.88c0.98,0.45,1.63,1.1,1.51,1.58c-0.14,0.56-1.31,0.66-2.6,0.23c-1.3-0.44-2.23-1.25-2.09-1.81c0.12-0.47,0.97-0.62,2.01-0.39l1.73-6.88c0.13-0.54,0.51-0.88,0.83-0.77C14.39,55.98,14.54,56.5,14.41,57.04z M18.32,52.88c-0.14,0.56-1.7,0.53-3.48-0.07c-1.78-0.6-3.11-1.54-2.97-2.11c0.14-0.56,1.7-0.53,3.48,0.07C17.13,51.37,18.46,52.32,18.32,52.88z M20.68,39.67c-0.12,0.47-0.97,0.62-2.01,0.39l-1.73,6.88c-0.13,0.54-0.51,0.88-0.83,0.77c-0.32-0.11-0.48-0.63-0.34-1.17l1.73-6.88c-0.98-0.45-1.63-1.1-1.51-1.58c0.14-0.56,1.31-0.67,2.6-0.23C19.89,38.3,20.82,39.11,20.68,39.67z"/> | |
| 55 | + | <path fill="none" stroke="#7C5A2D" stroke-width="0.25" d="M19.02,36.17c-2.92-0.98-5.59-0.56-5.97,0.94c-0.27,1.06,0.7,2.38,2.33,3.41l-2.03,8.06c-1.85-0.14-3.28,0.37-3.54,1.43c-0.27,1.06,0.7,2.38,2.33,3.41l-2.03,8.06c-1.84-0.14-3.27,0.37-3.54,1.43c-0.38,1.5,1.68,3.51,4.6,4.5c2.92,0.98,5.59,0.56,5.96-0.94c0.27-1.06-0.7-2.38-2.32-3.41l2.03-8.06c1.85,0.14,3.28-0.37,3.55-1.43c0.27-1.06-0.71-2.38-2.33-3.41l2.03-8.06c1.84,0.14,3.28-0.37,3.54-1.43C23.99,39.16,21.94,37.15,19.02,36.17z M14.41,57.04l-1.73,6.88c0.98,0.45,1.63,1.1,1.51,1.58c-0.14,0.56-1.31,0.66-2.6,0.23c-1.3-0.44-2.23-1.25-2.09-1.81c0.12-0.47,0.97-0.62,2.01-0.39l1.73-6.88c0.13-0.54,0.51-0.88,0.83-0.77C14.39,55.98,14.54,56.5,14.41,57.04z M18.32,52.88c-0.14,0.56-1.7,0.53-3.48-0.07c-1.78-0.6-3.11-1.54-2.97-2.11c0.14-0.56,1.7-0.53,3.48,0.07C17.13,51.37,18.46,52.32,18.32,52.88z M20.68,39.67c-0.12,0.47-0.97,0.62-2.01,0.39l-1.73,6.88c-0.13,0.54-0.51,0.88-0.83,0.77c-0.32-0.11-0.48-0.63-0.34-1.17l1.73-6.88c-0.98-0.45-1.63-1.1-1.51-1.58c0.14-0.56,1.31-0.67,2.6-0.23C19.89,38.3,20.82,39.11,20.68,39.67z"/> | |
| 56 | + | <path fill="#EADA9B" stroke="#7C5A2D" stroke-width="0.25" d="M19.85,67.7c-1.77-5.79,2.35-13.28,2.35-13.28s-7.35,7.49-5.29,13.28H19.85z"/> | |
| 57 | + | <path fill="#BFAA52" stroke="#7C5A2D" d="M30.33,52.2c-10.89,0-12.13-1.84-15.85-5.97c4.7-3.44,10.9-3.9,16.34,4.82L30.33,52.2z"/> | |
| 58 | + | <path fill="#DBC775" d="M27.52,50.68c-6.89,0-7.68-1.16-10.03-3.78c2.98-2.18,6.9-2.47,10.34,3.05L27.52,50.68z"/> | |
| 59 | + | <path fill="#BFAA52" stroke="#7C5A2D" d="M42.27,47.51c-0.36,2.52-6.85,4.67-9.62,5.14c-2.77,0.47,1.52-5.63,6.18-9.5C41.78,42.64,42.5,45.94,42.27,47.51z"/> | |
| 60 | + | <path fill="#DBC775" d="M37.59,49.31c-0.15,1.08-2.95,2.01-4.14,2.21c-1.19,0.2,0.66-2.42,2.66-4.09C37.38,47.21,37.69,48.63,37.59,49.31z"/> | |
| 61 | + | <path fill="#E5D389" stroke="#7C5A2D" stroke-width="0.75" d="M38.25,67.92c-7.82-3.7-5.44-11.92-5.44-18.72l-2.23-0.29c0,0-2.8,17.48,4.83,18.76L38.25,67.92z"/> | |
| 62 | + | <path fill="#BFAA52" stroke="#7C5A2D" d="M43.33,52.91v7.75c0,0-2.47,2.07-4.21,2.07c-1.73,0-6.93-9.55-6.44-10.59C33.19,51.1,42.84,50.85,43.33,52.91z"/> | |
| 63 | + | <path fill="#DBC775" d="M40.06,53.5c1.02,0.72,0,3.43,0,3.43s-1.33,0.91-2.27,0.91c-0.93,0-3.73-4.22-3.47-4.68S39.04,52.79,40.06,53.5z"/> | |
| 64 | + | <path fill="#BFAA52" stroke="#7C5A2D" d="M31.45,52.39c-12.87-1.29-10.89,4.39-14.11,10.07C27.49,65.3,28.23,57.04,31.45,52.39z"/> | |
| 65 | + | <path fill="#E2CF7F" d="M30.26,53.1c-8.21-0.82-6.95,2.8-9,6.42C27.74,61.34,28.21,56.07,30.26,53.1z"/> | |
| 66 | + | <path fill="#BFAA52" stroke="#7C5A2D" d="M32.93,50.42c0,0,3.69-9.06-0.25-11.11c-4.04-2.11-6.64-1.29-7.13,0c-0.5,1.29,3.91,12.41,5.65,12.66C32.93,52.23,32.93,50.42,32.93,50.42z"/> | |
| 67 | + | <path fill="#DBC775" d="M32.14,50.27c0,0,2.05-5.67-0.14-6.95c-2.24-1.32-3.68-0.81-3.96,0c-0.28,0.81,2.17,7.76,3.13,7.92C32.14,51.4,32.14,50.27,32.14,50.27z"/> | |
| 68 | + | <path fill="#EADDA7" stroke="#7C5A2D" stroke-width="0.25" d="M31.45,51.12c0,0-1.48-2.54-1.23-4.87"/> | |
| 69 | + | <path fill="#BFAA52" stroke="#7C5A2D" d="M30.33,52.6c0,0-4.13,5.57-2.01,5.48c2.41-0.11,2.6,0,5.1,0.46c2.57,0.47,0.2-5.55-1.6-6.32L30.33,52.6z"/> | |
| 70 | + | <path fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" d="M27.27,48.13c0,0,2.02,2.93,3.35,2.93c1.33,0,0.61,2.18,0,1.56C30,51.99,28.15,50.19,27.05,50C26.23,50,26.04,47.66,27.27,48.13z"/> | |
| 71 | + | <path fill="#D3BB54" stroke="#AF9A41" d="M44,12.51c-4.46,0-8.54,2.85-8.54,6.37c0,3.52,4.08,6.37,8.54,6.37V12.51z"/> | |
| 72 | + | <path fill="#D3BB54" stroke="#AF9A41" d="M33.33,7.64c0-3.52-3.62-6.23-8.08-6.23c-4.46,0-8.08,2.71-8.08,6.23s3.62,6.37,8.08,6.37C29.71,14.01,33.33,11.16,33.33,7.64z"/> | |
| 73 | + | <ellipse fill="#D3BB54" stroke="#AF9A41" cx="29.56" cy="17.54" rx="8.08" ry="6.37"/> | |
| 74 | + | <path fill="#DBC879" stroke="#AF9A41" d="M44,16.35V1.42H30.9c-2.93,1.56-3.79,4.49-3.79,7.49c0,4.79,4.93,8.68,11.02,8.68C40.21,17.58,42.34,17.13,44,16.35z"/> | |
| 75 | + | <path fill="#D3BB54" stroke="#AF9A41" d="M31.94,15.47c0,0-0.72,1.82-3.03,1.82"/> | |
| 76 | + | <path fill="#D3BB54" stroke="#AF9A41" d="M27.19,6.62c0,0-1.73,0.57-3.17-1.02"/> | |
| 77 | + | <path fill="#663333" d="M44,0H0v69h45V0H44z M43,2c0,1.91,0,63.09,0,65c-1.86,0-39.14,0-41,0C2,65.09,2,3.91,2,2C3.86,2,41.14,2,43,2z"/> | |
| 78 | + | <path fill="#E2CF7F" d="M31.13,52.9c0,0-1.71,2.31-0.83,2.27c1-0.04,1.08,0,2.11,0.19c1.06,0.2,0.08-2.3-0.66-2.62L31.13,52.9z"/> | |
| 79 | + | </g> | |
| 80 | + | <g id="season"> | |
| 81 | + | <rect x="1" y="1" fill="#E3E4FF" width="43" height="67"/> | |
| 82 | + | <polygon fill="#CAE0FF" stroke="#99B9FF" points="4.69,42.38 9.02,39.76 10.2,44.33 6.26,48.26 8.62,49.89 10.2,48.26 10.98,51.52 14.91,51.19 14.52,47.93 17.27,49.24 18.85,47.28 14.13,44.01 13.34,39.44 19.24,41.4 20.81,45.97 23.56,44.99 22.77,42.7 26.71,44.33 28.28,41.07 23.96,39.44 27.11,38.45 25.92,36.17 20.42,37.8 14.91,35.84 20.03,32.9 25.92,33.88 26.31,31.27 23.17,30.94 26.31,28.98 23.96,26.37 20.81,28 20.81,26.04 18.06,25.71 17.27,30.61 12.55,33.23 11.77,28.66 15.7,24.41 12.95,23.1 11.38,25.06 10.58,21.47 7.05,22.12 7.45,25.39 4.69,24.08 3.12,25.71 7.45,28.66 8.23,33.55 3.12,31.92 1,27.81 1,34.92 1.16,34.86 6.26,36.82 1.55,39.76 1,39.66 1,45.06 1.16,44.99 1,45.91 1,47.31 3.51,47.6"/> | |
| 83 | + | <path fill="#99B9FF" stroke="#678ED3" d="M40.63,26.67c1.1,0,2.17-0.13,3.21-0.35V1.5h-14.3c-2.44,2.67-3.95,6.22-3.95,10.13C25.59,19.94,32.32,26.67,40.63,26.67z"/> | |
| 84 | + | <polygon fill="#C9D3F2" stroke="#9EB3E8" stroke-width="0.5" points="22.61,14.9 22.3,14.59 20.96,16.9 23.27,18.23 23.39,17.81"/> | |
| 85 | + | <polygon fill="#C9D3F2" stroke="#9EB3E8" stroke-width="0.5" points="34.45,28.87 34.03,28.98 35.36,31.29 37.67,29.96 37.35,29.65"/> | |
| 86 | + | <polygon fill="#798CD8" points="43.83,30.1 43.83,29.71 43.59,29.96"/> | |
| 87 | + | <polygon fill="#C9D3F2" stroke="#9EB3E8" stroke-width="0.5" points="23.39,5.45 23.27,5.03 20.96,6.36 22.3,8.67 22.61,8.36"/> | |
| 88 | + | <polygon fill="#99B9FF" stroke="#678ED3" points="23.31,15.6 23.31,7.66 22.61,8.36 22.3,8.67 19.34,11.63 22.3,14.59 22.61,14.9"/> | |
| 89 | + | <polygon fill="#C9D3F2" stroke="#9EB3E8" stroke-width="0.5" points="26.66,23.47 26.23,23.36 26.23,26.02 28.9,26.02 28.78,25.6"/> | |
| 90 | + | <polygon fill="#99B9FF" stroke="#678ED3" points="37.35,29.65 37.67,29.96 40.63,32.92 43.59,29.96 43.83,29.71 43.83,28.95 36.66,28.95"/> | |
| 91 | + | <polygon fill="#99B9FF" stroke="#678ED3" points="35.41,28.61 28.53,24.64 28.78,25.6 28.9,26.02 29.98,30.07 34.03,28.98 34.45,28.87"/> | |
| 92 | + | <polygon fill="#99B9FF" stroke="#678ED3" points="27.61,23.73 23.64,16.85 23.39,17.81 23.27,18.23 22.19,22.27 26.23,23.36 26.66,23.47"/> | |
| 93 | + | <polygon fill="#99B9FF" stroke="#678ED3" points="23.39,5.45 23.64,6.41 26.48,1.5 22.33,1.5 23.27,5.03 "/> | |
| 94 | + | <path fill="#99B9FF" stroke="#678ED3" d="M30.67,46.67c0,0,2.32-3,7-3c5.67,0,5.67,9.33,5.67,9.33H19.67c0,0-0.33-6.67,7.67-6.67c3.67,0,6,4,6,4"/> | |
| 95 | + | <line fill="#99B9FF" stroke="#678ED3" x1="29.5" y1="57.27" x2="24.83" y2="65.36"/> | |
| 96 | + | <line fill="#99B9FF" stroke="#678ED3" x1="34.22" y1="55.68" x2="28.68" y2="65.95"/> | |
| 97 | + | <line fill="#99B9FF" stroke="#678ED3" x1="23.33" y1="61" x2="20.5" y2="65.91"/> | |
| 98 | + | <line fill="#99B9FF" stroke="#678ED3" x1="37.46" y1="54.63" x2="33.25" y2="62.59"/> | |
| 99 | + | <line fill="#99B9FF" stroke="#678ED3" x1="40.46" y1="62.17" x2="38.13" y2="66.21"/> | |
| 100 | + | <line fill="#99B9FF" stroke="#678ED3" x1="27.11" y1="54.52" x2="24.44" y2="59.14"/> | |
| 101 | + | <line fill="#99B9FF" stroke="#678ED3" x1="31.67" y1="54" x2="30.67" y2="55.73"/> | |
| 102 | + | <line fill="#99B9FF" stroke="#678ED3" x1="40.98" y1="55.6" x2="37.81" y2="61.09"/> | |
| 103 | + | <line fill="#99B9FF" stroke="#678ED3" x1="23.84" y1="55.04" x2="22.18" y2="57.92"/> | |
| 104 | + | <line fill="#99B9FF" stroke="#678ED3" x1="21.13" y1="55.63" x2="16.91" y2="63.59"/> | |
| 105 | + | <line fill="#99B9FF" stroke="#678ED3" x1="36" y1="63.6" x2="35" y2="65.33"/> | |
| 106 | + | <path fill="#678ED3" d="M44,0H0v69h45V0H44z M43,2c0,1.91,0,63.09,0,65c-1.86,0-39.14,0-41,0C2,65.09,2,3.91,2,2C3.86,2,41.14,2,43,2z"/> | |
| 107 | + | </g> | |
| 108 | + | <!-- 一索 is a bird, not a stick: a sparrow on a perch, drawn line-art in | |
| 109 | + | the sheet's own green, red and blue. Origin is the glyph's top-left; it | |
| 110 | + | occupies a 44x56 box that centers in the tile face. --> | |
| 111 | + | <g id="yitiao" fill="none" stroke-linecap="round" stroke-linejoin="round"> | |
| 112 | + | <!-- perch, with a knob at each end --> | |
| 113 | + | <g stroke="#006C00" fill="#006C00"> | |
| 114 | + | <path stroke-width="1.4" d="M4.5,35H39.5"/> | |
| 115 | + | <circle cx="3.6" cy="35" r="1.9"/> | |
| 116 | + | <circle cx="40.4" cy="35" r="1.9"/> | |
| 117 | + | </g> | |
| 118 | + | <!-- tail plumes --> | |
| 119 | + | <g stroke="#006C00" stroke-width="0.9"> | |
| 120 | + | <path d="M19.6,30.5C16,37,14.2,44,14,51.5"/> | |
| 121 | + | <path d="M20.8,31C18,38,17,45.5,17.5,53.5"/> | |
| 122 | + | <path d="M22,31.4C20.2,38.5,20.2,46.5,21.5,55"/> | |
| 123 | + | <path d="M23.2,31.4C22.6,38.5,23.6,46.5,25.8,55.5"/> | |
| 124 | + | <path d="M24.3,31C24.8,38,26.4,45.5,29,54"/> | |
| 125 | + | <path d="M25.2,30.3C26.4,36.5,28.6,43.5,31.6,51"/> | |
| 126 | + | </g> | |
| 127 | + | <!-- far wing --> | |
| 128 | + | <path stroke="#294D8E" stroke-width="1.2" d="M25.5,19.5C30,20.5,34.8,24.5,38.8,31C34.5,29.2,30,28.4,27,28.7"/> | |
| 129 | + | <!-- long flight feather, sweeping past the perch --> | |
| 130 | + | <path stroke="#294D8E" stroke-width="1.2" d="M23,23.5C17.5,27.5,10.8,34.5,5,44.5C11.5,38.5,17.8,32.5,22,29"/> | |
| 131 | + | <!-- near wing, folded over the breast --> | |
| 132 | + | <path stroke="#294D8E" stroke-width="1.2" d="M24,17.8C17.4,18.6,10.6,23.4,6.2,31.2C12,27.4,17.6,25.6,21.4,26.2C22.8,23.4,23.6,20.6,24,17.8Z"/> | |
| 133 | + | <g stroke="#294D8E" stroke-width="0.6"> | |
| 134 | + | <path d="M19,19.6C18.2,21.8,17.4,24.2,16.6,26.4"/> | |
| 135 | + | <path d="M16.6,21C15.8,23.2,14.8,25.4,14,27.4"/> | |
| 136 | + | <path d="M14.2,22.6C13.4,24.6,12.4,26.6,11.6,28.6"/> | |
| 137 | + | <path d="M11.8,24.6C11,26.4,10.2,28.2,9.4,29.8"/> | |
| 138 | + | </g> | |
| 139 | + | <!-- head, beak, back and breast --> | |
| 140 | + | <g stroke="#9C0000" stroke-width="1.3"> | |
| 141 | + | <path d="M20,8.6C17.2,7.8,14.4,9.4,13.2,11.8L7.4,13.9L13.2,15.9C12.6,17.6,12.4,19.4,12.7,21"/> | |
| 142 | + | <path d="M20.8,9.2C24.4,12,26.4,16.2,26.8,21.4C27.2,26.4,26.2,30,24.4,32"/> | |
| 143 | + | <path d="M13.4,17.2C14.2,22.4,16.4,27.4,19.8,31.2"/> | |
| 144 | + | </g> | |
| 145 | + | <g stroke="#9C0000" stroke-width="0.7"> | |
| 146 | + | <path d="M15.4,18.4C16,22,17.2,25.4,19,28.6"/> | |
| 147 | + | <path d="M18.4,19.2C18.8,22.8,19.8,26.2,21.4,29.6"/> | |
| 148 | + | <path d="M21.6,20C21.8,23.6,22.6,26.8,24,30.2"/> | |
| 149 | + | </g> | |
| 150 | + | <!-- swept-back crest --> | |
| 151 | + | <g stroke="#006C00" stroke-width="1.2"> | |
| 152 | + | <path d="M13.8,10.6C13.2,5.6,16.2,2.4,20.2,2.8C23.4,3.2,24.4,5.4,22.4,6.8"/> | |
| 153 | + | <path d="M16.6,8.8C17.8,7.2,20,6.2,22.4,6.8"/> | |
| 154 | + | </g> | |
| 155 | + | <path stroke="#9C0000" stroke-width="0.7" d="M8.8,14.6L13,14.2"/> | |
| 156 | + | <circle fill="#9C0000" stroke="none" cx="16.8" cy="11.9" r="1.4"/> | |
| 157 | + | </g> | |
| 158 | + | </defs> | |
| 159 | + | <use x="0" y="0" xlink:href="#bt1"/> | |
| 160 | + | <use x="64" y="0" xlink:href="#bt1"/> | |
| 161 | + | <use x="128" y="0" xlink:href="#bt1"/> | |
| 162 | + | <use x="192" y="0" xlink:href="#bt1"/> | |
| 163 | + | <use x="256" y="0" xlink:href="#bt1"/> | |
| 164 | + | <use x="320" y="0" xlink:href="#bt1"/> | |
| 165 | + | <use x="384" y="0" xlink:href="#bt1"/> | |
| 166 | + | <use x="448" y="0" xlink:href="#bt1"/> | |
| 167 | + | <use x="512" y="0" xlink:href="#bt1"/> | |
| 168 | + | <use x="576" y="0" xlink:href="#bt1"/> | |
| 169 | + | <use x="640" y="0" xlink:href="#bt1"/> | |
| 170 | + | <use x="704" y="0" xlink:href="#bt1"/> | |
| 171 | + | <use x="768" y="0" xlink:href="#bt1"/> | |
| 172 | + | <use x="832" y="0" xlink:href="#bt1"/> | |
| 173 | + | <use x="896" y="0" xlink:href="#bt1"/> | |
| 174 | + | <use x="960" y="0" xlink:href="#bt1"/> | |
| 175 | + | <use x="1024" y="0" xlink:href="#bt1"/> | |
| 176 | + | <use x="1088" y="0" xlink:href="#bt1"/> | |
| 177 | + | <use x="1152" y="0" xlink:href="#bt1"/> | |
| 178 | + | <use x="1216" y="0" xlink:href="#bt1"/> | |
| 179 | + | <use x="1280" y="0" xlink:href="#bt1"/> | |
| 180 | + | <use x="1344" y="0" xlink:href="#bt1"/> | |
| 181 | + | <use x="1408" y="0" xlink:href="#bt1"/> | |
| 182 | + | <use x="1472" y="0" xlink:href="#bt1"/> | |
| 183 | + | <use x="1536" y="0" xlink:href="#bt1"/> | |
| 184 | + | <use x="1600" y="0" xlink:href="#bt1"/> | |
| 185 | + | <use x="1664" y="0" xlink:href="#bt1"/> | |
| 186 | + | <use x="1728" y="0" xlink:href="#bt1"/> | |
| 187 | + | <use x="1792" y="0" xlink:href="#bt1"/> | |
| 188 | + | <use x="1856" y="0" xlink:href="#bt1"/> | |
| 189 | + | <use x="1920" y="0" xlink:href="#bt1"/> | |
| 190 | + | <use x="1984" y="0" xlink:href="#bt1"/> | |
| 191 | + | <use x="2048" y="0" xlink:href="#bt1"/> | |
| 192 | + | <use x="2112" y="0" xlink:href="#bt1"/> | |
| 193 | + | <use x="2176" y="0" xlink:href="#bt1"/> | |
| 194 | + | <use x="2240" y="0" xlink:href="#bt1"/> | |
| 195 | + | <use x="2304" y="0" xlink:href="#bt1"/> | |
| 196 | + | <use x="2368" y="0" xlink:href="#bt1"/> | |
| 197 | + | <use x="2432" y="0" xlink:href="#bt1"/> | |
| 198 | + | <use x="2496" y="0" xlink:href="#bt1"/> | |
| 199 | + | <use x="2560" y="0" xlink:href="#bt1"/> | |
| 200 | + | <use x="2624" y="0" xlink:href="#bt1"/> | |
| 201 | + | <use x="2688" y="0" xlink:href="#bt1"/> | |
| 202 | + | <use x="0" y="88" xlink:href="#bt2"/> | |
| 203 | + | <use x="64" y="88" xlink:href="#bt2"/> | |
| 204 | + | <use x="128" y="88" xlink:href="#bt2"/> | |
| 205 | + | <use x="192" y="88" xlink:href="#bt2"/> | |
| 206 | + | <use x="256" y="88" xlink:href="#bt2"/> | |
| 207 | + | <use x="320" y="88" xlink:href="#bt2"/> | |
| 208 | + | <use x="384" y="88" xlink:href="#bt2"/> | |
| 209 | + | <use x="448" y="88" xlink:href="#bt2"/> | |
| 210 | + | <use x="512" y="88" xlink:href="#bt2"/> | |
| 211 | + | <use x="576" y="88" xlink:href="#bt2"/> | |
| 212 | + | <use x="640" y="88" xlink:href="#bt2"/> | |
| 213 | + | <use x="704" y="88" xlink:href="#bt2"/> | |
| 214 | + | <use x="768" y="88" xlink:href="#bt2"/> | |
| 215 | + | <use x="832" y="88" xlink:href="#bt2"/> | |
| 216 | + | <use x="896" y="88" xlink:href="#bt2"/> | |
| 217 | + | <use x="960" y="88" xlink:href="#bt2"/> | |
| 218 | + | <use x="1024" y="88" xlink:href="#bt2"/> | |
| 219 | + | <use x="1088" y="88" xlink:href="#bt2"/> | |
| 220 | + | <use x="1152" y="88" xlink:href="#bt2"/> | |
| 221 | + | <use x="1216" y="88" xlink:href="#bt2"/> | |
| 222 | + | <use x="1280" y="88" xlink:href="#bt2"/> | |
| 223 | + | <use x="1344" y="88" xlink:href="#bt2"/> | |
| 224 | + | <use x="1408" y="88" xlink:href="#bt2"/> | |
| 225 | + | <use x="1472" y="88" xlink:href="#bt2"/> | |
| 226 | + | <use x="1536" y="88" xlink:href="#bt2"/> | |
| 227 | + | <use x="1600" y="88" xlink:href="#bt2"/> | |
| 228 | + | <use x="1664" y="88" xlink:href="#bt2"/> | |
| 229 | + | <use x="1728" y="88" xlink:href="#bt2"/> | |
| 230 | + | <use x="1792" y="88" xlink:href="#bt2"/> | |
| 231 | + | <use x="1856" y="88" xlink:href="#bt2"/> | |
| 232 | + | <use x="1920" y="88" xlink:href="#bt2"/> | |
| 233 | + | <use x="1984" y="88" xlink:href="#bt2"/> | |
| 234 | + | <use x="2048" y="88" xlink:href="#bt2"/> | |
| 235 | + | <use x="2112" y="88" xlink:href="#bt2"/> | |
| 236 | + | <use x="2176" y="88" xlink:href="#bt2"/> | |
| 237 | + | <use x="2240" y="88" xlink:href="#bt2"/> | |
| 238 | + | <use x="2304" y="88" xlink:href="#bt2"/> | |
| 239 | + | <use x="2368" y="88" xlink:href="#bt2"/> | |
| 240 | + | <use x="2432" y="88" xlink:href="#bt2"/> | |
| 241 | + | <use x="2496" y="88" xlink:href="#bt2"/> | |
| 242 | + | <use x="2560" y="88" xlink:href="#bt2"/> | |
| 243 | + | <use x="2624" y="88" xlink:href="#bt2"/> | |
| 244 | + | <use x="2688" y="88" xlink:href="#bt2"/> | |
| 245 | + | <use x="979" y="41" xlink:href="#nums"/> | |
| 246 | + | <use x="1043" y="41" xlink:href="#nums"/> | |
| 247 | + | <use x="1107" y="41" xlink:href="#nums"/> | |
| 248 | + | <use x="1171" y="41" xlink:href="#nums"/> | |
| 249 | + | <use x="1235" y="41" xlink:href="#nums"/> | |
| 250 | + | <use x="1299" y="41" xlink:href="#nums"/> | |
| 251 | + | <use x="1363" y="41" xlink:href="#nums"/> | |
| 252 | + | <use x="1427" y="41" xlink:href="#nums"/> | |
| 253 | + | <use x="1491" y="41" xlink:href="#nums"/> | |
| 254 | + | <use x="979" y="129" xlink:href="#nums"/> | |
| 255 | + | <use x="1043" y="129" xlink:href="#nums"/> | |
| 256 | + | <use x="1107" y="129" xlink:href="#nums"/> | |
| 257 | + | <use x="1171" y="129" xlink:href="#nums"/> | |
| 258 | + | <use x="1235" y="129" xlink:href="#nums"/> | |
| 259 | + | <use x="1299" y="129" xlink:href="#nums"/> | |
| 260 | + | <use x="1363" y="129" xlink:href="#nums"/> | |
| 261 | + | <use x="1427" y="129" xlink:href="#nums"/> | |
| 262 | + | <use x="1491" y="129" xlink:href="#nums"/> | |
| 263 | + | <use x="2125" y="6" xlink:href="#season"/> | |
| 264 | + | <use x="2189" y="6" xlink:href="#season"/> | |
| 265 | + | <use x="2253" y="6" xlink:href="#season"/> | |
| 266 | + | <use x="2317" y="6" xlink:href="#season"/> | |
| 267 | + | <use x="2125" y="94" xlink:href="#season"/> | |
| 268 | + | <use x="2189" y="94" xlink:href="#season"/> | |
| 269 | + | <use x="2253" y="94" xlink:href="#season"/> | |
| 270 | + | <use x="2317" y="94" xlink:href="#season"/> | |
| 271 | + | <use x="2445" y="6" xlink:href="#flower"/> | |
| 272 | + | <use x="2509" y="6" xlink:href="#flower"/> | |
| 273 | + | <use x="2573" y="6" xlink:href="#flower"/> | |
| 274 | + | <use x="2637" y="6" xlink:href="#flower"/> | |
| 275 | + | <use x="2445" y="94" xlink:href="#flower"/> | |
| 276 | + | <use x="2509" y="94" xlink:href="#flower"/> | |
| 277 | + | <use x="2573" y="94" xlink:href="#flower"/> | |
| 278 | + | <use x="2637" y="94" xlink:href="#flower"/> | |
| 279 | + | <use transform="translate(88.5,16.5) scale(1.22)" xlink:href="#dots"/> | |
| 280 | + | <use transform="translate(88.5,42.5) scale(1.22)" xlink:href="#dots"/> | |
| 281 | + | <use transform="translate(88.5,104.5) scale(1.22)" xlink:href="#dots"/> | |
| 282 | + | <use transform="translate(88.5,130.5) scale(1.22)" xlink:href="#dots"/> | |
| 283 | + | <use transform="translate(144.5,12.5)" xlink:href="#dots"/> | |
| 284 | + | <use transform="translate(144.5,100.5)" xlink:href="#dots"/> | |
| 285 | + | <use transform="translate(154.5,31.5)" xlink:href="#dots2"/> | |
| 286 | + | <use transform="translate(154.5,119.5)" xlink:href="#dots2"/> | |
| 287 | + | <use transform="translate(164.5,50.5)" xlink:href="#dots"/> | |
| 288 | + | <use transform="translate(164.5,138.5)" xlink:href="#dots"/> | |
| 289 | + | <use transform="translate(209,15) scale(.94)" xlink:href="#dots"/> | |
| 290 | + | <use transform="translate(209,49) scale(.94)" xlink:href="#dots"/> | |
| 291 | + | <use transform="translate(209,103) scale(.94)" xlink:href="#dots"/> | |
| 292 | + | <use transform="translate(209,137) scale(.94)" xlink:href="#dots"/> | |
| 293 | + | <use transform="translate(229,15) scale(.94)" xlink:href="#dots"/> | |
| 294 | + | <use transform="translate(229,49) scale(.94)" xlink:href="#dots"/> | |
| 295 | + | <use transform="translate(229,103) scale(.94)" xlink:href="#dots"/> | |
| 296 | + | <use transform="translate(229,137) scale(.94)" xlink:href="#dots"/> | |
| 297 | + | <use transform="translate(273.5,13.5) scale(.88)" xlink:href="#dots"/> | |
| 298 | + | <use transform="translate(273.5,51.5) scale(.88)" xlink:href="#dots"/> | |
| 299 | + | <use transform="translate(273.5,101.5) scale(.88)" xlink:href="#dots"/> | |
| 300 | + | <use transform="translate(273.5,139.5) scale(.88)" xlink:href="#dots"/> | |
| 301 | + | <use transform="translate(283.5,32.5) scale(.88)" xlink:href="#dots2"/> | |
| 302 | + | <use transform="translate(283.5,120.5) scale(.88)" xlink:href="#dots2"/> | |
| 303 | + | <use transform="translate(293.5,13.5) scale(.88)" xlink:href="#dots"/> | |
| 304 | + | <use transform="translate(293.5,51.5) scale(.88)" xlink:href="#dots"/> | |
| 305 | + | <use transform="translate(293.5,101.5) scale(.88)" xlink:href="#dots"/> | |
| 306 | + | <use transform="translate(293.5,139.5) scale(.88)" xlink:href="#dots"/> | |
| 307 | + | <use transform="translate(340,14) scale(.72)" xlink:href="#dots"/> | |
| 308 | + | <use transform="translate(340,37) scale(.72)" xlink:href="#dots2"/> | |
| 309 | + | <use transform="translate(340,54) scale(.72)" xlink:href="#dots2"/> | |
| 310 | + | <use transform="translate(340,102) scale(.72)" xlink:href="#dots"/> | |
| 311 | + | <use transform="translate(340,125) scale(.72)" xlink:href="#dots2"/> | |
| 312 | + | <use transform="translate(340,142) scale(.72)" xlink:href="#dots2"/> | |
| 313 | + | <use transform="translate(358,14) scale(.72)" xlink:href="#dots"/> | |
| 314 | + | <use transform="translate(358,37) scale(.72)" xlink:href="#dots2"/> | |
| 315 | + | <use transform="translate(358,54) scale(.72)" xlink:href="#dots2"/> | |
| 316 | + | <use transform="translate(358,102) scale(.72)" xlink:href="#dots"/> | |
| 317 | + | <use transform="translate(358,125) scale(.72)" xlink:href="#dots2"/> | |
| 318 | + | <use transform="translate(358,142) scale(.72)" xlink:href="#dots2"/> | |
| 319 | + | <use transform="translate(399,10) scale(.72)" xlink:href="#dots"/> | |
| 320 | + | <use transform="translate(404,41) scale(.72)" xlink:href="#dots2"/> | |
| 321 | + | <use transform="translate(404,58) scale(.72)" xlink:href="#dots2"/> | |
| 322 | + | <use transform="translate(413,17) scale(.72)" xlink:href="#dots"/> | |
| 323 | + | <use transform="translate(427,24) scale(.72)" xlink:href="#dots"/> | |
| 324 | + | <use transform="translate(404,129) scale(.72)" xlink:href="#dots2"/> | |
| 325 | + | <use transform="translate(404,146) scale(.72)" xlink:href="#dots2"/> | |
| 326 | + | <use transform="translate(399,98) scale(.72)" xlink:href="#dots"/> | |
| 327 | + | <use transform="translate(422,41) scale(.72)" xlink:href="#dots2"/> | |
| 328 | + | <use transform="translate(422,58) scale(.72)" xlink:href="#dots2"/> | |
| 329 | + | <use transform="translate(413,105) scale(.72)" xlink:href="#dots"/> | |
| 330 | + | <use transform="translate(427,112) scale(.72)" xlink:href="#dots"/> | |
| 331 | + | <use transform="translate(422,129) scale(.72)" xlink:href="#dots2"/> | |
| 332 | + | <use transform="translate(422,146) scale(.72)" xlink:href="#dots2"/> | |
| 333 | + | <use transform="translate(469,10) scale(.72)" xlink:href="#dots"/> | |
| 334 | + | <use transform="translate(469,26) scale(.72)" xlink:href="#dots"/> | |
| 335 | + | <use transform="translate(469,42) scale(.72)" xlink:href="#dots"/> | |
| 336 | + | <use transform="translate(469,58) scale(.72)" xlink:href="#dots"/> | |
| 337 | + | <use transform="translate(469,98) scale(.72)" xlink:href="#dots"/> | |
| 338 | + | <use transform="translate(469,114) scale(.72)" xlink:href="#dots"/> | |
| 339 | + | <use transform="translate(469,130) scale(.72)" xlink:href="#dots"/> | |
| 340 | + | <use transform="translate(469,146) scale(.72)" xlink:href="#dots"/> | |
| 341 | + | <use transform="translate(485,10) scale(.72)" xlink:href="#dots"/> | |
| 342 | + | <use transform="translate(485,26) scale(.72)" xlink:href="#dots"/> | |
| 343 | + | <use transform="translate(485,42) scale(.72)" xlink:href="#dots"/> | |
| 344 | + | <use transform="translate(485,58) scale(.72)" xlink:href="#dots"/> | |
| 345 | + | <use transform="translate(485,98) scale(.72)" xlink:href="#dots"/> | |
| 346 | + | <use transform="translate(485,114) scale(.72)" xlink:href="#dots"/> | |
| 347 | + | <use transform="translate(485,130) scale(.72)" xlink:href="#dots"/> | |
| 348 | + | <use transform="translate(485,146) scale(.72)" xlink:href="#dots"/> | |
| 349 | + | <use transform="translate(526,14) scale(.72)" xlink:href="#dots"/> | |
| 350 | + | <use transform="translate(526,34) scale(.72)" xlink:href="#dots2"/> | |
| 351 | + | <use transform="translate(526,54) scale(.72)" xlink:href="#dots"/> | |
| 352 | + | <use transform="translate(526,102) scale(.72)" xlink:href="#dots"/> | |
| 353 | + | <use transform="translate(526,122) scale(.72)" xlink:href="#dots2"/> | |
| 354 | + | <use transform="translate(526,142) scale(.72)" xlink:href="#dots"/> | |
| 355 | + | <use transform="translate(541,14) scale(.72)" xlink:href="#dots"/> | |
| 356 | + | <use transform="translate(541,34) scale(.72)" xlink:href="#dots2"/> | |
| 357 | + | <use transform="translate(541,54) scale(.72)" xlink:href="#dots"/> | |
| 358 | + | <use transform="translate(541,102) scale(.72)" xlink:href="#dots"/> | |
| 359 | + | <use transform="translate(541,122) scale(.72)" xlink:href="#dots2"/> | |
| 360 | + | <use transform="translate(541,142) scale(.72)" xlink:href="#dots"/> | |
| 361 | + | <use transform="translate(556,14) scale(.72)" xlink:href="#dots"/> | |
| 362 | + | <use transform="translate(556,34) scale(.72)" xlink:href="#dots2"/> | |
| 363 | + | <use transform="translate(556,54) scale(.72)" xlink:href="#dots"/> | |
| 364 | + | <use transform="translate(556,102) scale(.72)" xlink:href="#dots"/> | |
| 365 | + | <use transform="translate(556,122) scale(.72)" xlink:href="#dots2"/> | |
| 366 | + | <use transform="translate(556,142) scale(.72)" xlink:href="#dots"/> | |
| 367 | + | <use transform="translate(1550.5,11.9)" xlink:href="#yitiao"/> | |
| 368 | + | <use transform="translate(1550.5,99.9)" xlink:href="#yitiao"/> | |
| 369 | + | <use transform="translate(1630,9.5)" xlink:href="#bams"/> | |
| 370 | + | <use transform="translate(1630,44.5)" xlink:href="#bams"/> | |
| 371 | + | <use transform="translate(1630,97.5)" xlink:href="#bams"/> | |
| 372 | + | <use transform="translate(1630,132.5)" xlink:href="#bams"/> | |
| 373 | + | <use transform="translate(1682,44.5)" xlink:href="#bams"/> | |
| 374 | + | <use transform="translate(1682,132.5)" xlink:href="#bams"/> | |
| 375 | + | <use transform="translate(1694,9.5)" xlink:href="#bams"/> | |
| 376 | + | <use transform="translate(1694,97.5)" xlink:href="#bams"/> | |
| 377 | + | <use transform="translate(1705,44.5)" xlink:href="#bams"/> | |
| 378 | + | <use transform="translate(1705,132.5)" xlink:href="#bams"/> | |
| 379 | + | <use transform="translate(1746,9.5)" xlink:href="#bams"/> | |
| 380 | + | <use transform="translate(1746,44.5)" xlink:href="#bams"/> | |
| 381 | + | <use transform="translate(1746,97.5)" xlink:href="#bams"/> | |
| 382 | + | <use transform="translate(1746,132.5)" xlink:href="#bams"/> | |
| 383 | + | <use transform="translate(1770,9.5)" xlink:href="#bams"/> | |
| 384 | + | <use transform="translate(1770,44.5)" xlink:href="#bams"/> | |
| 385 | + | <use transform="translate(1770,97.5)" xlink:href="#bams"/> | |
| 386 | + | <use transform="translate(1770,132.5)" xlink:href="#bams"/> | |
| 387 | + | <use transform="translate(1810,10.5) scale(.91,.925)" xlink:href="#bams"/> | |
| 388 | + | <use transform="translate(1810,45.5) scale(.91,.925)" xlink:href="#bams"/> | |
| 389 | + | <use transform="translate(1810,98.5) scale(.91,.925)" xlink:href="#bams"/> | |
| 390 | + | <use transform="translate(1810,133.5) scale(.91,.925)" xlink:href="#bams"/> | |
| 391 | + | <use transform="translate(1822.5,116) scale(.91,.925)" xlink:href="#bams2"/> | |
| 392 | + | <use transform="translate(1822.5,28) scale(.91,.925)" xlink:href="#bams2"/> | |
| 393 | + | <use transform="translate(1835,10.5) scale(.91,.925)" xlink:href="#bams"/> | |
| 394 | + | <use transform="translate(1835,45.5) scale(.91,.925)" xlink:href="#bams"/> | |
| 395 | + | <use transform="translate(1835,98.5) scale(.91,.925)" xlink:href="#bams"/> | |
| 396 | + | <use transform="translate(1835,133.5) scale(.91,.925)" xlink:href="#bams"/> | |
| 397 | + | <use transform="translate(1875,12.5) scale(.82,.85)" xlink:href="#bams"/> | |
| 398 | + | <use transform="translate(1875,45.5) scale(.82,.85)" xlink:href="#bams"/> | |
| 399 | + | <use transform="translate(1875,100.5) scale(.82,.85)" xlink:href="#bams"/> | |
| 400 | + | <use transform="translate(1875,133.5) scale(.82,.85)" xlink:href="#bams"/> | |
| 401 | + | <use transform="translate(1887,12.5) scale(.82,.85)" xlink:href="#bams"/> | |
| 402 | + | <use transform="translate(1887,45.5) scale(.82,.85)" xlink:href="#bams"/> | |
| 403 | + | <use transform="translate(1887,100.5) scale(.82,.85)" xlink:href="#bams"/> | |
| 404 | + | <use transform="translate(1887,133.5) scale(.82,.85)" xlink:href="#bams"/> | |
| 405 | + | <use transform="translate(1899,12.5) scale(.82,.85)" xlink:href="#bams"/> | |
| 406 | + | <use transform="translate(1899,45.5) scale(.82,.85)" xlink:href="#bams"/> | |
| 407 | + | <use transform="translate(1899,100.5) scale(.82,.85)" xlink:href="#bams"/> | |
| 408 | + | <use transform="translate(1899,133.5) scale(.82,.85)" xlink:href="#bams"/> | |
| 409 | + | <use transform="translate(1939.5,31.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 410 | + | <use transform="translate(1951.5,31.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 411 | + | <use transform="translate(1963.5,31.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 412 | + | <use transform="translate(1939.5,50.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 413 | + | <use transform="translate(1951.5,50.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 414 | + | <use transform="translate(1963.5,50.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 415 | + | <use transform="translate(1939.5,119.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 416 | + | <use transform="translate(1951.5,119.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 417 | + | <use transform="translate(1963.5,119.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 418 | + | <use transform="translate(1939.5,138.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 419 | + | <use transform="translate(1951.5,138.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 420 | + | <use transform="translate(1963.5,138.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 421 | + | <use transform="translate(1951.5,12.5) scale(.73,.66)" xlink:href="#bams2"/> | |
| 422 | + | <use transform="translate(1951.5,100.5) scale(.73,.66)" xlink:href="#bams2"/> | |
| 423 | + | <use transform="translate(2003.5,13.5) scale(.73,.77)" xlink:href="#bams"/> | |
| 424 | + | <use transform="translate(2003.5,46.5) scale(.73,.77)" xlink:href="#bams"/> | |
| 425 | + | <use transform="translate(2003.5,101.5) scale(.73,.77)" xlink:href="#bams"/> | |
| 426 | + | <use transform="translate(2003.5,134.5) scale(.73,.77)" xlink:href="#bams"/> | |
| 427 | + | <use transform="translate(2027.5,13.5) scale(.73,.77)" xlink:href="#bams"/> | |
| 428 | + | <use transform="translate(2027.5,46.5) scale(.73,.77)" xlink:href="#bams"/> | |
| 429 | + | <use transform="translate(2027.5,101.5) scale(.73,.77)" xlink:href="#bams"/> | |
| 430 | + | <use transform="translate(2027.5,134.5) scale(.73,.77)" xlink:href="#bams"/> | |
| 431 | + | <use transform="translate(2067.5,12.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 432 | + | <use transform="translate(2067.5,31.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 433 | + | <use transform="translate(2067.5,50.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 434 | + | <use transform="translate(2067.5,100.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 435 | + | <use transform="translate(2067.5,119.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 436 | + | <use transform="translate(2067.5,138.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 437 | + | <use transform="translate(2079.5,12.5) scale(.73,.66)" xlink:href="#bams2"/> | |
| 438 | + | <use transform="translate(2079.5,31.5) scale(.73,.66)" xlink:href="#bams2"/> | |
| 439 | + | <use transform="translate(2079.5,50.5) scale(.73,.66)" xlink:href="#bams2"/> | |
| 440 | + | <use transform="translate(2079.5,100.5) scale(.73,.66)" xlink:href="#bams2"/> | |
| 441 | + | <use transform="translate(2079.5,119.5) scale(.73,.66)" xlink:href="#bams2"/> | |
| 442 | + | <use transform="translate(2079.5,138.5) scale(.73,.66)" xlink:href="#bams2"/> | |
| 443 | + | <use transform="translate(2091.5,12.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 444 | + | <use transform="translate(2091.5,31.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 445 | + | <use transform="translate(2091.5,50.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 446 | + | <use transform="translate(2091.5,100.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 447 | + | <use transform="translate(2091.5,119.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 448 | + | <use transform="translate(2091.5,138.5) scale(.73,.66)" xlink:href="#bams"/> | |
| 449 | + | <path fill="#006C00" d="M2031.75,29.81c-0.41-0.59-1.51-0.57-2.75-0.06l-3.11-4.44c0.9-1,1.3-2.02,0.89-2.61c-0.41-0.58-1.51-0.57-2.75-0.06l-3.11-4.44c0.9-1,1.3-2.02,0.89-2.61c-0.58-0.83-2.51-0.47-4.32,0.8s-2.81,2.96-2.23,3.79c0.41,0.59,1.51,0.57,2.75,0.06l3.11,4.44c-0.9,1-1.3,2.02-0.89,2.61c0.41,0.59,1.51,0.57,2.75,0.06l3.11,4.44c-0.9,1-1.3,2.02-0.89,2.61c0.58,0.83,2.51,0.47,4.32-0.8S2032.33,30.64,2031.75,29.81z M2021.19,22.48l-2.65-3.79c-0.67,0.38-1.29,0.49-1.47,0.22c-0.22-0.31,0.26-1.02,1.06-1.58c0.8-0.56,1.63-0.77,1.85-0.46c0.18,0.26-0.13,0.8-0.71,1.3l2.65,3.79c0.21,0.3,0.21,0.65,0.01,0.79C2021.73,22.9,2021.4,22.77,2021.19,22.48z M2021.5,26.4c-0.22-0.31,0.5-1.19,1.61-1.96c1.11-0.78,2.18-1.15,2.4-0.84c0.22,0.31-0.5,1.19-1.61,1.96C2022.79,26.33,2021.71,26.71,2021.5,26.4z M2028.87,32.67c-0.8,0.56-1.63,0.77-1.85,0.46c-0.18-0.26,0.13-0.8,0.71-1.3l-2.65-3.79c-0.21-0.29-0.21-0.65-0.01-0.79c0.2-0.14,0.53-0.02,0.74,0.28l2.65,3.79c0.67-0.38,1.29-0.49,1.47-0.22C2030.15,31.39,2029.67,32.1,2028.87,32.67z"/> | |
| 450 | + | <path fill="#006C00" d="M2021.52,16.4c-1.81-1.27-3.75-1.62-4.32-0.8c-0.41,0.58-0.02,1.61,0.89,2.61l-3.11,4.44c-1.25-0.51-2.35-0.53-2.75,0.06c-0.41,0.59-0.02,1.61,0.89,2.61l-3.11,4.44c-1.25-0.51-2.34-0.53-2.75,0.06c-0.58,0.83,0.42,2.52,2.23,3.79c1.81,1.27,3.75,1.63,4.32,0.8c0.41-0.59,0.02-1.61-0.89-2.61l3.11-4.44c1.25,0.51,2.34,0.53,2.75-0.06c0.41-0.58,0.02-1.61-0.89-2.61l3.11-4.44c1.25,0.51,2.34,0.53,2.75-0.06C2024.33,19.36,2023.33,17.67,2021.52,16.4z M2013.92,28.03l-2.65,3.79c0.58,0.5,0.9,1.04,0.71,1.31c-0.22,0.31-1.04,0.11-1.85-0.46s-1.28-1.27-1.06-1.58c0.18-0.26,0.8-0.15,1.47,0.22l2.65-3.79c0.21-0.3,0.54-0.42,0.74-0.28C2014.13,27.38,2014.13,27.74,2013.92,28.03z M2017.5,26.4c-0.22,0.31-1.29-0.07-2.4-0.84c-1.11-0.77-1.83-1.65-1.61-1.96c0.22-0.31,1.29,0.07,2.4,0.84C2017,25.21,2017.72,26.09,2017.5,26.4z M2021.93,18.91c-0.18,0.26-0.8,0.15-1.47-0.22l-2.65,3.79c-0.21,0.3-0.54,0.42-0.74,0.28c-0.2-0.14-0.2-0.49,0.01-0.79l2.65-3.79c-0.58-0.5-0.9-1.04-0.71-1.3c0.22-0.31,1.04-0.11,1.85,0.46S2022.15,18.6,2021.93,18.91z"/> | |
| 451 | + | <path fill="#006C00" d="M2031.75,117.81c-0.41-0.59-1.51-0.57-2.75-0.06l-3.11-4.44c0.9-1,1.3-2.02,0.89-2.61c-0.41-0.58-1.51-0.57-2.75-0.06l-3.11-4.44c0.9-1,1.3-2.02,0.89-2.61c-0.58-0.83-2.51-0.47-4.32,0.8c-1.81,1.27-2.81,2.96-2.23,3.79c0.41,0.59,1.51,0.57,2.75,0.06l3.11,4.44c-0.9,1-1.3,2.02-0.89,2.61c0.41,0.58,1.51,0.56,2.75,0.06l3.11,4.44c-0.9,1-1.3,2.02-0.89,2.61c0.58,0.83,2.51,0.47,4.32-0.8C2031.33,120.33,2032.33,118.64,2031.75,117.81z M2021.19,110.48l-2.65-3.79c-0.67,0.38-1.29,0.49-1.47,0.22c-0.22-0.31,0.26-1.02,1.06-1.58s1.63-0.77,1.85-0.46c0.18,0.26-0.13,0.8-0.71,1.3l2.65,3.79c0.21,0.3,0.21,0.65,0.01,0.79C2021.73,110.9,2021.4,110.77,2021.19,110.48z M2021.5,114.4c-0.22-0.31,0.5-1.19,1.61-1.96c1.11-0.78,2.18-1.15,2.4-0.84c0.22,0.31-0.5,1.19-1.61,1.96C2022.79,114.34,2021.71,114.71,2021.5,114.4z M2028.87,120.67c-0.8,0.56-1.63,0.77-1.85,0.46c-0.18-0.26,0.13-0.8,0.71-1.31l-2.65-3.79c-0.21-0.3-0.21-0.65-0.01-0.79c0.2-0.14,0.53-0.02,0.74,0.28l2.65,3.79c0.67-0.38,1.29-0.49,1.47-0.22C2030.15,119.39,2029.67,120.1,2028.87,120.67z"/> | |
| 452 | + | <path fill="#006C00" d="M2021.52,104.4c-1.81-1.27-3.75-1.63-4.32-0.8c-0.41,0.59-0.02,1.61,0.89,2.61l-3.11,4.44c-1.25-0.51-2.35-0.53-2.75,0.06c-0.41,0.59-0.02,1.61,0.89,2.61l-3.11,4.44c-1.25-0.51-2.34-0.53-2.75,0.06c-0.58,0.83,0.42,2.52,2.23,3.79c1.81,1.27,3.75,1.62,4.32,0.8c0.41-0.58,0.02-1.61-0.89-2.61l3.11-4.44c1.25,0.51,2.34,0.53,2.75-0.06c0.41-0.59,0.02-1.61-0.89-2.61l3.11-4.44c1.25,0.51,2.34,0.53,2.75-0.06C2024.33,107.36,2023.33,105.67,2021.52,104.4z M2013.92,116.03l-2.65,3.79c0.58,0.5,0.9,1.04,0.71,1.31c-0.22,0.31-1.04,0.11-1.85-0.46c-0.8-0.56-1.28-1.27-1.06-1.58c0.18-0.26,0.8-0.15,1.47,0.22l2.65-3.79c0.21-0.29,0.54-0.42,0.74-0.28C2014.13,115.38,2014.13,115.74,2013.92,116.03z M2017.5,114.4c-0.22,0.31-1.29-0.07-2.4-0.84c-1.11-0.77-1.83-1.65-1.61-1.96c0.22-0.31,1.29,0.07,2.4,0.84C2017,113.21,2017.72,114.09,2017.5,114.4z M2021.93,106.91c-0.18,0.26-0.8,0.15-1.47-0.22l-2.65,3.79c-0.21,0.3-0.54,0.42-0.74,0.28c-0.2-0.14-0.2-0.49,0.01-0.79l2.65-3.79c-0.58-0.5-0.9-1.04-0.71-1.3c0.22-0.31,1.04-0.11,1.85,0.46C2021.67,105.9,2022.15,106.61,2021.93,106.91z"/> | |
| 453 | + | <path fill="#006C00" d="M2023.75,148.81c-0.41-0.59-1.51-0.57-2.75-0.06l-3.11-4.44c0.9-1,1.3-2.02,0.89-2.61c-0.41-0.59-1.51-0.57-2.75-0.06l-3.11-4.44c0.9-1,1.3-2.02,0.89-2.61c-0.58-0.83-2.51-0.47-4.32,0.8s-2.81,2.96-2.23,3.79c0.41,0.59,1.51,0.57,2.75,0.06l3.11,4.44c-0.9,1-1.3,2.02-0.89,2.61c0.41,0.58,1.51,0.56,2.75,0.06l3.11,4.44c-0.9,1-1.3,2.02-0.89,2.61c0.58,0.83,2.51,0.47,4.32-0.8C2023.33,151.33,2024.33,149.64,2023.75,148.81z M2013.19,141.48l-2.65-3.79c-0.67,0.38-1.29,0.49-1.47,0.22c-0.22-0.31,0.26-1.02,1.06-1.58s1.63-0.77,1.85-0.46c0.18,0.26-0.13,0.8-0.71,1.3l2.65,3.79c0.21,0.29,0.21,0.65,0.01,0.79C2013.73,141.9,2013.4,141.77,2013.19,141.48z M2013.5,145.4c-0.22-0.31,0.5-1.19,1.61-1.96c1.11-0.78,2.18-1.15,2.4-0.84c0.22,0.31-0.5,1.19-1.61,1.96S2013.71,145.71,2013.5,145.4zM2020.87,151.67c-0.8,0.56-1.63,0.77-1.85,0.46c-0.18-0.26,0.13-0.8,0.71-1.3l-2.65-3.79c-0.21-0.3-0.21-0.65-0.01-0.79c0.2-0.14,0.53-0.02,0.74,0.28l2.65,3.79c0.67-0.38,1.29-0.49,1.47-0.22C2022.15,150.4,2021.67,151.1,2020.87,151.67z"/> | |
| 454 | + | <path fill="#006C00" d="M2029.52,135.4c-1.81-1.27-3.75-1.63-4.32-0.8c-0.41,0.59-0.02,1.61,0.89,2.61l-3.11,4.44c-1.25-0.51-2.34-0.53-2.75,0.06c-0.41,0.59-0.02,1.61,0.89,2.61l-3.11,4.44c-1.25-0.51-2.34-0.53-2.75,0.06c-0.58,0.83,0.42,2.52,2.23,3.79c1.81,1.27,3.75,1.62,4.32,0.8c0.41-0.58,0.02-1.61-0.89-2.61l3.11-4.44c1.25,0.51,2.35,0.53,2.75-0.06c0.41-0.58,0.02-1.61-0.89-2.61l3.11-4.44c1.25,0.51,2.34,0.53,2.75-0.06C2032.33,138.36,2031.33,136.67,2029.52,135.4z M2021.92,147.03l-2.65,3.79c0.58,0.5,0.9,1.04,0.71,1.3c-0.22,0.31-1.04,0.11-1.85-0.46c-0.8-0.56-1.28-1.27-1.06-1.58c0.18-0.26,0.8-0.15,1.47,0.22l2.65-3.79c0.21-0.3,0.54-0.42,0.74-0.28C2022.13,146.39,2022.13,146.74,2021.92,147.03z M2025.5,145.4c-0.22,0.31-1.29-0.07-2.4-0.84c-1.11-0.77-1.83-1.65-1.61-1.96c0.22-0.31,1.29,0.07,2.4,0.84C2025,144.21,2025.72,145.09,2025.5,145.4z M2029.93,137.92c-0.18,0.26-0.8,0.15-1.47-0.22l-2.65,3.79c-0.21,0.29-0.54,0.42-0.74,0.28c-0.2-0.14-0.2-0.49,0.01-0.79l2.65-3.79c-0.58-0.5-0.9-1.04-0.71-1.3c0.22-0.31,1.04-0.11,1.85,0.46C2029.67,136.9,2030.15,137.61,2029.93,137.92z"/> | |
| 455 | + | <path fill="#006C00" d="M2023.75,60.81c-0.41-0.59-1.51-0.57-2.75-0.06l-3.11-4.44c0.9-1,1.3-2.02,0.89-2.61c-0.41-0.59-1.51-0.56-2.75-0.06l-3.11-4.44c0.9-1,1.3-2.02,0.89-2.61c-0.58-0.83-2.51-0.47-4.32,0.8c-1.81,1.27-2.81,2.96-2.23,3.79c0.41,0.59,1.51,0.57,2.75,0.06l3.11,4.44c-0.9,1-1.3,2.02-0.89,2.61c0.41,0.58,1.51,0.56,2.75,0.06l3.11,4.44c-0.9,1-1.3,2.02-0.89,2.61c0.58,0.83,2.51,0.47,4.32-0.8C2023.33,63.33,2024.33,61.64,2023.75,60.81z M2013.19,53.48l-2.65-3.79c-0.67,0.38-1.29,0.49-1.47,0.22 c-0.22-0.31,0.26-1.02,1.06-1.58s1.63-0.77,1.85-0.46c0.18,0.26-0.13,0.8-0.71,1.3l2.65,3.79c0.21,0.29,0.21,0.65,0.01,0.79C2013.73,53.9,2013.4,53.77,2013.19,53.48z M2013.5,57.4c-0.22-0.31,0.5-1.19,1.61-1.96c1.11-0.78,2.18-1.15,2.4-0.84c0.22,0.31-0.5,1.19-1.61,1.96C2014.79,57.34,2013.71,57.71,2013.5,57.4z M2020.87,63.67c-0.8,0.56-1.63,0.77-1.85,0.46c-0.18-0.26,0.13-0.8,0.71-1.3l-2.65-3.79c-0.21-0.3-0.21-0.65-0.01-0.79c0.2-0.14,0.53-0.02,0.74,0.28l2.65,3.79c0.67-0.38,1.29-0.49,1.47-0.22C2022.15,62.4,2021.67,63.1,2020.87,63.67z"/> | |
| 456 | + | <path fill="#006C00" d="M2029.52,47.4c-1.81-1.27-3.75-1.63-4.32-0.8c-0.41,0.59-0.02,1.61,0.89,2.61l-3.11,4.44c-1.25-0.51-2.34-0.53-2.75,0.06c-0.41,0.59-0.02,1.61,0.89,2.61l-3.11,4.44c-1.25-0.51-2.34-0.53-2.75,0.06c-0.58,0.83,0.42,2.52,2.23,3.79c1.81,1.27,3.75,1.62,4.32,0.8c0.41-0.58,0.02-1.61-0.89-2.61l3.11-4.44c1.25,0.51,2.35,0.53,2.75-0.06c0.41-0.59,0.02-1.61-0.89-2.61l3.11-4.44c1.25,0.51,2.34,0.53,2.75-0.06C2032.33,50.36,2031.33,48.67,2029.52,47.4z M2021.92,59.03l-2.65,3.79c0.58,0.5,0.9,1.04,0.71,1.3c-0.22,0.31-1.04,0.11-1.85-0.46c-0.8-0.56-1.28-1.27-1.06-1.58c0.18-0.26,0.8-0.15,1.47,0.22l2.65-3.79c0.21-0.3,0.54-0.42,0.74-0.28C2022.13,58.39,2022.13,58.74,2021.92,59.03z M2025.5,57.4c-0.22,0.31-1.29-0.07-2.4-0.84c-1.11-0.77-1.83-1.65-1.61-1.96c0.22-0.31,1.29,0.07,2.4,0.84C2025,56.21,2025.72,57.09,2025.5,57.4z M2029.93,49.92c-0.18,0.26-0.8,0.15-1.47-0.22l-2.65,3.79c-0.21,0.29-0.54,0.42-0.74,0.28c-0.2-0.14-0.2-0.49,0.01-0.79l2.65-3.79c-0.58-0.5-0.9-1.04-0.71-1.3c0.22-0.31,1.04-0.11,1.85,0.46C2029.67,48.9,2030.15,49.61,2029.93,49.92z"/> | |
| 457 | + | <path fill="#20208C" d="M1171.2,12.1l6.8,19.4l0.3-0.1c3.6-1.2,13.3-4.1,15.3-2.6c0.2,0.2,0.3,0.4,0.3,0.7l-0.1,0.8l0.6-0.5c6.8-5.3,9.4-14,9.4-14.1l0-0.2l-0.1-0.1c-4.9-7.3-24.9-1.3-25.7-1l-0.2,0.1l0,0.2c0,0-0.2,1.7-0.2,2.4c-0.4-1.5-1.1-4.1-1.1-4.1l-5.6-1.3L1171.2,12.1z M1178.1,17.8c1.2-0.3,2.6-0.7,4-1c0,0,0,0,0,0s1.9,9.3,2,9.9c-0.5,0.1-3,0.8-3.6,1c-0.1-0.4-1.4-5.6-2.5-9.9C1178,17.9,1178.1,17.8,1178.1,17.8z M1189.8,15.5c0,0.1-0.4,9.6-0.4,10.2c-0.4,0.1-2.1,0.4-2.6,0.5c-0.1-0.6-1.8-9.6-1.8-9.9C1186.7,15.9,1188.3,15.7,1189.8,15.5z M1197.7,16.4c-0.2,2.3-1.1,8.1-2.6,9.9c-0.1-0.1,0-0.2-0.1-0.3c-0.5-0.5-1.6-0.7-3.3-0.6c0.3-4.5,1-7.5,1.9-7.9l0.7-0.3c0,0-3.5-1.7-3.8-1.8C1194.3,15,1197.3,15.1,1197.7,16.4z"/> | |
| 458 | + | <path fill="#20208C" d="M1003.8,18.1c-0.3,0.2-0.4,0.5-0.4,0.8c0,0.3,0.1,0.5,0.1,0.7c-2.6,0.2-19.7,1.8-23.9,1.4l-0.4,0l1.7,6l0.3-0.1c3.3-1.1,24.1-2.4,29-1.4c0.8,0.1,1.2,0,1.4-0.4c0.5-1-1.9-3.5-3.4-4.8C1007.3,19.4,1004.8,17.5,1003.8,18.1z"/> | |
| 459 | + | <path fill="#20208C" d="M1067.9,23.9c-0.3,0.2-0.4,0.5-0.4,0.9c0,0.2,0,0.5,0.1,0.7c-2.8,0.3-19.9,1.9-24.1,1.4l-0.5-0.1l1.7,6.2l0.4-0.1c3.3-1.2,24.3-2.4,29.3-1.4c0.8,0.2,1.3,0,1.5-0.4c0.7-1.3-3-4.6-3.4-4.9C1071.5,25.2,1069,23.2,1067.9,23.9z"/> | |
| 460 | + | <path fill="#20208C" d="M1060.1,12.9c-2.6,0.9-6.2,2.2-12.3,2.2l-0.7,0l2.9,4.2l16.7-2.2l0.5-3.1l-0.1-0.1C1064.7,11.2,1063,11.8,1060.1,12.9z"/> | |
| 461 | + | <path fill="#20208C" d="M1131.7,26.3c-0.3,0.2-0.4,0.5-0.4,0.9c0,0.3,0,0.5,0.1,0.7c-2.7,0.3-19.3,1.9-23.4,1.4l-0.5-0.1l1.7,6.2l0.3-0.1c3.2-1.2,23.6-2.4,28.4-1.4c0.8,0.2,1.3,0,1.5-0.4c0.6-1.3-2.9-4.6-3.3-5C1135.1,27.7,1132.7,25.6,1131.7,26.3z"/> | |
| 462 | + | <polygon fill="#20208C" points="1125.3,19 1114,20.4 1115.6,24.7 1127.6,23.3 1125.6,19 "/> | |
| 463 | + | <path fill="#20208C" d="M1122.9,8.6c-3,1.1-7,2.6-14,2.6l-0.6,0l3.2,4.9l18.9-2.6l0.6-3.6l-0.1-0.1C1128.2,6.6,1126.3,7.3,1122.9,8.6z"/> | |
| 464 | + | <path fill="#20208C" d="M1245.8,10.3l-10.3,10.5l0.4,0.5c0,0,5.6-3.2,7-4c-0.6,1.5-4.3,11.6-4.3,11.6l3.3,3.5c0,0,2.4-15.6,2.4-15.9c0.3-0.2,5.1-2.9,5.1-2.9l-3.4-3.7L1245.8,10.3z"/> | |
| 465 | + | <path fill="#20208C" d="M1258.6,10.2h-1.9c0,0-0.6,1.3-0.9,1.9c0,0-0.1-0.1-0.1-0.1l-5.9,3.5l0.2,0.7c0,0,3-0.2,3.9-0.2c-0.4,0.8-2.2,4.5-2.3,4.8c-0.4,0-6.3,0.5-6.3,0.5l-0.1,0.7l4,1.6c0,0,0.7-0.3,1.3-0.5c-0.6,1.2-2.5,5.2-2.7,5.5c-1.3,0.1-2.3,0.1-2.9,0l-0.4,0l0.5,3.7l0.3-0.1c1.4-0.2,12.2-1.6,14.1-1.7c1.8-0.1,7.3,2.1,7.4,2.1l0.7,0.3l-0.2-0.7c-0.1-0.4-1.3-3.9-2.2-4.8l-0.1-0.1l-0.1,0c-0.8-0.2-3.6,0-7.5,0.4c0.9-1,4.3-4.6,4.3-4.6l-3.2-3.4c0,0-2.9,7.8-3,8.2c-0.3,0-2.7,0.3-3.5,0.3c0.4-0.9,2.5-6.5,2.6-6.7c0.3-0.1,3.2-1.2,3.2-1.2l-0.2-0.7c0,0-1.7,0.3-2.5,0.4c0.3-0.9,1.6-4.1,1.7-4.4c0.2,0,1.2-0.1,1.2-0.1s-0.6-0.9-0.7-1.1c0.1-0.3,1.7-4.4,1.7-4.4H1258.6z"/> | |
| 466 | + | <path fill="#20208C" d="M1313.2,9.8c0,0.3,0,0.6,0,1c0,1.6-0.2,3.7-0.7,6.2c-5.9,0.7-11.1,1.5-11.2,1.5l-0.3,0V21c0,0,9.9-0.4,10.7-0.5c-0.2,0.7-1,4-1,4l0.6,0.3c0,0,2.9-4.1,3.1-4.4c11.7-0.3,14.4,0.2,15,0.5l0.6,0.4l-0.6-3.5c-0.3-1-2-2.1-12.2-1.3c0.6-0.9,3.2-4.6,3.2-4.6l-7.2-2.7L1313.2,9.8z"/> | |
| 467 | + | <path fill="#20208C" d="M1307.6,24.8c0,0,0.8,4.7-1.6,8.5l0.5,0.5c4.4-2.9,5.9-6.6,5.9-6.7l0.1-0.3l-5-2.6L1307.6,24.8z"/> | |
| 468 | + | <path fill="#20208C" d="M1317.1,23.7c0.2,0.3,5.9,7.7,7.9,9.6l0.1,0.1l0.2,0c0.4,0,0.8-0.3,1.1-0.7c0.8-1.1,1.3-3.2,1-4.3c-0.4-1.3-7.6-4.3-9.8-5.2l-1.2-0.5L1317.1,23.7z"/> | |
| 469 | + | <path fill="#20208C" d="M1387.2,11.7L1387.2,11.7c-0.2,0.4-1.5,1.9-8.3,7.6c-0.1-0.7-0.2-1.6-0.2-1.6c-0.2-1.9-0.4-4.1-0.8-6.5l-0.1-0.3l-3.2,0.3l0,0.3c0,0.6,0,1.1,0,1.7c0,3.3,0.3,6.2,0.9,8.9c-0.3,0.2-10,8.2-10,8.2l0.3,0.3l1.9,1.8c0,0,7.9-6.9,8.6-7.6c0.7,2.2,1.6,4,2.7,5c2,2,4.2,1.7,7,1.3c2-0.3,4.3-0.6,7-0.2l0.5,0.1l-0.6-2.8c0-0.6-0.3-1.1-0.7-1.5c-1.3-0.9-3.8-0.4-6.5,0.1c-1.3,0.3-2.7,0.5-3.9,0.6c-1.3-1.2-2.1-2.8-2.6-5.2c7.5-6.2,10.3-7.8,11.3-8.1l0.2-0.1l0-0.3c0-0.2,0-0.3,0-0.4c0-1.8-0.9-2.8-1.7-3C1388.5,10.4,1387.7,10.6,1387.2,11.7z"/> | |
| 470 | + | <path fill="#20208C" d="M1432.4,19c0,3.7-4.5,9.2-4.7,9.4l-0.7,0.9l1.1-0.3c5.2-1.4,8.2-5,8.3-5.1l0.2-0.2l-4.2-5.7V19z"/> | |
| 471 | + | <path fill="#20208C" d="M1437.9,11.3l-2,0.3l0.3,0.5c0.1,0.2,9.3,15.7,10.6,19.3l0.1,0.3l0.3-0.1c2.2-0.4,4-0.2,5.8,0c2.2,0.2,4.3,0.4,6.7-0.4l0.4-0.1l-0.2-0.4c-1.2-2-2.5-2.4-4.2-2.9c-3.2-0.9-8-2.3-17.3-16.3l-0.1-0.2L1437.9,11.3z"/> | |
| 472 | + | <path fill="#20208C" d="M1520.6,23.6c0,0-2.9,3.2-3.1,3.4c-3.2,0.7-8.4,1.2-8.8-0.4c-0.5-1.9,3.4-10.4,4.9-13.6l0.2-0.3l-5.6-2.8l-0.1,0.4c-0.1,0.2-0.4,1.3-0.9,3c-2,0-4.2,0-4.5,0c0-0.4-0.1-1.3-0.1-1.3l0,0c-1.4-2.3-7.4-0.7-8.1-0.6l-0.4,0.1l0.2,0.4c0.2,0.4,0.4,0.9,0.5,1.4c-0.8,0-3.8,0-3.8,0l1.9,3.6c0,0,1.8-0.3,2.4-0.4c0,0.2,0,0.5,0,0.7c0,6.2-2.7,14.1-2.7,14.2l0.6,0.3c0.3-0.4,7.9-9.5,9.3-16.4c0.2,0,2.4-0.4,4.4-0.7c-1.2,4.7-2.6,12.1-0.3,14.8c3.4,4,16.6,3.7,17.1,3.5l0.3-0.1L1520.6,23.6z"/> | |
| 473 | + | <path fill="#20208C" d="M1171.2,100.1l6.8,19.4l0.3-0.1c3.6-1.2,13.3-4.1,15.3-2.6c0.2,0.2,0.3,0.4,0.3,0.7l-0.1,0.8l0.6-0.5c6.8-5.3,9.4-14,9.4-14.1l0-0.2l-0.1-0.1c-4.9-7.3-24.9-1.3-25.7-1l-0.2,0.1l0,0.2c0,0-0.2,1.7-0.2,2.4c-0.4-1.5-1.1-4.1-1.1-4.1l-5.6-1.3L1171.2,100.1z M1178.1,105.8c1.2-0.3,2.6-0.7,4-1c0,0,0,0,0,0s1.9,9.3,2,9.9c-0.5,0.1-3,0.8-3.6,1c-0.1-0.4-1.4-5.6-2.5-9.9C1178,105.9,1178.1,105.8,1178.1,105.8z M1189.8,103.5c0,0.1-0.4,9.6-0.4,10.2c-0.4,0.1-2.1,0.4-2.6,0.5c-0.1-0.6-1.8-9.6-1.8-9.9C1186.7,103.9,1188.3,103.7,1189.8,103.5z M1197.7,104.4c-0.2,2.3-1.1,8.1-2.6,9.9c-0.1-0.1,0-0.2-0.1-0.3c-0.5-0.5-1.6-0.7-3.3-0.6c0.3-4.5,1-7.5,1.9-7.9l0.7-0.3c0,0-3.5-1.7-3.8-1.8C1194.3,103,1197.3,103.1,1197.7,104.4z"/> | |
| 474 | + | <path fill="#20208C" d="M1003.8,106.1c-0.3,0.2-0.4,0.5-0.4,0.8c0,0.3,0.1,0.5,0.1,0.7c-2.6,0.2-19.7,1.8-23.9,1.4l-0.4,0l1.7,6l0.3-0.1c3.3-1.1,24.1-2.4,29-1.4c0.8,0.1,1.2,0,1.4-0.4c0.5-1-1.9-3.5-3.4-4.8C1007.3,107.4,1004.8,105.5,1003.8,106.1z"/> | |
| 475 | + | <path fill="#20208C" d="M1067.9,111.9c-0.3,0.2-0.4,0.5-0.4,0.9c0,0.2,0,0.5,0.1,0.7c-2.8,0.3-19.9,1.9-24.1,1.4l-0.5-0.1l1.7,6.2l0.4-0.1c3.3-1.2,24.3-2.4,29.3-1.4c0.8,0.2,1.3,0,1.5-0.4c0.7-1.3-3-4.6-3.4-4.9C1071.5,113.2,1069,111.2,1067.9,111.9z"/> | |
| 476 | + | <path fill="#20208C" d="M1060.1,100.9c-2.6,0.9-6.2,2.2-12.3,2.2l-0.7,0l2.9,4.2l16.7-2.2l0.5-3.1l-0.1-0.1C1064.7,99.2,1063,99.8,1060.1,100.9z"/> | |
| 477 | + | <path fill="#20208C" d="M1131.7,114.3c-0.3,0.2-0.4,0.5-0.4,0.9c0,0.3,0,0.5,0.1,0.7c-2.7,0.3-19.3,1.9-23.4,1.4l-0.5-0.1l1.7,6.2l0.3-0.1c3.2-1.2,23.6-2.4,28.4-1.4c0.8,0.2,1.3,0,1.5-0.4c0.6-1.3-2.9-4.6-3.3-5C1135.1,115.7,1132.7,113.6,1131.7,114.3z"/> | |
| 478 | + | <polygon fill="#20208C" points="1125.3,107 1114,108.4 1115.6,112.7 1127.6,111.3 1125.6,107 "/> | |
| 479 | + | <path fill="#20208C" d="M1122.9,96.6c-3,1.1-7,2.6-14,2.6l-0.6,0l3.2,4.9l18.9-2.6l0.6-3.6l-0.1-0.1C1128.2,94.6,1126.3,95.3,1122.9,96.6z"/> | |
| 480 | + | <path fill="#20208C" d="M1245.8,98.3l-10.3,10.5l0.4,0.5c0,0,5.6-3.2,7-4c-0.6,1.5-4.3,11.6-4.3,11.6l3.3,3.5c0,0,2.4-15.6,2.4-15.9c0.3-0.2,5.1-2.9,5.1-2.9l-3.4-3.7L1245.8,98.3z"/> | |
| 481 | + | <path fill="#20208C" d="M1258.6,98.2h-1.9c0,0-0.6,1.3-0.9,1.9c0,0-0.1-0.1-0.1-0.1l-5.9,3.5l0.2,0.7c0,0,3-0.2,3.9-0.2c-0.4,0.8-2.2,4.5-2.3,4.8c-0.4,0-6.3,0.5-6.3,0.5l-0.1,0.7l4,1.6c0,0,0.7-0.3,1.3-0.5c-0.6,1.2-2.5,5.2-2.7,5.5c-1.3,0.1-2.3,0.1-2.9,0l-0.4,0l0.5,3.7l0.3-0.1c1.4-0.2,12.2-1.6,14.1-1.7c1.8-0.1,7.3,2.1,7.4,2.1l0.7,0.3l-0.2-0.7c-0.1-0.4-1.3-3.9-2.2-4.8l-0.1-0.1l-0.1,0c-0.8-0.2-3.6,0-7.5,0.4c0.9-1,4.3-4.6,4.3-4.6l-3.2-3.4c0,0-2.9,7.8-3,8.2c-0.3,0-2.7,0.3-3.5,0.3c0.4-0.9,2.5-6.5,2.6-6.7c0.3-0.1,3.2-1.2,3.2-1.2l-0.2-0.7c0,0-1.7,0.3-2.5,0.4c0.3-0.9,1.6-4.1,1.7-4.4c0.2,0,1.2-0.1,1.2-0.1s-0.6-0.9-0.7-1.1c0.1-0.3,1.7-4.4,1.7-4.4H1258.6z"/> | |
| 482 | + | <path fill="#20208C" d="M1313.2,97.8c0,0.3,0,0.6,0,1c0,1.6-0.2,3.7-0.7,6.2c-5.9,0.7-11.1,1.5-11.2,1.5l-0.3,0v2.5c0,0,9.9-0.4,10.7-0.5c-0.2,0.7-1,4-1,4l0.6,0.3c0,0,2.9-4.1,3.1-4.4c11.7-0.3,14.4,0.2,15,0.5l0.6,0.4l-0.6-3.5c-0.3-1-2-2.1-12.2-1.3c0.6-0.9,3.2-4.6,3.2-4.6l-7.2-2.7L1313.2,97.8z"/> | |
| 483 | + | <path fill="#20208C" d="M1307.6,112.8c0,0,0.8,4.7-1.6,8.5l0.5,0.5c4.4-2.9,5.9-6.6,5.9-6.7l0.1-0.3l-5-2.6L1307.6,112.8z"/> | |
| 484 | + | <path fill="#20208C" d="M1317.1,111.7c0.2,0.3,5.9,7.7,7.9,9.6l0.1,0.1l0.2,0c0.4,0,0.8-0.3,1.1-0.7c0.8-1.1,1.3-3.2,1-4.3c-0.4-1.3-7.6-4.3-9.8-5.2l-1.2-0.5L1317.1,111.7z"/> | |
| 485 | + | <path fill="#20208C" d="M1387.2,99.7L1387.2,99.7c-0.2,0.4-1.5,1.9-8.3,7.6c-0.1-0.7-0.2-1.6-0.2-1.6c-0.2-1.9-0.4-4.1-0.8-6.5l-0.1-0.3l-3.2,0.3l0,0.3c0,0.6,0,1.1,0,1.7c0,3.3,0.3,6.2,0.9,8.9c-0.3,0.2-10,8.2-10,8.2l0.3,0.3l1.9,1.8c0,0,7.9-6.9,8.6-7.6c0.7,2.2,1.6,4,2.7,5c2,2,4.2,1.7,7,1.3c2-0.3,4.3-0.6,7-0.2l0.5,0.1l-0.6-2.8c0-0.6-0.3-1.1-0.7-1.5c-1.3-0.9-3.8-0.4-6.5,0.1c-1.3,0.3-2.7,0.5-3.9,0.6c-1.3-1.2-2.1-2.8-2.6-5.2c7.5-6.2,10.3-7.8,11.3-8.1l0.2-0.1l0-0.3c0-0.2,0-0.3,0-0.4c0-1.8-0.9-2.8-1.7-3C1388.5,98.4,1387.7,98.6,1387.2,99.7z"/> | |
| 486 | + | <path fill="#20208C" d="M1432.4,107c0,3.7-4.5,9.2-4.7,9.4l-0.7,0.9l1.1-0.3c5.2-1.4,8.2-5,8.3-5.1l0.2-0.2l-4.2-5.7V107z"/> | |
| 487 | + | <path fill="#20208C" d="M1437.9,99.3l-2,0.3l0.3,0.5c0.1,0.2,9.3,15.7,10.6,19.3l0.1,0.3l0.3-0.1c2.2-0.4,4-0.2,5.8,0c2.2,0.2,4.3,0.4,6.7-0.4l0.4-0.1l-0.2-0.4c-1.2-2-2.5-2.4-4.2-2.9c-3.2-0.9-8-2.3-17.3-16.3l-0.1-0.2L1437.9,99.3z"/> | |
| 488 | + | <path fill="#20208C" d="M1520.6,111.6c0,0-2.9,3.2-3.1,3.4c-3.2,0.7-8.4,1.2-8.8-0.4c-0.5-1.9,3.4-10.4,4.9-13.6l0.2-0.3l-5.6-2.8l-0.1,0.4c-0.1,0.2-0.4,1.3-0.9,3c-2,0-4.2,0-4.5,0c0-0.4-0.1-1.3-0.1-1.3l0,0c-1.4-2.3-7.4-0.7-8.1-0.6l-0.4,0.1l0.2,0.4c0.2,0.4,0.4,0.9,0.5,1.4c-0.8,0-3.8,0-3.8,0l1.9,3.6c0,0,1.8-0.3,2.4-0.4c0,0.2,0,0.5,0,0.7c0,6.2-2.7,14.1-2.7,14.2l0.6,0.3c0.3-0.4,7.9-9.5,9.3-16.4c0.2,0,2.4-0.4,4.4-0.7c-1.2,4.7-2.6,12.1-0.3,14.8c3.4,4,16.6,3.7,17.1,3.5l0.3-0.1L1520.6,111.6z"/> | |
| 489 | + | <path fill="#101040" d="M632.3,142.7c-2-0.9-11.8,3.3-11.8,3.3l0.5-20.7c0,0,5.3-4.3,9.6-1.7l-1.2-5c-1.4-0.2-8.4,5.5-8.4,5.5s-0.6-18.8,4.8-19.9c0,0-8.9-2.6-9.7-2.2c-0.4,0.2-0.4,13.8-0.2,27.1c-3.2,3.1-5.9,6.2-7.1,7.3l0.4-28.5c0,0-4.7-2.6-5.8-1.4l0.2,14.8c-0.3-0.5-0.7-0.9-1-1.3c-1-1.3-6.1-4.2-6.1-4.2s1.7,5.6,2.5,6.6c0.7,0.9,3,2.9,4.6,2.2l0.2,16.5c-3.4,3-7.9,6.8-8.5,5.4c-1.5-3.2-4.7-3-5.2-1.5c-0.5,1.6,4.2,10.2,5.8,10.1c0.8-0.1,4.4-4.9,8-10l0.1,5.3c1.6,2.1,3.8,0.5,4.6-0.7l0.3-11c1.1-1.6,4-4.5,7.1-7.9c0.2,12.1,0.3,22.8,0.3,22.8c4.9-5.2,16.7-4,16.7-4L632.3,142.7z"/> | |
| 490 | + | <path fill="#101040" d="M688.4,14.3c-8.5,5.7-21.5,6.9-28.8,5.9l-0.9,2c11.7,3.8,26.4,1,31.2-3.2L688.4,14.3z"/> | |
| 491 | + | <path fill="#101040" d="M692.5,39.2c-3.6-0.9-7.3-1.4-10.8-1.7c0.8-2.9,1.2-6,1-9.2l-7.8,2c0.6,1.7,0.6,4.3,0.2,7c-1.6,0-3.1,0.1-4.5,0.3c0.2-2.7,0.4-3.3-0.5-6l-4.7,1.3c1,1.8,1.5,2.3,1.8,5.1c-3.4,1-5.7,1.8-6.9,3.2c-5.1,6.1,0,23.2,0,23.2s-0.2-15.4,2-17c2.2-1.6,4.7-1.8,4.7-1.8c0.1,1.5-1.6,6.7-1.6,6.7c2.2-2.7,3.2-3.6,4.3-7.2c1.6-0.5,2.5-0.8,4.1-1c-1.6,6.1-4.5,10.9-4.5,10.9l7.4,2c-4.1-0.1-13.3,5.1-13.3,5.1s16.2-4,20.8-1.1l1.2-2.9c-3.4-2.7-10.5-4.9-10.5-4.9s2.2-4.1,4.6-9.4c2.3,0.1,5.6,0.3,7.7,0.9c9.2,4.1-1.7,22.4-1.7,22.4S705.7,45.9,692.5,39.2z"/> | |
| 492 | + | <path fill="#101040" d="M735.3,14.2c0.4,0.5,0.9,2.3,0.9,8.6c0,0.9,0,1.8,0,2.8c-3.4,0.5-6.3,1-6.8,1.2l-0.6,0.2l2.8,2.7l0.2-0.1c1.1-0.3,2.6-0.5,4.2-0.7c0,0.7-0.4,7.3-0.4,7.9c-4.9,0.4-9.2,1.1-12.7,1.9c-3,0.8-4.8,2.6-5.2,5.4c-0.1,0.4-0.1,0.9-0.1,1.4c0,7.1,6.9,16.9,7,16.9l0.8,0.5l-0.3-0.9c0-0.1-2.8-9.4-2.8-15.6c0-1.8,0.2-3.3,0.8-4.3c0.4-0.6,0.8-0.9,1.4-1c1.3-0.2,2.7-0.4,4-0.6c0,0,3.1,4,3.7,4.7c-0.5,0.1-1.4,0.3-1.4,0.3l0,0.3l0.2,2.2l0.4-0.1c0.6-0.2,1.8-0.5,3.2-0.7c0,0.6,0.1,2.4,0.2,2.9c-1.7,0.5-3.2,1.1-3.8,1.4l-0.4,0.2l1.5,1.6l0.2-0.1c0.7-0.3,1.7-0.6,2.7-0.9c0,0.7,0.3,5.6,0.3,5.6l0.7,0c0,0,1.1-5.5,1.2-6c1.8-0.2,3.5-0.1,4.8,0.2l0.4,0.1v-3l-0.2-0.1c-0.8-0.4-2.3-0.4-4.5,0.1c0.1-0.7,0.4-2.1,0.5-2.5c1.6-0.1,2.9-0.2,3.8-0.1l0.5,0.1l-0.7-2.6c0,0-0.9,0-1.4,0.1c0.8-0.9,3.5-4.4,3.7-4.6c0.3,0,3.9,0.1,3.9,0.1c1.6,0.1,3,0.7,4.1,2c1.3,1.5,2,3.9,2,6.3c0,0.5,0,1.1-0.1,1.6l-0.1,0.7c-0.7,5.1-1.3,8.8-3.1,9.6c-1.2,0.5-3.1-0.3-5.9-2.4l-0.5,0.5l5.5,9.6l0.3-0.1c6.7-2.4,7.7-3.8,10.4-15.4c1.2-5.1,0.8-9.1-1.3-11.8c-2.5-3.3-6.3-3.7-6.5-3.7c-0.1,0-6.7-0.3-14.6,0.2c0.2-0.8,2-7.5,2.1-7.9c2.5,0,4.7,0.1,5.7,0.5l0.4,0.1l0.4-4.7l-0.4,0c-1.1,0-2.8,0.1-5.1,0.3c1.3-4.4,1.9-5.8,2.1-6.2l0.2-0.3l-8.1-4.7L735.3,14.2z M739.7,39.5c-0.1,0.7-0.7,4.3-0.7,4.7c0,0-0.1,0-0.1,0c0-0.1,0.1-0.4,0.1-0.4l-4.4,0.4c0,0,0,0.5,0,0.6c-0.4,0.1-1,0.2-1.4,0.2c0-0.7,0-5,0-5.1C735.4,39.8,737.5,39.6,739.7,39.5z"/> | |
| 493 | + | <path fill="#101040" d="M804.5,41.3l0.1-6.4l4.4-0.3l-0.3-2.4l-4.1,1.2l0-5.7c2.4,0.3,5,0.8,7.1,0.9c4.3,0.3-1.2,14.1-1.2,14.1s16.3-14.4,2.6-18.1c-1.4,0-4.9,0.2-8.4,0.4l0-3.9l7.5-1.9l-7.5-0.1l0-4l-4.6-3.4l0.5,8.7l-4,2.7l4.1-1l0.2,3.2c-1.8,0.2-3.4,0.3-4.3,0.5c-11.5,2.2,1,17.2,1,17.2s-4-14,0.5-15.3c0.9-0.3,1.8-0.3,2.9-0.3l0.4,7l-3,0.9l3-0.2l0.2,4.1c-9.2,12.1-19.7,16.6-19.7,16.6s10.2-1.1,20-11.9l1,18.7c-3.7-1.5-7-3.6-7-3.6s2.4,7.1,7.5,10.1l4.3-5.1c-1-0.2-2.1-0.5-3.2-0.9l0.2-18.9c2.7,3.2,9,10.5,11.7,11l9-3C821.9,51.6,807.7,43.3,804.5,41.3z"/> | |
| 494 | + | <path fill="#101040" d="M688.4,102.3c-8.5,5.7-21.5,6.9-28.8,5.9l-0.9,2c11.7,3.8,26.4,1,31.2-3.2L688.4,102.3z"/> | |
| 495 | + | <path fill="#101040" d="M692.5,127.2c-3.6-0.9-7.3-1.4-10.8-1.7c0.8-2.9,1.2-6,1-9.2l-7.8,2c0.6,1.7,0.6,4.3,0.2,7c-1.6,0-3.1,0.1-4.5,0.3c0.2-2.7,0.4-3.3-0.5-6l-4.7,1.3c1,1.8,1.5,2.3,1.8,5.1c-3.4,1-5.7,1.8-6.9,3.2c-5.1,6.1,0,23.2,0,23.2s-0.2-15.4,2-17c2.2-1.6,4.7-1.8,4.7-1.8c0.1,1.5-1.6,6.7-1.6,6.7c2.2-2.7,3.2-3.6,4.3-7.2c1.6-0.5,2.5-0.8,4.1-1c-1.6,6.1-4.5,10.9-4.5,10.9l7.4,2c-4.1-0.1-13.3,5.1-13.3,5.1s16.2-4,20.8-1.1l1.2-2.9c-3.4-2.7-10.5-4.9-10.5-4.9s2.2-4.1,4.6-9.4c2.3,0.1,5.6,0.3,7.7,0.9c9.2,4.1-1.7,22.4-1.7,22.4S705.7,133.9,692.5,127.2z"/> | |
| 496 | + | <path fill="#101040" d="M735.3,102.2c0.4,0.5,0.9,2.3,0.9,8.6c0,0.8,0,1.8,0,2.8c-3.4,0.5-6.3,1-6.8,1.2l-0.6,0.2l2.8,2.7l0.2-0.1c1.1-0.3,2.6-0.5,4.2-0.7c0,0.7-0.4,7.3-0.4,7.9c-4.9,0.4-9.2,1.1-12.7,1.9c-3,0.8-4.8,2.6-5.2,5.4c-0.1,0.4-0.1,0.9-0.1,1.4c0,7.1,6.9,16.9,7,16.9l0.8,0.5l-0.3-0.9c0-0.1-2.8-9.4-2.8-15.6c0-1.8,0.2-3.3,0.8-4.3c0.4-0.6,0.8-0.9,1.4-1c1.3-0.2,2.7-0.4,4-0.6c0,0,3.1,4,3.7,4.7c-0.5,0.1-1.4,0.3-1.4,0.3l0,0.3l0.2,2.2l0.4-0.1c0.6-0.2,1.8-0.5,3.2-0.7c0,0.6,0.1,2.4,0.2,2.9c-1.7,0.5-3.2,1.1-3.8,1.4l-0.4,0.2l1.5,1.6l0.2-0.1c0.7-0.3,1.7-0.6,2.7-0.9c0,0.7,0.3,5.6,0.3,5.6l0.7,0c0,0,1.1-5.5,1.2-6c1.8-0.2,3.5-0.1,4.8,0.2l0.4,0.1v-3l-0.2-0.1c-0.8-0.4-2.3-0.4-4.5,0.1c0.1-0.7,0.4-2.1,0.5-2.5c1.6-0.1,2.9-0.2,3.8-0.1l0.5,0.1l-0.7-2.6c0,0-0.9,0-1.4,0.1c0.8-0.9,3.5-4.4,3.7-4.6c0.3,0,3.9,0.1,3.9,0.1c1.6,0.1,3,0.7,4.1,2c1.3,1.5,2,3.9,2,6.3c0,0.5,0,1.1-0.1,1.6l-0.1,0.7c-0.7,5.1-1.3,8.8-3.1,9.6c-1.2,0.5-3.1-0.3-5.9-2.4l-0.5,0.5l5.5,9.6l0.3-0.1c6.7-2.4,7.7-3.8,10.4-15.4c1.2-5.1,0.8-9.1-1.3-11.8c-2.5-3.3-6.3-3.7-6.5-3.7c-0.1,0-6.7-0.3-14.6,0.2c0.2-0.8,2-7.5,2.1-7.9c2.5,0,4.7,0.1,5.7,0.5l0.4,0.1l0.4-4.7l-0.4,0c-1.1,0-2.8,0.1-5.1,0.3c1.3-4.4,1.9-5.8,2.1-6.2l0.2-0.3l-8.1-4.7L735.3,102.2z M739.7,127.5c-0.1,0.7-0.7,4.3-0.7,4.7c0,0-0.1,0-0.1,0c0-0.1,0.1-0.4,0.1-0.4l-4.4,0.4c0,0,0,0.5,0,0.6c-0.4,0.1-1,0.2-1.4,0.2c0-0.7,0-5,0-5.1C735.4,127.8,737.5,127.6,739.7,127.5z"/> | |
| 497 | + | <path fill="#101040" d="M804.5,129.3l0.1-6.4l4.4-0.3l-0.3-2.4l-4.1,1.2l0-5.7c2.4,0.3,5,0.8,7.1,0.9c4.3,0.3-1.2,14.1-1.2,14.1s16.3-14.4,2.6-18.1c-1.4,0-4.9,0.2-8.4,0.4l0-3.9l7.5-1.9l-7.5-0.1l0-4l-4.6-3.4l0.5,8.7l-4,2.7l4.1-1l0.2,3.2c-1.8,0.2-3.4,0.3-4.3,0.5c-11.5,2.2,1,17.2,1,17.2s-4-14,0.5-15.3c0.9-0.3,1.8-0.3,2.9-0.3l0.4,7l-3,0.9l3-0.2l0.2,4.1c-9.2,12.1-19.7,16.6-19.7,16.6s10.2-1.1,20-11.9l1,18.7c-3.7-1.5-7-3.6-7-3.6s2.4,7.1,7.5,10.1l4.3-5.1c-1-0.2-2.1-0.5-3.2-0.9l0.2-18.9c2.7,3.2,9,10.5,11.7,11l9-3C821.9,139.6,807.7,131.3,804.5,129.3z"/> | |
| 498 | + | <path fill="none" stroke="#10106C" stroke-width="0.75" d="M49.4,40.5c0,7.7-6.2,13.9-13.9,13.9c-7.7,0-13.9-6.2-13.9-13.9c0-7.7,6.2-13.9,13.9-13.9C43.2,26.6,49.4,32.8,49.4,40.5z"/> | |
| 499 | + | <circle fill="none" stroke="#10106C" stroke-width="1.6" cx="42" cy="40.5" r="6.5"/> | |
| 500 | + | <circle fill="none" stroke="#10106C" stroke-width="1.6" cx="29" cy="40.5" r="6.5"/> | |
| 501 | + | <circle fill="none" stroke="#10106C" stroke-width="1.6" cx="35.5" cy="34" r="6.5"/> | |
| 502 | + | <circle fill="none" stroke="#10106C" stroke-width="1.6" cx="35.5" cy="47" r="6.5"/> | |
| 503 | + | <path fill="none" stroke="#10106C" stroke-width="0.75" d="M44.8,40.5c0,5.1-4.2,9.3-9.3,9.3s-9.3-4.2-9.3-9.3c0-5.1,4.2-9.3,9.3-9.3S44.8,35.4,44.8,40.5z"/> | |
| 504 | + | <path fill="none" stroke="#BA0000" stroke-width="2" d="M52.2,40.5c0,9.2-7.5,16.7-16.7,16.7c-9.2,0-16.7-7.5-16.7-16.7c0-9.2,7.5-16.7,16.7-16.7C44.7,23.8,52.2,31.3,52.2,40.5z"/> | |
| 505 | + | <path fill="none" stroke="#10106C" stroke-width="0.75" d="M55,40.5C55,51.3,46.3,60,35.5,60C24.7,60,16,51.3,16,40.5C16,29.7,24.7,21,35.5,21C46.3,21,55,29.7,55,40.5z"/> | |
| 506 | + | <path fill="none" stroke="#10106C" stroke-width="0.75" d="M49.4,128.5c0,7.7-6.2,13.9-13.9,13.9c-7.7,0-13.9-6.2-13.9-13.9s6.2-13.9,13.9-13.9C43.2,114.6,49.4,120.8,49.4,128.5z"/> | |
| 507 | + | <circle fill="none" stroke="#10106C" stroke-width="1.6" cx="42" cy="128.5" r="6.5"/> | |
| 508 | + | <circle fill="none" stroke="#10106C" stroke-width="1.6" cx="29" cy="128.5" r="6.5"/> | |
| 509 | + | <circle fill="none" stroke="#10106C" stroke-width="1.6" cx="35.5" cy="122" r="6.5"/> | |
| 510 | + | <circle fill="none" stroke="#10106C" stroke-width="1.6" cx="35.5" cy="135" r="6.5"/> | |
| 511 | + | <path fill="none" stroke="#10106C" stroke-width="0.75" d="M44.8,128.5c0,5.1-4.2,9.3-9.3,9.3s-9.3-4.2-9.3-9.3c0-5.1,4.2-9.3,9.3-9.3S44.8,123.4,44.8,128.5z"/> | |
| 512 | + | <path fill="none" stroke="#BA0000" stroke-width="2" d="M52.2,128.5c0,9.2-7.5,16.7-16.7,16.7c-9.2,0-16.7-7.5-16.7-16.7c0-9.2,7.5-16.7,16.7-16.7C44.7,111.8,52.2,119.3,52.2,128.5z"/> | |
| 513 | + | <path fill="none" stroke="#10106C" stroke-width="0.75" d="M55,128.5c0,10.8-8.7,19.5-19.5,19.5c-10.8,0-19.5-8.7-19.5-19.5c0-10.8,8.7-19.5,19.5-19.5C46.3,109,55,117.7,55,128.5z"/> | |
| 514 | + | <path fill="#BA0000" d="M861.8,10.2c-0.4,0.4-0.5,1-0.5,1.7c0,1.3,0.4,2.9,0.6,3.5c0,0,0.3,7,0.4,7.8c-0.9,0.1-2.5,0.2-3.1,0.4l-0.8,0c-1.7,0.1-3.3,0.2-4,0.9l-0.3,0.3l0.3,0.4c0.5,0.6,0.7,2.3,0.9,4.1c0.5,3.7,1.1,8.3,4.1,10.1l0.6,0.3L860,39c0,0,0.1-0.3,0.1-0.7c0-0.3-0.1-0.8-0.2-1.5c0.8,0.1,2.5,0.3,3.1,0.3c0.1,0.8,2.7,33.4,2.7,33.4l1,0c0,0,1.1-32.2,1.1-33.2c0.7,0,3.8,0.1,4.7,0.2c-0.2,0.6-1,3.1-1,3.1l1.4-1.3c0.4-0.3,9.1-8.2,7.9-13.4c-0.4-1.7-1.8-2.9-4.1-3.5l-0.1,0h-0.1c-1.3,0-5.3,0.1-8.3,0.3c0.1-2.6,0.1-4,0.1-4.7c0-0.6,0-0.7,0-0.8c0,0,0-0.1,0-0.1c0-0.5,0.5-1.2,0.9-1.7c0.4-0.6,0.9-1.1,0.9-1.7c0-0.2-0.1-0.4-0.2-0.6c-0.6-0.9-5-3.2-6.9-3.4C862.4,9.8,862,9.9,861.8,10.2z M861.9,26.6c0,0,0.2,0,0.5,0c0,0.9,0.3,7.2,0.4,8.1c-0.8-0.1-2.7-0.3-3.3-0.3c-0.3-1.3-0.4-2.7-0.5-4.1c-0.1-1.2-0.2-2.4-0.4-3.5C859.5,26.8,861.9,26.6,861.9,26.6z M868,29.7c0,0,0.1-2.4,0.1-3.2c1.3,0.2,3,0.3,4.6,0.3c0,0,1.2,0,1.8,0c0,0.4-0.1,0.9-0.1,0.9c-0.2,2.2-0.5,4.8-1.4,7.4c-0.6,0-4.3-0.2-5.1-0.3C867.9,34.1,868,29.7,868,29.7z"/> | |
| 515 | + | <path fill="#BA0000" d="M861.8,98.2c-0.4,0.4-0.5,1-0.5,1.7c0,1.3,0.4,2.9,0.6,3.5c0,0,0.3,7,0.4,7.8c-0.9,0.1-2.5,0.2-3.1,0.4l-0.8,0c-1.7,0.1-3.3,0.2-4,0.9l-0.3,0.3l0.3,0.4c0.5,0.6,0.7,2.3,0.9,4.1c0.5,3.7,1.1,8.3,4.1,10.1l0.6,0.3l0.2-0.6c0,0,0.1-0.3,0.1-0.7c0-0.3-0.1-0.8-0.2-1.5c0.8,0.1,2.5,0.3,3.1,0.3c0.1,0.8,2.7,33.4,2.7,33.4l1,0c0,0,1.1-32.2,1.1-33.2c0.7,0,3.8,0.1,4.7,0.2c-0.2,0.6-1,3.1-1,3.1l1.4-1.3c0.4-0.3,9.1-8.2,7.9-13.4c-0.4-1.7-1.8-2.9-4.1-3.5l-0.1,0h-0.1c-1.3,0-5.3,0.1-8.3,0.3c0.1-2.6,0.1-4,0.1-4.7c0-0.6,0-0.7,0-0.8c0,0,0-0.1,0-0.1c0-0.5,0.5-1.2,0.9-1.7c0.4-0.6,0.9-1.1,0.9-1.7c0-0.2-0.1-0.4-0.2-0.6c-0.6-0.9-5-3.2-6.9-3.4C862.4,97.8,862,97.9,861.8,98.2z M861.9,114.6c0,0,0.2,0,0.5,0c0,0.9,0.3,7.2,0.4,8.1c-0.8-0.1-2.7-0.3-3.3-0.3c-0.3-1.3-0.4-2.7-0.5-4.1c-0.1-1.2-0.2-2.4-0.4-3.5C859.5,114.8,861.9,114.6,861.9,114.6z M868,117.7c0,0,0.1-2.4,0.1-3.2c1.3,0.2,3,0.3,4.6,0.3c0,0,1.2,0,1.8,0c0,0.4-0.1,0.9-0.1,0.9c-0.2,2.2-0.5,4.8-1.4,7.4c-0.6,0-4.3-0.2-5.1-0.3C867.9,122.1,868,117.7,868,117.7z"/> | |
| 516 | + | <path fill="#004C00" d="M925,47.8c0,0.1-1.4,5.1-5.1,7.1c-0.5-0.6-3.1-4.2-3.1-4.2l-0.9,0.5l4.4,11.4l1-0.2c0-2.2,1.1-3.5,2.4-4.9c1.8-2.1,3.8-4.5,2.3-9.7l-0.5-1.7L925,47.8z"/> | |
| 517 | + | <path fill="#004C00" d="M929.5,37c0,0-0.5,4.9-1.9,7.8l-0.3,0.7l0.7,0.1c1.8,0.1,3.5-3.7,4.1-5.4c0.4,0,1.4,0.1,2,0.1c-0.3,1.2-0.7,2.8-0.7,3.8c0,0.4,0,0.7,0.2,0.9l0.5,0.3c0.8,0.1,5.2-0.2,6.1-0.3l0-1c-1.5-0.2-3.3-0.7-3.5-1.2c-0.2-0.6,0.6-3,1.5-5l0.3-0.7l-9-0.6L929.5,37z"/> | |
| 518 | + | <path fill="#004C00" d="M942,46.6L927.3,47c0,0,4.9,6.3,5.4,6.9c-2.6,2.4-4.9,4.2-7,5.4l-0.6,0.4l0.6,0.5c1.4,1.1,4.2-0.2,8.6-4.2c2.3,3.2,3.8,5.5,4.3,6.9l0.2,0.7l2.7-2.2l0-0.2l0-0.3c0-0.5,0-2.1-4.7-7.3c3-3.1,5.6-6,5.6-6.1l0.7-0.9L942,46.6z M937.3,49.4c-0.8,0.9-1.7,1.8-2.1,2.3c-0.4-0.4-1.2-1.2-1.9-1.9C934.4,49.6,936,49.5,937.3,49.4z"/> | |
| 519 | + | <path fill="#004C00" d="M931.1,25.2l-1.7,1.7l-4.5-5.2l-0.2,0c-0.7,0-4.3,0.1-5.2,1.4c-0.1,0.1-0.1,0.3-0.1,0.4c0,0.1,0,0.1,0,0.2c0.1,0.5,0.7,0.7,1.8,1.1c0.5,0.2,1.2,0.5,1.5,0.7c-1.1,2.3-2.3,4.4-3.7,6.4c-0.7-0.4-4.1-2.5-4.1-2.5l-0.6,0.8c0,0,2.8,3.1,3.3,3.6c-2.8,3.5-6.2,6.6-10.4,9.5l0.4,0.9c12.8-3.3,21.9-16.2,22.1-16.5l2.3,2.2c-3.2,2-6.2,3.8-6.2,3.8l0.5,0.9c2-0.8,4.8-1.9,7.6-3L945,42.4l10.8-3.8l-0.1-1c-5.1-0.2-12-4.4-17.1-7.9c2.9-1,5.3-1.7,6.4-1.6c0.7,0,0.9-0.3,1-0.5c0.4-1.1-3.5-4.4-11.4-9.7l-0.5-0.3l-0.2,0.6 M934.9,27c0.7-1.2,1.6-2.9,1.9-3.3c0.5,0.3,1.8,1.1,2.5,1.6c-0.4,0.2-1.9,1.1-3.7,2.3C935.4,27.4,935.2,27.2,934.9,27z"/> | |
| 520 | + | <path fill="#004C00" d="M926.9,35.4l-6.2,3.5l0.3,0.9c0,0,1.1-0.1,1.8-0.1c-0.2,0.7-0.6,1.8-1.1,3.2c-0.6,0-5-0.2-5-0.2l0.8,0.9c0.3,0.3,1.6,1.7,2.7,1.7c0,0,0.3-0.1,0.6-0.1c-0.8,2-1.7,3.7-2.6,5l0.7,0.7c1.5-1.2,3.2-3.4,4.8-6.4c0.3-0.1,1.2-0.4,1.2-0.4l-0.1-1c0,0-0.2,0-0.4,0c1.8-3.6,3.1-7.1,3.1-7.1l0.5-1.3L926.9,35.4z"/> | |
| 521 | + | <path fill="#004C00" d="M925,135.8c0,0.1-1.4,5.1-5.1,7.1c-0.5-0.6-3.1-4.2-3.1-4.2l-0.9,0.5l4.4,11.4l1-0.2c0-2.2,1.1-3.5,2.4-4.9c1.8-2.1,3.8-4.5,2.3-9.7l-0.5-1.7L925,135.8z"/> | |
| 522 | + | <path fill="#004C00" d="M929.5,125c0,0-0.5,4.9-1.9,7.8l-0.3,0.7l0.7,0.1c1.8,0.1,3.5-3.7,4.1-5.4c0.4,0,1.4,0.1,2,0.1c-0.3,1.2-0.7,2.8-0.7,3.8c0,0.4,0,0.7,0.2,0.9l0.5,0.3c0.8,0.1,5.2-0.2,6.1-0.3l0-1c-1.5-0.2-3.3-0.7-3.5-1.2c-0.2-0.6,0.6-3,1.5-5l0.3-0.7l-9-0.6L929.5,125z"/> | |
| 523 | + | <path fill="#004C00" d="M942,134.6l-14.7,0.4c0,0,4.9,6.3,5.4,6.9c-2.6,2.4-4.9,4.2-7,5.4l-0.6,0.4l0.6,0.5c1.4,1.1,4.2-0.2,8.6-4.2c2.3,3.2,3.8,5.5,4.3,6.9l0.2,0.7l2.7-2.3l0-0.2l0-0.3c0-0.5,0-2.1-4.7-7.3c3-3.1,5.6-6,5.6-6.1l0.7-0.9L942,134.6z M937.3,137.4c-0.8,0.9-1.7,1.8-2.1,2.3c-0.4-0.4-1.2-1.2-1.9-1.9C934.4,137.6,936,137.5,937.3,137.4z"/> | |
| 524 | + | <path fill="#004C00" d="M931.1,113.2l-1.7,1.7l-4.5-5.2l-0.2,0c-0.7,0-4.3,0.1-5.2,1.4c-0.1,0.1-0.1,0.3-0.1,0.4c0,0.1,0,0.1,0,0.2c0.1,0.5,0.7,0.7,1.8,1.1c0.5,0.2,1.2,0.5,1.5,0.7c-1.1,2.3-2.3,4.4-3.7,6.4c-0.7-0.4-4.1-2.5-4.1-2.5l-0.6,0.8c0,0,2.8,3.1,3.3,3.6c-2.8,3.5-6.2,6.6-10.4,9.5l0.4,0.9c12.8-3.3,21.9-16.2,22.1-16.5l2.3,2.2c-3.2,2-6.2,3.8-6.2,3.8l0.5,0.9c2-0.8,4.8-1.9,7.6-3l11.4,10.9l10.8-3.8l-0.1-1c-5.1-0.2-12-4.4-17.1-7.9c2.9-1,5.3-1.7,6.4-1.6c0.7,0,0.9-0.3,1-0.5c0.4-1.1-3.5-4.4-11.4-9.7l-0.5-0.3l-0.2,0.6 M934.9,115c0.7-1.2,1.6-2.9,1.9-3.3c0.5,0.3,1.8,1.1,2.5,1.6c-0.4,0.2-1.9,1.1-3.7,2.3C935.4,115.4,935.2,115.2,934.9,115z"/> | |
| 525 | + | <path fill="#004C00" d="M926.9,123.4l-6.2,3.5l0.3,0.9c0,0,1.1-0.1,1.8-0.1c-0.2,0.7-0.6,1.8-1.1,3.2c-0.6,0-5-0.2-5-0.2l0.8,0.9c0.3,0.3,1.6,1.7,2.7,1.7c0,0,0.3-0.1,0.6-0.1c-0.8,2-1.7,3.7-2.6,5l0.7,0.7c1.5-1.2,3.2-3.4,4.8-6.4c0.3-0.1,1.2-0.4,1.2-0.4l-0.1-1c0,0-0.2,0-0.4,0c1.8-3.6,3.1-7.1,3.1-7.1l0.5-1.3L926.9,123.4z"/> | |
| 526 | + | <rect x="2386" y="12" rx="5" fill="#294D8E" width="35" height="57"/> | |
| 527 | + | <rect x="2387" y="13" rx="4" fill="#F4F4F4" width="33" height="55"/> | |
| 528 | + | <rect x="2390" y="16" rx="3" fill="#294D8E" width="27" height="49"/> | |
| 529 | + | <rect x="2393" y="19" rx="7" fill="#F4F4F4" width="21" height="43"/> | |
| 530 | + | <rect x="2386" y="100" rx="5" fill="#294D8E" width="35" height="57"/> | |
| 531 | + | <rect x="2387" y="101" rx="4" fill="#FFFFFF" width="33" height="55"/> | |
| 532 | + | <rect x="2390" y="104" rx="3" fill="#294D8E" width="27" height="49"/> | |
| 533 | + | <rect x="2393" y="107" rx="7" fill="#FFFFFF" width="21" height="43"/> | |
| 534 | + | <path fill="#99B9FF" stroke="#678ED3" d="M2323.8,13.9l-0.1,1.3c0,0,8.9-2.4,9.4-0.6c0,0-0.4,4.1-1.4,4.9c0-0.5-0.7-0.6-1.7-0.5c0.1-1.1,0.3-3.7,1-4l-1.6-0.7l-0.2,4.8c-0.5,0.1-1,0.2-1.5,0.3l-0.9-4.8l-1,0.4l0.9,4.7c-1.1,0.3-2,0.5-2,0.5l-1.8-7l-2.2-0.5l3.1,8.9c0,0,7.7-2.7,7.5-0.7c3.2-2.5,4.3-6.5,4.3-6.5C2333.4,11,2323.8,13.9,2323.8,13.9z"/> | |
| 535 | + | <path fill="#99B9FF" stroke="#678ED3" d="M2267.6,19.9c-0.8-2.5,4.7,2.6,2.6,2.2c-2.1-0.4-10.6,0.1-11.9,0.6l-0.6-2.2C2259.5,20.7,2267.6,19.9,2267.6,19.9z"/> | |
| 536 | + | <path fill="#99B9FF" stroke="#678ED3" d="M2139.6,16.1c-0.8-2.5,4.7,2.6,2.6,2.2c-2.1-0.4-10.6,0.1-11.9,0.6l-0.6-2.2C2131.5,16.9,2139.6,16.1,2139.6,16.1z"/> | |
| 537 | + | <polygon fill="#99B9FF" stroke="#678ED3" points="2264.9,16.3 2265.7,17.8 2260.9,18.3 2260.4,16.8"/> | |
| 538 | + | <path fill="#99B9FF" stroke="#678ED3" d="M2267.1,12.5c-2-2.3-2.8,0.6-9,0.6l1.1,1.7l7.7-1L2267.1,12.5z"/> | |
| 539 | + | <path fill="#99B9FF" stroke="#678ED3" d="M2203.6,18.9c-0.8-2.5,4.7,2.6,2.6,2.2c-2.1-0.4-10.6,0.1-11.9,0.6l-0.6-2.2C2195.5,19.7,2203.6,18.9,2203.6,18.9z"/> | |
| 540 | + | <path fill="#99B9FF" stroke="#678ED3" d="M2203.1,14.5c-2-2.3-2.8,0.6-9,0.6l1.1,1.7l7.7-1L2203.1,14.5z"/> | |
| 541 | + | <path fill="#99B9FF" stroke="#678ED3" d="M2323.8,101.9l-0.1,1.3c0,0,8.9-2.4,9.4-0.6c0,0-0.4,4.1-1.4,4.9c0-0.5-0.7-0.6-1.7-0.5c0.1-1.1,0.3-3.7,1-4l-1.6-0.7l-0.2,4.8c-0.5,0.1-1,0.2-1.5,0.3l-0.9-4.8l-1,0.4l0.9,4.7c-1.1,0.3-2,0.5-2,0.5l-1.8-7l-2.2-0.5l3.1,8.9c0,0,7.7-2.7,7.5-0.7c3.2-2.5,4.3-6.5,4.3-6.5C2333.4,99,2323.8,101.9,2323.8,101.9z"/> | |
| 542 | + | <path fill="#99B9FF" stroke="#678ED3" d="M2267.6,107.9c-0.8-2.5,4.7,2.6,2.6,2.2c-2.1-0.4-10.6,0.1-11.9,0.6l-0.6-2.2C2259.5,108.7,2267.6,107.9,2267.6,107.9z"/> | |
| 543 | + | <path fill="#99B9FF" stroke="#678ED3" d="M2139.6,104.1c-0.8-2.5,4.7,2.6,2.6,2.2c-2.1-0.4-10.6,0.1-11.9,0.6l-0.6-2.2C2131.5,104.9,2139.6,104.1,2139.6,104.1z"/> | |
| 544 | + | <polygon fill="#99B9FF" stroke="#678ED3" points="2264.9,104.3 2265.7,105.8 2260.9,106.3 2260.4,104.8 "/> | |
| 545 | + | <path fill="#99B9FF" stroke="#678ED3" d="M2267.1,100.5c-2-2.3-2.8,0.6-9,0.6l1.1,1.7l7.7-1L2267.1,100.5z"/> | |
| 546 | + | <path fill="#99B9FF" stroke="#678ED3" d="M2203.6,106.9c-0.8-2.5,4.7,2.6,2.6,2.2c-2.1-0.4-10.6,0.1-11.9,0.6l-0.6-2.2C2195.5,107.7,2203.6,106.9,2203.6,106.9z"/> | |
| 547 | + | <path fill="#99B9FF" stroke="#678ED3" d="M2203.1,102.5c-2-2.3-2.8,0.6-9,0.6l1.1,1.7l7.7-1L2203.1,102.5z"/> | |
| 548 | + | <path fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" d="M2643.1,20.3l-0.1,1.3c0,0,8.9-2.4,9.4-0.6c0,0-0.4,4.1-1.4,4.9c0-0.5-0.7-0.6-1.7-0.5c0.1-1.1,0.3-3.7,1-4l-1.6-0.7l-0.2,4.8c-0.5,0.1-1,0.2-1.5,0.3l-0.9-4.8l-1,0.4l0.9,4.7c-1.1,0.3-2,0.5-2,0.5l-1.8-7L2640,19l3.1,8.9c0,0,7.7-2.7,7.5-0.7c3.2-2.5,4.3-6.5,4.3-6.5C2652.7,17.4,2643.1,20.3,2643.1,20.3z"/> | |
| 549 | + | <path fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" d="M2586.9,26.3c-0.8-2.5,4.7,2.6,2.6,2.2c-2.1-0.4-10.6,0.1-11.9,0.6l-0.6-2.2C2578.9,27.1,2586.9,26.3,2586.9,26.3z"/> | |
| 550 | + | <path fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" d="M2458.9,22.5c-0.8-2.5,4.7,2.6,2.6,2.2c-2.1-0.4-10.6,0.1-11.9,0.6l-0.6-2.2C2450.9,23.3,2458.9,22.5,2458.9,22.5z"/> | |
| 551 | + | <polygon fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" points="2584.3,22.6 2585,24.1 2580.3,24.7 2579.7,23.2 "/> | |
| 552 | + | <path fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" d="M2586.5,18.8c-2-2.3-2.8,0.6-9,0.6l1.1,1.7l7.7-1L2586.5,18.8z"/> | |
| 553 | + | <path fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" d="M2522.9,25.3c-0.8-2.5,4.7,2.6,2.6,2.2c-2.1-0.4-10.6,0.1-11.9,0.6l-0.6-2.2C2514.9,26.1,2522.9,25.3,2522.9,25.3z"/> | |
| 554 | + | <path fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" d="M2522.5,20.8c-2-2.3-2.8,0.6-9,0.6l1.1,1.7l7.7-1L2522.5,20.8z"/> | |
| 555 | + | <path fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" d="M2643.1,108.3l-0.1,1.3c0,0,8.9-2.4,9.4-0.6c0,0-0.4,4.1-1.4,4.9c0-0.5-0.7-0.6-1.7-0.5c0.1-1.1,0.3-3.7,1-4l-1.6-0.7l-0.2,4.8c-0.5,0.1-1,0.2-1.5,0.3l-0.9-4.8l-1,0.4l0.9,4.7c-1.1,0.3-2,0.5-2,0.5l-1.8-7l-2.2-0.5l3.1,8.9c0,0,7.7-2.7,7.5-0.7c3.2-2.5,4.3-6.5,4.3-6.5C2652.7,105.4,2643.1,108.3,2643.1,108.3z"/> | |
| 556 | + | <path fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" d="M2586.9,114.3c-0.8-2.5,4.7,2.6,2.6,2.2c-2.1-0.4-10.6,0.1-11.9,0.6l-0.6-2.2C2578.9,115.1,2586.9,114.3,2586.9,114.3z"/> | |
| 557 | + | <path fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" d="M2458.9,110.5c-0.8-2.5,4.7,2.6,2.6,2.2c-2.1-0.4-10.6,0.1-11.9,0.6l-0.6-2.2C2450.9,111.3,2458.9,110.5,2458.9,110.5z"/> | |
| 558 | + | <polygon fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" points="2584.3,110.6 2585,112.1 2580.3,112.7 2579.7,111.2 "/> | |
| 559 | + | <path fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" d="M2586.5,106.8c-2-2.3-2.8,0.6-9,0.6l1.1,1.7l7.7-1L2586.5,106.8z"/> | |
| 560 | + | <path fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" d="M2522.9,113.3c-0.8-2.5,4.7,2.6,2.6,2.2c-2.1-0.4-10.6,0.1-11.9,0.6l-0.6-2.2C2514.9,114.1,2522.9,113.3,2522.9,113.3z"/> | |
| 561 | + | <path fill="#DBC879" stroke="#7C5A2D" stroke-width="0.5" d="M2522.5,108.8c-2-2.3-2.8,0.6-9,0.6l1.1,1.7l7.7-1L2522.5,108.8z"/> | |
| 562 | + | <path fill="#101040" d="M632.3,54.7c-2-0.9-11.8,3.3-11.8,3.3l0.5-20.7c0,0,5.3-4.3,9.6-1.7l-1.2-5c-1.4-0.2-8.4,5.5-8.4,5.5s-0.6-18.8,4.8-19.9c0,0-8.9-2.6-9.7-2.2c-0.4,0.2-0.4,13.8-0.2,27.1c-3.2,3.1-5.9,6.2-7.1,7.3l0.4-28.5c0,0-4.7-2.6-5.8-1.4l0.2,14.8c-0.3-0.5-0.7-0.9-1-1.3c-1-1.3-6.1-4.2-6.1-4.2s1.7,5.6,2.5,6.6c0.7,0.9,3,2.9,4.6,2.2l0.2,16.5c-3.4,3-7.9,6.8-8.5,5.4c-1.5-3.2-4.7-3-5.2-1.5c-0.5,1.6,4.2,10.2,5.8,10.1c0.8-0.1,4.4-4.9,8-10l0.1,5.3c1.6,2.1,3.8,0.5,4.6-0.7l0.3-11c1.1-1.6,4-4.5,7.1-7.9c0.2,12.1,0.3,22.8,0.3,22.8c4.9-5.2,16.7-4,16.7-4L632.3,54.7z"/> | |
| 563 | + | </svg> |
addedsrc/game/ctl.ts+71 −0
| 1 | + | import type { Claim, ClaimOption, GameState, SoundCue } from './engine'; | |
| 2 | + | import type { Tile } from './tiles'; | |
| 3 | + | import type { Rules, SeatId } from './types'; | |
| 4 | + | ||
| 5 | + | /** | |
| 6 | + | * How hard a tile left a phone, in the thrower's own frame: `u` is where along | |
| 7 | + | * their edge it was let go of (0 = their left, 1 = their right), `vx` is speed | |
| 8 | + | * along that edge, `vy` speed *into* the table, both px/s. The common screen | |
| 9 | + | * turns this into a throw from that player's side of the table. | |
| 10 | + | */ | |
| 11 | + | export interface NetThrow { | |
| 12 | + | u: number; | |
| 13 | + | vx: number; | |
| 14 | + | vy: number; | |
| 15 | + | spin: number; | |
| 16 | + | } | |
| 17 | + | ||
| 18 | + | /** | |
| 19 | + | * What the table's UI needs from whatever is running the game. | |
| 20 | + | * | |
| 21 | + | * `Game` satisfies this directly — the hotseat and solo tables talk straight to | |
| 22 | + | * the engine, exactly as before. The online tables put `net/table.ts` here | |
| 23 | + | * instead: same surface, but a call either drives the authoritative engine (on | |
| 24 | + | * the host) or is sent to whoever has it. The UI cannot tell the difference, | |
| 25 | + | * which is the point — every screen is drawn from a `GameState` and every | |
| 26 | + | * button is one of these calls, wherever the state actually lives. | |
| 27 | + | */ | |
| 28 | + | export interface TableCtl { | |
| 29 | + | readonly state: GameState; | |
| 30 | + | subscribe(fn: () => void): () => void; | |
| 31 | + | getSnapshot(): number; | |
| 32 | + | ||
| 33 | + | discard(seat: SeatId, tile: Tile, thrown?: NetThrow): void; | |
| 34 | + | respond(seat: SeatId, claim: Claim | 'pass'): void; | |
| 35 | + | resolveNow(): void; | |
| 36 | + | declareConcealedKong(seat: SeatId, tile: Tile): void; | |
| 37 | + | declareAddedKong(seat: SeatId, tile: Tile): void; | |
| 38 | + | declareSelfDraw(seat: SeatId): void; | |
| 39 | + | reorderHand(seat: SeatId, from: number, to: number): void; | |
| 40 | + | sortHand(seat: SeatId): void; | |
| 41 | + | nextHand(): void; | |
| 42 | + | newGame(bots?: boolean[], names?: string[]): void; | |
| 43 | + | applySettings(names: string[], rules: Rules): void; | |
| 44 | + | ||
| 45 | + | undoable(): { zh: string; en: string } | null; | |
| 46 | + | undo(): void; | |
| 47 | + | ||
| 48 | + | canSelfDrawWin(seat: SeatId): boolean; | |
| 49 | + | concealedKongs(seat: SeatId): Tile[]; | |
| 50 | + | addedKongs(seat: SeatId): Tile[]; | |
| 51 | + | tenpai(seat: SeatId): Tile[]; | |
| 52 | + | sacredTiles(seat: SeatId): Tile[]; | |
| 53 | + | nextDrawer(): SeatId | null; | |
| 54 | + | ||
| 55 | + | readonly hasBots: boolean; | |
| 56 | + | isBot(seat: SeatId): boolean; | |
| 57 | + | readonly wallLeft: number; | |
| 58 | + | readonly roundWind: number; | |
| 59 | + | ||
| 60 | + | onSound(fn: (cue: SoundCue) => void): () => void; | |
| 61 | + | } | |
| 62 | + | ||
| 63 | + | /** Claim options a seat is still owed an answer on, or undefined. */ | |
| 64 | + | export const pendingOptions = ( | |
| 65 | + | state: GameState, | |
| 66 | + | seat: SeatId, | |
| 67 | + | ): ClaimOption[] | undefined => | |
| 68 | + | (state.phase === 'claims' || state.phase === 'robkong') && | |
| 69 | + | state.responses[seat] === undefined | |
| 70 | + | ? state.options[seat] | |
| 71 | + | : undefined; |
modifiedsrc/game/engine.ts+26 −1
| ⋯ 140 unchanged lines | |||
| 141 | 141 | state: GameState; | |
| 142 | 142 | private listeners = new Set<() => void>(); | |
| 143 | 143 | private version = 0; | |
| 144 | + | /** | |
| 145 | + | * Whether moves are written to localStorage. Off for the online tables: the | |
| 146 | + | * authoritative copy lives in the room, a mirror of somebody else's game must | |
| 147 | + | * not overwrite the local solo save — and the sandbox a deployed game runs in | |
| 148 | + | * has no storage to write to anyway. | |
| 149 | + | */ | |
| 150 | + | persist = true; | |
| 144 | 151 | ||
| 145 | 152 | constructor(names: string[] = DEFAULT_NAMES, rules: Rules = DEFAULT_RULES) { | |
| 146 | 153 | this.state = { | |
| ⋯ 37 unchanged lines | |||
| 184 | 191 | getSnapshot = () => this.version; | |
| 185 | 192 | private emit() { | |
| 186 | 193 | this.version++; | |
| 187 | - | saveGame(this.state); | |
| 194 | + | if (this.persist) saveGame(this.state); | |
| 188 | 195 | for (const l of this.listeners) l(); | |
| 189 | 196 | } | |
| 190 | 197 | ||
| ⋯ 92 unchanged lines | |||
| 283 | 290 | this.state = state; | |
| 284 | 291 | this.emit(); | |
| 285 | 292 | } | |
| 293 | + | ||
| 294 | + | /** One nameplate, not the whole settings screen — a phone claimed a seat. */ | |
| 295 | + | renameSeat(seat: SeatId, name: string) { | |
| 296 | + | if (!name || this.state.players[seat].name === name) return; | |
| 297 | + | this.state.players[seat].name = name; | |
| 298 | + | this.emit(); | |
| 299 | + | } | |
| 300 | + | ||
| 301 | + | /** | |
| 302 | + | * Hand a seat to the computer, or take it back. This is what happens online | |
| 303 | + | * when a player drops mid-hand: their seat cannot hold the table forever, and | |
| 304 | + | * it cannot simply be skipped either — somebody has to play it. | |
| 305 | + | */ | |
| 306 | + | setBot(seat: SeatId, bot: boolean) { | |
| 307 | + | if (this.state.bots[seat] === bot) return; | |
| 308 | + | this.state.bots[seat] = bot; | |
| 309 | + | this.emit(); | |
| 310 | + | } | |
| 286 | 311 | private say(msg: string) { | |
| 287 | 312 | this.state.log.unshift(msg); | |
| 288 | 313 | if (this.state.log.length > 40) this.state.log.pop(); | |
| ⋯ 717 unchanged lines | |||
addedsrc/net/handOrder.test.ts+29 −0
| 1 | + | import { describe, expect, it } from 'vitest'; | |
| 2 | + | import { reconcileOrder } from './handOrder'; | |
| 3 | + | ||
| 4 | + | describe('reconcileOrder', () => { | |
| 5 | + | it('keeps an arrangement while the tiles are all still there', () => { | |
| 6 | + | expect(reconcileOrder([5, 2, 9, 2], [2, 2, 5, 9])).toEqual([5, 2, 9, 2]); | |
| 7 | + | }); | |
| 8 | + | ||
| 9 | + | it('appends a new draw at the end, whatever the engine sorted it next to', () => { | |
| 10 | + | expect(reconcileOrder([5, 2, 9], [2, 3, 5, 9])).toEqual([5, 2, 9, 3]); | |
| 11 | + | }); | |
| 12 | + | ||
| 13 | + | it('drops a discard and keeps everything else where it was', () => { | |
| 14 | + | expect(reconcileOrder([5, 2, 9, 3], [2, 5, 9])).toEqual([5, 2, 9]); | |
| 15 | + | }); | |
| 16 | + | ||
| 17 | + | it('handles duplicates by count, not identity', () => { | |
| 18 | + | // Two of the 2s went (a pung claim), one 7 arrived. | |
| 19 | + | expect(reconcileOrder([2, 8, 2, 2], [2, 7, 8])).toEqual([2, 8, 7]); | |
| 20 | + | }); | |
| 21 | + | ||
| 22 | + | it('builds an order from nothing', () => { | |
| 23 | + | expect(reconcileOrder([], [3, 1, 2])).toEqual([3, 1, 2]); | |
| 24 | + | }); | |
| 25 | + | ||
| 26 | + | it('survives a whole new hand', () => { | |
| 27 | + | expect(reconcileOrder([1, 2, 3], [10, 11, 12])).toEqual([10, 11, 12]); | |
| 28 | + | }); | |
| 29 | + | }); |
addedsrc/net/handOrder.ts+37 −0
| 1 | + | import type { Tile } from '../game/tiles'; | |
| 2 | + | ||
| 3 | + | /** | |
| 4 | + | * A hand's arrangement is the player's own business, and online it stays on | |
| 5 | + | * their device: sending every drag over the wire and waiting for it to come | |
| 6 | + | * back would put a round-trip inside a gesture. So the shared state carries the | |
| 7 | + | * hand as a multiset in whatever order the engine keeps it, and each client | |
| 8 | + | * lays its own copy out however its player left it. | |
| 9 | + | * | |
| 10 | + | * This is the reconciliation: keep the previous arrangement wherever the tiles | |
| 11 | + | * are still there, drop what has gone, and append what is new at the end — the | |
| 12 | + | * engine appends the drawn tile last, so the tile that just arrived lands in | |
| 13 | + | * the drawn slot the UI already marks. | |
| 14 | + | */ | |
| 15 | + | export function reconcileOrder(prev: Tile[], hand: Tile[]): Tile[] { | |
| 16 | + | const counts = new Map<Tile, number>(); | |
| 17 | + | for (const t of hand) counts.set(t, (counts.get(t) ?? 0) + 1); | |
| 18 | + | ||
| 19 | + | const out: Tile[] = []; | |
| 20 | + | for (const t of prev) { | |
| 21 | + | const n = counts.get(t) ?? 0; | |
| 22 | + | if (n > 0) { | |
| 23 | + | counts.set(t, n - 1); | |
| 24 | + | out.push(t); | |
| 25 | + | } | |
| 26 | + | } | |
| 27 | + | // Whatever is left arrived since we last looked, in the engine's own order — | |
| 28 | + | // which puts the newest draw at the end, where the 摸 marker expects it. | |
| 29 | + | for (const t of hand) { | |
| 30 | + | const n = counts.get(t) ?? 0; | |
| 31 | + | if (n > 0) { | |
| 32 | + | counts.set(t, n - 1); | |
| 33 | + | out.push(t); | |
| 34 | + | } | |
| 35 | + | } | |
| 36 | + | return out; | |
| 37 | + | } |
addedsrc/net/protocol.ts+102 −0
| 1 | + | import type { Claim, GameState } from '../game/engine'; | |
| 2 | + | import type { NetThrow } from '../game/ctl'; | |
| 3 | + | import type { Tile } from '../game/tiles'; | |
| 4 | + | import type { SeatId } from '../game/types'; | |
| 5 | + | ||
| 6 | + | /** | |
| 7 | + | * What goes over the wire, in one place. | |
| 8 | + | * | |
| 9 | + | * The room is host-authoritative: exactly one client — whichever one antics | |
| 10 | + | * says is the host — runs the real engine, and everything it decides is | |
| 11 | + | * published as plain `GameState` JSON under one key of the shared room state. | |
| 12 | + | * Everyone else renders that and asks for things by sending intents, which the | |
| 13 | + | * host validates against the seat map before playing them through the same | |
| 14 | + | * engine methods a button would have called. The engine itself never knows any | |
| 15 | + | * of this is happening — the same trick `autoplay.ts` plays, stretched over a | |
| 16 | + | * network. | |
| 17 | + | */ | |
| 18 | + | ||
| 19 | + | /** The two ways a room can be playing. */ | |
| 20 | + | export type NetMode = 'online' | 'party'; | |
| 21 | + | ||
| 22 | + | /** Who holds a seat. The name outlives the connection — see the antics docs on | |
| 23 | + | * leavers: record it where it arrives, the match outlives the connection. */ | |
| 24 | + | export interface SeatSlot { | |
| 25 | + | /** antics player id, or null while nobody holds it. */ | |
| 26 | + | id: string | null; | |
| 27 | + | name: string; | |
| 28 | + | } | |
| 29 | + | ||
| 30 | + | /** The shared room state, as the host writes it. */ | |
| 31 | + | export interface SharedKeys { | |
| 32 | + | mode: NetMode; | |
| 33 | + | seats: SeatSlot[]; | |
| 34 | + | /** The whole engine state. null while the room is still in its lobby. */ | |
| 35 | + | game: GameState | null; | |
| 36 | + | } | |
| 37 | + | ||
| 38 | + | /** Something a player asks the host to play for their seat. */ | |
| 39 | + | export type Act = | |
| 40 | + | | { kind: 'discard'; tile: Tile; thrown?: NetThrow } | |
| 41 | + | | { kind: 'respond'; claim: Claim | 'pass' } | |
| 42 | + | | { kind: 'resolveNow' } | |
| 43 | + | | { kind: 'ankong'; tile: Tile } | |
| 44 | + | | { kind: 'addkong'; tile: Tile } | |
| 45 | + | | { kind: 'selfDraw' } | |
| 46 | + | | { kind: 'nextHand' } | |
| 47 | + | | { kind: 'newGame' }; | |
| 48 | + | ||
| 49 | + | export interface ActMsg { | |
| 50 | + | seat: SeatId; | |
| 51 | + | act: Act; | |
| 52 | + | } | |
| 53 | + | ||
| 54 | + | /** Ask the host for a seat. null means "any free one". */ | |
| 55 | + | export interface SeatMsg { | |
| 56 | + | seat: SeatId | null; | |
| 57 | + | } | |
| 58 | + | ||
| 59 | + | /** | |
| 60 | + | * Host to everyone else: things worth seeing or hearing that are not state — a | |
| 61 | + | * sound cue, or the throw a discard arrived with, so every table can show the | |
| 62 | + | * tile flying in from that player's edge. | |
| 63 | + | */ | |
| 64 | + | export type FxMsg = | |
| 65 | + | | { fx: 'sfx'; kind: string; tile?: Tile } | |
| 66 | + | | { fx: 'throw'; seat: SeatId; t: NetThrow }; | |
| 67 | + | ||
| 68 | + | export const EV_ACT = 'act'; | |
| 69 | + | export const EV_SEAT = 'seat'; | |
| 70 | + | export const EV_FX = 'fx'; | |
| 71 | + | ||
| 72 | + | export const EMPTY_SEATS: SeatSlot[] = [ | |
| 73 | + | { id: null, name: '' }, | |
| 74 | + | { id: null, name: '' }, | |
| 75 | + | { id: null, name: '' }, | |
| 76 | + | { id: null, name: '' }, | |
| 77 | + | ]; | |
| 78 | + | ||
| 79 | + | /** The seat in the QR's link, if the hosting page passed it through to us. */ | |
| 80 | + | export function seatFromUrl(): SeatId | null { | |
| 81 | + | try { | |
| 82 | + | const raw = new URLSearchParams(window.location.search).get('seat'); | |
| 83 | + | if (raw === null) return null; | |
| 84 | + | const n = Number(raw); | |
| 85 | + | return n === 0 || n === 1 || n === 2 || n === 3 ? (n as SeatId) : null; | |
| 86 | + | } catch { | |
| 87 | + | return null; | |
| 88 | + | } | |
| 89 | + | } | |
| 90 | + | ||
| 91 | + | /** Whether the page arrived with a room to join — i.e. we are on antics. */ | |
| 92 | + | export function roomFromUrl(): string | null { | |
| 93 | + | try { | |
| 94 | + | return new URLSearchParams(window.location.search).get('room'); | |
| 95 | + | } catch { | |
| 96 | + | return null; | |
| 97 | + | } | |
| 98 | + | } | |
| 99 | + | ||
| 100 | + | /** The room's invite link with the seat stitched in, for that seat's QR. */ | |
| 101 | + | export const seatLink = (link: string, seat: SeatId) => | |
| 102 | + | `${link}${link.includes('?') ? '&' : '?'}seat=${seat}`; |
addedsrc/net/session.ts+331 −0
| 1 | + | import { joinRoom, type Room } from 'antics-sdk'; | |
| 2 | + | import { AutoPlay } from '../game/autoplay'; | |
| 3 | + | import type { NetThrow } from '../game/ctl'; | |
| 4 | + | import type { SoundEvent } from '../game/engine'; | |
| 5 | + | import type { Tile } from '../game/tiles'; | |
| 6 | + | import type { Rules, SeatId } from '../game/types'; | |
| 7 | + | import { | |
| 8 | + | EMPTY_SEATS, | |
| 9 | + | EV_ACT, | |
| 10 | + | EV_FX, | |
| 11 | + | EV_SEAT, | |
| 12 | + | type ActMsg, | |
| 13 | + | type FxMsg, | |
| 14 | + | type NetMode, | |
| 15 | + | type SeatMsg, | |
| 16 | + | type SeatSlot, | |
| 17 | + | } from './protocol'; | |
| 18 | + | import { NetTable } from './table'; | |
| 19 | + | ||
| 20 | + | const SEATS: SeatId[] = [0, 1, 2, 3]; | |
| 21 | + | ||
| 22 | + | /** | |
| 23 | + | * One device's connection to a room, and everything that follows from it. | |
| 24 | + | * | |
| 25 | + | * antics elects a host, and the host runs the table: the real engine, the | |
| 26 | + | * computer players, the answering of everyone else's intents. Everybody else | |
| 27 | + | * mirrors the state the host publishes. If the host leaves, antics picks a new | |
| 28 | + | * one, whose mirror is already current — it starts its own engine from what it | |
| 29 | + | * was just watching and the game carries on. | |
| 30 | + | * | |
| 31 | + | * `role` is this device's, not the room's: the laptop that starts a party game | |
| 32 | + | * is the `screen` everyone throws at; every other device is a `player`. | |
| 33 | + | */ | |
| 34 | + | export type NetRole = 'player' | 'screen'; | |
| 35 | + | ||
| 36 | + | export class NetSession { | |
| 37 | + | readonly room: Room; | |
| 38 | + | readonly table: NetTable; | |
| 39 | + | role: NetRole = 'player'; | |
| 40 | + | /** Latest connection trouble worth showing, or null. */ | |
| 41 | + | err: string | null = null; | |
| 42 | + | /** | |
| 43 | + | * A phone threw a tile: the full-size tables put it into their pool so it | |
| 44 | + | * flies in from that player's edge. Wired by whoever owns a pool. | |
| 45 | + | */ | |
| 46 | + | onThrowFx: (seat: SeatId, t: NetThrow) => void = () => {}; | |
| 47 | + | ||
| 48 | + | private autoplay: AutoPlay | null = null; | |
| 49 | + | private stopAutoplay: (() => void) | null = null; | |
| 50 | + | private busy: () => boolean = () => false; | |
| 51 | + | private listeners = new Set<() => void>(); | |
| 52 | + | private version = 0; | |
| 53 | + | private unsubs: (() => void)[] = []; | |
| 54 | + | ||
| 55 | + | private constructor(room: Room) { | |
| 56 | + | this.room = room; | |
| 57 | + | this.table = new NetTable({ | |
| 58 | + | isAuthority: () => this.isHost, | |
| 59 | + | mySeat: () => this.mySeat, | |
| 60 | + | sendAct: (seat, act) => this.room.send(EV_ACT, { seat, act } as never), | |
| 61 | + | }); | |
| 62 | + | ||
| 63 | + | const u = this.unsubs; | |
| 64 | + | u.push( | |
| 65 | + | room.onState(() => { | |
| 66 | + | this.table.adopt(this.sharedGame()); | |
| 67 | + | this.syncAutoplay(); | |
| 68 | + | this.bump(); | |
| 69 | + | }), | |
| 70 | + | room.on(EV_ACT, (p, from) => this.handleAct(p as unknown as ActMsg, from)), | |
| 71 | + | room.on(EV_SEAT, (p, from) => this.handleSeat(p as unknown as SeatMsg, from)), | |
| 72 | + | room.on(EV_FX, (p) => this.handleFx(p as unknown as FxMsg)), | |
| 73 | + | room.onJoin(() => this.bump()), | |
| 74 | + | room.onLeave((p) => { | |
| 75 | + | if (this.isHost) this.vacate(p.id); | |
| 76 | + | this.bump(); | |
| 77 | + | }), | |
| 78 | + | room.onHostChange(() => { | |
| 79 | + | // A new host's engine is its mirror — already current. It only has to | |
| 80 | + | // start acting like the host, which `syncAutoplay` and the guards on | |
| 81 | + | // every handler do by asking `isHost` fresh each time. | |
| 82 | + | this.syncAutoplay(); | |
| 83 | + | this.bump(); | |
| 84 | + | }), | |
| 85 | + | room.onError((e) => { | |
| 86 | + | this.err = `${e.message} — ${e.hint}`; | |
| 87 | + | this.bump(); | |
| 88 | + | }), | |
| 89 | + | // The host's own engine is the source of truth: publish every change. | |
| 90 | + | this.table.engine.subscribe(() => { | |
| 91 | + | if (this.isHost && this.mode) this.publishGame(); | |
| 92 | + | this.syncAutoplay(); | |
| 93 | + | }), | |
| 94 | + | this.table.engine.onSound((cue) => { | |
| 95 | + | if (!this.isHost) return; | |
| 96 | + | this.table.emitSound(cue); | |
| 97 | + | this.room.send(EV_FX, { fx: 'sfx', kind: cue.kind, tile: cue.tile } as never); | |
| 98 | + | }), | |
| 99 | + | ); | |
| 100 | + | // Mirror whatever the room already holds — a late joiner walks in on the | |
| 101 | + | // game as it stands, not on an empty table. | |
| 102 | + | this.table.adopt(this.sharedGame()); | |
| 103 | + | } | |
| 104 | + | ||
| 105 | + | /** Join the room in the page URL, or create a fresh one. */ | |
| 106 | + | static async join(): Promise<NetSession> { | |
| 107 | + | const room = await joinRoom({}); | |
| 108 | + | return new NetSession(room); | |
| 109 | + | } | |
| 110 | + | ||
| 111 | + | leave() { | |
| 112 | + | for (const u of this.unsubs) u(); | |
| 113 | + | this.unsubs = []; | |
| 114 | + | this.stopAutoplay?.(); | |
| 115 | + | this.stopAutoplay = null; | |
| 116 | + | this.room.leave(); | |
| 117 | + | } | |
| 118 | + | ||
| 119 | + | // ---- store plumbing ---------------------------------------------------- | |
| 120 | + | subscribe = (fn: () => void) => { | |
| 121 | + | this.listeners.add(fn); | |
| 122 | + | return () => { | |
| 123 | + | this.listeners.delete(fn); | |
| 124 | + | }; | |
| 125 | + | }; | |
| 126 | + | getSnapshot = () => this.version; | |
| 127 | + | private bump() { | |
| 128 | + | this.version++; | |
| 129 | + | for (const l of this.listeners) l(); | |
| 130 | + | } | |
| 131 | + | ||
| 132 | + | // ---- what the room says ------------------------------------------------ | |
| 133 | + | get isHost() { | |
| 134 | + | return this.room.isHost; | |
| 135 | + | } | |
| 136 | + | get mode(): NetMode | null { | |
| 137 | + | const m = this.room.state.mode; | |
| 138 | + | return m === 'online' || m === 'party' ? m : null; | |
| 139 | + | } | |
| 140 | + | get seats(): SeatSlot[] { | |
| 141 | + | const s = this.room.state.seats; | |
| 142 | + | return Array.isArray(s) && s.length === 4 ? (s as unknown as SeatSlot[]) : EMPTY_SEATS; | |
| 143 | + | } | |
| 144 | + | private sharedGame(): unknown { | |
| 145 | + | return this.room.state.game ?? null; | |
| 146 | + | } | |
| 147 | + | get mySeat(): SeatId | null { | |
| 148 | + | const seats = this.seats; | |
| 149 | + | for (const i of SEATS) if (seats[i].id === this.room.me.id) return i; | |
| 150 | + | return null; | |
| 151 | + | } | |
| 152 | + | /** Whether a hand has actually been dealt in this room. */ | |
| 153 | + | get started(): boolean { | |
| 154 | + | const g = this.table.engine.state; | |
| 155 | + | return this.sharedGame() !== null && g.phase !== 'lobby'; | |
| 156 | + | } | |
| 157 | + | /** Everyone here, for the lobby's presence line. */ | |
| 158 | + | get playerCount() { | |
| 159 | + | return this.room.players.length; | |
| 160 | + | } | |
| 161 | + | ||
| 162 | + | // ---- lobby / starting -------------------------------------------------- | |
| 163 | + | /** | |
| 164 | + | * Open an online game in this room: publish the mode and take a seat. Only | |
| 165 | + | * the host can (room state is the host's to write); the button that calls | |
| 166 | + | * this is only shown to the host. | |
| 167 | + | */ | |
| 168 | + | openOnlineLobby() { | |
| 169 | + | if (!this.isHost) return; | |
| 170 | + | this.room.setState({ mode: 'online', seats: structuredClone(EMPTY_SEATS) as never }); | |
| 171 | + | this.claimSeat(0); | |
| 172 | + | } | |
| 173 | + | ||
| 174 | + | /** Deal the online game. Seats nobody claimed go to the computer. */ | |
| 175 | + | startOnline(rules: Rules) { | |
| 176 | + | if (!this.isHost) return; | |
| 177 | + | const seats = this.seats; | |
| 178 | + | const bots = seats.map((s) => s.id === null); | |
| 179 | + | if (bots.every(Boolean)) return; // nobody seated at all | |
| 180 | + | const names = seats.map((s, i) => s.name || `玩家 ${i + 1}`); | |
| 181 | + | const eng = this.table.engine; | |
| 182 | + | eng.applySettings(names, rules); | |
| 183 | + | eng.newGame(bots, names); | |
| 184 | + | } | |
| 185 | + | ||
| 186 | + | /** | |
| 187 | + | * The party table: this device becomes the common screen and deals at once. | |
| 188 | + | * All four seats start as people at this screen, exactly like hotseat — a | |
| 189 | + | * phone that scans a seat's QR takes that seat over, and gives it back if it | |
| 190 | + | * leaves. | |
| 191 | + | */ | |
| 192 | + | startParty(names: string[], rules: Rules) { | |
| 193 | + | if (!this.isHost) return; | |
| 194 | + | this.role = 'screen'; | |
| 195 | + | const seats = EMPTY_SEATS.map((_, i) => ({ id: null, name: names[i] ?? '' })); | |
| 196 | + | this.room.setState({ mode: 'party', seats: seats as never }); | |
| 197 | + | const eng = this.table.engine; | |
| 198 | + | eng.applySettings(names, rules); | |
| 199 | + | eng.newGame([false, false, false, false], names); | |
| 200 | + | } | |
| 201 | + | ||
| 202 | + | /** Ask for a seat — a numbered one, or any free one. */ | |
| 203 | + | claimSeat(seat: SeatId | null) { | |
| 204 | + | if (this.isHost) this.handleSeat({ seat }, this.room.me.id); | |
| 205 | + | else this.room.send(EV_SEAT, { seat } as never); | |
| 206 | + | } | |
| 207 | + | ||
| 208 | + | /** Hand a deserted seat to the computer (host's own button). */ | |
| 209 | + | handToBot(seat: SeatId) { | |
| 210 | + | if (!this.isHost) return; | |
| 211 | + | if (this.seats[seat].id !== null) return; | |
| 212 | + | this.table.engine.setBot(seat, true); | |
| 213 | + | } | |
| 214 | + | ||
| 215 | + | /** The pool animations to wait on before a computer player moves. */ | |
| 216 | + | setBusy(busy: () => boolean) { | |
| 217 | + | this.busy = busy; | |
| 218 | + | } | |
| 219 | + | ||
| 220 | + | // ---- host duties ------------------------------------------------------- | |
| 221 | + | private handleSeat(msg: SeatMsg, from: string) { | |
| 222 | + | if (!this.isHost) return; | |
| 223 | + | const name = this.room.player(from)?.name ?? ''; | |
| 224 | + | const seats = structuredClone(this.seats); | |
| 225 | + | let seat = msg.seat; | |
| 226 | + | if (seat === null) { | |
| 227 | + | seat = SEATS.find((i) => seats[i].id === null) ?? null; | |
| 228 | + | if (seat === null) return; | |
| 229 | + | } | |
| 230 | + | if (seats[seat].id !== null && seats[seat].id !== from) return; // taken | |
| 231 | + | // One seat per player: claiming a new one lets go of the old. | |
| 232 | + | for (const i of SEATS) if (seats[i].id === from) seats[i] = { id: null, name: seats[i].name }; | |
| 233 | + | seats[seat] = { id: from, name: name || seats[seat].name }; | |
| 234 | + | this.room.setState({ seats: seats as never }); | |
| 235 | + | ||
| 236 | + | // Mid-game, the seat comes back from the computer (or the common screen) | |
| 237 | + | // to the person who claimed it. | |
| 238 | + | if (this.sharedGame() !== null) { | |
| 239 | + | this.table.engine.setBot(seat, false); | |
| 240 | + | if (name) this.table.engine.renameSeat(seat, name); | |
| 241 | + | } | |
| 242 | + | this.bump(); | |
| 243 | + | } | |
| 244 | + | ||
| 245 | + | private vacate(id: string) { | |
| 246 | + | const seats = structuredClone(this.seats); | |
| 247 | + | let changed = false; | |
| 248 | + | for (const i of SEATS) { | |
| 249 | + | if (seats[i].id === id) { | |
| 250 | + | seats[i] = { id: null, name: seats[i].name }; | |
| 251 | + | changed = true; | |
| 252 | + | } | |
| 253 | + | } | |
| 254 | + | if (changed) this.room.setState({ seats: seats as never }); | |
| 255 | + | // The seat stays human: at a party table the common screen simply plays it | |
| 256 | + | // again, and online the host is offered the 電腦代打 button instead of the | |
| 257 | + | // table deciding on its own that a dropped friend is gone for good. | |
| 258 | + | } | |
| 259 | + | ||
| 260 | + | private handleAct(msg: ActMsg, from: string) { | |
| 261 | + | if (!this.isHost || !msg || typeof msg.seat !== 'number') return; | |
| 262 | + | const seat = msg.seat; | |
| 263 | + | // Their own seat — or one nobody holds, which is how the party screen | |
| 264 | + | // plays its unclaimed seats when it is not the one running the engine | |
| 265 | + | // (after a reload, say, when a phone has inherited the host's chair). | |
| 266 | + | const holder = this.seats[seat]?.id; | |
| 267 | + | if (holder !== from && holder !== null) return; | |
| 268 | + | const eng = this.table.engine; | |
| 269 | + | const act = msg.act; | |
| 270 | + | switch (act.kind) { | |
| 271 | + | case 'discard': | |
| 272 | + | if (act.thrown) { | |
| 273 | + | // Show the throw before the state lands: the pool holds the release | |
| 274 | + | // until the discard it belongs to arrives. | |
| 275 | + | this.onThrowFx(seat, act.thrown); | |
| 276 | + | this.room.send(EV_FX, { fx: 'throw', seat, t: act.thrown } as never); | |
| 277 | + | } | |
| 278 | + | eng.discard(seat, act.tile as Tile); | |
| 279 | + | break; | |
| 280 | + | case 'respond': | |
| 281 | + | eng.respond(seat, act.claim); | |
| 282 | + | break; | |
| 283 | + | case 'resolveNow': | |
| 284 | + | if (eng.nextDrawer() === seat) eng.resolveNow(); | |
| 285 | + | break; | |
| 286 | + | case 'ankong': | |
| 287 | + | eng.declareConcealedKong(seat, act.tile as Tile); | |
| 288 | + | break; | |
| 289 | + | case 'addkong': | |
| 290 | + | eng.declareAddedKong(seat, act.tile as Tile); | |
| 291 | + | break; | |
| 292 | + | case 'selfDraw': | |
| 293 | + | eng.declareSelfDraw(seat); | |
| 294 | + | break; | |
| 295 | + | case 'nextHand': | |
| 296 | + | eng.nextHand(); | |
| 297 | + | break; | |
| 298 | + | case 'newGame': | |
| 299 | + | eng.newGame(eng.state.bots); | |
| 300 | + | break; | |
| 301 | + | } | |
| 302 | + | } | |
| 303 | + | ||
| 304 | + | private handleFx(msg: FxMsg) { | |
| 305 | + | if (this.isHost || !msg) return; | |
| 306 | + | if (msg.fx === 'sfx') { | |
| 307 | + | this.table.emitSound({ kind: msg.kind as SoundEvent, tile: msg.tile }); | |
| 308 | + | } else if (msg.fx === 'throw') { | |
| 309 | + | this.onThrowFx(msg.seat, msg.t); | |
| 310 | + | } | |
| 311 | + | } | |
| 312 | + | ||
| 313 | + | private publishGame() { | |
| 314 | + | // The engine mutates its state in place; the room keeps what it is handed, | |
| 315 | + | // so it gets a copy that will hold still. | |
| 316 | + | this.room.setState({ game: structuredClone(this.table.engine.state) as never }); | |
| 317 | + | } | |
| 318 | + | ||
| 319 | + | /** The computer plays exactly when this device is the host of a live game. */ | |
| 320 | + | private syncAutoplay() { | |
| 321 | + | const should = this.isHost && this.mode !== null && this.sharedGame() !== null; | |
| 322 | + | if (should && !this.autoplay) { | |
| 323 | + | this.autoplay = new AutoPlay(this.table.engine, () => this.busy()); | |
| 324 | + | this.stopAutoplay = this.autoplay.start(); | |
| 325 | + | } else if (!should && this.autoplay) { | |
| 326 | + | this.stopAutoplay?.(); | |
| 327 | + | this.autoplay = null; | |
| 328 | + | this.stopAutoplay = null; | |
| 329 | + | } | |
| 330 | + | } | |
| 331 | + | } |
addedsrc/net/table.ts+264 −0
| 1 | + | import type { NetThrow, TableCtl } from '../game/ctl'; | |
| 2 | + | import { Game, type Claim, type GameState, type SoundCue } from '../game/engine'; | |
| 3 | + | import { sortTiles, type Tile } from '../game/tiles'; | |
| 4 | + | import type { Rules, SeatId } from '../game/types'; | |
| 5 | + | import { reconcileOrder } from './handOrder'; | |
| 6 | + | import type { Act } from './protocol'; | |
| 7 | + | ||
| 8 | + | /** | |
| 9 | + | * How the network session talks to the table. Kept this small so the table can | |
| 10 | + | * be reasoned about (and tested) without a room behind it. | |
| 11 | + | */ | |
| 12 | + | export interface TableWire { | |
| 13 | + | /** Whether this device is running the authoritative engine right now. */ | |
| 14 | + | isAuthority(): boolean; | |
| 15 | + | /** The seat this device's player holds, if any. */ | |
| 16 | + | mySeat(): SeatId | null; | |
| 17 | + | sendAct(seat: SeatId, act: Act): void; | |
| 18 | + | } | |
| 19 | + | ||
| 20 | + | /** How long an unanswered discard hides its tile before we admit it was lost. */ | |
| 21 | + | const PENDING_TTL = 3000; | |
| 22 | + | ||
| 23 | + | /** | |
| 24 | + | * The online table: the same surface the engine offers (`TableCtl`), backed by | |
| 25 | + | * a `Game` that is either the real one or a mirror of the host's. | |
| 26 | + | * | |
| 27 | + | * On the host the inner engine is authoritative and calls go straight into it. | |
| 28 | + | * Everywhere else the inner engine is a mirror kept current by `adopt`, and a | |
| 29 | + | * call becomes an intent sent to the host — with two exceptions, both about | |
| 30 | + | * your own hand, both because a round-trip inside a gesture is lag you can | |
| 31 | + | * feel. Arranging your tiles never goes over the wire at all: the shared state | |
| 32 | + | * carries the hand as a multiset and each device keeps its own arrangement | |
| 33 | + | * (`handOrder.ts`). And a discard hides the tile at once, so the hand does not | |
| 34 | + | * hold a tile you have visibly thrown while the wire does its round trip. | |
| 35 | + | */ | |
| 36 | + | export class NetTable implements TableCtl { | |
| 37 | + | /** The engine. Authoritative on the host, a mirror everywhere else. */ | |
| 38 | + | readonly engine: Game; | |
| 39 | + | /** Whether the undo button works here — the party screen, nowhere else. */ | |
| 40 | + | allowUndo = false; | |
| 41 | + | ||
| 42 | + | private wire: TableWire; | |
| 43 | + | private listeners = new Set<() => void>(); | |
| 44 | + | private version = 0; | |
| 45 | + | ||
| 46 | + | // ---- the local arrangement of this player's own hand ------------------- | |
| 47 | + | private order: Tile[] = []; | |
| 48 | + | private orderRev = 0; | |
| 49 | + | /** The player slid the drawn tile into the row, so the 摸 marker comes off. */ | |
| 50 | + | private drawnMerged = false; | |
| 51 | + | private drawnKey = ''; | |
| 52 | + | /** A discard sent but not yet echoed back, hidden from the hand meanwhile. */ | |
| 53 | + | private pending: { tile: Tile; len: number; at: number } | null = null; | |
| 54 | + | ||
| 55 | + | private view: GameState | null = null; | |
| 56 | + | private viewKey = ''; | |
| 57 | + | private lastAdopted: unknown = null; | |
| 58 | + | ||
| 59 | + | private sounds = new Set<(cue: SoundCue) => void>(); | |
| 60 | + | ||
| 61 | + | constructor(wire: TableWire) { | |
| 62 | + | this.wire = wire; | |
| 63 | + | this.engine = new Game(); | |
| 64 | + | this.engine.persist = false; | |
| 65 | + | this.engine.subscribe(() => this.bump()); | |
| 66 | + | } | |
| 67 | + | ||
| 68 | + | // ---- store plumbing ---------------------------------------------------- | |
| 69 | + | subscribe = (fn: () => void) => { | |
| 70 | + | this.listeners.add(fn); | |
| 71 | + | return () => { | |
| 72 | + | this.listeners.delete(fn); | |
| 73 | + | }; | |
| 74 | + | }; | |
| 75 | + | getSnapshot = () => this.version; | |
| 76 | + | private bump() { | |
| 77 | + | this.version++; | |
| 78 | + | for (const l of this.listeners) l(); | |
| 79 | + | } | |
| 80 | + | ||
| 81 | + | /** The host published a new state and we are not the host: mirror it. */ | |
| 82 | + | adopt(shared: unknown) { | |
| 83 | + | if (this.wire.isAuthority() || shared === this.lastAdopted || !shared) return; | |
| 84 | + | this.lastAdopted = shared; | |
| 85 | + | // The room hands its state out read-only and the engine mutates in place, | |
| 86 | + | // so the mirror gets its own copy. | |
| 87 | + | this.engine.restore(structuredClone(shared) as GameState); | |
| 88 | + | } | |
| 89 | + | ||
| 90 | + | // ---- the state the UI draws -------------------------------------------- | |
| 91 | + | get state(): GameState { | |
| 92 | + | const seat = this.wire.mySeat(); | |
| 93 | + | const s = this.engine.state; | |
| 94 | + | if (seat === null) return s; | |
| 95 | + | ||
| 96 | + | const key = `${this.version}:${this.orderRev}`; | |
| 97 | + | if (this.view && this.viewKey === key) return this.view; | |
| 98 | + | ||
| 99 | + | // A fresh draw un-merges: the marker is about *this* tile, not the last. | |
| 100 | + | const drawnKey = `${s.handNumber}:${s.turn}:${s.drawnTile}:${s.players[seat].hand.length}`; | |
| 101 | + | if (drawnKey !== this.drawnKey) { | |
| 102 | + | this.drawnKey = drawnKey; | |
| 103 | + | this.drawnMerged = false; | |
| 104 | + | } | |
| 105 | + | ||
| 106 | + | this.order = reconcileOrder(this.order, s.players[seat].hand); | |
| 107 | + | let hand = this.order; | |
| 108 | + | const p = this.pending; | |
| 109 | + | if (p) { | |
| 110 | + | if (s.players[seat].hand.length <= p.len || Date.now() - p.at > PENDING_TTL) { | |
| 111 | + | this.pending = null; | |
| 112 | + | } else { | |
| 113 | + | const i = hand.indexOf(p.tile); | |
| 114 | + | if (i >= 0) hand = [...hand.slice(0, i), ...hand.slice(i + 1)]; | |
| 115 | + | } | |
| 116 | + | } | |
| 117 | + | ||
| 118 | + | const players = s.players.slice(); | |
| 119 | + | players[seat] = { ...players[seat], hand }; | |
| 120 | + | this.view = { | |
| 121 | + | ...s, | |
| 122 | + | players, | |
| 123 | + | drawnSeparate: s.drawnSeparate && (s.turn !== seat || !this.drawnMerged), | |
| 124 | + | }; | |
| 125 | + | this.viewKey = key; | |
| 126 | + | return this.view; | |
| 127 | + | } | |
| 128 | + | ||
| 129 | + | // ---- actions ----------------------------------------------------------- | |
| 130 | + | private act(seat: SeatId, act: Act, run: () => void) { | |
| 131 | + | if (this.wire.isAuthority()) run(); | |
| 132 | + | else this.wire.sendAct(seat, act); | |
| 133 | + | } | |
| 134 | + | ||
| 135 | + | discard(seat: SeatId, tile: Tile, thrown?: NetThrow) { | |
| 136 | + | if (this.wire.isAuthority()) return this.engine.discard(seat, tile); | |
| 137 | + | if (seat !== this.wire.mySeat()) return; | |
| 138 | + | if (this.pending) return; // one throw at a time | |
| 139 | + | const held = this.state.players[seat].hand.length; | |
| 140 | + | this.wire.sendAct(seat, { kind: 'discard', tile, thrown }); | |
| 141 | + | this.pending = { tile, len: held - 1, at: Date.now() }; | |
| 142 | + | this.orderRev++; | |
| 143 | + | this.bump(); | |
| 144 | + | } | |
| 145 | + | ||
| 146 | + | respond(seat: SeatId, claim: Claim | 'pass') { | |
| 147 | + | this.act(seat, { kind: 'respond', claim }, () => this.engine.respond(seat, claim)); | |
| 148 | + | } | |
| 149 | + | ||
| 150 | + | resolveNow() { | |
| 151 | + | if (this.wire.isAuthority()) return this.engine.resolveNow(); | |
| 152 | + | const seat = this.wire.mySeat(); | |
| 153 | + | if (seat !== null) this.wire.sendAct(seat, { kind: 'resolveNow' }); | |
| 154 | + | } | |
| 155 | + | ||
| 156 | + | declareConcealedKong(seat: SeatId, tile: Tile) { | |
| 157 | + | this.act(seat, { kind: 'ankong', tile }, () => this.engine.declareConcealedKong(seat, tile)); | |
| 158 | + | } | |
| 159 | + | ||
| 160 | + | declareAddedKong(seat: SeatId, tile: Tile) { | |
| 161 | + | this.act(seat, { kind: 'addkong', tile }, () => this.engine.declareAddedKong(seat, tile)); | |
| 162 | + | } | |
| 163 | + | ||
| 164 | + | declareSelfDraw(seat: SeatId) { | |
| 165 | + | this.act(seat, { kind: 'selfDraw' }, () => this.engine.declareSelfDraw(seat)); | |
| 166 | + | } | |
| 167 | + | ||
| 168 | + | nextHand() { | |
| 169 | + | if (this.wire.isAuthority()) return this.engine.nextHand(); | |
| 170 | + | const seat = this.wire.mySeat(); | |
| 171 | + | if (seat !== null) this.wire.sendAct(seat, { kind: 'nextHand' }); | |
| 172 | + | } | |
| 173 | + | ||
| 174 | + | newGame(bots?: boolean[], names?: string[]) { | |
| 175 | + | if (this.wire.isAuthority()) return this.engine.newGame(bots, names); | |
| 176 | + | const seat = this.wire.mySeat(); | |
| 177 | + | if (seat !== null) this.wire.sendAct(seat, { kind: 'newGame' }); | |
| 178 | + | } | |
| 179 | + | ||
| 180 | + | applySettings(names: string[], rules: Rules) { | |
| 181 | + | if (this.wire.isAuthority()) this.engine.applySettings(names, rules); | |
| 182 | + | } | |
| 183 | + | ||
| 184 | + | // Your own hand is arranged on your own device and nowhere else. Somebody | |
| 185 | + | // else's hand only reaches here on the party screen, whose engine is real. | |
| 186 | + | reorderHand(seat: SeatId, from: number, to: number) { | |
| 187 | + | if (seat === this.wire.mySeat()) { | |
| 188 | + | const hand = this.order; | |
| 189 | + | if (from === to || from < 0 || to < 0 || from >= hand.length || to >= hand.length) return; | |
| 190 | + | const s = this.engine.state; | |
| 191 | + | const last = hand.length - 1; | |
| 192 | + | if (s.drawnSeparate && s.turn === seat && (from === last || to === last)) this.drawnMerged = true; | |
| 193 | + | const [t] = hand.splice(from, 1); | |
| 194 | + | hand.splice(to, 0, t); | |
| 195 | + | this.orderRev++; | |
| 196 | + | this.bump(); | |
| 197 | + | return; | |
| 198 | + | } | |
| 199 | + | if (this.wire.isAuthority()) this.engine.reorderHand(seat, from, to); | |
| 200 | + | } | |
| 201 | + | ||
| 202 | + | sortHand(seat: SeatId) { | |
| 203 | + | if (seat === this.wire.mySeat()) { | |
| 204 | + | this.order = sortTiles(this.order); | |
| 205 | + | this.drawnMerged = true; | |
| 206 | + | this.orderRev++; | |
| 207 | + | this.bump(); | |
| 208 | + | return; | |
| 209 | + | } | |
| 210 | + | if (this.wire.isAuthority()) this.engine.sortHand(seat); | |
| 211 | + | } | |
| 212 | + | ||
| 213 | + | // ---- undo -------------------------------------------------------------- | |
| 214 | + | undoable() { | |
| 215 | + | return this.wire.isAuthority() && this.allowUndo ? this.engine.undoable() : null; | |
| 216 | + | } | |
| 217 | + | undo() { | |
| 218 | + | if (this.wire.isAuthority() && this.allowUndo) this.engine.undo(); | |
| 219 | + | } | |
| 220 | + | ||
| 221 | + | // ---- queries, all answered by the engine off the same state ------------ | |
| 222 | + | canSelfDrawWin(seat: SeatId) { | |
| 223 | + | return this.engine.canSelfDrawWin(seat); | |
| 224 | + | } | |
| 225 | + | concealedKongs(seat: SeatId) { | |
| 226 | + | return this.engine.concealedKongs(seat); | |
| 227 | + | } | |
| 228 | + | addedKongs(seat: SeatId) { | |
| 229 | + | return this.engine.addedKongs(seat); | |
| 230 | + | } | |
| 231 | + | tenpai(seat: SeatId) { | |
| 232 | + | return this.engine.tenpai(seat); | |
| 233 | + | } | |
| 234 | + | sacredTiles(seat: SeatId) { | |
| 235 | + | return this.engine.sacredTiles(seat); | |
| 236 | + | } | |
| 237 | + | nextDrawer() { | |
| 238 | + | return this.engine.nextDrawer(); | |
| 239 | + | } | |
| 240 | + | get hasBots() { | |
| 241 | + | return this.engine.hasBots; | |
| 242 | + | } | |
| 243 | + | isBot(seat: SeatId) { | |
| 244 | + | return this.engine.isBot(seat); | |
| 245 | + | } | |
| 246 | + | get wallLeft() { | |
| 247 | + | return this.engine.wallLeft; | |
| 248 | + | } | |
| 249 | + | get roundWind() { | |
| 250 | + | return this.engine.roundWind; | |
| 251 | + | } | |
| 252 | + | ||
| 253 | + | // ---- sound ------------------------------------------------------------- | |
| 254 | + | /** Fed by the session: the engine's own cues on the host, fx events off it. */ | |
| 255 | + | emitSound(cue: SoundCue) { | |
| 256 | + | for (const l of this.sounds) l(cue); | |
| 257 | + | } | |
| 258 | + | onSound = (fn: (cue: SoundCue) => void) => { | |
| 259 | + | this.sounds.add(fn); | |
| 260 | + | return () => { | |
| 261 | + | this.sounds.delete(fn); | |
| 262 | + | }; | |
| 263 | + | }; | |
| 264 | + | } |
modifiedsrc/styles.css+224 −3
| ⋯ 210 unchanged lines | |||
| 211 | 211 | --tw: calc(var(--tile-w) * 0.64); | |
| 212 | 212 | } | |
| 213 | 213 | .tile.face { | |
| 214 | - | background-image: url('/tiles/tiles.svg'); | |
| 214 | + | background-image: url('./assets/tiles.svg'); | |
| 215 | 215 | background-repeat: no-repeat; | |
| 216 | 216 | background-size: calc(var(--tw) * 43) calc(var(--tw) * 2.75); | |
| 217 | 217 | background-position: calc(var(--col) * var(--tw) * -1) 0; | |
| 218 | 218 | filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.45)); | |
| 219 | 219 | } | |
| 220 | 220 | .tile.back { | |
| 221 | - | background-image: url('/tiles/back.png'); | |
| 221 | + | background-image: url('./assets/back.png'); | |
| 222 | 222 | background-size: 100% 100%; | |
| 223 | 223 | filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.45)); | |
| 224 | 224 | } | |
| ⋯ 501 unchanged lines | |||
| 726 | 726 | width: var(--ws); | |
| 727 | 727 | height: var(--wd); | |
| 728 | 728 | margin: calc(var(--wd) / -2) 0 0 calc(var(--ws) / -2); | |
| 729 | - | background-image: url('/tiles/back.png'); | |
| 729 | + | background-image: url('./assets/back.png'); | |
| 730 | 730 | background-size: 100% 100%; | |
| 731 | 731 | filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.45)); | |
| 732 | 732 | } | |
| ⋯ 1257 unchanged lines | |||
| 1990 | 1990 | display: block; | |
| 1991 | 1991 | flex: none; | |
| 1992 | 1992 | } | |
| 1993 | + | ||
| 1994 | + | /* ---------- the party table's QRs ---------- */ | |
| 1995 | + | /* A free seat wears its QR at the end of the strip, inside the seat's own | |
| 1996 | + | rotation so it sits by the right edge of the table whoever it belongs to. | |
| 1997 | + | It must not cover the hand: an unclaimed seat is still played right here. */ | |
| 1998 | + | .qr-dock { | |
| 1999 | + | position: absolute; | |
| 2000 | + | right: 6px; | |
| 2001 | + | bottom: 34px; | |
| 2002 | + | z-index: 3; | |
| 2003 | + | display: flex; | |
| 2004 | + | flex-direction: column; | |
| 2005 | + | align-items: center; | |
| 2006 | + | gap: 4px; | |
| 2007 | + | padding: 6px; | |
| 2008 | + | border-radius: 10px; | |
| 2009 | + | background: rgba(10, 49, 38, 0.92); | |
| 2010 | + | border: 1px solid rgba(217, 180, 92, 0.45); | |
| 2011 | + | } | |
| 2012 | + | .seat-qr { | |
| 2013 | + | width: clamp(72px, calc(var(--depth) * 0.44), 132px); | |
| 2014 | + | aspect-ratio: 1; | |
| 2015 | + | border-radius: 6px; | |
| 2016 | + | overflow: hidden; | |
| 2017 | + | background: #fff; | |
| 2018 | + | padding: 3px; | |
| 2019 | + | } | |
| 2020 | + | .seat-qr svg { | |
| 2021 | + | display: block; | |
| 2022 | + | width: 100%; | |
| 2023 | + | height: 100%; | |
| 2024 | + | } | |
| 2025 | + | .qr-hint { | |
| 2026 | + | font-size: 10px; | |
| 2027 | + | line-height: 1.3; | |
| 2028 | + | color: var(--ivory); | |
| 2029 | + | text-align: center; | |
| 2030 | + | max-width: 140px; | |
| 2031 | + | } | |
| 2032 | + | .qr-hint em { | |
| 2033 | + | display: block; | |
| 2034 | + | font-style: normal; | |
| 2035 | + | opacity: 0.6; | |
| 2036 | + | font-size: 9px; | |
| 2037 | + | } | |
| 2038 | + | .nameplate .tag.phone { | |
| 2039 | + | background: rgba(29, 90, 168, 0.35); | |
| 2040 | + | border-color: rgba(29, 90, 168, 0.7); | |
| 2041 | + | cursor: pointer; | |
| 2042 | + | user-select: none; | |
| 2043 | + | touch-action: none; | |
| 2044 | + | } | |
| 2045 | + | ||
| 2046 | + | /* ---------- the phone controller ---------- */ | |
| 2047 | + | /* A hand held in landscape: status, buttons, tiles, in that order down the | |
| 2048 | + | screen, with the common screen doing everything else. Tiles get the room — | |
| 2049 | + | they are the only thing this screen is for. */ | |
| 2050 | + | .ctl { | |
| 2051 | + | --tile-w: clamp(40px, 5.2vw, 76px); | |
| 2052 | + | position: fixed; | |
| 2053 | + | inset: 0; | |
| 2054 | + | display: flex; | |
| 2055 | + | flex-direction: column; | |
| 2056 | + | background: radial-gradient(ellipse at center, var(--felt) 0%, var(--felt-2) 80%); | |
| 2057 | + | color: var(--ivory); | |
| 2058 | + | overflow: hidden; | |
| 2059 | + | touch-action: none; | |
| 2060 | + | } | |
| 2061 | + | .ctl-top { | |
| 2062 | + | display: flex; | |
| 2063 | + | align-items: center; | |
| 2064 | + | gap: 8px; | |
| 2065 | + | padding: 8px 12px 4px; | |
| 2066 | + | flex-wrap: wrap; | |
| 2067 | + | font-size: 13px; | |
| 2068 | + | } | |
| 2069 | + | .ctl-top .wind { | |
| 2070 | + | font-weight: 900; | |
| 2071 | + | font-size: 18px; | |
| 2072 | + | color: var(--gold); | |
| 2073 | + | } | |
| 2074 | + | .ctl-top .wind em { | |
| 2075 | + | font-style: normal; | |
| 2076 | + | font-size: 10px; | |
| 2077 | + | margin-left: 2px; | |
| 2078 | + | opacity: 0.7; | |
| 2079 | + | } | |
| 2080 | + | .ctl-top .pname { | |
| 2081 | + | font-weight: 600; | |
| 2082 | + | } | |
| 2083 | + | .ctl-top .tag { | |
| 2084 | + | border: 1px solid rgba(246, 240, 226, 0.4); | |
| 2085 | + | border-radius: 20px; | |
| 2086 | + | padding: 1px 8px; | |
| 2087 | + | font-size: 11px; | |
| 2088 | + | } | |
| 2089 | + | .ctl-top .tag.ting { | |
| 2090 | + | border-color: var(--gold); | |
| 2091 | + | color: var(--gold); | |
| 2092 | + | } | |
| 2093 | + | .ctl-status { | |
| 2094 | + | margin-left: auto; | |
| 2095 | + | opacity: 0.75; | |
| 2096 | + | } | |
| 2097 | + | .ctl-status em { | |
| 2098 | + | font-style: normal; | |
| 2099 | + | opacity: 0.6; | |
| 2100 | + | margin-left: 4px; | |
| 2101 | + | } | |
| 2102 | + | .ctl-status.hot { | |
| 2103 | + | opacity: 1; | |
| 2104 | + | color: var(--gold); | |
| 2105 | + | font-weight: 600; | |
| 2106 | + | } | |
| 2107 | + | .ctl-last { | |
| 2108 | + | display: inline-flex; | |
| 2109 | + | align-items: center; | |
| 2110 | + | gap: 4px; | |
| 2111 | + | } | |
| 2112 | + | .ctl-top .chips { | |
| 2113 | + | font-weight: 600; | |
| 2114 | + | color: var(--gold); | |
| 2115 | + | } | |
| 2116 | + | .ctl-actions { | |
| 2117 | + | display: flex; | |
| 2118 | + | align-items: center; | |
| 2119 | + | justify-content: center; | |
| 2120 | + | gap: 8px; | |
| 2121 | + | flex-wrap: wrap; | |
| 2122 | + | min-height: var(--bar-h); | |
| 2123 | + | padding: 2px 10px; | |
| 2124 | + | } | |
| 2125 | + | .ctl-hint { | |
| 2126 | + | font-size: 11px; | |
| 2127 | + | opacity: 0.55; | |
| 2128 | + | } | |
| 2129 | + | .ctl-hand { | |
| 2130 | + | margin-top: auto; | |
| 2131 | + | display: flex; | |
| 2132 | + | justify-content: center; | |
| 2133 | + | padding: 0 8px calc(10px + env(safe-area-inset-bottom, 0px)); | |
| 2134 | + | overflow-x: auto; | |
| 2135 | + | } | |
| 2136 | + | .ctl-hand .hand { | |
| 2137 | + | flex-wrap: nowrap; | |
| 2138 | + | } | |
| 2139 | + | /* The tile in the fingers once it has left the row, and its exit: flung, it | |
| 2140 | + | keeps going up off the screen — the common screen takes it from here. */ | |
| 2141 | + | .ctl-held { | |
| 2142 | + | position: fixed; | |
| 2143 | + | z-index: 5; | |
| 2144 | + | pointer-events: none; | |
| 2145 | + | transform: translate(-50%, -50%); | |
| 2146 | + | } | |
| 2147 | + | .ctl-held .tile { | |
| 2148 | + | width: 100%; | |
| 2149 | + | height: 100%; | |
| 2150 | + | } | |
| 2151 | + | .ctl-held.flung { | |
| 2152 | + | animation: fling-up 0.3s ease-in forwards; | |
| 2153 | + | } | |
| 2154 | + | @keyframes fling-up { | |
| 2155 | + | to { | |
| 2156 | + | transform: translate(-50%, -140vh) rotate(18deg); | |
| 2157 | + | opacity: 0.9; | |
| 2158 | + | } | |
| 2159 | + | } | |
| 2160 | + | .ctl-result { | |
| 2161 | + | position: fixed; | |
| 2162 | + | left: 50%; | |
| 2163 | + | top: 44%; | |
| 2164 | + | transform: translate(-50%, -50%); | |
| 2165 | + | z-index: 6; | |
| 2166 | + | background: rgba(10, 49, 38, 0.96); | |
| 2167 | + | border: 1px solid rgba(217, 180, 92, 0.5); | |
| 2168 | + | border-radius: 14px; | |
| 2169 | + | padding: 16px 22px; | |
| 2170 | + | text-align: center; | |
| 2171 | + | max-width: min(92vw, 480px); | |
| 2172 | + | } | |
| 2173 | + | .ctl-result h2 { | |
| 2174 | + | margin: 0 0 8px; | |
| 2175 | + | font-size: 18px; | |
| 2176 | + | } | |
| 2177 | + | .ctl-result .deltas { | |
| 2178 | + | display: flex; | |
| 2179 | + | gap: 12px; | |
| 2180 | + | flex-wrap: wrap; | |
| 2181 | + | justify-content: center; | |
| 2182 | + | margin-bottom: 12px; | |
| 2183 | + | font-size: 13px; | |
| 2184 | + | } | |
| 2185 | + | .ctl-result .deltas .up { | |
| 2186 | + | color: var(--gold); | |
| 2187 | + | } | |
| 2188 | + | .ctl-result .deltas .down { | |
| 2189 | + | color: #e08a84; | |
| 2190 | + | } | |
| 2191 | + | ||
| 2192 | + | /* Seat picking, on the phone and in the online lobby. */ | |
| 2193 | + | .ctl-pick { | |
| 2194 | + | align-items: center; | |
| 2195 | + | justify-content: center; | |
| 2196 | + | text-align: center; | |
| 2197 | + | overflow-y: auto; | |
| 2198 | + | touch-action: pan-y; | |
| 2199 | + | } | |
| 2200 | + | .pick-grid { | |
| 2201 | + | display: grid; | |
| 2202 | + | grid-template-columns: repeat(2, minmax(150px, 220px)); | |
| 2203 | + | gap: 10px; | |
| 2204 | + | justify-content: center; | |
| 2205 | + | margin: 12px 0; | |
| 2206 | + | } | |
| 2207 | + | .pick-grid .btn em { | |
| 2208 | + | display: block; | |
| 2209 | + | font-style: normal; | |
| 2210 | + | font-size: 11px; | |
| 2211 | + | opacity: 0.65; | |
| 2212 | + | margin-top: 2px; | |
| 2213 | + | } | |
modifiedsrc/table/pool.ts+75 −3
| 1 | + | import type { NetThrow } from '../game/ctl'; | |
| 1 | 2 | import type { GameState } from '../game/engine'; | |
| 2 | 3 | import { makeRng, type Tile } from '../game/tiles'; | |
| 3 | 4 | import type { SeatId } from '../game/types'; | |
| 5 | + | import { SEAT_ROT } from '../ui/rotation'; | |
| 4 | 6 | import { | |
| 5 | 7 | aimAt, | |
| 6 | 8 | canThrow, | |
| ⋯ 121 unchanged lines | |||
| 128 | 130 | /** Bodies by `seat:index` into that seat's discards. */ | |
| 129 | 131 | private entries = new Map<string, Body>(); | |
| 130 | 132 | private release: Release | null = null; | |
| 133 | + | /** When the throw was let go of. Online its discard takes a round-trip to | |
| 134 | + | * come back, so a release has to be able to outlive a few frames. */ | |
| 135 | + | private releasedAt = 0; | |
| 131 | 136 | /** What the wall looked like when we last measured, so we know to re-measure. */ | |
| 132 | 137 | private measuredAt = ''; | |
| 133 | 138 | private hand = -1; | |
| ⋯ 123 unchanged lines | |||
| 257 | 262 | vy: vy * scale, | |
| 258 | 263 | spin, | |
| 259 | 264 | }; | |
| 265 | + | this.releasedAt = Date.now(); | |
| 266 | + | } | |
| 267 | + | ||
| 268 | + | /** | |
| 269 | + | * A throw that happened on somebody's phone, arriving over the wire in their | |
| 270 | + | * own frame: `u` along their edge, `vy` into the table. It is stood at their | |
| 271 | + | * side of this screen and turned into the table's coordinates, so the tile | |
| 272 | + | * flies in from off their edge — from where they are sitting, as a thrown | |
| 273 | + | * tile should. The discard it belongs to arrives through the state as usual; | |
| 274 | + | * this only decides how it enters. | |
| 275 | + | */ | |
| 276 | + | throwFromNet(seat: SeatId, t: NetThrow) { | |
| 277 | + | const geo = this.geo; | |
| 278 | + | if (!geo) return; | |
| 279 | + | const f = geo.felt; | |
| 280 | + | // Just beyond the felt, where the thrower's strip is. | |
| 281 | + | const m = geo.tile.h * 1.4; | |
| 282 | + | const u = Math.min(1, Math.max(0, t.u)); | |
| 283 | + | let x: number; | |
| 284 | + | let y: number; | |
| 285 | + | let vx: number; | |
| 286 | + | let vy: number; | |
| 287 | + | // Each seat's frame: x runs to the player's right along their edge, y into | |
| 288 | + | // the table. The screen rotation for each seat is what `ROT` says it is. | |
| 289 | + | switch (seat) { | |
| 290 | + | case 0: | |
| 291 | + | x = f.x + u * f.w; | |
| 292 | + | y = f.y + f.h + m; | |
| 293 | + | vx = t.vx; | |
| 294 | + | vy = -t.vy; | |
| 295 | + | break; | |
| 296 | + | case 1: | |
| 297 | + | x = f.x + f.w + m; | |
| 298 | + | y = f.y + (1 - u) * f.h; | |
| 299 | + | vx = -t.vy; | |
| 300 | + | vy = -t.vx; | |
| 301 | + | break; | |
| 302 | + | case 2: | |
| 303 | + | x = f.x + (1 - u) * f.w; | |
| 304 | + | y = f.y - m; | |
| 305 | + | vx = -t.vx; | |
| 306 | + | vy = t.vy; | |
| 307 | + | break; | |
| 308 | + | default: | |
| 309 | + | x = f.x - m; | |
| 310 | + | y = f.y + u * f.h; | |
| 311 | + | vx = t.vy; | |
| 312 | + | vy = t.vx; | |
| 313 | + | break; | |
| 314 | + | } | |
| 315 | + | const speed = Math.hypot(vx, vy); | |
| 316 | + | const scale = speed > MAX_THROW_SPEED ? MAX_THROW_SPEED / speed : 1; | |
| 317 | + | this.release = { | |
| 318 | + | seat, | |
| 319 | + | x, | |
| 320 | + | y, | |
| 321 | + | // Facing its thrower, the way a tile lifted from their hand would be. | |
| 322 | + | angle: (SEAT_ROT[seat] * Math.PI) / 180, | |
| 323 | + | w: geo.tile.w, | |
| 324 | + | h: geo.tile.h, | |
| 325 | + | vx: vx * scale, | |
| 326 | + | vy: vy * scale, | |
| 327 | + | spin: t.spin, | |
| 328 | + | }; | |
| 329 | + | this.releasedAt = Date.now(); | |
| 260 | 330 | } | |
| 261 | 331 | ||
| 262 | 332 | // ---- keeping up with the game ---------------------------------------- | |
| ⋯ 49 unchanged lines | |||
| 312 | 382 | this.entries.set(a.key, rebuilding ? this.settle(geo, state, a) : this.launch(geo, state, a)); | |
| 313 | 383 | } | |
| 314 | 384 | ||
| 315 | - | // A throw is good for exactly one discard, and the tile it belonged to has | |
| 316 | - | // either just arrived or is never going to. | |
| 317 | - | if (added.length || this.release) { | |
| 385 | + | // A throw is good for exactly one discard. At a local table the discard is | |
| 386 | + | // in the state before the next frame, so the tile has either just arrived | |
| 387 | + | // or is never going to; online it is out on a round-trip, so the release | |
| 388 | + | // waits — but not forever, in case the host turned the move down. | |
| 389 | + | if (added.length || (this.release && Date.now() - this.releasedAt > 2500)) { | |
| 318 | 390 | this.release = null; | |
| 319 | 391 | this.held = null; | |
| 320 | 392 | } | |
| ⋯ 286 unchanged lines | |||
modifiedsrc/ui/Center.tsx+15 −5
| 1 | - | import type { Game } from '../game/engine'; | |
| 1 | + | import type { TableCtl } from '../game/ctl'; | |
| 2 | 2 | import type { Prefs } from '../game/prefs'; | |
| 3 | 3 | import type { SeatId } from '../game/types'; | |
| 4 | 4 | import { labelOf } from '../game/tiles'; | |
| ⋯ 5 unchanged lines | |||
| 10 | 10 | import { WallRing } from './WallRing'; | |
| 11 | 11 | ||
| 12 | 12 | interface Props { | |
| 13 | - | game: Game; | |
| 13 | + | game: TableCtl; | |
| 14 | 14 | prefs: Prefs; | |
| 15 | 15 | /** Phone-sized table: the centre becomes a single bar between the seats. */ | |
| 16 | 16 | compact?: boolean; | |
| 17 | + | /** The seat drawn at the bottom of this screen — 0 except online, where the | |
| 18 | + | * table is turned so it is your own. */ | |
| 19 | + | viewSeat?: SeatId; | |
| 20 | + | /** Online: the one person reading this screen, whatever the bots are doing. */ | |
| 21 | + | preferSeat?: SeatId | null; | |
| 17 | 22 | onPrefs: (p: Prefs) => void; | |
| 18 | 23 | onSettings: () => void; | |
| 19 | 24 | } | |
| 20 | 25 | ||
| 21 | - | export function Center({ game, prefs, compact, onPrefs, onSettings }: Props) { | |
| 26 | + | export function Center({ game, prefs, compact, viewSeat = 0, preferSeat = null, onPrefs, onSettings }: Props) { | |
| 22 | 27 | const s = game.state; | |
| 23 | 28 | const r = s.result; | |
| 24 | 29 | // Point the live readout at whoever can move the game along. | |
| ⋯ 3 unchanged lines | |||
| 28 | 33 | // Against the computer there is only one person reading this, and they are | |
| 29 | 34 | // sitting at the bottom of the screen. | |
| 30 | 35 | const human = s.bots.indexOf(false); | |
| 31 | - | const facing = human >= 0 && game.hasBots ? (human as SeatId) : game.nextDrawer() ?? s.turn; | |
| 36 | + | const facing = | |
| 37 | + | preferSeat ?? (human >= 0 && game.hasBots ? (human as SeatId) : game.nextDrawer() ?? s.turn); | |
| 32 | 38 | // Deliberately not per-seat: whoever spots the mis-tap should be able to | |
| 33 | 39 | // reach it, not just the player who made it. | |
| 34 | 40 | const undoable = s.phase === 'gameEnd' ? null : game.undoable(); | |
| ⋯ 15 unchanged lines | |||
| 50 | 56 | ||
| 51 | 57 | <div | |
| 52 | 58 | className="facing" | |
| 53 | - | style={compact ? undefined : { transform: `rotate(${SEAT_ROT[facing]}deg)` }} | |
| 59 | + | style={ | |
| 60 | + | compact | |
| 61 | + | ? undefined | |
| 62 | + | : { transform: `rotate(${SEAT_ROT[(((facing - viewSeat + 4) % 4) as SeatId)]}deg)` } | |
| 63 | + | } | |
| 54 | 64 | > | |
| 55 | 65 | {/* The tile just thrown is out there in the middle, lit, where everyone | |
| 56 | 66 | can see it — so saying it again here would only cover the pile. The | |
| ⋯ 136 unchanged lines | |||
addedsrc/ui/Controller.tsx+273 −0
| 1 | + | import { useEffect, useRef, useState } from 'react'; | |
| 2 | + | import { pendingOptions } from '../game/ctl'; | |
| 3 | + | import { labelOf, WIND_EN, WIND_NAMES, type Tile } from '../game/tiles'; | |
| 4 | + | import type { SeatId } from '../game/types'; | |
| 5 | + | import type { NetSession } from '../net/session'; | |
| 6 | + | import { seatFromUrl } from '../net/protocol'; | |
| 7 | + | import { Bi, CLAIM_LABEL } from './Bi'; | |
| 8 | + | import { Hand, type Throw } from './Hand'; | |
| 9 | + | import { TileView } from './TileView'; | |
| 10 | + | ||
| 11 | + | const SEATS: SeatId[] = [0, 1, 2, 3]; | |
| 12 | + | ||
| 13 | + | /** | |
| 14 | + | * The phone at the party table: just your own tiles, held in landscape, with | |
| 15 | + | * the common screen doing all the talking. What it keeps of the full table is | |
| 16 | + | * the two things that are yours — the buttons a claim window owes you, and the | |
| 17 | + | * throw. Flick a tile up off the top of the phone and it leaves your hand | |
| 18 | + | * here, crosses the wire, and comes flying in from your edge of the shared | |
| 19 | + | * screen. | |
| 20 | + | */ | |
| 21 | + | export function Controller({ session }: { session: NetSession }) { | |
| 22 | + | const seat = session.mySeat; | |
| 23 | + | ||
| 24 | + | // The QR carries which seat it was stuck to; a phone that arrived some other | |
| 25 | + | // way — or lost the parameter on the way in — picks one by hand below. | |
| 26 | + | const asked = useRef(false); | |
| 27 | + | useEffect(() => { | |
| 28 | + | if (asked.current || seat !== null) return; | |
| 29 | + | const want = seatFromUrl(); | |
| 30 | + | if (want !== null && session.seats[want].id === null) { | |
| 31 | + | asked.current = true; | |
| 32 | + | session.claimSeat(want); | |
| 33 | + | } | |
| 34 | + | }, [session, seat]); | |
| 35 | + | ||
| 36 | + | if (seat === null) return <SeatPick session={session} />; | |
| 37 | + | return <SeatStrip session={session} seat={seat} />; | |
| 38 | + | } | |
| 39 | + | ||
| 40 | + | function SeatPick({ session }: { session: NetSession }) { | |
| 41 | + | const seats = session.seats; | |
| 42 | + | const s = session.table.state; | |
| 43 | + | return ( | |
| 44 | + | <div className="ctl ctl-pick"> | |
| 45 | + | <h1>台灣麻將</h1> | |
| 46 | + | <p className="sub">選一個座位 · pick your seat</p> | |
| 47 | + | <div className="pick-grid"> | |
| 48 | + | {SEATS.map((i) => { | |
| 49 | + | const taken = seats[i].id !== null; | |
| 50 | + | const wind = (i - s.dealer + 4) % 4; | |
| 51 | + | return ( | |
| 52 | + | <button | |
| 53 | + | key={i} | |
| 54 | + | className="btn big-btn" | |
| 55 | + | disabled={taken} | |
| 56 | + | onClick={() => session.claimSeat(i)} | |
| 57 | + | > | |
| 58 | + | {WIND_NAMES[wind]} {WIND_EN[wind]} | |
| 59 | + | <em>{taken ? `已有人 · ${seats[i].name}` : seats[i].name || '空位 open'}</em> | |
| 60 | + | </button> | |
| 61 | + | ); | |
| 62 | + | })} | |
| 63 | + | </div> | |
| 64 | + | </div> | |
| 65 | + | ); | |
| 66 | + | } | |
| 67 | + | ||
| 68 | + | /** A tile in the fingers, drawn by us once it has left the row. */ | |
| 69 | + | interface HeldTile { | |
| 70 | + | tile: Tile; | |
| 71 | + | x: number; | |
| 72 | + | y: number; | |
| 73 | + | size: number; | |
| 74 | + | /** Set when it has been let go of: it flies up off the screen and is gone. */ | |
| 75 | + | flung: boolean; | |
| 76 | + | } | |
| 77 | + | ||
| 78 | + | function SeatStrip({ session, seat }: { session: NetSession; seat: SeatId }) { | |
| 79 | + | const table = session.table; | |
| 80 | + | const s = table.state; | |
| 81 | + | const p = s.players[seat]; | |
| 82 | + | const [sel, setSel] = useState<number | null>(null); | |
| 83 | + | const [held, setHeld] = useState<HeldTile | null>(null); | |
| 84 | + | ||
| 85 | + | const myTurn = s.phase === 'turn' && s.turn === seat; | |
| 86 | + | const opts = pendingOptions(s, seat); | |
| 87 | + | const canProceed = table.nextDrawer() === seat; | |
| 88 | + | const canHu = myTurn && table.canSelfDrawWin(seat); | |
| 89 | + | const concealedKongs = myTurn ? table.concealedKongs(seat) : []; | |
| 90 | + | const addedKongs = myTurn ? table.addedKongs(seat) : []; | |
| 91 | + | const tenpai = s.phase !== 'handEnd' && s.phase !== 'gameEnd' ? table.tenpai(seat) : []; | |
| 92 | + | const drawnIndex = myTurn && s.drawnSeparate && s.drawnTile !== null ? p.hand.length - 1 : null; | |
| 93 | + | const selTile = sel === null ? null : p.hand[sel] ?? null; | |
| 94 | + | const seatWind = (seat - s.dealer + 4) % 4; | |
| 95 | + | const r = s.result; | |
| 96 | + | ||
| 97 | + | useEffect(() => { | |
| 98 | + | setSel(null); | |
| 99 | + | }, [myTurn, s.handNumber, p.hand.length]); | |
| 100 | + | ||
| 101 | + | const doDiscard = (i: number) => { | |
| 102 | + | table.discard(seat, p.hand[i]); | |
| 103 | + | setSel(null); | |
| 104 | + | }; | |
| 105 | + | ||
| 106 | + | // The flick. `Hand` decides when a drag has become a throw; all that is left | |
| 107 | + | // here is to say it in the seat's own frame — how far along the edge it left | |
| 108 | + | // from, and how fast it was going up and across — and let it go. | |
| 109 | + | const fling = (i: number, t: Throw) => { | |
| 110 | + | const tile = p.hand[i]; | |
| 111 | + | if (tile === undefined) return setHeld(null); | |
| 112 | + | table.discard(seat, tile, { | |
| 113 | + | u: t.x / window.innerWidth, | |
| 114 | + | vx: t.vx, | |
| 115 | + | // Up off the top of the phone is into the table. | |
| 116 | + | vy: -t.vy, | |
| 117 | + | spin: t.spin, | |
| 118 | + | }); | |
| 119 | + | setSel(null); | |
| 120 | + | setHeld((h) => (h ? { ...h, x: t.x, y: t.y, flung: true } : null)); | |
| 121 | + | }; | |
| 122 | + | ||
| 123 | + | const carry = { | |
| 124 | + | onPickUp: (i: number, x: number, y: number, size: number) => | |
| 125 | + | setHeld({ tile: p.hand[i], x, y, size, flung: false }), | |
| 126 | + | onMoveTo: (x: number, y: number) => setHeld((h) => (h && !h.flung ? { ...h, x, y } : h)), | |
| 127 | + | onThrow: fling, | |
| 128 | + | onCancel: () => setHeld(null), | |
| 129 | + | }; | |
| 130 | + | ||
| 131 | + | const status = myTurn | |
| 132 | + | ? { zh: '輪到你', en: 'your turn', hot: true } | |
| 133 | + | : opts | |
| 134 | + | ? { zh: '要叫牌嗎?', en: 'claim it?', hot: true } | |
| 135 | + | : { zh: `等 ${s.players[s.turn].name}`, en: 'waiting', hot: false }; | |
| 136 | + | ||
| 137 | + | return ( | |
| 138 | + | <div className="ctl"> | |
| 139 | + | <div className="ctl-top"> | |
| 140 | + | <span className="wind"> | |
| 141 | + | {WIND_NAMES[seatWind]} | |
| 142 | + | <em>{WIND_EN[seatWind]}</em> | |
| 143 | + | </span> | |
| 144 | + | <span className="pname">{p.name}</span> | |
| 145 | + | {seat === s.dealer && <span className="tag">莊</span>} | |
| 146 | + | {tenpai.length > 0 && <span className="tag ting">聽 {tenpai.map(labelOf).join('·')}</span>} | |
| 147 | + | <span className={`ctl-status ${status.hot ? 'hot' : ''}`}> | |
| 148 | + | {status.zh} <em>{status.en}</em> | |
| 149 | + | </span> | |
| 150 | + | {s.lastDiscard && s.phase !== 'handEnd' && ( | |
| 151 | + | <span className="ctl-last"> | |
| 152 | + | {s.players[s.lastDiscard.from].name} 打<TileView tile={s.lastDiscard.tile} size="small" /> | |
| 153 | + | </span> | |
| 154 | + | )} | |
| 155 | + | <span className="chips">{p.score}</span> | |
| 156 | + | <button className="btn ghost sort" onClick={() => table.sortHand(seat)}> | |
| 157 | + | 理牌 | |
| 158 | + | </button> | |
| 159 | + | </div> | |
| 160 | + | ||
| 161 | + | <div className="ctl-actions"> | |
| 162 | + | {opts?.map((o, i) => ( | |
| 163 | + | <button | |
| 164 | + | key={i} | |
| 165 | + | className={`btn claim ${o.type}`} | |
| 166 | + | onClick={() => table.respond(seat, { type: o.type, with: o.with })} | |
| 167 | + | > | |
| 168 | + | <Bi zh={CLAIM_LABEL[o.type][0]} en={CLAIM_LABEL[o.type][1]} sub={o.with && o.with.map(labelOf).join(' ')} /> | |
| 169 | + | </button> | |
| 170 | + | ))} | |
| 171 | + | {opts && !canProceed && ( | |
| 172 | + | <button className="btn pass" onClick={() => table.respond(seat, 'pass')}> | |
| 173 | + | <Bi zh="過" en="Pass" /> | |
| 174 | + | </button> | |
| 175 | + | )} | |
| 176 | + | {canProceed && ( | |
| 177 | + | <button className={`btn proceed ${opts ? 'pass' : ''}`} onClick={() => table.resolveNow()}> | |
| 178 | + | <Bi | |
| 179 | + | zh={s.phase === 'robkong' ? '繼續' : opts ? '過.摸牌' : '摸牌'} | |
| 180 | + | en={s.phase === 'robkong' ? 'Continue' : opts ? 'Pass & draw' : 'Draw'} | |
| 181 | + | /> | |
| 182 | + | </button> | |
| 183 | + | )} | |
| 184 | + | {canHu && ( | |
| 185 | + | <button className="btn claim hu" onClick={() => table.declareSelfDraw(seat)}> | |
| 186 | + | <Bi zh="自摸" en="Self-draw" /> | |
| 187 | + | </button> | |
| 188 | + | )} | |
| 189 | + | {concealedKongs.map((t) => ( | |
| 190 | + | <button key={`ak${t}`} className="btn claim kong" onClick={() => table.declareConcealedKong(seat, t)}> | |
| 191 | + | <Bi zh="暗槓" en="Kong" sub={labelOf(t)} /> | |
| 192 | + | </button> | |
| 193 | + | ))} | |
| 194 | + | {addedKongs.map((t) => ( | |
| 195 | + | <button key={`gk${t}`} className="btn claim kong" onClick={() => table.declareAddedKong(seat, t)}> | |
| 196 | + | <Bi zh="加槓" en="Kong" sub={labelOf(t)} /> | |
| 197 | + | </button> | |
| 198 | + | ))} | |
| 199 | + | {selTile !== null && sel !== null && myTurn && ( | |
| 200 | + | <button className="btn discard" onClick={() => doDiscard(sel)}> | |
| 201 | + | <Bi zh="打出" en="Discard" sub={labelOf(selTile)} /> | |
| 202 | + | </button> | |
| 203 | + | )} | |
| 204 | + | {myTurn && <span className="ctl-hint">往上甩出去 · flick a tile up to throw it</span>} | |
| 205 | + | </div> | |
| 206 | + | ||
| 207 | + | <div className="ctl-hand"> | |
| 208 | + | <Hand | |
| 209 | + | tiles={p.hand} | |
| 210 | + | drawnIndex={drawnIndex} | |
| 211 | + | selected={sel} | |
| 212 | + | canDiscard={myTurn} | |
| 213 | + | onSelect={setSel} | |
| 214 | + | onDiscard={doDiscard} | |
| 215 | + | onMove={(from, to) => table.reorderHand(seat, from, to)} | |
| 216 | + | carry={carry} | |
| 217 | + | /> | |
| 218 | + | </div> | |
| 219 | + | ||
| 220 | + | {/* The tile out of the row, following the finger — and, once flung, on | |
| 221 | + | its way up off the screen to reappear on the common one. */} | |
| 222 | + | {held && ( | |
| 223 | + | <div | |
| 224 | + | className={`ctl-held ${held.flung ? 'flung' : ''}`} | |
| 225 | + | style={{ left: held.x, top: held.y, width: held.size, height: held.size * 1.375 }} | |
| 226 | + | onAnimationEnd={() => setHeld(null)} | |
| 227 | + | > | |
| 228 | + | <TileView tile={held.tile} /> | |
| 229 | + | </div> | |
| 230 | + | )} | |
| 231 | + | ||
| 232 | + | {r && s.phase === 'handEnd' && ( | |
| 233 | + | <div className="ctl-result"> | |
| 234 | + | {r.drawGame ? ( | |
| 235 | + | <h2>流局 · Draw</h2> | |
| 236 | + | ) : ( | |
| 237 | + | r.winners.map((w) => ( | |
| 238 | + | <h2 key={w.seat}> | |
| 239 | + | {s.players[w.seat].name} {w.selfDraw ? '自摸' : '胡牌'} · {w.tai} 台 | |
| 240 | + | </h2> | |
| 241 | + | )) | |
| 242 | + | )} | |
| 243 | + | <div className="deltas"> | |
| 244 | + | {r.deltas.map((d, i) => ( | |
| 245 | + | <span key={i} className={d > 0 ? 'up' : d < 0 ? 'down' : ''}> | |
| 246 | + | {s.players[i].name} {d > 0 ? `+${d}` : d} | |
| 247 | + | </span> | |
| 248 | + | ))} | |
| 249 | + | </div> | |
| 250 | + | <button className="btn big-btn" onClick={() => table.nextHand()}> | |
| 251 | + | <Bi zh="下一局" en="Next hand" /> | |
| 252 | + | </button> | |
| 253 | + | </div> | |
| 254 | + | )} | |
| 255 | + | ||
| 256 | + | {s.phase === 'gameEnd' && ( | |
| 257 | + | <div className="ctl-result"> | |
| 258 | + | <h2>一將終了 · Game over</h2> | |
| 259 | + | <div className="deltas"> | |
| 260 | + | {s.players.map((q, i) => ( | |
| 261 | + | <span key={i}> | |
| 262 | + | {q.name} {q.score} | |
| 263 | + | </span> | |
| 264 | + | ))} | |
| 265 | + | </div> | |
| 266 | + | <button className="btn big-btn" onClick={() => table.newGame(s.bots)}> | |
| 267 | + | <Bi zh="再來一將" en="New game" /> | |
| 268 | + | </button> | |
| 269 | + | </div> | |
| 270 | + | )} | |
| 271 | + | </div> | |
| 272 | + | ); | |
| 273 | + | } |
addedsrc/ui/OnlineLobby.tsx+83 −0
| 1 | + | import { useEffect, useRef } from 'react'; | |
| 2 | + | import type { Prefs } from '../game/prefs'; | |
| 3 | + | import { WIND_EN, WIND_NAMES } from '../game/tiles'; | |
| 4 | + | import type { SeatId } from '../game/types'; | |
| 5 | + | import type { NetSession } from '../net/session'; | |
| 6 | + | import { Bi } from './Bi'; | |
| 7 | + | ||
| 8 | + | const SEATS: SeatId[] = [0, 1, 2, 3]; | |
| 9 | + | ||
| 10 | + | /** | |
| 11 | + | * The room before the deal: who is here, who is sitting where, and — for the | |
| 12 | + | * host — the button that starts it. Seats nobody takes go to the computer, so | |
| 13 | + | * two friends and two bots is a game, not a wait. | |
| 14 | + | */ | |
| 15 | + | export function OnlineLobby({ session, prefs }: { session: NetSession; prefs: Prefs }) { | |
| 16 | + | const seats = session.seats; | |
| 17 | + | const me = session.room.me.id; | |
| 18 | + | ||
| 19 | + | // Walking in unseated: take the first free chair rather than making everyone | |
| 20 | + | // pick one. Moving is one tap on another seat. | |
| 21 | + | const claimed = useRef(false); | |
| 22 | + | useEffect(() => { | |
| 23 | + | if (claimed.current || session.mySeat !== null) return; | |
| 24 | + | if (seats.some((s) => s.id === null)) { | |
| 25 | + | claimed.current = true; | |
| 26 | + | session.claimSeat(null); | |
| 27 | + | } | |
| 28 | + | }, [session, seats]); | |
| 29 | + | ||
| 30 | + | const seated = seats.filter((s) => s.id !== null).length; | |
| 31 | + | ||
| 32 | + | return ( | |
| 33 | + | <div className="lobby"> | |
| 34 | + | <h1>台灣麻將</h1> | |
| 35 | + | <p className="sub">線上對戰 · online game · {session.playerCount} 人在房 in the room</p> | |
| 36 | + | <p className="hint"> | |
| 37 | + | 把這頁的連結傳給朋友,他們一開就進同一房。 | |
| 38 | + | <br /> | |
| 39 | + | Share this page's invite link — anyone who opens it lands at this table. | |
| 40 | + | </p> | |
| 41 | + | ||
| 42 | + | <div className="pick-grid"> | |
| 43 | + | {SEATS.map((i) => { | |
| 44 | + | const slot = seats[i]; | |
| 45 | + | const mine = slot.id === me; | |
| 46 | + | const taken = slot.id !== null && !mine; | |
| 47 | + | return ( | |
| 48 | + | <button | |
| 49 | + | key={i} | |
| 50 | + | className={`btn big-btn ${mine ? '' : 'ghost'}`} | |
| 51 | + | disabled={taken} | |
| 52 | + | onClick={() => !mine && session.claimSeat(i)} | |
| 53 | + | > | |
| 54 | + | {WIND_NAMES[i]} {WIND_EN[i]} | |
| 55 | + | <em> | |
| 56 | + | {slot.id === null | |
| 57 | + | ? '空位,開局時由電腦代打 · open — a bot if nobody sits' | |
| 58 | + | : mine | |
| 59 | + | ? `${slot.name || '你'} · you` | |
| 60 | + | : slot.name} | |
| 61 | + | </em> | |
| 62 | + | </button> | |
| 63 | + | ); | |
| 64 | + | })} | |
| 65 | + | </div> | |
| 66 | + | ||
| 67 | + | <div className="lobby-buttons"> | |
| 68 | + | {session.isHost ? ( | |
| 69 | + | <button | |
| 70 | + | className="btn big-btn" | |
| 71 | + | disabled={seated === 0} | |
| 72 | + | onClick={() => session.startOnline(prefs.rules)} | |
| 73 | + | > | |
| 74 | + | <Bi zh="開局" en="Deal" sub={seated < 4 ? `${4 - seated} 個電腦 bots fill in` : undefined} /> | |
| 75 | + | </button> | |
| 76 | + | ) : ( | |
| 77 | + | <p className="hint">等房主開局 · waiting for the host to deal</p> | |
| 78 | + | )} | |
| 79 | + | </div> | |
| 80 | + | {session.err && <p className="hint">{session.err}</p>} | |
| 81 | + | </div> | |
| 82 | + | ); | |
| 83 | + | } |
modifiedsrc/ui/Pool.tsx+23 −6
| 1 | 1 | import { useEffect, useRef, useState } from 'react'; | |
| 2 | - | import type { Game } from '../game/engine'; | |
| 2 | + | // Imported rather than named by path, so the canvas draws the very files the | |
| 3 | + | // CSS tiles use wherever the bundle ends up being served from. | |
| 4 | + | import backUrl from '../assets/back.png'; | |
| 5 | + | import tilesUrl from '../assets/tiles.svg'; | |
| 6 | + | import type { TableCtl } from '../game/ctl'; | |
| 3 | 7 | import { barge, knock } from '../game/sound'; | |
| 4 | 8 | import { SPRITE_COL, SPRITE_COLS } from '../game/tiles'; | |
| 5 | 9 | import { faceOn, zScale } from '../table/physics'; | |
| ⋯ 14 unchanged lines | |||
| 20 | 24 | * state changes, a tile is picked up, or the window resizes, so a table nobody | |
| 21 | 25 | * is touching costs nothing. | |
| 22 | 26 | */ | |
| 23 | - | export function Pool({ game, pool }: { game: Game; pool: TablePool }) { | |
| 27 | + | export function Pool({ | |
| 28 | + | game, | |
| 29 | + | pool, | |
| 30 | + | viewSeat = 0, | |
| 31 | + | }: { | |
| 32 | + | game: TableCtl; | |
| 33 | + | pool: TablePool; | |
| 34 | + | /** The seat drawn at the bottom of this screen — see App. An edge the physics | |
| 35 | + | * reports belongs to whoever is drawn there, not to seat 0 by birthright. */ | |
| 36 | + | viewSeat?: SeatId; | |
| 37 | + | }) { | |
| 24 | 38 | const canvasRef = useRef<HTMLCanvasElement>(null); | |
| 25 | 39 | const reachRef = useRef<HTMLDivElement>(null); | |
| 26 | 40 | const art = useSheet(); | |
| 27 | 41 | const wash = useWash(pool); | |
| 28 | 42 | ||
| 29 | 43 | // What the loop reads. Refreshed on every render, read on every frame. | |
| 30 | - | const live = useRef({ state: game.state, art }); | |
| 31 | - | live.current = { state: game.state, art }; | |
| 44 | + | const live = useRef({ state: game.state, art, viewSeat }); | |
| 45 | + | live.current = { state: game.state, art, viewSeat }; | |
| 32 | 46 | ||
| 33 | 47 | // Poked by the effect below, and by the gesture through pool.wake. | |
| 34 | 48 | const wake = useRef<() => void>(() => {}); | |
| ⋯ 38 unchanged lines | |||
| 73 | 87 | // A contact quiet enough not to be worth hearing still counts as a tile | |
| 74 | 88 | // arriving in somebody's lap, which is why the two thresholds differ. | |
| 75 | 89 | if (i.strength > AUDIBLE) loudest = Math.max(loudest, i.strength); | |
| 76 | - | if (i.edge) shove(pool, table, EDGE_SEAT[i.edge], i.seat, i.strength); | |
| 90 | + | if (i.edge) { | |
| 91 | + | const at = ((EDGE_SEAT[i.edge] + live.current.viewSeat) % 4) as SeatId; | |
| 92 | + | shove(pool, table, at, i.seat, i.strength); | |
| 93 | + | } | |
| 77 | 94 | } | |
| 78 | 95 | if (loudest > 0) knock(loudest); | |
| 79 | 96 | paint(ctx, canvas, pool, live.current.art); | |
| ⋯ 104 unchanged lines | |||
| 184 | 201 | () => null, | |
| 185 | 202 | ); | |
| 186 | 203 | }; | |
| 187 | - | void Promise.all([load('/tiles/tiles.svg'), load('/tiles/back.png')]).then( | |
| 204 | + | void Promise.all([load(tilesUrl), load(backUrl)]).then( | |
| 188 | 205 | ([faces, back]) => alive && setArt({ faces, back }), | |
| 189 | 206 | ); | |
| 190 | 207 | return () => { | |
| ⋯ 347 unchanged lines | |||
modifiedsrc/ui/Seat.tsx+85 −22
| 1 | 1 | import { useEffect, useRef, useState } from 'react'; | |
| 2 | - | import type { Game } from '../game/engine'; | |
| 2 | + | import type { TableCtl } from '../game/ctl'; | |
| 3 | 3 | import { labelOf, WIND_EN, WIND_NAMES } from '../game/tiles'; | |
| 4 | 4 | import type { SeatId } from '../game/types'; | |
| 5 | 5 | import type { TablePool } from '../table/pool'; | |
| 6 | 6 | import { Bi, CLAIM_LABEL } from './Bi'; | |
| 7 | 7 | import { Hand } from './Hand'; | |
| 8 | + | import { SeatQr } from './SeatQr'; | |
| 9 | + | import { SEAT_ROT, SEAT_SLOT } from './rotation'; | |
| 8 | 10 | import { TileView } from './TileView'; | |
| 9 | 11 | ||
| 10 | - | const ROT: Record<SeatId, number> = { 0: 0, 1: -90, 2: 180, 3: 90 }; | |
| 11 | - | // seat 0 = bottom (near edge of the laptop), then counter-clockwise on screen: | |
| 12 | - | // 1 = right, 2 = top, 3 = left — which is the natural 東南西北 seating order. | |
| 13 | - | const SLOT: Record<SeatId, string> = { 0: 'slot-bottom', 1: 'slot-right', 2: 'slot-top', 3: 'slot-left' }; | |
| 12 | + | /** | |
| 13 | + | * A seat that exists somewhere else — a phone at the party table, or an online | |
| 14 | + | * player. What of it shows on this screen is decided here. | |
| 15 | + | */ | |
| 16 | + | export interface RemoteSeat { | |
| 17 | + | /** Party screen: the link that claims this seat, drawn as a QR while free. */ | |
| 18 | + | qrUrl?: string; | |
| 19 | + | /** Who holds it from their own device, or null while nobody does. */ | |
| 20 | + | holder?: string | null; | |
| 21 | + | /** Online host: the seat's player is gone; offer it to the computer. */ | |
| 22 | + | onHandToBot?: () => void; | |
| 23 | + | } | |
| 14 | 24 | ||
| 15 | 25 | interface Props { | |
| 16 | - | game: Game; | |
| 26 | + | game: TableCtl; | |
| 17 | 27 | seat: SeatId; | |
| 18 | - | /** Phone-sized table: nothing is rotated and only seat 0 gets a full strip. */ | |
| 28 | + | /** | |
| 29 | + | * Which edge of this screen the seat is drawn at. At a shared table that is | |
| 30 | + | * the seat itself; online the table is turned so your own seat is at the | |
| 31 | + | * bottom, wherever you are actually sitting. | |
| 32 | + | */ | |
| 33 | + | pos?: SeatId; | |
| 34 | + | /** | |
| 35 | + | * Whether this device may act for the seat. The hotseat table says yes to | |
| 36 | + | * everyone, the online table only to your own chair — everything private | |
| 37 | + | * (the tiles' faces, the 聽 and 過水 badges, the buttons) keys off this. | |
| 38 | + | */ | |
| 39 | + | local?: boolean; | |
| 40 | + | remote?: RemoteSeat; | |
| 41 | + | /** Phone-sized table: nothing is rotated and only the bottom gets a full strip. */ | |
| 19 | 42 | compact?: boolean; | |
| 20 | 43 | /** The tiles in the middle. Absent on a phone, which has no middle. */ | |
| 21 | 44 | pool?: TablePool; | |
| 22 | 45 | onHelp: () => void; | |
| 23 | 46 | } | |
| 24 | 47 | ||
| 25 | - | export function Seat({ game, seat, compact, pool, onHelp }: Props) { | |
| 48 | + | export function Seat({ game, seat, pos = seat, local = true, remote, compact, pool, onHelp }: Props) { | |
| 26 | 49 | const s = game.state; | |
| 27 | 50 | const p = s.players[seat]; | |
| 28 | 51 | const [sel, setSel] = useState<number | null>(null); | |
| 29 | - | // A computer seat has no buttons and no readable hand: its tiles go face down | |
| 30 | - | // and its 聽 / 過水 badges stay off, since both would give the hand away. | |
| 31 | - | // At the end of a hand everyone turns their tiles over, as they would. | |
| 52 | + | // Held down to re-show a claimed seat's QR — for the player who lost theirs. | |
| 53 | + | const [peek, setPeek] = useState(false); | |
| 32 | 54 | const bot = s.bots[seat]; | |
| 33 | - | const concealed = bot && s.phase !== 'handEnd' && s.phase !== 'gameEnd'; | |
| 34 | - | const myTurn = s.phase === 'turn' && s.turn === seat && !bot; | |
| 35 | - | const opts = bot ? undefined : s.options[seat]; | |
| 55 | + | // Somebody this device answers for: a person here, not the computer, not a | |
| 56 | + | // hand that lives on another screen. | |
| 57 | + | const acts = local && !bot; | |
| 58 | + | // A hand this device may not read goes face down and its 聽 / 過水 badges | |
| 59 | + | // stay off, since both would give it away. At the end of a hand everyone | |
| 60 | + | // turns their tiles over, as they would. | |
| 61 | + | const concealed = !acts && s.phase !== 'handEnd' && s.phase !== 'gameEnd'; | |
| 62 | + | const myTurn = s.phase === 'turn' && s.turn === seat && acts; | |
| 63 | + | const opts = acts ? s.options[seat] : undefined; | |
| 36 | 64 | const responded = s.responses[seat] !== undefined; | |
| 37 | 65 | const claiming = (s.phase === 'claims' || s.phase === 'robkong') && !!opts && !responded; | |
| 38 | 66 | // Whoever draws next closes the claim window — nobody has to wait on a | |
| 39 | 67 | // player who hasn't noticed their buttons. | |
| 40 | - | const canProceed = game.nextDrawer() === seat && !bot; | |
| 68 | + | const canProceed = game.nextDrawer() === seat && acts; | |
| 41 | 69 | ||
| 42 | 70 | useEffect(() => { | |
| 43 | 71 | setSel(null); | |
| ⋯ 2 unchanged lines | |||
| 46 | 74 | const seatWind = (seat - s.dealer + 4) % 4; | |
| 47 | 75 | const isDealer = seat === s.dealer; | |
| 48 | 76 | const live = s.phase !== 'handEnd' && s.phase !== 'lobby'; | |
| 49 | - | const tenpai = live && !bot ? game.tenpai(seat) : []; | |
| 50 | - | const sacred = live && !bot ? game.sacredTiles(seat) : []; | |
| 77 | + | const tenpai = live && acts ? game.tenpai(seat) : []; | |
| 78 | + | const sacred = live && acts ? game.sacredTiles(seat) : []; | |
| 51 | 79 | const concealedKongs = myTurn ? game.concealedKongs(seat) : []; | |
| 52 | 80 | const addedKongs = myTurn ? game.addedKongs(seat) : []; | |
| 53 | 81 | const canHu = myTurn && game.canSelfDrawWin(seat); | |
| 54 | 82 | const delta = s.phase === 'handEnd' ? s.result?.deltas[seat] ?? 0 : 0; | |
| 55 | 83 | // 一炮多響 can put more than one seat here, so this is a `some`, not a find. | |
| 56 | 84 | const won = s.phase === 'handEnd' && !!s.result?.winners.some((w) => w.seat === seat); | |
| 85 | + | // A deserted online seat, waiting for its person to come back — or for the | |
| 86 | + | // host to hand it to the computer so the table can move. | |
| 87 | + | const deserted = !!remote?.onHandToBot && !bot && remote.holder == null && live; | |
| 57 | 88 | ||
| 58 | 89 | // Compact lays the discards out as one line that scrolls rather than a block | |
| 59 | 90 | // that wraps, so the newest tile — the only one anybody is looking for — has | |
| ⋯ 31 unchanged lines | |||
| 91 | 122 | y, | |
| 92 | 123 | // Facing the way it did in the hand, so it does not snap round the | |
| 93 | 124 | // instant it is lifted. | |
| 94 | - | angle: (ROT[seat] * Math.PI) / 180, | |
| 125 | + | angle: (SEAT_ROT[pos] * Math.PI) / 180, | |
| 95 | 126 | w: size, | |
| 96 | 127 | h: size * 1.375, | |
| 97 | 128 | }); | |
| ⋯ 21 unchanged lines | |||
| 119 | 150 | ||
| 120 | 151 | return ( | |
| 121 | 152 | // `data-seat` is how table/geometry.ts finds this seat's hand to work out | |
| 122 | - | // where it throws from — see measure(). | |
| 123 | - | <div className={`slot ${SLOT[seat]} ${seat === 0 ? 'mine' : 'opp'}`} data-seat={seat}> | |
| 153 | + | // where it throws from — see measure(). It carries the seat itself, not the | |
| 154 | + | // edge it is drawn at: the physics is about who threw the tile. | |
| 155 | + | <div className={`slot ${SEAT_SLOT[pos]} ${pos === 0 ? 'mine' : 'opp'}`} data-seat={seat}> | |
| 124 | 156 | <div | |
| 125 | 157 | className={`seat ${myTurn ? 'active' : ''} ${claiming ? 'claiming' : ''}`} | |
| 126 | - | style={compact ? undefined : { transform: `translate(-50%, -50%) rotate(${ROT[seat]}deg)` }} | |
| 158 | + | style={compact ? undefined : { transform: `translate(-50%, -50%) rotate(${SEAT_ROT[pos]}deg)` }} | |
| 127 | 159 | > | |
| 128 | 160 | {/* Points down the strip at whoever won. It is inside the seat, so it | |
| 129 | 161 | turns with it and lands on their edge of the table wherever they | |
| ⋯ 4 unchanged lines | |||
| 134 | 166 | </div> | |
| 135 | 167 | )} | |
| 136 | 168 | ||
| 169 | + | {/* Party screen: a free seat wears its QR; scanning it takes the hand | |
| 170 | + | to a phone. A claimed seat hides it, but its player can ask for it | |
| 171 | + | back — the 手機 tag below holds it down to reveal. */} | |
| 172 | + | {remote?.qrUrl && (remote.holder == null || peek) && ( | |
| 173 | + | <div className="qr-dock"> | |
| 174 | + | <SeatQr url={remote.qrUrl} /> | |
| 175 | + | <span className="qr-hint"> | |
| 176 | + | 掃碼用手機拿牌 | |
| 177 | + | <em>scan to play from your phone</em> | |
| 178 | + | </span> | |
| 179 | + | </div> | |
| 180 | + | )} | |
| 181 | + | ||
| 137 | 182 | {/* Only on a phone. At full size a discard is thrown into the middle | |
| 138 | 183 | and stays there for the hand, so the row would be the same tiles | |
| 139 | 184 | listed twice. */} | |
| ⋯ 65 unchanged lines | |||
| 205 | 250 | {!claiming && !canProceed && responded && s.phase !== 'turn' && ( | |
| 206 | 251 | <div className="waiting">已回應 Responded</div> | |
| 207 | 252 | )} | |
| 253 | + | {deserted && ( | |
| 254 | + | <button className="btn ghost" onClick={remote!.onHandToBot}> | |
| 255 | + | <Bi zh="電腦代打" en="Hand to computer" /> | |
| 256 | + | </button> | |
| 257 | + | )} | |
| 208 | 258 | {myTurn && ( | |
| 209 | 259 | <> | |
| 210 | 260 | {canHu && ( | |
| ⋯ 39 unchanged lines | |||
| 250 | 300 | <em>{WIND_EN[seatWind]}</em> | |
| 251 | 301 | </span> | |
| 252 | 302 | <span className="pname">{p.name}</span> | |
| 303 | + | {remote?.holder != null && ( | |
| 304 | + | <span | |
| 305 | + | className="tag phone" | |
| 306 | + | title="這手牌在手機上 · played from a phone. 按住看 QR · hold to show the QR again" | |
| 307 | + | onPointerDown={() => setPeek(true)} | |
| 308 | + | onPointerUp={() => setPeek(false)} | |
| 309 | + | onPointerCancel={() => setPeek(false)} | |
| 310 | + | onPointerLeave={() => setPeek(false)} | |
| 311 | + | > | |
| 312 | + | 📱 {remote.holder} | |
| 313 | + | </span> | |
| 314 | + | )} | |
| 315 | + | {deserted && <span className="tag sacred">已離線 offline</span>} | |
| 253 | 316 | {isDealer && ( | |
| 254 | 317 | <span className="tag"> | |
| 255 | 318 | 莊 Dealer{s.dealerStreak > 0 ? ` 連${s.dealerStreak}` : ''} | |
| ⋯ 9 unchanged lines | |||
| 265 | 328 | </span> | |
| 266 | 329 | )} | |
| 267 | 330 | <span className="chips">{p.score}</span> | |
| 268 | - | {p.hand.length > 0 && s.phase !== 'handEnd' && !bot && ( | |
| 331 | + | {p.hand.length > 0 && s.phase !== 'handEnd' && acts && ( | |
| 269 | 332 | <button className="btn ghost sort" onClick={() => game.sortHand(seat)}> | |
| 270 | 333 | 理牌 Sort | |
| 271 | 334 | </button> | |
| ⋯ 9 unchanged lines | |||
addedsrc/ui/SeatQr.tsx+11 −0
| 1 | + | import { useMemo } from 'react'; | |
| 2 | + | import { renderSVG } from 'uqr'; | |
| 3 | + | ||
| 4 | + | /** | |
| 5 | + | * The QR that hands a seat to a phone. Just the code — where it sits and which | |
| 6 | + | * way it faces is its seat's business, and it reads at any angle anyway. | |
| 7 | + | */ | |
| 8 | + | export function SeatQr({ url }: { url: string }) { | |
| 9 | + | const svg = useMemo(() => renderSVG(url, { border: 1 }), [url]); | |
| 10 | + | return <div className="seat-qr" dangerouslySetInnerHTML={{ __html: svg }} />; | |
| 11 | + | } |
modifiedvite.config.ts+3 −0
| ⋯ 3 unchanged lines | |||
| 4 | 4 | // https://vite.dev/config/ | |
| 5 | 5 | export default defineConfig({ | |
| 6 | 6 | plugins: [react()], | |
| 7 | + | // Served from wherever antics puts the bundle, not from a domain root — so | |
| 8 | + | // every built URL has to be relative to the page. | |
| 9 | + | base: './', | |
| 7 | 10 | // portless hands us PORT/HOST; vite doesn't read them on its own | |
| 8 | 11 | server: { | |
| 9 | 12 | port: process.env.PORT ? Number(process.env.PORT) : undefined, | |
| ⋯ 3 unchanged lines | |||