Compare commits

..

No commits in common. "main" and "migrate-to-sveltekit" have entirely different histories.

124 changed files with 4840 additions and 7970 deletions

View file

@ -13,24 +13,24 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up SSH - name: Use Node.js 19
run: | uses: actions/setup-node@v4
mkdir -p ~/.ssh/ with:
echo "${{ secrets.SSH_KEY }}" > ./deploy.key node-version: 19
chmod 600 ./deploy.key
echo "${{ secrets.SSH_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
- name: Create env file env:
run: | SSH_PRIVATE_KEY: ${{secrets.SSH_KEY}}
touch .env SSH_KNOWN_HOSTS: ${{secrets.SSH_KNOWN_HOSTS}}
echo EMAIL_ACCESS_KEY=${{ secrets.EMAIL_ACCESS_KEY }} >> .env
echo EMAIL_BASE_URL=${{ secrets.EMAIL_BASE_URL }} >> .env
- name: Verify .env file creation
run: cat .env
- name: Install PM2 - name: Install PM2
run: npm i -g pm2 run: npm i -g pm2
- name: Add Deploy Key to SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_KEY }}" >> ./deploy.key
sudo chmod 600 ./deploy.key
echo "${{ secrets.SSH_KNOWN_HOSTS}}" > ~/.ssh/known_hosts
- name: Deploy - name: Deploy
run: env $(cat .env | grep -v \"#\" | xargs) pm2 deploy ecosystem.config.cjs production run: pm2 deploy ecosystem.config.cjs production

1
.gitignore vendored
View file

@ -8,4 +8,3 @@ node_modules
!.env.example !.env.example
vite.config.js.timestamp-* vite.config.js.timestamp-*
vite.config.ts.timestamp-* vite.config.ts.timestamp-*
deploy.key

View file

@ -1,73 +1,38 @@
# 🧵 Embroidery Viewer # sv
![Logo](/logo.webp) Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
![Deploy workflow status](https://git.leomurca.xyz/leomurca/embroidery-viewer/actions/workflows/deploy.yml/badge.svg) ## Creating a project
**The simplest way to preview embroidery files — instantly, in your browser.** If you're seeing this, you've probably already done this step. Congrats!
👉 **Try it now:** https://embroideryviewer.xyz ```bash
# create a new project in the current directory
npx sv create
![Demo](/demo.gif) # create a new project in my-app
npx sv create my-app
```
<a href="https://buymeacoffee.com/embroideryviewerxyz"> ## Developing
<img src="docs/yellow-button.png" width="200" alt="Alt Text">
</a>
--- Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
## ✨ Why Embroidery Viewer? ```bash
npm run dev
Working with embroidery files shouldnt require heavy, expensive software. # or start the server and open the app in a new browser tab
npm run dev -- --open
```
Embroidery Viewer was built to solve a simple problem: ## Building
> _“I just want to quickly see my design.”_ To create a production version of your app:
No installs. No friction. Just drag, drop, and view. ```bash
npm run build
```
--- You can preview the production build with `npm run preview`.
## 🚀 Features > To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
- ⚡ **Instant preview** — open files in seconds
- 🔒 **Private by design** — everything runs in your browser
- 🧵 **Multiple formats supported**
- 🖥️ **Works on any device** (desktop, tablet, mobile)
- 📂 **Batch-friendly** — view multiple files in sequence
---
## 📁 Supported Formats
- `.pes`
- `.dst`
- `.pec`
- `.jef`
- `.exp`
---
## 🧠 How it works
All processing happens **client-side** using modern web technologies.
Your files are never uploaded to any server.
---
## 💡 Inspiration
Inspired by:
https://github.com/redteam316/html5-embroidery.git
---
## ❤️ Support the project
If this tool helped you, consider supporting its development:
- Share it with others
- Give feedback
- Or contribute to the project
---

BIN
demo.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View file

@ -1,7 +1,7 @@
module.exports = { module.exports = {
apps: [ apps: [
{ {
name: 'embroidery-viewer-prod', name: 'embroidery-viewer',
script: './build/index.js', script: './build/index.js',
time: true, time: true,
instances: 1, instances: 1,
@ -10,8 +10,6 @@ module.exports = {
watch: false, watch: false,
max_memory_restart: '1G', max_memory_restart: '1G',
env: { env: {
PORT: 7281,
PUBLIC_APP_ENV: 'production',
NODE_ENV: 'production', NODE_ENV: 'production',
}, },
}, },
@ -24,13 +22,11 @@ module.exports = {
ref: 'origin/main', ref: 'origin/main',
repo: 'git@git.leomurca.xyz:leomurca/embroidery-viewer.git', repo: 'git@git.leomurca.xyz:leomurca/embroidery-viewer.git',
path: '/home/deployer/embroidery-viewer', path: '/home/deployer/embroidery-viewer',
'pre-deploy': 'pre-deploy': 'rm package-lock.json && npm i',
'rm -rf node_modules build .svelte-kit && npm ci && PUBLIC_APP_ENV=production npm run build',
'post-deploy': 'post-deploy':
'pm2 startOrReload ecosystem.config.cjs --only embroidery-viewer-prod --env production && pm2 save', 'npm run build && pm2 reload ecosystem.config.cjs --only acelera-alagoas-prod --env production && pm2 save',
env: { env: {
PORT: 7281, PORT: 7017,
PUBLIC_APP_ENV: 'production',
NODE_ENV: 'production', NODE_ENV: 'production',
}, },
}, },

BIN
logo.webp

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

2531
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
{ {
"name": "embroidery-viewer", "name": "embroidery-viewer",
"private": true, "private": true,
"version": "3.1.0", "version": "0.0.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",
@ -14,24 +14,23 @@
"lint": "prettier --check . && eslint ." "lint": "prettier --check . && eslint ."
}, },
"devDependencies": { "devDependencies": {
"@eslint/compat": "^2.0.5", "@eslint/compat": "^1.2.5",
"@eslint/js": "^10.0.1", "@eslint/js": "^9.18.0",
"@sveltejs/kit": "^2.57.1", "@sveltejs/adapter-auto": "^6.0.0",
"@sveltejs/vite-plugin-svelte": "^7.0.0", "@sveltejs/kit": "^2.16.0",
"@types/node": "^25.6.0", "@sveltejs/vite-plugin-svelte": "^5.0.0",
"eslint": "^10.2.0", "eslint": "^9.28.0",
"eslint-config-prettier": "^10.0.1", "eslint-config-prettier": "^10.0.1",
"eslint-plugin-svelte": "^3.17.0", "eslint-plugin-svelte": "^3.9.1",
"globals": "^17.5.0", "globals": "^16.0.0",
"prettier": "^3.8.3", "prettier": "^3.5.3",
"prettier-plugin-svelte": "^3.5.1", "prettier-plugin-svelte": "^3.3.3",
"svelte": "^5.55.4", "svelte": "^5.0.0",
"svelte-check": "^4.4.6", "svelte-check": "^4.0.0",
"typescript": "^6.0.2", "typescript": "^5.0.0",
"vite": "^8.0.8" "vite": "^6.2.6"
}, },
"dependencies": { "dependencies": {
"@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"
} }

View file

@ -1,44 +1,92 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<script <style>
data-name="BMC-Widget" :root {
data-cfasync="false" font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
src="https://cdnjs.buymeacoffee.com/1.0.0/widget.prod.min.js" font-size: 16px;
data-id="embroideryviewerxyz" line-height: 24px;
data-description="Support me on Buy me a coffee!" font-weight: 400;
data-message="Enjoying Embroidery Viewer? font-synthesis: none;
Buy me a coffee and help keep it running ☕" text-rendering: optimizeLegibility;
data-color="#FFDD03" -webkit-font-smoothing: antialiased;
data-position="Right" -moz-osx-font-smoothing: grayscale;
data-x_margin="18" -webkit-text-size-adjust: 100%;
data-y_margin="18" }
></script>
<!-- Basic --> * {
<meta charset="utf-8" /> box-sizing: border-box;
<meta name="viewport" content="width=device-width, initial-scale=1" /> }
<meta name="author" content="Embroidery Viewer" />
<meta name="theme-color" content="#ffffff" /> 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" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- Favicon -->
<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" />
<link rel="shortcut icon" href="/favicon.ico" /> <link rel="shortcut icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" /> <link rel="manifest" href="/site.webmanifest" />
<link rel="canonical" href="https://embroideryviewer.xyz/" /> <link rel="canonical" href="https://embroideryviewer.xyz/" />
<!-- Mobile / PWA friendliness -->
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<link
rel="preload"
href="/fonts/merienda.regular.woff2"
as="font"
type="font/woff2"
crossorigin="anonymous"
/>
%sveltekit.head% %sveltekit.head%
</head> </head>
<body data-sveltekit-preload-data="hover"> <body data-sveltekit-preload-data="hover">

543
src/lib/assets/bitcoin.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

1230
src/lib/assets/monero.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 155 KiB

1013
src/lib/assets/paypal.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 130 KiB

View file

@ -1,9 +0,0 @@
<script>
import { isDevelopment } from '$lib/utils/env';
</script>
<svelte:head>
{#if !isDevelopment()}
<script async src="https://hk.leomurca.xyz/hk.js"></script>
{/if}
</svelte:head>

View file

@ -1,69 +0,0 @@
<script>
import { t } from '$lib/translations';
const trackEvent = () => {
// @ts-ignore
window.hk?.event?.('install_now');
};
</script>
<div class="bar">
<div class="content">
<p>
{$t('announcement.message')}
<a
href="https://play.google.com/store/apps/details?id=xyz.embroideryviewer.android"
target="_blank"
rel="noopener noreferrer"
class="cta"
onclick={trackEvent}
>
{$t('announcement.cta-text')}
</a>
</p>
</div>
</div>
<style>
.bar {
width: 100%;
background: #06345f;
color: white;
font-size: 0.95rem;
z-index: 3;
}
.content {
max-width: 1200px;
margin: 0 auto;
padding: 0.6rem 1rem;
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
}
p {
margin: 0;
line-height: 1.4;
}
.cta {
margin-left: 0.5rem;
font-weight: 600;
text-decoration: underline;
color: #ffffff;
white-space: nowrap;
}
.cta:hover {
opacity: 0.85;
}
@media (max-width: 640px) {
.content {
flex-direction: column;
align-items: flex-start;
}
}
</style>

View file

@ -2,30 +2,17 @@
import { t } from '$lib/translations'; import { t } from '$lib/translations';
import renderFileToCanvas from '$lib/file-renderer'; import renderFileToCanvas from '$lib/file-renderer';
/**
* @type {ArrayLike<any>}
*/
export let files = []; export let files = [];
/**
* @type {HTMLElement}
*/
let errorMessageRef;
let canvasRefs = []; let canvasRefs = [];
let colorRefs = []; let colorRefs = [];
let stitchesRefs = []; let stitchesRefs = [];
let sizeRefs = []; let sizeRefs = [];
let errorMessageRef;
let localizedStrings = { let localizedStrings = {
stitches: $t('viewer.stitches'), stitches: $t('viewer.stitches'),
dimensions: $t('viewer.dimensions'), dimensions: $t('viewer.dimensions'),
}; };
/**
* Downloads a given HTMLCanvasElement as a PNG image.
*
* @param {HTMLCanvasElement} canvas - The canvas element to export as an image.
* @param {string} filename - The desired name of the downloaded file (extension will be replaced with `.png`).
*/
const downloadCanvasAsImage = (canvas, filename) => { const downloadCanvasAsImage = (canvas, filename) => {
const image = canvas const image = canvas
.toDataURL('image/png') .toDataURL('image/png')
@ -37,22 +24,16 @@
link.click(); link.click();
}; };
/**
* Cliks the button to render the files when user press enter.
*
* @param {KeyboardEvent} evt - The event that triggered the language switch.
*/
const onKeydown = (evt) => { const onKeydown = (evt) => {
if (evt.key === 'Enter') { if (evt.key === 'Enter') {
const button = document.getElementById('download-button'); document.getElementById('download-button').click();
if (button) button.click();
} }
}; };
</script> </script>
{#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 (i)} {#each Array.from(files) as file, 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>
@ -63,8 +44,8 @@
id="download-button" id="download-button"
role="button" role="button"
tabindex="0" tabindex="0"
onkeydown={onKeydown} on:keydown={onKeydown}
onclick={() => downloadCanvasAsImage(canvasRefs[i], file.name)} on:click={() => downloadCanvasAsImage(canvasRefs[i], file.name)}
> >
{$t('viewer.download')} {$t('viewer.download')}
</div> </div>

View file

@ -11,8 +11,6 @@
</script> </script>
<!-- svelte-ignore a11y-no-noninteractive-tabindex --> <!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<!-- eslint-disable svelte/no-at-html-tags -->
<div <div
id="dropzone" id="dropzone"
tabindex={0} tabindex={0}

View file

@ -1,8 +1,5 @@
<script> <script>
export let title; export let title;
/**
* @type {ArrayLike<any>}
*/
export let files = []; export let files = [];
export let isError = false; export let isError = false;
</script> </script>
@ -11,7 +8,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, i (i)} {#each Array.from(files) as file}
<div id={isError ? 'selected-file-card-error' : 'selected-file-card'}> <div id={isError ? 'selected-file-card-error' : 'selected-file-card'}>
<span>{file.name}</span> <span>{file.name}</span>
<span>{Math.round(file.size / 1000)} KB</span> <span>{Math.round(file.size / 1000)} KB</span>

View file

@ -1,305 +1,96 @@
<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-main"> <div class="footer-content">
<div class="footer-decoration" aria-hidden="true"> <div class="footer-info">
<svg <p>
class="footer-decoration__svg" {@html $t(
xmlns="http://www.w3.org/2000/svg" 'footer.copyright',
viewBox="0 0 1440 480" /** @type {any} */ ({
preserveAspectRatio="xMidYMid slice" year: new Date().getFullYear(),
fill="none" website: 'https://leomurca.xyz',
> }),
<g stroke="white" stroke-linecap="round"> )}
<circle </p>
cx="1180" <p>
cy="360" {@html $t(
r="130" 'footer.version',
stroke-width="2" /** @type {any} */ ({ version: appVersion() }),
stroke-opacity="0.14" )}
stroke-dasharray="10 8" </p>
/>
<circle cx="1180" cy="360" r="98" stroke-width="1" stroke-opacity="0.08" />
<path
d="M60 100 Q220 20 400 110 T720 80"
stroke-width="1.5"
stroke-opacity="0.16"
stroke-dasharray="6 10"
/>
<path
d="M100 320 Q340 260 560 340 T980 300"
stroke-width="1.2"
stroke-opacity="0.12"
stroke-dasharray="4 12"
/>
<path
d="M200 60 L240 100 M240 60 L200 100"
stroke-width="1"
stroke-opacity="0.1"
/>
<path
d="M320 400 L350 430 M350 400 L320 430"
stroke-width="1"
stroke-opacity="0.1"
/>
<path
d="M900 140 L930 170 M930 140 L900 170"
stroke-width="1"
stroke-opacity="0.08"
/>
<circle cx="180" cy="200" r="4" fill="white" fill-opacity="0.12" stroke="none" />
<circle cx="210" cy="220" r="3" fill="white" fill-opacity="0.1" stroke="none" />
<circle cx="240" cy="205" r="3.5" fill="white" fill-opacity="0.1" stroke="none" />
<circle cx="680" cy="90" r="3" fill="white" fill-opacity="0.08" stroke="none" />
<circle cx="710" cy="105" r="4" fill="white" fill-opacity="0.08" stroke="none" />
</g>
<path
d="M1320 40 L1348 8 L1356 16 L1328 48 Z"
fill="white"
fill-opacity="0.1"
/>
</svg>
</div> </div>
<div id="content-container"> <nav class="footer-nav">
<section class="footer-block"> <a href="/about">{$t('footer.about')}</a>
<img <a href="/privacy-policy">{$t('footer.privacy.policy')}</a>
src={logo} <a href="/terms-of-service">{$t('footer.terms.of.service')}</a>
style="height: 80px; width: auto; margin-lft: -5px;"
alt="Logotipo da Embroidery Viewer."
/>
<p>{$t('footer.slogan')}</p>
</section>
<section class="footer-block" aria-labelledby="contact-title">
<h1 id="contact-title">{$t('footer.contact-title')}</h1>
<p>{$t('footer.contact-description')}</p>
<address class="contact-container">
<div class="contact-item">
<MailIcon size={30} />
<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('/pes-file-viewer')}>{$t('footer.pesViewer')}</a>
<a href={resolve('/dst-file-viewer')}>{$t('footer.dstViewer')}</a>
<a href={resolve('/jef-file-viewer')}>{$t('footer.jefViewer')}</a>
<a href={resolve('/exp-file-viewer')}>{$t('footer.expViewer')}</a>
<a href={resolve('/embroidery-viewer-android')}>{$t('footer.androidApp')}</a>
<a href={resolve('/privacy-policy')}>{$t('footer.privacy.policy')}</a>
<a href={resolve('/terms-of-service')}
>{$t('footer.terms.of.service')}</a
>
</nav> </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>
</div>
<section class="credits-container">
<div class="credits-decoration" aria-hidden="true">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1440 48"
preserveAspectRatio="none"
fill="none"
>
<path
d="M0 24 Q180 8 360 24 T720 24 T1080 24 T1440 24"
stroke="white"
stroke-width="1"
stroke-opacity="0.2"
stroke-dasharray="5 9"
/>
</svg>
</div>
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;
border-top: 1px solid #ddd;
padding: 20px;
width: 100%; width: 100%;
} }
.footer-main { .footer-content {
position: relative;
overflow: hidden;
background-color: var(--color-primary);
}
.footer-decoration {
position: absolute;
inset: 0;
pointer-events: none;
z-index: 0;
}
.footer-decoration__svg {
width: 100%;
height: 100%;
opacity: 0.95;
}
#content-container {
position: relative;
z-index: 1;
width: 85%;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
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; align-items: center;
gap: 8px; flex-wrap: wrap;
margin-top: 10px; max-width: 960px;
} margin: 0 auto;
.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;
} }
.back-to-top-button:hover { .footer-info {
background-color: #ffffff; flex: 1 1 100%;
color: var(--color-primary); margin-bottom: 10px;
} }
.credits-container { .footer-info p {
position: relative; margin: 4px 0;
z-index: 1; font-size: 14px;
background-color: var(--color-secondary); color: #333;
color: white;
margin: 0 auto;
padding: 20px 30px;
padding-left: 9%;
width: 100%;
overflow: hidden;
} }
.credits-decoration { .footer-info p:first-child {
position: absolute; font-weight: bold;
top: 0;
left: 0;
right: 0;
height: 48px;
pointer-events: none;
transform: translateY(-50%);
} }
.credits-decoration svg { .footer-nav {
width: 100%; flex: 1 1 100%;
height: 100%;
} }
.credits-container a { .footer-nav a {
color: white; margin: 0 10px;
border-bottom: 1px solid white; font-size: 14px;
} }
.credits-container a:hover { @media (min-width: 600px) {
background-color: white; .footer-content {
color: var(--color-secondary); flex-wrap: nowrap;
text-align: left;
} }
/* Responsive */ .footer-info,
@media (max-width: 768px) { .footer-nav {
#content-container { flex: 1 1 50%;
width: 100%; margin-bottom: 0;
margin: 0;
padding: 60px 30px;
flex-direction: column;
} }
.back-to-top-button { .footer-info {
width: 100%; text-align: left;
justify-content: center; }
gap: 5px;
.footer-nav {
text-align: right;
} }
} }
</style> </style>

View file

@ -1,107 +0,0 @@
<script>
import { PUBLIC_IMAGE_BASE_URL } from '$env/static/public';
import { locale, t } from '$lib/translations';
import { normalizeLocaleUnderscore } from '$lib/utils/normalizeLocaleUnderscore';
import { isDevelopment } from '$lib/utils/env';
/**
* =========================
* Props
* =========================
*/
/** @type {string} Page title (translation key) */
export let title;
/** @type {string} Page description (translation key) */
export let description;
/** @type {string} SEO keywords (translation key or raw string) */
export let keywords;
/** @type {string} Canonical URL (absolute) */
export let url;
/** @type {string} Open Graph type (e.g., 'website', 'article') */
export let ogType = 'website';
/** @type {string} Optional override for Open Graph description */
export let ogDescription = description;
/** @type {string | undefined} Optional Open Graph image URL (translation key or absolute URL) */
export let ogImage = undefined;
/** @type {string} Twitter card type */
export let twitterCard = 'summary_large_image';
/** @type {boolean} Whether the page should be indexed */
export let shouldBeIndexed = !isDevelopment();
/**
* =========================
* Derived / Computed values
* =========================
*/
let defaultImage = `${PUBLIC_IMAGE_BASE_URL}/t/f_webp/embroidery-viewer/logo-icon.webp`;
// Translations (avoid repeating $t everywhere)
$: translatedTitle = title ? $t(title) : '';
$: translatedDescription = description ? $t(description) : '';
$: translatedKeywords = keywords ? $t(keywords) : '';
// Fallbacks
$: finalOgDescription = ogDescription
? $t(ogDescription)
: translatedDescription;
$: ogImageUrl = ogImage
? ogImage.startsWith('http')
? ogImage
: $t(ogImage)
: defaultImage;
$: canonicalUrl = url
? url.startsWith('http')
? url
: $t(url)
: '';
// Locale formatting (e.g., en-US -> en_US)
$: ogLocale = normalizeLocaleUnderscore($locale);
// Robots directive
$: robotsContent = shouldBeIndexed ? 'index, follow' : 'noindex, nofollow';
</script>
<svelte:head>
<!-- Primary SEO -->
<title>{translatedTitle}</title>
<meta name="description" content={translatedDescription} />
<meta name="keywords" content={translatedKeywords} />
{#if canonicalUrl}
<link rel="canonical" href={canonicalUrl} />
{/if}
<!-- Robots -->
<meta name="robots" content={robotsContent} />
<meta name="googlebot" content={robotsContent} />
<!-- Open Graph (Facebook, LinkedIn, etc.) -->
<meta property="og:type" content={ogType} />
<meta property="og:title" content={translatedTitle} />
<meta property="og:description" content={finalOgDescription} />
<meta property="og:image" content={ogImageUrl} />
<meta property="og:url" content={canonicalUrl} />
<meta property="og:locale" content={ogLocale} />
<meta property="og:site_name" content="Embroidery Viewer" />
<!-- Twitter -->
<meta name="twitter:card" content={twitterCard} />
<meta name="twitter:title" content={translatedTitle} />
<meta name="twitter:description" content={finalOgDescription} />
<meta name="twitter:image" content={ogImageUrl} />
<!-- Optional: improves link previews in some platforms -->
<meta property="og:image:alt" content={translatedTitle} />
</svelte:head>

View file

@ -1,64 +1,91 @@
<script> <script>
import { t } from '$lib/translations'; import { t, locale, locales, SUPPORTED_LOCALES } 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); const configsFor = (/** @type {boolean} */ matches) => {
let route = $derived(page.url.pathname); return matches
$effect(() => { ? { src: logo, width: 150, height: 70 } // mobile
route; // track dependency : { src: logo, width: 150, height: 100 }; // desktop
isOpen = false; };
});
const onSwitchToOppositeLang = () => {
$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>
<a href={resolve('/')}> {@const configs = configsFor(matches)}
<a href="/">
<img <img
src={logo} src={configs.src}
alt="Embroidery viewer logo" alt="Embroidery viewer logo"
width="150" width={configs.width}
height={matches ? 70 : 100} height={configs.height}
/> />
</a> </a>
</MediaQuery> </MediaQuery>
</div> </div>
<nav class:active={route !== '/'} id="menuToggle"> <div class="nav-container">
<input type="checkbox" bind:checked={isOpen} /> <MediaQuery query="(max-width: 768px)" let:matches>
<span></span> <slot let-matches>
<span></span> {#if matches}
<span></span> <button class="hamburger" on:click={() => (isMenuOpen = !isMenuOpen)}>
{#if isMenuOpen}x{:else}{/if}
<ul id="menu"> </button>
<li><a href={resolve('/about')}>{$t('header.aboutNav')}</a></li> {/if}
<li><a href={resolve('/viewer')}>{$t('header.viewerNav')}</a></li> </slot>
<li><a href={resolve('/support-us')}>{$t('header.supportUsNav')}</a></li> </MediaQuery>
<li style="font-size: 22px; padding: 10px 0;"><LanguageSwitch /></li> <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> </ul>
</nav> </nav>
<a
class="common-switch {$locale === SUPPORTED_LOCALES.EN_US
? 'portuguese-switch'
: 'english-switch'}"
href="#"
on:click|preventDefault={onSwitchToOppositeLang}
>
<div style="display: flex; width: fit-content;">
<span style="font-size: 20px;">{$t('header.languageSwitch')}</span>
</div>
</a>
</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: 45px 30px 10px 100px; padding: 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;
@ -71,166 +98,109 @@
.logo a:hover { .logo a:hover {
background: transparent; background: transparent;
} }
#menuToggle {
position: relative; .nav-container {
display: flex; display: flex;
flex-direction: column; gap: 20px;
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);
} }
#menu li a { nav ul {
padding: 10px 0; list-style: none;
font-size: 22px; margin: 0;
color: white; padding: 0;
display: block; display: flex;
gap: 20px;
} }
#menu li a:hover { nav ul li {
color: #adadad; display: flex;
transition: all 0.5s ease; font-weight: bold;
} }
#menuToggle input:checked ~ ul { .hamburger {
transform: scale(1, 1); background: none;
opacity: 1; border: none;
font-size: 35px;
width: 35px;
padding: 0;
margin: 0;
cursor: pointer;
display: none;
} }
.active input:checked ~ span:nth-child(2), .common-switch {
.active input:checked ~ span:nth-child(3), width: fit-content;
.active input:checked ~ span:nth-child(4) {
background-color: white !important;
} }
.active span { .portuguese-switch {
background-color: var(--color-primary) !important; color: #0c8f27;
border-bottom: 3px solid #0c8f27 !important;
fill: #0c8f27 !important;
} }
@media (max-width: 1458px) { .portuguese-switch:hover {
#menuToggle span { background: #0c8f27;
background-color: var(--color-primary); color: #ffffff;
} fill: #ffffff !important;
/* Top bar → rotates */
#menuToggle input:checked ~ span:nth-child(2) {
background-color: white;
} }
/* Middle bar → fades out */ .english-switch {
#menuToggle input:checked ~ span:nth-child(3) { color: #be0a2f;
background-color: white; border-bottom: 3px solid #be0a2f;
width: fit-content;
fill: #be0a2f !important;
} }
/* Bottom bar → rotates opposite */ .english-switch:hover {
#menuToggle input:checked ~ span:nth-child(4) { background: #be0a2f;
background-color: white; color: #ffffff;
} fill: #ffffff !important;
}
@media (max-width: 1159px) {
header {
padding-top: 70px;
}
} }
@media (max-width: 768px) { @media (max-width: 768px) {
header { header {
padding: 110px 20px 10px 20px; padding: 10px 20px;
}
.hamburger {
display: block;
width: 35px;
} }
#menu { nav {
width: 100vw; display: none;
top: -110px; flex-direction: column;
margin: 0px 0 0 0; gap: 10px;
right: -20px; background-color: #f8f9fa;
border-radius: 0; position: absolute;
border-radius: 0% 0% 80% 80%; top: 60px;
transform: translate(0%, -100%); right: 0;
border: 1px solid #ddd;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
nav.is-open {
display: inline-block;
margin-top: 25px;
width: 100%;
}
nav ul {
flex-direction: column;
gap: 0px;
width: 100%;
}
nav ul li {
text-align: center;
width: 100%;
}
nav ul li a {
display: inline-block;
width: 100%;
padding: 10px;
border-bottom: none;
} }
} }
</style> </style>

View file

@ -1,145 +0,0 @@
<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>

View file

@ -0,0 +1,61 @@
<script>
import { t } from '$lib/translations';
/** @type {string} Title of the page */
export let title;
/** @type {string} Description of the page */
export let description;
/** @type {string} SEO keywords */
export let keywords;
/** @type {string} Canonical URL of the page */
export let url;
/** @type {string} Main image URL for sharing */
export let image;
/** @type {string} Open Graph type (e.g., 'website', 'article') */
export let ogType = 'website';
/** @type {string} Open Graph title (defaults to title) */
export let ogTitle = title;
/** @type {string} Open Graph description (defaults to description) */
export let ogDescription = description;
/** @type {string} Open Graph image (defaults to image) */
export let ogImage = image;
/** @type {string} Twitter card type (e.g., 'summary_large_image') */
export let twitterCard = 'summary_large_image';
/** @type {string} Twitter title (defaults to title) */
export let twitterTitle = title;
/** @type {string} Twitter description (defaults to description) */
export let twitterDescription = description;
/** @type {string} Twitter image (defaults to image) */
export let twitterImage = image;
</script>
<svelte:head>
<title>{$t(title)}</title>
<meta name="description" content={$t(description)} />
<meta name="keywords" content={$t(keywords)} />
<!-- Open Graph -->
<meta property="og:type" content={ogType} />
<meta property="og:title" content={$t(ogTitle)} />
<meta property="og:description" content={$t(ogDescription)} />
<meta property="og:image" content={ogImage} />
<meta property="og:url" content={url} />
<!-- Twitter -->
<meta name="twitter:card" content={twitterCard} />
<meta name="twitter:title" content={twitterTitle} />
<meta name="twitter:description" content={$t(twitterDescription)} />
<meta name="twitter:image" content={twitterImage} />
</svelte:head>

View file

@ -1,11 +0,0 @@
<script>
/**
* JSON-LD structured data for SEO (FAQPage, WebApplication).
* @type {Record<string, unknown> | Record<string, unknown>[]}
*/
export let data;
</script>
<svelte:head>
{@html `<script type="application/ld+json">${JSON.stringify(data)}</script>`}
</svelte:head>

View file

@ -1,14 +0,0 @@
<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>

View file

@ -1,21 +0,0 @@
<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>

File diff suppressed because one or more lines are too long

View file

@ -1,21 +0,0 @@
<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
>

View file

@ -1,17 +0,0 @@
<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>

View file

@ -1,18 +0,0 @@
<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>

View file

@ -1,19 +0,0 @@
<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>

View file

@ -1,17 +0,0 @@
<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
>

View file

@ -1,93 +0,0 @@
<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>

View file

@ -1,224 +0,0 @@
<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>

View file

@ -1,97 +0,0 @@
<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>

View file

@ -1,194 +0,0 @@
<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;
overflow: hidden;
/* Base */
background-color: #ffffff;
/* Embroidery-inspired layers */
background-image:
/* soft radial "fabric tension" */
radial-gradient(
circle at 20% 30%,
rgba(6, 52, 95, 0.06),
transparent 60%
),
radial-gradient(
circle at 80% 70%,
rgba(6, 52, 95, 0.05),
transparent 60%
),
/* diagonal "thread lines" */
repeating-linear-gradient(
45deg,
rgba(6, 52, 95, 0.04) 0px,
rgba(6, 52, 95, 0.04) 1px,
transparent 1px,
transparent 12px
),
/* opposite direction stitching */
repeating-linear-gradient(
-45deg,
rgba(6, 52, 95, 0.025) 0px,
rgba(6, 52, 95, 0.025) 1px,
transparent 1px,
transparent 14px
);
}
.app::before {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
background-image:
linear-gradient(rgba(6, 52, 95, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(6, 52, 95, 0.03) 1px, transparent 1px);
background-size: 40px 40px;
}
.blob {
position: absolute;
width: 500px;
height: 500px;
background: radial-gradient(circle, rgba(6, 52, 95, 0.15), transparent 70%);
filter: blur(80px);
top: -100px;
right: -100px;
z-index: 0;
}
.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>

View file

@ -1,59 +0,0 @@
@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;
}

View file

@ -1,117 +0,0 @@
: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;
}

View file

@ -1,6 +0,0 @@
:root {
--color-primary: #06345f;
--color-secondary: #094275;
--font-base: 'Merienda';
}

View file

@ -1,34 +1,9 @@
{ {
"seo.title": "About Embroidery Viewer The Story Behind the Tool", "title": " About Embroidery Viewer",
"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 shes 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>Its 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 didnt 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. Thats when I realized something important:",
"quote2": "There are probably many people facing the exact same problem."
},
"product": {
"p1": "People dont want complicated software.",
"p2": "People dont 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"
}
} }

View file

@ -1,4 +0,0 @@
{
"message": "🚀 Embroidery Viewer is now on Android — view your designs anywhere, customize thread & background colors.",
"cta-text": "Install now"
}

View file

@ -0,0 +1,18 @@
{
"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.",
"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"
}

View file

@ -1,90 +0,0 @@
{
"seo.title": "Free DST File Viewer Online — Open Embroidery Files Instantly",
"seo.description": "Open DST files online for free. Preview commercial and home embroidery designs in your browser — no install, no upload to servers. Also supports PES, JEF, EXP & PEC.",
"seo.keywords": "DST viewer, open DST file online, DST file viewer, free DST viewer, embroidery file viewer, how to view embroidery files, convert DST to PES, convert PES to DST, what is a DST file, Tajima embroidery file, online embroidery preview",
"seo.url": "https://embroideryviewer.xyz/dst-file-viewer",
"seo.image": "https://embroideryviewer.xyz/og/dst-viewer.png",
"hero.tagline": "Free · Private · No signup",
"hero.title": "Free DST File Viewer Online — Open Embroidery Files Instantly",
"hero.subtitle": "Preview stitch paths and dimensions in your browser — free, private, and no signup. DST is the industry-standard format for commercial machines and many home brands.",
"hero.cta": "Try Your Design",
"whatIs.title": "What is a DST file?",
"whatIs.p1": "A <strong>DST file</strong> (.dst) is a stitch-data format originally developed by <strong>Tajima</strong> and now the de facto standard for commercial embroidery. Most professional shops and many home machines accept DST.",
"whatIs.p2": "DST files store stitch movements, jumps, trims, and color-change stops. Unlike PES or JEF, DST does not embed a full thread palette in the file — colors are assigned by your machine or software, which is why previews focus on stitch paths and layout.",
"whatIs.p3": "You will see DST on design marketplaces, factory workflows, and converted downloads. Embroidery Viewer lets you open DST online and check size and stitch structure before loading the file on your machine.",
"howTo.title": "How to view DST embroidery files online",
"howTo.step1": "Open the <strong>free embroidery viewer</strong> in your browser.",
"howTo.step2": "Click <strong>Choose files</strong> or drag your .dst file (or .pes, .jef, .exp, .pec) into the drop zone.",
"howTo.step3": "Click <strong>Render files</strong> to generate a stitch preview on screen.",
"howTo.step4": "Review stitch count and dimensions — then download a PNG if you need a reference image.",
"formats.title": "Embroidery file formats we support",
"formats.intro": "Comparing DST with PES, JEF, and other formats Embroidery Viewer opens in your browser:",
"formats.table.headers": {
"format": "Format",
"extension": "Extension",
"machines": "Common machines",
"notes": "Notes"
},
"formats.rows.dst.format": "DST",
"formats.rows.dst.extension": ".dst",
"formats.rows.dst.machines": "Commercial & many home brands",
"formats.rows.dst.notes": "Industry standard; stitch data without embedded thread colors",
"formats.rows.pes.format": "PES / PEC",
"formats.rows.pes.extension": ".pes, .pec",
"formats.rows.pes.machines": "Brother, Babylock",
"formats.rows.pes.notes": "Popular for home embroidery downloads",
"formats.rows.jef.format": "JEF",
"formats.rows.jef.extension": ".jef",
"formats.rows.jef.machines": "Janome, Elna, Kenmore",
"formats.rows.jef.notes": "Janome's native format",
"formats.rows.exp.format": "EXP",
"formats.rows.exp.extension": ".exp",
"formats.rows.exp.machines": "Melco, Bernina (export)",
"formats.rows.exp.notes": "Simple stitch list, widely supported",
"convert.title": "Can I convert DST to PES online?",
"convert.p1": "Embroidery Viewer is a <strong>preview tool</strong>, not a file converter. It lets you open a DST file online and see stitch paths, color stops, and overall size before you stitch or send the file to production.",
"convert.p2": "To convert DST to PES (or PES to DST) use embroidery software such as Wilcom, Hatch, Embrilliance, or Ink/Stitch. After converting, use Embroidery Viewer to preview the new file for free.",
"screenshots.title": "See your DST design before you stitch",
"screenshots.intro": "Preview stitch paths and dimensions without installing desktop software.",
"screenshots.viewer.alt": "Embroidery Viewer showing a DST file preview with stitch paths",
"screenshots.hero.alt": "Upload interface for opening DST embroidery files online",
"faq.title": "Frequently asked questions about DST & embroidery files",
"faq.intro": "Answers to common searches — from opening DST online to converting formats and using the embroidery file viewer.",
"faq.items": {
"openDstOnline": {
"summary": "How can I open a DST file online?",
"description": "Use Embroidery Viewer: open the free viewer, drag your .dst file into the drop zone, and click Render files. Everything runs in your browser — no Tajima software or Windows-only tools required."
},
"pesViewer": {
"summary": "Can I open PES files too?",
"description": "Yes. The same viewer supports PES, DST, JEF, EXP, and PEC. If you work with Brother home formats, see our PES file viewer guide at embroideryviewer.xyz/pes-file-viewer."
},
"howToView": {
"summary": "How do I view embroidery files without software?",
"description": "Open embroideryviewer.xyz in any modern browser. Supported formats include DST, PES, JEF, EXP, and PEC. Your files are processed locally and never sent to a server."
},
"convertDstPes": {
"summary": "How do I convert DST to PES or PES to DST?",
"description": "Conversion requires embroidery editing software — Embrilliance, Hatch, Wilcom, or Ink/Stitch (free). Export to the format your machine needs, then preview the result in Embroidery Viewer before stitching."
},
"whatIsJef": {
"summary": "What is a JEF file?",
"description": "JEF (.jef) is Janome's native embroidery format. It stores stitches and thread colors for Janome, Elna, and compatible machines. See our dedicated JEF guide at embroideryviewer.xyz/jef-file-viewer."
},
"embroideryViewer": {
"summary": "What is the best free embroidery file viewer?",
"description": "Embroidery Viewer is built for quick, private previews: no account, no install, no upload. It supports DST and the formats most embroiderers use daily on desktop and mobile."
},
"isSafe": {
"summary": "Is it safe to open my DST files here?",
"description": "Yes. Files are read and rendered inside your browser using JavaScript. They are not uploaded, stored, or shared with any server."
},
"mobile": {
"summary": "Can I open DST files on my phone?",
"description": "Yes, on modern mobile browsers. Tap Choose files, select your .dst from Files or cloud storage, and tap Render files to preview."
}
},
"cta.title": "Ready to preview your DST design?",
"cta.subtitle": "Open DST, PES, JEF, EXP, or PEC files in seconds — free and private.",
"cta.button": "Try Your Design"
}

View file

@ -1,93 +0,0 @@
{
"seo.title": "Embroidery Viewer Android App — Preview PES, DST & JEF on Your Phone",
"seo.description": "Download Embroidery Viewer for Android. Open PES, DST, JEF, EXP, VP3 and more on your phone — offline, free, with color preview and stitch details. Get it on Google Play.",
"seo.keywords": "embroidery viewer android, embroidery app android, view embroidery files on phone, PES viewer android, DST viewer app, JEF viewer android, free embroidery app, embroidery file preview android, Google Play embroidery viewer",
"seo.url": "https://embroideryviewer.xyz/embroidery-viewer-android",
"seo.image": "https://embroideryviewer.xyz/og/android-app.png",
"playStore.ctaAlt": "Get Embroidery Viewer on Google Play",
"hero.tagline": "Free on Google Play",
"hero.title": "Embroidery Viewer Android App — Preview Designs Anywhere",
"hero.subtitle": "Open embroidery files on your phone or tablet. See stitch paths, colors, and dimensions before you stitch — fast, offline-ready, and built for embroiderers on the go.",
"why.title": "Why use the Android app?",
"why.p1": "The <strong>web viewer</strong> is great at your desk, but the <strong>Android app</strong> goes with you to the machine, craft room, or market. Preview a design from email, Google Drive, or your downloads folder without a laptop.",
"why.p2": "Customize background and thread colors, tap to highlight color sequences, and read stitch count and size at a glance. The app is lightweight and works when you are offline — perfect for quick checks before you hoop.",
"features.title": "What you get in the app",
"features.items.formats": "Open PES, JEF, PEC, VP3, DST, and EXP files",
"features.items.customization": "Customize background and thread colors",
"features.items.highlight": "Tap stitches to highlight thread sequences",
"features.items.metadata": "View stitch count, dimensions, and color breakdown",
"features.items.performance": "Fast, lightweight, and works offline",
"features.items.accessibility": "Accessible controls and readable layouts",
"howTo.title": "How to preview embroidery files on Android",
"howTo.step1": "Install <strong>Embroidery Viewer</strong> from Google Play (link below).",
"howTo.step2": "Tap <strong>Open file</strong> and choose a design from your device or cloud storage.",
"howTo.step3": "Pinch to zoom and pan the stitch preview; tap colors to explore sequences.",
"howTo.step4": "Check stitches, width, and height before you load the design on your machine.",
"formats.title": "Supported embroidery formats on Android",
"formats.intro": "The Android app supports the formats most home and commercial embroiderers use daily:",
"formats.list": "PES, PEC, DST, JEF, EXP, and VP3",
"compare.title": "Android app vs web viewer",
"compare.intro": "Both are free. Pick what fits your workflow — or use both.",
"compare.table.headers.feature": "Feature",
"compare.table.headers.web": "Web viewer",
"compare.table.headers.android": "Android app",
"compare.rows.preview.feature": "Stitch preview",
"compare.rows.preview.web": "Yes — in browser",
"compare.rows.preview.android": "Yes — native app",
"compare.rows.offline.feature": "Offline use",
"compare.rows.offline.web": "Needs internet to load site",
"compare.rows.offline.android": "Works offline after install",
"compare.rows.formats.feature": "File formats",
"compare.rows.formats.web": "PES, DST, JEF, EXP, PEC",
"compare.rows.formats.android": "PES, DST, JEF, EXP, PEC, VP3",
"compare.rows.colors.feature": "Color customization",
"compare.rows.colors.web": "Basic preview",
"compare.rows.colors.android": "Background + thread colors",
"compare.rows.highlight.feature": "Highlight color stops",
"compare.rows.highlight.web": "No",
"compare.rows.highlight.android": "Tap to highlight sequences",
"screenshots.title": "See the app in action",
"screenshots.intro": "A focused mobile experience for checking embroidery files before you stitch.",
"screenshots.app.alt": "Embroidery Viewer Android app showing a design preview on a phone",
"screenshots.play.alt": "Google Play download badge for Embroidery Viewer Android app",
"faq.title": "Frequently asked questions",
"faq.intro": "Common questions about the Embroidery Viewer Android app and viewing embroidery files on mobile.",
"faq.items": {
"isFree": {
"summary": "Is the Embroidery Viewer Android app free?",
"description": "Yes. The app is free to download on Google Play. There is no subscription required to preview embroidery files."
},
"openPesAndroid": {
"summary": "Can I open PES files on Android?",
"description": "Yes. The app opens PES and PEC files from your phone storage, downloads, or file apps. You can preview Brother and compatible home formats without a desktop program."
},
"offline": {
"summary": "Does the app work offline?",
"description": "Yes. After you install the app, you can open files stored on your device without an internet connection — useful at your machine or away from WiFi."
},
"vsWeb": {
"summary": "Should I use the app or the website?",
"description": "Use the website for quick previews on any computer. Use the Android app when you want offline access, VP3 support, color customization, and a touch-friendly interface on your phone."
},
"vp3": {
"summary": "Does the Android app support VP3 files?",
"description": "Yes. The Android app supports VP3 in addition to PES, DST, JEF, EXP, and PEC — helpful for Husqvarna Viking and Pfaff users."
},
"privacy": {
"summary": "Is my data private?",
"description": "Designs you open are processed on your device. Read our app privacy policy for full details on permissions and data handling."
},
"webViewer": {
"summary": "Is there also a free online viewer?",
"description": "Yes. Visit embroideryviewer.xyz/viewer in any browser for PES, DST, JEF, EXP, and PEC — no install required."
},
"ios": {
"summary": "Is there an iPhone app?",
"description": "Embroidery Viewer is currently available on Android via Google Play. For iOS, use the free web viewer at embroideryviewer.xyz."
}
},
"cta.title": "Download Embroidery Viewer for Android",
"cta.subtitle": "Free on Google Play — preview embroidery files in seconds.",
"cta.privacy": "App privacy policy",
"cta.web": "Use the free web viewer instead"
}

View file

@ -1,15 +0,0 @@
{
"seo.title": "Page not found — Embroidery Viewer",
"seo.description": "The page you are looking for could not be found.",
"seo.keywords": "404, page not found, embroidery viewer",
"seo.url": "https://embroideryviewer.xyz",
"notFound.code": "404",
"notFound.title": "This stitch went off pattern",
"notFound.description": "The page you are looking for may have been moved, removed, or never existed. Let's get you back to previewing embroidery designs.",
"notFound.home": "Back to home",
"notFound.viewer": "Open the viewer",
"notFound.imageAlt": "Embroidery design preview on screen",
"generic.title": "Something went wrong",
"generic.description": "We hit a snag while loading this page. Please try again or return to the home page.",
"generic.home": "Back to home"
}

View file

@ -1,90 +0,0 @@
{
"seo.title": "Free EXP File Viewer Online — Open Embroidery Files Instantly",
"seo.description": "Open EXP files online for free. Preview Melco and Bernina embroidery designs in your browser — no install, no upload to servers. Also supports PES, DST, JEF & PEC.",
"seo.keywords": "EXP viewer, open EXP file online, EXP file viewer, free EXP viewer, Melco embroidery file, embroidery file viewer, how to view embroidery files, convert EXP to DST, convert EXP to PES, Bernina EXP embroidery, online embroidery preview",
"seo.url": "https://embroideryviewer.xyz/exp-file-viewer",
"seo.image": "https://embroideryviewer.xyz/og/exp-viewer.png",
"hero.tagline": "Free · Private · No signup",
"hero.title": "Free EXP File Viewer Online — Open Embroidery Files Instantly",
"hero.subtitle": "Preview stitch paths and dimensions in your browser — free, private, and no signup. EXP is widely used by Melco systems and many machines that accept universal stitch formats.",
"hero.cta": "Try Your Design",
"whatIs.title": "What is an EXP file?",
"whatIs.p1": "An <strong>EXP file</strong> (.exp) is a compact embroidery format associated with <strong>Melco</strong> commercial systems and commonly exported by <strong>Bernina</strong> and other software as a universal stitch file.",
"whatIs.p2": "EXP encodes stitch movements as a simple stream of coordinates with stop, trim, and color-change commands. It does not carry a rich thread palette like PES or JEF — similar to DST, colors are often assigned by your machine or software.",
"whatIs.p3": "You will find EXP in commercial workflows, converted downloads, and older design libraries. Embroidery Viewer lets you open EXP online and check stitch structure and size before loading the file on your machine.",
"howTo.title": "How to view EXP embroidery files online",
"howTo.step1": "Open the <strong>free embroidery viewer</strong> in your browser.",
"howTo.step2": "Click <strong>Choose files</strong> or drag your .exp file (or .pes, .dst, .jef, .pec) into the drop zone.",
"howTo.step3": "Click <strong>Render files</strong> to generate a stitch preview on screen.",
"howTo.step4": "Review stitch count and dimensions — then download a PNG if you need a reference image.",
"formats.title": "Embroidery file formats we support",
"formats.intro": "Comparing EXP with PES, DST, JEF, and other formats Embroidery Viewer opens in your browser:",
"formats.table.headers": {
"format": "Format",
"extension": "Extension",
"machines": "Common machines",
"notes": "Notes"
},
"formats.rows.exp.format": "EXP",
"formats.rows.exp.extension": ".exp",
"formats.rows.exp.machines": "Melco, Bernina (export)",
"formats.rows.exp.notes": "Compact stitch stream; widely supported",
"formats.rows.pes.format": "PES / PEC",
"formats.rows.pes.extension": ".pes, .pec",
"formats.rows.pes.machines": "Brother, Babylock",
"formats.rows.pes.notes": "Popular for home embroidery downloads",
"formats.rows.dst.format": "DST",
"formats.rows.dst.extension": ".dst",
"formats.rows.dst.machines": "Commercial & many home brands",
"formats.rows.dst.notes": "Industry standard for production shops",
"formats.rows.jef.format": "JEF",
"formats.rows.jef.extension": ".jef",
"formats.rows.jef.machines": "Janome, Elna, Kenmore",
"formats.rows.jef.notes": "Janome's native format",
"convert.title": "Can I convert EXP to DST or PES online?",
"convert.p1": "Embroidery Viewer is a <strong>preview tool</strong>, not a file converter. It lets you open an EXP file online and see stitch paths, color stops, and overall size before you stitch or send the file to production.",
"convert.p2": "To convert EXP to DST, PES, or JEF use embroidery software such as Wilcom, Hatch, Embrilliance, or Ink/Stitch. After converting, you can preview the new file in Embroidery Viewer for free.",
"screenshots.title": "See your EXP design before you stitch",
"screenshots.intro": "Preview stitch paths and dimensions without installing desktop software.",
"screenshots.viewer.alt": "Embroidery Viewer showing an EXP file preview with stitch paths",
"screenshots.hero.alt": "Upload interface for opening EXP embroidery files online",
"faq.title": "Frequently asked questions about EXP & embroidery files",
"faq.intro": "Answers to common searches — from opening EXP online to converting formats and using the embroidery file viewer.",
"faq.items": {
"openExpOnline": {
"summary": "How can I open an EXP file online?",
"description": "Use Embroidery Viewer: open the free viewer, drag your .exp file into the drop zone, and click Render files. Everything runs in your browser — no Melco software or Windows-only tools required."
},
"whatIsExp": {
"summary": "What is an EXP file used for?",
"description": "EXP is a stitch-data format used by Melco commercial embroidery systems and often exported from Bernina and other software. It stores movement commands for embroidery machines that accept the format."
},
"otherFormats": {
"summary": "Can I open PES, DST, or JEF files too?",
"description": "Yes. The same viewer supports PES, DST, JEF, EXP, and PEC. See our guides at embroideryviewer.xyz/pes-file-viewer, /dst-file-viewer, and /jef-file-viewer."
},
"howToView": {
"summary": "How do I view embroidery files without software?",
"description": "Open embroideryviewer.xyz in any modern browser. Supported formats include EXP, PES, DST, JEF, and PEC. Your files are processed locally and never sent to a server."
},
"convertExp": {
"summary": "How do I convert EXP to DST or PES?",
"description": "Conversion requires embroidery editing software — Embrilliance, Hatch, Wilcom, or Ink/Stitch (free). Export to the format your machine needs, then preview the result in Embroidery Viewer before stitching."
},
"embroideryViewer": {
"summary": "What is the best free embroidery file viewer?",
"description": "Embroidery Viewer is built for quick, private previews: no account, no install, no upload. It supports EXP and the formats most embroiderers use daily on desktop and mobile."
},
"isSafe": {
"summary": "Is it safe to open my EXP files here?",
"description": "Yes. Files are read and rendered inside your browser using JavaScript. They are not uploaded, stored, or shared with any server."
},
"mobile": {
"summary": "Can I open EXP files on my phone?",
"description": "Yes, on modern mobile browsers. Tap Choose files, select your .exp from Files or cloud storage, and tap Render files to preview."
}
},
"cta.title": "Ready to preview your EXP design?",
"cta.subtitle": "Open EXP, PES, DST, JEF, or PEC files in seconds — free and private.",
"cta.button": "Try Your Design"
}

View file

@ -1,30 +0,0 @@
{
"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 PES, DST, JEF, EXP, and PEC — the most common formats for home and commercial embroidery 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."
}
}
}

View file

@ -1,27 +0,0 @@
{
"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"
}

View file

@ -1,20 +1,7 @@
{ {
"slogan": "Preview your embroidery designs instantly — no software needed", "about": " About",
"resources": "Resources", "privacy.policy": "🔐 Privacy Policy",
"contact-title": "Contact", "terms.of.service": "📝 Terms of Service",
"contact-description": "Do you want to help or have any questions? Contact us.", "copyright": "Copyright © {{year}} <a href=\"{{website}}\" target=\"_blank\" rel=\"noreferrer\">Leonardo Murça</a>. <br/> All rights reserved.",
"back-to-top": { "version": "🧵 Version: {{version}}"
"label": "Back To Top",
"aria-label": "Back to top of the page"
},
"about": "About",
"pesViewer": "PES File Viewer",
"dstViewer": "DST File Viewer",
"jefViewer": "JEF File Viewer",
"expViewer": "EXP File Viewer",
"androidApp": "Android App",
"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}}"
} }

View file

@ -1,6 +1,7 @@
{ {
"homeNav": "Home", "languageSwitch": "🇧🇷",
"aboutNav": "About", "homeNav": "🏠 Home",
"viewerNav": "Viewer", "aboutNav": " About",
"supportUsNav": "Support Us" "viewerNav": "🧵 Viewer",
"donateNav": "💖 Donate"
} }

View file

@ -1,5 +0,0 @@
{
"title": "Preview your embroidery designs instantly — no software needed",
"description": "Fast, private & no signup required",
"cta": "Try Your Design"
}

View file

@ -1,12 +1,22 @@
{ {
"seo.title": "Free Online Embroidery File Viewer — Fast, Private & No Signup", "main.title": "🧵 Free Online Embroidery File Viewer",
"seo.description": "Preview embroidery files instantly in your browser with Embroidery Viewer. Supports PES, DST, JEF, EXP, and PEC. No install, no signup — free and private.", "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>",
"seo.keywords": "embroidery viewer, online embroidery viewer, embroidery file preview, DST viewer, PES viewer, free embroidery tool, JEF viewer, EXP embroidery, embroidery preview tool, browser embroidery renderer", "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 its 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.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/viewer.png", "seo.image": "https://embroideryviewer.xyz/og/"
"howTo.title": "How to preview embroidery files online",
"howTo.step1": "Open Embroidery Viewer in your web browser.",
"howTo.step2": "Go to the online viewer page.",
"howTo.step3": "Drag and drop your embroidery file (PES, DST, JEF, EXP, or PEC).",
"howTo.step4": "Preview your design instantly — no software installation required."
} }

View file

@ -1,90 +0,0 @@
{
"seo.title": "Free JEF File Viewer Online — Open Janome Embroidery Files Instantly",
"seo.description": "Open JEF files online for free. Preview Janome and Elna embroidery designs in your browser — no install, no upload to servers. Also supports PES, DST, EXP & PEC.",
"seo.keywords": "what is a JEF file, JEF viewer, open JEF file online, JEF file viewer, free JEF viewer, Janome embroidery file, embroidery file viewer, how to view embroidery files, convert JEF to PES, convert JEF to DST, online embroidery preview",
"seo.url": "https://embroideryviewer.xyz/jef-file-viewer",
"seo.image": "https://embroideryviewer.xyz/og/jef-viewer.png",
"hero.tagline": "Free · Private · No signup",
"hero.title": "Free JEF File Viewer Online — Open Janome Embroidery Files Instantly",
"hero.subtitle": "Preview stitch paths, colors, and dimensions in your browser — free, private, and no signup. JEF is the native format for Janome, Elna, and compatible home machines.",
"hero.cta": "Try Your Design",
"whatIs.title": "What is a JEF file?",
"whatIs.p1": "A <strong>JEF file</strong> (.jef) is the native embroidery format for <strong>Janome</strong> and <strong>Elna</strong> machines. Many Kenmore and other Janome-compatible models also read JEF.",
"whatIs.p2": "JEF files contain stitch coordinates, color-change commands, and a thread palette — similar to PES for Brother machines. If you download designs for a Janome Memory Craft or Elna Express, they often arrive as .jef.",
"whatIs.p3": "Embroidery Viewer lets you open JEF online to check layout, stitch count, and size before you hoop and stitch — without installing Janome Artistic Digitizer or other desktop software.",
"howTo.title": "How to view JEF embroidery files online",
"howTo.step1": "Open the <strong>free embroidery viewer</strong> in your browser.",
"howTo.step2": "Click <strong>Choose files</strong> or drag your .jef file (or .pes, .dst, .exp, .pec) into the drop zone.",
"howTo.step3": "Click <strong>Render files</strong> to generate a stitch preview on screen.",
"howTo.step4": "Review colors, stitch count, and dimensions — then download a PNG if you need a reference image.",
"formats.title": "Embroidery file formats we support",
"formats.intro": "Comparing JEF with PES, DST, and other formats Embroidery Viewer opens in your browser:",
"formats.table.headers": {
"format": "Format",
"extension": "Extension",
"machines": "Common machines",
"notes": "Notes"
},
"formats.rows.jef.format": "JEF",
"formats.rows.jef.extension": ".jef",
"formats.rows.jef.machines": "Janome, Elna, Kenmore",
"formats.rows.jef.notes": "Native Janome format with embedded thread colors",
"formats.rows.pes.format": "PES / PEC",
"formats.rows.pes.extension": ".pes, .pec",
"formats.rows.pes.machines": "Brother, Babylock",
"formats.rows.pes.notes": "Popular for home embroidery downloads",
"formats.rows.dst.format": "DST",
"formats.rows.dst.extension": ".dst",
"formats.rows.dst.machines": "Commercial & many home brands",
"formats.rows.dst.notes": "Industry standard for production shops",
"formats.rows.exp.format": "EXP",
"formats.rows.exp.extension": ".exp",
"formats.rows.exp.machines": "Melco, Bernina (export)",
"formats.rows.exp.notes": "Simple stitch list, widely supported",
"convert.title": "Can I convert JEF to PES or DST online?",
"convert.p1": "Embroidery Viewer is a <strong>preview tool</strong>, not a file converter. It lets you open a JEF file online and see stitch paths, color stops, and overall size before you load the design on your Janome or Elna machine.",
"convert.p2": "To convert JEF to PES, DST, or another format use embroidery software such as Embrilliance, Hatch, or Ink/Stitch. After converting, preview the new file in Embroidery Viewer for free.",
"screenshots.title": "See your JEF design before you stitch",
"screenshots.intro": "Preview stitch paths, colors, and dimensions without installing desktop software.",
"screenshots.viewer.alt": "Embroidery Viewer showing a JEF file preview with stitch paths and colors",
"screenshots.hero.alt": "Upload interface for opening JEF embroidery files online",
"faq.title": "Frequently asked questions about JEF & embroidery files",
"faq.intro": "Answers to common searches — from what is a JEF file to opening designs online and converting formats.",
"faq.items": {
"openJefOnline": {
"summary": "How can I open a JEF file online?",
"description": "Use Embroidery Viewer: open the free viewer, drag your .jef file into the drop zone, and click Render files. Everything runs in your browser — no Janome software or Windows-only tools required."
},
"whatIsJef": {
"summary": "What is a JEF file used for?",
"description": "JEF is Janome's embroidery format. It stores stitches and thread colors for Janome, Elna, and compatible machines. You use it to load designs from USB or transfer designs downloaded from the web."
},
"pesViewer": {
"summary": "Can I open PES or DST files too?",
"description": "Yes. The same viewer supports PES, DST, JEF, EXP, and PEC. For Brother formats see embroideryviewer.xyz/pes-file-viewer; for DST see embroideryviewer.xyz/dst-file-viewer."
},
"howToView": {
"summary": "How do I view embroidery files without software?",
"description": "Open embroideryviewer.xyz in any modern browser. Supported formats include JEF, PES, DST, EXP, and PEC. Your files are processed locally and never sent to a server."
},
"convertJef": {
"summary": "How do I convert JEF to PES or DST?",
"description": "Conversion requires embroidery editing software — Embrilliance, Hatch, Wilcom, or Ink/Stitch (free). Export to the format your machine needs, then preview the result in Embroidery Viewer before stitching."
},
"embroideryViewer": {
"summary": "What is the best free embroidery file viewer?",
"description": "Embroidery Viewer is built for quick, private previews: no account, no install, no upload. It supports JEF and the formats most home embroiderers use daily on desktop and mobile."
},
"isSafe": {
"summary": "Is it safe to open my JEF files here?",
"description": "Yes. Files are read and rendered inside your browser using JavaScript. They are not uploaded, stored, or shared with any server."
},
"mobile": {
"summary": "Can I open JEF files on my phone?",
"description": "Yes, on modern mobile browsers. Tap Choose files, select your .jef from Files or cloud storage, and tap Render files to preview."
}
},
"cta.title": "Ready to preview your JEF design?",
"cta.subtitle": "Open JEF, PES, DST, EXP, or PEC files in seconds — free and private.",
"cta.button": "Try Your Design"
}

View file

@ -1,10 +0,0 @@
{
"title": "Privacy Policy",
"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>",
"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.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.image": "https://embroideryviewer.xyz/og/privacy-policy.png"
}

View file

@ -1,17 +0,0 @@
{
"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"
}

View file

@ -1,90 +0,0 @@
{
"seo.title": "Free PES File Viewer Online — Open Embroidery Files Instantly",
"seo.description": "Open PES files online for free. Preview Brother embroidery designs in your browser — no install, no upload to servers. Also supports DST, JEF, EXP & PEC.",
"seo.keywords": "open PES file online, PES viewer, PES file viewer, free PES viewer, embroidery file viewer, how to view embroidery files, DST viewer, convert PES to DST, what is a JEF file, Brother embroidery file, online embroidery preview",
"seo.url": "https://embroideryviewer.xyz/pes-file-viewer",
"seo.image": "https://embroideryviewer.xyz/og/pes-viewer.png",
"hero.tagline": "Free · Private · No signup",
"hero.title": "Free PES File Viewer Online — Open Embroidery Files Instantly",
"hero.subtitle": "Preview stitch paths, colors, and dimensions in your browser — free, private, and no signup. Works with Brother machines and most home embroidery software.",
"hero.cta": "Try Your Design",
"whatIs.title": "What is a PES file?",
"whatIs.p1": "A <strong>PES file</strong> (.pes) is a proprietary embroidery format created by <strong>Brother</strong> and used by many home embroidery machines — including Brother, Babylock, Bernina (with conversion), and others that accept PES.",
"whatIs.p2": "Inside a PES file you will find stitch coordinates, color-change commands, jump/trim instructions, and a color palette. PES is one of the most common formats for downloadable embroidery designs on Etsy, Creative Fabrica, and embroidery forums.",
"whatIs.p3": "PES files often travel with a companion <strong>.pec</strong> file (older palette/stitch container). Embroidery Viewer reads both so you can preview designs without installing Wilcom, PE-Design, or Embrilliance.",
"howTo.title": "How to view embroidery files online",
"howTo.step1": "Open the <strong>free embroidery viewer</strong> in your browser.",
"howTo.step2": "Click <strong>Choose files</strong> or drag your .pes, .dst, .jef, .exp, or .pec file into the drop zone.",
"howTo.step3": "Click <strong>Render files</strong> to generate a stitch preview on screen.",
"howTo.step4": "Review colors, stitch count, and dimensions — then download a PNG if you need a reference image.",
"formats.title": "Embroidery file formats we support",
"formats.intro": "Looking for a DST viewer or wondering what a JEF file is? Here is a quick comparison of the formats Embroidery Viewer opens in your browser:",
"formats.table.headers": {
"format": "Format",
"extension": "Extension",
"machines": "Common machines",
"notes": "Notes"
},
"formats.rows.pes.format": "PES / PEC",
"formats.rows.pes.extension": ".pes, .pec",
"formats.rows.pes.machines": "Brother, Babylock",
"formats.rows.pes.notes": "Most popular for home embroidery downloads",
"formats.rows.dst.format": "DST",
"formats.rows.dst.extension": ".dst",
"formats.rows.dst.machines": "Commercial & many home brands",
"formats.rows.dst.notes": "Industry-standard; limited colors in file",
"formats.rows.jef.format": "JEF",
"formats.rows.jef.extension": ".jef",
"formats.rows.jef.machines": "Janome, Elna, Kenmore",
"formats.rows.jef.notes": "Janome's native format",
"formats.rows.exp.format": "EXP",
"formats.rows.exp.extension": ".exp",
"formats.rows.exp.machines": "Melco, Bernina (export)",
"formats.rows.exp.notes": "Simple stitch list, widely supported",
"convert.title": "Can I convert PES to DST online?",
"convert.p1": "Embroidery Viewer is a <strong>preview tool</strong>, not a file converter. It lets you open a PES file online and see exactly how the design stitches out — stitch paths, color stops, and size — before you load it on your machine.",
"convert.p2": "To convert PES to DST you will need dedicated embroidery software (Wilcom, Hatch, Embrilliance, Ink/Stitch, etc.) or your machine's included editor. After converting, you can return here to preview the DST file for free.",
"screenshots.title": "See your embroidery design before you stitch",
"screenshots.intro": "Preview stitch paths, color layers, and dimensions without installing desktop software.",
"screenshots.viewer.alt": "Embroidery Viewer showing a PES file preview with stitch paths and colors",
"screenshots.hero.alt": "Drag and drop interface for opening PES embroidery files online",
"faq.title": "Frequently asked questions about PES & embroidery files",
"faq.intro": "Answers to the questions embroiderers search for most — from opening PES online to understanding JEF and DST files.",
"faq.items": {
"openPesOnline": {
"summary": "How can I open a PES file online?",
"description": "Use the free viewer on this page: drag your .pes file into the drop zone and click Render files. Embroidery Viewer runs entirely in your browser — no Brother software or Windows-only tools required."
},
"dstViewer": {
"summary": "Is there a free DST viewer online?",
"description": "Yes. Embroidery Viewer opens DST files the same way as PES — upload or drag the file, then preview stitches and dimensions. See our dedicated DST guide at embroideryviewer.xyz/dst-file-viewer."
},
"howToView": {
"summary": "How do I view embroidery files without software?",
"description": "Open embroideryviewer.xyz in any modern browser (Chrome, Safari, Firefox, Edge). Supported formats include PES, DST, JEF, EXP, and PEC. Your files are processed locally and never sent to a server."
},
"convertPesDst": {
"summary": "How do I convert PES to DST?",
"description": "Conversion requires embroidery editing software — for example Embrilliance, Hatch, or Ink/Stitch (free). Export or save as DST, then use Embroidery Viewer to preview the result online before stitching."
},
"whatIsJef": {
"summary": "What is a JEF file?",
"description": "JEF (.jef) is Janome's native embroidery format. It stores stitches and thread colors for Janome, Elna, and compatible machines. See our dedicated JEF guide at embroideryviewer.xyz/jef-file-viewer."
},
"embroideryViewer": {
"summary": "What is the best free embroidery file viewer?",
"description": "Embroidery Viewer is built for quick, private previews: no account, no install, no upload. It supports the formats most home embroiderers use daily and works on desktop and mobile browsers."
},
"isSafe": {
"summary": "Is it safe to open my embroidery files here?",
"description": "Yes. Files are read and rendered inside your browser using JavaScript. They are not uploaded, stored, or shared with any server."
},
"mobile": {
"summary": "Can I open PES files on my phone?",
"description": "Yes, on modern mobile browsers. Tap Choose files, select your .pes from Files or cloud storage, and tap Render files to preview."
}
},
"cta.title": "Ready to preview your design?",
"cta.subtitle": "Open PES, DST, JEF, EXP, or PEC files in seconds — free and private.",
"cta.button": "Try Your Design"
}

View file

@ -1,8 +1,8 @@
{ {
"title": "Privacy Policy", "title": "🔐 Privacy Policy",
"last.update": "Last updated: Apr 24, 2026", "last.update": "Last updated: May 9, 2025",
"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>", "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>",
"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",

View file

@ -1,10 +0,0 @@
{
"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"
}

View file

@ -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",

View file

@ -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",

View file

@ -16,7 +16,7 @@ export const SUPPORTED_LOCALES = Object.freeze({
/** @type {import('sveltekit-i18n').Config} */ /** @type {import('sveltekit-i18n').Config} */
const config = { const config = {
initLocale: SUPPORTED_LOCALES.EN_US, initLocale: navigator.language,
fallbackLocale: SUPPORTED_LOCALES.EN_US, fallbackLocale: SUPPORTED_LOCALES.EN_US,
loaders: [ loaders: [
{ {
@ -43,9 +43,9 @@ const config = {
}, },
{ {
locale: SUPPORTED_LOCALES.EN_US, locale: SUPPORTED_LOCALES.EN_US,
key: 'support-us', key: 'donate',
routes: ['/support-us'], routes: ['/donate'],
loader: async () => (await import('./en-US/support-us.json')).default, loader: async () => (await import('./en-US/donate.json')).default,
}, },
{ {
locale: SUPPORTED_LOCALES.EN_US, locale: SUPPORTED_LOCALES.EN_US,
@ -53,13 +53,6 @@ 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,
key: 'mobile.app.privacy.policy',
routes: ['/mobile-app/privacy-policy'],
loader: async () =>
(await import('./en-US/mobile-app-privacy-policy.json')).default,
},
{ {
locale: SUPPORTED_LOCALES.EN_US, locale: SUPPORTED_LOCALES.EN_US,
key: 'terms.of.service', key: 'terms.of.service',
@ -73,41 +66,6 @@ const config = {
routes: ['/viewer'], routes: ['/viewer'],
loader: async () => (await import('./en-US/viewer.json')).default, loader: async () => (await import('./en-US/viewer.json')).default,
}, },
{
locale: SUPPORTED_LOCALES.EN_US,
key: 'pes-file-viewer',
routes: ['/pes-file-viewer'],
loader: async () =>
(await import('./en-US/pes-file-viewer.json')).default,
},
{
locale: SUPPORTED_LOCALES.EN_US,
key: 'dst-file-viewer',
routes: ['/dst-file-viewer'],
loader: async () =>
(await import('./en-US/dst-file-viewer.json')).default,
},
{
locale: SUPPORTED_LOCALES.EN_US,
key: 'jef-file-viewer',
routes: ['/jef-file-viewer'],
loader: async () =>
(await import('./en-US/jef-file-viewer.json')).default,
},
{
locale: SUPPORTED_LOCALES.EN_US,
key: 'exp-file-viewer',
routes: ['/exp-file-viewer'],
loader: async () =>
(await import('./en-US/exp-file-viewer.json')).default,
},
{
locale: SUPPORTED_LOCALES.EN_US,
key: 'embroidery-viewer-android',
routes: ['/embroidery-viewer-android'],
loader: async () =>
(await import('./en-US/embroidery-viewer-android.json')).default,
},
{ {
locale: SUPPORTED_LOCALES.PT_BR, locale: SUPPORTED_LOCALES.PT_BR,
key: 'header', key: 'header',
@ -132,9 +90,9 @@ const config = {
}, },
{ {
locale: SUPPORTED_LOCALES.PT_BR, locale: SUPPORTED_LOCALES.PT_BR,
key: 'support-us', key: 'donate',
routes: ['/support-us'], routes: ['/donate'],
loader: async () => (await import('./pt-BR/support-us.json')).default, loader: async () => (await import('./pt-BR/donate.json')).default,
}, },
{ {
locale: SUPPORTED_LOCALES.PT_BR, locale: SUPPORTED_LOCALES.PT_BR,
@ -142,13 +100,6 @@ const config = {
routes: ['/privacy-policy'], routes: ['/privacy-policy'],
loader: async () => (await import('./pt-BR/privacy-policy.json')).default, loader: async () => (await import('./pt-BR/privacy-policy.json')).default,
}, },
{
locale: SUPPORTED_LOCALES.PT_BR,
key: 'mobile.app.privacy.policy',
routes: ['/mobile-app/privacy-policy'],
loader: async () =>
(await import('./pt-BR/mobile-app-privacy-policy.json')).default,
},
{ {
locale: SUPPORTED_LOCALES.PT_BR, locale: SUPPORTED_LOCALES.PT_BR,
key: 'terms.of.service', key: 'terms.of.service',
@ -162,101 +113,6 @@ 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: 'pes-file-viewer',
routes: ['/pes-file-viewer'],
loader: async () =>
(await import('./pt-BR/pes-file-viewer.json')).default,
},
{
locale: SUPPORTED_LOCALES.PT_BR,
key: 'dst-file-viewer',
routes: ['/dst-file-viewer'],
loader: async () =>
(await import('./pt-BR/dst-file-viewer.json')).default,
},
{
locale: SUPPORTED_LOCALES.PT_BR,
key: 'jef-file-viewer',
routes: ['/jef-file-viewer'],
loader: async () =>
(await import('./pt-BR/jef-file-viewer.json')).default,
},
{
locale: SUPPORTED_LOCALES.PT_BR,
key: 'exp-file-viewer',
routes: ['/exp-file-viewer'],
loader: async () =>
(await import('./pt-BR/exp-file-viewer.json')).default,
},
{
locale: SUPPORTED_LOCALES.PT_BR,
key: 'embroidery-viewer-android',
routes: ['/embroidery-viewer-android'],
loader: async () =>
(await import('./pt-BR/embroidery-viewer-android.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,
},
{
locale: SUPPORTED_LOCALES.PT_BR,
key: 'announcement',
loader: async () => (await import('./pt-BR/announcement.json')).default,
},
{
locale: SUPPORTED_LOCALES.EN_US,
key: 'announcement',
loader: async () => (await import('./en-US/announcement.json')).default,
},
{
locale: SUPPORTED_LOCALES.PT_BR,
key: 'error',
loader: async () => (await import('./pt-BR/error.json')).default,
},
{
locale: SUPPORTED_LOCALES.EN_US,
key: 'error',
loader: async () => (await import('./en-US/error.json')).default,
},
], ],
}; };
@ -271,10 +127,7 @@ export const {
} = new i18n(config); } = new i18n(config);
locale.subscribe(($locale) => { locale.subscribe(($locale) => {
if (typeof localStorage !== 'undefined' && $locale) { if (typeof document !== 'undefined') {
const existing = localStorage.getItem('locale'); document.cookie = `locale=${$locale}; path=/; SameSite=Strict;`;
if (existing !== $locale) {
localStorage.setItem('locale', $locale);
}
} }
}); });

View file

@ -1,34 +1,9 @@
{ {
"seo.title": "Sobre o Embroidery Viewer - Por que esta ferramenta foi criada", "title": " Sobre o Embroidery Viewer",
"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"
}
} }

View file

@ -1,4 +0,0 @@
{
"message": "🚀 Embroidery Viewer agora está no Android — visualize seus designs em qualquer lugar, personalize as cores das linhas e do fundo.",
"cta-text": "Instale agora"
}

View file

@ -0,0 +1,18 @@
{
"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"
}

View file

@ -1,90 +0,0 @@
{
"seo.title": "Visualizador de Arquivos DST Online Grátis — Abra Bordados na Hora",
"seo.description": "Abra arquivos DST online gratuitamente. Visualize bordados comerciais e domésticos no navegador — sem instalar, sem enviar para servidores. Também suporta PES, JEF, EXP e PEC.",
"seo.keywords": "visualizador DST, abrir arquivo DST online, visualizador de bordado, como ver arquivo de bordado, converter DST para PES, converter PES para DST, o que é arquivo DST, arquivo Tajima bordado, preview bordado online",
"seo.url": "https://embroideryviewer.xyz/dst-file-viewer",
"seo.image": "https://embroideryviewer.xyz/og/dst-viewer.png",
"hero.tagline": "Grátis · Privado · Sem cadastro",
"hero.title": "Visualizador de Arquivos DST Online Grátis — Abra Bordados na Hora",
"hero.subtitle": "Veja caminhos de pontos e dimensões no navegador — grátis, privado e sem cadastro. DST é o padrão da indústria para máquinas comerciais e muitas domésticas.",
"hero.cta": "Experimente seu desenho",
"whatIs.title": "O que é um arquivo DST?",
"whatIs.p1": "Um <strong>arquivo DST</strong> (.dst) é um formato de dados de pontos criado originalmente pela <strong>Tajima</strong> e hoje o padrão de fato no bordado comercial. A maioria das oficinas profissionais e muitas máquinas domésticas aceita DST.",
"whatIs.p2": "Arquivos DST armazenam movimentos de ponto, saltos, aparos e paradas de troca de cor. Diferente de PES ou JEF, o DST não embute uma paleta completa de linhas — as cores são definidas pela máquina ou software.",
"whatIs.p3": "Você encontra DST em marketplaces, fluxos de fábrica e downloads convertidos. O Embroidery Viewer permite abrir DST online e conferir tamanho e estrutura antes de bordar.",
"howTo.title": "Como visualizar arquivos DST de bordado online",
"howTo.step1": "Abra o <strong>visualizador de bordado gratuito</strong> no navegador.",
"howTo.step2": "Clique em <strong>Escolher arquivos</strong> ou arraste seu .dst (ou .pes, .jef, .exp, .pec) para a área de soltar.",
"howTo.step3": "Clique em <strong>Renderizar arquivos</strong> para gerar a prévia na tela.",
"howTo.step4": "Revise contagem de pontos e dimensões — e baixe um PNG se precisar de referência.",
"formats.title": "Formatos de bordado suportados",
"formats.intro": "Comparação entre DST, PES, JEF e outros formatos que o Embroidery Viewer abre no navegador:",
"formats.table.headers": {
"format": "Formato",
"extension": "Extensão",
"machines": "Máquinas comuns",
"notes": "Observações"
},
"formats.rows.dst.format": "DST",
"formats.rows.dst.extension": ".dst",
"formats.rows.dst.machines": "Comercial e várias domésticas",
"formats.rows.dst.notes": "Padrão da indústria; dados de ponto sem cores embutidas",
"formats.rows.pes.format": "PES / PEC",
"formats.rows.pes.extension": ".pes, .pec",
"formats.rows.pes.machines": "Brother, Babylock",
"formats.rows.pes.notes": "Popular em downloads domésticos",
"formats.rows.jef.format": "JEF",
"formats.rows.jef.extension": ".jef",
"formats.rows.jef.machines": "Janome, Elna, Kenmore",
"formats.rows.jef.notes": "Formato nativo Janome",
"formats.rows.exp.format": "EXP",
"formats.rows.exp.extension": ".exp",
"formats.rows.exp.machines": "Melco, Bernina (exportação)",
"formats.rows.exp.notes": "Lista simples de pontos, amplamente suportado",
"convert.title": "Posso converter DST para PES online?",
"convert.p1": "O Embroidery Viewer é uma <strong>ferramenta de prévia</strong>, não um conversor. Ele permite abrir DST online e ver pontos, paradas de cor e tamanho antes de bordar ou enviar para produção.",
"convert.p2": "Para converter DST em PES (ou PES em DST) use software de bordado como Wilcom, Hatch, Embrilliance ou Ink/Stitch. Depois visualize o novo arquivo gratuitamente no visualizador.",
"screenshots.title": "Veja seu DST antes de costurar",
"screenshots.intro": "Visualize pontos e dimensões sem instalar software no computador.",
"screenshots.viewer.alt": "Embroidery Viewer exibindo prévia de arquivo DST com caminhos de pontos",
"screenshots.hero.alt": "Interface de envio para abrir arquivos DST de bordado online",
"faq.title": "Perguntas frequentes sobre DST e arquivos de bordado",
"faq.intro": "Respostas às buscas mais comuns — de abrir DST online a converter formatos e usar o visualizador.",
"faq.items": {
"openDstOnline": {
"summary": "Como abrir um arquivo DST online?",
"description": "Use o Embroidery Viewer: abra o visualizador gratuito, arraste o .dst para a área e clique em Renderizar arquivos. Tudo roda no navegador — sem software Tajima ou ferramentas só para Windows."
},
"pesViewer": {
"summary": "Posso abrir arquivos PES também?",
"description": "Sim. O mesmo visualizador suporta PES, DST, JEF, EXP e PEC. Para formatos Brother, veja o guia em embroideryviewer.xyz/pes-file-viewer."
},
"howToView": {
"summary": "Como ver arquivos de bordado sem software?",
"description": "Abra embroideryviewer.xyz em qualquer navegador moderno. Formatos: DST, PES, JEF, EXP e PEC. Os arquivos são processados localmente e nunca enviados a um servidor."
},
"convertDstPes": {
"summary": "Como converter DST para PES ou PES para DST?",
"description": "A conversão exige software de edição — Embrilliance, Hatch, Wilcom ou Ink/Stitch (grátis). Exporte para o formato da sua máquina e visualize o resultado no Embroidery Viewer antes de bordar."
},
"whatIsJef": {
"summary": "O que é um arquivo JEF?",
"description": "JEF (.jef) é o formato nativo da Janome. Armazena pontos e cores para Janome, Elna e compatíveis. Veja o guia JEF em embroideryviewer.xyz/jef-file-viewer."
},
"embroideryViewer": {
"summary": "Qual o melhor visualizador gratuito de bordado?",
"description": "O Embroidery Viewer foi feito para prévias rápidas e privadas: sem conta, sem instalação, sem upload. Suporta DST e os formatos mais usados no desktop e celular."
},
"isSafe": {
"summary": "É seguro abrir meus arquivos DST aqui?",
"description": "Sim. Os arquivos são lidos e renderizados no navegador com JavaScript. Não são enviados, armazenados nem compartilhados com servidores."
},
"mobile": {
"summary": "Posso abrir DST no celular?",
"description": "Sim, em navegadores móveis modernos. Toque em Escolher arquivos, selecione o .dst e toque em Renderizar arquivos."
}
},
"cta.title": "Pronto para ver seu DST?",
"cta.subtitle": "Abra DST, PES, JEF, EXP ou PEC em segundos — grátis e privado.",
"cta.button": "Experimente seu desenho"
}

View file

@ -1,93 +0,0 @@
{
"seo.title": "App Embroidery Viewer para Android — Veja PES, DST e JEF no Celular",
"seo.description": "Baixe o Embroidery Viewer para Android. Abra PES, DST, JEF, EXP, VP3 e mais no celular — offline, grátis, com prévia de cores e detalhes de pontos. Disponível na Google Play.",
"seo.keywords": "visualizador de bordado android, app bordado android, ver arquivo de bordado no celular, visualizador PES android, app DST bordado, visualizador JEF android, app bordado grátis, prévia bordado android, Google Play embroidery viewer",
"seo.url": "https://embroideryviewer.xyz/embroidery-viewer-android",
"seo.image": "https://embroideryviewer.xyz/og/android-app.png",
"playStore.ctaAlt": "Baixar Embroidery Viewer na Google Play",
"hero.tagline": "Grátis na Google Play",
"hero.title": "App Embroidery Viewer para Android — Veja Bordados em Qualquer Lugar",
"hero.subtitle": "Abra arquivos de bordado no celular ou tablet. Veja pontos, cores e dimensões antes de costurar — rápido, funciona offline e feito para quem borda fora do computador.",
"why.title": "Por que usar o app Android?",
"why.p1": "O <strong>visualizador web</strong> é ótimo na mesa, mas o <strong>app Android</strong> vai com você até a máquina, o ateliê ou a feira. Visualize um desenho do e-mail, Google Drive ou downloads sem notebook.",
"why.p2": "Personalize cores de fundo e linha, toque para destacar sequências de cor e veja contagem de pontos e tamanho na hora. O app é leve e funciona offline — ideal para conferir antes de prender no bastidor.",
"features.title": "O que o app oferece",
"features.items.formats": "Abre arquivos PES, JEF, PEC, VP3, DST e EXP",
"features.items.customization": "Personalize cores de fundo e das linhas",
"features.items.highlight": "Toque nos pontos para destacar sequências de cor",
"features.items.metadata": "Veja pontos, dimensões e detalhes das cores",
"features.items.performance": "Rápido, leve e funciona offline",
"features.items.accessibility": "Controles acessíveis e layout legível",
"howTo.title": "Como visualizar bordados no Android",
"howTo.step1": "Instale o <strong>Embroidery Viewer</strong> na Google Play (link abaixo).",
"howTo.step2": "Toque em <strong>Abrir arquivo</strong> e escolha um desenho no dispositivo ou na nuvem.",
"howTo.step3": "Use pinça para zoom; toque nas cores para explorar sequências.",
"howTo.step4": "Confira pontos, largura e altura antes de carregar na máquina.",
"formats.title": "Formatos suportados no Android",
"formats.intro": "O app Android suporta os formatos mais usados no bordado doméstico e comercial:",
"formats.list": "PES, PEC, DST, JEF, EXP e VP3",
"compare.title": "App Android vs visualizador web",
"compare.intro": "Ambos são grátis. Escolha o que combina com seu fluxo — ou use os dois.",
"compare.table.headers.feature": "Recurso",
"compare.table.headers.web": "Web",
"compare.table.headers.android": "App Android",
"compare.rows.preview.feature": "Prévia de pontos",
"compare.rows.preview.web": "Sim — no navegador",
"compare.rows.preview.android": "Sim — app nativo",
"compare.rows.offline.feature": "Uso offline",
"compare.rows.offline.web": "Precisa de internet para abrir o site",
"compare.rows.offline.android": "Funciona offline após instalar",
"compare.rows.formats.feature": "Formatos",
"compare.rows.formats.web": "PES, DST, JEF, EXP, PEC",
"compare.rows.formats.android": "PES, DST, JEF, EXP, PEC, VP3",
"compare.rows.colors.feature": "Personalização de cores",
"compare.rows.colors.web": "Prévia básica",
"compare.rows.colors.android": "Fundo + cores das linhas",
"compare.rows.highlight.feature": "Destacar paradas de cor",
"compare.rows.highlight.web": "Não",
"compare.rows.highlight.android": "Toque para destacar sequências",
"screenshots.title": "Veja o app em ação",
"screenshots.intro": "Experiência mobile focada em conferir bordados antes de costurar.",
"screenshots.app.alt": "App Embroidery Viewer Android exibindo prévia de bordado no celular",
"screenshots.play.alt": "Selo Google Play para baixar Embroidery Viewer Android",
"faq.title": "Perguntas frequentes",
"faq.intro": "Dúvidas comuns sobre o app Embroidery Viewer para Android e visualização de bordados no celular.",
"faq.items": {
"isFree": {
"summary": "O app Embroidery Viewer para Android é grátis?",
"description": "Sim. O app é gratuito na Google Play. Não é necessário assinatura para visualizar arquivos de bordado."
},
"openPesAndroid": {
"summary": "Posso abrir arquivos PES no Android?",
"description": "Sim. O app abre PES e PEC do armazenamento do celular, downloads ou apps de arquivos. Visualize formatos Brother sem programa no computador."
},
"offline": {
"summary": "O app funciona offline?",
"description": "Sim. Após instalar, você pode abrir arquivos no dispositivo sem internet — útil na máquina ou longe do WiFi."
},
"vsWeb": {
"summary": "Devo usar o app ou o site?",
"description": "Use o site para prévias rápidas no computador. Use o app Android para offline, suporte VP3, personalização de cores e interface touch no celular."
},
"vp3": {
"summary": "O app Android suporta arquivos VP3?",
"description": "Sim. O app suporta VP3 além de PES, DST, JEF, EXP e PEC — útil para usuários Husqvarna Viking e Pfaff."
},
"privacy": {
"summary": "Meus dados são privados?",
"description": "Os desenhos que você abre são processados no dispositivo. Leia a política de privacidade do app para detalhes sobre permissões e dados."
},
"webViewer": {
"summary": "Existe visualizador online grátis?",
"description": "Sim. Acesse embroideryviewer.xyz/viewer no navegador para PES, DST, JEF, EXP e PEC — sem instalar."
},
"ios": {
"summary": "Existe app para iPhone?",
"description": "O Embroidery Viewer está disponível no Android pela Google Play. No iOS, use o visualizador web gratuito em embroideryviewer.xyz."
}
},
"cta.title": "Baixe o Embroidery Viewer para Android",
"cta.subtitle": "Grátis na Google Play — visualize bordados em segundos.",
"cta.privacy": "Política de privacidade do app",
"cta.web": "Usar o visualizador web gratuito"
}

View file

@ -1,15 +0,0 @@
{
"seo.title": "Página não encontrada — Embroidery Viewer",
"seo.description": "A página que você procura não foi encontrada.",
"seo.keywords": "404, página não encontrada, visualizador de bordado",
"seo.url": "https://embroideryviewer.xyz",
"notFound.code": "404",
"notFound.title": "Este ponto saiu do desenho",
"notFound.description": "A página pode ter sido movida, removida ou nunca existiu. Vamos voltar para visualizar seus bordados.",
"notFound.home": "Voltar ao início",
"notFound.viewer": "Abrir o visualizador",
"notFound.imageAlt": "Prévia de desenho de bordado na tela",
"generic.title": "Algo deu errado",
"generic.description": "Encontramos um problema ao carregar esta página. Tente novamente ou volte ao início.",
"generic.home": "Voltar ao início"
}

View file

@ -1,90 +0,0 @@
{
"seo.title": "Visualizador de Arquivos EXP Online Grátis — Abra Bordados na Hora",
"seo.description": "Abra arquivos EXP online gratuitamente. Visualize bordados Melco e Bernina no navegador — sem instalar, sem enviar para servidores. Também suporta PES, DST, JEF e PEC.",
"seo.keywords": "visualizador EXP, abrir arquivo EXP online, visualizador de bordado, arquivo Melco bordado, como ver arquivo de bordado, converter EXP para DST, converter EXP para PES, bordado Bernina EXP, preview bordado online",
"seo.url": "https://embroideryviewer.xyz/exp-file-viewer",
"seo.image": "https://embroideryviewer.xyz/og/exp-viewer.png",
"hero.tagline": "Grátis · Privado · Sem cadastro",
"hero.title": "Visualizador de Arquivos EXP Online Grátis — Abra Bordados na Hora",
"hero.subtitle": "Veja caminhos de pontos e dimensões no navegador — grátis, privado e sem cadastro. EXP é muito usado em sistemas Melco e em máquinas que aceitam formatos universais de pontos.",
"hero.cta": "Experimente seu desenho",
"whatIs.title": "O que é um arquivo EXP?",
"whatIs.p1": "Um <strong>arquivo EXP</strong> (.exp) é um formato compacto associado a sistemas comerciais <strong>Melco</strong> e frequentemente exportado por <strong>Bernina</strong> e outros softwares como arquivo universal de pontos.",
"whatIs.p2": "EXP codifica movimentos de ponto com comandos de parada, aparo e troca de cor. Não traz paleta rica como PES ou JEF — cores costumam ser definidas pela máquina ou software, parecido com DST.",
"whatIs.p3": "Você encontra EXP em fluxos comerciais, downloads convertidos e bibliotecas antigas. O Embroidery Viewer permite abrir EXP online e conferir estrutura e tamanho antes de bordar.",
"howTo.title": "Como visualizar arquivos EXP de bordado online",
"howTo.step1": "Abra o <strong>visualizador de bordado gratuito</strong> no navegador.",
"howTo.step2": "Clique em <strong>Escolher arquivos</strong> ou arraste seu .exp (ou .pes, .dst, .jef, .pec) para a área de soltar.",
"howTo.step3": "Clique em <strong>Renderizar arquivos</strong> para gerar a prévia na tela.",
"howTo.step4": "Revise contagem de pontos e dimensões — e baixe um PNG se precisar de referência.",
"formats.title": "Formatos de bordado suportados",
"formats.intro": "Comparação entre EXP, PES, DST, JEF e outros formatos que o Embroidery Viewer abre no navegador:",
"formats.table.headers": {
"format": "Formato",
"extension": "Extensão",
"machines": "Máquinas comuns",
"notes": "Observações"
},
"formats.rows.exp.format": "EXP",
"formats.rows.exp.extension": ".exp",
"formats.rows.exp.machines": "Melco, Bernina (exportação)",
"formats.rows.exp.notes": "Fluxo compacto de pontos; amplamente suportado",
"formats.rows.pes.format": "PES / PEC",
"formats.rows.pes.extension": ".pes, .pec",
"formats.rows.pes.machines": "Brother, Babylock",
"formats.rows.pes.notes": "Popular em downloads domésticos",
"formats.rows.dst.format": "DST",
"formats.rows.dst.extension": ".dst",
"formats.rows.dst.machines": "Comercial e várias domésticas",
"formats.rows.dst.notes": "Padrão da indústria para oficinas",
"formats.rows.jef.format": "JEF",
"formats.rows.jef.extension": ".jef",
"formats.rows.jef.machines": "Janome, Elna, Kenmore",
"formats.rows.jef.notes": "Formato nativo Janome",
"convert.title": "Posso converter EXP para DST ou PES online?",
"convert.p1": "O Embroidery Viewer é uma <strong>ferramenta de prévia</strong>, não um conversor. Ele permite abrir EXP online e ver pontos, paradas de cor e tamanho antes de bordar ou enviar para produção.",
"convert.p2": "Para converter EXP em DST, PES ou JEF use software de bordado como Wilcom, Hatch, Embrilliance ou Ink/Stitch. Depois visualize o novo arquivo gratuitamente no visualizador.",
"screenshots.title": "Veja seu EXP antes de costurar",
"screenshots.intro": "Visualize pontos e dimensões sem instalar software no computador.",
"screenshots.viewer.alt": "Embroidery Viewer exibindo prévia de arquivo EXP com caminhos de pontos",
"screenshots.hero.alt": "Interface de envio para abrir arquivos EXP de bordado online",
"faq.title": "Perguntas frequentes sobre EXP e arquivos de bordado",
"faq.intro": "Respostas às buscas mais comuns — de abrir EXP online a converter formatos e usar o visualizador.",
"faq.items": {
"openExpOnline": {
"summary": "Como abrir um arquivo EXP online?",
"description": "Use o Embroidery Viewer: abra o visualizador gratuito, arraste o .exp para a área e clique em Renderizar arquivos. Tudo roda no navegador — sem software Melco ou ferramentas só para Windows."
},
"whatIsExp": {
"summary": "Para que serve um arquivo EXP?",
"description": "EXP é um formato de dados de pontos usado em sistemas Melco e frequentemente exportado pela Bernina e outros softwares. Armazena comandos de movimento para máquinas compatíveis."
},
"otherFormats": {
"summary": "Posso abrir arquivos PES, DST ou JEF também?",
"description": "Sim. O mesmo visualizador suporta PES, DST, JEF, EXP e PEC. Veja embroideryviewer.xyz/pes-file-viewer, /dst-file-viewer e /jef-file-viewer."
},
"howToView": {
"summary": "Como ver arquivos de bordado sem software?",
"description": "Abra embroideryviewer.xyz em qualquer navegador moderno. Formatos: EXP, PES, DST, JEF e PEC. Os arquivos são processados localmente e nunca enviados a um servidor."
},
"convertExp": {
"summary": "Como converter EXP para DST ou PES?",
"description": "A conversão exige software de edição — Embrilliance, Hatch, Wilcom ou Ink/Stitch (grátis). Exporte para o formato da sua máquina e visualize o resultado no Embroidery Viewer antes de bordar."
},
"embroideryViewer": {
"summary": "Qual o melhor visualizador gratuito de bordado?",
"description": "O Embroidery Viewer foi feito para prévias rápidas e privadas: sem conta, sem instalação, sem upload. Suporta EXP e os formatos mais usados no desktop e celular."
},
"isSafe": {
"summary": "É seguro abrir meus arquivos EXP aqui?",
"description": "Sim. Os arquivos são lidos e renderizados no navegador com JavaScript. Não são enviados, armazenados nem compartilhados com servidores."
},
"mobile": {
"summary": "Posso abrir EXP no celular?",
"description": "Sim, em navegadores móveis modernos. Toque em Escolher arquivos, selecione o .exp e toque em Renderizar arquivos."
}
},
"cta.title": "Pronto para ver seu EXP?",
"cta.subtitle": "Abra EXP, PES, DST, JEF ou PEC em segundos — grátis e privado.",
"cta.button": "Experimente seu desenho"
}

View file

@ -1,30 +0,0 @@
{
"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 PES, DST, JEF, EXP e PEC — os formatos mais usados em máquinas de bordado domésticas e comerciais."
},
"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."
}
}
}

View file

@ -1,27 +0,0 @@
{
"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"
}

View file

@ -1,20 +1,7 @@
{ {
"slogan": "Visualize bordados instantaneamente — sem software", "about": " Sobre",
"resources": "Recursos", "privacy.policy": "🔐 Política de Privacidade",
"contact-title": "Contato", "terms.of.service": "📝 Termos de Serviço",
"contact-description": "Quer ajudar ou tirar alguma dúvida? Contate-nos.", "copyright": "Copyright © {{year}} <a href=\"{{website}}/pt-br\" target=\"_blank\" rel=\"noreferrer\">Leonardo Murça</a>. <br/> Todos os direitos reservados.",
"back-to-top": { "version": "🧵 Versão: {{version}}"
"label": "Voltar ao topo",
"aria-label": "Voltar ao topo da página"
},
"about": "Sobre",
"pesViewer": "Visualizador PES",
"dstViewer": "Visualizador DST",
"jefViewer": "Visualizador JEF",
"expViewer": "Visualizador EXP",
"androidApp": "App Android",
"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}}"
} }

View file

@ -1,6 +1,7 @@
{ {
"homeNav": "Página Inicial", "languageSwitch": "🇺🇸",
"aboutNav": "Sobre", "homeNav": "🏠 Página Inicial",
"viewerNav": "Visualizador", "aboutNav": " Sobre",
"supportUsNav": "Apoie-nos" "viewerNav": "🧵 Visualizador",
"donateNav": "💖 Doe"
} }

View file

@ -1,5 +0,0 @@
{
"title": "Visualize bordados instantaneamente — sem software",
"description": "Rápido, privado & sem necessidade de cadastro.",
"cta": "Teste Agora"
}

View file

@ -1,12 +1,22 @@
{ {
"seo.title": "Visualizador de Bordado Online Grátis — Rápido, Privado e Sem Cadastro", "main.title": "🧵 Visualizador de arquivos de bordado online gratuito",
"seo.description": "Visualize arquivos de bordado instantaneamente no navegador com o Embroidery Viewer. Compatível com PES, DST, JEF, EXP e PEC. Sem instalação, sem cadastro — gratuito e privado.", "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>",
"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, pré-visualização de bordado, renderizador de bordado no navegador", "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.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/viewer.png", "seo.image": "https://embroideryviewer.xyz/og/"
"howTo.title": "Como visualizar arquivos de bordado online",
"howTo.step1": "Abra o Embroidery Viewer no seu navegador.",
"howTo.step2": "Acesse a página do visualizador online.",
"howTo.step3": "Arraste e solte seu arquivo de bordado (PES, DST, JEF, EXP ou PEC).",
"howTo.step4": "Visualize o design na hora — sem instalar nenhum software."
} }

View file

@ -1,90 +0,0 @@
{
"seo.title": "Visualizador de Arquivos JEF Online Grátis — Abra Bordados Janome na Hora",
"seo.description": "Abra arquivos JEF online gratuitamente. Visualize bordados Janome e Elna no navegador — sem instalar, sem enviar para servidores. Também suporta PES, DST, EXP e PEC.",
"seo.keywords": "o que é arquivo JEF, visualizador JEF, abrir arquivo JEF online, visualizador de bordado, arquivo Janome bordado, como ver arquivo de bordado, converter JEF para PES, converter JEF para DST, preview bordado online",
"seo.url": "https://embroideryviewer.xyz/jef-file-viewer",
"seo.image": "https://embroideryviewer.xyz/og/jef-viewer.png",
"hero.tagline": "Grátis · Privado · Sem cadastro",
"hero.title": "Visualizador de Arquivos JEF Online Grátis — Abra Bordados Janome na Hora",
"hero.subtitle": "Veja caminhos de pontos, cores e dimensões no navegador — grátis, privado e sem cadastro. JEF é o formato nativo para Janome, Elna e máquinas compatíveis.",
"hero.cta": "Experimente seu desenho",
"whatIs.title": "O que é um arquivo JEF?",
"whatIs.p1": "Um <strong>arquivo JEF</strong> (.jef) é o formato de bordado nativo das máquinas <strong>Janome</strong> e <strong>Elna</strong>. Muitos modelos Kenmore e compatíveis com Janome também leem JEF.",
"whatIs.p2": "Arquivos JEF contêm coordenadas de pontos, comandos de troca de cor e uma paleta de linhas — parecido com PES nas máquinas Brother. Downloads para Janome Memory Craft ou Elna Express costumam vir como .jef.",
"whatIs.p3": "O Embroidery Viewer permite abrir JEF online para conferir layout, contagem de pontos e tamanho antes de bordar — sem instalar Janome Artistic Digitizer ou outro software desktop.",
"howTo.title": "Como visualizar arquivos JEF de bordado online",
"howTo.step1": "Abra o <strong>visualizador de bordado gratuito</strong> no navegador.",
"howTo.step2": "Clique em <strong>Escolher arquivos</strong> ou arraste seu .jef (ou .pes, .dst, .exp, .pec) para a área de soltar.",
"howTo.step3": "Clique em <strong>Renderizar arquivos</strong> para gerar a prévia na tela.",
"howTo.step4": "Revise cores, contagem de pontos e dimensões — e baixe um PNG se precisar de referência.",
"formats.title": "Formatos de bordado suportados",
"formats.intro": "Comparação entre JEF, PES, DST e outros formatos que o Embroidery Viewer abre no navegador:",
"formats.table.headers": {
"format": "Formato",
"extension": "Extensão",
"machines": "Máquinas comuns",
"notes": "Observações"
},
"formats.rows.jef.format": "JEF",
"formats.rows.jef.extension": ".jef",
"formats.rows.jef.machines": "Janome, Elna, Kenmore",
"formats.rows.jef.notes": "Formato nativo Janome com cores de linha embutidas",
"formats.rows.pes.format": "PES / PEC",
"formats.rows.pes.extension": ".pes, .pec",
"formats.rows.pes.machines": "Brother, Babylock",
"formats.rows.pes.notes": "Popular em downloads domésticos",
"formats.rows.dst.format": "DST",
"formats.rows.dst.extension": ".dst",
"formats.rows.dst.machines": "Comercial e várias domésticas",
"formats.rows.dst.notes": "Padrão da indústria para oficinas",
"formats.rows.exp.format": "EXP",
"formats.rows.exp.extension": ".exp",
"formats.rows.exp.machines": "Melco, Bernina (exportação)",
"formats.rows.exp.notes": "Lista simples de pontos, amplamente suportado",
"convert.title": "Posso converter JEF para PES ou DST online?",
"convert.p1": "O Embroidery Viewer é uma <strong>ferramenta de prévia</strong>, não um conversor. Ele permite abrir JEF online e ver pontos, paradas de cor e tamanho antes de carregar na Janome ou Elna.",
"convert.p2": "Para converter JEF em PES, DST ou outro formato use software de bordado como Embrilliance, Hatch ou Ink/Stitch. Depois visualize o novo arquivo gratuitamente no visualizador.",
"screenshots.title": "Veja seu JEF antes de costurar",
"screenshots.intro": "Visualize pontos, cores e dimensões sem instalar software no computador.",
"screenshots.viewer.alt": "Embroidery Viewer exibindo prévia de arquivo JEF com pontos e cores",
"screenshots.hero.alt": "Interface de envio para abrir arquivos JEF de bordado online",
"faq.title": "Perguntas frequentes sobre JEF e arquivos de bordado",
"faq.intro": "Respostas às buscas mais comuns — do que é um JEF a abrir desenhos online e converter formatos.",
"faq.items": {
"openJefOnline": {
"summary": "Como abrir um arquivo JEF online?",
"description": "Use o Embroidery Viewer: abra o visualizador gratuito, arraste o .jef para a área e clique em Renderizar arquivos. Tudo roda no navegador — sem software Janome ou ferramentas só para Windows."
},
"whatIsJef": {
"summary": "Para que serve um arquivo JEF?",
"description": "JEF é o formato de bordado da Janome. Armazena pontos e cores de linha para Janome, Elna e compatíveis. Você o usa para carregar desenhos via USB ou transferir downloads da web."
},
"pesViewer": {
"summary": "Posso abrir arquivos PES ou DST também?",
"description": "Sim. O mesmo visualizador suporta PES, DST, JEF, EXP e PEC. Para Brother veja embroideryviewer.xyz/pes-file-viewer; para DST veja embroideryviewer.xyz/dst-file-viewer."
},
"howToView": {
"summary": "Como ver arquivos de bordado sem software?",
"description": "Abra embroideryviewer.xyz em qualquer navegador moderno. Formatos: JEF, PES, DST, EXP e PEC. Os arquivos são processados localmente e nunca enviados a um servidor."
},
"convertJef": {
"summary": "Como converter JEF para PES ou DST?",
"description": "A conversão exige software de edição — Embrilliance, Hatch, Wilcom ou Ink/Stitch (grátis). Exporte para o formato da sua máquina e visualize o resultado no Embroidery Viewer antes de bordar."
},
"embroideryViewer": {
"summary": "Qual o melhor visualizador gratuito de bordado?",
"description": "O Embroidery Viewer foi feito para prévias rápidas e privadas: sem conta, sem instalação, sem upload. Suporta JEF e os formatos mais usados no desktop e celular."
},
"isSafe": {
"summary": "É seguro abrir meus arquivos JEF aqui?",
"description": "Sim. Os arquivos são lidos e renderizados no navegador com JavaScript. Não são enviados, armazenados nem compartilhados com servidores."
},
"mobile": {
"summary": "Posso abrir JEF no celular?",
"description": "Sim, em navegadores móveis modernos. Toque em Escolher arquivos, selecione o .jef e toque em Renderizar arquivos."
}
},
"cta.title": "Pronto para ver seu JEF?",
"cta.subtitle": "Abra JEF, PES, DST, EXP ou PEC em segundos — grátis e privado.",
"cta.button": "Experimente seu desenho"
}

View file

@ -1,10 +0,0 @@
{
"title": "Política de Privacidade",
"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>",
"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.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.image": "https://embroideryviewer.xyz/og/privacy-policy.png"
}

View file

@ -1,17 +0,0 @@
{
"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"
}

View file

@ -1,90 +0,0 @@
{
"seo.title": "Visualizador de Arquivos PES Online Grátis — Abra Bordados na Hora",
"seo.description": "Abra arquivos PES online gratuitamente. Visualize bordados Brother no navegador — sem instalar, sem enviar para servidores. Também suporta DST, JEF, EXP e PEC.",
"seo.keywords": "abrir arquivo PES online, visualizador PES, visualizador de bordado, como ver arquivo de bordado, visualizador DST, converter PES para DST, o que é arquivo JEF, arquivo Brother bordado, preview bordado online",
"seo.url": "https://embroideryviewer.xyz/pes-file-viewer",
"seo.image": "https://embroideryviewer.xyz/og/pes-viewer.png",
"hero.tagline": "Grátis · Privado · Sem cadastro",
"hero.title": "Visualizador de Arquivos PES Online Grátis — Abra Bordados na Hora",
"hero.subtitle": "Veja caminhos de pontos, cores e dimensões no navegador — grátis, privado e sem cadastro. Funciona com máquinas Brother e a maioria dos softwares domésticos.",
"hero.cta": "Experimente seu desenho",
"whatIs.title": "O que é um arquivo PES?",
"whatIs.p1": "Um <strong>arquivo PES</strong> (.pes) é um formato de bordado proprietário criado pela <strong>Brother</strong> e usado por muitas máquinas domésticas — incluindo Brother, Babylock, Bernina (com conversão) e outras que aceitam PES.",
"whatIs.p2": "Dentro de um PES há coordenadas de pontos, comandos de troca de cor, saltos/aparos e uma paleta de cores. É um dos formatos mais comuns em downloads do Etsy, Creative Fabrica e fóruns de bordado.",
"whatIs.p3": "Arquivos PES costumam vir com um <strong>.pec</strong> complementar (container mais antigo). O Embroidery Viewer lê ambos para você visualizar sem instalar Wilcom, PE-Design ou Embrilliance.",
"howTo.title": "Como visualizar arquivos de bordado online",
"howTo.step1": "Abra o <strong>visualizador de bordado gratuito</strong> no navegador.",
"howTo.step2": "Clique em <strong>Escolher arquivos</strong> ou arraste .pes, .dst, .jef, .exp ou .pec para a área de soltar.",
"howTo.step3": "Clique em <strong>Renderizar arquivos</strong> para gerar a prévia na tela.",
"howTo.step4": "Revise cores, contagem de pontos e dimensões — e baixe um PNG se precisar de referência.",
"formats.title": "Formatos de bordado suportados",
"formats.intro": "Procura um visualizador DST ou quer saber o que é um JEF? Comparação rápida dos formatos que o Embroidery Viewer abre no navegador:",
"formats.table.headers": {
"format": "Formato",
"extension": "Extensão",
"machines": "Máquinas comuns",
"notes": "Observações"
},
"formats.rows.pes.format": "PES / PEC",
"formats.rows.pes.extension": ".pes, .pec",
"formats.rows.pes.machines": "Brother, Babylock",
"formats.rows.pes.notes": "Mais popular em downloads domésticos",
"formats.rows.dst.format": "DST",
"formats.rows.dst.extension": ".dst",
"formats.rows.dst.machines": "Comercial e várias domésticas",
"formats.rows.dst.notes": "Padrão da indústria; cores limitadas no arquivo",
"formats.rows.jef.format": "JEF",
"formats.rows.jef.extension": ".jef",
"formats.rows.jef.machines": "Janome, Elna, Kenmore",
"formats.rows.jef.notes": "Formato nativo Janome",
"formats.rows.exp.format": "EXP",
"formats.rows.exp.extension": ".exp",
"formats.rows.exp.machines": "Melco, Bernina (exportação)",
"formats.rows.exp.notes": "Lista simples de pontos, amplamente suportado",
"convert.title": "Posso converter PES para DST online?",
"convert.p1": "O Embroidery Viewer é uma <strong>ferramenta de prévia</strong>, não um conversor. Ele permite abrir PES online e ver como o desenho será bordado — pontos, paradas de cor e tamanho — antes de carregar na máquina.",
"convert.p2": "Para converter PES em DST use software de bordado (Wilcom, Hatch, Embrilliance, Ink/Stitch, etc.) ou o editor da sua máquina. Depois volte aqui para visualizar o DST gratuitamente.",
"screenshots.title": "Veja o bordado antes de costurar",
"screenshots.intro": "Visualize pontos, cores e dimensões sem instalar software no computador.",
"screenshots.viewer.alt": "Embroidery Viewer exibindo prévia de arquivo PES com pontos e cores",
"screenshots.hero.alt": "Interface de arrastar e soltar para abrir arquivos PES de bordado online",
"faq.title": "Perguntas frequentes sobre PES e arquivos de bordado",
"faq.intro": "Respostas às dúvidas mais buscadas — de abrir PES online a entender JEF e DST.",
"faq.items": {
"openPesOnline": {
"summary": "Como abrir um arquivo PES online?",
"description": "Use o visualizador gratuito nesta página: arraste o .pes para a área e clique em Renderizar arquivos. Tudo roda no navegador — sem software Brother ou ferramentas só para Windows."
},
"dstViewer": {
"summary": "Existe visualizador DST online grátis?",
"description": "Sim. O Embroidery Viewer abre DST como PES — envie ou arraste o arquivo e veja pontos e dimensões. Veja o guia DST em embroideryviewer.xyz/dst-file-viewer."
},
"howToView": {
"summary": "Como ver arquivos de bordado sem software?",
"description": "Abra embroideryviewer.xyz em qualquer navegador moderno. Formatos: PES, DST, JEF, EXP e PEC. Os arquivos são processados localmente e nunca enviados a um servidor."
},
"convertPesDst": {
"summary": "Como converter PES para DST?",
"description": "A conversão exige software de edição — Embrilliance, Hatch ou Ink/Stitch (grátis). Exporte como DST e use o Embroidery Viewer para prévia online antes de bordar."
},
"whatIsJef": {
"summary": "O que é um arquivo JEF?",
"description": "JEF (.jef) é o formato nativo da Janome. Armazena pontos e cores para Janome, Elna e compatíveis. Veja o guia JEF em embroideryviewer.xyz/jef-file-viewer."
},
"embroideryViewer": {
"summary": "Qual o melhor visualizador gratuito de bordado?",
"description": "O Embroidery Viewer foi feito para prévias rápidas e privadas: sem conta, sem instalação, sem upload. Suporta os formatos mais usados e funciona no celular e no desktop."
},
"isSafe": {
"summary": "É seguro abrir meus arquivos aqui?",
"description": "Sim. Os arquivos são lidos e renderizados no navegador com JavaScript. Não são enviados, armazenados nem compartilhados com servidores."
},
"mobile": {
"summary": "Posso abrir PES no celular?",
"description": "Sim, em navegadores móveis modernos. Toque em Escolher arquivos, selecione o .pes e toque em Renderizar arquivos."
}
},
"cta.title": "Pronto para ver seu desenho?",
"cta.subtitle": "Abra PES, DST, JEF, EXP ou PEC em segundos — grátis e privado.",
"cta.button": "Experimente seu desenho"
}

View file

@ -1,8 +1,8 @@
{ {
"title": "Política de Privacidade", "title": "🔐 Política de Privacidade",
"last.update": "Última atualização: 24 de abril de 2026", "last.update": "Última atualização: 9 de maio de 2025",
"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>", "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>",
"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",

View file

@ -1,10 +0,0 @@
{
"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"
}

View file

@ -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",

View file

@ -1,5 +1,6 @@
{ {
"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",
@ -8,10 +9,9 @@
"selected": "Arquivos selecionados", "selected": "Arquivos selecionados",
"rejected": "Arquivos recusados", "rejected": "Arquivos recusados",
"stitches": "Pontos", "stitches": "Pontos",
"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",

View file

@ -4,8 +4,4 @@ function appVersion() {
return APP_VERSION; return APP_VERSION;
} }
function isDevelopment() { export { appVersion };
return import.meta.env.MODE === 'development';
}
export { appVersion, isDevelopment };

View file

@ -1,9 +0,0 @@
import { browser } from '$app/environment';
export const isMobile = () => {
if (browser) {
return window.matchMedia('only screen and (max-width: 768px)').matches;
} else {
return null;
}
};

View file

@ -1,15 +0,0 @@
/**
* Converts a locale string from hyphen format (e.g., "en-US")
* to underscore format (e.g., "en_US").
*
* Useful for APIs or systems that expect locales with underscores.
*
* @param {string} locale - The locale string in BCP 47 format (e.g., "en-US").
* @returns {string} The normalized locale string using underscores (e.g., "en_US").
*
* @example
* normalizeLocaleUnderscore("en-US"); // "en_US"
*/
export const normalizeLocaleUnderscore = (locale) => {
return locale.split('-').join('_');
};

View file

@ -1,283 +0,0 @@
<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';
import Head from '$lib/components/Head.svelte';
/** @type {{ status: number }} */
let { status } = $props();
const isNotFound = $derived(status === 404);
const embroideryImage = isMobile()
? `${PUBLIC_IMAGE_BASE_URL}/t/f_webp,w_480/embroidery-viewer/hero-mobile.webp`
: `${PUBLIC_IMAGE_BASE_URL}/t/f_webp,w_640/embroidery-viewer/viewer-screenshot.webp`;
</script>
<Head
title="error.seo.title"
description="error.seo.description"
keywords="error.seo.keywords"
url="error.seo.url"
shouldBeIndexed={false}
/>
<section class="error-page" aria-labelledby="error-heading">
<div class="thread-bg" aria-hidden="true"></div>
<div class="error-card" class:single-column={!isNotFound}>
<div class="copy">
<p class="code">{isNotFound ? $t('error.notFound.code') : status}</p>
<h1 id="error-heading">
{isNotFound ? $t('error.notFound.title') : $t('error.generic.title')}
</h1>
<p class="description">
{isNotFound
? $t('error.notFound.description')
: $t('error.generic.description')}
</p>
<div class="actions">
<a class="organic-btn" href={resolve('/')}>
{isNotFound ? $t('error.notFound.home') : $t('error.generic.home')}
</a>
{#if isNotFound}
<a class="organic-btn-secondary outline" href={resolve('/viewer')}>
{$t('error.notFound.viewer')}
</a>
{/if}
</div>
</div>
{#if isNotFound}
<div class="visual">
<div class="hoop" aria-hidden="true">
<div class="hoop-inner">
<img
src={embroideryImage}
width="640"
height="480"
alt={$t('error.notFound.imageAlt')}
loading="lazy"
/>
</div>
</div>
<div class="needle" aria-hidden="true"></div>
</div>
{/if}
</div>
</section>
<style>
.error-page {
position: relative;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 120px 24px 80px;
overflow: hidden;
background:
radial-gradient(
circle at 15% 20%,
rgba(6, 52, 95, 0.08),
transparent 45%
),
radial-gradient(
circle at 85% 80%,
rgba(25, 71, 149, 0.1),
transparent 50%
),
linear-gradient(180deg, #f8fafb 0%, #eef3f8 100%);
}
.thread-bg {
position: absolute;
inset: 0;
pointer-events: none;
opacity: 0.35;
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 fill='none' stroke='%2306345f' stroke-width='1.2' opacity='0.5'%3E%3Cpath d='M80 120 Q200 40 320 140 T520 100' stroke-dasharray='6 8'/%3E%3Cpath d='M60 380 Q180 300 300 400 T540 360' stroke-dasharray='4 10'/%3E%3Ccircle cx='300' cy='300' r='120' stroke-dasharray='3 6'/%3E%3C/g%3E%3C/svg%3E");
background-size: 520px;
background-position: center;
background-repeat: no-repeat;
}
.error-card.single-column {
grid-template-columns: 1fr;
max-width: 520px;
text-align: center;
}
.error-card.single-column .description {
margin-left: auto;
margin-right: auto;
}
.error-card.single-column .actions {
justify-content: center;
}
.error-card {
position: relative;
z-index: 1;
width: min(100%, 1000px);
display: grid;
grid-template-columns: 1fr 1fr;
gap: 48px;
align-items: center;
padding: 48px;
background: rgba(255, 255, 255, 0.88);
border-radius: 32px 58% 42% 68% / 48% 38% 62% 52%;
box-shadow:
0 24px 48px rgba(6, 52, 95, 0.12),
0 0 0 1px rgba(6, 52, 95, 0.06);
backdrop-filter: blur(6px);
}
.code {
display: inline-block;
margin: 0 0 8px;
font-size: clamp(3rem, 10vw, 4.5rem);
font-weight: 700;
line-height: 1;
letter-spacing: -0.04em;
color: var(--color-primary);
background: linear-gradient(135deg, #06345f 0%, #194795 55%, #3d6eb5 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
h1 {
margin: 0 0 16px;
font-size: clamp(1.5rem, 3vw, 2rem);
line-height: 1.25;
color: var(--color-primary);
}
.description {
margin: 0;
font-size: 1.05rem;
line-height: 1.65;
color: #3a4a5c;
max-width: 38ch;
}
.actions {
display: flex;
flex-wrap: wrap;
gap: 14px;
margin-top: 28px;
}
.actions .organic-btn,
.actions .organic-btn-secondary {
font-size: 1rem;
padding: 16px 36px;
}
.organic-btn-secondary.outline {
background: transparent;
color: var(--color-primary);
border: 2px solid var(--color-primary);
}
.organic-btn-secondary.outline:hover {
background: var(--color-primary);
color: white;
}
.visual {
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
.hoop {
position: relative;
width: min(100%, 340px);
aspect-ratio: 1;
border-radius: 50%;
padding: 14px;
background: linear-gradient(145deg, #c9a227 0%, #8b6914 40%, #d4af37 100%);
box-shadow:
inset 0 2px 4px rgba(255, 255, 255, 0.45),
inset 0 -4px 8px rgba(0, 0, 0, 0.2),
0 16px 32px rgba(6, 52, 95, 0.2);
}
.hoop-inner {
width: 100%;
height: 100%;
border-radius: 50%;
overflow: hidden;
background: #f2f6f5;
border: 3px dashed rgba(6, 52, 95, 0.15);
}
.hoop-inner img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.needle {
position: absolute;
top: 8%;
right: 6%;
width: 48px;
height: 48px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%2306345f' d='M8 40 L24 4 L28 8 L14 38 Z'/%3E%3Ccircle cx='8' cy='40' r='4' fill='%23194795'/%3E%3C/svg%3E");
background-size: contain;
background-repeat: no-repeat;
transform: rotate(12deg);
filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.15));
}
@media (max-width: 860px) {
.error-card {
grid-template-columns: 1fr;
text-align: center;
padding: 36px 28px;
border-radius: 28px;
}
.description {
margin-left: auto;
margin-right: auto;
}
.actions {
justify-content: center;
}
.visual {
order: -1;
}
.hoop {
width: min(280px, 80vw);
}
}
@media (max-width: 480px) {
.error-page {
padding-top: 100px;
}
.actions {
flex-direction: column;
width: 100%;
}
.actions .organic-btn,
.actions .organic-btn-secondary {
width: 100%;
text-align: center;
}
}
</style>

View file

@ -1,51 +1,17 @@
import { browser } from '$app/environment'; import { setLocale, setRoute } from '$lib/translations';
import { loadTranslations, setLocale, setRoute } from '$lib/translations';
import { SUPPORTED_LOCALES } from '$lib/translations';
/** /**
* Type guard that checks if a string is a supported locale. * @typedef {Object} LayoutData
* @param {string | null} locale * @property {string} route
* @returns {locale is "en-US" | "pt-BR"} * @property {string} language
*/ */
function isSupportedLocale(locale) {
// @ts-ignore
return locale !== null && Object.values(SUPPORTED_LOCALES).includes(locale);
}
/** /** @type {import('@sveltejs/kit').Load<LayoutData>} */
* Client-side load function to initialize translations based on localStorage or server fallback. export const load = async ({ data }) => {
* const { route, language } = data ?? {};
* This function runs in the browser and:
* - Prioritizes locale from localStorage (if valid)
* - Falls back to the server-provided fallbackLanguage (from Accept-Language)
* - Initializes translations and routing
*
* @type {import('@sveltejs/kit').Load}
*/
export const load = async ({ data, url }) => {
/** @type {string} */
const route = url.pathname;
/** @type {"en-US" | "pt-BR"} */ if (route) await setRoute(route);
let language; if (language) await setLocale(language);
if (browser) { return data ?? {};
/**
* Locale stored in the browser, if any.
* @type {string | null}
*/
const stored = localStorage.getItem('locale');
if (isSupportedLocale(stored)) {
language = stored; // Type narrowed here
} else {
language = data?.fallbackLanguage ?? SUPPORTED_LOCALES.EN_US;
}
await loadTranslations(language, route);
await setLocale(language);
await setRoute(route);
}
return {};
}; };

View file

@ -1,44 +1,54 @@
import { parse } from 'accept-language-parser'; import { parse } from 'accept-language-parser';
import { loadTranslations, setLocale, setRoute } from '$lib/translations';
import { SUPPORTED_LOCALES } from '$lib/translations'; import { SUPPORTED_LOCALES } from '$lib/translations';
/** /**
* A Set of all supported locale codes for quick validation. * A set of all supported locale codes, used to validate and match against
* user preferences from cookies or Accept-Language headers. We're using a
* Set for better performance in lookup.
*
* Example values: "en-US", "pt-BR" * Example values: "en-US", "pt-BR"
* @type {Set<string>} * @type {Set<string>}
*/ */
const SUPPORTED_LOCALE_SET = new Set(Object.values(SUPPORTED_LOCALES)); const SUPPORTED_LOCALE_SET = new Set(Object.values(SUPPORTED_LOCALES));
/** /**
* Extracts the best matching locale from an Accept-Language HTTP header. * Returns a valid locale from cookies, or null if not valid/found.
* * @param {{ get: (cookies: string) => any; }} cookies
* @param {string | null} header - The Accept-Language header value.
* @returns {string | null} - A supported locale string like "en-US", or null if none matched.
*/ */
function localeFromHeader(header) { function localeFromCookies(cookies) {
if (!header) return null; const locale = cookies.get('locale');
const parsed = parse(header); return locale && SUPPORTED_LOCALE_SET.has(locale) ? locale : null;
for (const { code, region } of parsed) {
const locale = region ? `${code}-${region}` : code;
if (SUPPORTED_LOCALE_SET.has(locale)) return locale;
}
return null;
} }
/** /**
* Server-side load function that returns the initial route and fallback language. * Parses the Accept-Language header and returns the best matching locale.
* The language is inferred from the Accept-Language header. * @param {string | null | undefined} header
* `localStorage` will take precedence on the client.
*
* @type {import('@sveltejs/kit').ServerLoad}
*/ */
export async function load({ url, request }) { function localeFromHeader(header) {
/** @type {string} */ if (!header) return null;
const parsedLanguages = parse(header);
for (const { code, region } of parsedLanguages) {
const locale = region ? `${code}-${region}` : code;
if (SUPPORTED_LOCALE_SET.has(locale)) {
return locale;
}
}
return null;
}
/** @type {import('@sveltejs/kit').ServerLoad}*/
export async function load({ url, request, cookies }) {
const cookieLocale = localeFromCookies(cookies);
const headerLocale = localeFromHeader(request.headers.get('accept-language'));
const language = cookieLocale || headerLocale || SUPPORTED_LOCALES.EN_US;
const route = url.pathname; const route = url.pathname;
/** @type {string} */ await loadTranslations(language, route);
const fallbackLanguage = setLocale(language);
localeFromHeader(request.headers.get('accept-language')) || setRoute(route);
SUPPORTED_LOCALES.EN_US;
return { fallbackLanguage, route }; return { language, route };
} }

View file

@ -1,43 +1,18 @@
<script> <script>
import '$lib/styles/fonts.css';
import '$lib/styles/variables.css';
import '$lib/styles/global.css';
import { browser } from '$app/environment';
import { onMount } from 'svelte';
import Header from '$lib/components/Header.svelte'; import Header from '$lib/components/Header.svelte';
import Footer from '$lib/components/Footer.svelte'; import Footer from '$lib/components/Footer.svelte';
import Analytics from '$lib/components/Analytics.svelte';
import AnnouncementBar from '$lib/components/AnnouncementBar.svelte';
let { children } = $props();
let mounted = $state(false);
if (browser) {
onMount(() => {
mounted = true;
});
}
</script> </script>
<Analytics /> <Header />
<main>
{#if mounted} <slot />
<AnnouncementBar /> </main>
<Header /> <Footer />
<main>
{@render children()}
</main>
<Footer />
{/if}
<style> <style>
main { main {
flex: 1; /* This pushes footer to bottom */ flex: 1; /* This pushes footer to bottom */
padding: 0; padding: 20px;
min-height: 90vh; min-height: 90vh;
overflow-x: hidden;
} }
</style> </style>

12
src/routes/+page.js Normal file
View file

@ -0,0 +1,12 @@
/** @type {import('./$types').PageLoad} */
export function load() {
return {
metadata: {
title: 'home.seo.title',
description: 'home.seo.description',
keywords: 'home.seo.keywords',
url: 'home.seo.url',
image: 'home.seo.image',
},
};
}

View file

@ -1,33 +0,0 @@
import { EMAIL_ACCESS_KEY, EMAIL_BASE_URL } from '$env/static/private';
/** @type {import('./$types').Actions} */
export const actions = {
default: async ({ request }) => {
const formData = await request.formData();
console.log(formData);
const response = await fetch(`${EMAIL_BASE_URL}/submit`, {
method: 'POST',
body: JSON.stringify({
accessKey: EMAIL_ACCESS_KEY,
subject: 'Contato - Embroidery Viewer Beta Testers!',
name: formData.get('name'),
email: formData.get('email'),
}),
headers: { 'Content-Type': 'application/json' },
});
const json = await response.json();
if (json.error === undefined) {
return {
message: 'home.banner.feedback.success',
textColor: 'green',
};
} else {
return {
message: 'home.banner.feedback.error',
textColor: 'red',
};
}
},
};

View file

@ -1,120 +1,64 @@
<script> <script>
import { PUBLIC_IMAGE_BASE_URL } from '$env/static/public'; import Seo from '$lib/components/Seo.svelte';
import { t } from '$lib/translations'; import { t } from '$lib/translations';
import Head from '$lib/components/Head.svelte'; /** @type {import('./$types').PageProps} */
import StructuredData from '$lib/components/StructuredData.svelte'; let { data } = $props();
import Hero from '$lib/sections/Hero.svelte';
import Features from '$lib/sections/Features.svelte';
import Faq from '$lib/sections/Faq.svelte';
import MobileApp from '$lib/sections/MobileApp.svelte';
const baseUrl = 'https://embroideryviewer.xyz'; const metadata = data.metadata;
const viewerUrl = `${baseUrl}/viewer`;
const logoUrl = `${PUBLIC_IMAGE_BASE_URL}/t/f_webp/embroidery-viewer/logo-icon.webp`;
const faqKeys = [
'openPesOnline',
'supportedFormats',
'needSoftware',
'isSafe',
'multipleFiles',
'mobileSupport',
];
const howToSteps = ['step1', 'step2', 'step3', 'step4'];
$: structuredData = {
'@context': 'https://schema.org',
'@graph': [
{
'@type': 'WebSite',
'@id': `${baseUrl}/#website`,
url: baseUrl,
name: 'Embroidery Viewer',
description: $t('home.seo.description'),
inLanguage: ['en-US', 'pt-BR'],
publisher: { '@id': `${baseUrl}/#organization` },
},
{
'@type': 'Organization',
'@id': `${baseUrl}/#organization`,
name: 'Embroidery Viewer',
url: baseUrl,
logo: {
'@type': 'ImageObject',
url: logoUrl,
},
email: 'leo@leomurca.xyz',
},
{
'@type': 'WebPage',
'@id': `${baseUrl}/#webpage`,
url: baseUrl,
name: $t('home.seo.title'),
description: $t('home.seo.description'),
isPartOf: { '@id': `${baseUrl}/#website` },
about: { '@id': `${baseUrl}/#webapp` },
primaryImageOfPage: {
'@type': 'ImageObject',
url: $t('home.seo.image'),
},
},
{
'@type': 'WebApplication',
'@id': `${baseUrl}/#webapp`,
name: 'Embroidery Viewer',
url: viewerUrl,
applicationCategory: 'DesignApplication',
operatingSystem: 'Any',
browserRequirements: 'Requires JavaScript. Requires HTML5.',
offers: {
'@type': 'Offer',
price: '0',
priceCurrency: 'USD',
},
description: $t('home.seo.description'),
featureList:
'PES, DST, JEF, EXP, PEC embroidery file preview; multiple files; browser-based',
screenshot: $t('home.seo.image'),
},
{
'@type': 'FAQPage',
'@id': `${baseUrl}/#faq`,
mainEntity: faqKeys.map((key) => ({
'@type': 'Question',
name: $t(`faq.items.${key}.summary`),
acceptedAnswer: {
'@type': 'Answer',
text: $t(`faq.items.${key}.description`),
},
})),
},
{
'@type': 'HowTo',
'@id': `${baseUrl}/#howto`,
name: $t('home.howTo.title'),
step: howToSteps.map((step, i) => ({
'@type': 'HowToStep',
position: i + 1,
text: $t(`home.howTo.${step}`),
})),
},
],
};
</script> </script>
<Head <Seo {...metadata} />
title="home.seo.title"
description="home.seo.description"
keywords="home.seo.keywords"
url="home.seo.url"
ogImage="home.seo.image"
/>
<StructuredData data={structuredData} /> <div class="home-container">
<section aria-labelledby="main-title">
<h1 id="main-title">{$t('home.main.title')}</h1>
{@html $t('home.main.description')}
</section>
<Hero /> <section aria-labelledby="features-title">
<Features /> <h2 id="features-title">{$t('home.features.title')}</h2>
<MobileApp /> {@html $t('home.features.list')}
<Faq /> </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>
.home-container {
margin: 0 auto;
width: 70%;
}
@media (max-width: 768px) {
.home-container {
width: 100%;
}
}
</style>

12
src/routes/about/+page.js Normal file
View file

@ -0,0 +1,12 @@
/** @type {import('./$types').PageLoad} */
export function load() {
return {
metadata: {
title: 'about.seo.title',
description: 'about.seo.description',
keywords: 'about.seo.keywords',
url: 'about.seo.url',
image: 'about.seo.image',
},
};
}

View file

@ -1,239 +1,36 @@
<script> <script>
import { resolve } from '$app/paths';
import { PUBLIC_IMAGE_BASE_URL } from '$env/static/public';
import { t } from '$lib/translations'; import { t } from '$lib/translations';
import { isMobile } from '$lib/utils/isMobile';
import Head from '$lib/components/Head.svelte'; import Seo from '$lib/components/Seo.svelte';
import DonateIcon from '$lib/components/icons/DonateIcon.svelte';
const backgroundImage = isMobile() /** @type {import('./$types').PageProps} */
? `${PUBLIC_IMAGE_BASE_URL}/t/f_webp/embroidery-viewer/route-wallpaper-mobile.webp` let { data } = $props();
: `${PUBLIC_IMAGE_BASE_URL}/t/f_webp,w_1920,h_1080/embroidery-viewer/route-wallpaper.webp`;
const metadata = data.metadata;
</script> </script>
<Head <Seo {...metadata} />
title="about.seo.title"
description="about.seo.description"
keywords="about.seo.keywords"
url="about.seo.url"
/>
<section aria-labelledby="about-heading"> <section aria-labelledby="about-heading">
<div <h1 id="about-heading">{$t('about.title')}</h1>
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>
<section id="about-content"> {@html $t('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>
.embroidery-bg { section {
position: absolute; width: 70%;
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;
}
.organic-btn-secondary {
font-size: 1.3rem;
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; margin: 0 auto;
padding-top: 150px;
} }
.split-left { h1 {
flex: 1; padding: 0;
display: flex; margin-bottom: 7px;
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) {
.heading-container { section {
width: 100%; 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>

View file

@ -0,0 +1,12 @@
/** @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',
},
};
}

View file

@ -0,0 +1,186 @@
<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&currency_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: 100%;
}
.donation-options {
flex-direction: column;
align-items: center;
}
.donation-method {
width: 100%;
}
button,
.donation-link {
width: 100%;
height: 55px;
font-size: 1em;
}
}
</style>

View file

@ -1,400 +0,0 @@
<script>
import { resolve } from '$app/paths';
import { PUBLIC_IMAGE_BASE_URL } from '$env/static/public';
import { t, locale } from '$lib/translations';
import Head from '$lib/components/Head.svelte';
import StructuredData from '$lib/components/StructuredData.svelte';
const NS = 'dst-file-viewer';
const baseUrl = 'https://embroideryviewer.xyz/dst-file-viewer';
const dstFilePreview = `${PUBLIC_IMAGE_BASE_URL}/t/f_webp,w_800/embroidery-viewer/${$locale}/dst-file-preview.webp`;
const viewerInterface = `${PUBLIC_IMAGE_BASE_URL}/t/f_webp,w_800/embroidery-viewer/${$locale}/viewer-interface.webp`;
const faqKeys = [
'openDstOnline',
'pesViewer',
'howToView',
'convertDstPes',
'whatIsJef',
'embroideryViewer',
'isSafe',
'mobile',
];
$: faqStructuredData = {
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: faqKeys.map((key) => ({
'@type': 'Question',
name: $t(`${NS}.faq.items.${key}.summary`),
acceptedAnswer: {
'@type': 'Answer',
text: $t(`${NS}.faq.items.${key}.description`),
},
})),
};
$: webAppStructuredData = {
'@context': 'https://schema.org',
'@type': 'WebApplication',
name: 'Embroidery Viewer — DST File Viewer',
url: baseUrl,
applicationCategory: 'DesignApplication',
operatingSystem: 'Any',
offers: {
'@type': 'Offer',
price: '0',
priceCurrency: 'USD',
},
description: $t(`${NS}.seo.description`),
featureList: 'DST, PES, JEF, EXP, PEC embroidery file preview',
};
$: howToStructuredData = {
'@context': 'https://schema.org',
'@type': 'HowTo',
name: $t(`${NS}.howTo.title`),
step: ['step1', 'step2', 'step3', 'step4'].map((step, i) => ({
'@type': 'HowToStep',
position: i + 1,
text: $t(`${NS}.howTo.${step}`),
})),
};
</script>
<Head
title={`${NS}.seo.title`}
description={`${NS}.seo.description`}
keywords={`${NS}.seo.keywords`}
url={`${NS}.seo.url`}
ogImage={`${NS}.seo.image`}
/>
<StructuredData
data={[faqStructuredData, webAppStructuredData, howToStructuredData]}
/>
<section class="hero" aria-labelledby="dst-hero-heading">
<p class="tagline">{$t(`${NS}.hero.tagline`)}</p>
<h1 id="dst-hero-heading">{$t(`${NS}.hero.title`)}</h1>
<p class="hero-subtitle">{$t(`${NS}.hero.subtitle`)}</p>
<a class="organic-btn" href={resolve('/viewer')}>{$t(`${NS}.hero.cta`)}</a>
</section>
<article id="what-is-dst" class="content-section">
<h2>{$t(`${NS}.whatIs.title`)}</h2>
<!-- eslint-disable svelte/no-at-html-tags -->
<p>{@html $t(`${NS}.whatIs.p1`)}</p>
<p>{@html $t(`${NS}.whatIs.p2`)}</p>
<p>{@html $t(`${NS}.whatIs.p3`)}</p>
</article>
<article id="how-to-view" class="content-section alt">
<h2>{$t(`${NS}.howTo.title`)}</h2>
<ol class="steps">
{#each ['step1', 'step2', 'step3', 'step4'] as step (step)}
<li>{@html $t(`${NS}.howTo.${step}`)}</li>
{/each}
</ol>
</article>
<article id="formats" class="content-section">
<h2>{$t(`${NS}.formats.title`)}</h2>
<p>{$t(`${NS}.formats.intro`)}</p>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>{$t(`${NS}.formats.table.headers.format`)}</th>
<th>{$t(`${NS}.formats.table.headers.extension`)}</th>
<th>{$t(`${NS}.formats.table.headers.machines`)}</th>
<th>{$t(`${NS}.formats.table.headers.notes`)}</th>
</tr>
</thead>
<tbody>
{#each ['dst', 'pes', 'jef', 'exp'] as row (row)}
<tr>
<td>{$t(`${NS}.formats.rows.${row}.format`)}</td>
<td>{$t(`${NS}.formats.rows.${row}.extension`)}</td>
<td>{$t(`${NS}.formats.rows.${row}.machines`)}</td>
<td>{$t(`${NS}.formats.rows.${row}.notes`)}</td>
</tr>
{/each}
</tbody>
</table>
</div>
</article>
<article id="convert-dst-pes" class="content-section alt">
<h2>{$t(`${NS}.convert.title`)}</h2>
<p>{@html $t(`${NS}.convert.p1`)}</p>
<p>{@html $t(`${NS}.convert.p2`)}</p>
</article>
<section
id="screenshots"
class="screenshots-section"
aria-labelledby="screenshots-heading"
>
<h2 id="screenshots-heading">{$t(`${NS}.screenshots.title`)}</h2>
<p class="screenshots-intro">{$t(`${NS}.screenshots.intro`)}</p>
<div class="screenshot-grid">
<figure>
<img
src={dstFilePreview}
width="800"
height="600"
loading="lazy"
alt={$t(`${NS}.screenshots.viewer.alt`)}
/>
<figcaption>{$t(`${NS}.screenshots.viewer.alt`)}</figcaption>
</figure>
<figure>
<img
src={viewerInterface}
width="800"
height="533"
loading="lazy"
alt={$t(`${NS}.screenshots.hero.alt`)}
/>
<figcaption>{$t(`${NS}.screenshots.hero.alt`)}</figcaption>
</figure>
</div>
</section>
<section id="faq" class="faq-section" aria-labelledby="faq-heading">
<h2 id="faq-heading">{$t(`${NS}.faq.title`)}</h2>
<p class="faq-intro">{$t(`${NS}.faq.intro`)}</p>
<div class="faq-list">
{#each faqKeys as key (key)}
<details>
<summary>{$t(`${NS}.faq.items.${key}.summary`)}</summary>
<p>{$t(`${NS}.faq.items.${key}.description`)}</p>
</details>
{/each}
</div>
</section>
<section class="cta-section" aria-labelledby="cta-heading">
<h2 id="cta-heading">{$t(`${NS}.cta.title`)}</h2>
<p>{$t(`${NS}.cta.subtitle`)}</p>
<a class="organic-btn-secondary" href={resolve('/viewer')}>
{$t(`${NS}.cta.button`)}
</a>
</section>
<style>
.hero {
padding: 140px 24px 80px;
text-align: center;
max-width: 900px;
margin: 0 auto;
}
.tagline {
text-transform: uppercase;
letter-spacing: 0.12em;
font-size: 0.85rem;
color: var(--color-primary);
margin-bottom: 12px;
}
.hero h1 {
font-size: clamp(1.75rem, 4vw, 2.5rem);
line-height: 1.25;
margin: 0 0 1rem;
}
.hero-subtitle {
font-size: 1.1rem;
line-height: 1.6;
max-width: 720px;
margin: 0 auto 1.5rem;
color: #333;
}
.hero .organic-btn {
display: block;
margin: 0 auto;
margin-top: 40px;
}
.content-section {
max-width: 800px;
margin: 0 auto;
padding: 48px 24px;
line-height: 1.7;
}
.content-section.alt {
background: rgba(6, 52, 95, 0.04);
max-width: none;
padding-left: max(24px, calc((100% - 800px) / 2));
padding-right: max(24px, calc((100% - 800px) / 2));
}
.content-section h2 {
margin-top: 0;
font-size: 1.75rem;
}
.steps {
padding-left: 1.25rem;
}
.steps li {
margin-bottom: 12px;
}
.table-wrap {
overflow-x: auto;
margin-top: 24px;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 0.95rem;
}
th,
td {
border: 1px solid #d3dce6;
padding: 12px 14px;
text-align: left;
}
th {
background: var(--color-primary);
color: white;
}
tr:nth-child(even) {
background: #f8fafb;
}
.screenshots-section {
padding: 64px 24px;
max-width: 960px;
margin: 0 auto;
text-align: center;
}
.screenshots-intro {
margin-bottom: 32px;
line-height: 1.6;
}
.screenshot-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 32px;
}
.screenshot-grid img {
width: 100%;
height: auto;
border-radius: 12px;
border: 3px solid var(--color-primary);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
}
figcaption {
margin-top: 10px;
font-size: 0.9rem;
color: #444;
line-height: 1.4;
}
.faq-section {
padding: 64px 24px;
max-width: 800px;
margin: 0 auto;
}
.faq-section h2 {
text-align: center;
font-size: 1.75rem;
}
.faq-intro {
text-align: center;
margin-bottom: 32px;
line-height: 1.6;
}
.faq-list details {
border-bottom: 1px solid #eee;
padding: 20px 0;
}
.faq-list summary {
cursor: pointer;
font-weight: 600;
font-size: 1.1rem;
list-style: none;
position: relative;
color: var(--color-primary);
padding-right: 28px;
}
.faq-list summary::after {
content: '+';
position: absolute;
right: 0;
font-size: 1.5rem;
transition: transform 0.3s ease;
}
.faq-list details[open] summary::after {
transform: rotate(45deg);
}
.faq-list p {
margin-top: 10px;
line-height: 1.6;
}
.cta-section {
text-align: center;
padding: 64px 24px 100px;
background: var(--color-primary);
color: white;
}
.cta-section h2 {
color: white;
margin-top: 0;
}
.cta-section p {
margin-bottom: 24px;
opacity: 0.95;
}
.cta-section .organic-btn-secondary {
display: inline-block;
margin: 0 auto;
}
@media (max-width: 768px) {
.hero {
padding-top: 120px;
}
.screenshot-grid {
grid-template-columns: 1fr;
}
table {
font-size: 0.85rem;
}
th,
td {
padding: 8px;
}
}
</style>

View file

@ -1,574 +0,0 @@
<script>
import { resolve } from '$app/paths';
import { PUBLIC_IMAGE_BASE_URL } from '$env/static/public';
import { t, locale } from '$lib/translations';
import Head from '$lib/components/Head.svelte';
import StructuredData from '$lib/components/StructuredData.svelte';
const NS = 'embroidery-viewer-android';
const baseUrl = 'https://embroideryviewer.xyz/embroidery-viewer-android';
const PLAY_STORE_URL =
'https://play.google.com/store/apps/details?id=xyz.embroideryviewer.android';
const appScreenshot = `${PUBLIC_IMAGE_BASE_URL}/t/f_webp/embroidery-viewer/${$locale}/app-with-frame.webp`;
const playBadge = `${PUBLIC_IMAGE_BASE_URL}/t/w_240,h_76,f_webp/embroidery-viewer/${$locale}/android-download.webp`;
const featureKeys = [
'formats',
'customization',
'highlight',
'metadata',
'performance',
'accessibility',
];
const faqKeys = [
'isFree',
'openPesAndroid',
'offline',
'vsWeb',
'vp3',
'privacy',
'webViewer',
'ios',
];
const compareRows = [
'preview',
'offline',
'formats',
'colors',
'highlight',
];
$: faqStructuredData = {
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: faqKeys.map((key) => ({
'@type': 'Question',
name: $t(`${NS}.faq.items.${key}.summary`),
acceptedAnswer: {
'@type': 'Answer',
text: $t(`${NS}.faq.items.${key}.description`),
},
})),
};
$: mobileAppStructuredData = {
'@context': 'https://schema.org',
'@type': 'MobileApplication',
name: 'Embroidery Viewer',
operatingSystem: 'Android',
applicationCategory: 'DesignApplication',
offers: {
'@type': 'Offer',
price: '0',
priceCurrency: 'USD',
},
downloadUrl: PLAY_STORE_URL,
installUrl: PLAY_STORE_URL,
description: $t(`${NS}.seo.description`),
featureList: $t(`${NS}.formats.list`),
};
$: howToStructuredData = {
'@context': 'https://schema.org',
'@type': 'HowTo',
name: $t(`${NS}.howTo.title`),
step: ['step1', 'step2', 'step3', 'step4'].map((step, i) => ({
'@type': 'HowToStep',
position: i + 1,
text: $t(`${NS}.howTo.${step}`),
})),
};
</script>
<Head
title={`${NS}.seo.title`}
description={`${NS}.seo.description`}
keywords={`${NS}.seo.keywords`}
url={`${NS}.seo.url`}
ogImage={`${NS}.seo.image`}
/>
<StructuredData
data={[faqStructuredData, mobileAppStructuredData, howToStructuredData]}
/>
<section class="hero" aria-labelledby="android-hero-heading">
<div class="hero-grid">
<div class="hero-text">
<p class="tagline">{$t(`${NS}.hero.tagline`)}</p>
<h1 id="android-hero-heading">{$t(`${NS}.hero.title`)}</h1>
<p class="hero-subtitle">{$t(`${NS}.hero.subtitle`)}</p>
<a
class="play-badge-link"
href={PLAY_STORE_URL}
target="_blank"
rel="noopener noreferrer"
>
<img
src={playBadge}
width="240"
height="76"
alt={$t(`${NS}.playStore.ctaAlt`)}
/>
</a>
</div>
<div class="hero-visual">
<img
src={appScreenshot}
width="380"
height="760"
alt={$t(`${NS}.screenshots.app.alt`)}
fetchpriority="high"
/>
</div>
</div>
</section>
<article id="why-android" class="content-section">
<h2>{$t(`${NS}.why.title`)}</h2>
<!-- eslint-disable svelte/no-at-html-tags -->
<p>{@html $t(`${NS}.why.p1`)}</p>
<p>{@html $t(`${NS}.why.p2`)}</p>
</article>
<section id="features" class="features-section" aria-labelledby="features-heading">
<h2 id="features-heading">{$t(`${NS}.features.title`)}</h2>
<ul class="features-grid">
{#each featureKeys as key (key)}
<li class="feature-card">
<span class="feature-icon" aria-hidden="true"></span>
<span>{$t(`${NS}.features.items.${key}`)}</span>
</li>
{/each}
</ul>
</section>
<article id="how-to" class="content-section alt">
<h2>{$t(`${NS}.howTo.title`)}</h2>
<ol class="steps">
{#each ['step1', 'step2', 'step3', 'step4'] as step (step)}
<li>{@html $t(`${NS}.howTo.${step}`)}</li>
{/each}
</ol>
<a
class="play-badge-link inline"
href={PLAY_STORE_URL}
target="_blank"
rel="noopener noreferrer"
>
<img
src={playBadge}
width="240"
height="76"
alt={$t(`${NS}.playStore.ctaAlt`)}
/>
</a>
</article>
<article id="formats" class="content-section">
<h2>{$t(`${NS}.formats.title`)}</h2>
<p>{$t(`${NS}.formats.intro`)}</p>
<p class="formats-list"><strong>{$t(`${NS}.formats.list`)}</strong></p>
<p class="format-links">
<a href={resolve('/pes-file-viewer')}>PES</a>
·
<a href={resolve('/dst-file-viewer')}>DST</a>
·
<a href={resolve('/jef-file-viewer')}>JEF</a>
·
<a href={resolve('/exp-file-viewer')}>EXP</a>
</p>
</article>
<article id="compare" class="content-section alt">
<h2>{$t(`${NS}.compare.title`)}</h2>
<p>{$t(`${NS}.compare.intro`)}</p>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>{$t(`${NS}.compare.table.headers.feature`)}</th>
<th>{$t(`${NS}.compare.table.headers.web`)}</th>
<th>{$t(`${NS}.compare.table.headers.android`)}</th>
</tr>
</thead>
<tbody>
{#each compareRows as row (row)}
<tr>
<td>{$t(`${NS}.compare.rows.${row}.feature`)}</td>
<td>{$t(`${NS}.compare.rows.${row}.web`)}</td>
<td>{$t(`${NS}.compare.rows.${row}.android`)}</td>
</tr>
{/each}
</tbody>
</table>
</div>
</article>
<section
id="screenshots"
class="screenshots-section"
aria-labelledby="screenshots-heading"
>
<h2 id="screenshots-heading">{$t(`${NS}.screenshots.title`)}</h2>
<p class="screenshots-intro">{$t(`${NS}.screenshots.intro`)}</p>
<div class="screenshot-center">
<img
src={appScreenshot}
width="400"
height="800"
loading="lazy"
alt={$t(`${NS}.screenshots.app.alt`)}
/>
</div>
</section>
<section id="faq" class="faq-section" aria-labelledby="faq-heading">
<h2 id="faq-heading">{$t(`${NS}.faq.title`)}</h2>
<p class="faq-intro">{$t(`${NS}.faq.intro`)}</p>
<div class="faq-list">
{#each faqKeys as key (key)}
<details>
<summary>{$t(`${NS}.faq.items.${key}.summary`)}</summary>
{#if key === 'privacy'}
<p>
{$t(`${NS}.faq.items.privacy.description`)}
<a href={resolve('/mobile-app/privacy-policy')}
>{$t(`${NS}.cta.privacy`)}</a
>.
</p>
{:else}
<p>{$t(`${NS}.faq.items.${key}.description`)}</p>
{/if}
</details>
{/each}
</div>
</section>
<section class="cta-section" aria-labelledby="cta-heading">
<h2 id="cta-heading">{$t(`${NS}.cta.title`)}</h2>
<p>{$t(`${NS}.cta.subtitle`)}</p>
<a
class="play-badge-link"
href={PLAY_STORE_URL}
target="_blank"
rel="noopener noreferrer"
>
<img
src={playBadge}
width="280"
height="88"
alt={$t(`${NS}.playStore.ctaAlt`)}
/>
</a>
<p class="cta-links">
<a href={resolve('/mobile-app/privacy-policy')}>{$t(`${NS}.cta.privacy`)}</a>
·
<a href={resolve('/viewer')}>{$t(`${NS}.cta.web`)}</a>
</p>
</section>
<style>
.hero {
padding: 120px 24px 64px;
background-color: #ffffff;
background-image:
radial-gradient(circle at 20% 30%, rgba(6, 52, 95, 0.06), transparent 60%),
radial-gradient(circle at 80% 70%, rgba(6, 52, 95, 0.05), transparent 60%);
}
.hero-grid {
max-width: 1100px;
margin: 0 auto;
display: grid;
grid-template-columns: 1.1fr 1fr;
gap: 3rem;
align-items: center;
}
.tagline {
text-transform: uppercase;
letter-spacing: 0.12em;
font-size: 0.85rem;
color: var(--color-primary);
margin-bottom: 12px;
}
.hero h1 {
font-size: clamp(1.75rem, 4vw, 2.5rem);
line-height: 1.25;
margin: 0 0 1rem;
}
.hero-subtitle {
font-size: 1.1rem;
line-height: 1.6;
margin-bottom: 1.5rem;
color: #333;
}
.play-badge-link {
display: inline-block;
border: none;
padding: 0;
margin: 0;
}
.play-badge-link:hover {
background: transparent;
opacity: 0.9;
}
.play-badge-link.inline {
margin-top: 24px;
}
.hero-visual {
display: flex;
justify-content: center;
}
.hero-visual img {
width: 100%;
max-width: 340px;
height: auto;
}
.content-section {
max-width: 800px;
margin: 0 auto;
padding: 48px 24px;
line-height: 1.7;
}
.content-section.alt {
background: rgba(6, 52, 95, 0.04);
max-width: none;
padding-left: max(24px, calc((100% - 800px) / 2));
padding-right: max(24px, calc((100% - 800px) / 2));
}
.content-section h2 {
margin-top: 0;
font-size: 1.75rem;
}
.formats-list {
font-size: 1.15rem;
margin: 16px 0;
}
.format-links {
font-size: 0.95rem;
}
.features-section {
padding: 64px 24px;
max-width: 960px;
margin: 0 auto;
}
.features-section h2 {
text-align: center;
font-size: 1.75rem;
margin-top: 0;
}
.features-grid {
list-style: none;
padding: 0;
margin: 32px 0 0;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
.feature-card {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 16px;
background: white;
border: 1px solid #d3dce6;
border-radius: 12px;
line-height: 1.5;
color: #06345f;
}
.feature-icon {
flex-shrink: 0;
font-weight: 700;
color: var(--color-primary);
}
.steps {
padding-left: 1.25rem;
}
.steps li {
margin-bottom: 12px;
}
.table-wrap {
overflow-x: auto;
margin-top: 24px;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 0.95rem;
}
th,
td {
border: 1px solid #d3dce6;
padding: 12px 14px;
text-align: left;
}
th {
background: var(--color-primary);
color: white;
}
tr:nth-child(even) {
background: #f8fafb;
}
.screenshots-section {
padding: 64px 24px;
text-align: center;
max-width: 960px;
margin: 0 auto;
}
.screenshots-intro {
margin-bottom: 32px;
line-height: 1.6;
}
.screenshot-center img {
max-width: 320px;
width: 100%;
height: auto;
filter: drop-shadow(0 12px 32px rgba(6, 52, 95, 0.15));
}
.faq-section {
padding: 64px 24px;
max-width: 800px;
margin: 0 auto;
}
.faq-section h2 {
text-align: center;
font-size: 1.75rem;
}
.faq-intro {
text-align: center;
margin-bottom: 32px;
line-height: 1.6;
}
.faq-list details {
border-bottom: 1px solid #eee;
padding: 20px 0;
}
.faq-list summary {
cursor: pointer;
font-weight: 600;
font-size: 1.1rem;
list-style: none;
position: relative;
color: var(--color-primary);
padding-right: 28px;
}
.faq-list summary::after {
content: '+';
position: absolute;
right: 0;
font-size: 1.5rem;
transition: transform 0.3s ease;
}
.faq-list details[open] summary::after {
transform: rotate(45deg);
}
.faq-list p {
margin-top: 10px;
line-height: 1.6;
}
.cta-section {
text-align: center;
padding: 64px 24px 100px;
background: var(--color-primary);
color: white;
}
.cta-section h2 {
color: white;
margin-top: 0;
}
.cta-section > p {
margin-bottom: 24px;
opacity: 0.95;
}
.cta-links {
margin-top: 24px;
font-size: 0.95rem;
}
.cta-links a {
color: white;
border-bottom-color: white;
}
.cta-links a:hover {
color: var(--color-primary);
background: white;
}
@media (max-width: 900px) {
.hero-grid {
grid-template-columns: 1fr;
text-align: center;
}
.hero-text {
display: flex;
flex-direction: column;
align-items: center;
}
.features-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.hero {
padding-top: 110px;
}
table {
font-size: 0.85rem;
}
th,
td {
padding: 8px;
}
}
</style>

Some files were not shown because too many files have changed in this diff Show more