Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions bitext/src/app.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,11 @@ declare global {
}
}

/** Some verification snippets use `value` instead of `content` on `<meta>`. */
declare module 'svelte/elements' {
export interface HTMLMetaAttributes {
value?: string | undefined | null;
}
}

export {};
14 changes: 14 additions & 0 deletions bitext/src/lib/brand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ export const ALIGNER_SITE_URL = 'https://aligner.tinygods.dev';
/** Host label for attribution text in raster/SVG exports. */
export const ALIGNER_SITE_HOST = 'aligner.tinygods.dev';

/** Public contact for privacy, about, and similar pages. */
export const SITE_CONTACT_EMAIL = 'dani@tinygods.dev';

/** User-facing product name in copy (legal/site title may differ). */
export const ALIGNER_DISPLAY_NAME = 'Aligner';

/** Multiline tooltip for partner link cards; shown on “?” hint. */
export const PARTNER_LINK_WHY_TOOLTIP = `Why is this here?

${ALIGNER_DISPLAY_NAME} stays free and without aggressive ads. Hosting and ongoing upkeep still have a cost, so we add a few optional partner links - use them if you were already considering the service. It will help us keep the site running. The referral bonuses come from the provider. Here I recommend the services that I happily use myself.

Thanks,
Dani`;

/** Plain attribution line (matches standalone SVG export footer text). */
export const EXPORT_ATTRIBUTION_PLAIN = `Created with ${ALIGNER_SITE_HOST}`;

Expand Down
20 changes: 20 additions & 0 deletions bitext/src/lib/components/partners/PartnerBannerPreply.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<script lang="ts">
import PartnerBannerShell from './PartnerBannerShell.svelte';

const href =
'https://preply.com/en/?pref=MjI1NTg5OTI=&id=1778450837.497915&ep=w2';
</script>

<PartnerBannerShell
title="Preply — language tutors"
{href}
ctaLabel="Open Preply"
toneClass="border-l-[#FF7AAC] bg-[#FF7AAC]/10 dark:bg-[#FF7AAC]/15"
>
<p class="m-0">
Personally, I use Preply for language learning. If you want to try an online
tutor, our link includes{' '}
<strong class="font-medium text-gray-800 dark:text-gray-200">70% off the trial lesson</strong>.
We earn a small bonus if you go on to paid lessons - it helps to keep the site running.
</p>
</PartnerBannerShell>
18 changes: 18 additions & 0 deletions bitext/src/lib/components/partners/PartnerBannerRailway.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<script lang="ts">
import PartnerBannerShell from './PartnerBannerShell.svelte';

const href = 'https://railway.com?referralCode=J6cBod';
</script>

<PartnerBannerShell
title="Railway - easy deployment"
{href}
ctaLabel="Open Railway"
toneClass="border-l-[#853bce] bg-[#853bce]/10 dark:bg-[#853bce]/15"
>
<p class="m-0">
This project is deployed on Railway. For me it works like a charm: I just add my repo and Railway builds and deploys it by itself. This link gives <strong class="font-medium text-gray-800 dark:text-gray-200"
>$20 in credits</strong
>. No pressure - use if it fits your stack.
</p>
</PartnerBannerShell>
66 changes: 66 additions & 0 deletions bitext/src/lib/components/partners/PartnerBannerShell.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<script lang="ts">
import type { Snippet } from 'svelte';
import { PARTNER_LINK_WHY_TOOLTIP } from '$lib/brand.js';

let {
title,
href,
ctaLabel,
toneClass,
children
}: {
title: string;
href: string;
ctaLabel: string;
/** Left accent bar + pale tinted surface (light/dark), e.g. border-l-[#hex] bg-[#hex]/10 */
toneClass: string;
children: Snippet;
} = $props();

const linkClass =
'inline-flex max-w-full items-center gap-1 text-sm font-medium text-primary-700 underline decoration-primary-700/40 underline-offset-2 hover:text-primary-800 hover:decoration-primary-800 dark:text-primary-400 dark:decoration-primary-400/50 dark:hover:text-primary-300';
</script>

<article
class="min-w-0 rounded-md border border-gray-200 border-l-4 p-4 sm:p-5 dark:border-gray-700 {toneClass}"
aria-label={title}
>
<div
class="grid grid-cols-1 gap-2 sm:grid-cols-[minmax(0,1fr)_auto] sm:items-start sm:gap-x-3 sm:gap-y-2"
>
<h3
class="font-heading m-0 flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 self-start text-base font-semibold leading-snug text-gray-900 sm:col-start-1 sm:row-start-1 sm:text-lg dark:text-white"
>
<span class="min-w-0">{title}</span>
<span class="relative inline-flex shrink-0 items-center">
<button
type="button"
class="peer inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full border border-gray-300/90 bg-gray-50/80 text-[10px] font-normal leading-none text-gray-400 transition-colors hover:border-gray-400 hover:bg-gray-100 hover:text-gray-600 focus-visible:outline focus-visible:ring-2 focus-visible:ring-gray-400 dark:border-gray-600 dark:bg-gray-800/60 dark:text-gray-500 dark:hover:border-gray-500 dark:hover:bg-gray-700 dark:hover:text-gray-300 dark:focus-visible:ring-gray-500"
aria-label="Why is this here?"
>
?
</button>
<span
role="tooltip"
class="pointer-events-none absolute top-full left-1/2 z-[60] mt-1.5 hidden w-max max-w-[min(24rem,calc(100vw-2rem))] -translate-x-1/2 whitespace-pre-line rounded-md bg-gray-900 px-3 py-2.5 text-left text-xs font-normal leading-snug text-white shadow-md peer-hover:block peer-focus-visible:block dark:bg-gray-700"
>
{PARTNER_LINK_WHY_TOOLTIP}
</span>
</span>
</h3>
<div
class="text-sm leading-relaxed text-gray-600 sm:col-span-2 sm:col-start-1 sm:row-start-2 dark:text-gray-300"
>
{@render children()}
</div>
<a
class="{linkClass} mt-1 max-sm:mt-2 sm:col-start-2 sm:row-start-1 sm:mt-0 sm:justify-self-end sm:text-right"
href={href}
target="_blank"
rel="noopener noreferrer sponsored"
>
{ctaLabel}
<span aria-hidden="true">→</span>
</a>
</div>
</article>
74 changes: 43 additions & 31 deletions bitext/src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import SettingsPanel from '$lib/components/settings/SettingsPanel.svelte';
import ExportCard from '$lib/components/settings/ExportCard.svelte';
import ShareQuickRow from '$lib/components/share/ShareQuickRow.svelte';
import PartnerBannerPreply from '$lib/components/partners/PartnerBannerPreply.svelte';
import PartnerBannerRailway from '$lib/components/partners/PartnerBannerRailway.svelte';
import SeoIntro from '$lib/components/seo/SeoIntro.svelte';
import SeoSections from '$lib/components/seo/SeoSections.svelte';
import JsonLd from '$lib/components/seo/JsonLd.svelte';
Expand Down Expand Up @@ -160,37 +162,13 @@
class="w-full min-w-0 overflow-x-hidden px-4 pt-4 pb-8 sm:px-6 md:pt-6 md:pb-12 lg:px-10"
>
<header class="mb-8 border-b border-gray-200 pb-8 dark:border-gray-700">
<div class="flex flex-wrap items-start justify-between gap-x-8 gap-y-4">
<div class="min-w-0 max-w-3xl flex-1 text-left">
<h1
class="font-heading text-2xl font-semibold leading-tight tracking-tight text-gray-900 sm:text-3xl dark:text-white"
>
Word-by-word translation visualizer
</h1>
<p
class="mt-4 max-w-prose text-base leading-relaxed text-gray-600 dark:text-gray-400 lg:text-[1.05rem]"
>
See exactly which word matches which across stacked lines. Add rows for glosses or IPA if you
need them, click a word then its match on the line above or below, and export or share the
diagram—great for lessons, posts, or conlang notes.
</p>
<p class="mt-4 max-w-prose text-sm leading-relaxed text-gray-600 dark:text-gray-400">
Created by
<a
href={authorSite}
class="font-medium text-primary-700 underline decoration-primary-700/40 underline-offset-2 hover:text-primary-800 hover:decoration-primary-800 dark:text-primary-400 dark:decoration-primary-400/50 dark:hover:text-primary-300"
target="_blank"
rel="noopener noreferrer">Dani</a
>. See other
<a
href={toolsPage}
class="font-medium text-primary-700 underline decoration-primary-700/40 underline-offset-2 hover:text-primary-800 hover:decoration-primary-800 dark:text-primary-400 dark:decoration-primary-400/50 dark:hover:text-primary-300"
target="_blank"
rel="noopener noreferrer">tools</a
> for linguistics and conlanging.
</p>
</div>
<div class="flex shrink-0 flex-wrap items-center justify-end gap-3">
<div class="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between sm:gap-4">
<h1
class="font-heading min-w-0 text-left text-2xl font-semibold leading-tight tracking-tight text-gray-900 sm:flex-1 sm:pr-4 sm:text-3xl dark:text-white"
>
Word-by-word translation visualizer
</h1>
<div class="flex shrink-0 flex-wrap items-center gap-3 sm:justify-end">
<a
href={resolve('/about')}
class="text-sm font-medium text-gray-600 underline decoration-gray-400/50 underline-offset-2 hover:text-gray-900 hover:decoration-gray-500/60 dark:text-gray-400 dark:decoration-gray-500/50 dark:hover:text-gray-100 dark:hover:decoration-gray-400/60"
Expand Down Expand Up @@ -254,6 +232,37 @@
</div>
</div>
</div>
<div
class="mt-6 flex flex-col gap-6 lg:flex-row lg:items-start lg:justify-between lg:gap-8 xl:gap-10"
>
<div class="min-w-0 max-w-3xl flex-1 text-left">
<p
class="mt-0 max-w-prose text-base leading-relaxed text-gray-600 dark:text-gray-400 lg:text-[1.05rem]"
>
See exactly which word matches which across stacked lines. Add rows for glosses or IPA if you
need them, click a word then its match on the line above or below, and export or share the
diagram—great for lessons, posts, or conlang notes.
</p>
<p class="mt-4 max-w-prose text-sm leading-relaxed text-gray-600 dark:text-gray-400">
Created by
<a
href={authorSite}
class="font-medium text-primary-700 underline decoration-primary-700/40 underline-offset-2 hover:text-primary-800 hover:decoration-primary-800 dark:text-primary-400 dark:decoration-primary-400/50 dark:hover:text-primary-300"
target="_blank"
rel="noopener noreferrer">Dani</a
>. See other
<a
href={toolsPage}
class="font-medium text-primary-700 underline decoration-primary-700/40 underline-offset-2 hover:text-primary-800 hover:decoration-primary-800 dark:text-primary-400 dark:decoration-primary-400/50 dark:hover:text-primary-300"
target="_blank"
rel="noopener noreferrer">tools</a
> for linguistics and conlanging.
</p>
</div>
<div class="w-full min-w-0 lg:w-auto lg:flex-1">
<PartnerBannerPreply />
</div>
</div>
</header>

<div class="grid grid-cols-12 gap-6 lg:gap-8">
Expand Down Expand Up @@ -490,6 +499,9 @@
<div class="mt-6">
<ShareQuickRow />
</div>
<div class="mt-6 min-w-0">
<PartnerBannerRailway />
</div>
<p class="mt-2 text-center">
<!-- Looks like a text link; <button> avoids SvelteKit href + eslint; Tally uses data-tally-*. -->
<button
Expand Down
34 changes: 33 additions & 1 deletion bitext/src/routes/about/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<script lang="ts">
import { page } from '$app/state';
import { resolve } from '$app/paths';
import { TALLY_FORM_ID } from '$lib/brand.js';
import { SITE_CONTACT_EMAIL, TALLY_FORM_ID } from '$lib/brand.js';
import PartnerBannerPreply from '$lib/components/partners/PartnerBannerPreply.svelte';
import PartnerBannerRailway from '$lib/components/partners/PartnerBannerRailway.svelte';
import { settingsStore } from '$lib/state/settings.svelte.js';

const TITLE = 'About';
Expand Down Expand Up @@ -244,6 +246,8 @@
<li><a href="#doc-settings" class={tocLinkClass}>Settings</a></li>
<li><a href="#doc-export-share" class={tocLinkClass}>Export and share</a></li>
<li><a href="#doc-examples" class={tocLinkClass}>Examples and motion demos</a></li>
<li><a href="#doc-partners" class={tocLinkClass}>Partner links</a></li>
<li><a href="#doc-contact" class={tocLinkClass}>Contact</a></li>
<li><a href="#doc-privacy" class={tocLinkClass}>Privacy</a></li>
</ul>
</nav>
Expand Down Expand Up @@ -425,6 +429,34 @@
</figure>
</div>

<h2 id="doc-partners" class={headingClass}>Partner links</h2>
<p class="mt-3">
{DISPLAY_NAME} stays free and without aggressive ads. Hosting and ongoing upkeep still have a cost, so
we add a few optional partner links - use them if you were already considering the service. It will help us keep the site running. The
referral bonuses come from the provider. Here I recommend the services that I happily use myself.
</p>
<div class="mt-5 flex min-w-0 flex-col gap-4">
<PartnerBannerPreply />
<PartnerBannerRailway />
</div>

<h2 id="doc-contact" class={headingClass}>Contact</h2>
<p class="mt-3">
Questions or feedback about {DISPLAY_NAME}:{' '}
<a href={`mailto:${SITE_CONTACT_EMAIL}`} class={linkClass}>{SITE_CONTACT_EMAIL}</a>
<span class="text-gray-400 dark:text-gray-600"> · </span>
<button
type="button"
class={feedbackBtnClass}
data-tally-open={TALLY_FORM_ID}
data-tally-auto-close="0"
data-tally-hide-title="1"
data-tally-form-events-forwarding="1"
>
Feedback form (Tally)
</button>
</p>

<h2 id="doc-privacy" class={headingClass}>Privacy</h2>
<p class="mt-3">
We do not run accounts or store your text on our infrastructure. Details on analytics, feedback,
Expand Down
15 changes: 10 additions & 5 deletions bitext/src/routes/privacy/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import { page } from '$app/state';
import { resolve } from '$app/paths';
import { SITE_CONTACT_EMAIL } from '$lib/brand.js';
import { SITE_NAME } from '$lib/seo/metadata.js';

const TITLE = 'Privacy policy';
Expand All @@ -9,7 +10,7 @@

const canonical = $derived(page.url.origin + page.url.pathname);
/** Last meaningful content review of this policy. Bump when wording changes. */
const LAST_UPDATED = '2026-05-09';
const LAST_UPDATED = '2026-05-10';

const headingClass = 'font-heading mt-8 text-xl font-semibold text-gray-900 dark:text-white';
const linkClass =
Expand Down Expand Up @@ -47,8 +48,9 @@

<p class="mt-6">
<a href={resolve('/')} class={linkClass}>{SITE_NAME}</a> ("the tool", "we") is a free, browser-based
visualizer for word-by-word translations. We don't run user accounts, we don't ask for an email, and
the sentences you align never leave your browser unless you choose to share or export them. This page
visualizer for word-by-word translations. We don't run user accounts, you don't need to provide an
email address to use the tool, and the sentences you align never leave your browser unless you choose
to share or export them. This page
summarizes what data is involved when you use the tool.
</p>

Expand Down Expand Up @@ -172,12 +174,15 @@

<h2 class={headingClass}>Contact</h2>
<p class="mt-3">
Questions or requests about this policy can be sent through the
Questions or requests about this policy:
<a class={linkClass} href={`mailto:${SITE_CONTACT_EMAIL}`}>{SITE_CONTACT_EMAIL}</a>
<span class="text-gray-400 dark:text-gray-600"> · </span>
the
<a
class={linkClass}
href="https://danipolani.github.io/en/"
target="_blank"
rel="noopener noreferrer">author's site</a
> or via the in-app "Send feedback" form.
>, or the in-app "Send feedback" form (Tally).
</p>
</main>
Loading