Compare commits
	
		
			No commits in common. "10c63e9e5819cab894050778b7642b844dd7e5da" and "2707def3a3954040e017d9bd749660859a3f0113" have entirely different histories.
		
	
	
		
			10c63e9e58
			...
			2707def3a3
		
	
		
							
								
								
									
										21
									
								
								index.html
									
										
									
									
									
								
							
							
						
						| 
						 | 
					@ -10,13 +10,22 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <script defer src="https://umami.leomurca.xyz/script.js" data-website-id="bd4c0533-36e6-402d-ac04-577993aaf43a"></script>
 | 
					  <script defer src="https://umami.leomurca.xyz/script.js" data-website-id="bd4c0533-36e6-402d-ac04-577993aaf43a"></script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
 | 
					  <link rel="apple-touch-icon" sizes="57x57" href="/apple-icon-57x57.png">
 | 
				
			||||||
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
 | 
					  <link rel="apple-touch-icon" sizes="60x60" href="/apple-icon-60x60.png">
 | 
				
			||||||
  <link rel="shortcut icon" href="/favicon.ico" />
 | 
					  <link rel="apple-touch-icon" sizes="72x72" href="/apple-icon-72x72.png">
 | 
				
			||||||
  <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
 | 
					  <link rel="apple-touch-icon" sizes="76x76" href="/apple-icon-76x76.png">
 | 
				
			||||||
  <meta name="apple-mobile-web-app-title" content="Embroidery Viewer" />
 | 
					  <link rel="apple-touch-icon" sizes="114x114" href="/apple-icon-114x114.png">
 | 
				
			||||||
  <link rel="manifest" href="/site.webmanifest" />
 | 
					  <link rel="apple-touch-icon" sizes="120x120" href="/apple-icon-120x120.png">
 | 
				
			||||||
 | 
					  <link rel="apple-touch-icon" sizes="144x144" href="/apple-icon-144x144.png">
 | 
				
			||||||
 | 
					  <link rel="apple-touch-icon" sizes="152x152" href="/apple-icon-152x152.png">
 | 
				
			||||||
 | 
					  <link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180x180.png">
 | 
				
			||||||
 | 
					  <link rel="icon" type="image/png" sizes="192x192" href="/android-icon-192x192.png">
 | 
				
			||||||
 | 
					  <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
 | 
				
			||||||
 | 
					  <link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png">
 | 
				
			||||||
 | 
					  <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
 | 
				
			||||||
 | 
					  <link rel="manifest" href="/manifest.json">
 | 
				
			||||||
  <link rel="canonical" href="https://embroideryviewer.xyz/">
 | 
					  <link rel="canonical" href="https://embroideryviewer.xyz/">
 | 
				
			||||||
 | 
					  <meta name="msapplication-TileColor" content="#ffffff">
 | 
				
			||||||
</head>
 | 
					</head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<body>
 | 
					<body>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  "name": "embroidery-viewer",
 | 
					  "name": "embroidery-viewer",
 | 
				
			||||||
  "private": true,
 | 
					  "private": true,
 | 
				
			||||||
  "version": "2.0.0",
 | 
					  "version": "1.2.6",
 | 
				
			||||||
  "type": "module",
 | 
					  "type": "module",
 | 
				
			||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
    "dev": "vite",
 | 
					    "dev": "vite",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										1
									
								
								public/ads.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					google.com, pub-5761689301112420, DIRECT, f08c47fec0942fa0
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								public/android-icon-144x144.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 8.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/android-icon-192x192.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 11 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/android-icon-36x36.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.3 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/android-icon-48x48.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.2 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/android-icon-72x72.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/android-icon-96x96.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 5.2 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/apple-icon-114x114.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 6.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/apple-icon-120x120.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 6.8 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/apple-icon-144x144.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 8.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/apple-icon-152x152.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 9.3 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/apple-icon-180x180.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 11 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/apple-icon-57x57.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.8 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/apple-icon-60x60.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.9 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/apple-icon-72x72.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/apple-icon-76x76.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.9 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/apple-icon-precomposed.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 11 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/apple-icon.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 11 KiB  | 
| 
		 Before Width: | Height: | Size: 18 KiB  | 
							
								
								
									
										2
									
								
								public/browserconfig.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					<?xml version="1.0" encoding="utf-8"?>
 | 
				
			||||||
 | 
					<browserconfig><msapplication><tile><square70x70logo src="/ms-icon-70x70.png"/><square150x150logo src="/ms-icon-150x150.png"/><square310x310logo src="/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								public/favicon-16x16.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.2 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/favicon-32x32.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.1 KiB  | 
| 
		 Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 5.2 KiB  | 
| 
		 Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 1.1 KiB  | 
| 
		 Before Width: | Height: | Size: 61 KiB  | 
							
								
								
									
										41
									
								
								public/manifest.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,41 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					 "name": "App",
 | 
				
			||||||
 | 
					 "icons": [
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					   "src": "\/android-icon-36x36.png",
 | 
				
			||||||
 | 
					   "sizes": "36x36",
 | 
				
			||||||
 | 
					   "type": "image\/png",
 | 
				
			||||||
 | 
					   "density": "0.75"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					   "src": "\/android-icon-48x48.png",
 | 
				
			||||||
 | 
					   "sizes": "48x48",
 | 
				
			||||||
 | 
					   "type": "image\/png",
 | 
				
			||||||
 | 
					   "density": "1.0"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					   "src": "\/android-icon-72x72.png",
 | 
				
			||||||
 | 
					   "sizes": "72x72",
 | 
				
			||||||
 | 
					   "type": "image\/png",
 | 
				
			||||||
 | 
					   "density": "1.5"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					   "src": "\/android-icon-96x96.png",
 | 
				
			||||||
 | 
					   "sizes": "96x96",
 | 
				
			||||||
 | 
					   "type": "image\/png",
 | 
				
			||||||
 | 
					   "density": "2.0"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					   "src": "\/android-icon-144x144.png",
 | 
				
			||||||
 | 
					   "sizes": "144x144",
 | 
				
			||||||
 | 
					   "type": "image\/png",
 | 
				
			||||||
 | 
					   "density": "3.0"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					   "src": "\/android-icon-192x192.png",
 | 
				
			||||||
 | 
					   "sizes": "192x192",
 | 
				
			||||||
 | 
					   "type": "image\/png",
 | 
				
			||||||
 | 
					   "density": "4.0"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					 ]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								public/ms-icon-144x144.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 8.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/ms-icon-150x150.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 9.1 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/ms-icon-310x310.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 24 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/ms-icon-70x70.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 3.4 KiB  | 
| 
						 | 
					@ -1,21 +0,0 @@
 | 
				
			||||||
{
 | 
					 | 
				
			||||||
  "name": "Embroidery Viewer",
 | 
					 | 
				
			||||||
  "short_name": "EmbViewer",
 | 
					 | 
				
			||||||
  "icons": [
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "src": "/web-app-manifest-192x192.png",
 | 
					 | 
				
			||||||
      "sizes": "192x192",
 | 
					 | 
				
			||||||
      "type": "image/png",
 | 
					 | 
				
			||||||
      "purpose": "maskable"
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "src": "/web-app-manifest-512x512.png",
 | 
					 | 
				
			||||||
      "sizes": "512x512",
 | 
					 | 
				
			||||||
      "type": "image/png",
 | 
					 | 
				
			||||||
      "purpose": "maskable"
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  "theme_color": "#06345f",
 | 
					 | 
				
			||||||
  "background_color": "#06345f",
 | 
					 | 
				
			||||||
  "display": "standalone"
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 20 KiB  | 
| 
		 Before Width: | Height: | Size: 71 KiB  | 
| 
						 | 
					@ -1,11 +1,24 @@
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
  import Head from "./lib/sections/Head.svelte";
 | 
					  import Head from "./lib/Head.svelte";
 | 
				
			||||||
  import Header from "./lib/sections/Header.svelte";
 | 
					  import Header from "./lib/Header.svelte";
 | 
				
			||||||
  import Footer from "./lib/sections/Footer.svelte";
 | 
					  import FileViewer from "./lib/FileViewer.svelte";
 | 
				
			||||||
  import Main from "./lib/sections/Main.svelte";
 | 
					  import Footer from "./lib/Footer.svelte";
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<Head/>
 | 
					<Head/>
 | 
				
			||||||
<Header />
 | 
					<Header />
 | 
				
			||||||
<Main />
 | 
					<main>
 | 
				
			||||||
 | 
					  <FileViewer />
 | 
				
			||||||
 | 
					</main>
 | 
				
			||||||
<Footer />
 | 
					<Footer />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style>
 | 
				
			||||||
 | 
					  main {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    box-sizing: border-box;
 | 
				
			||||||
 | 
					    padding: 15px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										15
									
								
								src/app.css
									
										
									
									
									
								
							
							
						
						| 
						 | 
					@ -17,10 +17,9 @@
 | 
				
			||||||
body {
 | 
					body {
 | 
				
			||||||
  display: flex;
 | 
					  display: flex;
 | 
				
			||||||
  justify-content: center;
 | 
					  justify-content: center;
 | 
				
			||||||
  flex-direction: column;
 | 
					 | 
				
			||||||
  margin: 0;
 | 
					  margin: 0;
 | 
				
			||||||
  width: 100%;
 | 
					  width: 100%;
 | 
				
			||||||
  height: 100%;
 | 
					  min-height: 100vh;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#app {
 | 
					#app {
 | 
				
			||||||
| 
						 | 
					@ -60,15 +59,3 @@ body a:hover {
 | 
				
			||||||
  background-color: #06345F;
 | 
					  background-color: #06345F;
 | 
				
			||||||
  color: #ffffff;
 | 
					  color: #ffffff;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
:is(h1, h2, h3, h4, h5, h6) {
 | 
					 | 
				
			||||||
    color: #06345F;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
strong {
 | 
					 | 
				
			||||||
  color: #06345F;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ul li::marker {
 | 
					 | 
				
			||||||
  color: #06345F;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 84 KiB  | 
| 
		 Before Width: | Height: | Size: 17 KiB  | 
| 
		 Before Width: | Height: | Size: 155 KiB  | 
| 
		 Before Width: | Height: | Size: 130 KiB  | 
| 
						 | 
					@ -1,18 +0,0 @@
 | 
				
			||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
					 | 
				
			||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<svg
 | 
					 | 
				
			||||||
   width="68.965652mm"
 | 
					 | 
				
			||||||
   height="68.948975mm"
 | 
					 | 
				
			||||||
   viewBox="0 0 68.965652 68.948975"
 | 
					 | 
				
			||||||
   version="1.1"
 | 
					 | 
				
			||||||
   id="svg1"
 | 
					 | 
				
			||||||
   xml:space="preserve"
 | 
					 | 
				
			||||||
   xmlns="http://www.w3.org/2000/svg"
 | 
					 | 
				
			||||||
   xmlns:svg="http://www.w3.org/2000/svg"><defs
 | 
					 | 
				
			||||||
     id="defs1" /><g
 | 
					 | 
				
			||||||
     id="layer1"
 | 
					 | 
				
			||||||
     transform="translate(-78.581248,-114.14203)"><path
 | 
					 | 
				
			||||||
       d="m 101.71922,133.815 7.89657,-7.93103 v 33.06897 c 0,4.59771 6.89657,4.59771 6.89657,0 v -33.06897 l 7.89657,7.93103 c 1.34889,1.35999 3.54767,1.35999 4.89656,0 1.36001,-1.34889 1.36001,-3.5477 0,-4.89659 l -13.79313,-13.79308 c -0.32789,-0.31411 -0.71459,-0.56036 -1.1379,-0.72463 -0.83953,-0.34489 -1.78117,-0.34489 -2.6207,0 -0.42331,0.16427 -0.81001,0.41052 -1.1379,0.72463 l -13.793128,13.79308 c -3.265094,3.26437 1.631464,8.16096 4.896558,4.89659 z m 42.3794,14.7931 c -1.90447,0 -3.44833,1.5439 -3.44828,3.44837 v 20.68969 c -2e-5,1.90442 -1.54386,3.44824 -3.44828,3.44824 H 88.926098 c -1.904415,0 -3.448254,-1.54382 -3.448278,-3.44824 v -20.68969 c 0,-4.59771 -6.89657,-4.59771 -6.89657,0 v 20.68969 c -10e-7,5.7133 4.631545,10.34485 10.344848,10.34485 h 48.275962 c 5.7133,0 10.34485,-4.63155 10.34485,-10.34485 v -20.68969 c 5e-5,-1.90447 -1.54382,-3.44837 -3.44829,-3.44837 z"
 | 
					 | 
				
			||||||
       id="path1"
 | 
					 | 
				
			||||||
       style="opacity:1;mix-blend-mode:normal;fill:#06345f;fill-opacity:1;stroke-width:3.448;stroke-dasharray:none" /></g></svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 1.4 KiB  | 
| 
						 | 
					@ -208,7 +208,7 @@ Pattern.prototype.drawShapeTo = function (canvas) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Pattern.prototype.drawColorsTo = function (colorContainer) {
 | 
					Pattern.prototype.drawColorsTo = function (colorContainer) {
 | 
				
			||||||
  this.colors.forEach((color) => {
 | 
					  this.colors.forEach((color) => {
 | 
				
			||||||
    colorContainer.innerHTML += `<div style='background-color: rgb(${color.r}, ${color.g}, ${color.b}); height: 25px; width: 25px; border: 1px solid #000000; border-radius: 16px;'></div>`;
 | 
					    colorContainer.innerHTML += `<div style='background-color: rgb(${color.r}, ${color.g}, ${color.b}); height: 25px; width: 25px; border: 1px solid #000000;'></div>`;
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,35 +1,23 @@
 | 
				
			||||||
import { derived, writable } from "svelte/store";
 | 
					import { derived, writable } from "svelte/store";
 | 
				
			||||||
import translations from "./translations";
 | 
					import translations from "./translations";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const storedLocale = localStorage.getItem("locale");
 | 
					 | 
				
			||||||
const browserLocale = navigator.language || "en";
 | 
					const browserLocale = navigator.language || "en";
 | 
				
			||||||
const [baseLang] = browserLocale.split("-");
 | 
					const [baseLang, region] = browserLocale.split("-");
 | 
				
			||||||
 | 
					 | 
				
			||||||
export const DEFAULT_LOCALE =
 | 
					 | 
				
			||||||
  storedLocale && translations[storedLocale] ? storedLocale :
 | 
					 | 
				
			||||||
  translations[browserLocale] ? browserLocale :
 | 
					 | 
				
			||||||
  translations[baseLang] ? baseLang :
 | 
					 | 
				
			||||||
  "en";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const DEFAULT_LOCALE = translations[browserLocale] ? browserLocale : translations[baseLang] ? baseLang :"en";
 | 
				
			||||||
export const locale = writable(DEFAULT_LOCALE);
 | 
					export const locale = writable(DEFAULT_LOCALE);
 | 
				
			||||||
 | 
					 | 
				
			||||||
locale.subscribe((value) => {
 | 
					 | 
				
			||||||
  if (value) localStorage.setItem("locale", value);
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const locales = Object.entries(translations).map(([key, lang]) => [key, lang.name]);
 | 
					export const locales = Object.entries(translations).map(([key, lang]) => [key, lang.name]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function translate(locale, key, vars = {}) {
 | 
					function translate(locale, key, vars = {}) {
 | 
				
			||||||
  if (!key) throw new Error("Translation key is required.");
 | 
					  if (!key) throw new Error("Translation key is required.");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const fallbackLocale = "en";
 | 
					 | 
				
			||||||
  const validLocale = translations[locale]
 | 
					  const validLocale = translations[locale]
 | 
				
			||||||
    ? locale
 | 
					    ? locale
 | 
				
			||||||
    : translations[baseLang]
 | 
					    : translations[baseLang]
 | 
				
			||||||
    ? baseLang
 | 
					    ? baseLang
 | 
				
			||||||
    : fallbackLocale;
 | 
					    : "en";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let text = translations[validLocale][key] || translations[fallbackLocale][key];
 | 
					  let text = translations[validLocale][key] || translations["en"][key];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!text) {
 | 
					  if (!text) {
 | 
				
			||||||
    console.error(`Missing translation for key "${key}" in locale "${validLocale}".`);
 | 
					    console.error(`Missing translation for key "${key}" in locale "${validLocale}".`);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,50 +5,19 @@ export default {
 | 
				
			||||||
      "head.keywords": "free embroidery file viewer, open PES files online, view DST files, embroidery file preview, EXP file viewer, multiple embroidery files",
 | 
					      "head.keywords": "free embroidery file viewer, open PES files online, view DST files, embroidery file preview, EXP file viewer, multiple embroidery files",
 | 
				
			||||||
      "head.ogtitle": "Free Online Embroidery File Viewer – Open PES, DST & More",
 | 
					      "head.ogtitle": "Free Online Embroidery File Viewer – Open PES, DST & More",
 | 
				
			||||||
      "head.ogdescription": "Upload and preview multiple embroidery files like PES, DST, and EXP online for free. No software needed!",
 | 
					      "head.ogdescription": "Upload and preview multiple embroidery files like PES, DST, and EXP online for free. No software needed!",
 | 
				
			||||||
      "nav.home": "🏠 Home",
 | 
					 | 
				
			||||||
      "nav.viewer": "🧵 Viewer",
 | 
					 | 
				
			||||||
      "nav.donate": "💖 Donate",
 | 
					 | 
				
			||||||
      "nav.about": "ℹ️ About",
 | 
					 | 
				
			||||||
      "main.title": "Upload files",
 | 
					      "main.title": "Upload files",
 | 
				
			||||||
      "home.main.title": "🧵 Free Online Embroidery File Viewer",
 | 
					      "main.languageSwitch": "Mudar para Português",
 | 
				
			||||||
      "home.main.description": "<p>✨Upload and preview your embroidery designs instantly – no software needed.</p> <p><strong>Embroidery Viewer</strong> is a free, browser-based tool that supports multiple embroidery file formats. View your designs quickly and securely, right in your browser.</p>",
 | 
					      "main.fileSize": "Max file size is <strong>{{fileSize}}kb</strong>.",
 | 
				
			||||||
      "home.features.title": "🚀 Features",
 | 
					 | 
				
			||||||
      "home.features.list": "<ul><li>📂 <strong>Supports Multiple Formats:</strong> DST, PES, JEF, EXP, VP3, and more</li><li>⚡ <strong>Quick Previews:</strong> See your embroidery files rendered as images</li><li>🧷 <strong>Multiple Files at Once:</strong> Upload several designs and view them side-by-side</li><li>🔒 <strong>No Upload to Server:</strong> Your files stay private – all processing happens locally</li><li>⬇️ <strong>Download as Image:</strong> Save each embroidery design preview as a PNG</li><li>💸 <strong>Fast & Free:</strong> No installations, no sign-ups – just open and use</li></ul>",
 | 
					 | 
				
			||||||
      "home.howtouse.title": "📘 How to Use",
 | 
					 | 
				
			||||||
      "home.howtouse.list": "<ol><li>📁 <strong>Click</strong> the upload button <em>or</em> <strong>drag and drop</strong> your embroidery files into the drop area</li><li>🧵 Select one or more embroidery files</li><li>▶️ Click the <strong>“Render files”</strong> button to preview your designs</li><li>👀 Instantly view your designs right in your browser – it’s that simple</li></ol>",
 | 
					 | 
				
			||||||
      "home.testimonials.title": "❤️ Loved by Hobbyists and Professionals",
 | 
					 | 
				
			||||||
      "home.testimonials.description": "<p>Whether you're a hobbyist working on your next DIY project or a professional digitizer reviewing client files, <strong>Embroidery Viewer</strong> gives you a no-fuss, instant way to visualize your work.</p>",
 | 
					 | 
				
			||||||
      "home.donation.title": "💖 Help Keep It Free",
 | 
					 | 
				
			||||||
      "home.donation.description": "<p><strong>Embroidery Viewer is completely free</strong> for everyone to use, with no ads.</p><p>If you find it useful and want to support ongoing development and hosting costs, please consider making a small donation.</p>",
 | 
					 | 
				
			||||||
      "home.donation.cta": "🙌 Donate Now",
 | 
					 | 
				
			||||||
      "home.donation.cta.description": "every little bit helps!",
 | 
					 | 
				
			||||||
      "home.cta.title": "🚀 Try It Now",
 | 
					 | 
				
			||||||
      "home.cta.cta": "🧵 Open Viewer",
 | 
					 | 
				
			||||||
      "home.cta.cta.description": "the fastest <strong>Free Online Embroidery File Viewer</strong>.",
 | 
					 | 
				
			||||||
      "donate.title": "💖 Donate",
 | 
					 | 
				
			||||||
      "donate.subtitle": "Help support Embroidery Viewer and its development!",
 | 
					 | 
				
			||||||
      "donate.description": "⭐️ <strong>Embroidery Viewer</strong> is free to use, with no ads. If you find this tool helpful, please consider making a donation to keep it running and fund future improvements.",
 | 
					 | 
				
			||||||
      "donate.ways": "💸 Ways to Donate",
 | 
					 | 
				
			||||||
      "donate.bitcoin.description": "Scan or copy the address",
 | 
					 | 
				
			||||||
      "donate.copy": "Copy Address",
 | 
					 | 
				
			||||||
      "donate.copied": "Copied to Clipboard!",
 | 
					 | 
				
			||||||
      "donate.copy.failed": "Copy Failed!",
 | 
					 | 
				
			||||||
      "donate.monero.description": "Private and secure donation option.",
 | 
					 | 
				
			||||||
      "donate.paypal.description": "Want to show support in a friendly way?",
 | 
					 | 
				
			||||||
      "donate.paypal.link": "Open Donation link",
 | 
					 | 
				
			||||||
      "main.languageSwitch": "🇧🇷",
 | 
					 | 
				
			||||||
      "main.fileSize": "Max file size is <strong>{{fileSize}}MB</strong>.",
 | 
					 | 
				
			||||||
      "main.supportedFormats": "Accepted formats: <strong>{{supportedFormats}}</strong>.",
 | 
					      "main.supportedFormats": "Accepted formats: <strong>{{supportedFormats}}</strong>.",
 | 
				
			||||||
      "main.render": "Render files",
 | 
					      "main.render": "Render files",
 | 
				
			||||||
      "main.dropzone": "<strong>Choose files</strong><br /><span>or drag and drop them here</span>",
 | 
					      "main.dropzone": "Drag and drop files here or click to upload.",
 | 
				
			||||||
      "main.browse": "Browse",
 | 
					 | 
				
			||||||
      "main.selected": "Selected files",
 | 
					      "main.selected": "Selected files",
 | 
				
			||||||
      "main.rejected": "Rejected files",
 | 
					      "main.rejected": "Rejected files",
 | 
				
			||||||
      "main.stitches": "Stitches",
 | 
					      "main.stitches": "Stitches",
 | 
				
			||||||
      "main.dimensions": "Dimensions (x, y)",
 | 
					      "main.dimensions": "Dimensions (x, y)",
 | 
				
			||||||
      "main.download": "Download image",
 | 
					      "main.download": "Download image",
 | 
				
			||||||
      "main.copyright": "Copyright © {{year}} <a href=\"{{website}}\" target=\"_blank\" rel=\"noreferrer\">Leonardo Murça</a>. <br/> All rights reserved.",
 | 
					      "main.copyright": "Copyright © {{year}} <a href=\"{{website}}\" target=\"_blank\" rel=\"noreferrer\">Leonardo Murça</a>. <br/> All rights reserved.",
 | 
				
			||||||
      "main.version": "🧵 Version: {{version}}"
 | 
					      "main.version": "Version: {{version}}"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    pt: {
 | 
					    pt: {
 | 
				
			||||||
      "head.title": "Visualizador de arquivos de bordado online gratuito – Abra PES, DST, EXP e mais",
 | 
					      "head.title": "Visualizador de arquivos de bordado online gratuito – Abra PES, DST, EXP e mais",
 | 
				
			||||||
| 
						 | 
					@ -56,49 +25,18 @@ export default {
 | 
				
			||||||
      "head.keywords": "visualizador de arquivos de bordado grátis, abra arquivos PES online, visualize arquivos DST, pré-visualização de arquivos de bordado, visualizador de arquivos EXP, vários arquivos de bordado",
 | 
					      "head.keywords": "visualizador de arquivos de bordado grátis, abra arquivos PES online, visualize arquivos DST, pré-visualização de arquivos de bordado, visualizador de arquivos EXP, vários arquivos de bordado",
 | 
				
			||||||
      "head.ogtitle": "Visualizador de arquivos de bordado online gratuito – Abra PES, DST e mais",
 | 
					      "head.ogtitle": "Visualizador de arquivos de bordado online gratuito – Abra PES, DST e mais",
 | 
				
			||||||
      "head.ogdescription": "Carregue e visualize vários arquivos de bordado como PES, DST e EXP online gratuitamente. Não precisa de software!",
 | 
					      "head.ogdescription": "Carregue e visualize vários arquivos de bordado como PES, DST e EXP online gratuitamente. Não precisa de software!",
 | 
				
			||||||
      "nav.home": "🏠 Página Inicial",
 | 
					 | 
				
			||||||
      "nav.viewer": "🧵 Visualizador",
 | 
					 | 
				
			||||||
      "nav.donate": "💖 Doe",
 | 
					 | 
				
			||||||
      "nav.about": "ℹ️ Sobre",
 | 
					 | 
				
			||||||
      "home.main.title": "🧵 Visualizador de arquivos de bordado online gratuito",
 | 
					 | 
				
			||||||
      "home.main.description": "<p>✨Carregue e visualize seus desenhos de bordado instantaneamente – sem necessidade de software</p> <p><strong>Embroidery Viewer</strong> é uma ferramenta gratuita para navegador que suporta diversos formatos de arquivo de bordado. Visualize seus designs de forma rápida e segura, diretamente no seu navegador.</p>",
 | 
					 | 
				
			||||||
      "home.features.title": "🚀 Funcionalidades",
 | 
					 | 
				
			||||||
      "home.features.list": "<ul><li>📂 <strong>Suporta vários formatos:</strong> DST, PES, JEF, EXP, VP3 e mais</li><li>⚡ <strong>Visualizações rápidas:</strong> Veja seus arquivos de bordado renderizados como imagens</li><li>🧷 <strong>Vários arquivos de uma só vez:</strong> Carregue vários designs e visualize-os lado a lado</li><li>🔒 <strong>Sem upload para o servidor:</strong> Seus arquivos permanecem privados – todo o processamento acontece localmente</li><li>⬇️ <strong>Baixar como imagem:</strong> Salve cada pré-visualização do desenho do bordado como um PNG</li><li>💸 <strong>Rápido e gratuito:</strong> Sem instalações, sem cadastros – basta abrir e usar</li></ul>",
 | 
					 | 
				
			||||||
      "home.howtouse.title": "📘 Como usar",
 | 
					 | 
				
			||||||
      "home.howtouse.list": "<ol><li>📁 <strong>Clique</strong> no botão de upload <em>ou</em> <strong>arraste e solte</strong> seus arquivos de bordado na área de soltar</li><li>🧵 Selecione um ou mais arquivos de bordado</li><li>▶️ Clique no botão <strong>“Renderizar arquivos”</strong> para visualizar seus designs</li><li>👀 Visualize seus designs instantaneamente no seu navegador – é simples assim</li></ol>",
 | 
					 | 
				
			||||||
      "home.testimonials.title": "❤️ Amado por Hobbyistas e Profissionais",
 | 
					 | 
				
			||||||
      "home.testimonials.description": "<p>Seja você um amador trabalhando em seu próximo projeto \"faça você mesmo\" ou um digitalizador profissional revisando arquivos de clientes, o <strong>Embroidery Viewer</strong> oferece uma maneira fácil e instantânea de visualizar seu trabalho.</p>",
 | 
					 | 
				
			||||||
      "home.donation.title": "💖 Ajude a mantê-lo gratuito",
 | 
					 | 
				
			||||||
      "home.donation.description": "<p><strong>O Embroidery Viewer é totalmente gratuito</strong> para todos usarem, sem anúncios.</p><p>Se você o achar útil e quiser apoiar o desenvolvimento contínuo e os custos de hospedagem, considere fazer uma pequena doação.</p>",
 | 
					 | 
				
			||||||
      "home.donation.cta": "🙌 Doe agora",
 | 
					 | 
				
			||||||
      "home.donation.cta.description": "cada pequena ajuda é bem-vinda!",
 | 
					 | 
				
			||||||
      "home.cta.title": "🚀 Experimente agora",
 | 
					 | 
				
			||||||
      "home.cta.cta": "🧵 Abrir visualizador",
 | 
					 | 
				
			||||||
      "home.cta.cta.description": "o <strong>visualizador de arquivos de bordado online gratuito</strong> mais rápido.",
 | 
					 | 
				
			||||||
      "donate.title": "💖 Doe",
 | 
					 | 
				
			||||||
      "donate.subtitle": "Ajude a apoiar o Embroidery Viewer e seu desenvolvimento!",
 | 
					 | 
				
			||||||
      "donate.description": "⭐️ O <strong>Embroidery Viewer</strong> é gratuito e sem anúncios. Se você achar esta ferramenta útil, considere fazer uma doação para mantê-la funcionando e financiar melhorias futuras.",
 | 
					 | 
				
			||||||
      "donate.ways": "💸 Formas de doar",
 | 
					 | 
				
			||||||
      "donate.bitcoin.description": "Escaneie ou copie o endereço",
 | 
					 | 
				
			||||||
      "donate.copy": "Copiar Endereço",
 | 
					 | 
				
			||||||
      "donate.copied": "Copiado para a área de transferência!",
 | 
					 | 
				
			||||||
      "donate.copy.failed": "Falha na Cópia!",
 | 
					 | 
				
			||||||
      "donate.monero.description": "Opção de doação privada e segura.",
 | 
					 | 
				
			||||||
      "donate.paypal.description": "Quer demonstrar apoio de uma forma amigável?",
 | 
					 | 
				
			||||||
      "donate.paypal.link": "Abrir Link de Doação",
 | 
					 | 
				
			||||||
      "main.title": "Carregar arquivos",
 | 
					      "main.title": "Carregar arquivos",
 | 
				
			||||||
      "main.languageSwitch": "🇺🇸",
 | 
					      "main.languageSwitch": "Switch to English",
 | 
				
			||||||
      "main.fileSize": "O tamanho máximo de cada arquivo é <strong>{{fileSize}}MB</strong>.",
 | 
					      "main.fileSize": "O tamanho máximo do arquivo é <strong>{{fileSize}}kb</strong>.",
 | 
				
			||||||
      "main.supportedFormats": "Formatos aceitos: <strong>{{supportedFormats}}</strong>.",
 | 
					      "main.supportedFormats": "Formatos aceitos: <strong>{{supportedFormats}}</strong>.",
 | 
				
			||||||
      "main.render": "Renderizar arquivos",
 | 
					      "main.render": "Renderizar arquivos",
 | 
				
			||||||
      "main.dropzone": "<strong>Selecione arquivos</strong><br /><span>ou arraste e solte-os aqui</span>",
 | 
					      "main.dropzone": "Arraste e solte os arquivos aqui ou clique para fazer upload.",
 | 
				
			||||||
      "main.browse": "Selecionar arquivos",
 | 
					 | 
				
			||||||
      "main.selected": "Arquivos selecionados",
 | 
					      "main.selected": "Arquivos selecionados",
 | 
				
			||||||
      "main.rejected": "Arquivos recusados",
 | 
					      "main.rejected": "Arquivos recusados",
 | 
				
			||||||
      "main.stitches": "Pontos",
 | 
					      "main.stitches": "Pontos",
 | 
				
			||||||
      "main.dimensions": "Dimensões (x, y)",
 | 
					      "main.dimensions": "Dimensões (x, y)",
 | 
				
			||||||
      "main.download": "Baixar imagem",
 | 
					      "main.download": "Baixar imagem",
 | 
				
			||||||
      "main.copyright": "Copyright © {{year}} <a href=\"{{website}}/pt-br\" target=\"_blank\" rel=\"noreferrer\">Leonardo Murça</a>. <br/> Todos os direitos reservados.",
 | 
					      "main.copyright": "Copyright © {{year}} <a href=\"{{website}}/pt-br\" target=\"_blank\" rel=\"noreferrer\">Leonardo Murça</a>. <br/> Todos os direitos reservados.",
 | 
				
			||||||
      "main.version": "🧵 Versão: {{version}}"
 | 
					      "main.version": "Versão: {{version}}"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
  import { t } from "../../i18n"
 | 
					  import { t } from "../i18n"
 | 
				
			||||||
  import renderFileToCanvas from "../../file-renderer";
 | 
					  import renderFileToCanvas from "../file-renderer";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let files = [];
 | 
					  export let files = [];
 | 
				
			||||||
  let canvasRefs = [];
 | 
					  let canvasRefs = [];
 | 
				
			||||||
| 
						 | 
					@ -84,9 +84,7 @@
 | 
				
			||||||
    max-height: 1000px;
 | 
					    max-height: 1000px;
 | 
				
			||||||
    margin-bottom: 15px;
 | 
					    margin-bottom: 15px;
 | 
				
			||||||
    padding: 10px;
 | 
					    padding: 10px;
 | 
				
			||||||
/*    border: 2px solid black;*/
 | 
					    border: 2px solid black;
 | 
				
			||||||
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
 | 
					 | 
				
			||||||
    border-radius: 16px;
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .canvas {
 | 
					  .canvas {
 | 
				
			||||||
| 
						 | 
					@ -110,13 +108,10 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  div[role="button"] {
 | 
					  div[role="button"] {
 | 
				
			||||||
    background-color: #05345f;
 | 
					    background-color: #05345f;
 | 
				
			||||||
    font-weight: bold;
 | 
					    font-weight: 500;
 | 
				
			||||||
    color: white;
 | 
					    color: white;
 | 
				
			||||||
    padding: 10px;
 | 
					    padding: 10px;
 | 
				
			||||||
    border-radius: 10px;
 | 
					    border-radius: 0;
 | 
				
			||||||
    padding: 10px;
 | 
					 | 
				
			||||||
    width: 50%;
 | 
					 | 
				
			||||||
    text-align: center;
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  div[role="button"]:hover {
 | 
					  div[role="button"]:hover {
 | 
				
			||||||
| 
						 | 
					@ -134,10 +129,5 @@
 | 
				
			||||||
    #container {
 | 
					    #container {
 | 
				
			||||||
      width: 100%;
 | 
					      width: 100%;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    div[role="button"] {
 | 
					 | 
				
			||||||
      width: 100%;
 | 
					 | 
				
			||||||
      padding: 15px;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,5 @@
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
  import { t } from "../../i18n"
 | 
					  import { t } from "../i18n"
 | 
				
			||||||
  import upload from "../../assets/upload.svg"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let files;
 | 
					  export let files;
 | 
				
			||||||
  export let supportedFormats;
 | 
					  export let supportedFormats;
 | 
				
			||||||
| 
						 | 
					@ -16,11 +15,13 @@
 | 
				
			||||||
  tabindex={0}
 | 
					  tabindex={0}
 | 
				
			||||||
  role="region"
 | 
					  role="region"
 | 
				
			||||||
  on:keydown={onKeydown}
 | 
					  on:keydown={onKeydown}
 | 
				
			||||||
 | 
					  on:click={onClick}
 | 
				
			||||||
  on:dragover|preventDefault|stopPropagation
 | 
					  on:dragover|preventDefault|stopPropagation
 | 
				
			||||||
  on:drop|preventDefault|stopPropagation={onDrop}
 | 
					  on:drop|preventDefault|stopPropagation={onDrop}
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
  <img src={upload} width="40" height="40" alt="Upload icon" />
 | 
					  <label id="file-label" for="file-input"
 | 
				
			||||||
  <label id="file-label" for="file-input">{@html $t("main.dropzone")}</label>
 | 
					    >{$t("main.dropzone")}</label
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
  <input
 | 
					  <input
 | 
				
			||||||
    id="file-input"
 | 
					    id="file-input"
 | 
				
			||||||
    type="file"
 | 
					    type="file"
 | 
				
			||||||
| 
						 | 
					@ -28,58 +29,38 @@
 | 
				
			||||||
    accept={supportedFormats.join(",")}
 | 
					    accept={supportedFormats.join(",")}
 | 
				
			||||||
    multiple
 | 
					    multiple
 | 
				
			||||||
    on:change={onChange}
 | 
					    on:change={onChange}
 | 
				
			||||||
    bind:this={files}
 | 
					    bind:files
 | 
				
			||||||
  />
 | 
					  />
 | 
				
			||||||
  <button on:click|preventDefault={onClick}>{$t("main.browse")}</button>
 | 
					 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style>
 | 
					<style>
 | 
				
			||||||
  #dropzone {
 | 
					  #dropzone {
 | 
				
			||||||
    display: flex;
 | 
					    display: flex;
 | 
				
			||||||
    flex-direction: column;
 | 
					    height: 100px;
 | 
				
			||||||
    align-items: center;
 | 
					 | 
				
			||||||
    text-align: center;
 | 
					 | 
				
			||||||
    border: 1px solid #d3dce6;
 | 
					 | 
				
			||||||
    border-radius: 12px;
 | 
					 | 
				
			||||||
    width: 100%;
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    border: 5px dotted black;
 | 
				
			||||||
    padding: 15px;
 | 
					    padding: 15px;
 | 
				
			||||||
    z-index: 10;
 | 
					    z-index: 10;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  #file-label {
 | 
					  #file-label {
 | 
				
			||||||
    z-index: -1;
 | 
					    z-index: -1;
 | 
				
			||||||
    margin-top: 10px;
 | 
					    font-weight: 600;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  #file-input {
 | 
					  #file-input {
 | 
				
			||||||
    display: none;
 | 
					    display: none;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  button {
 | 
					  #dropzone:hover {
 | 
				
			||||||
    margin-top: 20px;
 | 
					 | 
				
			||||||
    padding: 12px 24px;
 | 
					 | 
				
			||||||
    background-color: #06345F;
 | 
					 | 
				
			||||||
    color: white;
 | 
					 | 
				
			||||||
    border: none;
 | 
					 | 
				
			||||||
    border-radius: 10px;
 | 
					 | 
				
			||||||
    font-size: 1rem;
 | 
					 | 
				
			||||||
    cursor: pointer;
 | 
					    cursor: pointer;
 | 
				
			||||||
    width: 60%;
 | 
					    border: 5px dotted #05345f;
 | 
				
			||||||
    font-weight: bold;
 | 
					    color: #05345f;
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  button:hover {
 | 
					 | 
				
			||||||
    color: #fff;
 | 
					 | 
				
			||||||
    background-color: #000;
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @media only screen and (max-device-width: 812px) {
 | 
					  @media only screen and (max-device-width: 812px) {
 | 
				
			||||||
    #dropzone {
 | 
					    #dropzone {
 | 
				
			||||||
      width: 100%;
 | 
					      width: 100%;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    button {
 | 
					 | 
				
			||||||
      width: 100%;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -7,39 +7,24 @@
 | 
				
			||||||
{#if files.length !== 0}
 | 
					{#if files.length !== 0}
 | 
				
			||||||
  <div id="selected-files-container">
 | 
					  <div id="selected-files-container">
 | 
				
			||||||
    <h2>{title}:</h2>
 | 
					    <h2>{title}:</h2>
 | 
				
			||||||
    <div id="files-list">
 | 
					 | 
				
			||||||
    {#each Array.from(files) as file}
 | 
					    {#each Array.from(files) as file}
 | 
				
			||||||
      <div id={isError ? "selected-file-card-error" : "selected-file-card"}>
 | 
					      <div id={isError ? "selected-file-card-error" : "selected-file-card"}>
 | 
				
			||||||
        <span>{file.name}</span>
 | 
					        <p>{file.name} ({file.size / 1000} kb)</p>
 | 
				
			||||||
        <span>{Math.round(file.size / 1000)} KB</span>
 | 
					 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    {/each}
 | 
					    {/each}
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
{/if}
 | 
					{/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style>
 | 
					<style>
 | 
				
			||||||
  #files-list{
 | 
					 | 
				
			||||||
    display: flex;
 | 
					 | 
				
			||||||
    flex-direction: column;
 | 
					 | 
				
			||||||
    align-items: center;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  #selected-file-card {
 | 
					  #selected-file-card {
 | 
				
			||||||
    display: flex;
 | 
					    border: 1px solid #000;
 | 
				
			||||||
    justify-content: space-between;
 | 
					 | 
				
			||||||
    color: #06345F;
 | 
					 | 
				
			||||||
    font-weight: bolder;
 | 
					 | 
				
			||||||
    width: 500px;
 | 
					    width: 500px;
 | 
				
			||||||
    padding-left: 15px;
 | 
					    padding-left: 15px;
 | 
				
			||||||
    margin-top: 10px;
 | 
					    margin-top: 10px;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  #selected-file-card-error {
 | 
					  #selected-file-card-error {
 | 
				
			||||||
    display: flex;
 | 
					    border: 1px solid red;
 | 
				
			||||||
    justify-content: space-between;
 | 
					 | 
				
			||||||
    color: #06345F;
 | 
					 | 
				
			||||||
    font-weight: bolder;
 | 
					 | 
				
			||||||
    width: 500px;
 | 
					    width: 500px;
 | 
				
			||||||
    padding-left: 15px;
 | 
					    padding-left: 15px;
 | 
				
			||||||
    margin-top: 10px;
 | 
					    margin-top: 10px;
 | 
				
			||||||
| 
						 | 
					@ -2,17 +2,18 @@
 | 
				
			||||||
  import CardList from "./CardList.svelte";
 | 
					  import CardList from "./CardList.svelte";
 | 
				
			||||||
  import Dropzone from "./Dropzone.svelte";
 | 
					  import Dropzone from "./Dropzone.svelte";
 | 
				
			||||||
  import FileList from "./FileList.svelte";
 | 
					  import FileList from "./FileList.svelte";
 | 
				
			||||||
 | 
					  import LanguageIcon from './LanguageIcon.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  import { filterFiles } from "../../utils/filterFiles";
 | 
					  import { filterFiles } from "../utils/filterFiles";
 | 
				
			||||||
  import { supportedFormats } from "../../format-readers";
 | 
					  import { supportedFormats } from "../format-readers";
 | 
				
			||||||
  import { t } from "../../i18n"
 | 
					  import { t, locale, locales } from "../i18n"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let acceptedFiles;
 | 
					  let acceptedFiles;
 | 
				
			||||||
  let rejectedFiles;
 | 
					  let rejectedFiles;
 | 
				
			||||||
  let areAcceptedFilesRendered = false;
 | 
					  let areAcceptedFilesRendered = false;
 | 
				
			||||||
  const fileRequirements = {
 | 
					  const fileRequirements = {
 | 
				
			||||||
    supportedFormats: Object.values(supportedFormats).map((f) => f.ext),
 | 
					    supportedFormats: Object.values(supportedFormats).map((f) => f.ext),
 | 
				
			||||||
    maxSize: 1000000,
 | 
					    maxSize: 700000,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const onSubmit = () => {
 | 
					  const onSubmit = () => {
 | 
				
			||||||
| 
						 | 
					@ -46,6 +47,11 @@
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const onSwitchToOppositeLang = () => {
 | 
				
			||||||
 | 
					    const oppositeLang = locales.find(item => item[0] !== $locale);
 | 
				
			||||||
 | 
					    locale.set(oppositeLang[0]);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<form
 | 
					<form
 | 
				
			||||||
| 
						 | 
					@ -55,9 +61,17 @@
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
    <div class="title-container">
 | 
					    <div class="title-container">
 | 
				
			||||||
      <h2>{$t("main.title")}</h2>
 | 
					      <h2>{$t("main.title")}</h2>
 | 
				
			||||||
 | 
					        <a class="common-switch {$locale === 'en' ? 'portuguese-switch' : 'english-switch' }" href="#" on:click|preventDefault={onSwitchToOppositeLang}>
 | 
				
			||||||
 | 
					            <div style="display: flex; width: fit-content;">
 | 
				
			||||||
 | 
					              <div class="language-icon">
 | 
				
			||||||
 | 
					                <LanguageIcon cssClass="{$locale === 'en' ? 'portuguese-switch' : 'english-switch' }" />
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            <span>{$t("main.languageSwitch")}</span>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  <p>
 | 
					  <p>
 | 
				
			||||||
    {@html $t("main.fileSize", { fileSize: fileRequirements.maxSize / 1000000 })}
 | 
					    {@html $t("main.fileSize", { fileSize: fileRequirements.maxSize / 1000 })}
 | 
				
			||||||
    {@html $t("main.supportedFormats", { supportedFormats: fileRequirements.supportedFormats.join(", ") })}
 | 
					    {@html $t("main.supportedFormats", { supportedFormats: fileRequirements.supportedFormats.join(", ") })}
 | 
				
			||||||
  </p>
 | 
					  </p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -70,7 +84,7 @@
 | 
				
			||||||
    {onChange}
 | 
					    {onChange}
 | 
				
			||||||
  />
 | 
					  />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <input id="submit" type="submit" value={$t("main.render")} />
 | 
					  <input type="submit" value={$t("main.render")} />
 | 
				
			||||||
</form>
 | 
					</form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{#if areAcceptedFilesRendered}
 | 
					{#if areAcceptedFilesRendered}
 | 
				
			||||||
| 
						 | 
					@ -81,23 +95,47 @@
 | 
				
			||||||
{/if}
 | 
					{/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style>
 | 
					<style>
 | 
				
			||||||
  form {
 | 
					 | 
				
			||||||
    width: fit-content;
 | 
					 | 
				
			||||||
    margin: 0 auto;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  .title-container {
 | 
					  .title-container {
 | 
				
			||||||
    display: flex;
 | 
					    display: flex;
 | 
				
			||||||
    justify-content: space-between;
 | 
					    justify-content: space-between;
 | 
				
			||||||
    align-items: center;
 | 
					    align-items: center;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  #submit {
 | 
					  .language-icon {
 | 
				
			||||||
    border: none;
 | 
					    width: 30px;
 | 
				
			||||||
    border-radius: 10px;
 | 
					    height: 24px;
 | 
				
			||||||
    padding: 15px
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @media only screen and (max-device-width: 768px) {
 | 
					  .common-switch {
 | 
				
			||||||
 | 
					    width: fit-content;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .portuguese-switch {
 | 
				
			||||||
 | 
					    color: #0C8F27;
 | 
				
			||||||
 | 
					    border-bottom: 3px solid #0C8F27;
 | 
				
			||||||
 | 
					    fill: #0C8F27 !important;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .portuguese-switch:hover {
 | 
				
			||||||
 | 
					    background: #0C8F27;
 | 
				
			||||||
 | 
					    color: #ffffff;
 | 
				
			||||||
 | 
					    fill: #ffffff !important;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .english-switch{
 | 
				
			||||||
 | 
					    color: #BE0A2F;
 | 
				
			||||||
 | 
					    border-bottom: 3px solid #BE0A2F;
 | 
				
			||||||
 | 
					    width: fit-content;
 | 
				
			||||||
 | 
					    fill: #BE0A2F !important;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .english-switch:hover {
 | 
				
			||||||
 | 
					    background: #BE0A2F;
 | 
				
			||||||
 | 
					    color: #ffffff;
 | 
				
			||||||
 | 
					    fill: #ffffff !important;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @media only screen and (max-device-width: 812px) {
 | 
				
			||||||
    #form {
 | 
					    #form {
 | 
				
			||||||
      width: 100%;
 | 
					      width: 100%;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
							
								
								
									
										18
									
								
								src/lib/Footer.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,18 @@
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					  import { t } from "../i18n"
 | 
				
			||||||
 | 
					  import { appVersion } from "../utils/env";
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<footer>
 | 
				
			||||||
 | 
					  <p>{@html $t("main.copyright", { year: new Date().getFullYear(), website: "https://leomurca.xyz" })}</p>
 | 
				
			||||||
 | 
					  <p>{@html $t("main.version", { version: appVersion() })}</p>
 | 
				
			||||||
 | 
					</footer>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style>
 | 
				
			||||||
 | 
					  footer {
 | 
				
			||||||
 | 
					    bottom: 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  p {
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
    import { t, locale } from "../../i18n";
 | 
					    import { t, locale } from "../i18n";
 | 
				
			||||||
    import thumbnail from "../../assets/thumbnail.webp";
 | 
					    import thumbnail from "../assets/thumbnail.webp";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $: document.documentElement.lang = $locale;
 | 
					    $: document.documentElement.lang = $locale;
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
							
								
								
									
										42
									
								
								src/lib/Header.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,42 @@
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					  import MediaQuery from "../lib/MediaQuery.svelte";
 | 
				
			||||||
 | 
					  import logo from "../assets/embroidery-viewer-logo.webp";
 | 
				
			||||||
 | 
					  import logoMobile from "../assets/embroidery-viewer-logo-mobile.webp";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const configsFor = (matches) => {
 | 
				
			||||||
 | 
					    return matches
 | 
				
			||||||
 | 
					      ? { src: logoMobile, width: 350, height: 96 }
 | 
				
			||||||
 | 
					      : { src: logo, width: 460, height: 200 };
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<header>
 | 
				
			||||||
 | 
					  <a href="/">
 | 
				
			||||||
 | 
					    <MediaQuery query="(min-width: 481px) and (max-width: 812px)" let:matches>
 | 
				
			||||||
 | 
					      {@const configs = configsFor(matches)}
 | 
				
			||||||
 | 
					      <img
 | 
				
			||||||
 | 
					        class="logo"
 | 
				
			||||||
 | 
					        alt="Embroidery viewer logo."
 | 
				
			||||||
 | 
					        src={configs.src}
 | 
				
			||||||
 | 
					        width={configs.width}
 | 
				
			||||||
 | 
					        height={configs.height}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </MediaQuery>
 | 
				
			||||||
 | 
					  </a>
 | 
				
			||||||
 | 
					</header>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style>
 | 
				
			||||||
 | 
					  header {
 | 
				
			||||||
 | 
					    margin-top: 100px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .logo {
 | 
				
			||||||
 | 
					    background-image: logo;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @media only screen and (max-device-width: 812px) {
 | 
				
			||||||
 | 
					    .logo {
 | 
				
			||||||
 | 
					      width: 100%;
 | 
				
			||||||
 | 
					      padding: 20px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
							
								
								
									
										63
									
								
								src/lib/LanguageIcon.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,63 @@
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
						export let cssClass;
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<svg version="1.1" id="Optimized" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
 | 
				
			||||||
 | 
						 viewBox="0 0 841.8897705 595.2755737" style="enable-background:new 0 0 841.8897705 595.2755737;" xml:space="preserve">
 | 
				
			||||||
 | 
					<polygon class={cssClass} style="fill: #020203;" points="512.5472412,488.2385864 542.3688354,537.3009033 558.0941772,491.724762 "/>
 | 
				
			||||||
 | 
					<path class={cssClass} style="fill:#020203;" d="M277.7966003,213.2223663c-1.1176147-1.0975342,1.4554749,8.9684143,5.0365906,12.5897064
 | 
				
			||||||
 | 
						c6.3496399,6.4062805,11.3096008,7.23172,13.9502563,7.3376465c5.8437805,0.2337646,13.0553894-1.4554596,17.3377991-3.2506104
 | 
				
			||||||
 | 
						c4.1435852-1.767746,11.404541-5.4748993,14.1529236-10.8822479c0.5825195-1.1559601,2.1731262-3.0972137,1.1742859-7.8927765
 | 
				
			||||||
 | 
						c-0.7579041-3.6888733-3.1064148-4.980011-5.9698181-4.7754517c-2.8634644,0.1935883-11.5323792,2.5055237-15.7253113,3.7948151
 | 
				
			||||||
 | 
						c-4.1947021,1.2728424-12.8344116,3.9025269-16.6000366,4.7188416
 | 
				
			||||||
 | 
						C287.3968811,215.6767426,279.1151123,214.4842987,277.7966003,213.2223663z"/>
 | 
				
			||||||
 | 
					<path class={cssClass} style="fill: #020203;" d="M383.9637451,333.5533752c-1.6582031-0.6026001-35.9649353-14.8140564-40.828064-17.1424255
 | 
				
			||||||
 | 
						c-3.979248-1.9137573-13.7365417-6.0391541-18.3276062-7.9127808c12.9312439-19.9383545,21.0942688-34.984314,22.1808472-37.276123
 | 
				
			||||||
 | 
						c2.0106506-4.1929321,15.697876-30.9757843,16.0174561-32.6248322c0.3104858-1.6709595,0.6994324-7.8434601,0.3981323-9.3098907
 | 
				
			||||||
 | 
						c-0.3013-1.495636-5.3196411,1.3787689-12.1331482,3.6889343c-6.8244629,2.3009491-19.7940674,10.7361145-24.8032837,11.7934723
 | 
				
			||||||
 | 
						c-5.0274658,1.048233-21.0942688,7.134964-29.3157349,9.8631897c-8.2214966,2.7283783-23.7732849,7.4745941-30.1704102,9.2021637
 | 
				
			||||||
 | 
						c-6.4062805,1.7275696-11.9980316,1.8645325-15.5810242,2.9511414c0,0,0.4766541,5.0183411,1.4280701,6.5231934
 | 
				
			||||||
 | 
						c0.9404755,1.5046997,4.329895,5.193634,8.270813,6.2235718c3.9408875,1.037262,10.4640198,0.6208801,13.4352112-0.0584412
 | 
				
			||||||
 | 
						c2.9693909-0.6903381,8.1137695-3.203125,8.8040161-4.3006287c0.6976929-1.1158447-0.3596802-4.5527039,0.8144836-5.5917969
 | 
				
			||||||
 | 
						c1.1852417-1.0281067,16.8429565-4.6878052,22.754303-6.4738464c5.911377-1.8170166,28.5396118-9.6112061,31.6076355-9.2130585
 | 
				
			||||||
 | 
						c-0.9715576,3.2231903-19.1731262,39.2757416-25.0351562,50.0319366
 | 
				
			||||||
 | 
						c-5.8639221,10.7544556-39.9259338,58.0690613-47.1777039,66.4074402
 | 
				
			||||||
 | 
						c-5.5041504,6.3386841-18.8425598,22.5588379-23.4628143,26.2185059c1.1650848,0.3214722,9.4249115-0.3870544,10.9296875-1.3184509
 | 
				
			||||||
 | 
						c9.3774872-5.7762756,24.9968414-25.219635,30.0261078-31.1419983
 | 
				
			||||||
 | 
						c14.9492188-17.5313721,28.0831604-35.9465942,38.4978638-51.7503967h0.0109558
 | 
				
			||||||
 | 
						c2.0288696,0.8454895,18.4335022,14.2113342,22.7140503,17.1734619c4.2806396,2.9602051,21.172821,12.3851318,24.832489,13.9483643
 | 
				
			||||||
 | 
						c3.6596985,1.5832825,17.7249756,8.0681152,18.3166809,5.8730469
 | 
				
			||||||
 | 
						C388.7592468,347.1237793,385.6236877,334.1834412,383.9637451,333.5533752z"/>
 | 
				
			||||||
 | 
					<path class={cssClass} style="fill-rule:evenodd;clip-rule:evenodd;fill:#020203;" d="M304.6889954,512.2145996
 | 
				
			||||||
 | 
						c3.2871399,2.0087891,6.3916626,3.6523438,9.8614197,5.2958984c6.9394836,3.4697876,14.7920837,7.1221313,22.2794495,9.8614502
 | 
				
			||||||
 | 
						c10.2266541,3.8349609,20.4532776,6.9395142,30.6799316,9.3135376c5.6611633,1.2783203,11.8701782,2.3740234,17.8966064,3.2871094
 | 
				
			||||||
 | 
						c0.5478516,0,16.8009033,2.0087891,20.0880432,2.0087891h16.4356689c6.3916321-0.5478516,12.4180603-0.9130859,18.8096924-1.8261719
 | 
				
			||||||
 | 
						c5.1133423-0.7304688,10.7745056-1.6435547,16.2530518-2.921875c4.0176086-0.9130859,8.2178345-1.8261719,12.2354431-3.1045532
 | 
				
			||||||
 | 
						c3.8349915-1.0957031,8.2178345-2.5566406,12.4180603-4.0175781c2.7392883-0.9130859,5.6611938-2.1914062,8.5830688-3.2871094
 | 
				
			||||||
 | 
						c2.374054-1.0957642,5.2959595-2.3740845,8.0352173-3.4697876c3.2871399-1.4609375,7.1221313-3.4697266,10.7745056-5.2958984
 | 
				
			||||||
 | 
						c2.9219055-1.4609985,6.2090149-3.2871704,9.3135681-5.1133423c2.3740234-1.2783508,7.8526001-5.4785767,10.7744751-5.4785767
 | 
				
			||||||
 | 
						c3.2871094,0,5.4785767,2.9219055,5.4785767,5.4785767c0,5.2959595-7.1221313,6.9395142-10.4093018,9.3135376
 | 
				
			||||||
 | 
						c-3.4697266,2.3740234-7.6699829,4.2002563-11.3223267,6.2090454c-7.3047485,3.8349609-14.7921143,7.1221313-21.9142151,9.8613892
 | 
				
			||||||
 | 
						c-9.3135681,3.4697266-19.5401917,6.756897-28.6711121,8.9483032c-3.4697571,0.7304688-6.9395142,1.6435547-10.4092712,2.1914062
 | 
				
			||||||
 | 
						c-1.8261719,0.3652344-20.818512,3.2871704-26.1144409,3.2871704h-24.1056519
 | 
				
			||||||
 | 
						c-6.3916626-0.5478516-13.1485291-1.2783203-19.5401917-2.1914673c-5.6611633-0.9130859-11.6875916-2.0087891-17.3487549-3.2871094
 | 
				
			||||||
 | 
						c-4.382843-0.9130859-9.1309204-2.1914062-13.3311462-3.4697266c-7.3047485-2.0088501-14.4268799-4.5654907-21.366394-7.3047485
 | 
				
			||||||
 | 
						c-12.6006775-4.7481079-25.7492065-10.9571533-38.1672668-19.1749878c-2.1914062-1.4609375-2.3740234-2.921875-2.3740234-4.5654297
 | 
				
			||||||
 | 
						c0-2.7392883,2.0087891-5.2959595,5.295929-5.2959595C297.7495117,507.4664917,303.5932922,511.6667175,304.6889954,512.2145996z"/>
 | 
				
			||||||
 | 
					<g>
 | 
				
			||||||
 | 
						<path class={cssClass} d="M639.8383789,180.4025879l-40.7682495-12.975708V48.3634644
 | 
				
			||||||
 | 
							c0-3.4697266-2.5567017-5.843811-5.843811-5.843811c-2.5567017,0-84.9176636,28.3059082-91.4918823,30.6799316
 | 
				
			||||||
 | 
							c-22.3344727,7.4448242-86.798645,29.7334595-86.798645,29.7334595l-0.000061,0.0002441
 | 
				
			||||||
 | 
							c-1.0336304,0.2964478-2.6376953,0.7871704-4.7411499,1.4482422L252.854248,48.8499756
 | 
				
			||||||
 | 
							c-1.1881714-0.4193726-2.43396,0.4620972-2.43396,1.7220459v2v104.723877
 | 
				
			||||||
 | 
							c-24.4053345,8.1710205-41.8085327,14.0198364-42.7017212,14.335083c-1.6435547,0.5478516-4.2002563,0.9130859-5.6611938,2.921875
 | 
				
			||||||
 | 
							c-0.7304688,0.7304688-0.9130859,2.0088501-1.2783203,2.921936V484.456543c0,0.3652344,0.1826172,0.5478516,0.1826172,0.7304688
 | 
				
			||||||
 | 
							c1.0957031,2.3740845,3.1044922,3.835022,5.2959595,3.835022c2.7392578,0,208.3677368-69.0298462,212.9332275-70.8560181
 | 
				
			||||||
 | 
							c0.2156372-0.0718994,0.458374-0.2409668,0.6971436-0.4380493l218.84198,69.75177
 | 
				
			||||||
 | 
							c1.1779175,0.3754883,2.3807373-0.50354,2.3807373-1.7399292V182.1427612
 | 
				
			||||||
 | 
							C641.1107178,181.3475952,640.5961304,180.6437988,639.8383789,180.4025879z M410.9730225,409.4003296l-199.0542603,66.2905273
 | 
				
			||||||
 | 
							V182.0402222l199.0542603-66.2905273V409.4003296z M587.9303589,55.6682129v108.2130737l-164.4921875-52.3546143
 | 
				
			||||||
 | 
							L587.9303589,55.6682129z M567.6870728,385.267334l-10.5206299-38.4284668l-60.5161133-18.3422241l-13.0134277,31.3044434
 | 
				
			||||||
 | 
							l-29.2919922-8.8861084l62.1779785-152.5870361l28.5085449,8.6360474l51.9385376,187.1876831L567.6870728,385.267334z"/>
 | 
				
			||||||
 | 
						<polygon class={cssClass} style="fill: #020203;" points="507.8375244,300.3788452 547.7670288,312.4828491 529.5562744,247.8833618 	"/>
 | 
				
			||||||
 | 
					</g>
 | 
				
			||||||
 | 
					</svg>
 | 
				
			||||||
| 
						 | 
					@ -1,24 +0,0 @@
 | 
				
			||||||
<script>
 | 
					 | 
				
			||||||
    import { onMount } from 'svelte';
 | 
					 | 
				
			||||||
    import { routes, fallback } from '../../utils/routes.js';
 | 
					 | 
				
			||||||
    import { path } from '../../utils/stores.js';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const navigate = (to) => {
 | 
					 | 
				
			||||||
      history.pushState({}, '', to);
 | 
					 | 
				
			||||||
      path.set(to);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    window.addEventListener('popstate', () => {
 | 
					 | 
				
			||||||
      path.set(window.location.pathname);
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let component;
 | 
					 | 
				
			||||||
    const unsubscribe = path.subscribe(current => {
 | 
					 | 
				
			||||||
      component = routes[current] !== undefined ? routes[current].component : fallback;
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    onMount(() => () => unsubscribe());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  </script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  <svelte:component this={component} />
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,195 +0,0 @@
 | 
				
			||||||
 | 
					 | 
				
			||||||
<script>
 | 
					 | 
				
			||||||
  import { t } from "../../i18n"
 | 
					 | 
				
			||||||
  import bitcoin from "../../assets/bitcoin.svg"
 | 
					 | 
				
			||||||
  import monero from "../../assets/monero.svg"
 | 
					 | 
				
			||||||
  import paypal from "../../assets/paypal.svg"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  let bitcoinCopyStatus = '';
 | 
					 | 
				
			||||||
  let moneroCopyStatus= '';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const onCopyMonero = async (text) => {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const textarea = document.createElement('textarea');
 | 
					 | 
				
			||||||
      textarea.value = text;
 | 
					 | 
				
			||||||
      textarea.setAttribute('readonly', '');
 | 
					 | 
				
			||||||
      textarea.style.position = 'absolute';
 | 
					 | 
				
			||||||
      textarea.style.left = '-9999px';
 | 
					 | 
				
			||||||
      document.body.appendChild(textarea);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      textarea.select();
 | 
					 | 
				
			||||||
      document.execCommand('copy');
 | 
					 | 
				
			||||||
      document.body.removeChild(textarea);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      moneroCopyStatus = 'donate.copied';
 | 
					 | 
				
			||||||
    } catch (err) {
 | 
					 | 
				
			||||||
      console.error('Copy failed:', err);
 | 
					 | 
				
			||||||
      moneroCopyStatus = 'donate.copy.failed';
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    setTimeout(() => moneroCopyStatus = '', 2000);
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const onCopyBitcoin = async (text) => {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const textarea = document.createElement('textarea');
 | 
					 | 
				
			||||||
      textarea.value = text;
 | 
					 | 
				
			||||||
      textarea.setAttribute('readonly', '');
 | 
					 | 
				
			||||||
      textarea.style.position = 'absolute';
 | 
					 | 
				
			||||||
      textarea.style.left = '-9999px';
 | 
					 | 
				
			||||||
      document.body.appendChild(textarea);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      textarea.select();
 | 
					 | 
				
			||||||
      document.execCommand('copy');
 | 
					 | 
				
			||||||
      document.body.removeChild(textarea);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      bitcoinCopyStatus = 'donate.copied';
 | 
					 | 
				
			||||||
    } catch (err) {
 | 
					 | 
				
			||||||
      console.error('Copy failed:', err);
 | 
					 | 
				
			||||||
      bitcoinCopyStatus = 'donate.copy.failed';
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    setTimeout(() => bitcoinCopyStatus = '', 2000);
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
<section aria-labelledby="donate-title">
 | 
					 | 
				
			||||||
  <h1 id="donate-title">{$t("donate.title")}</h1>
 | 
					 | 
				
			||||||
    <p class="donate-subtitle">{$t("donate.subtitle")}</p>
 | 
					 | 
				
			||||||
    <p>
 | 
					 | 
				
			||||||
      {@html $t("donate.description")}
 | 
					 | 
				
			||||||
    </p>
 | 
					 | 
				
			||||||
</section>
 | 
					 | 
				
			||||||
  <section id="ways" aria-labelledby="ways-title">
 | 
					 | 
				
			||||||
    <h2>{$t("donate.ways")}</h2>
 | 
					 | 
				
			||||||
      <div class="donation-options">
 | 
					 | 
				
			||||||
        <article class="donation-method" aria-labelledby="btc-label">
 | 
					 | 
				
			||||||
          <img src={bitcoin} width="200" height="200" alt="Bitcoin QR code" />
 | 
					 | 
				
			||||||
          <h3 id="btc-label">Bitcoin</h3>
 | 
					 | 
				
			||||||
          <p>{$t("donate.bitcoin.description")}</p>
 | 
					 | 
				
			||||||
          <button id="copy-btc" aria-label="Copy Bitcoin address" on:click={() => onCopyBitcoin("bc1qpc4lpyr6stxrrg3u0k4clp4crlt6z4j6q845rq")}>
 | 
					 | 
				
			||||||
            {#if bitcoinCopyStatus}
 | 
					 | 
				
			||||||
              {$t(bitcoinCopyStatus)}
 | 
					 | 
				
			||||||
            {:else}
 | 
					 | 
				
			||||||
              {$t("donate.copy")}
 | 
					 | 
				
			||||||
            {/if}
 | 
					 | 
				
			||||||
          </button>
 | 
					 | 
				
			||||||
        </article>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <article class="donation-method" aria-labelledby="xmr-label">
 | 
					 | 
				
			||||||
          <img src={monero} alt="Monero QR code" width="200" height="200" />
 | 
					 | 
				
			||||||
          <h3 id="xmr-label">Monero</h3>
 | 
					 | 
				
			||||||
          <p>{$t("donate.monero.description")}</p>
 | 
					 | 
				
			||||||
          <button id="copy-monero" aria-label="Copy Monero address" on:click={() => onCopyMonero("8A9iyTskiBh6f6GDUwnUJaYhAW13gNjDYaZYJBftX434D3XLrcGBko4a8kC4pLSfiuJAoSJ7e8rwP8W4StsVypftCp6FGwm")}>
 | 
					 | 
				
			||||||
            {#if moneroCopyStatus}
 | 
					 | 
				
			||||||
              {$t(moneroCopyStatus)}
 | 
					 | 
				
			||||||
            {:else}
 | 
					 | 
				
			||||||
              {$t("donate.copy")}
 | 
					 | 
				
			||||||
            {/if}
 | 
					 | 
				
			||||||
          </button>
 | 
					 | 
				
			||||||
        </article>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <article class="donation-method" aria-labelledby="bmc-label">
 | 
					 | 
				
			||||||
          <img src={paypal} alt="PayPal" width="200" height="200" />
 | 
					 | 
				
			||||||
          <h3 id="bmc-label">PayPal</h3>
 | 
					 | 
				
			||||||
          <p>{$t("donate.paypal.description")}</p>
 | 
					 | 
				
			||||||
          <a id="paypal-donation-link" aria-label="Paypal donation link" target="_blank" href="https://www.paypal.com/donate/?business=leo@leomurca.xyz¤cy_code=USD">{$t("donate.paypal.link")}</a>
 | 
					 | 
				
			||||||
        </article>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </section>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style>
 | 
					 | 
				
			||||||
  h1 {
 | 
					 | 
				
			||||||
    padding: 0;
 | 
					 | 
				
			||||||
    margin-bottom: 7px;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  .donate-subtitle {
 | 
					 | 
				
			||||||
    font-weight: bold;
 | 
					 | 
				
			||||||
    color: #06345F;
 | 
					 | 
				
			||||||
    margin: 0;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  .donation-options {
 | 
					 | 
				
			||||||
    display: flex;
 | 
					 | 
				
			||||||
    justify-content: space-between;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  .donation-method {
 | 
					 | 
				
			||||||
    display: flex;
 | 
					 | 
				
			||||||
    flex-direction: column;
 | 
					 | 
				
			||||||
    align-items: center;
 | 
					 | 
				
			||||||
    justify-content: center;
 | 
					 | 
				
			||||||
    width: 33.33%;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  .donation-method p {
 | 
					 | 
				
			||||||
    margin-top: 0;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  button {
 | 
					 | 
				
			||||||
    font-size: 14px;
 | 
					 | 
				
			||||||
    background-color: #05345f;
 | 
					 | 
				
			||||||
    font-weight: bold;
 | 
					 | 
				
			||||||
    color: white;
 | 
					 | 
				
			||||||
    padding: 10px;
 | 
					 | 
				
			||||||
    border: none;
 | 
					 | 
				
			||||||
    border-radius: 10px;
 | 
					 | 
				
			||||||
    width: 200px;
 | 
					 | 
				
			||||||
    height: 45px;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  button:hover {
 | 
					 | 
				
			||||||
    cursor: pointer;
 | 
					 | 
				
			||||||
    background-color: black;
 | 
					 | 
				
			||||||
    color: white;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  #paypal-donation-link {
 | 
					 | 
				
			||||||
    font-size: 14px;
 | 
					 | 
				
			||||||
    background-color: #05345f;
 | 
					 | 
				
			||||||
    font-weight: bold;
 | 
					 | 
				
			||||||
    color: white;
 | 
					 | 
				
			||||||
    padding: 10px;
 | 
					 | 
				
			||||||
    border: none;
 | 
					 | 
				
			||||||
    border-radius: 10px;
 | 
					 | 
				
			||||||
    width: 200px;
 | 
					 | 
				
			||||||
    height: 45px;
 | 
					 | 
				
			||||||
    text-align: center;
 | 
					 | 
				
			||||||
    display: flex;
 | 
					 | 
				
			||||||
    align-items: center;
 | 
					 | 
				
			||||||
    justify-content: center;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  #paypal-donation-link:hover {
 | 
					 | 
				
			||||||
    cursor: pointer;
 | 
					 | 
				
			||||||
    background-color: black;
 | 
					 | 
				
			||||||
    color: white;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @media (max-width: 768px) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    button {
 | 
					 | 
				
			||||||
    font-size: 1em;
 | 
					 | 
				
			||||||
    width: 100%;
 | 
					 | 
				
			||||||
    height: 55px;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
    #paypal-donation-link {
 | 
					 | 
				
			||||||
      font-size: 1em;
 | 
					 | 
				
			||||||
      width: 100%;
 | 
					 | 
				
			||||||
      height: 55px;
 | 
					 | 
				
			||||||
      margin: 0;
 | 
					 | 
				
			||||||
      padding: 0;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
    .donation-options{
 | 
					 | 
				
			||||||
      display: flex;
 | 
					 | 
				
			||||||
      flex-direction: column;
 | 
					 | 
				
			||||||
      gap: 50px;
 | 
					 | 
				
			||||||
      justify-content: space-between;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    .donation-method {
 | 
					 | 
				
			||||||
      width: 100%;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,60 +0,0 @@
 | 
				
			||||||
<script>
 | 
					 | 
				
			||||||
    import { t } from "../../i18n"
 | 
					 | 
				
			||||||
    import { path } from '../../utils/stores.js';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const onNavigateTo = (e, route) => {
 | 
					 | 
				
			||||||
        e.preventDefault()
 | 
					 | 
				
			||||||
        history.pushState({}, '', route);
 | 
					 | 
				
			||||||
        path.set(route);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<div class="home-container">
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<section aria-labelledby="main-title">
 | 
					 | 
				
			||||||
    <h1 id="main-title">{$t("home.main.title")}</h1>
 | 
					 | 
				
			||||||
    {@html $t("home.main.description")}
 | 
					 | 
				
			||||||
</section>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<section aria-labelledby="features-title">
 | 
					 | 
				
			||||||
    <h2 id="features-title">{$t("home.features.title")}</h2>
 | 
					 | 
				
			||||||
    {@html $t("home.features.list")}
 | 
					 | 
				
			||||||
</section>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<section aria-labelledby="how-to-use-title">
 | 
					 | 
				
			||||||
    <h2 id="how-to-use-title">{$t("home.howtouse.title")}</h2>
 | 
					 | 
				
			||||||
    {@html $t("home.howtouse.list")}
 | 
					 | 
				
			||||||
</section>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<section aria-labelledby="testimonials-title">
 | 
					 | 
				
			||||||
    <h2 id="testimonials-title">{$t("home.testimonials.title")}</h2>
 | 
					 | 
				
			||||||
    {@html $t("home.testimonials.description")}
 | 
					 | 
				
			||||||
</section>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<section aria-labelledby="donation-title">
 | 
					 | 
				
			||||||
    <h2 id="donation-title">{$t("home.donation.title")}</h2>
 | 
					 | 
				
			||||||
    {@html $t("home.donation.description")}
 | 
					 | 
				
			||||||
    <p><a href="#" on:click={(e) => onNavigateTo(e, "/donate")}  class="button">{$t("home.donation.cta")}</a> – {$t("home.donation.cta.description")}</p>
 | 
					 | 
				
			||||||
</section>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<!--TODO: add video preview-->
 | 
					 | 
				
			||||||
<section aria-labelledby="cta-title">
 | 
					 | 
				
			||||||
    <h2 id="cta-title">{$t("home.cta.title")}</h2>
 | 
					 | 
				
			||||||
    <p><a href="#" on:click={(e) => onNavigateTo(e, "/viewer")} class="button">{$t("home.cta.cta")}</a> – {@html $t("home.cta.cta.description")}</p>
 | 
					 | 
				
			||||||
</section>
 | 
					 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style>
 | 
					 | 
				
			||||||
    .home-container {
 | 
					 | 
				
			||||||
        margin: 0 auto;
 | 
					 | 
				
			||||||
        width: 70%;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @media (max-width: 768px) {
 | 
					 | 
				
			||||||
        .home-container {
 | 
					 | 
				
			||||||
            width: 100%;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,2 +0,0 @@
 | 
				
			||||||
<h1>404 - Not Found</h1>
 | 
					 | 
				
			||||||
<p>Oops! That route does not exist.</p>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,4 +0,0 @@
 | 
				
			||||||
<script>
 | 
					 | 
				
			||||||
    import FileViewer from "../components/FileViewer.svelte"
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
<FileViewer/>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,49 +0,0 @@
 | 
				
			||||||
<script>
 | 
					 | 
				
			||||||
  import { t } from "../../i18n"
 | 
					 | 
				
			||||||
  import { appVersion } from "../../utils/env";
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<footer>
 | 
					 | 
				
			||||||
  <div class="footer-content">
 | 
					 | 
				
			||||||
    <p>{@html $t("main.copyright", { year: new Date().getFullYear(), website: "https://leomurca.xyz" })}</p>
 | 
					 | 
				
			||||||
    <p>{@html $t("main.version", { version: appVersion() })}</p>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</footer>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style>
 | 
					 | 
				
			||||||
  footer {
 | 
					 | 
				
			||||||
    display: flex;
 | 
					 | 
				
			||||||
    align-items: center;
 | 
					 | 
				
			||||||
    justify-content: center;
 | 
					 | 
				
			||||||
    padding: 20px;
 | 
					 | 
				
			||||||
    background-color: #f8f9fa;
 | 
					 | 
				
			||||||
    border-top: 1px solid #ddd;
 | 
					 | 
				
			||||||
    width: 100%;
 | 
					 | 
				
			||||||
    bottom: 0;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  .footer-content {
 | 
					 | 
				
			||||||
    text-align: center;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  footer p {
 | 
					 | 
				
			||||||
    margin: 5px 0;
 | 
					 | 
				
			||||||
    font-size: 14px;
 | 
					 | 
				
			||||||
    color: #333;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  footer p:first-child {
 | 
					 | 
				
			||||||
    font-weight: bold;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @media (max-width: 768px) {
 | 
					 | 
				
			||||||
    footer {
 | 
					 | 
				
			||||||
      padding: 15px;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    footer p {
 | 
					 | 
				
			||||||
      font-size: 12px;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,195 +0,0 @@
 | 
				
			||||||
<script>
 | 
					 | 
				
			||||||
  import MediaQuery from "../MediaQuery.svelte";
 | 
					 | 
				
			||||||
  import logo from "../../assets/logo.webp";
 | 
					 | 
				
			||||||
  import { t, locale, locales } from "../../i18n"
 | 
					 | 
				
			||||||
  import { path } from '../../utils/stores.js';
 | 
					 | 
				
			||||||
  import { routes } from '../../utils/routes.js';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const configsFor = (matches) => {
 | 
					 | 
				
			||||||
    return matches
 | 
					 | 
				
			||||||
      ? { src: logo, width: 150, height: 70} // mobile
 | 
					 | 
				
			||||||
      : { src: logo, width: 150, height: 100 }; // desktop
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const onSwitchToOppositeLang = () => {
 | 
					 | 
				
			||||||
    const oppositeLang = locales.find(item => item[0] !== $locale);
 | 
					 | 
				
			||||||
    locale.set(oppositeLang[0]);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const onNavigateTo = (e, route) => {
 | 
					 | 
				
			||||||
    e.preventDefault()
 | 
					 | 
				
			||||||
    history.pushState({}, '', route);
 | 
					 | 
				
			||||||
    path.set(route);
 | 
					 | 
				
			||||||
    if (isMenuOpen) {
 | 
					 | 
				
			||||||
      isMenuOpen = false
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  let isMenuOpen = false;
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<header>
 | 
					 | 
				
			||||||
  <div class="logo">
 | 
					 | 
				
			||||||
    <MediaQuery query="(max-width: 768px)" let:matches>
 | 
					 | 
				
			||||||
      {@const configs = configsFor(matches)}
 | 
					 | 
				
			||||||
          <a href="#" on:click={(e) => onNavigateTo(e, "/")}>
 | 
					 | 
				
			||||||
           <img src={configs.src} alt="Embroidery viewer logo" width={configs.width} height={configs.height}/>
 | 
					 | 
				
			||||||
          </a>
 | 
					 | 
				
			||||||
    </MediaQuery>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<div class="nav-container">
 | 
					 | 
				
			||||||
  <MediaQuery query="(max-width: 768px)" let:matches >
 | 
					 | 
				
			||||||
    <slot let-matches>
 | 
					 | 
				
			||||||
      {#if matches}
 | 
					 | 
				
			||||||
        <button class="hamburger" on:click={() => (isMenuOpen = !isMenuOpen)}>
 | 
					 | 
				
			||||||
          {#if isMenuOpen}x{:else}☰{/if}
 | 
					 | 
				
			||||||
        </button>
 | 
					 | 
				
			||||||
      {/if}
 | 
					 | 
				
			||||||
    </slot>
 | 
					 | 
				
			||||||
  </MediaQuery>
 | 
					 | 
				
			||||||
  <nav class:is-open={isMenuOpen}>
 | 
					 | 
				
			||||||
    <ul>
 | 
					 | 
				
			||||||
      {#each Object.entries(routes) as [route, config]}
 | 
					 | 
				
			||||||
      <li><a href="#" on:click={(e) => onNavigateTo(e, route)} >{$t(config.nameKey)}</a></li>
 | 
					 | 
				
			||||||
      {/each}
 | 
					 | 
				
			||||||
    </ul>
 | 
					 | 
				
			||||||
  </nav>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  <a class="common-switch {$locale === 'en' ? 'portuguese-switch' : 'english-switch' }" href="#" on:click|preventDefault={onSwitchToOppositeLang}>
 | 
					 | 
				
			||||||
    <div style="display: flex; width: fit-content;">
 | 
					 | 
				
			||||||
      <span style="font-size: 20px;">{$t("main.languageSwitch")}</span>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  </a>
 | 
					 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
</header>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  <style>
 | 
					 | 
				
			||||||
  header {
 | 
					 | 
				
			||||||
    display: flex;
 | 
					 | 
				
			||||||
    align-items: center;
 | 
					 | 
				
			||||||
    justify-content: space-between;
 | 
					 | 
				
			||||||
    padding: 10px 100px;
 | 
					 | 
				
			||||||
    background-color: #f8f9fa;
 | 
					 | 
				
			||||||
    border-bottom: 1px solid #ddd;
 | 
					 | 
				
			||||||
    width: 100%;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  .logo img {
 | 
					 | 
				
			||||||
    height: auto;
 | 
					 | 
				
			||||||
    max-height: 60px;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  .logo a {
 | 
					 | 
				
			||||||
    border-bottom: none;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  .logo a:hover {
 | 
					 | 
				
			||||||
    background: transparent;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  .nav-container {
 | 
					 | 
				
			||||||
    display: flex;
 | 
					 | 
				
			||||||
    gap: 20px;
 | 
					 | 
				
			||||||
    align-items: center;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  nav ul {
 | 
					 | 
				
			||||||
    list-style: none;
 | 
					 | 
				
			||||||
    margin: 0;
 | 
					 | 
				
			||||||
    padding: 0;
 | 
					 | 
				
			||||||
    display: flex;
 | 
					 | 
				
			||||||
    gap: 20px;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  nav ul li {
 | 
					 | 
				
			||||||
    display: flex;
 | 
					 | 
				
			||||||
    font-weight: bold;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  .hamburger {
 | 
					 | 
				
			||||||
    background: none;
 | 
					 | 
				
			||||||
    border: none;
 | 
					 | 
				
			||||||
    font-size: 35px;
 | 
					 | 
				
			||||||
    width: 35px;
 | 
					 | 
				
			||||||
    padding: 0;
 | 
					 | 
				
			||||||
    margin: 0;
 | 
					 | 
				
			||||||
    cursor: pointer;
 | 
					 | 
				
			||||||
    display: none;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  .common-switch {
 | 
					 | 
				
			||||||
    width: fit-content;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  .portuguese-switch {
 | 
					 | 
				
			||||||
    color: #0C8F27;
 | 
					 | 
				
			||||||
    border-bottom: 3px solid #0C8F27 !important;
 | 
					 | 
				
			||||||
    fill: #0C8F27 !important;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  .portuguese-switch:hover {
 | 
					 | 
				
			||||||
    background: #0C8F27;
 | 
					 | 
				
			||||||
    color: #ffffff;
 | 
					 | 
				
			||||||
    fill: #ffffff !important;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  .english-switch{
 | 
					 | 
				
			||||||
    color: #BE0A2F;
 | 
					 | 
				
			||||||
    border-bottom: 3px solid #BE0A2F;
 | 
					 | 
				
			||||||
    width: fit-content;
 | 
					 | 
				
			||||||
    fill: #BE0A2F !important;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  .english-switch:hover {
 | 
					 | 
				
			||||||
    background: #BE0A2F;
 | 
					 | 
				
			||||||
    color: #ffffff;
 | 
					 | 
				
			||||||
    fill: #ffffff !important;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @media (max-width: 768px) {
 | 
					 | 
				
			||||||
    header {
 | 
					 | 
				
			||||||
      padding: 10px 20px ;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    .hamburger {
 | 
					 | 
				
			||||||
      display: block;
 | 
					 | 
				
			||||||
      width: 35px;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    nav {
 | 
					 | 
				
			||||||
      display: none;
 | 
					 | 
				
			||||||
      flex-direction: column;
 | 
					 | 
				
			||||||
      gap: 10px;
 | 
					 | 
				
			||||||
      background-color: #f8f9fa;
 | 
					 | 
				
			||||||
      position: absolute;
 | 
					 | 
				
			||||||
      top: 60px;
 | 
					 | 
				
			||||||
      right: 0;
 | 
					 | 
				
			||||||
      border: 1px solid #ddd;
 | 
					 | 
				
			||||||
      box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    nav.is-open {
 | 
					 | 
				
			||||||
      display: inline-block;
 | 
					 | 
				
			||||||
      margin-top: 25px;
 | 
					 | 
				
			||||||
      width: 100%;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    nav ul {
 | 
					 | 
				
			||||||
      flex-direction: column;
 | 
					 | 
				
			||||||
      gap: 0px;
 | 
					 | 
				
			||||||
      width: 100%;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    nav ul li {
 | 
					 | 
				
			||||||
      text-align: center;
 | 
					 | 
				
			||||||
      width: 100%;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    nav ul li a {
 | 
					 | 
				
			||||||
      display: inline-block;
 | 
					 | 
				
			||||||
      width: 100%;
 | 
					 | 
				
			||||||
      padding: 10px;
 | 
					 | 
				
			||||||
      border-bottom: none;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  </style>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,14 +0,0 @@
 | 
				
			||||||
<script>
 | 
					 | 
				
			||||||
    import Router from "../components/Router.svelte";
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
<main>
 | 
					 | 
				
			||||||
    <Router />
 | 
					 | 
				
			||||||
</main>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style>
 | 
					 | 
				
			||||||
    main {
 | 
					 | 
				
			||||||
        flex: 1; /* This pushes footer to bottom */
 | 
					 | 
				
			||||||
        padding: 20px;
 | 
					 | 
				
			||||||
        min-height: 85vh;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,21 +0,0 @@
 | 
				
			||||||
import Home from '../lib/pages/Home.svelte';
 | 
					 | 
				
			||||||
import Donate from '../lib/pages/Donate.svelte';
 | 
					 | 
				
			||||||
import Viewer from '../lib/pages/Viewer.svelte';
 | 
					 | 
				
			||||||
import NotFound from '../lib/pages/NotFound.svelte';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const routes = {
 | 
					 | 
				
			||||||
  '/': {
 | 
					 | 
				
			||||||
    component: Home,
 | 
					 | 
				
			||||||
    nameKey: "nav.home"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  '/viewer': {
 | 
					 | 
				
			||||||
    component: Viewer,
 | 
					 | 
				
			||||||
    nameKey: "nav.viewer"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  '/donate': {
 | 
					 | 
				
			||||||
    component: Donate,
 | 
					 | 
				
			||||||
    nameKey: "nav.donate"
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const fallback = NotFound;
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,3 +0,0 @@
 | 
				
			||||||
import { writable } from 'svelte/store';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const path = writable(window.location.pathname);
 | 
					 | 
				
			||||||