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
Binary file added public/sponsors/voyageage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 5 additions & 2 deletions src/components/SiteFooter.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
import { getLang, t, localePath, sponsorHref } from "~/i18n";
import { getLang, t, localePath } from "~/i18n";
import { sponsors } from "~/i18n/pages/sponsors";

import TwMark from "./TwMark.astro";
import Bi from "./Bi.astro";
Expand Down Expand Up @@ -29,7 +30,9 @@ const link = (path: string) => ({ href: localePath(lang, path), "data-localize":
<Bi en={en.footer.tagline} zh={zh.footer.tagline} both={bilingual} />
</p>
<p class="mt-3 text-[var(--color-muted)]">
<Bi en={en.common.sponsors} zh={zh.common.sponsors} both={bilingual} /><a href={sponsorHref} class="underline decoration-white/20 underline-offset-[3px] hover:text-white"><Bi en={en.common.sponsorsCta} zh={zh.common.sponsorsCta} both={bilingual} /></a>
<Bi en={en.common.sponsors} zh={zh.common.sponsors} both={bilingual} />{sponsors.map((s) => (
<Fragment><a href={s.href} target="_blank" rel="noopener" class="underline decoration-white/20 underline-offset-[3px] hover:text-white">{s.name}</a> · </Fragment>
))}<a {...link("/sponsors")} class="underline decoration-white/20 underline-offset-[3px] hover:text-white"><Bi en={en.common.sponsorsCta} zh={zh.common.sponsorsCta} both={bilingual} /></a>
</p>
</div>

Expand Down
34 changes: 30 additions & 4 deletions src/components/pages/LitePage.astro
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import TwMark from "~/components/TwMark.astro";
import Base from "~/layouts/Base.astro";
import SiteHeader from "~/components/SiteHeader.astro";
import SiteFooter from "~/components/SiteFooter.astro";
import { getLang, localePath, sponsorHref, t } from "~/i18n";
import { getLang, localePath, t } from "~/i18n";
import { sponsors } from "~/i18n/pages/sponsors";
import { liteCopy } from "~/i18n/pages/lite";
import { formatSize, getLatestLiteRelease } from "~/lib/github";
import { liteLd } from "~/lib/structured-data";
Expand Down Expand Up @@ -158,7 +159,15 @@ const archLabel = { x64: "x64", arm64: "ARM64" } as const;
<LiteDownload />
<a href="#install" class="ghost">{c.hero.ctaSecondary}</a>
</div>
<p class="sponsor rise" style="--d:3">{ui.sponsors}<a href={sponsorHref}>{ui.sponsorsCta}</a></p>
<p class="sponsor rise" style="--d:3">
<span>{ui.sponsors}</span>
{sponsors.map((s) => (
<a class="sponsor-logo" href={s.href} target="_blank" rel="noopener">
<img src={s.logo} alt={s.name} width={s.logoWidth / 6} height={s.logoHeight / 6} />
</a>
))}
<a class="sponsor-cta" href={localePath(lang, "/sponsors")}>{ui.sponsorsCta}</a>
</p>
</div>
<div class="hero-mark" aria-hidden="true">
<div class="mark-halo"></div>
Expand Down Expand Up @@ -995,6 +1004,10 @@ const archLabel = { x64: "x64", arm64: "ARM64" } as const;
}
.sponsor {
margin: 20px 0 0;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 18px;
font-size: 14px;
color: var(--color-dim);
}
Expand All @@ -1004,14 +1017,27 @@ const archLabel = { x64: "x64", arm64: "ARM64" } as const;
margin-top: 48px;
}
}
.sponsor a {
.sponsor-logo {
display: inline-flex;
opacity: 0.8;
transition: opacity 160ms;
}
.sponsor-logo:hover {
opacity: 1;
}
.sponsor-logo img {
display: block;
height: 18px;
width: auto;
}
.sponsor-cta {
color: var(--color-muted);
text-decoration: underline;
text-decoration-color: rgba(255, 255, 255, 0.2);
text-underline-offset: 3px;
transition: color 160ms;
}
.sponsor a:hover {
.sponsor-cta:hover {
color: var(--color-text);
}
@media (max-width: 1023px) {
Expand Down
148 changes: 148 additions & 0 deletions src/components/pages/SponsorsPage.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
---
// The /sponsors page: the sponsors of ThinkWatch Lite and how to become one.
// Copy and the sponsor list live in src/i18n/pages/sponsors.ts.
import Base from "~/layouts/Base.astro";
import SiteHeader from "~/components/SiteHeader.astro";
import SiteFooter from "~/components/SiteFooter.astro";
import NeonMotion from "~/components/NeonMotion.astro";
import { getLang, sponsorHref } from "~/i18n";
import { sponsors, sponsorsCopy } from "~/i18n/pages/sponsors";
import { breadcrumbs, webPage } from "~/lib/structured-data";

const lang = getLang(Astro);
const c = sponsorsCopy[lang];
const reportHref = sponsorHref.replace("Sponsorship", "Sponsor%20Report");

const jsonLd = [
webPage(lang, "/sponsors", c.meta.title, c.meta.description),
breadcrumbs(lang, "/sponsors", c.meta.breadcrumbHome, c.meta.breadcrumbPage),
];
---

<Base title={c.meta.title} description={c.meta.description} jsonLd={jsonLd}>
<SiteHeader />
<main id="main">
<section class="nx-hero sp-hero">
<div class="nx-aurora" aria-hidden="true"><i></i><i></i><i></i></div>
<div class="nx-gridbg" aria-hidden="true"></div>
<div class="container-page">
<p class="nx-label nx-rise">{c.hero.eyebrow}</p>
<h1 class="nx-h1 sp-h1 nx-rise" style="--d:1"><span class="nx-grad">{c.hero.title}</span></h1>
<p class="nx-sub nx-rise" style="--d:2">{c.hero.sub}</p>

<!-- One tile per sponsor: logo, the sponsor's own tagline and description, and its site -->
<div class="sp-list nx-rise" style="--d:3">
{sponsors.map((s) => {
const e = c.entries[s.name];
return (
<article class="nx-tile sp-card">
<a href={s.href} target="_blank" rel="noopener" class="sp-logo">
<img src={s.logo} alt={s.name} width={s.logoWidth / 3} height={s.logoHeight / 3} />
</a>
<h2 class="sp-tagline">{e.tagline}</h2>
<p class="nx-body">{e.body}</p>
<div class="nx-actions">
<a href={s.href} target="_blank" rel="noopener" class="nx-pill">{e.visit}</a>
</div>
</article>
);
})}
</div>
</div>
</section>

<!-- Where sponsors appear, and the contact -->
<section class="nx-section-tight nx-hairline">
<div class="container-page sp-become">
<div>
<h2 class="nx-h3">{c.become.title}</h2>
<p class="nx-body">{c.become.body}</p>
<p class="nx-body">{c.become.note}</p>
</div>
<a href={sponsorHref} class="nx-ghost sp-mail">{c.become.contactLabel}</a>
</div>
</section>

<!-- What the project does and does not answer for -->
<section id="disclaimer" class="nx-section-tight nx-hairline sp-disclaimer">
<div class="container-page">
<h2 class="nx-h3">{c.disclaimer.title}</h2>
{c.disclaimer.body.map((p) => <p class="nx-body">{p}</p>)}
<p class="nx-body">{c.disclaimer.report.lead}<a href={reportHref} class="sp-inline-link">{c.disclaimer.report.label}</a>{c.disclaimer.report.end}</p>
</div>
</section>
</main>
<SiteFooter />
<NeonMotion />
</Base>

<style>
.sp-hero {
padding-bottom: 72px;
}
.sp-h1 {
margin-top: 16px;
}
.sp-list {
margin-top: 44px;
display: grid;
gap: 12px;
}
.sp-card {
padding: 36px;
}
@media (max-width: 639px) {
.sp-card {
padding: 26px 22px;
}
}
.sp-logo {
display: inline-block;
}
.sp-logo img {
display: block;
width: 240px;
max-width: 100%;
height: auto;
}
.sp-tagline {
margin: 28px 0 0;
font-size: 22px;
font-weight: 600;
letter-spacing: -0.015em;
color: #fff;
}
.sp-card .nx-body {
margin-top: 12px;
max-width: 560px;
}
.sp-card .nx-actions {
margin-top: 28px;
}
.sp-become {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 24px 48px;
}
.sp-become > div {
max-width: 680px;
}
.sp-disclaimer {
scroll-margin-top: 5rem;
}
.sp-inline-link {
color: var(--color-text);
text-decoration: underline;
text-decoration-color: rgba(255, 255, 255, 0.25);
text-underline-offset: 3px;
}
.sp-inline-link:hover {
color: #fff;
}
.sp-disclaimer .nx-body {
max-width: 760px;
font-size: 14px;
}
</style>
6 changes: 3 additions & 3 deletions src/i18n/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

export type Lang = "en" | "zh-CN";

/** Where the sponsor line on the Lite page and in the footer leads; the Lite README links to the same address */
/** The address for sponsorship enquiries, on the /sponsors page; the Lite README links to the same address */
export const sponsorHref = "mailto:fylorn@outlook.com?subject=ThinkWatch%20Lite%20Sponsorship";

const dict = {
Expand All @@ -15,7 +15,7 @@ const dict = {
copied: "Copied ✓",
skipToContent: "Skip to content",
sponsors: "Sponsors: ",
sponsorsCta: "Want to appear here?",
sponsorsCta: "Become a sponsor",
},

nav: {
Expand Down Expand Up @@ -247,7 +247,7 @@ const dict = {
copied: "已复制 ✓",
skipToContent: "跳转到正文",
sponsors: "赞助商:",
sponsorsCta: "想出现在这里吗?",
sponsorsCta: "成为赞助商",
},

nav: {
Expand Down
5 changes: 2 additions & 3 deletions src/i18n/pages/pricing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const links = {
liteLicense: "https://github.com/ThinkWatchProject/ThinkWatch-Lite/blob/main/LICENSE",
coreLicense: "https://github.com/ThinkWatchProject/ThinkWatch-Core/blob/main/LICENSE",
sales: "mailto:fylorn@outlook.com",
salesLabel: "fylorn@outlook.com",
} as const;

const tierNumbers = [
Expand Down Expand Up @@ -132,7 +131,7 @@ export const pricingCopy = {
contact: {
title: "Commercial licensing",
body: "If either free threshold is exceeded, a commercial license must be obtained before continuing production use of ThinkWatch Enterprise.",
label: links.salesLabel,
label: "Contact us",
href: links.sales,
},
},
Expand Down Expand Up @@ -246,7 +245,7 @@ export const pricingCopy = {
contact: {
title: "商业授权",
body: "超出任一免费阈值后,继续在生产环境使用 ThinkWatch 企业版之前须获取商业授权。",
label: links.salesLabel,
label: "联系我们",
href: links.sales,
},
},
Expand Down
105 changes: 105 additions & 0 deletions src/i18n/pages/sponsors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copy for the /sponsors page, and the sponsor list the Lite page and the
// footer show. A sponsor's tagline and description are its own copy, used as
// supplied (the Chinese is a translation of it); the Lite README (both
// languages) carries the same text, so change both together.

import type { Lang } from "~/i18n";

export interface Sponsor {
name: string;
href: string;
/** A white logo on a transparent background, under public/sponsors */
logo: string;
/** The logo's pixel size; it is shown at half of it or less */
logoWidth: number;
logoHeight: number;
}

export const sponsors: Sponsor[] = [
{
name: "VoyageAge",
href: "https://www.voyageage.com",
logo: "/sponsors/voyageage.png",
logoWidth: 720,
logoHeight: 108,
},
];

export const sponsorsCopy: Record<Lang, {
meta: { title: string; description: string; breadcrumbHome: string; breadcrumbPage: string };
hero: { eyebrow: string; title: string; sub: string };
entries: Record<string, { tagline: string; body: string; visit: string }>;
/** contactLabel is the text of the mail link; the address itself is not shown, as in the Lite README */
become: { title: string; body: string; note: string; contactLabel: string };
/** Shown at #disclaimer; the Lite README links there. The last paragraph ends with report, a mail link */
disclaimer: { title: string; body: string[]; report: { lead: string; label: string; end: string } };
}> = {
en: {
meta: {
title: "Sponsors · ThinkWatch Lite",
description: "The companies that sponsor the development of ThinkWatch Lite, and how to become a sponsor.",
breadcrumbHome: "Home",
breadcrumbPage: "Sponsors",
},
hero: {
eyebrow: "ThinkWatch Lite",
title: "Sponsors",
sub: "The companies below sponsor the development of ThinkWatch Lite. ThinkWatch Lite remains free and open source under the MIT license.",
},
entries: {
VoyageAge: {
tagline: "One API. More models. Lower costs.",
body: "VoyageAge brings leading AI models together through one OpenAI-compatible API. Built for developers, teams, and AI startups, it offers competitive pricing and pay-as-you-go billing, making it easier to integrate AI into your projects. Sign up for free trial credits and start building.",
visit: "Visit voyageage.com",
},
},
become: {
title: "Become a sponsor",
body: "Sponsors appear in the ThinkWatch Lite README in English and Chinese, on the ThinkWatch Lite page of this site and on this page.",
note: "Sponsorship does not affect how ThinkWatch Lite routes, prices or protects requests. The app itself shows no sponsors.",
contactLabel: "Contact us",
},
disclaimer: {
title: "Disclaimer",
body: [
"Sponsors are independent third parties. The ThinkWatch project does not operate, endorse or guarantee their products or services, and is not responsible for their content, availability, pricing, handling of data or conduct. Descriptions of a sponsor on this site and in the README are supplied by the sponsor.",
"Before using a sponsor's service, review its terms of service and privacy policy. Any agreement for that service is between the user and the sponsor.",
],
report: { lead: "A sponsor found to mislead or harm users is removed. ", label: "Report a problem with a sponsor", end: "." },
},
},
"zh-CN": {
meta: {
title: "赞助商 · ThinkWatch Lite",
description: "赞助 ThinkWatch Lite 开发的公司,以及成为赞助商的方式。",
breadcrumbHome: "首页",
breadcrumbPage: "赞助商",
},
hero: {
eyebrow: "ThinkWatch Lite",
title: "赞助商",
sub: "以下公司赞助 ThinkWatch Lite 的开发。ThinkWatch Lite 依然免费,以 MIT 许可证开源。",
},
entries: {
VoyageAge: {
tagline: "一个 API,更多模型,更低成本。",
body: "VoyageAge 通过一个兼容 OpenAI 的 API 汇集领先的 AI 模型。它面向开发者、团队和 AI 初创公司,价格具有竞争力,按量计费,让你更轻松地把 AI 集成到项目中。注册即可获得免费试用额度,立即开始构建。",
visit: "访问 voyageage.com",
},
},
become: {
title: "成为赞助商",
body: "赞助商展示在 ThinkWatch Lite 的中英文 README、本站 ThinkWatch Lite 页面与本页。",
note: "赞助不影响 ThinkWatch Lite 对请求的路由、计价与防护;应用内不展示任何赞助商。",
contactLabel: "联系我们",
},
disclaimer: {
title: "免责声明",
body: [
"赞助商是独立的第三方。ThinkWatch 项目不参与其运营,不为其产品或服务背书或担保,对其内容、可用性、价格、数据处理及经营行为不承担责任。本站与 README 中的赞助商介绍由赞助商提供。",
"使用赞助商的服务前,请阅读其服务条款与隐私政策。与该服务相关的约定存在于用户与赞助商之间。",
],
report: { lead: "赞助商如被发现误导或损害用户,将被撤下。", label: "反馈赞助商的问题", end: "。" },
},
},
};
Loading
Loading