400 lines
8.8 KiB
Svelte
400 lines
8.8 KiB
Svelte
<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>
|