feat: version-aware docs built + staged on ISO
- Copy Astro landing page source into docs/website/ (20K, no node_modules) - Add ISO version badge to LandingBody.astro (only shown when ASTRO_ISO_VERSION is set during build) - Add build_and_stage_docs() to build.sh: builds the Astro site with the ISO version, stages output at /usr/local/share/clawdie-iso/docs/ - Skips gracefully when node/npm unavailable - On the booted USB: open docs/index.html to see version-matched docs
This commit is contained in:
parent
6173e185ec
commit
fdd0d260d0
26 changed files with 7498 additions and 0 deletions
36
build.sh
36
build.sh
|
|
@ -1083,6 +1083,41 @@ install_zot_agent() {
|
|||
# Stage an on-image NVIDIA pkg repo (all branches) so clawdie_live_gpu can
|
||||
# `pkg install` the detected branch at boot (NVIDIA_UNIVERSAL lane).
|
||||
#
|
||||
# FreeBSD-build-host step (authored on Linux; runs + must be validated on
|
||||
# FreeBSD). Verify on the build host: (1) the `pkg fetch -o` layout matches what
|
||||
# `pkg repo` expects, (2) the dependency closure is complete for offline boot
|
||||
# Build the Astro docs site and stage it as static HTML on the ISO.
|
||||
# When ISO_VERSION is set, the docs carry a version badge matching the image.
|
||||
# Skips silently if node/npm are unavailable.
|
||||
build_and_stage_docs() {
|
||||
local _docs_src="${SCRIPT_DIR}/docs/website"
|
||||
local _docs_out="${MOUNT_POINT}/usr/local/share/clawdie-iso/docs"
|
||||
|
||||
if [ ! -d "${_docs_src}" ]; then
|
||||
echo " Docs source not found at ${_docs_src} — skipping"
|
||||
return 0
|
||||
fi
|
||||
if ! command -v node >/dev/null 2>&1 || ! command -v npm >/dev/null 2>&1; then
|
||||
echo " Node/npm not available — skipping docs build"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo " Building docs (ISO ${ISO_VERSION})..."
|
||||
(
|
||||
cd "${_docs_src}"
|
||||
npm ci --silent 2>&1 || { echo " WARN: npm ci failed, skipping docs"; exit 0; }
|
||||
ASTRO_ISO_VERSION="${ISO_VERSION}" \
|
||||
ASTRO_SITE_URL="file:///usr/local/share/clawdie-iso/docs" \
|
||||
npm run build --silent 2>&1 || { echo " WARN: astro build failed, skipping docs"; exit 0; }
|
||||
)
|
||||
|
||||
if [ -d "${_docs_src}/dist" ]; then
|
||||
mkdir -p "${_docs_out}"
|
||||
cp -a "${_docs_src}/dist/." "${_docs_out}/"
|
||||
echo " Docs staged at /usr/local/share/clawdie-iso/docs/ (version ${ISO_VERSION})"
|
||||
fi
|
||||
}
|
||||
|
||||
# FreeBSD-build-host step (authored on Linux; runs + must be validated on
|
||||
# FreeBSD). Verify on the build host: (1) the `pkg fetch -o` layout matches what
|
||||
# `pkg repo` expects, (2) the dependency closure is complete for offline boot
|
||||
|
|
@ -2483,6 +2518,7 @@ configure_live_operator_session
|
|||
install_colibri_service
|
||||
install_zot_agent
|
||||
install_nvidia_universal_repo
|
||||
build_and_stage_docs
|
||||
|
||||
# Copy payload
|
||||
# Rebuild payload paths from scratch inside the reusable work image. A failed
|
||||
|
|
|
|||
28
docs/website/astro.config.mjs
Normal file
28
docs/website/astro.config.mjs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { defineConfig, passthroughImageService } from 'astro/config';
|
||||
import sitemap from '@astrojs/sitemap';
|
||||
|
||||
const site = process.env.ASTRO_SITE_URL || 'https://clawdie.si';
|
||||
const outDir = process.env.ASTRO_OUT_DIR || './dist';
|
||||
|
||||
export default defineConfig({
|
||||
site,
|
||||
outDir,
|
||||
output: 'static',
|
||||
trailingSlash: 'always',
|
||||
image: {
|
||||
service: passthroughImageService(),
|
||||
},
|
||||
i18n: {
|
||||
defaultLocale: 'en',
|
||||
locales: ['en', 'sl'],
|
||||
routing: { prefixDefaultLocale: true, redirectToDefaultLocale: false },
|
||||
},
|
||||
integrations: [
|
||||
sitemap({
|
||||
i18n: {
|
||||
defaultLocale: 'en',
|
||||
locales: { en: 'en', sl: 'sl' },
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
6581
docs/website/package-lock.json
generated
Normal file
6581
docs/website/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
23
docs/website/package.json
Normal file
23
docs/website/package.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"name": "clawdie-si",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "astro dev --host 0.0.0.0",
|
||||
"start": "astro dev --host 0.0.0.0",
|
||||
"build": "astro build",
|
||||
"predeploy": "npm run build",
|
||||
"deploy": "node scripts/deploy.mjs",
|
||||
"preview": "astro preview --host 0.0.0.0",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/sitemap": "^3.2.1",
|
||||
"astro": "^5.16.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/check": "^0.9.6",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
22
docs/website/scripts/deploy.mjs
Normal file
22
docs/website/scripts/deploy.mjs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
|
||||
const localRoot = process.cwd();
|
||||
const distDir = path.join(localRoot, 'dist');
|
||||
const webroot = process.env.CMS_WEBROOT || '/usr/local/www/clawdie-si';
|
||||
|
||||
if (!fs.existsSync(distDir)) {
|
||||
console.error(`Build output not found: ${distDir}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const result = spawnSync(
|
||||
'rsync',
|
||||
['-av', '--delete', `${distDir}/`, `${webroot}/`],
|
||||
{ stdio: 'inherit', env: process.env },
|
||||
);
|
||||
|
||||
if (result.status !== 0) {
|
||||
process.exit(result.status ?? 1);
|
||||
}
|
||||
77
docs/website/src/components/LandingBody.astro
Normal file
77
docs/website/src/components/LandingBody.astro
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
---
|
||||
import { getCollection, render } from 'astro:content';
|
||||
import type { Locale } from '../i18n';
|
||||
import { t } from '../i18n';
|
||||
|
||||
interface Props {
|
||||
locale: Locale;
|
||||
}
|
||||
|
||||
const { locale } = Astro.props;
|
||||
const strings = t(locale);
|
||||
|
||||
const all = await getCollection('landing', ({ id }) => id.startsWith(`${locale}/`));
|
||||
const bySection = Object.fromEntries(all.map((e) => [e.data.section, e]));
|
||||
|
||||
const heroEntry = bySection['hero'];
|
||||
const aboutEntry = bySection['about'];
|
||||
const archEntry = bySection['architecture'];
|
||||
const ctaEntry = bySection['install-cta'];
|
||||
|
||||
const Hero = heroEntry ? (await render(heroEntry)).Content : null;
|
||||
const About = aboutEntry ? (await render(aboutEntry)).Content : null;
|
||||
const Arch = archEntry ? (await render(archEntry)).Content : null;
|
||||
const Cta = ctaEntry ? (await render(ctaEntry)).Content : null;
|
||||
|
||||
// Only set during ISO builds — shows the exact image version the docs were built against.
|
||||
const isoVersion = import.meta.env.ASTRO_ISO_VERSION || null;
|
||||
---
|
||||
|
||||
<header class="hero">
|
||||
<div class="header-inner">
|
||||
<div class="brand-mark">△</div>
|
||||
<div class="header-text">
|
||||
<h1>Clawdie<br /><span>{locale === 'sl' ? 'AI v vaših rokah' : 'AI you own'}</span></h1>
|
||||
<p class="tagline">{strings.meta.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="hero-statement">
|
||||
{Hero && <Hero />}
|
||||
</div>
|
||||
|
||||
{
|
||||
About && (
|
||||
<section class="landing-section" id="about">
|
||||
<h2>{aboutEntry?.data.title}</h2>
|
||||
<About />
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
Arch && (
|
||||
<section class="landing-section" id="architecture">
|
||||
<h2>{archEntry?.data.title}</h2>
|
||||
<Arch />
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
Cta && (
|
||||
<section class="landing-section" id="install">
|
||||
<h2>{ctaEntry?.data.title}</h2>
|
||||
<Cta />
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
isoVersion && (
|
||||
<footer class="iso-version-badge">
|
||||
<p>Clawdie ISO {isoVersion} — these docs match the image they were built with.</p>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
38
docs/website/src/components/LangSwitcher.astro
Normal file
38
docs/website/src/components/LangSwitcher.astro
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
import type { Locale } from '../i18n';
|
||||
import { locales, t } from '../i18n';
|
||||
|
||||
interface Props {
|
||||
current: Locale;
|
||||
}
|
||||
|
||||
const { current } = Astro.props;
|
||||
|
||||
// Astro's pathname is the request path (e.g. "/en/", "/sl/about/").
|
||||
// Strip the current locale prefix so we can prepend the target locale and
|
||||
// preserve the rest of the path on language switch.
|
||||
const pathname = Astro.url.pathname;
|
||||
const prefix = `/${current}`;
|
||||
const rest = pathname.startsWith(prefix)
|
||||
? pathname.slice(prefix.length) || '/'
|
||||
: pathname;
|
||||
|
||||
const flagFor: Record<Locale, string> = {
|
||||
en: '🇬🇧',
|
||||
sl: '🇸🇮',
|
||||
};
|
||||
---
|
||||
|
||||
<nav class="lang-switch" aria-label={t(current).switcher.label}>
|
||||
{
|
||||
locales.map((loc) => {
|
||||
const href = `/${loc}${rest.startsWith('/') ? rest : `/${rest}`}`;
|
||||
const label = t(current).switcher[loc];
|
||||
return (
|
||||
<a href={href} class={loc === current ? 'active' : ''}>
|
||||
{flagFor[loc]} {label}
|
||||
</a>
|
||||
);
|
||||
})
|
||||
}
|
||||
</nav>
|
||||
31
docs/website/src/components/OperatorBanner.astro
Normal file
31
docs/website/src/components/OperatorBanner.astro
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
import type { Locale } from '../i18n';
|
||||
import { t } from '../i18n';
|
||||
|
||||
interface Props {
|
||||
locale: Locale;
|
||||
}
|
||||
|
||||
const { locale } = Astro.props;
|
||||
const s = t(locale).banner;
|
||||
---
|
||||
|
||||
<div id="operator-banner" class="operator-banner" hidden>
|
||||
<div class="operator-banner-text">
|
||||
<strong>{s.heading}</strong> {s.body}
|
||||
</div>
|
||||
<div class="operator-banner-actions">
|
||||
<a href="/controlplane/">{s.openControlplane}</a>
|
||||
<a href="https://docs.clawdie.si/">{s.readDocs}</a>
|
||||
<a href="https://docs.clawdie.si/operate/public-domain/">{s.claimDomain}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script is:inline>
|
||||
(function () {
|
||||
if (typeof window === 'undefined') return;
|
||||
if (window.location.hostname === 'clawdie.si') return;
|
||||
var el = document.getElementById('operator-banner');
|
||||
if (el) el.hidden = false;
|
||||
})();
|
||||
</script>
|
||||
13
docs/website/src/content.config.ts
Normal file
13
docs/website/src/content.config.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { defineCollection, z } from 'astro:content';
|
||||
import { glob } from 'astro/loaders';
|
||||
|
||||
const landing = defineCollection({
|
||||
loader: glob({ base: './src/content/landing', pattern: '**/*.md' }),
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
order: z.number(),
|
||||
section: z.enum(['hero', 'about', 'architecture', 'install-cta']),
|
||||
}),
|
||||
});
|
||||
|
||||
export const collections = { landing };
|
||||
16
docs/website/src/content/landing/en/about.md
Normal file
16
docs/website/src/content/landing/en/about.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: What is Clawdie
|
||||
order: 2
|
||||
section: about
|
||||
---
|
||||
|
||||
Clawdie is a **self-hosted AI control plane** for operators who refuse to rent
|
||||
their compute, their data, or their users' privacy.
|
||||
|
||||
You install it on your own hardware — a workstation, a colocated box, a basement
|
||||
server. It provisions FreeBSD jails, runs your models, and gives every tenant on
|
||||
your machine a clean slice of the platform: their own database, their own site,
|
||||
their own backups.
|
||||
|
||||
No telemetry leaks upstream. No model weights walk out the door. No vendor
|
||||
decides what your assistant is allowed to say tomorrow.
|
||||
16
docs/website/src/content/landing/en/architecture.md
Normal file
16
docs/website/src/content/landing/en/architecture.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: Architecture
|
||||
order: 3
|
||||
section: architecture
|
||||
---
|
||||
|
||||
A Clawdie install has three layers:
|
||||
|
||||
- **Host** — FreeBSD with ZFS, nginx, Postgres, jail manager.
|
||||
- **Control plane** — the operator's command surface; provisions tenants, runs
|
||||
the assistant, ships the publish report.
|
||||
- **Tenants** — isolated jails, each with its own site, database, and backup
|
||||
policy. Tenants don't see each other; the operator sees everything.
|
||||
|
||||
Everything is reproducible. The same `setup` command that builds your machine
|
||||
today will build the same machine on a fresh box tomorrow.
|
||||
9
docs/website/src/content/landing/en/hero.md
Normal file
9
docs/website/src/content/landing/en/hero.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
title: Hero
|
||||
order: 1
|
||||
section: hero
|
||||
---
|
||||
|
||||
**Sovereign AI is an engineering problem, not a regulatory one.**
|
||||
The hardware exists. The models exist. The know-how exists.
|
||||
**The only thing missing is the decision to install it yourself.**
|
||||
12
docs/website/src/content/landing/en/install-cta.md
Normal file
12
docs/website/src/content/landing/en/install-cta.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
title: Install
|
||||
order: 4
|
||||
section: install-cta
|
||||
---
|
||||
|
||||
Boot the ISO, answer four questions, walk away.
|
||||
By the time you come back, your machine is provisioned, your assistant is
|
||||
online, and your first tenant site is reachable.
|
||||
|
||||
Read the [installation guide](https://docs.clawdie.si/install/) or jump
|
||||
straight to the [ISO download](https://docs.clawdie.si/install/iso/).
|
||||
10
docs/website/src/content/landing/sl/about.md
Normal file
10
docs/website/src/content/landing/sl/about.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: Kaj je Clawdie
|
||||
order: 2
|
||||
section: about
|
||||
---
|
||||
|
||||
Clawdie je **samogosotvana AI nadzorna ravan** za operaterje, ki nočejo najemati
|
||||
svoje računske moči, svojih podatkov ali zasebnosti svojih uporabnikov.
|
||||
|
||||
<!-- TODO: full SL translation pending Crowdin wiring -->
|
||||
9
docs/website/src/content/landing/sl/architecture.md
Normal file
9
docs/website/src/content/landing/sl/architecture.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
title: Arhitektura
|
||||
order: 3
|
||||
section: architecture
|
||||
---
|
||||
|
||||
Namestitev Clawdie ima tri plasti: gostitelj, nadzorna ravan, najemniki.
|
||||
|
||||
<!-- TODO: full SL translation pending Crowdin wiring -->
|
||||
11
docs/website/src/content/landing/sl/hero.md
Normal file
11
docs/website/src/content/landing/sl/hero.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
title: Hero
|
||||
order: 1
|
||||
section: hero
|
||||
---
|
||||
|
||||
**Suverena AI je inženirski problem, ne regulativni.**
|
||||
Strojna oprema obstaja. Modeli obstajajo. Znanje obstaja.
|
||||
**Edino, kar manjka, je odločitev, da to namestite sami.**
|
||||
|
||||
<!-- TODO: full SL translation pending Crowdin wiring -->
|
||||
11
docs/website/src/content/landing/sl/install-cta.md
Normal file
11
docs/website/src/content/landing/sl/install-cta.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
title: Namestitev
|
||||
order: 4
|
||||
section: install-cta
|
||||
---
|
||||
|
||||
Zaženite ISO, odgovorite na štiri vprašanja, odidite.
|
||||
|
||||
Preberite [vodič za namestitev](https://docs.clawdie.si/install/).
|
||||
|
||||
<!-- TODO: full SL translation pending Crowdin wiring -->
|
||||
32
docs/website/src/i18n/en.ts
Normal file
32
docs/website/src/i18n/en.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import type { Strings } from './types';
|
||||
|
||||
export const en: Strings = {
|
||||
meta: {
|
||||
title: 'Clawdie — Sovereign AI infrastructure',
|
||||
description:
|
||||
'Self-hosted AI control plane: your data, your jails, your rules. Operator-owned infrastructure that ships ready-to-use.',
|
||||
},
|
||||
nav: {
|
||||
docs: 'Docs',
|
||||
controlplane: 'Control plane',
|
||||
source: 'Source',
|
||||
},
|
||||
switcher: {
|
||||
label: 'Language',
|
||||
en: 'EN',
|
||||
sl: 'SL',
|
||||
},
|
||||
banner: {
|
||||
heading: 'You are not on clawdie.si.',
|
||||
body: 'This page is being served from an operator install. Use the controls below.',
|
||||
openControlplane: 'Open control plane',
|
||||
readDocs: 'Read docs',
|
||||
claimDomain: 'Claim a public domain',
|
||||
},
|
||||
footer: {
|
||||
tagline: 'Operator-owned AI · No surveillance, no rent extraction',
|
||||
docsLink: 'docs.clawdie.si',
|
||||
sourceLink: 'codeberg.org/Clawdie/Clawdie-AI',
|
||||
rights: 'Released under AGPL-3.0',
|
||||
},
|
||||
};
|
||||
14
docs/website/src/i18n/index.ts
Normal file
14
docs/website/src/i18n/index.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { en } from './en';
|
||||
import { sl } from './sl';
|
||||
import type { Locale, Strings } from './types';
|
||||
|
||||
export const locales: Locale[] = ['en', 'sl'];
|
||||
export const defaultLocale: Locale = 'en';
|
||||
|
||||
const registry: Record<Locale, Strings> = { en, sl };
|
||||
|
||||
export function t(locale: Locale): Strings {
|
||||
return registry[locale];
|
||||
}
|
||||
|
||||
export type { Locale, Strings };
|
||||
32
docs/website/src/i18n/sl.ts
Normal file
32
docs/website/src/i18n/sl.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import type { Strings } from './types';
|
||||
|
||||
export const sl: Strings = {
|
||||
meta: {
|
||||
title: 'Clawdie — Suverena AI infrastruktura',
|
||||
description:
|
||||
'Samogosotvana AI nadzorna ravan: vaši podatki, vaši jaili, vaša pravila. Operaterjeva infrastruktura, ki je takoj uporabna.',
|
||||
},
|
||||
nav: {
|
||||
docs: 'Dokumentacija',
|
||||
controlplane: 'Nadzorna ravan',
|
||||
source: 'Izvorna koda',
|
||||
},
|
||||
switcher: {
|
||||
label: 'Jezik',
|
||||
en: 'EN',
|
||||
sl: 'SL',
|
||||
},
|
||||
banner: {
|
||||
heading: 'Niste na clawdie.si.',
|
||||
body: 'Ta stran se streže iz operaterjeve namestitve. Uporabite spodnje gumbe.',
|
||||
openControlplane: 'Odpri nadzorno ravan',
|
||||
readDocs: 'Preberi dokumentacijo',
|
||||
claimDomain: 'Prevzemi javno domeno',
|
||||
},
|
||||
footer: {
|
||||
tagline: 'AI v lasti operaterja · Brez nadzora, brez najemnine',
|
||||
docsLink: 'docs.clawdie.si',
|
||||
sourceLink: 'codeberg.org/Clawdie/Clawdie-AI',
|
||||
rights: 'Izdano pod AGPL-3.0',
|
||||
},
|
||||
};
|
||||
31
docs/website/src/i18n/types.ts
Normal file
31
docs/website/src/i18n/types.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
export type Locale = 'en' | 'sl';
|
||||
|
||||
export interface Strings {
|
||||
meta: {
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
nav: {
|
||||
docs: string;
|
||||
controlplane: string;
|
||||
source: string;
|
||||
};
|
||||
switcher: {
|
||||
label: string;
|
||||
en: string;
|
||||
sl: string;
|
||||
};
|
||||
banner: {
|
||||
heading: string;
|
||||
body: string;
|
||||
openControlplane: string;
|
||||
readDocs: string;
|
||||
claimDomain: string;
|
||||
};
|
||||
footer: {
|
||||
tagline: string;
|
||||
docsLink: string;
|
||||
sourceLink: string;
|
||||
rights: string;
|
||||
};
|
||||
}
|
||||
70
docs/website/src/layouts/Landing.astro
Normal file
70
docs/website/src/layouts/Landing.astro
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
---
|
||||
import '../styles/global.css';
|
||||
import LangSwitcher from '../components/LangSwitcher.astro';
|
||||
import OperatorBanner from '../components/OperatorBanner.astro';
|
||||
import type { Locale } from '../i18n';
|
||||
import { locales, t } from '../i18n';
|
||||
|
||||
interface Props {
|
||||
locale: Locale;
|
||||
}
|
||||
|
||||
const { locale } = Astro.props;
|
||||
const strings = t(locale);
|
||||
const site = Astro.site?.toString().replace(/\/$/, '') ?? 'https://clawdie.si';
|
||||
const path = Astro.url.pathname;
|
||||
const stripPrefix = (p: string, l: Locale) => {
|
||||
const pref = `/${l}`;
|
||||
return p.startsWith(pref) ? p.slice(pref.length) || '/' : p;
|
||||
};
|
||||
const sharedPath = stripPrefix(path, locale);
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang={locale}>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{strings.meta.title}</title>
|
||||
<meta name="description" content={strings.meta.description} />
|
||||
{
|
||||
locales.map((l) => (
|
||||
<link
|
||||
rel="alternate"
|
||||
hreflang={l}
|
||||
href={`${site}/${l}${sharedPath === '/' ? '/' : sharedPath}`}
|
||||
/>
|
||||
))
|
||||
}
|
||||
<link
|
||||
rel="alternate"
|
||||
hreflang="x-default"
|
||||
href={`${site}/en${sharedPath === '/' ? '/' : sharedPath}`}
|
||||
/>
|
||||
<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=Cormorant+Garamond:ital,wght@0,300;0,400;0,600;1,300;1,400&family=DM+Mono:wght@300;400&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<OperatorBanner locale={locale} />
|
||||
<div class="triangle-bg"></div>
|
||||
<div class="container">
|
||||
<LangSwitcher current={locale} />
|
||||
<slot />
|
||||
<footer class="site-footer">
|
||||
<div>
|
||||
{strings.footer.tagline}<br />
|
||||
<a href="https://docs.clawdie.si/">{strings.footer.docsLink}</a>
|
||||
·
|
||||
<a href="https://codeberg.org/Clawdie/Clawdie-AI">{strings.footer.sourceLink}</a>
|
||||
<br />
|
||||
{strings.footer.rights}
|
||||
</div>
|
||||
<div class="footer-mark">△</div>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
8
docs/website/src/pages/en/index.astro
Normal file
8
docs/website/src/pages/en/index.astro
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
import Landing from '../../layouts/Landing.astro';
|
||||
import LandingBody from '../../components/LandingBody.astro';
|
||||
---
|
||||
|
||||
<Landing locale="en">
|
||||
<LandingBody locale="en" />
|
||||
</Landing>
|
||||
8
docs/website/src/pages/sl/index.astro
Normal file
8
docs/website/src/pages/sl/index.astro
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
import Landing from '../../layouts/Landing.astro';
|
||||
import LandingBody from '../../components/LandingBody.astro';
|
||||
---
|
||||
|
||||
<Landing locale="sl">
|
||||
<LandingBody locale="sl" />
|
||||
</Landing>
|
||||
355
docs/website/src/styles/global.css
Normal file
355
docs/website/src/styles/global.css
Normal file
|
|
@ -0,0 +1,355 @@
|
|||
:root {
|
||||
--bg: #0d1117;
|
||||
--accent: #00b4d8;
|
||||
--accent-dark: #0096b7;
|
||||
--fg: #e2e8f0;
|
||||
--fg-dim: #c9d1d9;
|
||||
--grey: #8b949e;
|
||||
--rule: #21262d;
|
||||
--paper: #161b22;
|
||||
--paper-2: #1c2333;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg);
|
||||
color: var(--fg-dim);
|
||||
font-family: 'Cormorant Garamond', Georgia, serif;
|
||||
font-size: 22px;
|
||||
line-height: 1.7;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.triangle-bg {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
opacity: 0.06;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100'%3E%3Cg stroke='%2300b4d8' stroke-width='0.8' fill='none'%3E%3Cline x1='0' y1='25' x2='40' y2='25'/%3E%3Cline x1='60' y1='25' x2='100' y2='25'/%3E%3Cline x1='0' y1='75' x2='40' y2='75'/%3E%3Cline x1='60' y1='75' x2='100' y2='75'/%3E%3Cline x1='25' y1='0' x2='25' y2='40'/%3E%3Cline x1='25' y1='60' x2='25' y2='100'/%3E%3Cline x1='75' y1='0' x2='75' y2='40'/%3E%3Cline x1='75' y1='60' x2='75' y2='100'/%3E%3Cpath d='M40 25 L50 25 L50 40'/%3E%3Cpath d='M60 25 L50 25 L50 40'/%3E%3Cpath d='M40 75 L50 75 L50 60'/%3E%3Cpath d='M60 75 L50 75 L50 60'/%3E%3C/g%3E%3Cg fill='%2300b4d8'%3E%3Ccircle cx='25' cy='25' r='2.5'/%3E%3Ccircle cx='75' cy='25' r='2.5'/%3E%3Ccircle cx='25' cy='75' r='2.5'/%3E%3Ccircle cx='75' cy='75' r='2.5'/%3E%3Ccircle cx='50' cy='50' r='2.5'/%3E%3C/g%3E%3C/svg%3E");
|
||||
background-size: 100px 100px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 0 2rem;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.top-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 1.5rem;
|
||||
padding: 1.2rem 0;
|
||||
border-bottom: 1px solid var(--rule);
|
||||
font-family: 'DM Mono', monospace;
|
||||
font-size: 0.85rem;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.top-nav a {
|
||||
color: var(--grey);
|
||||
text-decoration: none;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.top-nav a:hover,
|
||||
.top-nav a.active {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
header.hero {
|
||||
padding: 5rem 0 3rem;
|
||||
position: relative;
|
||||
animation: fadeUp 1.2s ease forwards;
|
||||
}
|
||||
|
||||
.header-inner {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 3rem;
|
||||
}
|
||||
|
||||
.brand-mark {
|
||||
font-size: 5rem;
|
||||
line-height: 1;
|
||||
animation: float 6s ease-in-out infinite;
|
||||
flex-shrink: 0;
|
||||
color: var(--accent);
|
||||
font-family: 'DM Mono', monospace;
|
||||
}
|
||||
|
||||
.header-text h1 {
|
||||
font-size: clamp(3rem, 8vw, 5.5rem);
|
||||
font-weight: 300;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 0.95;
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
.header-text h1 span {
|
||||
color: var(--accent);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.tagline {
|
||||
font-family: 'DM Mono', monospace;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 300;
|
||||
letter-spacing: 0.15em;
|
||||
text-transform: uppercase;
|
||||
color: var(--grey);
|
||||
margin-top: 1.2rem;
|
||||
border-left: 2px solid var(--accent);
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.hero-statement {
|
||||
background: var(--paper-2);
|
||||
color: var(--fg);
|
||||
padding: 4rem 3rem;
|
||||
margin: 3rem 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hero-statement::before {
|
||||
content: '△';
|
||||
position: absolute;
|
||||
right: -1rem;
|
||||
top: -2rem;
|
||||
font-size: 12rem;
|
||||
color: var(--accent);
|
||||
opacity: 0.08;
|
||||
font-family: 'DM Mono', monospace;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.hero-statement p {
|
||||
font-size: clamp(1.4rem, 3vw, 2rem);
|
||||
font-weight: 300;
|
||||
line-height: 1.5;
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
.hero-statement strong {
|
||||
color: var(--accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
section.landing-section {
|
||||
margin: 4rem 0;
|
||||
}
|
||||
|
||||
section.landing-section h2 {
|
||||
font-size: 1.8rem;
|
||||
font-weight: 300;
|
||||
font-style: italic;
|
||||
color: var(--fg);
|
||||
margin-bottom: 1.2rem;
|
||||
position: relative;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
section.landing-section h2::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 40px;
|
||||
height: 1px;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
section.landing-section p {
|
||||
color: var(--fg-dim);
|
||||
font-size: 1.1em;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
section.landing-section a {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid transparent;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
section.landing-section a:hover {
|
||||
border-bottom-color: var(--accent);
|
||||
}
|
||||
|
||||
footer.site-footer {
|
||||
margin-top: 6rem;
|
||||
padding: 3rem 0;
|
||||
border-top: 1px solid var(--rule);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
font-family: 'DM Mono', monospace;
|
||||
font-size: 0.85rem;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--grey);
|
||||
text-transform: uppercase;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
footer.site-footer a {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid transparent;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
footer.site-footer a:hover {
|
||||
border-bottom-color: var(--accent);
|
||||
}
|
||||
|
||||
.footer-mark {
|
||||
font-size: 2rem;
|
||||
opacity: 0.3;
|
||||
color: var(--accent);
|
||||
animation: float 8s ease-in-out infinite reverse;
|
||||
}
|
||||
|
||||
/* --- LANG SWITCHER (OSA-style) --- */
|
||||
.lang-switch {
|
||||
position: absolute;
|
||||
top: 1.5rem;
|
||||
right: 2rem;
|
||||
display: flex;
|
||||
gap: 0.3rem;
|
||||
font-family: 'DM Mono', monospace;
|
||||
font-size: 0.8rem;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.lang-switch a {
|
||||
color: var(--grey);
|
||||
text-decoration: none;
|
||||
padding: 0.25rem 0.55rem;
|
||||
border: 1px solid var(--rule);
|
||||
transition:
|
||||
color 0.2s,
|
||||
border-color 0.2s;
|
||||
}
|
||||
|
||||
.lang-switch a.active,
|
||||
.lang-switch a:hover {
|
||||
color: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
/* --- OPERATOR BANNER --- */
|
||||
.operator-banner {
|
||||
background: var(--paper);
|
||||
border-bottom: 1px solid var(--accent);
|
||||
padding: 1rem 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.operator-banner-text {
|
||||
font-family: 'DM Mono', monospace;
|
||||
font-size: 0.78rem;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--fg-dim);
|
||||
}
|
||||
|
||||
.operator-banner-text strong {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.operator-banner-actions {
|
||||
display: flex;
|
||||
gap: 0.6rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.operator-banner-actions a {
|
||||
font-family: 'DM Mono', monospace;
|
||||
font-size: 0.72rem;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: var(--fg);
|
||||
text-decoration: none;
|
||||
border: 1px solid var(--accent);
|
||||
padding: 0.45rem 0.9rem;
|
||||
transition:
|
||||
background 0.2s,
|
||||
color 0.2s;
|
||||
}
|
||||
|
||||
.operator-banner-actions a:hover {
|
||||
background: var(--accent);
|
||||
color: var(--bg);
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0px) rotate(-5deg);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-12px) rotate(3deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.header-inner {
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
.brand-mark {
|
||||
font-size: 3.5rem;
|
||||
}
|
||||
.header-text h1 {
|
||||
font-size: 3rem;
|
||||
}
|
||||
.hero-statement {
|
||||
padding: 2.5rem 1.5rem;
|
||||
}
|
||||
footer.site-footer {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
.lang-switch {
|
||||
right: 1rem;
|
||||
top: 1rem;
|
||||
}
|
||||
}
|
||||
5
docs/website/tsconfig.json
Normal file
5
docs/website/tsconfig.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"include": [".astro/types.d.ts", "**/*"],
|
||||
"exclude": ["dist"]
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue