Compare commits

..

No commits in common. "main" and "redesign_all" have entirely different histories.

57 changed files with 517 additions and 4467 deletions

View file

@ -1,73 +1,14 @@
# 🧵 Embroidery Viewer
# Embroidery Viewer
![Logo](/logo.webp)
![Deploy workflow status](https://git.leomurca.xyz/leomurca/embroidery-viewer/actions/workflows/deploy.yml/badge.svg)
**The simplest way to preview embroidery files — instantly, in your browser.**
👉 **Try it now:** https://embroideryviewer.xyz
A free online tool to view embroidery files.
Available at https://embroideryviewer.xyz.
![Demo](/demo.gif)
<a href="https://buymeacoffee.com/embroideryviewerxyz">
<img src="docs/yellow-button.png" width="200" alt="Alt Text">
</a>
Current supported formats: **.pes, .dst, .pec, .jef and .exp**.
---
## ✨ Why Embroidery Viewer?
Working with embroidery files shouldnt require heavy, expensive software.
Embroidery Viewer was built to solve a simple problem:
> _“I just want to quickly see my design.”_
No installs. No friction. Just drag, drop, and view.
---
## 🚀 Features
- ⚡ **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
---
Inspired by https://github.com/redteam316/html5-embroidery.git.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

600
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
{
"name": "embroidery-viewer",
"private": true,
"version": "3.1.0",
"version": "3.0.0",
"type": "module",
"scripts": {
"dev": "vite dev",

View file

@ -14,31 +14,23 @@ Buy me a coffee and help keep it running ☕"
data-x_margin="18"
data-y_margin="18"
></script>
<!-- Basic -->
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="author" content="Embroidery Viewer" />
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Caveat:wght@400..700&display=swap"
rel="stylesheet"
/>
<!-- Favicon -->
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<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%
</head>
<body data-sveltekit-preload-data="hover">

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

@ -9,66 +9,6 @@
</script>
<footer>
<div class="footer-main">
<div class="footer-decoration" aria-hidden="true">
<svg
class="footer-decoration__svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1440 480"
preserveAspectRatio="xMidYMid slice"
fill="none"
>
<g stroke="white" stroke-linecap="round">
<circle
cx="1180"
cy="360"
r="130"
stroke-width="2"
stroke-opacity="0.14"
stroke-dasharray="10 8"
/>
<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 id="content-container">
<section class="footer-block">
<img
@ -96,11 +36,6 @@
<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
@ -115,24 +50,7 @@
>
</section>
</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"
@ -146,31 +64,11 @@
<style>
footer {
width: 100%;
}
.footer-main {
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;
justify-content: space-between;
@ -251,30 +149,12 @@
}
.credits-container {
position: relative;
z-index: 1;
background-color: var(--color-secondary);
color: white;
margin: 0 auto;
padding: 20px 30px;
padding-left: 9%;
width: 100%;
overflow: hidden;
}
.credits-decoration {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 48px;
pointer-events: none;
transform: translateY(-50%);
}
.credits-decoration svg {
width: 100%;
height: 100%;
}
.credits-container a {

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

@ -51,7 +51,7 @@
display: flex;
align-items: center;
justify-content: space-between;
padding: 45px 30px 10px 100px;
padding: 10px 30px 10px 100px;
width: 100%;
}
@ -212,20 +212,14 @@
}
}
@media (max-width: 1159px) {
header {
padding-top: 70px;
}
}
@media (max-width: 768px) {
header {
padding: 110px 20px 10px 20px;
padding: 10px 20px;
}
#menu {
width: 100vw;
top: -110px;
top: -80px;
margin: 0px 0 0 0;
right: -20px;
border-radius: 0;

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={$t(ogImage)} /> -->
<meta property="og:url" content={$t(url)} />
<!-- Twitter -->
<!-- <meta name="twitter:card" content={$t(twitterCard)} /> -->
<meta name="twitter:title" content={$t(twitterTitle)} />
<meta name="twitter:description" content={$t(twitterDescription)} />
<!-- <meta name="twitter:image" content={$t(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

@ -70,64 +70,8 @@
.app {
position: relative;
padding: 6rem 1.5rem;
background: #ffffff;
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 {

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

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

@ -8,7 +8,7 @@
},
"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."
"description": "Embroidery Viewer supports popular formats such as PES, DST, and EXP. This allows you to preview most embroidery designs used by home and commercial machines."
},
"needSoftware": {
"summary": "Do I need to install any embroidery software?",

View file

@ -8,11 +8,6 @@
"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.",

View file

@ -1,12 +1,7 @@
{
"seo.title": "Free Online Embroidery File Viewer Fast, Private & No Signup",
"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.",
"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",
"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.image": "https://embroideryviewer.xyz/og/viewer.png",
"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."
"seo.image": "https://embroideryviewer.xyz/og/"
}

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

@ -73,41 +73,6 @@ const config = {
routes: ['/viewer'],
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,
key: 'header',
@ -162,41 +127,6 @@ const config = {
routes: ['/viewer'],
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',
@ -237,26 +167,6 @@ const config = {
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,
},
],
};

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

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

@ -8,7 +8,7 @@
},
"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."
"description": "O Embroidery Viewer suporta formatos populares como PES, DST e EXP. Isso permite visualizar a maioria dos designs de bordado usados em máquinas domésticas e industriais."
},
"needSoftware": {
"summary": "Preciso instalar algum software de bordado?",

View file

@ -8,11 +8,6 @@
"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.",

View file

@ -1,12 +1,7 @@
{
"seo.title": "Visualizador de Bordado Online Grátis Rápido, Privado e Sem Cadastro",
"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.",
"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",
"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.image": "https://embroideryviewer.xyz/og/viewer.png",
"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."
"seo.image": "https://embroideryviewer.xyz/og/"
}

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

@ -9,7 +9,6 @@
import Header from '$lib/components/Header.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();
@ -25,7 +24,6 @@
<Analytics />
{#if mounted}
<AnnouncementBar />
<Header />
<main>
{@render children()}

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,118 +1,17 @@
<script>
import { PUBLIC_IMAGE_BASE_URL } from '$env/static/public';
import { t } from '$lib/translations';
import Head from '$lib/components/Head.svelte';
import StructuredData from '$lib/components/StructuredData.svelte';
import Seo from '$lib/components/Seo.svelte';
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 viewerUrl = `${baseUrl}/viewer`;
const logoUrl = `${PUBLIC_IMAGE_BASE_URL}/t/f_webp/embroidery-viewer/logo-icon.webp`;
let { data } = $props();
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}`),
})),
},
],
};
// svelte-ignore state_referenced_locally
const metadata = data.metadata;
</script>
<Head
title="home.seo.title"
description="home.seo.description"
keywords="home.seo.keywords"
url="home.seo.url"
ogImage="home.seo.image"
/>
<StructuredData data={structuredData} />
<Seo {...metadata} />
<Hero />
<Features />

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,23 +1,24 @@
<script>
import DonateIcon from '$lib/components/icons/DonateIcon.svelte';
import { resolve } from '$app/paths';
import { PUBLIC_IMAGE_BASE_URL } from '$env/static/public';
import { t } from '$lib/translations';
import Seo from '$lib/components/Seo.svelte';
import { PUBLIC_IMAGE_BASE_URL } from '$env/static/public';
import { isMobile } from '$lib/utils/isMobile';
import Head from '$lib/components/Head.svelte';
import DonateIcon from '$lib/components/icons/DonateIcon.svelte';
/** @type {import('./$types').PageProps} */
let { data } = $props();
// svelte-ignore state_referenced_locally
const metadata = data.metadata;
const backgroundImage = isMobile()
? `${PUBLIC_IMAGE_BASE_URL}/t/f_webp/embroidery-viewer/route-wallpaper-mobile.webp`
: `${PUBLIC_IMAGE_BASE_URL}/t/f_webp,w_1920,h_1080/embroidery-viewer/route-wallpaper.webp`;
</script>
<Head
title="about.seo.title"
description="about.seo.description"
keywords="about.seo.keywords"
url="about.seo.url"
/>
<Seo {...metadata} />
<section aria-labelledby="about-heading">
<div

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>

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 = 'exp-file-viewer';
const baseUrl = 'https://embroideryviewer.xyz/exp-file-viewer';
const expFilePreview = `${PUBLIC_IMAGE_BASE_URL}/t/f_webp,w_800/embroidery-viewer/${$locale}/exp-file-preview.webp`;
const viewerInterface = `${PUBLIC_IMAGE_BASE_URL}/t/f_webp,w_800/embroidery-viewer/${$locale}/viewer-interface.webp`;
const faqKeys = [
'openExpOnline',
'whatIsExp',
'otherFormats',
'howToView',
'convertExp',
'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 — EXP File Viewer',
url: baseUrl,
applicationCategory: 'DesignApplication',
operatingSystem: 'Any',
offers: {
'@type': 'Offer',
price: '0',
priceCurrency: 'USD',
},
description: $t(`${NS}.seo.description`),
featureList: 'EXP, PES, DST, JEF, 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="exp-hero-heading">
<p class="tagline">{$t(`${NS}.hero.tagline`)}</p>
<h1 id="exp-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-exp" 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 ['exp', 'pes', 'dst', 'jef'] 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-exp" 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={expFilePreview}
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,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 = 'jef-file-viewer';
const baseUrl = 'https://embroideryviewer.xyz/jef-file-viewer';
const jefFilePreview = `${PUBLIC_IMAGE_BASE_URL}/t/f_webp,w_800/embroidery-viewer/${$locale}/jef-file-preview.webp`;
const viewerInterface = `${PUBLIC_IMAGE_BASE_URL}/t/f_webp,w_800/embroidery-viewer/${$locale}/viewer-interface.webp`;
const faqKeys = [
'openJefOnline',
'whatIsJef',
'pesViewer',
'howToView',
'convertJef',
'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 — JEF File Viewer',
url: baseUrl,
applicationCategory: 'DesignApplication',
operatingSystem: 'Any',
offers: {
'@type': 'Offer',
price: '0',
priceCurrency: 'USD',
},
description: $t(`${NS}.seo.description`),
featureList: 'JEF, PES, DST, 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="jef-hero-heading">
<p class="tagline">{$t(`${NS}.hero.tagline`)}</p>
<h1 id="jef-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-jef" 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 ['jef', 'pes', 'dst', '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-jef" 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={jefFilePreview}
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

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

View file

@ -1,14 +1,15 @@
<script>
import { t } from '$lib/translations';
import Head from '$lib/components/Head.svelte';
import Seo from '$lib/components/Seo.svelte';
/** @type {import('./$types').PageProps} */
let { data } = $props();
// svelte-ignore state_referenced_locally
const metadata = data.metadata;
</script>
<Head
title="mobile.app.privacy.policy.seo.title"
description="mobile.app.privacy.policy.seo.description"
keywords="mobile.app.privacy.policy.seo.keywords"
url="mobile.app.privacy.policy.seo.url"
/>
<Seo {...metadata} />
<section aria-labelledby="privacy-policy-heading">
<h1 id="privacy-policy-heading">{$t('mobile.app.privacy.policy.title')}</h1>

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 = 'pes-file-viewer';
const baseUrl = 'https://embroideryviewer.xyz/pes-file-viewer';
const pesFilePreview = `${PUBLIC_IMAGE_BASE_URL}/t/f_webp,w_800/embroidery-viewer/${$locale}/pes-file-preview.webp`;
const viewerInterface = `${PUBLIC_IMAGE_BASE_URL}/t/f_webp,w_800/embroidery-viewer/${$locale}/viewer-interface.webp`;
const faqKeys = [
'openPesOnline',
'dstViewer',
'howToView',
'convertPesDst',
'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 — PES File Viewer',
url: baseUrl,
applicationCategory: 'DesignApplication',
operatingSystem: 'Any',
offers: {
'@type': 'Offer',
price: '0',
priceCurrency: 'USD',
},
description: $t(`${NS}.seo.description`),
featureList: 'PES, DST, 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="pes-hero-heading">
<p class="tagline">{$t(`${NS}.hero.tagline`)}</p>
<h1 id="pes-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-pes" 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 ['pes', 'dst', '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-pes-dst" 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={pesFilePreview}
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

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

View file

@ -1,14 +1,15 @@
<script>
import { t } from '$lib/translations';
import Head from '$lib/components/Head.svelte';
import Seo from '$lib/components/Seo.svelte';
/** @type {import('./$types').PageProps} */
let { data } = $props();
// svelte-ignore state_referenced_locally
const metadata = data.metadata;
</script>
<Head
title="privacy.policy.seo.title"
description="privacy.policy.seo.description"
keywords="privacy.policy.seo.keywords"
url="privacy.policy.seo.url"
/>
<Seo {...metadata} />
<section aria-labelledby="privacy-policy-heading">
<h1 id="privacy-policy-heading">{$t('privacy.policy.title')}</h1>

View file

@ -2,26 +2,21 @@ export async function GET() {
const baseUrl = 'https://embroideryviewer.xyz';
const pages = [
{ path: '', priority: '0.8' },
{ path: 'pes-file-viewer', priority: '0.9' },
{ path: 'dst-file-viewer', priority: '0.9' },
{ path: 'jef-file-viewer', priority: '0.9' },
{ path: 'exp-file-viewer', priority: '0.9' },
{ path: 'embroidery-viewer-android', priority: '0.9' },
{ path: 'viewer', priority: '0.85' },
{ path: 'about', priority: '0.8' },
{ path: 'donate', priority: '0.8' },
{ path: 'terms-of-service', priority: '0.8' },
{ path: 'privacy-policy', priority: '0.8' },
'',
'about',
'donate',
'terms-of-service',
'privacy-policy',
'viewer',
];
const urls = pages
.map(
({ path, priority }) => `
(page) => `
<url>
<loc>${baseUrl}/${path}</loc>
<loc>${baseUrl}/${page}</loc>
<changefreq>weekly</changefreq>
<priority>${priority}</priority>
<priority>0.8</priority>
</url>`,
)
.join('');

View file

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

View file

@ -4,19 +4,20 @@
import { isMobile } from '$lib/utils/isMobile';
import BuyMeACoffeeIcon from '$lib/components/icons/BuyMeACoffeeIcon.svelte';
import Head from '$lib/components/Head.svelte';
import Seo from '$lib/components/Seo.svelte';
/** @type {import('./$types').PageProps} */
let { data } = $props();
// svelte-ignore state_referenced_locally
const metadata = data.metadata;
const backgroundImage = isMobile()
? `${PUBLIC_IMAGE_BASE_URL}/t/f_webp/embroidery-viewer/route-wallpaper-mobile.webp`
: `${PUBLIC_IMAGE_BASE_URL}/t/f_webp,w_1920,h_1080/embroidery-viewer/route-wallpaper.webp`;
</script>
<Head
title="support-us.seo.title"
description="support-us.seo.description"
keywords="support-us.seo.keywords"
url="support-us.seo.url"
/>
<Seo {...metadata} />
<section aria-labelledby="support-us">
<div

View file

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

View file

@ -1,14 +1,15 @@
<script>
import { t } from '$lib/translations';
import Head from '$lib/components/Head.svelte';
import Seo from '$lib/components/Seo.svelte';
/** @type {import('./$types').PageProps} */
let { data } = $props();
// svelte-ignore state_referenced_locally
const metadata = data.metadata;
</script>
<Head
title="terms.of.service.seo.title"
description="terms.of.service.seo.description"
keywords="terms.of.service.seo.keywords"
url="terms.of.service.seo.url"
/>
<Seo {...metadata} />
<section aria-labelledby="tos-heading">
<h1 id="tos-heading">{$t('terms.of.service.title')}</h1>

View file

@ -6,7 +6,7 @@
import CardList from '$lib/components/CardList.svelte';
import Dropzone from '$lib/components/Dropzone.svelte';
import FileList from '$lib/components/FileList.svelte';
import Head from '$lib/components/Head.svelte';
import Seo from '$lib/components/Seo.svelte';
import { filterFiles } from '$lib/utils/filterFiles';
import { supportedFormats } from '$lib/format-readers';
@ -78,14 +78,15 @@
if (el) el.click();
}
}
/** @type {import('./$types').PageProps} */
let { data } = $props();
// svelte-ignore state_referenced_locally
const metadata = data.metadata;
</script>
<Head
title="viewer.seo.title"
description="viewer.seo.description"
keywords="viewer.seo.keywords"
url="viewer.seo.url"
/>
<Seo {...metadata} />
<!-- eslint-disable svelte/no-at-html-tags -->
<form id="form" enctype="multipart/form-data" onsubmit={onSubmit}>

1
static/ads.txt Normal file
View file

@ -0,0 +1 @@
google.com, pub-5761689301112420, DIRECT, f08c47fec0942fa0

BIN
static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB