App shell + assets

It can render an image, but not much else.
Oh, and the icons were ported from an earlier version of Sakura.
This commit is contained in:
MindfulMinun
2020-09-11 02:17:45 -05:00
parent f653e67c4d
commit 1751d9bc22
15 changed files with 597 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
{
"discord.lowerDetailsEditing": "in 🌸 Sakura"
}
+5
View File
@@ -0,0 +1,5 @@
# 🌸 Sakura: Pink manga aesthetic
Sakura is a simple progressive web app that applies a pink filter onto a provided image. Once installed, you can share images from your native photos app to Sakura.
Sakura assumes your browser supports ES6 modules.
+67
View File
@@ -0,0 +1,67 @@
<!doctype html>
<!--
Made with love by MindfulMinun
https://benjic.xyz
-->
<html class="no-js">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Sakura</title>
<meta name="viewport" content="width=device-width, user-scalable=0">
<meta name="theme-color" content="#F1278C">
<link rel="icon" href="/static/icons/sakura-72.png" type="image/png">
<link rel="apple-touch-icon" href="/static/icons/sakura-maskable-192.png">
<link rel="manifest" href="/manifest.webmanifest">
<style media="screen">
*, *::before, *::after {
box-sizing: border-box;
}
.no-js .no-js-hide {
display: none;
}
</style>
<link rel="stylesheet" href="/static/styles/main.css">
<script>document.documentElement.classList.remove('no-js')</script>
<script src="/static/scripts/app.js" type="module"></script>
</head>
<body>
<div id="app">
<noscript class="welcome">
<div class="welcome--blossom">
<div class="welcome--blossom--box">
<img class="affix" src="/static/icons/sakura.svg" alt="" title="Sakura">
</div>
</div>
<div class="welcome--text">
<h1>Sakura</h1>
<p class="welcome--subtitle">Pink manga aesthetic</p>
<p>Please come back on a supporting browser.</p>
<p><em>I love you, please don’t ever forget that.</em></p>
</div>
</noscript>
</div>
<!-- Templates -->
<template id="template__welcome">
<div class="welcome">
<div class="welcome--blossom">
<div class="welcome--blossom--box">
<img class="affix" src="/static/icons/sakura.svg" alt="" title="Sakura" draggable="false">
</div>
</div>
<div class="welcome--text">
<h1>Sakura</h1>
<p class="welcome--subtitle">Pink manga aesthetic</p>
<p class="no-js-hide">Drag and drop an image anywhere or <button class="link-btn">choose one from your device</button></p>
</div>
</div>
</template>
<template id="template__editor">
<div class="editor">
<div class="cbox affix"></div>
</div>
</template>
<input type="file" id="input-file" hidden>
</body>
</html>
+39
View File
@@ -0,0 +1,39 @@
{
"name": "Sakura",
"short_name": "Sakura",
"description": "🌸 Pink manga aesthetic 🌸",
"start_url": "/?ref=homescreen",
"theme_color":"#F1278C",
"background_color":"#FCD0E5",
"display":"standalone",
"icons": [{
"src": "/static/icons/sakura-192.png",
"type": "image/png",
"sizes": "192x192"
}, {
"src": "/static/icons/sakura-512.png",
"type": "image/png",
"sizes": "512x512"
}, {
"purpose": "maskable",
"src": "/static/icons/sakura-maskable-192.png",
"type": "image/png",
"sizes": "192x192"
}, {
"purpose": "maskable",
"src": "/static/icons/sakura-maskable-512.png",
"type": "image/png",
"sizes": "512x512"
}],
"share_target": {
"action": "/share-target",
"method": "POST",
"enctype": "multipart/form-data",
"params": {
"files": [{
"name": "file",
"accept": ["image/*"]
}]
}
}
}
+1
View File
@@ -0,0 +1 @@
// The ServiceWorker file has to be at root to ensure that it scopes index.html
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

+69
View File
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg" xmlns:bx="https://boxy-svg.com">
<defs>
<clipPath id="clipPath16" clipPathUnits="userSpaceOnUse">
<path id="path18" d="M 0,38 38,38 38,0 0,0 0,38 Z"/>
</clipPath>
</defs>
<rect width="500" height="500" style="fill: rgb(241, 39, 140);"/>
<g transform="matrix(8.436184, 0, 0, -8.436184, 89.709549, 405.238831)" id="g10" style="" bx:origin="0.5 0.499828">
<g id="g12">
<g clip-path="url(#clipPath16)" id="g14">
<g transform="translate(19,16.6631)" id="g20">
<path id="path22" style="fill:#f4abba;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 0,0 c -0.162,0.292 -0.353,0.538 -0.588,0.709 -0.234,0.171 -0.528,0.276 -0.856,0.341 0.228,0.244 0.403,0.502 0.493,0.778 0.09,0.276 0.099,0.588 0.059,0.919 C -0.59,2.605 -0.29,2.519 0,2.519 0.29,2.519 0.59,2.605 0.894,2.747 0.853,2.416 0.862,2.104 0.952,1.828 1.042,1.552 1.217,1.294 1.444,1.05 1.117,0.985 0.823,0.88 0.589,0.709 0.354,0.538 0.162,0.292 0,0 m 13.298,-0.47 c 4.197,1.364 5.027,3.182 4.191,6.416 -0.952,-0.308 -2.105,0.001 -2.272,0.518 -0.168,0.513 0.581,1.443 1.533,1.753 -1.223,3.107 -2.964,4.089 -7.161,2.727 C 7.982,10.422 6.351,9.452 4.934,8.31 5.582,10.01 6,11.862 6,13.551 c 0,4.412 -1.473,5.765 -4.807,5.968 0,-1 -0.652,-2 -1.193,-2 -0.541,0 -1.194,1 -1.194,2 C -4.528,19.316 -6,17.963 -6,13.551 c 0,-1.689 0.417,-3.541 1.066,-5.241 -1.416,1.142 -3.049,2.111 -4.655,2.633 -4.197,1.364 -5.938,0.381 -7.162,-2.727 0.951,-0.31 1.701,-1.238 1.534,-1.753 -0.167,-0.515 -1.32,-0.826 -2.271,-0.518 -0.837,-3.233 -0.005,-5.051 4.19,-6.415 1.607,-0.521 3.497,-0.697 5.314,-0.605 -1.524,-0.994 -2.95,-2.247 -3.943,-3.613 -2.594,-3.571 -2.197,-5.531 0.381,-7.655 0.588,0.809 1.703,1.236 2.142,0.917 0.437,-0.317 0.378,-1.511 -0.21,-2.32 2.816,-1.795 4.802,-1.566 7.396,2.003 0.993,1.366 1.743,3.111 2.218,4.867 0.475,-1.757 1.226,-3.501 2.218,-4.867 2.594,-3.57 4.58,-3.798 7.397,-2.003 -0.587,0.809 -0.649,2.002 -0.21,2.321 0.437,0.318 1.553,-0.107 2.142,-0.917 2.577,2.123 2.973,4.083 0.381,7.654 -0.993,1.366 -2.42,2.619 -3.944,3.613 1.817,-0.092 3.707,0.084 5.314,0.605"/>
</g>
<g transform="translate(17.7949,18.3145)" id="g24">
<path id="path26" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M 0,0 C -0.12,-0.54 -0.653,-0.88 -1.193,-0.76 L -7.05,0.542 c -0.539,0.12 -0.879,0.653 -0.76,1.193 0.121,0.539 0.655,0.879 1.194,0.759 L -0.759,1.192 C -0.22,1.073 0.12,0.539 0,0"/>
</g>
<g transform="translate(18.771,18.0967)" id="g28">
<path id="path30" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M 0,0 C 0.12,0.54 0.654,0.88 1.193,0.76 L 7.05,-0.542 C 7.59,-0.662 7.929,-1.195 7.81,-1.735 7.69,-2.273 7.156,-2.614 6.617,-2.494 L 0.759,-1.192 C 0.22,-1.073 -0.12,-0.538 0,0"/>
</g>
<g transform="translate(18.771,18.0967)" id="g32">
<path id="path34" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 0,0 c -0.466,-0.296 -1.083,-0.159 -1.38,0.308 l -3.222,5.061 c -0.296,0.466 -0.159,1.084 0.307,1.381 0.466,0.296 1.084,0.159 1.38,-0.307 L 0.307,1.381 C 0.603,0.915 0.466,0.297 0,0"/>
</g>
<g transform="translate(19.5684,16.8447)" id="g36">
<path id="path38" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M 0,0 C 0.466,0.296 1.084,0.159 1.38,-0.307 L 4.603,-5.369 C 4.898,-5.836 4.762,-6.452 4.295,-6.75 3.829,-7.046 3.211,-6.909 2.914,-6.443 l -3.221,5.062 C -0.604,-0.915 -0.465,-0.297 0,0"/>
</g>
<g transform="translate(18.9883,19.0732)" id="g40">
<path id="path42" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M 0,0 C -0.539,0.12 -0.879,0.654 -0.759,1.193 L 0.543,7.05 C 0.662,7.589 1.195,7.93 1.735,7.81 2.275,7.69 2.614,7.155 2.494,6.616 L 1.193,0.759 C 1.073,0.22 0.539,-0.12 0,0"/>
</g>
<g transform="translate(18.771,18.0967)" id="g44">
<path id="path46" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 0,0 c 0.54,-0.119 0.879,-0.653 0.759,-1.192 l -1.3,-5.858 c -0.121,-0.54 -0.654,-0.88 -1.194,-0.76 -0.538,0.121 -0.879,0.655 -0.76,1.194 l 1.303,5.857 C -1.073,-0.22 -0.539,0.12 0,0"/>
</g>
<g transform="translate(18.7715,18.0977)" id="g48">
<path id="path50" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M 0,0 C -0.297,0.466 -0.159,1.083 0.307,1.38 L 5.369,4.601 C 5.835,4.897 6.453,4.761 6.749,4.294 7.046,3.829 6.908,3.211 6.442,2.914 L 1.38,-0.307 C 0.914,-0.604 0.296,-0.467 0,0"/>
</g>
<g transform="translate(18.7715,18.0977)" id="g52">
<path id="path54" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 0,0 c 0.296,-0.467 0.159,-1.084 -0.307,-1.381 l -5.062,-3.222 c -0.466,-0.295 -1.083,-0.16 -1.381,0.308 -0.296,0.465 -0.159,1.083 0.307,1.38 l 5.062,3.222 C -0.914,0.604 -0.296,0.466 0,0"/>
</g>
<g transform="translate(23.0117,11.4336)" id="g56">
<path id="path58" style="fill:#ea596e;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M 0,0 C 0.932,0.592 2.168,0.317 2.762,-0.614 3.354,-1.546 3.08,-2.782 2.147,-3.376 1.216,-3.969 -0.021,-3.694 -0.614,-2.762 -1.206,-1.83 -0.932,-0.594 0,0"/>
</g>
<g transform="translate(15.5498,23.1592)" id="g60">
<path id="path62" style="fill:#ea596e;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M 0,0 C -0.932,-0.593 -2.168,-0.319 -2.761,0.613 -3.354,1.545 -3.079,2.781 -2.147,3.375 -1.215,3.967 0.021,3.692 0.614,2.761 1.207,1.829 0.932,0.592 0,0"/>
</g>
<g transform="translate(17.4697,12.2412)" id="g64">
<path id="path66" style="fill:#ea596e;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M 0,0 C 1.078,-0.241 1.758,-1.31 1.52,-2.388 1.28,-3.466 0.211,-4.146 -0.868,-3.906 -1.946,-3.666 -2.626,-2.598 -2.386,-1.52 -2.146,-0.441 -1.078,0.238 0,0"/>
</g>
<g transform="translate(20.5068,25.9063)" id="g68">
<path id="path70" style="fill:#ea596e;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 0,0 c -1.078,0.239 -1.758,1.308 -1.519,2.386 0.24,1.078 1.308,1.758 2.386,1.519 C 1.945,3.665 2.626,2.597 2.386,1.519 2.146,0.44 1.078,-0.24 0,0"/>
</g>
<g transform="translate(14.4937,15.376)" id="g72">
<path id="path74" style="fill:#ea596e;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 0,0 c 0.592,-0.933 0.317,-2.169 -0.614,-2.763 -0.931,-0.592 -2.168,-0.318 -2.761,0.616 -0.593,0.93 -0.319,2.167 0.613,2.76 C -1.83,1.205 -0.594,0.931 0,0"/>
</g>
<g transform="translate(23.833,21.3188)" id="g76">
<path id="path78" style="fill:#ea596e;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M 0,0 C -0.593,0.932 -0.317,2.168 0.614,2.761 1.546,3.354 2.782,3.079 3.375,2.147 3.968,1.216 3.692,-0.021 2.762,-0.614 1.829,-1.207 0.593,-0.932 0,0"/>
</g>
<g transform="translate(24.6279,16.7949)" id="g80">
<path id="path82" style="fill:#ea596e;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M 0,0 C 0.24,1.079 1.309,1.759 2.387,1.52 3.465,1.279 4.145,0.211 3.905,-0.867 3.665,-1.945 2.597,-2.625 1.519,-2.386 0.44,-2.146 -0.239,-1.078 0,0"/>
</g>
<g transform="translate(11.938,19.6157)" id="g84">
<path id="path86" style="fill:#ea596e;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 0,0 c -0.24,-1.079 -1.308,-1.758 -2.386,-1.519 -1.078,0.24 -1.759,1.309 -1.519,2.387 0.239,1.078 1.308,1.757 2.386,1.518 C -0.44,2.146 0.239,1.078 0,0"/>
</g>
<g transform="translate(23,18.1816)" id="g88">
<path id="path90" style="fill:#ffcc4d;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 0,0 c 0,-2.209 -1.791,-4 -4,-4 -2.209,0 -4,1.791 -4,4 0,2.209 1.791,4 4,4 2.209,0 4,-1.791 4,-4"/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.7 KiB

+69
View File
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg" xmlns:bx="https://boxy-svg.com">
<defs>
<clipPath id="clipPath16" clipPathUnits="userSpaceOnUse">
<path id="path18" d="M 0,38 38,38 38,0 0,0 0,38 Z"/>
</clipPath>
</defs>
<circle style="fill: rgb(241, 39, 140);" cx="250" cy="250" r="230"/>
<g transform="matrix(8.436184, 0, 0, -8.436184, 89.709549, 405.238831)" id="g10" style="" bx:origin="0.5 0.499828">
<g id="g12">
<g clip-path="url(#clipPath16)" id="g14">
<g transform="translate(19,16.6631)" id="g20">
<path id="path22" style="fill:#f4abba;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 0,0 c -0.162,0.292 -0.353,0.538 -0.588,0.709 -0.234,0.171 -0.528,0.276 -0.856,0.341 0.228,0.244 0.403,0.502 0.493,0.778 0.09,0.276 0.099,0.588 0.059,0.919 C -0.59,2.605 -0.29,2.519 0,2.519 0.29,2.519 0.59,2.605 0.894,2.747 0.853,2.416 0.862,2.104 0.952,1.828 1.042,1.552 1.217,1.294 1.444,1.05 1.117,0.985 0.823,0.88 0.589,0.709 0.354,0.538 0.162,0.292 0,0 m 13.298,-0.47 c 4.197,1.364 5.027,3.182 4.191,6.416 -0.952,-0.308 -2.105,0.001 -2.272,0.518 -0.168,0.513 0.581,1.443 1.533,1.753 -1.223,3.107 -2.964,4.089 -7.161,2.727 C 7.982,10.422 6.351,9.452 4.934,8.31 5.582,10.01 6,11.862 6,13.551 c 0,4.412 -1.473,5.765 -4.807,5.968 0,-1 -0.652,-2 -1.193,-2 -0.541,0 -1.194,1 -1.194,2 C -4.528,19.316 -6,17.963 -6,13.551 c 0,-1.689 0.417,-3.541 1.066,-5.241 -1.416,1.142 -3.049,2.111 -4.655,2.633 -4.197,1.364 -5.938,0.381 -7.162,-2.727 0.951,-0.31 1.701,-1.238 1.534,-1.753 -0.167,-0.515 -1.32,-0.826 -2.271,-0.518 -0.837,-3.233 -0.005,-5.051 4.19,-6.415 1.607,-0.521 3.497,-0.697 5.314,-0.605 -1.524,-0.994 -2.95,-2.247 -3.943,-3.613 -2.594,-3.571 -2.197,-5.531 0.381,-7.655 0.588,0.809 1.703,1.236 2.142,0.917 0.437,-0.317 0.378,-1.511 -0.21,-2.32 2.816,-1.795 4.802,-1.566 7.396,2.003 0.993,1.366 1.743,3.111 2.218,4.867 0.475,-1.757 1.226,-3.501 2.218,-4.867 2.594,-3.57 4.58,-3.798 7.397,-2.003 -0.587,0.809 -0.649,2.002 -0.21,2.321 0.437,0.318 1.553,-0.107 2.142,-0.917 2.577,2.123 2.973,4.083 0.381,7.654 -0.993,1.366 -2.42,2.619 -3.944,3.613 1.817,-0.092 3.707,0.084 5.314,0.605"/>
</g>
<g transform="translate(17.7949,18.3145)" id="g24">
<path id="path26" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M 0,0 C -0.12,-0.54 -0.653,-0.88 -1.193,-0.76 L -7.05,0.542 c -0.539,0.12 -0.879,0.653 -0.76,1.193 0.121,0.539 0.655,0.879 1.194,0.759 L -0.759,1.192 C -0.22,1.073 0.12,0.539 0,0"/>
</g>
<g transform="translate(18.771,18.0967)" id="g28">
<path id="path30" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M 0,0 C 0.12,0.54 0.654,0.88 1.193,0.76 L 7.05,-0.542 C 7.59,-0.662 7.929,-1.195 7.81,-1.735 7.69,-2.273 7.156,-2.614 6.617,-2.494 L 0.759,-1.192 C 0.22,-1.073 -0.12,-0.538 0,0"/>
</g>
<g transform="translate(18.771,18.0967)" id="g32">
<path id="path34" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 0,0 c -0.466,-0.296 -1.083,-0.159 -1.38,0.308 l -3.222,5.061 c -0.296,0.466 -0.159,1.084 0.307,1.381 0.466,0.296 1.084,0.159 1.38,-0.307 L 0.307,1.381 C 0.603,0.915 0.466,0.297 0,0"/>
</g>
<g transform="translate(19.5684,16.8447)" id="g36">
<path id="path38" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M 0,0 C 0.466,0.296 1.084,0.159 1.38,-0.307 L 4.603,-5.369 C 4.898,-5.836 4.762,-6.452 4.295,-6.75 3.829,-7.046 3.211,-6.909 2.914,-6.443 l -3.221,5.062 C -0.604,-0.915 -0.465,-0.297 0,0"/>
</g>
<g transform="translate(18.9883,19.0732)" id="g40">
<path id="path42" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M 0,0 C -0.539,0.12 -0.879,0.654 -0.759,1.193 L 0.543,7.05 C 0.662,7.589 1.195,7.93 1.735,7.81 2.275,7.69 2.614,7.155 2.494,6.616 L 1.193,0.759 C 1.073,0.22 0.539,-0.12 0,0"/>
</g>
<g transform="translate(18.771,18.0967)" id="g44">
<path id="path46" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 0,0 c 0.54,-0.119 0.879,-0.653 0.759,-1.192 l -1.3,-5.858 c -0.121,-0.54 -0.654,-0.88 -1.194,-0.76 -0.538,0.121 -0.879,0.655 -0.76,1.194 l 1.303,5.857 C -1.073,-0.22 -0.539,0.12 0,0"/>
</g>
<g transform="translate(18.7715,18.0977)" id="g48">
<path id="path50" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M 0,0 C -0.297,0.466 -0.159,1.083 0.307,1.38 L 5.369,4.601 C 5.835,4.897 6.453,4.761 6.749,4.294 7.046,3.829 6.908,3.211 6.442,2.914 L 1.38,-0.307 C 0.914,-0.604 0.296,-0.467 0,0"/>
</g>
<g transform="translate(18.7715,18.0977)" id="g52">
<path id="path54" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 0,0 c 0.296,-0.467 0.159,-1.084 -0.307,-1.381 l -5.062,-3.222 c -0.466,-0.295 -1.083,-0.16 -1.381,0.308 -0.296,0.465 -0.159,1.083 0.307,1.38 l 5.062,3.222 C -0.914,0.604 -0.296,0.466 0,0"/>
</g>
<g transform="translate(23.0117,11.4336)" id="g56">
<path id="path58" style="fill:#ea596e;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M 0,0 C 0.932,0.592 2.168,0.317 2.762,-0.614 3.354,-1.546 3.08,-2.782 2.147,-3.376 1.216,-3.969 -0.021,-3.694 -0.614,-2.762 -1.206,-1.83 -0.932,-0.594 0,0"/>
</g>
<g transform="translate(15.5498,23.1592)" id="g60">
<path id="path62" style="fill:#ea596e;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M 0,0 C -0.932,-0.593 -2.168,-0.319 -2.761,0.613 -3.354,1.545 -3.079,2.781 -2.147,3.375 -1.215,3.967 0.021,3.692 0.614,2.761 1.207,1.829 0.932,0.592 0,0"/>
</g>
<g transform="translate(17.4697,12.2412)" id="g64">
<path id="path66" style="fill:#ea596e;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M 0,0 C 1.078,-0.241 1.758,-1.31 1.52,-2.388 1.28,-3.466 0.211,-4.146 -0.868,-3.906 -1.946,-3.666 -2.626,-2.598 -2.386,-1.52 -2.146,-0.441 -1.078,0.238 0,0"/>
</g>
<g transform="translate(20.5068,25.9063)" id="g68">
<path id="path70" style="fill:#ea596e;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 0,0 c -1.078,0.239 -1.758,1.308 -1.519,2.386 0.24,1.078 1.308,1.758 2.386,1.519 C 1.945,3.665 2.626,2.597 2.386,1.519 2.146,0.44 1.078,-0.24 0,0"/>
</g>
<g transform="translate(14.4937,15.376)" id="g72">
<path id="path74" style="fill:#ea596e;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 0,0 c 0.592,-0.933 0.317,-2.169 -0.614,-2.763 -0.931,-0.592 -2.168,-0.318 -2.761,0.616 -0.593,0.93 -0.319,2.167 0.613,2.76 C -1.83,1.205 -0.594,0.931 0,0"/>
</g>
<g transform="translate(23.833,21.3188)" id="g76">
<path id="path78" style="fill:#ea596e;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M 0,0 C -0.593,0.932 -0.317,2.168 0.614,2.761 1.546,3.354 2.782,3.079 3.375,2.147 3.968,1.216 3.692,-0.021 2.762,-0.614 1.829,-1.207 0.593,-0.932 0,0"/>
</g>
<g transform="translate(24.6279,16.7949)" id="g80">
<path id="path82" style="fill:#ea596e;fill-opacity:1;fill-rule:nonzero;stroke:none" d="M 0,0 C 0.24,1.079 1.309,1.759 2.387,1.52 3.465,1.279 4.145,0.211 3.905,-0.867 3.665,-1.945 2.597,-2.625 1.519,-2.386 0.44,-2.146 -0.239,-1.078 0,0"/>
</g>
<g transform="translate(11.938,19.6157)" id="g84">
<path id="path86" style="fill:#ea596e;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 0,0 c -0.24,-1.079 -1.308,-1.758 -2.386,-1.519 -1.078,0.24 -1.759,1.309 -1.519,2.387 0.239,1.078 1.308,1.757 2.386,1.518 C -0.44,2.146 0.239,1.078 0,0"/>
</g>
<g transform="translate(23,18.1816)" id="g88">
<path id="path90" style="fill:#ffcc4d;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 0,0 c 0,-2.209 -1.791,-4 -4,-4 -2.209,0 -4,1.791 -4,4 0,2.209 1.791,4 4,4 2.209,0 4,-1.791 4,-4"/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.7 KiB

