This commit is contained in:
d07riv
2019-07-31 09:44:31 +03:00
parent 58386b7cf4
commit 9d2d3b6089
12 changed files with 3485 additions and 2740 deletions

5833
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,7 @@
"babel-preset-react-app": "^9.0.0",
"camelcase": "^5.2.0",
"case-sensitive-paths-webpack-plugin": "2.2.0",
"classnames": "^2.2.6",
"css-loader": "2.1.1",
"dotenv": "6.2.0",
"dotenv-expand": "4.2.0",

View File

@@ -1,5 +1,6 @@
import React from 'react';
import './App.scss';
import classNames from 'classnames';
import create_fs from './fs';
import load_game from './api/loader';
@@ -28,15 +29,35 @@ function getDropFile(e) {
}
}
const TOUCH_MOVE = 0;
const TOUCH_RMB = 1;
const TOUCH_SHIFT = 2;
const Link = ({children, ...props}) => <a target="_blank" rel="noopener noreferrer" {...props}>{children}</a>;
class App extends React.Component {
files = new Map();
state = {started: false, loading: false, dropping: 0};
state = {started: false, loading: false, touch: false, dropping: 0};
cursorPos = {x: 0, y: 0};
touchButtons = [null, null, null, null, null, null];
touchCtx = [null, null, null, null, null, null];
touchMods = [false, false, false, false, false, false];
touchBelt = [-1, -1, -1, -1, -1, -1];
fs = create_fs(this);
constructor(props) {
super(props);
this.setTouch0 = this.setTouch_.bind(this, 0);
this.setTouch1 = this.setTouch_.bind(this, 1);
this.setTouch2 = this.setTouch_.bind(this, 2);
this.setTouch3 = this.setTouchBelt_.bind(this, 3);
this.setTouch4 = this.setTouchBelt_.bind(this, 4);
this.setTouch5 = this.setTouchBelt_.bind(this, 5);
}
componentDidMount() {
document.addEventListener("drop", this.onDrop, true);
document.addEventListener("dragover", this.onDragOver, true);
@@ -95,7 +116,20 @@ class App extends React.Component {
this.setState({progress: loaded / total});
}
onRender({images, text, clip}) {
drawBelt(idx, slot) {
if (!this.touchButtons[idx]) {
return;
}
this.touchBelt[idx] = slot;
if (slot >= 0) {
this.touchButtons[idx].style.display = "block";
this.touchCtx[idx].drawImage(this.canvas, 205 + 29 * slot, 357, 28, 28, 0, 0, 28, 28);
} else {
this.touchButtons[idx].style.display = "none";
}
}
onRender({images, text, clip, belt}) {
const ctx = this.renderer;
if (!ctx) {
return;
@@ -125,6 +159,23 @@ class App extends React.Component {
}
ctx.restore();
}
if (belt) {
const used = new Set();
let pos = 3;
for (let i = 0; i < belt.length && pos < 6; ++i) {
if (belt[i] >= 0 && !used.has(belt[i])) {
this.drawBelt(pos++, i);
used.add(belt[i]);
}
}
for (; pos < 6; ++pos) {
this.drawBelt(pos, -1);
}
} else {
this.drawBelt(3, -1);
this.drawBelt(4, -1);
this.drawBelt(5, -1);
}
}
start(file) {
@@ -148,10 +199,11 @@ class App extends React.Component {
document.addEventListener('contextmenu', this.onMenu, true);
document.addEventListener('touchstart', this.onTouchStart, {passive: false, capture: true});
document.addEventListener('touchmove', this.onTouchStart, {passive: false, capture: true});
document.addEventListener('touchend', this.onTouchStart, {passive: false, capture: true});
document.addEventListener('touchmove', this.onTouchMove, {passive: false, capture: true});
document.addEventListener('touchend', this.onTouchEnd, {passive: false, capture: true});
document.addEventListener('pointerlockchange', this.onPointerLockChange);
document.addEventListener('fullscreenchange', this.onFullscreenChange);
window.addEventListener('resize', this.onResize);
this.setState({started: true});
@@ -187,7 +239,7 @@ class App extends React.Component {
}
}
eventMods(e) {
return (e.shiftKey ? 1 : 0) + (e.ctrlKey ? 2 : 0) + (e.altKey ? 4 : 0);
return ((e.shiftKey || this.touchMods[TOUCH_SHIFT]) ? 1 : 0) + (e.ctrlKey ? 2 : 0) + (e.altKey ? 4 : 0) + (e.touches ? 8 : 0);
}
onResize = () => {
@@ -256,34 +308,165 @@ class App extends React.Component {
}
}
touchButton = null;
touchCanvas = null;
onFullscreenChange = () => {
this.setState({touch: (document.fullscreenElement === this.element)});
}
setTouchMod(index, value, use) {
if (index < 3) {
this.touchMods[index] = value;
if (this.touchButtons[index]) {
this.touchButtons[index].classList.toggle("active", value);
}
} else if (use && this.touchBelt[index] >= 0) {
const now = performance.now();
if (!this.beltTime || now - this.beltTime > 750) {
this.game("DApi_Char", 49 + this.touchBelt[index]);
this.beltTime = now;
}
}
}
updateTouchButton(touches, release) {
let touchOther = null;
const btn = this.touchButton;
for (let {target, identifier, clientX, clientY} of touches) {
if (btn && btn.id === identifier && this.touchButtons[btn.index] === target) {
if (touches.length > 1) {
btn.stick = false;
}
btn.clientX = clientX;
btn.clientY = clientY;
this.touchCanvas = [...touches].find(t => t.identifier !== identifier);
if (this.touchCanvas) {
this.touchCanvas = {clientX: this.touchCanvas.clientX, clientY: this.touchCanvas.clientY};
}
delete this.panPos;
return this.touchCanvas != null;
}
const idx = this.touchButtons.indexOf(target);
if (idx >= 0 && !touchOther) {
touchOther = {id: identifier, index: idx, stick: true, original: this.touchMods[idx], clientX, clientY};
}
}
if (btn && !touchOther && release && btn.stick) {
const rect = this.touchButtons[btn.index].getBoundingClientRect();
const {clientX, clientY} = btn;
if (clientX >= rect.left && clientX < rect.right && clientY >= rect.top && clientY < rect.bottom) {
this.setTouchMod(btn.index, !btn.original, true);
} else {
this.setTouchMod(btn.index, btn.original);
}
} else if (btn) {
this.setTouchMod(btn.index, false);
}
this.touchButton = touchOther;
if (touchOther) {
this.setTouchMod(touchOther.index, true);
delete this.panPos;
} else if (touches.length === 2) {
const x = (touches[1].clientX + touches[0].clientX) / 2, y = (touches[1].clientY + touches[0].clientY) / 2;
if (this.panPos) {
const dx = x - this.panPos.x, dy = y - this.panPos.y;
const step = this.canvas.offsetHeight / 12;
if (Math.max(Math.abs(dx), Math.abs(dy)) > step) {
let key;
if (Math.abs(dx) > Math.abs(dy)) {
key = (dx > 0 ? 0x25 : 0x27);
} else {
key = (dy > 0 ? 0x26 : 0x28);
}
this.game("DApi_Key", 0, 0, key);
// key up is ignored anyway
this.panPos = {x, y};
}
} else {
this.game("DApi_Mouse", 0, 0, 24, 320, 180);
this.game("DApi_Mouse", 2, 1, 24, 320, 180);
this.panPos = {x, y};
}
this.touchCanvas = null;
return false;
} else {
delete this.panPos;
}
this.touchCanvas = [...touches].find(t => !touchOther || t.identifier !== touchOther.id);
if (this.touchCanvas) {
this.touchCanvas = {clientX: this.touchCanvas.clientX, clientY: this.touchCanvas.clientY};
}
return this.touchCanvas != null;
}
onTouchStart = e => {
if (!this.canvas || !e.touches.length) return;
if (!this.canvas) return;
e.preventDefault();
this.element.requestFullscreen();
const {x, y} = this.mousePos(e.touches[0]);
this.game("DApi_Mouse", 0, 0, this.eventMods(e), x, y);
this.game("DApi_Mouse", 1, 0, this.eventMods(e), x, y);
if (this.updateTouchButton(e.touches, false)) {
const {x, y} = this.mousePos(this.touchCanvas);
this.game("DApi_Mouse", 0, 0, this.eventMods(e), x, y);
if (!this.touchMods[TOUCH_MOVE]) {
this.game("DApi_Mouse", 1, this.touchMods[TOUCH_RMB] ? 2 : 1, this.eventMods(e), x, y);
}
}
}
onTouchMove = e => {
if (!this.canvas || !e.touches.length) return;
if (!this.canvas) return;
e.preventDefault();
if (this.updateTouchButton(e.touches, false)) {
const {x, y} = this.mousePos(this.touchCanvas);
this.game("DApi_Mouse", 0, 0, this.eventMods(e), x, y);
}
}
onTouchEnd = e => {
if (!this.canvas) return;
e.preventDefault();
const prevTc = this.touchCanvas;
this.updateTouchButton(e.touches, true);
if (prevTc && !this.touchCanvas) {
const {x, y} = this.mousePos(prevTc);
this.game("DApi_Mouse", 2, 1, this.eventMods(e), x, y);
this.game("DApi_Mouse", 2, 2, this.eventMods(e), x, y);
}
if (!document.fullscreenElement) {
this.element.requestFullscreen();
}
}
setCanvas = e => this.canvas = e;
setElement = e => this.element = e;
setKeyboard = e => this.keyboard = e;
setTouchMove = e => this.touchMove = e;
setTouchRmb = e => this.touchRmb = e;
setTouchShift = e => this.touchShift = e;
setTouch_(i, e) {
this.touchButtons[i] = e;
}
setTouchBelt_(i, e) {
this.touchButtons[i] = e;
if (e) {
const canvas = document.createElement("canvas");
canvas.width = 28;
canvas.height = 28;
e.appendChild(canvas);
this.touchCtx[i] = canvas.getContext("2d");
} else {
this.touchCtx[i] = null;
}
}
render() {
const {started, loading, error, progress, dropping} = this.state;
const {started, loading, error, progress, dropping, touch} = this.state;
return (
<div className={"App touch " + (started ? " started" : "") + (dropping ? " dropping" : "")} ref={this.setElement}>
<div className={classNames("App", {touch, started, dropping})} ref={this.setElement}>
<div className="touch-ui touch-mods">
<div className={classNames("touch-button", "touch-button-0", {active: this.touchMods[0]})} ref={this.setTouch0}/>
<div className={classNames("touch-button", "touch-button-1", {active: this.touchMods[1]})} ref={this.setTouch1}/>
<div className={classNames("touch-button", "touch-button-2", {active: this.touchMods[2]})} ref={this.setTouch2}/>
</div>
<div className="touch-ui touch-belt">
<div className={classNames("touch-button", "touch-button-0")} ref={this.setTouch3}/>
<div className={classNames("touch-button", "touch-button-1")} ref={this.setTouch4}/>
<div className={classNames("touch-button", "touch-button-2")} ref={this.setTouch5}/>
</div>
<div className="Body">
{!error && (
<canvas ref={this.setCanvas} width={640} height={480}/>
@@ -320,14 +503,6 @@ class App extends React.Component {
</div>
)}
</div>
<div className="touch-ui">
<div className="touch-button touch-button-1" ref={this.touchMove}>
</div>
<div className="touch-button touch-button-2" ref={this.touchRmb}>
</div>
<div className="touch-button touch-button-3" ref={this.touchShift}>
</div>
</div>
</div>
);
}

View File

@@ -145,74 +145,135 @@ body, #root, .App {
.App {
.touch-ui {
display: none;
}
&.touch .touch-ui {
display: block;
pointer-events: none;
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 25vh;
.touch-button {
position: absolute;
left: 50%;
width: 20vh;
height: 20vh;
border-radius: 10vh;
background-color: #888;
background-color: #444;
transform: translate(-50%, -50%);
pointer-events: auto;
&.active {
background-color: #fff;
}
mask-size: 100% 100%;
}
.touch-button-1 {
.touch-button-0 {
top: 16.7vh;
}
.touch-button-2 {
.touch-button-1 {
top: 50vh;
}
.touch-button-3 {
.touch-button-2 {
top: 83.3vh;
}
}
.touch-ui.touch-mods {
left: 0;
.touch-button {
width: 20vh;
height: 20vh;
border-radius: 10vh;
}
.touch-button-0 {
mask-image: url(./icons/move.svg);
}
.touch-button-1 {
mask-image: url(./icons/rmb.svg);
}
.touch-button-2 {
mask-image: url(./icons/shift.svg);
}
}
.touch-ui.touch-belt {
right: 0;
.touch-button {
width: 14vh;
height: 14vh;
font-size: 1vh;
border-radius: 20%;
overflow: hidden;
background-color: #000;
border: 2px solid #444;
canvas {
position: absolute;
left: 5%;
top: 5%;
width: 90%;
height: 90%;
}
&::after {
content: '';
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
border-radius: 20%;
box-shadow: inset 0 0 1em 1.5em #000;
}
}
}
&.touch .touch-ui {
display: block;
}
}
@media (max-aspect-ratio: 880/480) {
.App.touch .touch-ui {
.App .touch-ui {
width: calc(50vw - 200vh / 3);
}
}
@media (max-aspect-ratio: 832/480) {
.App.touch .touch-ui {
.App .touch-ui {
width: 20vh;
}
}
@media (max-aspect-ratio: 640/480) {
.App.touch .touch-ui {
top: auto;
.App .touch-ui {
width: auto;
right: 0;
height: 20vw;
.touch-button {
top: 50%;
}
.touch-button-0 {
left: 16.7vw;
}
.touch-button-1 {
left: 50vw;
}
.touch-button-2 {
left: 83.3vw;
}
}
.App .touch-ui.touch-mods {
top: auto;
right: 0;
.touch-button {
width: 20vw;
height: 20vw;
border-radius: 10vw;
}
.touch-button-1 {
left: 16.7vw;
}
.touch-button-2 {
left: 50vw;
}
.touch-button-3 {
left: 83.3vw;
}
.App .touch-ui.touch-belt {
bottom: auto;
left: 0;
.touch-button {
width: 14vw;
height: 14vw;
font-size: 1vw;
}
}
}
@media (max-aspect-ratio: 640/736) {
.App.touch .touch-ui {
.App .touch-ui {
height: calc(50vh - 75vw / 2);
}
}
@media (max-aspect-ratio: 640/800) {
.App.touch .touch-ui {
.App .touch-ui {
height: 25vw;
}
}

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -8,6 +8,7 @@ const worker = self;
let files = null;
let renderBatch = null;
let drawBelt = null;
const DApi = {
exit_error(error) {
@@ -18,8 +19,10 @@ const DApi = {
renderBatch = {
images: [],
text: [],
clip: null
clip: null,
belt: drawBelt,
};
drawBelt = null;
},
draw_blit(x, y, w, h, data) {
if (ImageData.length) {
@@ -40,6 +43,9 @@ const DApi = {
worker.postMessage({action: "render", batch: renderBatch});
renderBatch = null;
},
draw_belt(items) {
drawBelt = items.slice();
},
get_file_size(path) {
const data = files.get(path.toLowerCase());

3
src/icons/move.svg Normal file
View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 640 640" width="640" height="640"><defs><path d="M326.88 298.88C326.88 298.88 326.88 298.88 326.88 298.88C314.75 298.88 304.92 289.04 304.92 276.91C304.92 273.13 304.92 242.92 304.92 239.14C304.92 227.01 314.75 217.17 326.88 217.17C326.88 217.17 326.88 217.17 326.88 217.17C339.02 217.17 348.85 227.01 348.85 239.14C348.85 241.66 348.85 254.25 348.85 276.91C342.3 291.56 334.97 298.88 326.88 298.88Z" id="a3YmE0kYNk"></path><path d="M360.46 232.22C360.46 237.46 360.46 279.36 360.46 284.6C360.46 296.93 352.38 307.55 340.76 312.34C340.76 314.22 340.76 323.6 340.76 340.5L454.07 340.5C454.07 283.76 454.07 252.23 454.07 245.93C454.07 192.37 409.2 148.96 353.86 148.96C352.99 148.96 348.62 148.96 340.76 148.96L340.76 204.48C353.89 214.75 360.46 224 360.46 232.22Z" id="a123Rizl1h"></path><path d="M199.7 421.97C199.7 475.53 244.57 518.94 299.91 518.94C305.3 518.94 348.47 518.94 353.86 518.94C409.2 518.94 454.07 475.53 454.07 421.97C454.07 417.08 454.07 392.62 454.07 348.59L199.7 348.59C199.7 392.62 199.7 417.08 199.7 421.97Z" id="a2PRuZ4XyI"></path><path d="M313.01 312.34C301.39 307.55 293.31 296.93 293.31 284.6C293.31 279.36 293.31 237.46 293.31 232.22C293.31 219.89 301.39 209.27 313.01 204.48C313.01 200.78 313.01 182.27 313.01 148.96C305.15 148.96 300.78 148.96 299.91 148.96C244.57 148.96 199.7 192.37 199.7 245.93C199.7 252.23 199.7 283.76 199.7 340.5L313.01 340.5C313.01 323.6 313.01 314.22 313.01 312.34Z" id="bOlZ8Knrm"></path><path d="M494.43 269.5L563.35 340.5L494.43 411.5L494.43 366.49L461.92 366.49L461.92 314.39L494.43 314.39L494.43 269.5Z" id="c2jJXhIrng"></path><path d="M158.97 269.5L90.06 340.5L158.97 411.5L158.97 366.49L191.49 366.49L191.49 314.39L158.97 314.39L158.97 269.5Z" id="ke7zAJ37M"></path><path d="M397.89 110.9L326.88 41.99L255.88 110.9L300.9 110.9L300.9 143.42L352.99 143.42L352.99 110.9L397.89 110.9Z" id="a4iiHlu6Tc"></path><path d="M397.41 556.21L326.41 625.13L255.41 556.21L300.42 556.21L300.42 523.7L352.52 523.7L352.52 556.21L397.41 556.21Z" id="b2XGLcPStX"></path></defs><g><g><g><use xlink:href="#a3YmE0kYNk" opacity="1" fill="#020202" fill-opacity="1"></use><g><use xlink:href="#a3YmE0kYNk" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#a123Rizl1h" opacity="1" fill="#020202" fill-opacity="1"></use><g><use xlink:href="#a123Rizl1h" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#a2PRuZ4XyI" opacity="1" fill="#020202" fill-opacity="1"></use><g><use xlink:href="#a2PRuZ4XyI" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#bOlZ8Knrm" opacity="1" fill="#020202" fill-opacity="1"></use><g><use xlink:href="#bOlZ8Knrm" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#c2jJXhIrng" opacity="1" fill="#000000" fill-opacity="1"></use></g><g><use xlink:href="#ke7zAJ37M" opacity="1" fill="#000000" fill-opacity="1"></use></g><g><use xlink:href="#a4iiHlu6Tc" opacity="1" fill="#000000" fill-opacity="1"></use></g><g><use xlink:href="#b2XGLcPStX" opacity="1" fill="#000000" fill-opacity="1"></use></g></g></g></svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

3
src/icons/rmb.svg Normal file
View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 640 640" width="640" height="640"><defs><path d="M313.25 276.99C313.25 276.99 313.25 276.99 313.25 276.99C298.37 276.99 286.31 264.93 286.31 250.05C286.31 245.42 286.31 208.36 286.31 203.73C286.31 188.85 298.37 176.79 313.25 176.79C313.25 176.79 313.25 176.79 313.25 176.79C328.13 176.79 340.19 188.85 340.19 203.73C340.19 206.82 340.19 222.26 340.19 250.05C332.15 268.01 323.17 276.99 313.25 276.99Z" id="bwtT01FaN"></path><path d="M354.42 195.25C354.42 201.67 354.42 253.06 354.42 259.48C354.42 274.61 344.51 287.62 330.26 293.5C330.26 295.8 330.26 307.31 330.26 328.03L469.22 328.03C469.22 258.45 469.22 219.79 469.22 212.06C469.22 146.38 414.2 93.13 346.33 93.13C345.26 93.13 339.91 93.13 330.26 93.13L330.26 161.23C346.37 173.82 354.42 185.16 354.42 195.25Z" id="a1ALqApg4B"></path><clipPath id="clipbywTQrek4"><use xlink:href="#a1ALqApg4B" opacity="1"></use></clipPath><path d="M157.28 427.94C157.28 493.62 212.3 546.87 280.16 546.87C286.78 546.87 339.72 546.87 346.33 546.87C414.2 546.87 469.22 493.62 469.22 427.94C469.22 421.94 469.22 391.95 469.22 337.96L157.28 337.96C157.28 391.95 157.28 421.94 157.28 427.94Z" id="aGiF88xvk"></path><path d="M296.23 293.5C281.99 287.62 272.08 274.61 272.08 259.48C272.08 253.06 272.08 201.67 272.08 195.25C272.08 180.12 281.99 167.11 296.23 161.23C296.23 156.69 296.23 133.99 296.23 93.13C286.59 93.13 281.24 93.13 280.16 93.13C212.3 93.13 157.28 146.38 157.28 212.06C157.28 219.79 157.28 258.45 157.28 328.03L296.23 328.03C296.23 307.31 296.23 295.8 296.23 293.5Z" id="acQhG8yZH"></path></defs><g><g><g><use xlink:href="#bwtT01FaN" opacity="1" fill="#020202" fill-opacity="1"></use><g><use xlink:href="#bwtT01FaN" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><g clip-path="url(#clipbywTQrek4)"><use xlink:href="#a1ALqApg4B" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="26" stroke-opacity="1"></use></g></g><g><use xlink:href="#aGiF88xvk" opacity="1" fill="#020202" fill-opacity="1"></use><g><use xlink:href="#aGiF88xvk" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#acQhG8yZH" opacity="1" fill="#020202" fill-opacity="1"></use><g><use xlink:href="#acQhG8yZH" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

3
src/icons/shift.svg Normal file
View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 640 640" width="640" height="640"><defs><path d="M439.52 108.04C477.06 108.04 507.5 138.47 507.5 176.02C507.5 240.02 507.5 357.26 507.5 419.57C507.5 461.8 473.26 496.04 431.03 496.04C371.33 496.04 264.71 496.04 204.02 496.04C164.52 496.04 132.5 464.02 132.5 424.52C132.5 361.22 132.5 245.68 132.5 183.09C132.5 141.64 166.1 108.04 207.55 108.04C267.54 108.04 378.11 108.04 439.52 108.04Z" id="c2OL5KwjUK"></path><clipPath id="clipaGcWUxBty"><use xlink:href="#c2OL5KwjUK" opacity="1"></use></clipPath><path d="M366 349.57L282 268.04L198 349.57L251.25 349.57L251.25 416.04L312.89 416.04L312.89 349.57L366 349.57Z" id="i5MOx8YLhh"></path></defs><g><g><g><g clip-path="url(#clipaGcWUxBty)"><use xlink:href="#c2OL5KwjUK" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="52" stroke-opacity="1"></use></g></g><g><use xlink:href="#i5MOx8YLhh" opacity="1" fill="#000000" fill-opacity="1"></use></g></g></g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB