Version 3.0.0 Redesign #34
|
|
@ -10,10 +10,9 @@ module.exports = {
|
||||||
watch: false,
|
watch: false,
|
||||||
max_memory_restart: '1G',
|
max_memory_restart: '1G',
|
||||||
env: {
|
env: {
|
||||||
EMAIL_ACCESS_KEY: process.env.EMAIL_ACCESS_KEY,
|
|
||||||
EMAIL_BASE_URL: process.env.EMAIL_BASE_URL,
|
|
||||||
NODE_ENV: 'production',
|
|
||||||
PORT: 7281,
|
PORT: 7281,
|
||||||
|
PUBLIC_APP_ENV: 'production',
|
||||||
|
NODE_ENV: 'production',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
@ -30,9 +29,8 @@ module.exports = {
|
||||||
'post-deploy':
|
'post-deploy':
|
||||||
'pm2 startOrReload ecosystem.config.cjs --only embroidery-viewer-prod --env production && pm2 save',
|
'pm2 startOrReload ecosystem.config.cjs --only embroidery-viewer-prod --env production && pm2 save',
|
||||||
env: {
|
env: {
|
||||||
EMAIL_ACCESS_KEY: process.env.EMAIL_ACCESS_KEY,
|
|
||||||
EMAIL_BASE_URL: process.env.EMAIL_BASE_URL,
|
|
||||||
PORT: 7281,
|
PORT: 7281,
|
||||||
|
PUBLIC_APP_ENV: 'production',
|
||||||
NODE_ENV: 'production',
|
NODE_ENV: 'production',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
2306
package-lock.json
generated
31
package.json
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "embroidery-viewer",
|
"name": "embroidery-viewer",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2.1.4",
|
"version": "3.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
|
|
@ -14,23 +14,24 @@
|
||||||
"lint": "prettier --check . && eslint ."
|
"lint": "prettier --check . && eslint ."
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/compat": "^1.2.5",
|
"@eslint/compat": "^2.0.5",
|
||||||
"@eslint/js": "^9.18.0",
|
"@eslint/js": "^10.0.1",
|
||||||
"@sveltejs/kit": "^2.16.0",
|
"@sveltejs/kit": "^2.57.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
||||||
"eslint": "^9.28.0",
|
"@types/node": "^25.6.0",
|
||||||
|
"eslint": "^10.2.0",
|
||||||
"eslint-config-prettier": "^10.0.1",
|
"eslint-config-prettier": "^10.0.1",
|
||||||
"eslint-plugin-svelte": "^3.9.1",
|
"eslint-plugin-svelte": "^3.17.0",
|
||||||
"globals": "^16.0.0",
|
"globals": "^17.5.0",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.8.3",
|
||||||
"prettier-plugin-svelte": "^3.3.3",
|
"prettier-plugin-svelte": "^3.5.1",
|
||||||
"svelte": "^5.0.0",
|
"svelte": "^5.55.4",
|
||||||
"svelte-check": "^4.0.0",
|
"svelte-check": "^4.4.6",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "^6.0.2",
|
||||||
"vite": "^6.2.6"
|
"vite": "^8.0.8"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sveltejs/adapter-node": "^5.2.12",
|
"@sveltejs/adapter-node": "^5.5.4",
|
||||||
"accept-language-parser": "^1.5.0",
|
"accept-language-parser": "^1.5.0",
|
||||||
"sveltekit-i18n": "^2.4.2"
|
"sveltekit-i18n": "^2.4.2"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
98
src/app.html
|
|
@ -1,85 +1,29 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<style>
|
<script
|
||||||
:root {
|
data-name="BMC-Widget"
|
||||||
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
data-cfasync="false"
|
||||||
font-size: 16px;
|
src="https://cdnjs.buymeacoffee.com/1.0.0/widget.prod.min.js"
|
||||||
line-height: 24px;
|
data-id="embroideryviewerxyz"
|
||||||
font-weight: 400;
|
data-description="Support me on Buy me a coffee!"
|
||||||
font-synthesis: none;
|
data-message="Enjoying Embroidery Viewer?
|
||||||
text-rendering: optimizeLegibility;
|
Buy me a coffee and help keep it running ☕"
|
||||||
-webkit-font-smoothing: antialiased;
|
data-color="#FFDD03"
|
||||||
-moz-osx-font-smoothing: grayscale;
|
data-position="Right"
|
||||||
-webkit-text-size-adjust: 100%;
|
data-x_margin="18"
|
||||||
}
|
data-y_margin="18"
|
||||||
|
></script>
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
flex-direction: column;
|
|
||||||
margin: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#app {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
background-color: #f2f6f5;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type='submit'] {
|
|
||||||
width: 100%;
|
|
||||||
font-size: 20px;
|
|
||||||
margin-top: 20px;
|
|
||||||
background-color: #05345f;
|
|
||||||
font-weight: 700;
|
|
||||||
color: white;
|
|
||||||
padding: 10px;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type='submit']:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: black;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
body a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: #06345f;
|
|
||||||
border-bottom: 3px solid #06345f;
|
|
||||||
}
|
|
||||||
|
|
||||||
body a:hover {
|
|
||||||
background-color: #06345f;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
:is(h1, h2, h3, h4, h5, h6) {
|
|
||||||
color: #06345f;
|
|
||||||
}
|
|
||||||
|
|
||||||
strong {
|
|
||||||
color: #06345f;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul li::marker {
|
|
||||||
color: #06345f;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Caveat:wght@400..700&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
|
||||||
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
|
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 84 KiB |
BIN
src/lib/assets/logo-white.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
src/lib/assets/logo.png
Normal file
|
After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 155 KiB |
|
Before Width: | Height: | Size: 130 KiB |
|
|
@ -52,7 +52,7 @@
|
||||||
|
|
||||||
{#if files.length !== 0}
|
{#if files.length !== 0}
|
||||||
<div id="container" style="width: 100%; heigth: 100vh;">
|
<div id="container" style="width: 100%; heigth: 100vh;">
|
||||||
{#each Array.from(files) as file, i}
|
{#each Array.from(files) as file, i (i)}
|
||||||
<div class="canvas-container">
|
<div class="canvas-container">
|
||||||
<canvas bind:this={canvasRefs[i]} class="canvas"></canvas>
|
<canvas bind:this={canvasRefs[i]} class="canvas"></canvas>
|
||||||
<p><strong>{file.name}</strong></p>
|
<p><strong>{file.name}</strong></p>
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||||
|
<!-- eslint-disable svelte/no-at-html-tags -->
|
||||||
<div
|
<div
|
||||||
id="dropzone"
|
id="dropzone"
|
||||||
tabindex={0}
|
tabindex={0}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
<div id="selected-files-container">
|
<div id="selected-files-container">
|
||||||
<h2>{title}:</h2>
|
<h2>{title}:</h2>
|
||||||
<div id="files-list">
|
<div id="files-list">
|
||||||
{#each Array.from(files) as file}
|
{#each Array.from(files) as file, i (i)}
|
||||||
<div id={isError ? 'selected-file-card-error' : 'selected-file-card'}>
|
<div id={isError ? 'selected-file-card-error' : 'selected-file-card'}>
|
||||||
<span>{file.name}</span>
|
<span>{file.name}</span>
|
||||||
<span>{Math.round(file.size / 1000)} KB</span>
|
<span>{Math.round(file.size / 1000)} KB</span>
|
||||||
|
|
|
||||||
|
|
@ -1,96 +1,185 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { appVersion } from '$lib/utils/env';
|
import { appVersion } from '$lib/utils/env';
|
||||||
|
|
||||||
|
import MailIcon from '$lib/components/icons/MailIcon.svelte';
|
||||||
|
import ArrowTopIcon from './icons/ArrowTopIcon.svelte';
|
||||||
|
import logo from '$lib/assets/logo-white.webp';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<div class="footer-content">
|
<div id="content-container">
|
||||||
<div class="footer-info">
|
<section class="footer-block">
|
||||||
<p>
|
<img
|
||||||
{@html $t(
|
src={logo}
|
||||||
'footer.copyright',
|
style="height: 80px; width: auto; margin-lft: -5px;"
|
||||||
/** @type {any} */ ({
|
alt="Logotipo da Embroidery Viewer."
|
||||||
year: new Date().getFullYear(),
|
/>
|
||||||
website: 'https://leomurca.xyz',
|
<p>{$t('footer.slogan')}</p>
|
||||||
}),
|
</section>
|
||||||
)}
|
<section class="footer-block" aria-labelledby="contact-title">
|
||||||
</p>
|
<h1 id="contact-title">{$t('footer.contact-title')}</h1>
|
||||||
<p>
|
<p>{$t('footer.contact-description')}</p>
|
||||||
{@html $t(
|
|
||||||
'footer.version',
|
|
||||||
/** @type {any} */ ({ version: appVersion() }),
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<nav class="footer-nav">
|
<address class="contact-container">
|
||||||
<a href="/about">{$t('footer.about')}</a>
|
<div class="contact-item">
|
||||||
<a href="/privacy-policy">{$t('footer.privacy.policy')}</a>
|
<MailIcon size={30} />
|
||||||
<a href="/terms-of-service">{$t('footer.terms.of.service')}</a>
|
|
||||||
</nav>
|
<a href="mailto:leo@leomurca.xyz}" aria-label="leo@leomurca.xyz"
|
||||||
|
>leo@leomurca.xyz</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</address>
|
||||||
|
</section>
|
||||||
|
<section class="footer-block">
|
||||||
|
<h1>{$t('footer.resources')}</h1>
|
||||||
|
<nav class="social-container" aria-label="Social media">
|
||||||
|
<a href={resolve('/about')}>{$t('footer.about')}</a>
|
||||||
|
<a href={resolve('/privacy-policy')}>{$t('footer.privacy.policy')}</a>
|
||||||
|
<a href={resolve('/terms-of-service')}
|
||||||
|
>{$t('footer.terms.of.service')}</a
|
||||||
|
>
|
||||||
|
</nav>
|
||||||
|
<button
|
||||||
|
class="back-to-top-button"
|
||||||
|
aria-label={$t('footer.back-to-top.aria-label')}
|
||||||
|
onclick={() => scrollTo({ top: 0, behavior: 'smooth' })}
|
||||||
|
>
|
||||||
|
<ArrowTopIcon size={30} /> {$t('footer.back-to-top.label')}</button
|
||||||
|
>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
<section class="credits-container">
|
||||||
|
Copyright {new Date().getFullYear()}
|
||||||
|
|
||||||
|
<a href="https://leomurca.xyz" target="_blank" rel="noreferrer"
|
||||||
|
>Leonardo Murça</a
|
||||||
|
>
|
||||||
|
|
|
||||||
|
{$t('footer.version')}:
|
||||||
|
<span style="font-family: var(--font-bold);">{appVersion()}</span>
|
||||||
|
</section>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
footer {
|
footer {
|
||||||
background-color: #f8f9fa;
|
background-color: var(--color-primary);
|
||||||
border-top: 1px solid #ddd;
|
|
||||||
padding: 20px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-content {
|
#content-container {
|
||||||
|
width: 85%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
max-width: 960px;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
padding: 60px 0 60px 30px;
|
||||||
|
scroll-margin-top: 100px;
|
||||||
|
color: white;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-block {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-weight: 700;
|
||||||
|
color: white;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
letter-spacing: 0.1rem;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-container a {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-item:hover {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-container a {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-container a:hover {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-to-top-button {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
background-color: transparent;
|
||||||
|
border-radius: 20px;
|
||||||
|
border: none;
|
||||||
|
padding: 13px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: white;
|
||||||
|
font-size: 1rem;
|
||||||
|
width: 40%;
|
||||||
|
margin-top: 30px;
|
||||||
|
border: 1px solid white;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-info {
|
.back-to-top-button:hover {
|
||||||
flex: 1 1 100%;
|
background-color: #ffffff;
|
||||||
margin-bottom: 10px;
|
color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-info p {
|
.credits-container {
|
||||||
margin: 4px 0;
|
background-color: var(--color-secondary);
|
||||||
font-size: 14px;
|
color: white;
|
||||||
color: #333;
|
margin: 0 auto;
|
||||||
|
padding: 20px 30px;
|
||||||
|
padding-left: 9%;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-info p:first-child {
|
.credits-container a {
|
||||||
font-weight: bold;
|
color: white;
|
||||||
|
border-bottom: 1px solid white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-nav {
|
.credits-container a:hover {
|
||||||
flex: 1 1 100%;
|
background-color: white;
|
||||||
|
color: var(--color-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-nav a {
|
/* Responsive */
|
||||||
margin: 0 10px;
|
@media (max-width: 768px) {
|
||||||
font-size: 14px;
|
#content-container {
|
||||||
}
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
@media (min-width: 600px) {
|
padding: 60px 30px;
|
||||||
.footer-content {
|
flex-direction: column;
|
||||||
flex-wrap: nowrap;
|
|
||||||
text-align: left;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-info,
|
.back-to-top-button {
|
||||||
.footer-nav {
|
width: 100%;
|
||||||
flex: 1 1 50%;
|
justify-content: center;
|
||||||
margin-bottom: 0;
|
gap: 5px;
|
||||||
}
|
|
||||||
|
|
||||||
.footer-info {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-nav {
|
|
||||||
text-align: right;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,109 +1,64 @@
|
||||||
<script>
|
<script>
|
||||||
import { t, locale, SUPPORTED_LOCALES } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
|
import { page } from '$app/state';
|
||||||
import logo from '$lib/assets/logo.webp';
|
import logo from '$lib/assets/logo.webp';
|
||||||
|
import LanguageSwitch from './LanguageSwitch.svelte';
|
||||||
import MediaQuery from './MediaQuery.svelte';
|
import MediaQuery from './MediaQuery.svelte';
|
||||||
|
|
||||||
/**
|
let isOpen = $state(false);
|
||||||
*
|
let route = $derived(page.url.pathname);
|
||||||
* Returns logo image configuration based on the screen size match.
|
$effect(() => {
|
||||||
*
|
route; // track dependency
|
||||||
* @param {boolean} matches - The event that triggered the language switch.
|
isOpen = false;
|
||||||
*/
|
});
|
||||||
const configsFor = (matches) => {
|
|
||||||
return matches
|
|
||||||
? { src: logo, width: 150, height: 70 } // mobile
|
|
||||||
: { src: logo, width: 150, height: 100 }; // desktop
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Switches the current locale to the opposite language (EN_US <-> PT_BR).
|
|
||||||
* Prevents the default link behavior (e.g., page jump).
|
|
||||||
*
|
|
||||||
* @param {MouseEvent | KeyboardEvent} e - The event that triggered the language switch.
|
|
||||||
*/
|
|
||||||
const onSwitchToOppositeLang = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
$locale =
|
|
||||||
$locale === SUPPORTED_LOCALES.EN_US
|
|
||||||
? SUPPORTED_LOCALES.PT_BR
|
|
||||||
: SUPPORTED_LOCALES.EN_US;
|
|
||||||
};
|
|
||||||
|
|
||||||
let isMenuOpen = false;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
<MediaQuery query="(max-width: 768px)" let:matches>
|
<MediaQuery query="(max-width: 768px)" let:matches>
|
||||||
{@const configs = configsFor(matches)}
|
<a href={resolve('/')}>
|
||||||
<a href="/">
|
|
||||||
<img
|
<img
|
||||||
src={configs.src}
|
src={logo}
|
||||||
alt="Embroidery viewer logo"
|
alt="Embroidery viewer logo"
|
||||||
width={configs.width}
|
width="150"
|
||||||
height={configs.height}
|
height={matches ? 70 : 100}
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</MediaQuery>
|
</MediaQuery>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="nav-container">
|
<nav class:active={route !== '/'} id="menuToggle">
|
||||||
<MediaQuery query="(max-width: 768px)" let:matches>
|
<input type="checkbox" bind:checked={isOpen} />
|
||||||
<slot let-matches>
|
<span></span>
|
||||||
{#if matches}
|
<span></span>
|
||||||
<button class="hamburger" onclick={() => (isMenuOpen = !isMenuOpen)}>
|
<span></span>
|
||||||
{#if isMenuOpen}x{:else}☰{/if}
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
</slot>
|
|
||||||
</MediaQuery>
|
|
||||||
<nav class:is-open={isMenuOpen}>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="/">{$t('header.homeNav')}</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/viewer">{$t('header.viewerNav')}</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/about">{$t('header.aboutNav')}</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/donate">{$t('header.donateNav')}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<button
|
<ul id="menu">
|
||||||
type="button"
|
<li><a href={resolve('/about')}>{$t('header.aboutNav')}</a></li>
|
||||||
class="common-switch {$locale === SUPPORTED_LOCALES.EN_US
|
<li><a href={resolve('/viewer')}>{$t('header.viewerNav')}</a></li>
|
||||||
? 'portuguese-switch'
|
<li><a href={resolve('/support-us')}>{$t('header.supportUsNav')}</a></li>
|
||||||
: 'english-switch'}"
|
<li style="font-size: 22px; padding: 10px 0;"><LanguageSwitch /></li>
|
||||||
onclick={onSwitchToOppositeLang}
|
</ul>
|
||||||
onkeydown={(e) => {
|
</nav>
|
||||||
if (e.key === 'Enter' || e.key === ' ') {
|
|
||||||
onSwitchToOppositeLang(e);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div style="display: flex; width: fit-content;">
|
|
||||||
<span style="font-size: 20px;">{$t('header.languageSwitch')}</span>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
header {
|
header {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
z-index: 2;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 10px 100px;
|
padding: 10px 30px 10px 100px;
|
||||||
background-color: #f8f9fa;
|
|
||||||
border-bottom: 1px solid #ddd;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
.logo img {
|
.logo img {
|
||||||
height: auto;
|
height: auto;
|
||||||
max-height: 60px;
|
max-height: 60px;
|
||||||
|
|
@ -116,112 +71,160 @@
|
||||||
.logo a:hover {
|
.logo a:hover {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
#menuToggle {
|
||||||
.nav-container {
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 20px;
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Click area */
|
||||||
|
#menuToggle input {
|
||||||
|
display: block;
|
||||||
|
width: 40px;
|
||||||
|
height: 32px;
|
||||||
|
position: absolute;
|
||||||
|
top: -7px;
|
||||||
|
left: -5px;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bars */
|
||||||
|
#menuToggle span {
|
||||||
|
display: block;
|
||||||
|
width: 30px;
|
||||||
|
height: 3px;
|
||||||
|
margin-bottom: 7px;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 2px;
|
||||||
|
transition: all 0.35s ease;
|
||||||
|
transform-origin: 4px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjust origins for rotation */
|
||||||
|
#menuToggle span:nth-child(2) {
|
||||||
|
transform-origin: 0% 0%;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menuToggle span:nth-child(4) {
|
||||||
|
transform-origin: 0% 100%;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === ANIMATION === */
|
||||||
|
|
||||||
|
/* Top bar → rotates */
|
||||||
|
#menuToggle input:checked ~ span:nth-child(2) {
|
||||||
|
transform: rotate(45deg) translate(-1px, -1px);
|
||||||
|
width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Middle bar → fades out */
|
||||||
|
#menuToggle input:checked ~ span:nth-child(3) {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bottom bar → rotates opposite */
|
||||||
|
#menuToggle input:checked ~ span:nth-child(4) {
|
||||||
|
transform: rotate(-45deg) translate(1px, -1px);
|
||||||
|
width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
position: absolute;
|
||||||
|
top: -80px;
|
||||||
|
width: 400px;
|
||||||
|
margin: -100px 0 0 0;
|
||||||
|
padding: 50px;
|
||||||
|
padding-top: 125px;
|
||||||
|
right: -100px;
|
||||||
|
background: var(--color-primary);
|
||||||
|
list-style-type: none;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
transform-origin: 0% 0%;
|
||||||
|
transform: translate(100%, 0);
|
||||||
|
transition: transform 0.5s cubic-bezier(0.77, 0.2, 0.05, 1);
|
||||||
|
z-index: -1;
|
||||||
|
|
||||||
|
margin: 30px;
|
||||||
|
border-radius: 70% 30% 30% 70% / 60% 40% 0 100%;
|
||||||
|
box-shadow:
|
||||||
|
0 2.8px 2.2px rgba(0, 0, 0, 0.02),
|
||||||
|
0 6.7px 5.3px rgba(0, 0, 0, 0.028),
|
||||||
|
0 12.5px 10px rgba(0, 0, 0, 0.035),
|
||||||
|
0 22.3px 17.9px rgba(0, 0, 0, 0.042),
|
||||||
|
0 41.8px 33.4px rgba(0, 0, 0, 0.05),
|
||||||
|
0 100px 80px rgba(0, 0, 0, 0.07);
|
||||||
}
|
}
|
||||||
|
|
||||||
nav ul {
|
#menu li a {
|
||||||
list-style: none;
|
padding: 10px 0;
|
||||||
margin: 0;
|
font-size: 22px;
|
||||||
padding: 0;
|
color: white;
|
||||||
display: flex;
|
display: block;
|
||||||
gap: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nav ul li {
|
#menu li a:hover {
|
||||||
display: flex;
|
color: #adadad;
|
||||||
font-weight: bold;
|
transition: all 0.5s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hamburger {
|
#menuToggle input:checked ~ ul {
|
||||||
background: none;
|
transform: scale(1, 1);
|
||||||
border: none;
|
opacity: 1;
|
||||||
font-size: 35px;
|
|
||||||
width: 35px;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.common-switch {
|
.active input:checked ~ span:nth-child(2),
|
||||||
border: none;
|
.active input:checked ~ span:nth-child(3),
|
||||||
background-color: unset;
|
.active input:checked ~ span:nth-child(4) {
|
||||||
width: fit-content;
|
background-color: white !important;
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.portuguese-switch {
|
.active span {
|
||||||
color: #0c8f27;
|
background-color: var(--color-primary) !important;
|
||||||
border-bottom: 3px solid #0c8f27 !important;
|
|
||||||
fill: #0c8f27 !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.portuguese-switch:hover {
|
@media (max-width: 1458px) {
|
||||||
background: #0c8f27;
|
#menuToggle span {
|
||||||
color: #ffffff;
|
background-color: var(--color-primary);
|
||||||
fill: #ffffff !important;
|
}
|
||||||
}
|
/* Top bar → rotates */
|
||||||
|
#menuToggle input:checked ~ span:nth-child(2) {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
.english-switch {
|
/* Middle bar → fades out */
|
||||||
color: #be0a2f;
|
#menuToggle input:checked ~ span:nth-child(3) {
|
||||||
border-bottom: 3px solid #be0a2f;
|
background-color: white;
|
||||||
width: fit-content;
|
}
|
||||||
fill: #be0a2f !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.english-switch:hover {
|
/* Bottom bar → rotates opposite */
|
||||||
background: #be0a2f;
|
#menuToggle input:checked ~ span:nth-child(4) {
|
||||||
color: #ffffff;
|
background-color: white;
|
||||||
fill: #ffffff !important;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
header {
|
header {
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
}
|
}
|
||||||
.hamburger {
|
|
||||||
display: block;
|
|
||||||
width: 35px;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav {
|
#menu {
|
||||||
display: none;
|
width: 100vw;
|
||||||
flex-direction: column;
|
top: -80px;
|
||||||
gap: 10px;
|
margin: 0px 0 0 0;
|
||||||
background-color: #f8f9fa;
|
right: -20px;
|
||||||
position: absolute;
|
border-radius: 0;
|
||||||
top: 60px;
|
border-radius: 0% 0% 80% 80%;
|
||||||
right: 0;
|
transform: translate(0%, -100%);
|
||||||
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>
|
</style>
|
||||||
|
|
|
||||||
145
src/lib/components/LanguageSwitch.svelte
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
<script>
|
||||||
|
import { locale, SUPPORTED_LOCALES } from '$lib/translations';
|
||||||
|
|
||||||
|
const isEnglish = $derived($locale === SUPPORTED_LOCALES.EN_US);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switches the current locale to the opposite language (EN_US <-> PT_BR).
|
||||||
|
* Prevents the default link behavior (e.g., page jump).
|
||||||
|
*/
|
||||||
|
const onSwitchToOppositeLang = () => {
|
||||||
|
$locale =
|
||||||
|
$locale === SUPPORTED_LOCALES.EN_US
|
||||||
|
? SUPPORTED_LOCALES.PT_BR
|
||||||
|
: SUPPORTED_LOCALES.EN_US;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<center>
|
||||||
|
<div class="switch">
|
||||||
|
<input
|
||||||
|
id="language-toggle"
|
||||||
|
class="check-toggle check-toggle-round-flat"
|
||||||
|
type="checkbox"
|
||||||
|
onclick={onSwitchToOppositeLang}
|
||||||
|
checked={isEnglish}
|
||||||
|
onkeydown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
onSwitchToOppositeLang();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label for="language-toggle"></label>
|
||||||
|
<span class="off">EN</span>
|
||||||
|
<span class="on">PT</span>
|
||||||
|
</div>
|
||||||
|
</center>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.switch {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 700;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch > span {
|
||||||
|
position: absolute;
|
||||||
|
top: 7px;
|
||||||
|
pointer-events: none;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 12px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
|
||||||
|
width: 50%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.check-toggle-round-flat:checked ~ .off {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.check-toggle-round-flat:checked ~ .on {
|
||||||
|
color: var(--color-primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch > span.on {
|
||||||
|
left: 0;
|
||||||
|
padding-left: 2px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch > span.off {
|
||||||
|
right: 0;
|
||||||
|
padding-right: 4px;
|
||||||
|
color: var(--color-primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-toggle {
|
||||||
|
position: absolute;
|
||||||
|
margin-left: -9999px;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
.check-toggle + label {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.check-toggle-round-flat + label {
|
||||||
|
padding: 2px;
|
||||||
|
width: 97px;
|
||||||
|
height: 35px;
|
||||||
|
background-color: white;
|
||||||
|
-webkit-border-radius: 60px;
|
||||||
|
-moz-border-radius: 60px;
|
||||||
|
-ms-border-radius: 60px;
|
||||||
|
-o-border-radius: 60px;
|
||||||
|
border-radius: 60px;
|
||||||
|
}
|
||||||
|
input.check-toggle-round-flat + label:before,
|
||||||
|
input.check-toggle-round-flat + label:after {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
input.check-toggle-round-flat + label:before {
|
||||||
|
top: 2px;
|
||||||
|
left: 2px;
|
||||||
|
bottom: 2px;
|
||||||
|
right: 2px;
|
||||||
|
background-color: white;
|
||||||
|
|
||||||
|
-moz-border-radius: 60px;
|
||||||
|
-ms-border-radius: 60px;
|
||||||
|
-o-border-radius: 60px;
|
||||||
|
border-radius: 60px;
|
||||||
|
}
|
||||||
|
input.check-toggle-round-flat + label:after {
|
||||||
|
top: 4px;
|
||||||
|
left: 4px;
|
||||||
|
bottom: 4px;
|
||||||
|
width: 48px;
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
-webkit-border-radius: 52px;
|
||||||
|
-moz-border-radius: 52px;
|
||||||
|
-ms-border-radius: 52px;
|
||||||
|
-o-border-radius: 52px;
|
||||||
|
border-radius: 52px;
|
||||||
|
-webkit-transition: margin 0.2s;
|
||||||
|
-moz-transition: margin 0.2s;
|
||||||
|
-o-transition: margin 0.2s;
|
||||||
|
transition: margin 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.check-toggle-round-flat:checked + label:after {
|
||||||
|
margin-left: 42px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
14
src/lib/components/icons/ArrowTopIcon.svelte
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
<script>
|
||||||
|
export let size = 20;
|
||||||
|
export let color = 'currentColor';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
height={size}
|
||||||
|
viewBox="0 -960 960 960"
|
||||||
|
width={size}
|
||||||
|
fill={color}
|
||||||
|
>
|
||||||
|
<path d="M480-528 296-344l-56-56 240-240 240 240-56 56-184-184Z" />
|
||||||
|
</svg>
|
||||||
21
src/lib/components/icons/BoltIcon.svelte
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script>
|
||||||
|
export let size = 20;
|
||||||
|
export let color = 'currentColor';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
fill={color}
|
||||||
|
aria-hidden="true"
|
||||||
|
id="Bolt--Streamline-Heroicons"
|
||||||
|
height={size}
|
||||||
|
width={size}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M9.743333333333332 1.0633333333333332a0.5 0.5 0 0 1 0.23933333333333331 0.568L8.654666666666666 6.5h4.845333333333333a0.5 0.5 0 0 1 0.36533333333333334 0.8413333333333333l-7 7.5a0.5 0.5 0 0 1 -0.848 -0.4733333333333333l1.3279999999999998 -4.867999999999999H2.5a0.5 0.5 0 0 1 -0.36533333333333334 -0.8413333333333333l7 -7.5a0.5 0.5 0 0 1 0.6086666666666667 -0.09533333333333333Z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
stroke-width="0.6667"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
79
src/lib/components/icons/BuyMeACoffeeIcon.svelte
Normal file
21
src/lib/components/icons/DonateIcon.svelte
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script>
|
||||||
|
export let size = 20;
|
||||||
|
export let color = 'currentColor';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
viewBox="0 0 115.03736 82.932701"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
xml:space="preserve"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
><defs id="defs1" /><g id="layer1" transform="translate(46.342105,-326.35286)"
|
||||||
|
><path
|
||||||
|
style={`fill:${color}`}
|
||||||
|
d="m -33.360673,409.11068 c -0.52052,-0.66147 -13.05506,-29.41883 -12.98111,-29.7819 0.0676,-0.33192 10.25008,-3.75875 11.16875,-3.75875 0.39694,0 -0.23972,-1.36265 6.66385,14.2629 3.11223,7.04422 5.6586,13.00337 5.6586,13.24254 0,0.28356 -1.49602,1.30029 -4.29948,2.92205 -2.36471,1.36794 -4.67009,2.70349 -5.12306,2.96789 -0.65389,0.38166 -0.87798,0.4116 -1.08755,0.14527 z m 51.11115,-7.18941 c -1.0255,-0.19444 -6.26425,-1.60693 -11.6416602,-3.13887 -11.60606,-3.30637 -11.88394,-3.36862 -15.03947,-3.36862 -3.0879598,0 -5.4300198,0.58865 -9.3851798,2.35884 -1.65323,0.73993 -3.20801,1.34533 -3.45507,1.34533 -0.33715,0 -1.59126,-2.59045 -5.02761,-10.38489 -2.51812,-5.7117 -4.63162,-10.51906 -4.69666,-10.68304 -0.065,-0.16398 0.75205,-0.77969 1.81577,-1.36826 1.06372,-0.58856 3.06512,-1.98586 4.44757,-3.1051 1.38245,-1.11925 3.21341,-2.51262 4.06879,-3.09639 5.178,-3.5338 12.3545098,-5.07308 18.9419698,-4.06285 3.59287,0.55099 6.45084,1.42963 10.85591,3.33749 7.3390302,3.17857 11.7920602,4.46814 22.2961402,6.45677 6.83656,1.29429 8.39146,1.84388 9.38771,3.31816 1.351,1.99922 0.46493,4.08861 -2.34074,5.5196 -2.74024,1.39761 -3.54371,1.37687 -13.73269,-0.35444 -8.2986,-1.4101 -9.07435,-1.4986 -12.17084,-1.38851 -2.5834002,0.0919 -3.7126702,0.25703 -5.1593702,0.75463 -1.63768,0.56329 -1.86179,0.72144 -1.93595,1.36615 -0.12182,1.05906 0.46688,1.15204 2.81088,0.44394 3.5053302,-1.05892 6.4809702,-0.89831 16.9844402,0.91671 8.37829,1.44778 8.95895,1.5149 10.60612,1.22601 4.34498,-0.76205 7.62602,-3.6216 7.64432,-6.66234 0.003,-0.56374 -0.31433,-1.57393 -0.76375,-2.42828 -0.88082,-1.67447 -0.97521,-1.4883 1.59586,-3.14763 1.45305,-0.93779 3.67139,-2.74223 8.64098,-7.02876 5.07066,-4.37371 7.8612,-5.88252 9.87621,-5.33994 0.5539,0.14915 1.07867,0.38701 1.16616,0.52858 0.0875,0.14156 0.75876,0.25739 1.49171,0.25739 2.23308,0 3.66323,1.33775 3.66323,3.42656 0,1.22305 -0.39272,1.86203 -5.78067,9.40573 -5.63224,7.88573 -7.41725,9.89443 -10.15569,11.42836 -5.52566,3.09519 -23.79832,12.65577 -25.06445,13.11418 -1.88226,0.68147 -7.21439,0.87102 -9.94397,0.35349 z m 8.93119,-37.4527 c -10.54204,-7.34003 -15.45466,-12.20175 -18.3280302,-18.13812 -2.47555,-5.11448 -2.73882,-10.04227 -0.74529,-13.94992 0.92633,-1.81576 3.3031302,-4.09338 5.2439402,-5.02511 3.14838,-1.51146 7.45008,-1.29668 10.56261,0.52739 1.77607,1.04084 4.13023,3.35279 5.47731,5.37909 0.66799,1.00478 1.26128,1.82688 1.31843,1.82688 0.0571,0 0.69136,-0.8632 1.40937,-1.91822 2.27222,-3.33879 4.61009,-5.23911 7.75182,-6.30101 2.33309,-0.78858 5.77205,-0.63428 7.95915,0.35711 1.75477,0.79542 4.18097,2.97573 5.14853,4.62674 4.08542,6.97124 0.45301,16.98315 -9.18256,25.30961 -4.39191,3.79521 -12.25534,9.42128 -13.13598,9.39843 -0.27047,-0.007 -1.83615,-0.94881 -3.4793,-2.09287 z"
|
||||||
|
id="path2"
|
||||||
|
/></g
|
||||||
|
></svg
|
||||||
|
>
|
||||||
17
src/lib/components/icons/FilesIcon.svelte
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<script>
|
||||||
|
export let size = 20;
|
||||||
|
export let color = 'currentColor';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
fill={color}
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
viewBox="0 0 256 256"
|
||||||
|
id="Flat"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M213.65723,66.34326l-40-40A8.00076,8.00076,0,0,0,168,24H88A16.01833,16.01833,0,0,0,72,40V56H56A16.01833,16.01833,0,0,0,40,72V216a16.01833,16.01833,0,0,0,16,16H168a16.01833,16.01833,0,0,0,16-16V200h16a16.01833,16.01833,0,0,0,16-16V72A8.00035,8.00035,0,0,0,213.65723,66.34326ZM136,192H88a8,8,0,0,1,0-16h48a8,8,0,0,1,0,16Zm0-32H88a8,8,0,0,1,0-16h48a8,8,0,0,1,0,16Zm64,24H184V104a8.00035,8.00035,0,0,0-2.34277-5.65674l-40-40A8.00076,8.00076,0,0,0,136,56H88V40h76.68652L200,75.314Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
18
src/lib/components/icons/FourSquaresIcon.svelte
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<script>
|
||||||
|
export let size = 20;
|
||||||
|
export let color = 'currentColor';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke={color}
|
||||||
|
stroke-width="1.5"
|
||||||
|
>
|
||||||
|
<rect x="3" y="3" width="7" height="7" rx="1.5" />
|
||||||
|
<rect x="14" y="3" width="7" height="7" rx="1.5" />
|
||||||
|
<rect x="3" y="14" width="7" height="7" rx="1.5" />
|
||||||
|
<rect x="14" y="14" width="7" height="7" rx="1.5" />
|
||||||
|
</svg>
|
||||||
19
src/lib/components/icons/MailIcon.svelte
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<script>
|
||||||
|
export let size = 20;
|
||||||
|
export let strokeWidth = 2;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width={strokeWidth}
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<rect x="3" y="5" width="18" height="14" rx="2" ry="2" />
|
||||||
|
<polyline points="3,7 12,13 21,7" />
|
||||||
|
</svg>
|
||||||
17
src/lib/components/icons/PadlockIcon.svelte
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<script>
|
||||||
|
export let size = 20;
|
||||||
|
export let color = 'currentColor';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg
|
||||||
|
fill={color}
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
id="lock-check"
|
||||||
|
class="icon glyph"
|
||||||
|
><path
|
||||||
|
d="M18,8H17V7A5,5,0,0,0,7,7V8H6a2,2,0,0,0-2,2V20a2,2,0,0,0,2,2H18a2,2,0,0,0,2-2V10A2,2,0,0,0,18,8ZM9,7a3,3,0,0,1,6,0V8H9Zm6.71,6.71-4,4a1,1,0,0,1-1.42,0l-2-2a1,1,0,0,1,1.42-1.42L11,15.59l3.29-3.3a1,1,0,0,1,1.42,1.42Z"
|
||||||
|
></path></svg
|
||||||
|
>
|
||||||
93
src/lib/sections/Faq.svelte
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
<script>
|
||||||
|
import { t } from '$lib/translations';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section id="faq">
|
||||||
|
<h1>{$t('faq.title')}</h1>
|
||||||
|
<p class="intro">
|
||||||
|
{$t('faq.intro')}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="faq-list">
|
||||||
|
<details>
|
||||||
|
<summary>{$t('faq.items.openPesOnline.summary')}</summary>
|
||||||
|
<p>{$t('faq.items.openPesOnline.description')}</p>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>{$t('faq.items.supportedFormats.summary')}</summary>
|
||||||
|
<p>{$t('faq.items.supportedFormats.description')}</p>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>{$t('faq.items.needSoftware.summary')}</summary>
|
||||||
|
<p>{$t('faq.items.needSoftware.description')}</p>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>{$t('faq.items.isSafe.summary')}</summary>
|
||||||
|
<p>{$t('faq.items.isSafe.description')}</p>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>{$t('faq.items.multipleFiles.summary')}</summary>
|
||||||
|
<p>{$t('faq.items.multipleFiles.description')}</p>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>{$t('faq.items.mobileSupport.summary')}</summary>
|
||||||
|
<p>{$t('faq.items.mobileSupport.description')}</p>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#faq {
|
||||||
|
padding: 100px 20px;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#faq h1 {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
#faq .intro {
|
||||||
|
text-align: center;
|
||||||
|
margin: 10px 0 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-list details {
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
padding: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-list summary {
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
list-style: none;
|
||||||
|
position: relative;
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-list summary::after {
|
||||||
|
content: '+';
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
font-size: 1.7rem;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-list details[open] summary::after {
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-list p {
|
||||||
|
margin-top: 10px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
224
src/lib/sections/Features.svelte
Normal file
|
|
@ -0,0 +1,224 @@
|
||||||
|
<script>
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
|
import { t } from '$lib/translations';
|
||||||
|
|
||||||
|
import BoltIcon from '$lib/components/icons/BoltIcon.svelte';
|
||||||
|
import PadlockIcon from '$lib/components/icons/PadlockIcon.svelte';
|
||||||
|
import FourSquaresIcon from '$lib/components/icons/FourSquaresIcon.svelte';
|
||||||
|
import FilesIcon from '$lib/components/icons/FilesIcon.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section id="features">
|
||||||
|
<header>
|
||||||
|
<h1>{$t('features.title')}</h1>
|
||||||
|
<p class="subtitle">{$t('features.subtitle')}</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="cards-container">
|
||||||
|
<div class="blob">
|
||||||
|
<p class="adjective">{$t('features.cards.fast.adjective')}</p>
|
||||||
|
<h2>{$t('features.cards.fast.title')}</h2>
|
||||||
|
<BoltIcon color="#06345f" size={100} />
|
||||||
|
<p class="description">{$t('features.cards.fast.description')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="blob">
|
||||||
|
<p class="adjective">{$t('features.cards.private.adjective')}</p>
|
||||||
|
<h2>{$t('features.cards.private.title')}</h2>
|
||||||
|
<PadlockIcon color="#06345f" size={100} />
|
||||||
|
<p class="description">{$t('features.cards.private.description')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="blob">
|
||||||
|
<p class="adjective">{$t('features.cards.optimized.adjective')}</p>
|
||||||
|
<h2>{$t('features.cards.optimized.title')}</h2>
|
||||||
|
<FourSquaresIcon color="#06345f" size={100} />
|
||||||
|
<p class="description">{$t('features.cards.optimized.description')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="blob">
|
||||||
|
<p class="adjective">{$t('features.cards.compatibility.adjective')}</p>
|
||||||
|
<h2>{$t('features.cards.compatibility.title')}</h2>
|
||||||
|
<FilesIcon color="#06345f" size={100} />
|
||||||
|
<p class="description">
|
||||||
|
{$t('features.cards.compatibility.description')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a class="organic-btn-secondary" href={resolve('/viewer')}
|
||||||
|
>{$t('features.cta')}</a
|
||||||
|
>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#features {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 100px 0;
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
#features::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg width='600' height='600' viewBox='0 0 200 200' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M10 150 C 60 20, 140 180, 190 50' stroke='white' stroke-width='1.5' stroke-dasharray='4 6' opacity='0.2'/%3E%3Ccircle cx='10' cy='150' r='3' fill='white' opacity='0.25'/%3E%3C/svg%3E");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 600px;
|
||||||
|
background-position: right -100px top -80px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
#features::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg width='600' height='600' viewBox='0 0 200 200' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20 40 C 80 180, 120 0, 180 140' stroke='white' stroke-width='1.2' stroke-dasharray='3 8' opacity='0.15'/%3E%3C/svg%3E");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 500px;
|
||||||
|
background-position: left -120px bottom -80px;
|
||||||
|
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
header {
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.5;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: white;
|
||||||
|
font-size: 2.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
color: white;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cards-container {
|
||||||
|
display: grid;
|
||||||
|
gap: 20px;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
padding: 100px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blob {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 380px;
|
||||||
|
height: 400px;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
border-radius: 25% 50% 75% 100%;
|
||||||
|
text-align: center;
|
||||||
|
padding: 30px 60px;
|
||||||
|
|
||||||
|
background: linear-gradient(
|
||||||
|
30deg in oklch shorter hue,
|
||||||
|
oklch(0.98 0.01 260) 10%,
|
||||||
|
oklch(0.92 0.02 260)
|
||||||
|
);
|
||||||
|
|
||||||
|
box-shadow: 1rem 1rem 50px #0001;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blob h2 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adjective {
|
||||||
|
color: var(--color-primary);
|
||||||
|
font-size: 0.8rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.organic-btn-secondary {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
padding: 30px 90px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1639px) {
|
||||||
|
.blob {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 340px;
|
||||||
|
height: 380px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1480px) {
|
||||||
|
.cards-container {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blob {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 1297px) {
|
||||||
|
.cards-container {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1210px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 2.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1033px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 880px) {
|
||||||
|
#features {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
header {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cards-container {
|
||||||
|
padding: 0;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blob {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
padding-bottom: 50px;
|
||||||
|
height: fit-content;
|
||||||
|
aspect-ratio: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 540px) {
|
||||||
|
.organic-btn-secondary {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 20px 50px;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
97
src/lib/sections/Hero.svelte
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
<script>
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
|
import { PUBLIC_IMAGE_BASE_URL } from '$env/static/public';
|
||||||
|
import { t } from '$lib/translations';
|
||||||
|
import { isMobile } from '$lib/utils/isMobile';
|
||||||
|
|
||||||
|
const backgroundImage = isMobile()
|
||||||
|
? `${PUBLIC_IMAGE_BASE_URL}/t/f_webp/embroidery-viewer/hero-mobile.webp`
|
||||||
|
: `${PUBLIC_IMAGE_BASE_URL}/t/f_webp,w_1920,h_1080/embroidery-viewer/hero.webp`;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section
|
||||||
|
id="hero"
|
||||||
|
style={`background: url(${backgroundImage}) center/cover no-repeat`}
|
||||||
|
>
|
||||||
|
<div class="overlay">
|
||||||
|
<h1>{$t('hero.title')}</h1>
|
||||||
|
<p>{$t('hero.description')}</p>
|
||||||
|
|
||||||
|
<a class="organic-btn" href={resolve('/viewer')}>{$t('hero.cta')}</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#hero {
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-width: 800px;
|
||||||
|
z-index: 1;
|
||||||
|
padding-left: 100px;
|
||||||
|
padding-top: 130px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: clamp(3.5rem, 4vw, 3.5rem);
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1350px) {
|
||||||
|
h1 {
|
||||||
|
font-size: clamp(3.3rem, 4vw, 3.3rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1280px) {
|
||||||
|
h1 {
|
||||||
|
font-size: clamp(3rem, 4vw, 3rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1180px) {
|
||||||
|
h1 {
|
||||||
|
font-size: clamp(3rem, 4vw, 3rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
h1 {
|
||||||
|
font-size: clamp(2rem, 4vw, 2rem);
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hero {
|
||||||
|
background-size: contain !important;
|
||||||
|
background-position: bottom !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
width: 100%;
|
||||||
|
padding-top: 100px;
|
||||||
|
padding-left: 0;
|
||||||
|
height: 100vh;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
138
src/lib/sections/MobileApp.svelte
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
<script>
|
||||||
|
import { locale, t } from '$lib/translations';
|
||||||
|
import { PUBLIC_IMAGE_BASE_URL } from '$env/static/public';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section class="app">
|
||||||
|
<div class="container">
|
||||||
|
<!-- LEFT: TEXT -->
|
||||||
|
<div class="content">
|
||||||
|
<h2>
|
||||||
|
{$t('mobile.title.prefix')}
|
||||||
|
<span>{$t('mobile.title.highlight')}</span>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p class="description">
|
||||||
|
{$t('mobile.description')}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- FEATURES -->
|
||||||
|
<div class="features">
|
||||||
|
<div>✅ {$t('mobile.features.formats')}</div>
|
||||||
|
<div>🎨 {$t('mobile.features.customization')}</div>
|
||||||
|
<div>🎯 {$t('mobile.features.highlight')}</div>
|
||||||
|
<div>📏 {$t('mobile.features.metadata')}</div>
|
||||||
|
<div>🚀 {$t('mobile.features.performance')}</div>
|
||||||
|
<div>♿ {$t('mobile.features.accessibility')}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- CTA -->
|
||||||
|
<a
|
||||||
|
class="cta-link"
|
||||||
|
href="https://play.google.com/store/apps/details?id=xyz.embroideryviewer.android"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={`${PUBLIC_IMAGE_BASE_URL}/t/w_240,h_76,f_webp/embroidery-viewer/${$locale}/android-download.webp`}
|
||||||
|
alt={$t('mobile.cta_alt')}
|
||||||
|
width="240"
|
||||||
|
height="76"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- RIGHT: IMAGE -->
|
||||||
|
<div class="visual">
|
||||||
|
<img
|
||||||
|
src={`${PUBLIC_IMAGE_BASE_URL}/t/f_webp/embroidery-viewer/${$locale}/app-with-frame.webp`}
|
||||||
|
alt={$t('mobile.image_alt')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="blob"></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.cta-link {
|
||||||
|
padding: 0;
|
||||||
|
margin-top: 50px;
|
||||||
|
width: fit-content;
|
||||||
|
height: fit-content;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-link:hover {
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app {
|
||||||
|
position: relative;
|
||||||
|
padding: 6rem 1.5rem;
|
||||||
|
background: #ffffff;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1100px;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1.1fr 1fr;
|
||||||
|
gap: 3rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: clamp(2rem, 4vw, 2.6rem);
|
||||||
|
color: #06345f;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 span {
|
||||||
|
font-weight: 900;
|
||||||
|
background: linear-gradient(120deg, #06345f, #194795);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.features {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
display: grid;
|
||||||
|
gap: 0.4rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #06345f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.visual {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.visual img {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 380px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.container {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
59
src/lib/styles/fonts.css
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Merienda';
|
||||||
|
font-display: swap;
|
||||||
|
src:
|
||||||
|
url('/fonts/merienda.regular.woff2') format('woff2'),
|
||||||
|
url('/fonts/merienda.regular.woff') format('woff');
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-optical-sizing: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Merienda';
|
||||||
|
font-display: swap;
|
||||||
|
src:
|
||||||
|
url('/fonts/merienda.medium.woff2') format('woff2'),
|
||||||
|
url('/fonts/merienda.medium.woff') format('woff');
|
||||||
|
font-weight: 500;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-optical-sizing: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Merienda';
|
||||||
|
font-display: swap;
|
||||||
|
src:
|
||||||
|
url('/fonts/merienda.semi-bold.woff2') format('woff2'),
|
||||||
|
url('/fonts/merienda.semi-bold.woff') format('woff');
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-optical-sizing: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Merienda';
|
||||||
|
font-display: swap;
|
||||||
|
src:
|
||||||
|
url('/fonts/merienda.bold.woff2') format('woff2'),
|
||||||
|
url('/fonts/merienda.bold.woff') format('woff');
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-optical-sizing: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Merienda';
|
||||||
|
font-display: swap;
|
||||||
|
src:
|
||||||
|
url('/fonts/merienda.extra-bold.woff2') format('woff2'),
|
||||||
|
url('/fonts/merienda.extra-bold.woff') format('woff');
|
||||||
|
font-weight: 900;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-optical-sizing: auto;
|
||||||
|
}
|
||||||
117
src/lib/styles/global.css
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
:root {
|
||||||
|
font-family: 'Merienda', cursive;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-weight: 400;
|
||||||
|
font-synthesis: none;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #f2f6f5;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='submit'] {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
background-color: #05345f;
|
||||||
|
font-weight: 700;
|
||||||
|
color: white;
|
||||||
|
padding: 10px;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='submit']:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
body a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #06345f;
|
||||||
|
border-bottom: 3px solid #06345f;
|
||||||
|
}
|
||||||
|
|
||||||
|
body a:hover {
|
||||||
|
background-color: #06345f;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(h1, h2, h3, h4, h5, h6) {
|
||||||
|
color: #06345f;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: #06345f;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul li::marker {
|
||||||
|
color: #06345f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.organic-btn {
|
||||||
|
background: var(--color-primary);
|
||||||
|
width: fit-content;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 20px 60px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 58% 42% 65% 27% / 40% 60% 60% 70%;
|
||||||
|
border: 1px solid var(--color-primary);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.organic-btn-secondary {
|
||||||
|
color: white;
|
||||||
|
width: fit-content;
|
||||||
|
padding: 20px 60px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 58% 42% 65% 27% / 40% 60% 60% 70%;
|
||||||
|
border: 1px solid white;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.organic-btn:hover {
|
||||||
|
color: var(--color-primary);
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.organic-btn-secondary:hover {
|
||||||
|
color: var(--color-primary);
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid white;
|
||||||
|
}
|
||||||
6
src/lib/styles/variables.css
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
:root {
|
||||||
|
--color-primary: #06345f;
|
||||||
|
--color-secondary: #094275;
|
||||||
|
|
||||||
|
--font-base: 'Merienda';
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,34 @@
|
||||||
{
|
{
|
||||||
"title": "ℹ About Embroidery Viewer",
|
"seo.title": "About Embroidery Viewer – The Story Behind the Tool",
|
||||||
"content": "<p>Hi there! 👋</p><p><strong>⭐️ Embroidery Viewer</strong> was born out of a simple need — helping someone I care about. 💖</p><p>My girlfriend loves embroidery, but she often struggled to find an easy and free way to preview her embroidery design files before stitching them. Most tools she tried were either paid, overly complex, or required technical knowledge — and she’s not a techie.</p><p>So, to make things easier for her (and others like her), I decided to build this web application.</p><p>Over the course of a few weeks, I created <strong>Embroidery Viewer</strong> — a lightweight, fast, and free tool that lets you view embroidery files directly in your browser. No installation, no setup, and no tech hurdles. Just upload your file and see your design.</p><p>It’s not a super sophisticated tool, but it solves the problem it was meant to solve: making embroidery file previews accessible to everyone.</p><p>If this tool has helped you too, that makes me really happy! I plan to continue improving it based on feedback from users like you.</p><p>Thanks for stopping by — and happy stitching! 🧵✨</p>",
|
|
||||||
"seo.title": "ℹ About Embroidery Viewer – The Story Behind the Tool",
|
|
||||||
"seo.description": "Learn the story behind Embroidery Viewer — a free, online tool created to make embroidery file previews simple, fast, and accessible to everyone.",
|
"seo.description": "Learn the story behind Embroidery Viewer — a free, online tool created to make embroidery file previews simple, fast, and accessible to everyone.",
|
||||||
"seo.keywords": "about embroidery viewer, embroidery viewer story, free embroidery viewer, why embroidery viewer was created, who created embroidery viewer, online embroidery viewer, free embroidery tool, embroidery viewer about",
|
"seo.keywords": "about embroidery viewer, embroidery viewer story, free embroidery viewer, why embroidery viewer was created, who created embroidery viewer, online embroidery viewer, free embroidery tool, embroidery viewer about",
|
||||||
"seo.url": "https://embroideryviewer.xyz/about",
|
"seo.url": "https://embroideryviewer.xyz/about",
|
||||||
"seo.image": "https://embroideryviewer.xyz/og/about.png"
|
"seo.image": "https://embroideryviewer.xyz/og/about.png",
|
||||||
|
"hero": {
|
||||||
|
"tagline": "A SMALL ACT OF LOVE",
|
||||||
|
"title": "That became a powerful way to preview embroidery files"
|
||||||
|
},
|
||||||
|
"story": {
|
||||||
|
"title": "The story behind Embroidery Viewer...",
|
||||||
|
"p1": "It didn’t start as a product. It started with a simple frustration.",
|
||||||
|
"p2": "Someone I care about loves embroidery. She enjoys creating, exploring designs, and bringing ideas to life through stitching. But every time she downloaded a new embroidery file, she ran into the same problem:",
|
||||||
|
"quote1": "“How do I even open this?”",
|
||||||
|
"p3": "Most of the tools available were either paid, overly complex, or built for people already familiar with technical embroidery software. For someone who just wanted to quickly preview a design before stitching, it felt unnecessarily difficult.",
|
||||||
|
"p4": "What should have been a simple step—just seeing the design—became a barrier. That’s when I realized something important:",
|
||||||
|
"quote2": "There are probably many people facing the exact same problem."
|
||||||
|
},
|
||||||
|
"product": {
|
||||||
|
"p1": "People don’t want complicated software.",
|
||||||
|
"p2": "People don’t want to install anything.",
|
||||||
|
"p3": "People just want to open an embroidery file and see their design instantly.",
|
||||||
|
"p4": "So instead of searching for a better tool…",
|
||||||
|
"quote": "I decided to build one."
|
||||||
|
},
|
||||||
|
"support": {
|
||||||
|
"title": "If it helped you, you can help it grow",
|
||||||
|
"p1": "Embroidery Viewer is completely free—and I intend to keep it that way.",
|
||||||
|
"p2": "But keeping it fast, improving support for more embroidery formats, and maintaining the infrastructure behind it takes time and resources.",
|
||||||
|
"p3": "If this tool made your workflow easier, helped you preview a design, or saved you from unnecessary hassle, you can support its future.",
|
||||||
|
"cta": "Support Us"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"title": "💖 Donate",
|
|
||||||
"subtitle": "Help support Embroidery Viewer and its development!",
|
|
||||||
"description": "⭐️ <strong>Embroidery Viewer</strong> is free to use. If you find this tool helpful, please consider making a donation to keep it running and fund future improvements.",
|
|
||||||
"ways": "💸 Ways to Donate",
|
|
||||||
"bitcoin.description": "Scan or copy the address",
|
|
||||||
"copy": "Copy Address",
|
|
||||||
"copied": "Copied to Clipboard!",
|
|
||||||
"copy.failed": "Copy Failed!",
|
|
||||||
"monero.description": "Private and secure donation option.",
|
|
||||||
"paypal.description": "Want to show support in a friendly way?",
|
|
||||||
"paypal.link": "Open Donation link",
|
|
||||||
"seo.title": "💖 Donate – Support Embroidery Viewer",
|
|
||||||
"seo.description": "Help keep Embroidery Viewer free and improving by making a donation. Choose from Bitcoin, Monero, PayPal, or other secure options to support ongoing development and hosting.",
|
|
||||||
"seo.keywords": "donate embroidery viewer, support embroidery viewer, embroidery viewer donations, help embroidery viewer, fund embroidery viewer, bitcoin donation embroidery, monero donation embroidery, paypal donation embroidery",
|
|
||||||
"url": "https://embroideryviewer.xyz/donate",
|
|
||||||
"image": "https://embroideryviewer.xyz/og/donate.png"
|
|
||||||
}
|
|
||||||
30
src/lib/translations/en-US/faq.json
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"title": "Frequently Asked Questions",
|
||||||
|
"intro": "Learn how to open embroidery files online, supported formats, and how Embroidery Viewer works — all in one place.",
|
||||||
|
"items": {
|
||||||
|
"openPesOnline": {
|
||||||
|
"summary": "How can I open a PES file online?",
|
||||||
|
"description": "You can open a PES file instantly using Embroidery Viewer. Just drag and drop your file into the browser to preview your embroidery design—no software installation required."
|
||||||
|
},
|
||||||
|
"supportedFormats": {
|
||||||
|
"summary": "What embroidery file formats are supported?",
|
||||||
|
"description": "Embroidery Viewer supports popular formats such as PES, DST, and EXP. This allows you to preview most embroidery designs used by home and commercial machines."
|
||||||
|
},
|
||||||
|
"needSoftware": {
|
||||||
|
"summary": "Do I need to install any embroidery software?",
|
||||||
|
"description": "No. Embroidery Viewer works entirely in your browser, so you can open embroidery files online without downloading or installing any software."
|
||||||
|
},
|
||||||
|
"isSafe": {
|
||||||
|
"summary": "Is it safe to use Embroidery Viewer?",
|
||||||
|
"description": "Yes. Your files are processed locally in your browser and are never uploaded to any server, ensuring complete privacy."
|
||||||
|
},
|
||||||
|
"multipleFiles": {
|
||||||
|
"summary": "Can I view multiple embroidery files at once?",
|
||||||
|
"description": "Yes. You can open and compare multiple embroidery files in a single view, making it easier to review and choose designs."
|
||||||
|
},
|
||||||
|
"mobileSupport": {
|
||||||
|
"summary": "Can I use this on mobile or tablet?",
|
||||||
|
"description": "Yes. Embroidery Viewer works on desktop, tablet, and mobile devices, as long as you are using a modern web browser."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/lib/translations/en-US/features.json
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"title": "The Easiest Way to Preview Embroidery Files",
|
||||||
|
"subtitle": "Whether you're a hobbyist working on your next DIY project or a professional digitizer reviewing client files, Embroidery Viewer gives you a fast, simple way to preview embroidery designs online—no software, no friction.",
|
||||||
|
"cards": {
|
||||||
|
"fast": {
|
||||||
|
"adjective": "FAST",
|
||||||
|
"title": "Instant Preview",
|
||||||
|
"description": "Drop your file and see your design instantly—no installs, no waiting."
|
||||||
|
},
|
||||||
|
"private": {
|
||||||
|
"adjective": "PRIVATE",
|
||||||
|
"title": "Private by Design",
|
||||||
|
"description": "Your files stay on your device. Nothing is uploaded, ever."
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"adjective": "OPTIMIZED",
|
||||||
|
"title": "Multiple Files, One View",
|
||||||
|
"description": "Open and compare several designs at the same time in a clean layout."
|
||||||
|
},
|
||||||
|
"compatibility": {
|
||||||
|
"adjective": "COMPATIBILITY",
|
||||||
|
"title": "Supports Popular Formats",
|
||||||
|
"description": "Works with PES, DST, EXP and other common embroidery formats."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cta": "Try it now"
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,15 @@
|
||||||
{
|
{
|
||||||
"about": "ℹ About",
|
"slogan": "Preview your embroidery designs instantly — no software needed",
|
||||||
"privacy.policy": "🔐 Privacy Policy",
|
"resources": "Resources",
|
||||||
"terms.of.service": "📝 Terms of Service",
|
"contact-title": "Contact",
|
||||||
"copyright": "Copyright © {{year}} <a href=\"{{website}}\" target=\"_blank\" rel=\"noreferrer\">Leonardo Murça</a>. <br/> All rights reserved.",
|
"contact-description": "Do you want to help or have any questions? Contact us.",
|
||||||
"version": "🧵 Version: {{version}}"
|
"back-to-top": {
|
||||||
|
"label": "Back To Top",
|
||||||
|
"aria-label": "Back to top of the page"
|
||||||
|
},
|
||||||
|
"about": "About",
|
||||||
|
"privacy.policy": "Privacy Policy",
|
||||||
|
"terms.of.service": "Terms of Service",
|
||||||
|
"copyright": "Copyright © {{year}} <a href=\"{{website}}\" target=\"_blank\" rel=\"noreferrer\">Leonardo Murça</a>. All rights reserved.",
|
||||||
|
"version": "Version {{version}}"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"languageSwitch": "🇧🇷",
|
"homeNav": "Home",
|
||||||
"homeNav": "🏠 Home",
|
"aboutNav": "About",
|
||||||
"aboutNav": "ℹ About",
|
"viewerNav": "Viewer",
|
||||||
"viewerNav": "🧵 Viewer",
|
"supportUsNav": "Support Us"
|
||||||
"donateNav": "💖 Donate"
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
5
src/lib/translations/en-US/hero.json
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"title": "Preview your embroidery designs instantly — no software needed",
|
||||||
|
"description": "Fast, private & no signup required",
|
||||||
|
"cta": "Try Your Design"
|
||||||
|
}
|
||||||
|
|
@ -1,31 +1,7 @@
|
||||||
{
|
{
|
||||||
"main.title": "🧵 Free Online Embroidery File Viewer",
|
"seo.title": "Free Online Embroidery File Viewer - Fast, Private & No Signup",
|
||||||
"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>",
|
|
||||||
"features.title": "🚀 Features",
|
|
||||||
"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>",
|
|
||||||
"howtouse.title": "📘 How to Use",
|
|
||||||
"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>",
|
|
||||||
"testimonials.title": "❤️ Loved by Hobbyists and Professionals",
|
|
||||||
"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>",
|
|
||||||
"donation.title": "💖 Help Keep It Free",
|
|
||||||
"donation.description": "<p><strong>Embroidery Viewer is completely free</strong> for everyone to use.</p><p>If you find it useful and want to support ongoing development and hosting costs, please consider making a small donation.</p>",
|
|
||||||
"donation.cta": "🙌 Donate Now",
|
|
||||||
"donation.cta.description": "every little bit helps!",
|
|
||||||
"cta.title": "🚀 Try It Now",
|
|
||||||
"cta.cta": "🧵 Open Viewer",
|
|
||||||
"cta.cta.description": "the fastest <strong>Free Online Embroidery File Viewer</strong>.",
|
|
||||||
"seo.title": "🏠 Free Online Embroidery File Viewer - Fast, Private & No Signup",
|
|
||||||
"seo.description": "Upload and preview embroidery files instantly with Embroidery Viewer. Supports DST, PES, JEF, EXP, VP3 and more. No installs, no uploads – 100% browser-based and free.",
|
"seo.description": "Upload and preview embroidery files instantly with Embroidery Viewer. Supports DST, PES, JEF, EXP, VP3 and more. No installs, no uploads – 100% browser-based and free.",
|
||||||
"seo.keywords": "embroidery viewer, online embroidery viewer, embroidery file preview, DST viewer, PES viewer, free embroidery tool, JEF viewer, EXP embroidery, VP3 embroidery viewer, embroidery preview tool, browser embroidery renderer, convert embroidery to PNG",
|
"seo.keywords": "embroidery viewer, online embroidery viewer, embroidery file preview, DST viewer, PES viewer, free embroidery tool, JEF viewer, EXP embroidery, VP3 embroidery viewer, embroidery preview tool, browser embroidery renderer, convert embroidery to PNG",
|
||||||
"seo.url": "https://embroideryviewer.xyz",
|
"seo.url": "https://embroideryviewer.xyz",
|
||||||
"seo.image": "https://embroideryviewer.xyz/og/",
|
"seo.image": "https://embroideryviewer.xyz/og/"
|
||||||
"banner.title": "🚀 Be a Beta Tester",
|
|
||||||
"banner.subtitle": "We’re launching the <strong style=\"color: white\">Embroidery Viewer Android App </strong> — and you can be one of the first to try it!",
|
|
||||||
"banner.description": "Enjoy a smooth, ad-free experience to visualize PES, JEF, PEC, VP3, DST and EXP embroidery files right from your phone. Help us improve it and get early access before everyone else.",
|
|
||||||
"banner.name": "Name",
|
|
||||||
"banner.email": "Google Account Email (used for accessing the Play Store)",
|
|
||||||
"banner.cta": "Join the Beta",
|
|
||||||
"banner.cta.loading": "Joining...",
|
|
||||||
"banner.feedback.success": "Name and email sent successfully! We'll contact you soon!",
|
|
||||||
"banner.feedback.error": "Something went wrong. Try sending you name and e-mail directly to leo@leomurca.xyz."
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"title": "🔐 Privacy Policy",
|
"title": "Privacy Policy",
|
||||||
"last.update": "Last updated: Jul 11, 2025",
|
"last.update": "Last updated: Jul 11, 2025",
|
||||||
"content": "<h1>Privacy Policy</h1><p>Thank you for using the Embroidery Viewer Companion app. Your privacy is important to us. This Privacy Policy explains how we handle your data.</p><h2>1. No Personal Data Collected</h2><p>The app does not collect, store, or transmit any personal data. All your embroidery files are processed locally on your device. We do not access or send your files anywhere.</p><h2>2. Storage Permissions</h2><p>The app requests access to your files only so that you can open and view embroidery files stored on your device. This permission is used solely for that purpose.</p><h2>3. Name Personalization</h2><p>If you choose to enter your name, it is stored locally on your device to personalize greetings (like \"Good morning, Leo\"). This information is not shared and is never sent over the internet.</p><h2>4. No Analytics or Advertising</h2><p>This app does not use any analytics tools or display ads. We believe in a distraction-free and respectful experience.</p><h2>5. Questions</h2><p>If you have any questions about this policy or the app, feel free to contact us at: <a href=\"mailto:leo@leomurca.xyz\">leo@leomurca.xyz</a></p>",
|
"content": "<h1>Privacy Policy</h1><p>Thank you for using the Embroidery Viewer Companion app. Your privacy is important to us. This Privacy Policy explains how we handle your data.</p><h2>1. No Personal Data Collected</h2><p>The app does not collect, store, or transmit any personal data. All your embroidery files are processed locally on your device. We do not access or send your files anywhere.</p><h2>2. Storage Permissions</h2><p>The app requests access to your files only so that you can open and view embroidery files stored on your device. This permission is used solely for that purpose.</p><h2>3. Name Personalization</h2><p>If you choose to enter your name, it is stored locally on your device to personalize greetings (like \"Good morning, Leo\"). This information is not shared and is never sent over the internet.</p><h2>4. No Analytics or Advertising</h2><p>This app does not use any analytics tools or display ads. We believe in a distraction-free and respectful experience.</p><h2>5. Questions</h2><p>If you have any questions about this policy or the app, feel free to contact us at: <a href=\"mailto:leo@leomurca.xyz\">leo@leomurca.xyz</a></p>",
|
||||||
"seo.title": "🔐 Privacy Policy - Embroidery Viewer Companion App",
|
"seo.title": "Privacy Policy - Embroidery Viewer Companion App",
|
||||||
"seo.description": "Learn how Embroidery Viewer Companion App respects your privacy. No personal data collected, files processed locally or temporarily, anonymous analytics only, no trackers used.",
|
"seo.description": "Learn how Embroidery Viewer Companion App respects your privacy. No personal data collected, files processed locally or temporarily, anonymous analytics only, no trackers used.",
|
||||||
"seo.keywords": "privacy policy, data protection, embroidery viewer privacy, file uploads privacy, anonymous analytics, no cookies, user privacy, privacy-friendly analytics, data security, embroideryviewer.xyz",
|
"seo.keywords": "privacy policy, data protection, embroidery viewer privacy, file uploads privacy, anonymous analytics, no cookies, user privacy, privacy-friendly analytics, data security, embroideryviewer.xyz",
|
||||||
"seo.url": "https://embroideryviewer.xyz/mobile-app/privacy-policy",
|
"seo.url": "https://embroideryviewer.xyz/mobile-app/privacy-policy",
|
||||||
|
|
|
||||||
17
src/lib/translations/en-US/mobile.json
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"title": {
|
||||||
|
"prefix": "Embroidery Viewer on ",
|
||||||
|
"highlight": "Android"
|
||||||
|
},
|
||||||
|
"description": "Visualize and explore your embroidery files directly on your phone — fast, simple, and built for real workflows.",
|
||||||
|
"features": {
|
||||||
|
"formats": "Supports PES, JEF, PEC, VP3, DST and EXP formats",
|
||||||
|
"customization": "Customize background and thread colors",
|
||||||
|
"highlight": "Tap to highlight thread sequences",
|
||||||
|
"metadata": "View stitches, size and color breakdown",
|
||||||
|
"performance": "Fast, lightweight and works offline",
|
||||||
|
"accessibility": "Accessible and easy to use"
|
||||||
|
},
|
||||||
|
"cta_alt": "Download on Google Play",
|
||||||
|
"image_alt": "Android Embroidery Viewer preview"
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"title": "🔐 Privacy Policy",
|
"title": "Privacy Policy",
|
||||||
"last.update": "Last updated: May 9, 2025",
|
"last.update": "Last updated: Apr 24, 2026",
|
||||||
"content": "<p>At <strong>Embroidery Viewer</strong> (<a href=\"https://embroideryviewer.xyz\">embroideryviewer.xyz</a>), we respect your privacy and are committed to protecting any information you share while using our service.</p><h2>1. Personal Information</h2><p>Embroidery Viewer does <strong>not</strong> collect or store any personal information. You do not need to create an account, and we do not ask for your name, email address, or any identifying details.</p><h2>2. File Uploads</h2><p>When you upload an embroidery file to the viewer, the file is processed in your browser or temporarily on our server (if required) for preview purposes only. <strong>No uploaded files are stored, saved, or shared.</strong></p><p>Please avoid uploading any copyrighted or sensitive material unless you have permission to use it.</p><h2>3. Analytics</h2><p>We use <strong>Umami</strong> to collect anonymous usage statistics about our website, such as the number of visitors, page views, device types, and referral sources. This data helps us understand how the site is being used and improve it over time.</p><p>Umami is a privacy-friendly, cookie-free analytics tool. It does <strong>not</strong> track users across sites, collect personal data, or use cookies. All data is aggregated and anonymized.</p><h2>4. Cookies</h2><p>Embroidery Viewer does <strong>not</strong> use cookies or other tracking mechanisms in your browser.</p><h2>5. Third-Party Services</h2><p>We do not use third-party advertising, embed external trackers, or share data with third parties.</p><h2>6. Changes to This Policy</h2><p>We may update this Privacy Policy from time to time. All updates will be posted on this page with the updated date.</p><h2>7. Contact</h2><p>If you have any questions about this Privacy Policy, you can reach us at <a href=\"mailto:leo@leomurca.xyz\">leo@leomurca.xyz</a>.</p>",
|
"content": "<p>At <strong>Embroidery Viewer</strong> (<a href=\"https://embroideryviewer.xyz\">embroideryviewer.xyz</a>), we respect your privacy and are committed to protecting any information you share while using our service.</p><h2>1. Personal Information</h2><p>Embroidery Viewer does <strong>not</strong> collect or store any personal information. You do not need to create an account, and we do not ask for your name, email address, or any identifying details.</p><h2>2. File Uploads</h2><p>When you upload an embroidery file to the viewer, the file is processed in your browser or temporarily on our server (if required) for preview purposes only. <strong>No uploaded files are stored, saved, or shared.</strong></p><p>Please avoid uploading any copyrighted or sensitive material unless you have permission to use it.</p><h2>3. Analytics</h2><p>We use <strong>HitKeep</strong> to collect anonymous usage statistics about our website, such as the number of visitors, page views, device types, and referral sources. This data helps us understand how the site is being used and improve it over time.</p><p>HitKeep is a privacy-friendly, cookie-free analytics tool. It does <strong>not</strong> track users across sites, collect personal data, or use cookies. All data is aggregated and anonymized.</p><h2>4. Cookies</h2><p>Embroidery Viewer does <strong>not</strong> use cookies or other tracking mechanisms in your browser.</p><h2>5. Third-Party Services</h2><p>We do not use third-party advertising, embed external trackers, or share data with third parties.</p><h2>6. Changes to This Policy</h2><p>We may update this Privacy Policy from time to time. All updates will be posted on this page with the updated date.</p><h2>7. Contact</h2><p>If you have any questions about this Privacy Policy, you can reach us at <a href=\"mailto:leo@leomurca.xyz\">leo@leomurca.xyz</a>.</p>",
|
||||||
"seo.title": "🔐 Privacy Policy - Embroidery Viewer",
|
"seo.title": "Privacy Policy - Embroidery Viewer",
|
||||||
"seo.description": "Learn how Embroidery Viewer respects your privacy. No personal data collected, files processed locally or temporarily, anonymous analytics only, no cookies or trackers used.",
|
"seo.description": "Learn how Embroidery Viewer respects your privacy. No personal data collected, files processed locally or temporarily, anonymous analytics only, no cookies or trackers used.",
|
||||||
"seo.keywords": "privacy policy, data protection, embroidery viewer privacy, file uploads privacy, anonymous analytics, no cookies, user privacy, privacy-friendly analytics, data security, embroideryviewer.xyz",
|
"seo.keywords": "privacy policy, data protection, embroidery viewer privacy, file uploads privacy, anonymous analytics, no cookies, user privacy, privacy-friendly analytics, data security, embroideryviewer.xyz",
|
||||||
"seo.url": "https://embroideryviewer.xyz/privacy-policy",
|
"seo.url": "https://embroideryviewer.xyz/privacy-policy",
|
||||||
|
|
|
||||||
10
src/lib/translations/en-US/support-us.json
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"title": "Keep embroidery simple for everyone",
|
||||||
|
"description": "Embroidery Viewer saves you time and removes the hassle of dealing with complex software. Your support helps keep it <strong>free, fast, and continuously improving</strong> for everyone.",
|
||||||
|
"cta": "Support the project",
|
||||||
|
"seo.title": "Support Embroidery Viewer",
|
||||||
|
"seo.description": "Help keep Embroidery Viewer free and improving by making a donation. Choose from Bitcoin, Monero, PayPal, or other secure options to support ongoing development and hosting.",
|
||||||
|
"seo.keywords": "donate embroidery viewer, support embroidery viewer, embroidery viewer donations, help embroidery viewer, fund embroidery viewer, bitcoin donation embroidery, monero donation embroidery, paypal donation embroidery",
|
||||||
|
"url": "https://embroideryviewer.xyz/support-us",
|
||||||
|
"image": "https://embroideryviewer.xyz/og/donate.png"
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"title": "📝 Terms of Service",
|
"title": "Terms of Service",
|
||||||
"update": "May 9, 2025",
|
"update": "May 9, 2025",
|
||||||
"content": "<p>Welcome to <strong>Embroidery Viewer</strong> (<a href=\"https://embroideryviewer.xyz\">embroideryviewer.xyz</a>). By accessing or using this website, you agree to be bound by the following Terms of Service. If you do not agree with any part of these terms, please do not use the site.</p><h2>1. Description of Service</h2><p>Embroidery Viewer is a free, browser-based tool that allows users to preview embroidery design files online. The service is intended for personal, non-commercial use.</p><h2>2. Use of the Service</h2><p>You agree to use the service only for lawful purposes. You are solely responsible for any content (including embroidery files) you upload, and you confirm that you have the legal right to use, view, and process those files.</p><p>You agree not to upload any files that are illegal, offensive, infringe on intellectual property rights, or contain malicious code.</p><h2>3. File Processing</h2><p>Files uploaded to Embroidery Viewer are processed either directly in your browser or temporarily on our servers. Files are not stored permanently, shared, or backed up.</p><p>While we aim to keep your content secure, you acknowledge that no system is 100% secure and you use the service at your own risk.</p><h2>4. No Warranty</h2><p>This service is provided \"as is\" and \"as available\" without any warranties, express or implied. We do not guarantee that the service will be uninterrupted, secure, or error-free.</p><h2>5. Limitation of Liability</h2><p>Embroidery Viewer shall not be held liable for any damages resulting from the use or inability to use the service, including but not limited to loss of data, loss of profits, or other incidental or consequential damages.</p><h2>6. Modifications to the Service</h2><p>We reserve the right to modify, suspend, or discontinue the service at any time without notice. We may also update these Terms of Service from time to time. Continued use of the service after changes constitutes your acceptance of the new terms.</p><h2>7. Governing Law</h2><p>These Terms shall be governed by and interpreted in accordance with the laws of Brazil, without regard to its conflict of law principles.</p><h2>8. Contact</h2><p>If you have any questions about these Terms of Service, feel free to contact us at <a href=\"mailto:leo@leomurca.xyz\">leo@leomurca.xyz</a>.</p>",
|
"content": "<p>Welcome to <strong>Embroidery Viewer</strong> (<a href=\"https://embroideryviewer.xyz\">embroideryviewer.xyz</a>). By accessing or using this website, you agree to be bound by the following Terms of Service. If you do not agree with any part of these terms, please do not use the site.</p><h2>1. Description of Service</h2><p>Embroidery Viewer is a free, browser-based tool that allows users to preview embroidery design files online. The service is intended for personal, non-commercial use.</p><h2>2. Use of the Service</h2><p>You agree to use the service only for lawful purposes. You are solely responsible for any content (including embroidery files) you upload, and you confirm that you have the legal right to use, view, and process those files.</p><p>You agree not to upload any files that are illegal, offensive, infringe on intellectual property rights, or contain malicious code.</p><h2>3. File Processing</h2><p>Files uploaded to Embroidery Viewer are processed either directly in your browser or temporarily on our servers. Files are not stored permanently, shared, or backed up.</p><p>While we aim to keep your content secure, you acknowledge that no system is 100% secure and you use the service at your own risk.</p><h2>4. No Warranty</h2><p>This service is provided \"as is\" and \"as available\" without any warranties, express or implied. We do not guarantee that the service will be uninterrupted, secure, or error-free.</p><h2>5. Limitation of Liability</h2><p>Embroidery Viewer shall not be held liable for any damages resulting from the use or inability to use the service, including but not limited to loss of data, loss of profits, or other incidental or consequential damages.</p><h2>6. Modifications to the Service</h2><p>We reserve the right to modify, suspend, or discontinue the service at any time without notice. We may also update these Terms of Service from time to time. Continued use of the service after changes constitutes your acceptance of the new terms.</p><h2>7. Governing Law</h2><p>These Terms shall be governed by and interpreted in accordance with the laws of Brazil, without regard to its conflict of law principles.</p><h2>8. Contact</h2><p>If you have any questions about these Terms of Service, feel free to contact us at <a href=\"mailto:leo@leomurca.xyz\">leo@leomurca.xyz</a>.</p>",
|
||||||
"seo.title": "📝 Terms of Service - Embroidery Viewer",
|
"seo.title": "Terms of Service - Embroidery Viewer",
|
||||||
"seo.description": "Read the Terms of Service for Embroidery Viewer. Personal use, upload rules, file processing, warranty disclaimers, liability limitations, and governing law.",
|
"seo.description": "Read the Terms of Service for Embroidery Viewer. Personal use, upload rules, file processing, warranty disclaimers, liability limitations, and governing law.",
|
||||||
"seo.keywords": "terms of service, terms of use, personal use, file upload, file processing, warranty disclaimer, liability limitation, Brazilian law, embroideryviewer.xyz",
|
"seo.keywords": "terms of service, terms of use, personal use, file upload, file processing, warranty disclaimer, liability limitation, Brazilian law, embroideryviewer.xyz",
|
||||||
"seo.url": "https://embroideryviewer.xyz/terms-of-service",
|
"seo.url": "https://embroideryviewer.xyz/terms-of-service",
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
"dimensions": "Dimensions (x, y)",
|
"dimensions": "Dimensions (x, y)",
|
||||||
"download": "Download image",
|
"download": "Download image",
|
||||||
"warning.copyright": "Do not upload copyrighted material you do not own or have rights to.",
|
"warning.copyright": "Do not upload copyrighted material you do not own or have rights to.",
|
||||||
"seo.title": "🧵 Free Online Embroidery File Viewer – Fast, Private & No Signup",
|
"seo.title": "Free Online Embroidery File Viewer – Fast, Private & No Signup",
|
||||||
"seo.description": "Upload and preview your embroidery files instantly with Embroidery Viewer. Supports DST, PES, JEF, EXP, VP3, and more. No installs, no uploads – 100% browser-based and free.",
|
"seo.description": "Upload and preview your embroidery files instantly with Embroidery Viewer. Supports DST, PES, JEF, EXP, VP3, and more. No installs, no uploads – 100% browser-based and free.",
|
||||||
"seo.keywords": "embroidery viewer, online embroidery viewer, embroidery file preview, DST viewer, PES viewer, free embroidery tool, JEF viewer, EXP embroidery, VP3 embroidery viewer, embroidery preview tool, browser embroidery renderer, convert embroidery to PNG",
|
"seo.keywords": "embroidery viewer, online embroidery viewer, embroidery file preview, DST viewer, PES viewer, free embroidery tool, JEF viewer, EXP embroidery, VP3 embroidery viewer, embroidery preview tool, browser embroidery renderer, convert embroidery to PNG",
|
||||||
"seo.url": "https://embroideryviewer.xyz/viewer",
|
"seo.url": "https://embroideryviewer.xyz/viewer",
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,9 @@ const config = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
locale: SUPPORTED_LOCALES.EN_US,
|
locale: SUPPORTED_LOCALES.EN_US,
|
||||||
key: 'donate',
|
key: 'support-us',
|
||||||
routes: ['/donate'],
|
routes: ['/support-us'],
|
||||||
loader: async () => (await import('./en-US/donate.json')).default,
|
loader: async () => (await import('./en-US/support-us.json')).default,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
locale: SUPPORTED_LOCALES.EN_US,
|
locale: SUPPORTED_LOCALES.EN_US,
|
||||||
|
|
@ -53,11 +53,12 @@ const config = {
|
||||||
routes: ['/privacy-policy'],
|
routes: ['/privacy-policy'],
|
||||||
loader: async () => (await import('./en-US/privacy-policy.json')).default,
|
loader: async () => (await import('./en-US/privacy-policy.json')).default,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
locale: SUPPORTED_LOCALES.EN_US,
|
locale: SUPPORTED_LOCALES.EN_US,
|
||||||
key: 'mobile.app.privacy.policy',
|
key: 'mobile.app.privacy.policy',
|
||||||
routes: ['/mobile-app/privacy-policy'],
|
routes: ['/mobile-app/privacy-policy'],
|
||||||
loader: async () => (await import('./en-US/mobile-app-privacy-policy.json')).default,
|
loader: async () =>
|
||||||
|
(await import('./en-US/mobile-app-privacy-policy.json')).default,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
locale: SUPPORTED_LOCALES.EN_US,
|
locale: SUPPORTED_LOCALES.EN_US,
|
||||||
|
|
@ -96,9 +97,9 @@ const config = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
locale: SUPPORTED_LOCALES.PT_BR,
|
locale: SUPPORTED_LOCALES.PT_BR,
|
||||||
key: 'donate',
|
key: 'support-us',
|
||||||
routes: ['/donate'],
|
routes: ['/support-us'],
|
||||||
loader: async () => (await import('./pt-BR/donate.json')).default,
|
loader: async () => (await import('./pt-BR/support-us.json')).default,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
locale: SUPPORTED_LOCALES.PT_BR,
|
locale: SUPPORTED_LOCALES.PT_BR,
|
||||||
|
|
@ -110,7 +111,8 @@ const config = {
|
||||||
locale: SUPPORTED_LOCALES.PT_BR,
|
locale: SUPPORTED_LOCALES.PT_BR,
|
||||||
key: 'mobile.app.privacy.policy',
|
key: 'mobile.app.privacy.policy',
|
||||||
routes: ['/mobile-app/privacy-policy'],
|
routes: ['/mobile-app/privacy-policy'],
|
||||||
loader: async () => (await import('./pt-BR/mobile-app-privacy-policy.json')).default,
|
loader: async () =>
|
||||||
|
(await import('./pt-BR/mobile-app-privacy-policy.json')).default,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
locale: SUPPORTED_LOCALES.PT_BR,
|
locale: SUPPORTED_LOCALES.PT_BR,
|
||||||
|
|
@ -125,6 +127,46 @@ const config = {
|
||||||
routes: ['/viewer'],
|
routes: ['/viewer'],
|
||||||
loader: async () => (await import('./pt-BR/viewer.json')).default,
|
loader: async () => (await import('./pt-BR/viewer.json')).default,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
locale: SUPPORTED_LOCALES.PT_BR,
|
||||||
|
key: 'hero',
|
||||||
|
loader: async () => (await import('./pt-BR/hero.json')).default,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
locale: SUPPORTED_LOCALES.EN_US,
|
||||||
|
key: 'hero',
|
||||||
|
loader: async () => (await import('./en-US/hero.json')).default,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
locale: SUPPORTED_LOCALES.PT_BR,
|
||||||
|
key: 'features',
|
||||||
|
loader: async () => (await import('./pt-BR/features.json')).default,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
locale: SUPPORTED_LOCALES.EN_US,
|
||||||
|
key: 'features',
|
||||||
|
loader: async () => (await import('./en-US/features.json')).default,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
locale: SUPPORTED_LOCALES.PT_BR,
|
||||||
|
key: 'faq',
|
||||||
|
loader: async () => (await import('./pt-BR/faq.json')).default,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
locale: SUPPORTED_LOCALES.EN_US,
|
||||||
|
key: 'faq',
|
||||||
|
loader: async () => (await import('./en-US/faq.json')).default,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
locale: SUPPORTED_LOCALES.PT_BR,
|
||||||
|
key: 'mobile',
|
||||||
|
loader: async () => (await import('./pt-BR/mobile.json')).default,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
locale: SUPPORTED_LOCALES.EN_US,
|
||||||
|
key: 'mobile',
|
||||||
|
loader: async () => (await import('./en-US/mobile.json')).default,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,34 @@
|
||||||
{
|
{
|
||||||
"title": "ℹ Sobre o Embroidery Viewer",
|
"seo.title": "Sobre o Embroidery Viewer - Por que esta ferramenta foi criada",
|
||||||
"content": "<p>Oi! 👋</p><p><strong>⭐️ Embroidery Viewer</strong> nasceu de uma necessidade simples — ajudar alguém que eu amo. 💖</p><p>Minha namorada adora bordado, mas ela sempre teve dificuldades para encontrar uma maneira fácil e gratuita de visualizar os arquivos de design de bordado antes de começar a costurar. A maioria das ferramentas que ela tentou eram pagas, muito complexas ou exigiam conhecimento técnico — e ela não é da área de tecnologia.</p><p>Então, para facilitar a vida dela (e de outras pessoas como ela), decidi criar este aplicativo web.</p><p>Ao longo de algumas semanas, criei o <strong>Embroidery Viewer</strong> — uma ferramenta leve, rápida e gratuita que permite visualizar arquivos de bordado diretamente no navegador. Sem instalação, sem configuração e sem obstáculos técnicos. Basta enviar o arquivo e ver o design.</p><p>Não é uma ferramenta super sofisticada, mas resolve o problema para o qual foi criada: tornar a visualização de arquivos de bordado acessível para todos.</p><p>Se essa ferramenta também te ajudou, isso me deixa muito feliz! Pretendo continuar melhorando com base no feedback de usuários como você.</p><p>Obrigado por visitar — e bons bordados! 🧵✨</p>",
|
|
||||||
"seo.title": "ℹSobre o Embroidery Viewer - Por que esta ferramenta foi criada",
|
|
||||||
"seo.description": "Conheça a história por trás do Embroidery Viewer — uma ferramenta gratuita e online criada para tornar a visualização de arquivos de bordado simples, rápida e acessível a todos.",
|
"seo.description": "Conheça a história por trás do Embroidery Viewer — uma ferramenta gratuita e online criada para tornar a visualização de arquivos de bordado simples, rápida e acessível a todos.",
|
||||||
"seo.keywords": "sobre embroidery viewer, história do embroidery viewer, visualizador de bordado gratuito, motivo da criação do embroidery viewer, quem criou o embroidery viewer, visualizador online de bordado, ferramenta gratuita para bordado, embroidery viewer sobre",
|
"seo.keywords": "sobre embroidery viewer, história do embroidery viewer, visualizador de bordado gratuito, motivo da criação do embroidery viewer, quem criou o embroidery viewer, visualizador online de bordado, ferramenta gratuita para bordado, embroidery viewer sobre",
|
||||||
"seo.url": "https://embroideryviewer.xyz/about",
|
"seo.url": "https://embroideryviewer.xyz/about",
|
||||||
"seo.image": "https://embroideryviewer.xyz/og/about.png"
|
"seo.image": "https://embroideryviewer.xyz/og/about.png",
|
||||||
|
"hero": {
|
||||||
|
"tagline": "UM PEQUENO ATO DE AMOR",
|
||||||
|
"title": "Que se tornou uma forma poderosa de visualizar arquivos de bordado"
|
||||||
|
},
|
||||||
|
"story": {
|
||||||
|
"title": "A história por trás do Embroidery Viewer...",
|
||||||
|
"p1": "Não começou como um produto. Começou com uma frustração simples.",
|
||||||
|
"p2": "Alguém que eu amo gosta de bordado. Ela adora criar, explorar designs e dar vida às ideias através dos pontos. Mas toda vez que baixava um novo arquivo de bordado, enfrentava o mesmo problema:",
|
||||||
|
"quote1": "“Como eu abro isso?”",
|
||||||
|
"p3": "A maioria das ferramentas disponíveis eram pagas, complexas demais ou feitas para pessoas que já entendiam softwares técnicos de bordado. Para alguém que só queria visualizar rapidamente um design antes de bordar, era complicado demais.",
|
||||||
|
"p4": "O que deveria ser um passo simples — apenas ver o design — se tornava uma barreira. Foi aí que percebi algo importante:",
|
||||||
|
"quote2": "Provavelmente existem muitas pessoas passando exatamente pelo mesmo problema."
|
||||||
|
},
|
||||||
|
"product": {
|
||||||
|
"p1": "As pessoas não querem softwares complicados.",
|
||||||
|
"p2": "As pessoas não querem instalar nada.",
|
||||||
|
"p3": "As pessoas só querem abrir um arquivo de bordado e ver o design na hora.",
|
||||||
|
"p4": "Então, em vez de procurar uma ferramenta melhor…",
|
||||||
|
"quote": "Eu decidi criar uma."
|
||||||
|
},
|
||||||
|
"support": {
|
||||||
|
"title": "Se te ajudou, você pode ajudar a crescer",
|
||||||
|
"p1": "O Embroidery Viewer é completamente gratuito — e eu pretendo manter assim.",
|
||||||
|
"p2": "Mas manter ele rápido, melhorar o suporte a mais formatos e sustentar a infraestrutura exige tempo e recursos.",
|
||||||
|
"p3": "Se essa ferramenta facilitou seu fluxo, ajudou você a visualizar um design ou evitou trabalho desnecessário, você pode apoiar o futuro dela.",
|
||||||
|
"cta": "Apoiar agora"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"title": "💖 Doe",
|
|
||||||
"subtitle": "Ajude a apoiar o Embroidery Viewer e seu desenvolvimento!",
|
|
||||||
"description": "⭐️ O <strong>Embroidery Viewer</strong> é gratuito. Se você achar esta ferramenta útil, considere fazer uma doação para mantê-la funcionando e financiar melhorias futuras.",
|
|
||||||
"ways": "💸 Formas de doar",
|
|
||||||
"bitcoin.description": "Escaneie ou copie o endereço",
|
|
||||||
"copy": "Copiar Endereço",
|
|
||||||
"copied": "Copiado para a área de transferência!",
|
|
||||||
"copy.failed": "Falha na Cópia!",
|
|
||||||
"monero.description": "Opção de doação privada e segura.",
|
|
||||||
"paypal.description": "Quer demonstrar apoio de uma forma amigável?",
|
|
||||||
"paypal.link": "Abrir Link de Doação",
|
|
||||||
"seo.title": "💖 Doe – Apoie o Embroidery Viewer",
|
|
||||||
"seo.description": "Ajude a manter o Embroidery Viewer gratuito e em constante melhoria fazendo uma doação. Escolha entre Bitcoin, Monero, PayPal ou outras opções seguras para apoiar o desenvolvimento e hospedagem.",
|
|
||||||
"seo.keywords": "doar embroidery viewer, apoie embroidery viewer, doações embroidery viewer, ajudar embroidery viewer, financiar embroidery viewer, doação bitcoin embroidery, doação monero embroidery, doação paypal embroidery",
|
|
||||||
"url": "https://embroideryviewer.xyz/doar",
|
|
||||||
"image": "https://embroideryviewer.xyz/og/doar.png"
|
|
||||||
}
|
|
||||||
30
src/lib/translations/pt-BR/faq.json
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"title": "Perguntas Frequentes",
|
||||||
|
"intro": "Saiba como abrir arquivos de bordado online, quais formatos são suportados e como o Embroidery Viewer funciona — tudo em um só lugar.",
|
||||||
|
"items": {
|
||||||
|
"openPesOnline": {
|
||||||
|
"summary": "Como posso abrir um arquivo PES online?",
|
||||||
|
"description": "Você pode abrir um arquivo PES instantaneamente usando o Embroidery Viewer. Basta arrastar e soltar o arquivo no navegador para visualizar o design de bordado — sem precisar instalar nenhum software."
|
||||||
|
},
|
||||||
|
"supportedFormats": {
|
||||||
|
"summary": "Quais formatos de arquivos de bordado são suportados?",
|
||||||
|
"description": "O Embroidery Viewer suporta formatos populares como PES, DST e EXP. Isso permite visualizar a maioria dos designs de bordado usados em máquinas domésticas e industriais."
|
||||||
|
},
|
||||||
|
"needSoftware": {
|
||||||
|
"summary": "Preciso instalar algum software de bordado?",
|
||||||
|
"description": "Não. O Embroidery Viewer funciona totalmente no navegador, permitindo abrir arquivos de bordado online sem precisar baixar ou instalar nada."
|
||||||
|
},
|
||||||
|
"isSafe": {
|
||||||
|
"summary": "É seguro usar o Embroidery Viewer?",
|
||||||
|
"description": "Sim. Seus arquivos são processados localmente no seu navegador e nunca são enviados para nenhum servidor, garantindo total privacidade."
|
||||||
|
},
|
||||||
|
"multipleFiles": {
|
||||||
|
"summary": "Posso visualizar vários arquivos de bordado ao mesmo tempo?",
|
||||||
|
"description": "Sim. Você pode abrir e comparar vários arquivos simultaneamente em uma única visualização, facilitando a análise e escolha dos designs."
|
||||||
|
},
|
||||||
|
"mobileSupport": {
|
||||||
|
"summary": "Posso usar no celular ou tablet?",
|
||||||
|
"description": "Sim. O Embroidery Viewer funciona em desktop, tablet e celular, desde que você utilize um navegador moderno."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/lib/translations/pt-BR/features.json
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"title": "A maneira mais fácil de visualizar arquivos de bordado",
|
||||||
|
"subtitle": "Seja você um entusiasta trabalhando no seu próximo projeto DIY ou um digitizador profissional revisando arquivos de clientes, o Embroidery Viewer oferece uma forma rápida e simples de visualizar designs de bordado online — sem software, sem complicação.",
|
||||||
|
"cards": {
|
||||||
|
"fast": {
|
||||||
|
"adjective": "RÁPIDO",
|
||||||
|
"title": "Visualização instantânea",
|
||||||
|
"description": "Arraste seu arquivo e veja o design na hora — sem instalar nada, sem esperar."
|
||||||
|
},
|
||||||
|
"private": {
|
||||||
|
"adjective": "PRIVADO",
|
||||||
|
"title": "Privacidade em primeiro lugar",
|
||||||
|
"description": "Seus arquivos ficam no seu dispositivo. Nada é enviado para a internet."
|
||||||
|
},
|
||||||
|
"optimized": {
|
||||||
|
"adjective": "OTIMIZADO",
|
||||||
|
"title": "Vários arquivos, uma visão",
|
||||||
|
"description": "Abra e compare vários designs ao mesmo tempo em um layout limpo."
|
||||||
|
},
|
||||||
|
"compatibility": {
|
||||||
|
"adjective": "COMPATÍVEL",
|
||||||
|
"title": "Suporta formatos populares",
|
||||||
|
"description": "Funciona com PES, DST, EXP e outros formatos comuns de bordado."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cta": "Experimente agora"
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,15 @@
|
||||||
{
|
{
|
||||||
"about": "ℹ Sobre",
|
"slogan": "Visualize bordados instantaneamente — sem software",
|
||||||
"privacy.policy": "🔐 Política de Privacidade",
|
"resources": "Recursos",
|
||||||
"terms.of.service": "📝 Termos de Serviço",
|
"contact-title": "Contato",
|
||||||
"copyright": "Copyright © {{year}} <a href=\"{{website}}/pt-br\" target=\"_blank\" rel=\"noreferrer\">Leonardo Murça</a>. <br/> Todos os direitos reservados.",
|
"contact-description": "Quer ajudar ou tirar alguma dúvida? Contate-nos.",
|
||||||
"version": "🧵 Versão: {{version}}"
|
"back-to-top": {
|
||||||
|
"label": "Voltar ao topo",
|
||||||
|
"aria-label": "Voltar ao topo da página"
|
||||||
|
},
|
||||||
|
"about": "Sobre",
|
||||||
|
"privacy.policy": "Política de Privacidade",
|
||||||
|
"terms.of.service": "Termos de Serviço",
|
||||||
|
"copyright": "Copyright © {{year}} <a href=\"{{website}}/pt-br\" target=\"_blank\" rel=\"noreferrer\">Leonardo Murça</a>. Todos os direitos reservados.",
|
||||||
|
"version": "Versão {{version}}"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"languageSwitch": "🇺🇸",
|
"homeNav": "Página Inicial",
|
||||||
"homeNav": "🏠 Página Inicial",
|
"aboutNav": "Sobre",
|
||||||
"aboutNav": "ℹ Sobre",
|
"viewerNav": "Visualizador",
|
||||||
"viewerNav": "🧵 Visualizador",
|
"supportUsNav": "Apoie-nos"
|
||||||
"donateNav": "💖 Doe"
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
5
src/lib/translations/pt-BR/hero.json
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"title": "Visualize bordados instantaneamente — sem software",
|
||||||
|
"description": "Rápido, privado & sem necessidade de cadastro.",
|
||||||
|
"cta": "Teste Agora"
|
||||||
|
}
|
||||||
|
|
@ -1,31 +1,7 @@
|
||||||
{
|
{
|
||||||
"main.title": "🧵 Visualizador de arquivos de bordado online gratuito",
|
"seo.title": "Visualizador de Bordado Online Grátis - Rápido, Privado e Sem Cadastro",
|
||||||
"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>",
|
|
||||||
"features.title": "🚀 Funcionalidades",
|
|
||||||
"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>",
|
|
||||||
"howtouse.title": "📘 Como usar",
|
|
||||||
"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>",
|
|
||||||
"testimonials.title": "❤️ Amado por Hobbyistas e Profissionais",
|
|
||||||
"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>",
|
|
||||||
"donation.title": "💖 Ajude a mantê-lo gratuito",
|
|
||||||
"donation.description": "<p><strong>O Embroidery Viewer é totalmente gratuito</strong> para todos usarem.</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>",
|
|
||||||
"donation.cta": "🙌 Doe agora",
|
|
||||||
"donation.cta.description": "cada pequena ajuda é bem-vinda!",
|
|
||||||
"cta.title": "🚀 Experimente agora",
|
|
||||||
"cta.cta": "🧵 Abrir visualizador",
|
|
||||||
"cta.cta.description": "o <strong>visualizador de arquivos de bordado online gratuito</strong> mais rápido.",
|
|
||||||
"seo.title": "🏠 Visualizador de Bordado Online Grátis - Rápido, Privado e Sem Cadastro",
|
|
||||||
"seo.description": "Envie e visualize arquivos de bordado instantaneamente com o Embroidery Viewer. Compatível com DST, PES, JEF, EXP, VP3 e mais. Sem instalações, sem uploads – 100% no navegador e gratuito.",
|
"seo.description": "Envie e visualize arquivos de bordado instantaneamente com o Embroidery Viewer. Compatível com DST, PES, JEF, EXP, VP3 e mais. Sem instalações, sem uploads – 100% no navegador e gratuito.",
|
||||||
"seo.keywords": "visualizador de bordado, visualizador online de bordado, visualizar arquivos de bordado, visualizar DST, visualizar PES, ferramenta gratuita de bordado, visualizador JEF, bordado EXP, visualizador VP3, pré-visualização de bordado, renderizador de bordado no navegador, converter bordado em PNG",
|
"seo.keywords": "visualizador de bordado, visualizador online de bordado, visualizar arquivos de bordado, visualizar DST, visualizar PES, ferramenta gratuita de bordado, visualizador JEF, bordado EXP, visualizador VP3, pré-visualização de bordado, renderizador de bordado no navegador, converter bordado em PNG",
|
||||||
"seo.url": "https://embroideryviewer.xyz",
|
"seo.url": "https://embroideryviewer.xyz",
|
||||||
"seo.image": "https://embroideryviewer.xyz/og/",
|
"seo.image": "https://embroideryviewer.xyz/og/"
|
||||||
"banner.title": "🚀 Seja um Testador Beta",
|
|
||||||
"banner.subtitle": "Estamos lançando o aplicativo <strong style=\"color: white\">Embroidery Viewer para Android</strong> — e você pode ser um dos primeiros a experimentá-lo!",
|
|
||||||
"banner.description": "Desfrute de uma experiência fluida e sem anúncios para visualizar arquivos de bordado PES, JEF, PEC, VP3, DST e EXP diretamente do seu celular. Ajude-nos a melhorá-lo e obtenha acesso antecipado antes de todo mundo.",
|
|
||||||
"banner.name": "Nome",
|
|
||||||
"banner.email": "E-mail da sua conta Google (usada na Play Store)",
|
|
||||||
"banner.cta": "Seja um Testador",
|
|
||||||
"banner.cta.loading": "Enviando...",
|
|
||||||
"banner.feedback.success": "Nome e e-mail enviados com sucesso! Entraremos em contato em breve!",
|
|
||||||
"banner.feedback.error": "Algo deu errado. Tente enviar seu nome e e-mail diretamente para leo@leomurca.xyz."
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"title": "🔐 Política de Privacidade",
|
"title": "Política de Privacidade",
|
||||||
"last.update": "Última atualização: 11 de julho de 2025",
|
"last.update": "Última atualização: 11 de julho de 2025",
|
||||||
"content": "<h1>Política de Privacidade</h1><p>Obrigado por usar o aplicativo Embroidery Viewer Companion. Sua privacidade é importante para nós. Esta Política de Privacidade explica como lidamos com seus dados.</p><h2>1. Nenhum dado pessoal coletado</h2><p>O aplicativo não coleta, armazena ou transmite nenhum dado pessoal. Todos os seus arquivos de bordado são processados localmente no seu dispositivo. Não acessamos nem enviamos seus arquivos para lugar algum.</p><h2>2. Permissões de armazenamento</h2><p>O aplicativo solicita acesso aos seus arquivos apenas para que você possa abrir e visualizar arquivos de bordado armazenados no seu dispositivo. Essa permissão é usada exclusivamente para esse fim.</p><h2>3. Personalização com nome</h2><p>Se você optar por informar seu nome, ele será armazenado localmente no seu dispositivo para personalizar as saudações (como \"Bom dia, Leo\"). Esta informação não é compartilhada e nunca é enviada pela internet.</p><h2>4. Sem análises ou anúncios</h2><p>Este aplicativo não utiliza ferramentas de análise nem exibe anúncios. Acreditamos em uma experiência respeitosa e sem distrações.</p><h2>5. Dúvidas</h2><p>Se você tiver alguma dúvida sobre esta política ou sobre o aplicativo, entre em contato conosco pelo e-mail: <a href=\"mailto:leo@leomurca.xyz\">leo@leomurca.xyz</a></p>",
|
"content": "<h1>Política de Privacidade</h1><p>Obrigado por usar o aplicativo Embroidery Viewer Companion. Sua privacidade é importante para nós. Esta Política de Privacidade explica como lidamos com seus dados.</p><h2>1. Nenhum dado pessoal coletado</h2><p>O aplicativo não coleta, armazena ou transmite nenhum dado pessoal. Todos os seus arquivos de bordado são processados localmente no seu dispositivo. Não acessamos nem enviamos seus arquivos para lugar algum.</p><h2>2. Permissões de armazenamento</h2><p>O aplicativo solicita acesso aos seus arquivos apenas para que você possa abrir e visualizar arquivos de bordado armazenados no seu dispositivo. Essa permissão é usada exclusivamente para esse fim.</p><h2>3. Personalização com nome</h2><p>Se você optar por informar seu nome, ele será armazenado localmente no seu dispositivo para personalizar as saudações (como \"Bom dia, Leo\"). Esta informação não é compartilhada e nunca é enviada pela internet.</p><h2>4. Sem análises ou anúncios</h2><p>Este aplicativo não utiliza ferramentas de análise nem exibe anúncios. Acreditamos em uma experiência respeitosa e sem distrações.</p><h2>5. Dúvidas</h2><p>Se você tiver alguma dúvida sobre esta política ou sobre o aplicativo, entre em contato conosco pelo e-mail: <a href=\"mailto:leo@leomurca.xyz\">leo@leomurca.xyz</a></p>",
|
||||||
"seo.title": "🔐 Política de Privacidade - Embroidery Viewer Companion App",
|
"seo.title": "Política de Privacidade - Embroidery Viewer Companion App",
|
||||||
"seo.description": "Saiba como o Embroidery Viewer respeita sua privacidade. Nenhum dado pessoal é coletado, arquivos processados localmente ou temporariamente, análises anônimas, sem cookies ou rastreadores.",
|
"seo.description": "Saiba como o Embroidery Viewer respeita sua privacidade. Nenhum dado pessoal é coletado, arquivos processados localmente ou temporariamente, análises anônimas, sem cookies ou rastreadores.",
|
||||||
"seo.keywords": "política de privacidade, proteção de dados, privacidade embroidery viewer, upload de arquivos, análises anônimas, sem cookies, privacidade do usuário, análises que respeitam a privacidade, segurança de dados, embroideryviewer.xyz",
|
"seo.keywords": "política de privacidade, proteção de dados, privacidade embroidery viewer, upload de arquivos, análises anônimas, sem cookies, privacidade do usuário, análises que respeitam a privacidade, segurança de dados, embroideryviewer.xyz",
|
||||||
"seo.url": "https://embroideryviewer.xyz/mobile-app/privacy-policy",
|
"seo.url": "https://embroideryviewer.xyz/mobile-app/privacy-policy",
|
||||||
|
|
|
||||||
17
src/lib/translations/pt-BR/mobile.json
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"title": {
|
||||||
|
"prefix": "Embroidery Viewer no ",
|
||||||
|
"highlight": "Android"
|
||||||
|
},
|
||||||
|
"description": "Visualize e explore seus arquivos de bordado diretamente no celular — rápido, simples e feito para o uso real.",
|
||||||
|
"features": {
|
||||||
|
"formats": "Suporte para formatos PES, JEF, PEC, VP3, DST e EXP",
|
||||||
|
"customization": "Personalize cores de fundo e das linhas",
|
||||||
|
"highlight": "Toque para destacar sequências de cores",
|
||||||
|
"metadata": "Veja pontos, tamanho e detalhes das cores",
|
||||||
|
"performance": "Rápido, leve e funciona offline",
|
||||||
|
"accessibility": "Acessível e fácil de usar"
|
||||||
|
},
|
||||||
|
"cta_alt": "Baixar na Google Play",
|
||||||
|
"image_alt": "Prévia do Embroidery Viewer no Android"
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"title": "🔐 Política de Privacidade",
|
"title": "Política de Privacidade",
|
||||||
"last.update": "Última atualização: 9 de maio de 2025",
|
"last.update": "Última atualização: 24 de abril de 2026",
|
||||||
"content": "<p>No <strong>Embroidery Viewer</strong> (<a href=\"https://embroideryviewer.xyz\">embroideryviewer.xyz</a>), respeitamos sua privacidade e estamos comprometidos em proteger qualquer informação que você compartilhe ao usar nosso serviço.</p><h2>1. Informações Pessoais</h2><p>O Embroidery Viewer <strong>não</strong> coleta nem armazena informações pessoais. Você não precisa criar uma conta e não pedimos seu nome, e-mail ou qualquer dado identificável.</p><h2>2. Envio de Arquivos</h2><p>Quando você envia um arquivo de bordado para o visualizador, o arquivo é processado no seu navegador ou temporariamente em nosso servidor (se necessário) apenas para fins de visualização. <strong>Nenhum arquivo enviado é armazenado, salvo ou compartilhado.</strong></p><p>Evite enviar materiais sensíveis ou protegidos por direitos autorais, a menos que tenha permissão para usá-los.</p><h2>3. Análises</h2><p>Utilizamos o <strong>Umami</strong> para coletar estatísticas anônimas de uso do site, como número de visitantes, visualizações de página, tipos de dispositivo e fontes de acesso. Esses dados nos ajudam a entender como o site está sendo utilizado e melhorá-lo com o tempo.</p><p>O Umami é uma ferramenta de análise que respeita a privacidade, não usa cookies e não rastreia os usuários entre sites. Todos os dados são agregados e anonimizados.</p><h2>4. Cookies</h2><p>O Embroidery Viewer <strong>não</strong> utiliza cookies ou outros mecanismos de rastreamento em seu navegador.</p><h2>5. Serviços de Terceiros</h2><p>Não utilizamos publicidade de terceiros, nem incorporamos rastreadores externos, nem compartilhamos dados com terceiros.</p><h2>6. Alterações nesta Política</h2><p>Podemos atualizar esta Política de Privacidade ocasionalmente. Todas as atualizações serão publicadas nesta página com a data de modificação.</p><h2>7. Contato</h2><p>Se você tiver dúvidas sobre esta Política de Privacidade, entre em contato pelo e-mail <a href=\"mailto:leo@leomurca.xyz\">leo@leomurca.xyz</a>.</p>",
|
"content": "<p>No <strong>Embroidery Viewer</strong> (<a href=\"https://embroideryviewer.xyz\">embroideryviewer.xyz</a>), respeitamos sua privacidade e estamos comprometidos em proteger qualquer informação que você compartilhe ao usar nosso serviço.</p><h2>1. Informações Pessoais</h2><p>O Embroidery Viewer <strong>não</strong> coleta nem armazena informações pessoais. Você não precisa criar uma conta e não pedimos seu nome, e-mail ou qualquer dado identificável.</p><h2>2. Envio de Arquivos</h2><p>Quando você envia um arquivo de bordado para o visualizador, o arquivo é processado no seu navegador ou temporariamente em nosso servidor (se necessário) apenas para fins de visualização. <strong>Nenhum arquivo enviado é armazenado, salvo ou compartilhado.</strong></p><p>Evite enviar materiais sensíveis ou protegidos por direitos autorais, a menos que tenha permissão para usá-los.</p><h2>3. Análises</h2><p>Utilizamos o <strong>HitKeep</strong> para coletar estatísticas anônimas de uso do site, como número de visitantes, visualizações de página, tipos de dispositivo e fontes de acesso. Esses dados nos ajudam a entender como o site está sendo utilizado e melhorá-lo com o tempo.</p><p>O HitKeep é uma ferramenta de análise que respeita a privacidade, não usa cookies e não rastreia os usuários entre sites. Todos os dados são agregados e anonimizados.</p><h2>4. Cookies</h2><p>O Embroidery Viewer <strong>não</strong> utiliza cookies ou outros mecanismos de rastreamento em seu navegador.</p><h2>5. Serviços de Terceiros</h2><p>Não utilizamos publicidade de terceiros, nem incorporamos rastreadores externos, nem compartilhamos dados com terceiros.</p><h2>6. Alterações nesta Política</h2><p>Podemos atualizar esta Política de Privacidade ocasionalmente. Todas as atualizações serão publicadas nesta página com a data de modificação.</p><h2>7. Contato</h2><p>Se você tiver dúvidas sobre esta Política de Privacidade, entre em contato pelo e-mail <a href=\"mailto:leo@leomurca.xyz\">leo@leomurca.xyz</a>.</p>",
|
||||||
"seo.title": "🔐 Política de Privacidade - Embroidery Viewer",
|
"seo.title": "Política de Privacidade - Embroidery Viewer",
|
||||||
"seo.description": "Saiba como o Embroidery Viewer respeita sua privacidade. Nenhum dado pessoal é coletado, arquivos processados localmente ou temporariamente, análises anônimas, sem cookies ou rastreadores.",
|
"seo.description": "Saiba como o Embroidery Viewer respeita sua privacidade. Nenhum dado pessoal é coletado, arquivos processados localmente ou temporariamente, análises anônimas, sem cookies ou rastreadores.",
|
||||||
"seo.keywords": "política de privacidade, proteção de dados, privacidade embroidery viewer, upload de arquivos, análises anônimas, sem cookies, privacidade do usuário, análises que respeitam a privacidade, segurança de dados, embroideryviewer.xyz",
|
"seo.keywords": "política de privacidade, proteção de dados, privacidade embroidery viewer, upload de arquivos, análises anônimas, sem cookies, privacidade do usuário, análises que respeitam a privacidade, segurança de dados, embroideryviewer.xyz",
|
||||||
"seo.url": "https://embroideryviewer.xyz/privacy-policy",
|
"seo.url": "https://embroideryviewer.xyz/privacy-policy",
|
||||||
|
|
|
||||||
10
src/lib/translations/pt-BR/support-us.json
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"title": "Mantenha o bordado simples para todos",
|
||||||
|
"description": "O Embroidery Viewer economiza seu tempo e elimina a complexidade de softwares complicados. Seu apoio ajuda a mantê-lo <strong>gratuito, rápido e em constante evolução</strong> para todos.",
|
||||||
|
"cta": "Apoiar o projeto",
|
||||||
|
"seo.title": "Apoie o Embroidery Viewer",
|
||||||
|
"seo.description": "Ajude a manter o Embroidery Viewer gratuito e em constante evolução fazendo uma doação. Escolha entre Bitcoin, Monero, PayPal ou outras opções seguras para apoiar o desenvolvimento e a infraestrutura.",
|
||||||
|
"seo.keywords": "doar embroidery viewer, apoiar embroidery viewer, doações embroidery viewer, ajudar embroidery viewer, financiar embroidery viewer, doação bitcoin bordado, doação monero bordado, doação paypal bordado",
|
||||||
|
"url": "https://embroideryviewer.xyz/support-us",
|
||||||
|
"image": "https://embroideryviewer.xyz/og/donate.png"
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"title": "📝 Termos de Serviço",
|
"title": "Termos de Serviço",
|
||||||
"update": "Última atualização: 9 de maio de 2025",
|
"update": "Última atualização: 9 de maio de 2025",
|
||||||
"content": "<p>Bem-vindo ao <strong>Embroidery Viewer</strong> (<a href=\"https://embroideryviewer.xyz\">embroideryviewer.xyz</a>). Ao acessar ou utilizar este site, você concorda em estar vinculado aos seguintes Termos de Serviço. Se você não concordar com qualquer parte destes termos, por favor, não utilize o site.</p><h2>1. Descrição do Serviço</h2><p>O Embroidery Viewer é uma ferramenta gratuita baseada em navegador que permite aos usuários visualizar arquivos de design de bordado online. O serviço é destinado ao uso pessoal e não comercial.</p><h2>2. Uso do Serviço</h2><p>Você concorda em usar o serviço apenas para fins legais. Você é o único responsável por qualquer conteúdo (incluindo arquivos de bordado) que enviar, e confirma que tem o direito legal de usar, visualizar e processar esses arquivos.</p><p>Você concorda em não enviar arquivos que sejam ilegais, ofensivos, infrinjam direitos de propriedade intelectual ou contenham código malicioso.</p><h2>3. Processamento de Arquivos</h2><p>Os arquivos enviados para o Embroidery Viewer são processados diretamente em seu navegador ou temporariamente em nossos servidores. Os arquivos não são armazenados permanentemente, compartilhados ou backupados.</p><p>Embora tenhamos o objetivo de manter seu conteúdo seguro, você reconhece que nenhum sistema é 100% seguro e você utiliza o serviço por sua conta e risco.</p><h2>4. Sem Garantia</h2><p>Este serviço é fornecido \"como está\" e \"como disponível\", sem quaisquer garantias, expressas ou implícitas. Não garantimos que o serviço será ininterrupto, seguro ou sem erros.</p><h2>5. Limitação de Responsabilidade</h2><p>O Embroidery Viewer não será responsabilizado por quaisquer danos resultantes do uso ou da impossibilidade de usar o serviço, incluindo, mas não se limitando a, perda de dados, perda de lucros ou outros danos incidentais ou consequenciais.</p><h2>6. Modificações no Serviço</h2><p>Reservamo-nos o direito de modificar, suspender ou descontinuar o serviço a qualquer momento, sem aviso prévio. Podemos também atualizar estes Termos de Serviço de tempos em tempos. O uso contínuo do serviço após as mudanças constitui sua aceitação dos novos termos.</p><h2>7. Lei Aplicável</h2><p>Estes Termos serão regidos e interpretados de acordo com as leis do Brasil, sem levar em consideração seus princípios de conflitos de leis.</p><h2>8. Contato</h2><p>Se você tiver qualquer dúvida sobre estes Termos de Serviço, sinta-se à vontade para entrar em contato conosco pelo e-mail <a href=\"mailto:leo@leomurca.xyz\">leo@leomurca.xyz</a>.</p>",
|
"content": "<p>Bem-vindo ao <strong>Embroidery Viewer</strong> (<a href=\"https://embroideryviewer.xyz\">embroideryviewer.xyz</a>). Ao acessar ou utilizar este site, você concorda em estar vinculado aos seguintes Termos de Serviço. Se você não concordar com qualquer parte destes termos, por favor, não utilize o site.</p><h2>1. Descrição do Serviço</h2><p>O Embroidery Viewer é uma ferramenta gratuita baseada em navegador que permite aos usuários visualizar arquivos de design de bordado online. O serviço é destinado ao uso pessoal e não comercial.</p><h2>2. Uso do Serviço</h2><p>Você concorda em usar o serviço apenas para fins legais. Você é o único responsável por qualquer conteúdo (incluindo arquivos de bordado) que enviar, e confirma que tem o direito legal de usar, visualizar e processar esses arquivos.</p><p>Você concorda em não enviar arquivos que sejam ilegais, ofensivos, infrinjam direitos de propriedade intelectual ou contenham código malicioso.</p><h2>3. Processamento de Arquivos</h2><p>Os arquivos enviados para o Embroidery Viewer são processados diretamente em seu navegador ou temporariamente em nossos servidores. Os arquivos não são armazenados permanentemente, compartilhados ou backupados.</p><p>Embora tenhamos o objetivo de manter seu conteúdo seguro, você reconhece que nenhum sistema é 100% seguro e você utiliza o serviço por sua conta e risco.</p><h2>4. Sem Garantia</h2><p>Este serviço é fornecido \"como está\" e \"como disponível\", sem quaisquer garantias, expressas ou implícitas. Não garantimos que o serviço será ininterrupto, seguro ou sem erros.</p><h2>5. Limitação de Responsabilidade</h2><p>O Embroidery Viewer não será responsabilizado por quaisquer danos resultantes do uso ou da impossibilidade de usar o serviço, incluindo, mas não se limitando a, perda de dados, perda de lucros ou outros danos incidentais ou consequenciais.</p><h2>6. Modificações no Serviço</h2><p>Reservamo-nos o direito de modificar, suspender ou descontinuar o serviço a qualquer momento, sem aviso prévio. Podemos também atualizar estes Termos de Serviço de tempos em tempos. O uso contínuo do serviço após as mudanças constitui sua aceitação dos novos termos.</p><h2>7. Lei Aplicável</h2><p>Estes Termos serão regidos e interpretados de acordo com as leis do Brasil, sem levar em consideração seus princípios de conflitos de leis.</p><h2>8. Contato</h2><p>Se você tiver qualquer dúvida sobre estes Termos de Serviço, sinta-se à vontade para entrar em contato conosco pelo e-mail <a href=\"mailto:leo@leomurca.xyz\">leo@leomurca.xyz</a>.</p>",
|
||||||
"seo.title": "📝 Termos de Serviço - Embroidery Viewer",
|
"seo.title": "Termos de Serviço - Embroidery Viewer",
|
||||||
"seo.description": "Leia os Termos de Serviço do Embroidery Viewer. Uso pessoal, regras de upload, processamento de arquivos, isenção de garantias, limitações de responsabilidade e legislação aplicável.",
|
"seo.description": "Leia os Termos de Serviço do Embroidery Viewer. Uso pessoal, regras de upload, processamento de arquivos, isenção de garantias, limitações de responsabilidade e legislação aplicável.",
|
||||||
"seo.keywords": "termos de serviço, condições de uso, uso pessoal, upload de arquivos, processamento de arquivos, isenção de garantias, limitações de responsabilidade, legislação brasileira, embroideryviewer.xyz",
|
"seo.keywords": "termos de serviço, condições de uso, uso pessoal, upload de arquivos, processamento de arquivos, isenção de garantias, limitações de responsabilidade, legislação brasileira, embroideryviewer.xyz",
|
||||||
"seo.url": "https://embroideryviewer.xyz/termos-de-servico",
|
"seo.url": "https://embroideryviewer.xyz/termos-de-servico",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
{
|
{
|
||||||
"title": "Carregar arquivos",
|
"title": "Carregar arquivos",
|
||||||
"languageSwitch": "🇺🇸",
|
|
||||||
"fileSize": "O tamanho máximo de cada arquivo é <strong>{{fileSize}}MB</strong>.",
|
"fileSize": "O tamanho máximo de cada arquivo é <strong>{{fileSize}}MB</strong>.",
|
||||||
"supportedFormats": "Formatos aceitos: <strong>{{supportedFormats}}</strong>.",
|
"supportedFormats": "Formatos aceitos: <strong>{{supportedFormats}}</strong>.",
|
||||||
"render": "Renderizar arquivos",
|
"render": "Renderizar arquivos",
|
||||||
|
|
@ -12,7 +11,7 @@
|
||||||
"dimensions": "Dimensões (x, y)",
|
"dimensions": "Dimensões (x, y)",
|
||||||
"download": "Baixar imagem",
|
"download": "Baixar imagem",
|
||||||
"warning.copyright": "Não carregue material protegido por direitos autorais que você não possui ou sobre os quais não tenha direitos.",
|
"warning.copyright": "Não carregue material protegido por direitos autorais que você não possui ou sobre os quais não tenha direitos.",
|
||||||
"seo.title": "🧵 Visualizador Online Gratuito de Arquivos de Bordado – Rápido, Privado e Sem Cadastro",
|
"seo.title": "Visualizador Online Gratuito de Arquivos de Bordado – Rápido, Privado e Sem Cadastro",
|
||||||
"seo.description": "Faça upload e visualize seus arquivos de bordado instantaneamente com o Embroidery Viewer. Suporta DST, PES, JEF, EXP, VP3 e muito mais. Sem instalações, sem upload para servidor – 100% baseado no navegador e gratuito.",
|
"seo.description": "Faça upload e visualize seus arquivos de bordado instantaneamente com o Embroidery Viewer. Suporta DST, PES, JEF, EXP, VP3 e muito mais. Sem instalações, sem upload para servidor – 100% baseado no navegador e gratuito.",
|
||||||
"seo.keywords": "visualizador de bordado, visualizador online de bordado, pré-visualização de arquivos de bordado, visualizador DST, visualizador PES, ferramenta gratuita de bordado, visualizador JEF, bordado EXP, visualizador VP3, ferramenta de pré-visualização de bordado, renderizador de bordado no navegador, converter bordado para PNG",
|
"seo.keywords": "visualizador de bordado, visualizador online de bordado, pré-visualização de arquivos de bordado, visualizador DST, visualizador PES, ferramenta gratuita de bordado, visualizador JEF, bordado EXP, visualizador VP3, ferramenta de pré-visualização de bordado, renderizador de bordado no navegador, converter bordado para PNG",
|
||||||
"seo.url": "https://embroideryviewer.xyz/viewer",
|
"seo.url": "https://embroideryviewer.xyz/viewer",
|
||||||
|
|
|
||||||
9
src/lib/utils/isMobile.js
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { browser } from '$app/environment';
|
||||||
|
|
||||||
|
export const isMobile = () => {
|
||||||
|
if (browser) {
|
||||||
|
return window.matchMedia('only screen and (max-width: 768px)').matches;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
|
import '$lib/styles/fonts.css';
|
||||||
|
import '$lib/styles/variables.css';
|
||||||
|
import '$lib/styles/global.css';
|
||||||
|
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
|
@ -6,7 +10,9 @@
|
||||||
import Footer from '$lib/components/Footer.svelte';
|
import Footer from '$lib/components/Footer.svelte';
|
||||||
import Analytics from '$lib/components/Analytics.svelte';
|
import Analytics from '$lib/components/Analytics.svelte';
|
||||||
|
|
||||||
let mounted = false;
|
let { children } = $props();
|
||||||
|
|
||||||
|
let mounted = $state(false);
|
||||||
|
|
||||||
if (browser) {
|
if (browser) {
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
|
@ -20,7 +26,7 @@
|
||||||
{#if mounted}
|
{#if mounted}
|
||||||
<Header />
|
<Header />
|
||||||
<main>
|
<main>
|
||||||
<slot />
|
{@render children()}
|
||||||
</main>
|
</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -30,5 +36,6 @@
|
||||||
flex: 1; /* This pushes footer to bottom */
|
flex: 1; /* This pushes footer to bottom */
|
||||||
padding: 0;
|
padding: 0;
|
||||||
min-height: 90vh;
|
min-height: 90vh;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
/** @type {import('./$types').PageLoad} */
|
/** @type {import('./$types').PageLoad} */
|
||||||
export function load() {
|
export function load() {
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,33 @@
|
||||||
import { EMAIL_ACCESS_KEY, EMAIL_BASE_URL } from '$env/static/private';
|
import { EMAIL_ACCESS_KEY, EMAIL_BASE_URL } from '$env/static/private';
|
||||||
|
|
||||||
|
|
||||||
/** @type {import('./$types').Actions} */
|
/** @type {import('./$types').Actions} */
|
||||||
export const actions = {
|
export const actions = {
|
||||||
default: async ({ request }) => {
|
default: async ({ request }) => {
|
||||||
const formData = await request.formData();
|
const formData = await request.formData();
|
||||||
console.log(formData);
|
console.log(formData);
|
||||||
|
|
||||||
const response = await fetch(`${EMAIL_BASE_URL}/submit`, {
|
const response = await fetch(`${EMAIL_BASE_URL}/submit`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
accessKey: EMAIL_ACCESS_KEY,
|
accessKey: EMAIL_ACCESS_KEY,
|
||||||
subject: 'Contato - Embroidery Viewer Beta Testers!',
|
subject: 'Contato - Embroidery Viewer Beta Testers!',
|
||||||
name: formData.get('name'),
|
name: formData.get('name'),
|
||||||
email: formData.get('email'),
|
email: formData.get('email'),
|
||||||
}),
|
}),
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
if (json.error === undefined) {
|
if (json.error === undefined) {
|
||||||
return {
|
return {
|
||||||
message: "home.banner.feedback.success",
|
message: 'home.banner.feedback.success',
|
||||||
textColor: 'green'
|
textColor: 'green',
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
message: 'home.banner.feedback.error',
|
message: 'home.banner.feedback.error',
|
||||||
textColor: 'red'
|
textColor: 'red',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,271 +1,19 @@
|
||||||
<script>
|
<script>
|
||||||
// @ts-nocheck
|
|
||||||
|
|
||||||
import { applyAction, enhance } from '$app/forms';
|
|
||||||
import { invalidateAll } from '$app/navigation';
|
|
||||||
import { t, locale, SUPPORTED_LOCALES } from '$lib/translations';
|
|
||||||
|
|
||||||
import Seo from '$lib/components/Seo.svelte';
|
import Seo from '$lib/components/Seo.svelte';
|
||||||
import appScreenshot from '$lib/assets/app-with-frame.png';
|
import Hero from '$lib/sections/Hero.svelte';
|
||||||
import appScreenshotPt from '$lib/assets/app-with-frame-pt.png';
|
import Features from '$lib/sections/Features.svelte';
|
||||||
import { browser } from '$app/environment';
|
import Faq from '$lib/sections/Faq.svelte';
|
||||||
|
import MobileApp from '$lib/sections/MobileApp.svelte';
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
/**
|
|
||||||
* @type {{ textColor: String; message: String; } | null}
|
|
||||||
*/
|
|
||||||
let feedbackMessage = $state(null);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {boolean}
|
|
||||||
*/
|
|
||||||
let loading = $state(false);
|
|
||||||
|
|
||||||
|
// svelte-ignore state_referenced_locally
|
||||||
const metadata = data.metadata;
|
const metadata = data.metadata;
|
||||||
|
|
||||||
const resetFeedback = () => {
|
|
||||||
if (feedbackMessage) feedbackMessage = null
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Seo {...metadata} />
|
<Seo {...metadata} />
|
||||||
|
|
||||||
<div class="beta-section">
|
<Hero />
|
||||||
<div class="beta-content">
|
<Features />
|
||||||
<div class="beta-image">
|
<MobileApp />
|
||||||
|
<Faq />
|
||||||
<img src={$locale === SUPPORTED_LOCALES.EN_US ? appScreenshot : appScreenshotPt} alt="Embroidery Viewer App Screenshot" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="beta-text">
|
|
||||||
<h1>{$t("home.banner.title")}</h1>
|
|
||||||
<p class="lead">
|
|
||||||
{@html $t('home.banner.subtitle')}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{$t('home.banner.description')}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
|
|
||||||
<form
|
|
||||||
action="/"
|
|
||||||
method="POST"
|
|
||||||
class="beta-form"
|
|
||||||
use:enhance={() => {
|
|
||||||
loading = true;
|
|
||||||
return async ({ result }) => {
|
|
||||||
loading = false;
|
|
||||||
feedbackMessage = result.data;
|
|
||||||
if (result.type === 'success') await invalidateAll();
|
|
||||||
applyAction(result);
|
|
||||||
};
|
|
||||||
}}>
|
|
||||||
|
|
||||||
<label for="name">{$t("home.banner.name")}</label>
|
|
||||||
<input type="text" name="name" id="name" oninput={resetFeedback} required />
|
|
||||||
|
|
||||||
<label for="email">{$t("home.banner.email")}</label>
|
|
||||||
<input type="email" name="email" id="email" oninput={resetFeedback} required />
|
|
||||||
|
|
||||||
<button type="submit">{$t(loading ? 'home.banner.cta.loading' : 'home.banner.cta') }</button>
|
|
||||||
{#if feedbackMessage !== null}
|
|
||||||
<p style="margin-top: 1rem; color: {feedbackMessage.textColor}">
|
|
||||||
{$t(feedbackMessage.message)}
|
|
||||||
</p>
|
|
||||||
{/if}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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="/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="/viewer" class="button">{$t('home.cta.cta')}</a>
|
|
||||||
– {@html $t('home.cta.cta.description')}
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
.beta-form {
|
|
||||||
background: white;
|
|
||||||
color: #000;
|
|
||||||
padding: 1.5rem;
|
|
||||||
border-radius: 10px;
|
|
||||||
max-width: 400px;
|
|
||||||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.beta-form label {
|
|
||||||
display: block;
|
|
||||||
margin-top: 1rem;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.beta-form input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.6rem;
|
|
||||||
margin-top: 0.3rem;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.beta-form button {
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
width: 100%;
|
|
||||||
background-color: #05345f;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 0.8rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.beta-form button:disabled {
|
|
||||||
background-color: #7aa3c1;
|
|
||||||
cursor: wait;
|
|
||||||
}
|
|
||||||
|
|
||||||
.beta-form button:hover:enabled {
|
|
||||||
background-color: #042b4f;
|
|
||||||
}
|
|
||||||
.home-container {
|
|
||||||
margin: 0 auto;
|
|
||||||
width: 70%;
|
|
||||||
padding-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.home-container {
|
|
||||||
width: 90%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.beta-section {
|
|
||||||
background-color: #05345f;
|
|
||||||
color: white;
|
|
||||||
padding: 3rem 1rem;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.beta-content {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 2rem;
|
|
||||||
max-width: 1000px;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.beta-image img {
|
|
||||||
max-width: 300px;
|
|
||||||
border-radius: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.beta-text {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 280px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.beta-text h1 {
|
|
||||||
font-size: 2rem;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.beta-text .lead {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.beta-text p {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.beta-form {
|
|
||||||
background: white;
|
|
||||||
color: #000;
|
|
||||||
padding: 1.5rem;
|
|
||||||
border-radius: 10px;
|
|
||||||
max-width: 400px;
|
|
||||||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.beta-form label {
|
|
||||||
display: block;
|
|
||||||
margin-top: 1rem;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.beta-form input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.6rem;
|
|
||||||
margin-top: 0.3rem;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.beta-form button {
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
width: 100%;
|
|
||||||
background-color: #05345f;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 0.8rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.beta-form button:hover {
|
|
||||||
background-color: #042b4f;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.beta-content {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.beta-form {
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,240 @@
|
||||||
<script>
|
<script>
|
||||||
|
import DonateIcon from '$lib/components/icons/DonateIcon.svelte';
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
|
|
||||||
import Seo from '$lib/components/Seo.svelte';
|
import Seo from '$lib/components/Seo.svelte';
|
||||||
|
import { PUBLIC_IMAGE_BASE_URL } from '$env/static/public';
|
||||||
|
import { isMobile } from '$lib/utils/isMobile';
|
||||||
|
|
||||||
/** @type {import('./$types').PageProps} */
|
/** @type {import('./$types').PageProps} */
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
|
// svelte-ignore state_referenced_locally
|
||||||
const metadata = data.metadata;
|
const metadata = data.metadata;
|
||||||
|
|
||||||
|
const backgroundImage = isMobile()
|
||||||
|
? `${PUBLIC_IMAGE_BASE_URL}/t/f_webp/embroidery-viewer/route-wallpaper-mobile.webp`
|
||||||
|
: `${PUBLIC_IMAGE_BASE_URL}/t/f_webp,w_1920,h_1080/embroidery-viewer/route-wallpaper.webp`;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Seo {...metadata} />
|
<Seo {...metadata} />
|
||||||
|
|
||||||
<section aria-labelledby="about-heading">
|
<section aria-labelledby="about-heading">
|
||||||
<h1 id="about-heading">{$t('about.title')}</h1>
|
<div
|
||||||
|
class="heading-container"
|
||||||
|
style={`background: url(${backgroundImage}) center/cover no-repeat`}
|
||||||
|
>
|
||||||
|
<div class="overlay">
|
||||||
|
<p>{$t('about.hero.tagline')}</p>
|
||||||
|
<h1 id="about-heading">{$t('about.hero.title')}</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
{@html $t('about.content')}
|
<section id="about-content">
|
||||||
|
<div class="embroidery-bg"></div>
|
||||||
|
<h1>{$t('about.story.title')}</h1>
|
||||||
|
<div class="content-split">
|
||||||
|
<div class="split-left">
|
||||||
|
<p>{$t('about.story.p1')}</p>
|
||||||
|
<p>{$t('about.story.p2')}</p>
|
||||||
|
<p style="font-size: 1.7rem;">
|
||||||
|
{$t('about.story.quote1')}
|
||||||
|
</p>
|
||||||
|
<p>{$t('about.story.p3')}</p>
|
||||||
|
<p>{$t('about.story.p4')}</p>
|
||||||
|
<p style="font-size: 1.7rem;">
|
||||||
|
{$t('about.story.quote2')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="split-right">
|
||||||
|
<img
|
||||||
|
src={`${PUBLIC_IMAGE_BASE_URL}/t/f_webp/embroidery-viewer/woman-sad.webp`}
|
||||||
|
style="border: 3px solid white;"
|
||||||
|
width={isMobile() ? 300 : 400}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="content-split"
|
||||||
|
style={isMobile() ? 'flex-direction: column-reverse' : ''}
|
||||||
|
>
|
||||||
|
<div class="split-left" style={isMobile() ? 'align-items: center' : ''}>
|
||||||
|
<img
|
||||||
|
src={`${PUBLIC_IMAGE_BASE_URL}/t/f_webp/embroidery-viewer/viewer-screenshot.webp`}
|
||||||
|
style="border: 3px solid black;"
|
||||||
|
width={isMobile() ? 300 : 400}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="split-right" style="align-items: flex-start;">
|
||||||
|
<p>{$t('about.product.p1')}</p>
|
||||||
|
<p>{$t('about.product.p2')}</p>
|
||||||
|
<p>{$t('about.product.p3')}</p>
|
||||||
|
<p>{$t('about.product.p4')}</p>
|
||||||
|
|
||||||
|
<p style="font-size: 1.7rem;">
|
||||||
|
{$t('about.product.quote')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-split">
|
||||||
|
<div class="split-left">
|
||||||
|
<h2>{$t('about.support.title')}</h2>
|
||||||
|
|
||||||
|
<p>{$t('about.support.p1')}</p>
|
||||||
|
<p>{$t('about.support.p2')}</p>
|
||||||
|
<p>{$t('about.support.p3')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="split-right"
|
||||||
|
style="align-items: center; justify-content: center;"
|
||||||
|
>
|
||||||
|
<DonateIcon color="#ffffff" size={360} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="button-container">
|
||||||
|
<a class="organic-btn-secondary" href={resolve('/support-us')}>
|
||||||
|
{$t('about.support.cta')}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
section {
|
.embroidery-bg {
|
||||||
width: 70%;
|
position: absolute;
|
||||||
margin: 0 auto;
|
inset: 0;
|
||||||
|
z-index: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.15;
|
||||||
|
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg width='600' height='600' viewBox='0 0 600 600' xmlns='http://www.w3.org/2000/svg'%3E%3Cg stroke='%23ffffff' stroke-width='1.5' fill='none'%3E%3C!-- curved thread lines --%3E%3Cpath d='M50 100 Q150 50 250 120 T450 100' stroke-dasharray='6 6'/%3E%3Cpath d='M100 300 Q200 250 300 320 T500 300' stroke-dasharray='4 8'/%3E%3C!-- small crosses (stitches) --%3E%3Cg stroke-width='2'%3E%3Cpath d='M100 200 l10 10 M110 200 l-10 10'/%3E%3Cpath d='M300 400 l10 10 M310 400 l-10 10'/%3E%3Cpath d='M500 150 l10 10 M510 150 l-10 10'/%3E%3C/g%3E%3C!-- circular embroidery hoop hint --%3E%3Ccircle cx='500' cy='500' r='80' stroke-dasharray='5 10'/%3E%3C/g%3E%3C/svg%3E");
|
||||||
|
background-size: 600px 600px;
|
||||||
|
}
|
||||||
|
.button-container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 70px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
.organic-btn-secondary {
|
||||||
padding: 0;
|
font-size: 1.3rem;
|
||||||
margin-bottom: 7px;
|
padding: 20px 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: white;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 2.3rem;
|
||||||
|
margin-top: 100px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-split {
|
||||||
|
width: 85%;
|
||||||
|
display: flex;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding-top: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.split-left {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.split-right {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.split-right {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading-container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 80%;
|
||||||
|
z-index: 1;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading-container h1 {
|
||||||
|
font-size: clamp(3.5rem, 4vw, 3.5rem);
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#about-content {
|
||||||
|
position: relative; /* IMPORTANT */
|
||||||
|
margin: 0;
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
color: white;
|
||||||
|
padding: 100px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#about-content > *:not(.embroidery-bg) {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#about-content h1 {
|
||||||
|
color: white;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 2.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#about-content p {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
section {
|
.heading-container {
|
||||||
width: 90%;
|
width: 100%;
|
||||||
|
}
|
||||||
|
.heading-container h1 {
|
||||||
|
font-size: clamp(2.8rem, 4vw, 3.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-split {
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
padding-top: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#about-content {
|
||||||
|
padding: 30px 20px;
|
||||||
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
/** @type {import('./$types').PageLoad} */
|
|
||||||
export function load() {
|
|
||||||
return {
|
|
||||||
metadata: {
|
|
||||||
title: 'donate.seo.title',
|
|
||||||
description: 'donate.seo.description',
|
|
||||||
keywords: 'donate.seo.keywords',
|
|
||||||
url: 'donate.seo.url',
|
|
||||||
image: 'donate.seo.image',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,186 +0,0 @@
|
||||||
<script>
|
|
||||||
import { t } from '$lib/translations';
|
|
||||||
|
|
||||||
import bitcoin from '$lib/assets/bitcoin.svg';
|
|
||||||
import monero from '$lib/assets/monero.svg';
|
|
||||||
import paypal from '$lib/assets/paypal.svg';
|
|
||||||
|
|
||||||
import Seo from '$lib/components/Seo.svelte';
|
|
||||||
|
|
||||||
/** @type {import('./$types').PageProps} */
|
|
||||||
let { data } = $props();
|
|
||||||
|
|
||||||
const metadata = data.metadata;
|
|
||||||
|
|
||||||
const BTC_ADDRESS = 'bc1qpc4lpyr6stxrrg3u0k4clp4crlt6z4j6q845rq';
|
|
||||||
const XMR_ADDRESS =
|
|
||||||
'8A9iyTskiBh6f6GDUwnUJaYhAW13gNjDYaZYJBftX434D3XLrcGBko4a8kC4pLSfiuJAoSJ7e8rwP8W4StsVypftCp6FGwm';
|
|
||||||
|
|
||||||
let copyStatus = {
|
|
||||||
btc: '',
|
|
||||||
xmr: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} text
|
|
||||||
* @param {'btc' | 'xmr'} key
|
|
||||||
*/
|
|
||||||
async function copyToClipboard(text, key) {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(text);
|
|
||||||
copyStatus[key] = 'donate.copied';
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Copy failed:', err);
|
|
||||||
copyStatus[key] = 'donate.copy.failed';
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => (copyStatus[key] = ''), 2000);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Seo {...metadata} />
|
|
||||||
|
|
||||||
<section aria-labelledby="donate-title" class="donate-container">
|
|
||||||
<header>
|
|
||||||
<h1 id="donate-title">{$t('donate.title')}</h1>
|
|
||||||
<p class="donate-subtitle">{$t('donate.subtitle')}</p>
|
|
||||||
<p>{@html $t('donate.description')}</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<h2 id="ways-title">{$t('donate.ways')}</h2>
|
|
||||||
<div class="donation-options" aria-labelledby="ways-title">
|
|
||||||
<article class="donation-method" aria-labelledby="btc-label">
|
|
||||||
<img src={bitcoin} alt="Bitcoin QR code" width="200" height="200" />
|
|
||||||
<h3 id="btc-label">Bitcoin</h3>
|
|
||||||
<p>{$t('donate.bitcoin.description')}</p>
|
|
||||||
<button
|
|
||||||
aria-label="Copy Bitcoin address"
|
|
||||||
on:click={() => copyToClipboard(BTC_ADDRESS, 'btc')}
|
|
||||||
>
|
|
||||||
{#if copyStatus.btc}
|
|
||||||
{$t(copyStatus.btc)}
|
|
||||||
{: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
|
|
||||||
aria-label="Copy Monero address"
|
|
||||||
on:click={() => copyToClipboard(XMR_ADDRESS, 'xmr')}
|
|
||||||
>
|
|
||||||
{#if copyStatus.xmr}
|
|
||||||
{$t(copyStatus.xmr)}
|
|
||||||
{:else}
|
|
||||||
{$t('donate.copy')}
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<article class="donation-method" aria-labelledby="paypal-label">
|
|
||||||
<img src={paypal} alt="PayPal" width="200" height="200" />
|
|
||||||
<h3 id="paypal-label">PayPal</h3>
|
|
||||||
<p>{$t('donate.paypal.description')}</p>
|
|
||||||
<a
|
|
||||||
class="donation-link"
|
|
||||||
href="https://www.paypal.com/donate/?business=leo@leomurca.xyz¤cy_code=USD"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
aria-label="PayPal donation link"
|
|
||||||
>
|
|
||||||
{$t('donate.paypal.link')}
|
|
||||||
</a>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.donate-container {
|
|
||||||
width: 70%;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
margin-bottom: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.donate-subtitle {
|
|
||||||
font-weight: bold;
|
|
||||||
color: #06345f;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.donation-options {
|
|
||||||
display: flex;
|
|
||||||
gap: 2rem;
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.donation-method {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
width: 30rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.donation-method p {
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
text-align: center;
|
|
||||||
display: block;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
button,
|
|
||||||
.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;
|
|
||||||
margin-top: 1rem;
|
|
||||||
transition: background-color 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover,
|
|
||||||
.donation-link:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: black;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.donate-container {
|
|
||||||
width: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.donation-options {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.donation-method {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
button,
|
|
||||||
.donation-link {
|
|
||||||
width: 100%;
|
|
||||||
height: 55px;
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
/** @type {import('./$types').PageProps} */
|
/** @type {import('./$types').PageProps} */
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
|
// svelte-ignore state_referenced_locally
|
||||||
const metadata = data.metadata;
|
const metadata = data.metadata;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -14,6 +15,7 @@
|
||||||
<h1 id="privacy-policy-heading">{$t('mobile.app.privacy.policy.title')}</h1>
|
<h1 id="privacy-policy-heading">{$t('mobile.app.privacy.policy.title')}</h1>
|
||||||
<p><em>{$t('mobile.app.privacy.policy.last.update')}</em></p>
|
<p><em>{$t('mobile.app.privacy.policy.last.update')}</em></p>
|
||||||
|
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
{@html $t('mobile.app.privacy.policy.content')}
|
{@html $t('mobile.app.privacy.policy.content')}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
/** @type {import('./$types').PageProps} */
|
/** @type {import('./$types').PageProps} */
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
|
// svelte-ignore state_referenced_locally
|
||||||
const metadata = data.metadata;
|
const metadata = data.metadata;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -14,6 +15,7 @@
|
||||||
<h1 id="privacy-policy-heading">{$t('privacy.policy.title')}</h1>
|
<h1 id="privacy-policy-heading">{$t('privacy.policy.title')}</h1>
|
||||||
<p><em>{$t('privacy.policy.last.update')}</em></p>
|
<p><em>{$t('privacy.policy.last.update')}</em></p>
|
||||||
|
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
{@html $t('privacy.policy.content')}
|
{@html $t('privacy.policy.content')}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
@ -21,6 +23,7 @@
|
||||||
section {
|
section {
|
||||||
width: 70%;
|
width: 70%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
margin-top: 130px;
|
||||||
}
|
}
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
|
|
|
||||||
12
src/routes/support-us/+page.js
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
/** @type {import('./$types').PageLoad} */
|
||||||
|
export function load() {
|
||||||
|
return {
|
||||||
|
metadata: {
|
||||||
|
title: 'support-us.seo.title',
|
||||||
|
description: 'support-us.seo.description',
|
||||||
|
keywords: 'support-us.seo.keywords',
|
||||||
|
url: 'support-us.seo.url',
|
||||||
|
image: 'support-us.seo.image',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
117
src/routes/support-us/+page.svelte
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
<script>
|
||||||
|
import { t } from '$lib/translations';
|
||||||
|
import { PUBLIC_IMAGE_BASE_URL } from '$env/static/public';
|
||||||
|
import { isMobile } from '$lib/utils/isMobile';
|
||||||
|
|
||||||
|
import BuyMeACoffeeIcon from '$lib/components/icons/BuyMeACoffeeIcon.svelte';
|
||||||
|
import Seo from '$lib/components/Seo.svelte';
|
||||||
|
|
||||||
|
/** @type {import('./$types').PageProps} */
|
||||||
|
let { data } = $props();
|
||||||
|
|
||||||
|
// svelte-ignore state_referenced_locally
|
||||||
|
const metadata = data.metadata;
|
||||||
|
|
||||||
|
const backgroundImage = isMobile()
|
||||||
|
? `${PUBLIC_IMAGE_BASE_URL}/t/f_webp/embroidery-viewer/route-wallpaper-mobile.webp`
|
||||||
|
: `${PUBLIC_IMAGE_BASE_URL}/t/f_webp,w_1920,h_1080/embroidery-viewer/route-wallpaper.webp`;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Seo {...metadata} />
|
||||||
|
|
||||||
|
<section aria-labelledby="support-us">
|
||||||
|
<div
|
||||||
|
class="heading-container"
|
||||||
|
style={`background: url(${backgroundImage}) center/cover no-repeat`}
|
||||||
|
>
|
||||||
|
<div class="overlay">
|
||||||
|
<h1 id="support-us">{$t('support-us.title')}</h1>
|
||||||
|
<p>
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
|
{@html $t('support-us.description')}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="bmac-button"
|
||||||
|
href="https://buymeacoffee.com/embroideryviewerxyz"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<BuyMeACoffeeIcon size={30} />
|
||||||
|
{$t('support-us.cta')}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.bmac-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
text-decoration: none;
|
||||||
|
background-color: #ffdd03;
|
||||||
|
border: none;
|
||||||
|
padding: 15px 20px;
|
||||||
|
border-radius: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: black;
|
||||||
|
gap: 10px;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
margin-top: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bmac-button:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
.heading-container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 80%;
|
||||||
|
z-index: 1;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading-container h1 {
|
||||||
|
font-size: clamp(3.5rem, 4vw, 3.5rem);
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.heading-container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.heading-container h1 {
|
||||||
|
font-size: clamp(2.5rem, 4vw, 3rem);
|
||||||
|
}
|
||||||
|
.bmac-button {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
/** @type {import('./$types').PageProps} */
|
/** @type {import('./$types').PageProps} */
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
|
// svelte-ignore state_referenced_locally
|
||||||
const metadata = data.metadata;
|
const metadata = data.metadata;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -14,6 +15,7 @@
|
||||||
<h1 id="tos-heading">{$t('terms.of.service.title')}</h1>
|
<h1 id="tos-heading">{$t('terms.of.service.title')}</h1>
|
||||||
<p><em>{$t('terms.of.service.update')}</em></p>
|
<p><em>{$t('terms.of.service.update')}</em></p>
|
||||||
|
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
{@html $t('terms.of.service.content')}
|
{@html $t('terms.of.service.content')}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
@ -21,6 +23,7 @@
|
||||||
section {
|
section {
|
||||||
width: 70%;
|
width: 70%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
margin-top: 130px;
|
||||||
}
|
}
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
|
|
||||||
import CardList from '$lib/components/CardList.svelte';
|
import CardList from '$lib/components/CardList.svelte';
|
||||||
|
|
@ -80,11 +82,13 @@
|
||||||
/** @type {import('./$types').PageProps} */
|
/** @type {import('./$types').PageProps} */
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
|
// svelte-ignore state_referenced_locally
|
||||||
const metadata = data.metadata;
|
const metadata = data.metadata;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Seo {...metadata} />
|
<Seo {...metadata} />
|
||||||
|
|
||||||
|
<!-- eslint-disable svelte/no-at-html-tags -->
|
||||||
<form id="form" enctype="multipart/form-data" onsubmit={onSubmit}>
|
<form id="form" enctype="multipart/form-data" onsubmit={onSubmit}>
|
||||||
<div class="title-container">
|
<div class="title-container">
|
||||||
<h2>{$t('viewer.title')}</h2>
|
<h2>{$t('viewer.title')}</h2>
|
||||||
|
|
@ -126,6 +130,7 @@
|
||||||
form {
|
form {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
margin-top: 130px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-container {
|
.title-container {
|
||||||
|
|
|
||||||
BIN
static/fonts/merienda.bold.woff
Normal file
BIN
static/fonts/merienda.bold.woff2
Normal file
BIN
static/fonts/merienda.extra-bold.woff
Normal file
BIN
static/fonts/merienda.extra-bold.woff2
Normal file
BIN
static/fonts/merienda.medium.woff
Normal file
BIN
static/fonts/merienda.medium.woff2
Normal file
BIN
static/fonts/merienda.regular.woff
Normal file
BIN
static/fonts/merienda.regular.woff2
Normal file
BIN
static/fonts/merienda.semi-bold.woff
Normal file
BIN
static/fonts/merienda.semi-bold.woff2
Normal file
|
|
@ -17,6 +17,9 @@ const config = {
|
||||||
prerender: {
|
prerender: {
|
||||||
origin: 'https://embroideryviewer.xyz',
|
origin: 'https://embroideryviewer.xyz',
|
||||||
},
|
},
|
||||||
|
env: {
|
||||||
|
publicPrefix: 'PUBLIC_',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|
|
||||||