+221
View File
@@ -0,0 +1,221 @@
const Sakura = {
/** @param {File[]} files */
fileDrop(files) {
const file = files.find(file => file.type.startsWith('image/'))
if (!file) {
return alert("Be sure to drop a file your browser supports :)")
}
this.image.src = URL.createObjectURL(file)
// this.loadView('loading')
this.image.onload = () => {
this.ctx.canvas.height = this.image.naturalHeight
this.ctx.canvas.width = this.image.naturalWidth
this.ctx.drawImage(this.image, 0, 0)
this.loadView('editor')
}
},
/** @type {{[key: string]: ((app: Sakura) => void)[]}} */
_loadCallbacks: {},
/** @param {string} id */
loadView(id) {
/** @ts-ignore @type {HTMLTemplateElement} */
const temp = document.getElementById(`template__${id}`)
const clone = temp.content.cloneNode(true)
this.view.innerHTML = ''
this.view.appendChild(clone)
this.returnCall(id)
return Promise.resolve(this)
},
view: document.getElementById('app'),
/** @ts-ignore @type {HTMLInputElement} */
fileElement: document.getElementById('input-file'),
ctx: document.createElement('canvas').getContext('2d'),
image: new Image(),
/**
* Calls callback after the id has loaded. May be called more than once
* @param {string} id
* @param {(app: Sakura) => void} cb
*/
onload(id, cb) {
this._loadCallbacks[id] = this._loadCallbacks[id] || []
this._loadCallbacks[id].push(cb)
},
/** @param {string} id */
returnCall(id) {
const cbs = this._loadCallbacks[id]
if (cbs) {
cbs.forEach(cb => cb.call(this, this))
}
}
}
// @ts-ignore
globalThis['Sakura'] = Sakura
// Handle file selects
Sakura.fileElement.addEventListener('change', function () {
if (this.files) {
Sakura.fileDrop([...this.files])
}
})
// Listen for file drops
window.addEventListener('drop', async ev => {
ev.preventDefault()
Sakura.fileDrop([...ev.dataTransfer.files])
})
// Prevent the browser from replacing the document when a file's dropped over it
document.addEventListener('dragover', ev => ev.preventDefault())
// Register load callbacks
Sakura.onload('welcome', app => {
app.view.querySelector('.link-btn')
.addEventListener('click', () => app.fileElement.click())
})
Sakura.onload('editor', app => {
app.ctx.canvas.width
/** @ts-ignore @type {HTMLDivElement} */
const canvasBox = app.view.querySelector('.cbox')
canvasBox.appendChild(app.ctx.canvas)
const rect = canvasBox.getBoundingClientRect()
const initialX = rect.width / 2 - app.ctx.canvas.width / 2
const initialY = rect.height / 2 - app.ctx.canvas.height / 2
canvasBox.style.setProperty('--x', `${initialX}px`)
canvasBox.style.setProperty('--y', `${initialY}px`)
touchManip({
canvasBox,
rect,
image: app.image
})
})
/**
* Adds a bunch of event listeners that are responsible for ~glitchy~ touch interactions
* @param {{canvasBox: HTMLDivElement, rect: DOMRect, image: HTMLImageElement}} any
*/
function touchManip({canvasBox, rect, image}) {
const SCROLL_SCALE = 480
const PINCH_SCALE = 300
const PADDING_BOX = 64
// Movement event listeners
// Mouse events fire way too often
// getComputedStyle too expensive to call on each of these events
// Instead, accumulate the scrolled distance and update once per frame
const pointer = {
isPanning: false,
multitouch: false,
lastX: 0,
lastY: 0,
lastSize: 0,
aggregateX: 0,
aggregateY: 0,
aggregateScroll: 0,
/** @param {{x: number, y: number}} a */
onPointerMove: ({x, y}) => {
if (pointer.isPanning) {
pointer.aggregateX += (x - pointer.lastX)
pointer.aggregateY += (y - pointer.lastY)
pointer.lastX = x
pointer.lastY = y
}
},
/** @param {{x: number, y: number}} a */
onPointerDown: ({x, y}) => {
pointer.isPanning = true
pointer.lastX = x
pointer.lastY = y
},
}
canvasBox.addEventListener('wheel', ev => pointer.aggregateScroll -= ev.deltaY / SCROLL_SCALE, { passive: true })
canvasBox.addEventListener('mousemove', pointer.onPointerMove)
canvasBox.addEventListener('touchmove', ev => {
if (ev.targetTouches.length === 1) {
pointer.multitouch = false
pointer.onPointerMove({
x: ev.targetTouches[0].clientX,
y: ev.targetTouches[0].clientY
})
} else {
const [left, right] = ev.targetTouches
const midpoint = {
x: (left.clientX + right.clientX) / 2,
y: (left.clientY + right.clientY) / 2
}
const size = Math.hypot(right.clientX - left.clientX, right.clientY - left.clientY)
// Ignore this touch since it's like start of the second touch
if (!pointer.multitouch) {
pointer.lastX = midpoint.x
pointer.lastY = midpoint.y
pointer.lastSize = size
pointer.multitouch = true
}
// Scale by the position
pointer.aggregateScroll += (size - pointer.lastSize) / PINCH_SCALE
pointer.lastSize = size
pointer.onPointerMove(midpoint)
}
}, { passive: true })
canvasBox.addEventListener('mousedown', pointer.onPointerDown)
canvasBox.addEventListener('touchstart', ev => pointer.onPointerDown({
x: ev.targetTouches[0].clientX,
y: ev.targetTouches[0].clientY
}), { passive: true })
canvasBox.addEventListener('touchend', () => {
pointer.isPanning = false
pointer.multitouch = true
})
canvasBox.addEventListener('mouseup', () => pointer.isPanning = false)
canvasBox.addEventListener('mouseleave', () => pointer.isPanning = false)
requestAnimationFrame(function _rAF() {
if (!canvasBox.isConnected) return
requestAnimationFrame(_rAF)
const computed = getComputedStyle(canvasBox)
// Scroll
const parsedScale = parseFloat(computed.getPropertyValue('--scale') || '0')
// .1 <= scale <= 20
const scale = Math.max(.1, Math.min(20, parsedScale + pointer.aggregateScroll))
canvasBox.style.setProperty('--scale', '' + scale)
// Position
const parsedX = parseFloat(computed.getPropertyValue('--x') || '0px')
const parsedY = parseFloat(computed.getPropertyValue('--y') || '0px')
const x = Math.max(
-image.width + PADDING_BOX,
Math.min(rect.width - PADDING_BOX, parsedX + pointer.aggregateX)
)
const y = Math.max(
-image.height + PADDING_BOX,
Math.min(rect.height - PADDING_BOX, parsedY + pointer.aggregateY)
)
canvasBox.style.setProperty('--x', `${x}px`)
canvasBox.style.setProperty('--y', `${y}px`)
pointer.aggregateX = 0
pointer.aggregateY = 0
pointer.aggregateScroll = 0
})
}
Sakura.loadView('welcome')
+122
View File
@@ -0,0 +1,122 @@
*, *::before, *::after {
box-sizing: border-box;
}
:root {
--pink-light: #FCD0E5;
--pink-hard: #F1278C;
/* https://material.io/design/motion/speed.html#easing */
--anim-std: cubic-bezier(0.4, 0.0, 0.2, 1);
--anim-peak-to-rest: cubic-bezier(0.0, 0.0, 0.2, 1);
--anim-rest-to-peak: cubic-bezier(0.4, 0.0, 1, 1);
--anim-ez-overshoot: cubic-bezier(0.4, 0.0, 0.2, 1.4);
--anim-hard-overshoot: cubic-bezier(0.4, 0.0, 0.2, 1.6);
}
:focus {
outline-color: var(--pink-hard);
outline-offset: 4px;
outline-width: 2px;
outline-style: solid;
}
html { height: 100%; }
body {
margin: 0;
height: 100%;
font-size: 16px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
display: flex;
flex-direction: column;
/* Prevent the weird overscroll bounce */
overflow: hidden;
overscroll-behavior: none;
background-color: var(--pink-light);
color: var(--pink-hard);
}
canvas {
display: block;
}
.container {
padding: 1em;
margin: 0 auto;
max-width: 720px;
width: 100%;
}
#app {
height: 100%;
display: flex;
flex-direction: column;
overscroll-behavior: none;
}
/* Helper styles */
.link-btn {
color: inherit;
font: inherit;
border: 0;
background: 0;
padding: 0;
font-weight: 600;
text-decoration: underline;
cursor: pointer;
}
.affix {
position: absolute;
display: block;
top: 0; bottom: 0; left: 0; right: 0;
width: 100%;
height: 100%;
}
/* Welcome styles */
.welcome {
margin: auto;
width: 400px;
max-width: 100%;
text-align: center;
animation: 1800ms var(--anim-std) welcome-fade-in;
}
.welcome--blossom {
width: 50%;
margin: 0 auto;
}
.welcome--blossom--box {
position: relative;
padding-top: 100%;
}
.welcome--text {
padding: 0 1em;
}
/* Editor styles */
.editor {
margin: auto;
animation: 250ms var(--anim-std) fade-in;
}
.cbox {
/* background-color: #1e1e1e; */
position: fixed;
overflow: visible;
--x: 0;
--y: 0;
--scale: 1;
}
.cbox > canvas {
transform: translate(var(--x), var(--y)) scale(var(--scale));
}
@keyframes welcome-fade-in {
from { opacity: 0; transform: translateY(5em); }
}
@keyframes fade-in {
from { opacity: 0; }
}