Postavljanje i implementacija Astro Starlighta
Ovaj vas vodič vodi korak po korak kroz uspostavu, konfiguraciju i implementaciju višejezičnog dokumentacijskog portala visokih performansi pomoću okvira Astro i Starlight te platforme Vercel. Na kraju ćete imati produkcijski spremnu stranicu s interaktivnom navigacijom, automatskom optimizacijom slika, Pagefind pretraživanjem i automatiziranom implementacijom u oblak.
Načela kojima se vodi ovaj priručnik
Odjeljak s naslovom “Načela kojima se vodi ovaj priručnik”U skladu sa standardima tehničkog pisanja:
- Bez preskakanja koraka: Svaka naredba, stvaranje datoteke i konfiguracijski redak točno su navedeni.
- Kontekst prije izvođenja: Tehnički pojmovi i arhitektonski razlozi objašnjeni su prije unošenja naredbi.
- Pokaži, a ne samo objasni: Uključene su cjelovite, funkcionalne konfiguracijske datoteke umjesto nepotpunih isječaka.
- Provjereno i testirano: Sve upute izravno odražavaju testiranu konfiguraciju koja pokreće upravo ovu dokumentacijsku web stranicu.
Preduvjeti
Odjeljak s naslovom “Preduvjeti”Prije početka provjerite imate li spremno sljedeće:
- Node.js: Instalirana verzija 20.x ili novija (
node -v) - Upravitelj paketa: npm (verzija 10.x ili novija) ili pnpm
- Vercel račun: Besplatan račun na vercel.com
- Vercel CLI: Dostupan putem
npx vercelili instaliran globalno (npm install -g vercel) - Terminalski pristup: Ljuska na operacijskim sustavima Linux (Debian/Ubuntu), macOS ili Windows WSL2
- Uređivač teksta: VS Code, Cursor, Nano ili vaš omiljeni uređivač
Terminologija i zamjenske oznake
Odjeljak s naslovom “Terminologija i zamjenske oznake”U ovom vodiču koristimo sljedeće zamjenske oznake:
| Oznaka | Značenje | Primjer |
|---|---|---|
<naziv_projekta> |
Naziv direktorija ili repozitorija dokumentacije | antigravity-docs |
<produkcijski_url> |
Dodijeljena produkcijska web adresa na Vercelu | https://antigravity-docs-teal.vercel.app |
<kod_jezika> |
Dvoslovni ISO identifikator jezika | sl, en, hr |
<github_repo_url> |
Poveznica na vaš Git repozitorij | https://github.com/google-gemini |
Korak 1 – Postavljanje kostura projekta (Scaffolding)
Odjeljak s naslovom “Korak 1 – Postavljanje kostura projekta (Scaffolding)”Novi Astro projekt pokrećemo s pomoću službenog Starlight predloška. Kako bismo izbjegli interaktivna pitanja u terminalu tijekom automatiziranih ili agentskih procesa, prosljeđujemo neinteraktivne zastavice.
U terminalu pokrenite:
npm create astro@latest antigravity-docs -- \ --template starlight \ --yes \ --install \ --no-git \ --typescript strictZnačenje zastavica:
Odjeljak s naslovom “Značenje zastavica:”--template starlight: Inicijalizira projekt sa službenim predloškom i dodacima za Starlight.--yes: Automatski prihvaća sve preporučene zadane vrijednosti.--install: Odmah pokrećenpm installza preuzimanje osnovnih ovisnosti.--no-git: Sprječava stvaranje praznog Git repozitorija ako već radite unutar postojećeg projekta.--typescript strict: Omogućuje strogu provjeru tipova za stabilan i siguran rad s definicijama sadržaja.
Prijeđite u direktorij projekta:
cd antigravity-docsKorak 2 – Instalacija produkcijskih ovisnosti
Odjeljak s naslovom “Korak 2 – Instalacija produkcijskih ovisnosti”Uz osnovni paket Starlight, instaliramo knjižnicu Sharp. Astro koristi Sharp za brzo procesiranje, sažimanje i pretvaranje slika u moderan .webp format tijekom izgradnje.
Instalirajte Sharp:
npm install sharpCjelovita datoteka package.json
Odjeljak s naslovom “Cjelovita datoteka package.json”Provjerite sadrži li vaša datoteka package.json odgovarajuće skripte i ovisnosti:
{ "name": "antigravity-docs", "type": "module", "version": "0.0.1", "scripts": { "dev": "astro dev", "start": "astro dev", "build": "astro build", "preview": "astro preview", "astro": "astro" }, "dependencies": { "@astrojs/starlight": "^0.42.2", "astro": "^5.0.0", "sharp": "^0.35.3" }}Korak 3 – Postavljanje sheme sadržaja (src/content.config.ts)
Odjeljak s naslovom “Korak 3 – Postavljanje sheme sadržaja (src/content.config.ts)”Astro 5 koristi Content Layer API. U datoteci src/content.config.ts definiramo zbirku docs i zbirku i18n za lokalizaciju sučelja.
Stvorite datoteku src/content.config.ts:
import { defineCollection } from 'astro:content';import { docsLoader, i18nLoader } from '@astrojs/starlight/loaders';import { docsSchema, i18nSchema } from '@astrojs/starlight/schema';
export const collections = { docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }), i18n: defineCollection({ loader: i18nLoader(), schema: i18nSchema() }),};Korak 4 – Glavna konfiguracija (astro.config.mjs)
Odjeljak s naslovom “Korak 4 – Glavna konfiguracija (astro.config.mjs)”Datoteka astro.config.mjs upravlja vizualnim identitetom, višejezičnim rutama, navigacijom u bočnoj traci i CSS stilovima.
4.1 – Višejezična arhitektura usmjeravanja
Odjeljak s naslovom “4.1 – Višejezična arhitektura usmjeravanja”U našoj postavci konfiguriramo tri jezika:
- Korijen (
/): Slovenski (sl) kao zadani jezik u korijenu. - Engleski (
/en/): Engleska dokumentacija. - Hrvatski (
/hr/): Hrvatska dokumentacija.
4.2 – Cjelovita datoteka astro.config.mjs
Odjeljak s naslovom “4.2 – Cjelovita datoteka astro.config.mjs”Stvorite ili ažurirajte datoteku astro.config.mjs:
// @ts-checkimport { defineConfig } from 'astro/config';import starlight from '@astrojs/starlight';
// https://astro.build/configexport default defineConfig({ site: 'https://antigravity.clawdie.si', integrations: [ starlight({ title: { sl: 'Antigravity Dokumentacija', en: 'Antigravity Docs', hr: 'Antigravity Dokumentacija', }, defaultLocale: 'root', locales: { root: { label: 'SI', lang: 'sl', }, en: { label: 'EN', lang: 'en', }, hr: { label: 'HR', lang: 'hr', }, }, customCss: ['./src/styles/custom.css'], components: { PageTitle: './src/components/PageTitle.astro', }, logo: { src: './src/assets/antigravity-icon.png', }, social: [{ icon: 'github', label: 'GitHub', href: 'https://github.com/google-gemini' }], sidebar: [ { label: 'Antigravity Vodnik', translations: { en: 'Antigravity Guide', hr: 'Antigravity Vodič', }, items: [ { label: '1. Pregled in arhitektura', slug: 'overview', translations: { en: '1. Overview & Architecture', hr: '1. Pregled i arhitektura' } }, { label: '2. Pravila in AGENTS.md', slug: 'rules', translations: { en: '2. Rules & AGENTS.md', hr: '2. Pravila i AGENTS.md' } }, { label: '3. Prilagojena TUI vrstica stanja', slug: 'statusline', translations: { en: '3. Custom TUI Status Line', hr: '3. Prilagođena TUI traka statusa' } }, { label: '4. Ukazi in bližnjice', slug: 'commands-shortcuts', translations: { en: '4. Commands & Shortcuts', hr: '4. Naredbe i prečaci' } }, { label: '5. Dovoljenja in način YOLO', slug: 'permissions', translations: { en: '5. Permissions & YOLO Mode', hr: '5. Dozvole i YOLO način' } }, { label: '6. Večagentna arhitektura', slug: 'subagents', translations: { en: '6. Multi-Agent Architecture', hr: '6. Višeagentska arhitektura' } }, { label: '7. Veščine in priročniki', slug: 'skills', translations: { en: '7. Skills & Runbooks', hr: '7. Vještine i priručnici' } }, { label: '8. Samodejni sprožilci dogodkov (Hooks)', slug: 'hooks', translations: { en: '8. Lifecycle Hooks', hr: '8. Automatski okidači događaja (Hooks)' } }, { label: '9. Zunanji ponudniki in Vercel', slug: 'mcp-vercel', translations: { en: '9. External Providers & Vercel', hr: '9. Vanjski pružatelji i Vercel' } }, { label: '10. Postavitev Astro Starlight', slug: 'starlight-setup', translations: { en: '10. Astro Starlight Guide', hr: '10. Postavljanje Astro Starlighta' } }, ], }, ], }), ],});Korak 5 – Prijevod korisničkog sučelja (src/content/i18n/)
Odjeljak s naslovom “Korak 5 – Prijevod korisničkog sučelja (src/content/i18n/)”U mapi src/content/i18n/ definiramo rječnike sa specifičnim prijevodima za elemente sučelja (pretraživanje, izbornik teme, preskakanje na sadržaj).
5.1 – Slovenski rječnik (src/content/i18n/sl.json)
Odjeljak s naslovom “5.1 – Slovenski rječnik (src/content/i18n/sl.json)”{ "skipLink.label": "Preskoči na vsebino", "search.label": "Iskanje", "search.ctrlKey": "Ctrl", "search.cancelLabel": "Prekliči", "search.devWarning": "Iskanje je na voljo le v produkcijski gradnji.\nPoskusite zgraditi in zagnati predogled lokalno.", "themeSelect.accessibleLabel": "Izbira teme", "themeSelect.dark": "Temna", "themeSelect.light": "Svetla", "themeSelect.auto": "Samodejno", "languageSelect.accessibleLabel": "Izbira jezika", "menuButton.accessibleLabel": "Meni", "sidebarNav.accessibleLabel": "Glavna navigacija", "tableOfContents.onThisPage": "Na tej strani", "tableOfContents.overview": "Pregled", "i18n.untranslatedContent": "Ta vsebina še ni na voljo v vašem jeziku.", "page.editLink": "Uredi stran", "page.lastUpdated": "Zadnja posodobitev:", "page.previousLink": "Prejšnja", "page.nextLink": "Naslednja", "page.draft": "Ta vsebina je osnutek in ne bo vključena v produkcijsko gradnjo.", "404.text": "Stran ni bila najdena. Preverite spletni naslov ali uporabite iskalnik.", "aside.note": "Opomba", "aside.tip": "Nasvet", "aside.caution": "Pozor", "aside.danger": "Nevarnost", "fileTree.directory": "Mapa", "builtWithStarlight.label": "Zgrajeno s Starlight", "heading.anchorLabel": "Razdelek z naslovom “{{title}}”"}5.2 – Hrvatski rječnik (src/content/i18n/hr.json)
Odjeljak s naslovom “5.2 – Hrvatski rječnik (src/content/i18n/hr.json)”{ "skipLink.label": "Preskoči na sadržaj", "search.label": "Pretraživanje", "search.ctrlKey": "Ctrl", "search.cancelLabel": "Odustani", "search.devWarning": "Pretraživanje je dostupno samo u produkcijskoj verziji.\nPokušajte izgraditi i pregledati lokalno.", "themeSelect.accessibleLabel": "Odabir teme", "themeSelect.dark": "Tamna", "themeSelect.light": "Svijetla", "themeSelect.auto": "Automatski", "languageSelect.accessibleLabel": "Odabir jezika", "menuButton.accessibleLabel": "Izbornik", "sidebarNav.accessibleLabel": "Glavna navigacija", "tableOfContents.onThisPage": "Na ovoj stranici", "tableOfContents.overview": "Pregled", "i18n.untranslatedContent": "Ovaj sadržaj još nije dostupan na vašem jeziku.", "page.editLink": "Uredi stranicu", "page.lastUpdated": "Zadnje ažurirano:", "page.previousLink": "Prethodna", "page.nextLink": "Sljedeća", "page.draft": "Ovaj sadržaj je skica i neće biti uključen u produkcijsku verziju.", "404.text": "Stranica nije pronađena. Provjerite URL ili upotrijebite pretraživanje.", "aside.note": "Napomena", "aside.tip": "Savjet", "aside.caution": "Oprez", "aside.danger": "Opasnost", "fileTree.directory": "Mapa", "builtWithStarlight.label": "Izgrađeno sa Starlight", "heading.anchorLabel": "Odjeljak s naslovom “{{title}}”"}Korak 6 – Prilagodba CSS stilova (src/styles/custom.css)
Odjeljak s naslovom “Korak 6 – Prilagodba CSS stilova (src/styles/custom.css)”Starlight prema zadanim postavkama smješta odabir jezika ispred ikona društvenih mreža. U našem dizajnu premještamo odabir jezika (SI, EN, HR) u sredinu, između GitHub ikone i prekidača za temu pomoću svojstva order u Flexboxu:
/* Pozicioniranje odabira jezika između ikona društvenih mreža (GitHub) i odabira teme */.right-group { display: flex; align-items: center; gap: 1rem;}
.right-group .social-icons { order: 1;}
.right-group starlight-lang-select { order: 2; display: flex; align-items: center; gap: 0.5rem;}
.right-group starlight-lang-select::after { content: ''; height: 1.75rem; border-inline-end: 1px solid var(--sl-color-gray-5); margin-inline-start: 0.5rem;}
.right-group starlight-theme-select { order: 3;}
/* Izgled za mobilni izbornik */.mobile-preferences .social-icons { order: 1;}
.mobile-preferences starlight-lang-select { order: 2;}
.mobile-preferences starlight-theme-select { order: 3;}Korak 7 – Vercel komponenta za kopiranje stranice i prikaz Markdown kôda (src/components/PageTitle.astro)
Odjeljak s naslovom “Korak 7 – Vercel komponenta za kopiranje stranice i prikaz Markdown kôda (src/components/PageTitle.astro)”Moderne dokumentacijske platforme (poput službene Vercel dokumentacije) na vrhu svake stranice nude interaktivni padajući izbornik koji posjetiteljima i razvojnim inženjerima omogućuje trenutačno kopiranje izvornog Markdown kôda u međuspremnik ili pregled čistog kôda u skočnom dijaloškom okviru (modal dialog).
Starlight omogućuje nadjačavanje (override) zadanih komponenti korisničkog sučelja putem svojstva components u astro.config.mjs. Nadjačavanjem komponente PageTitle zadržavamo standardni naslov <h1>, a istodobno ubacujemo padajući gumb za kopiranje i pregled bez vanjskih API zahtjeva ili klijentskog JavaScript opterećenja (zero-hydration).
Arhitektonske značajke:
Odjeljak s naslovom “Arhitektonske značajke:”- Ekstrakcija na poslužitelju tijekom izgradnje (Build-time extraction): Tijekom statičke izgradnje (
npm run build), Astro komponenta putemAstro.locals.starlightRoute.entry.filePathi Node.js modulanode:fsčita datoteku izravno s diska i umeće izvorni Markdown unutar inertnog HTML elementa<template class="raw-markdown-source">. - Pristupačan nativni dijalog
<dialog>: Za prikaz kôda koristi se standardni HTML element<dialog>s pozivomdialog.showModal(), što osigurava nativnu pristupačnost, upravljanje tipkovnicom (Escape za zatvaranje) i blokiranje pomicanja pozadine. - Pravilno centriranje na sredinu zaslona: Kako bi se izbjeglo fiksiranje dijaloga u gornji lijevi kut unutar flex kontejnera, primjenjuje se robusno centriranje:
position: fixed;top: 50%;left: 50%;transform: translate(-50%, -50%);margin: 0;width: min(90vw, 52rem);max-height: 85vh;
- Višejezična lokalizacija natpisa: Oznake na gumbima i u modalnom prozoru automatski se prilagođavaju aktivnom jeziku stranice (
hr,sl,en). - Otpornost na tranzicije stranica (View Transitions): Slušači događaja registrirani su na
document.addEventListener('astro:page-load', ...), omogućujući besprijekoran rad gumba tijekom klijentske navigacije.
Implementacija:
Odjeljak s naslovom “Implementacija:”Kreirajte komponentu src/components/PageTitle.astro:
---import fs from 'node:fs';import path from 'node:path';
const { entry, entryMeta } = Astro.locals.starlightRoute;const title = entry.data.title;const lang = entryMeta.lang || 'sl';
// Read full source markdown from disk at build timelet fullMarkdown = '';try { if (entry.filePath) { fullMarkdown = fs.readFileSync(path.resolve(entry.filePath), 'utf-8'); } else { fullMarkdown = entry.body || ''; }} catch { fullMarkdown = entry.body || '';}
// Localized stringsconst labels: Record<string, { copyPage: string; copied: string; viewMarkdown: string; rawModalTitle: string; close: string }> = { en: { copyPage: 'Copy page', copied: 'Copied!', viewMarkdown: 'View as Markdown', rawModalTitle: 'Raw Markdown Source', close: 'Close', }, sl: { copyPage: 'Kopiraj stran', copied: 'Kopirano!', viewMarkdown: 'Poglej kot Markdown', rawModalTitle: 'Izvorna Markdown koda', close: 'Zapri', }, hr: { copyPage: 'Kopiraj stranicu', copied: 'Kopirano!', viewMarkdown: 'Prikaži kao Markdown', rawModalTitle: 'Izvorni Markdown kôd', close: 'Zatvori', },};
const t = labels[lang] || labels.en;---
<div class="page-title-wrapper"> <h1 id="_top">{title}</h1>
<div class="page-actions-container"> <div class="copy-dropdown" data-copied-label={t.copied}> <div class="copy-btn-group"> <button type="button" class="action-btn copy-page-btn" aria-label={t.copyPage} title={t.copyPage} > <svg class="copy-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect> <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path> </svg> <svg class="check-icon" style="display: none;" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#22c55e" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"> <polyline points="20 6 9 17 4 12"></polyline> </svg> <span class="btn-text">{t.copyPage}</span> </button>
<button type="button" class="action-btn dropdown-toggle-btn" aria-haspopup="true" aria-expanded="false" aria-label="More options" > <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"> <polyline points="6 9 12 15 18 9"></polyline> </svg> </button> </div>
<div class="dropdown-menu" role="menu"> <button type="button" class="menu-item menu-copy" role="menuitem"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect> <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path> </svg> <span>{t.copyPage}</span> </button>
<button type="button" class="menu-item menu-view" role="menuitem"> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8z"></path> <circle cx="12" cy="12" r="3"></circle> </svg> <span>{t.viewMarkdown}</span> </button> </div> </div> </div>
<template class="raw-markdown-source">{fullMarkdown}</template>
<dialog class="markdown-viewer-dialog"> <div class="dialog-content"> <div class="dialog-header"> <div class="dialog-title-group"> <span class="dialog-title">{t.rawModalTitle}</span> <span class="dialog-badge">.md</span> </div> <div class="dialog-actions"> <button type="button" class="dialog-copy-btn action-btn" data-copied-label={t.copied}> <svg class="dialog-copy-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect> <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path> </svg> <svg class="dialog-check-icon" style="display: none;" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#22c55e" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"> <polyline points="20 6 9 17 4 12"></polyline> </svg> <span class="dialog-copy-text">{t.copyPage}</span> </button> <button type="button" class="dialog-close-btn" aria-label={t.close}> <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="18" y1="6" x2="6" y2="18"></line> <line x1="6" y1="6" x2="18" y2="18"></line> </svg> </button> </div> </div> <div class="dialog-body"> <pre class="dialog-code"><code>{fullMarkdown}</code></pre> </div> </div> </dialog></div>
<script> function setupPageActions() { const wrappers = document.querySelectorAll('.page-title-wrapper');
wrappers.forEach((wrapper) => { if (wrapper.getAttribute('data-actions-initialized')) return; wrapper.setAttribute('data-actions-initialized', 'true');
const template = wrapper.querySelector('.raw-markdown-source') as HTMLTemplateElement | null; const rawMarkdown = template ? template.content.textContent || '' : '';
const copyBtn = wrapper.querySelector('.copy-page-btn') as HTMLButtonElement | null; const copyIcon = copyBtn?.querySelector('.copy-icon') as HTMLElement | null; const checkIcon = copyBtn?.querySelector('.check-icon') as HTMLElement | null; const btnText = copyBtn?.querySelector('.btn-text') as HTMLElement | null; const originalText = btnText?.textContent || 'Copy page';
const toggleBtn = wrapper.querySelector('.dropdown-toggle-btn') as HTMLButtonElement | null; const dropdownMenu = wrapper.querySelector('.dropdown-menu') as HTMLElement | null; const menuCopy = wrapper.querySelector('.menu-copy') as HTMLButtonElement | null; const menuView = wrapper.querySelector('.menu-view') as HTMLButtonElement | null;
const dialog = wrapper.querySelector('.markdown-viewer-dialog') as HTMLDialogElement | null; const dialogClose = wrapper.querySelector('.dialog-close-btn') as HTMLButtonElement | null; const dialogCopy = wrapper.querySelector('.dialog-copy-btn') as HTMLButtonElement | null; const dialogCopyIcon = dialogCopy?.querySelector('.dialog-copy-icon') as HTMLElement | null; const dialogCheckIcon = dialogCopy?.querySelector('.dialog-check-icon') as HTMLElement | null; const dialogCopyText = dialogCopy?.querySelector('.dialog-copy-text') as HTMLElement | null; const dialogOriginalText = dialogCopyText?.textContent || 'Copy page';
function copyToClipboard(onSuccess: () => void) { if (!rawMarkdown) return; navigator.clipboard.writeText(rawMarkdown).then(() => { onSuccess(); }).catch(() => { const textarea = document.createElement('textarea'); textarea.value = rawMarkdown; document.body.appendChild(textarea); textarea.select(); document.execCommand('copy'); document.body.removeChild(textarea); onSuccess(); }); }
function triggerCopyFeedback() { if (!copyIcon || !checkIcon || !btnText) return; copyIcon.style.display = 'none'; checkIcon.style.display = 'inline-block'; btnText.textContent = wrapper.querySelector('.copy-dropdown')?.getAttribute('data-copied-label') || 'Copied!';
setTimeout(() => { copyIcon.style.display = 'inline-block'; checkIcon.style.display = 'none'; btnText.textContent = originalText; }, 2000); }
function triggerDialogCopyFeedback() { if (!dialogCopyIcon || !dialogCheckIcon || !dialogCopyText) return; dialogCopyIcon.style.display = 'none'; dialogCheckIcon.style.display = 'inline-block'; dialogCopyText.textContent = dialogCopy?.getAttribute('data-copied-label') || 'Copied!';
setTimeout(() => { dialogCopyIcon.style.display = 'inline-block'; dialogCheckIcon.style.display = 'none'; dialogCopyText.textContent = dialogOriginalText; }, 2000); }
copyBtn?.addEventListener('click', (e) => { e.stopPropagation(); closeDropdown(); copyToClipboard(() => { triggerCopyFeedback(); }); });
toggleBtn?.addEventListener('click', (e) => { e.stopPropagation(); const isOpen = dropdownMenu?.classList.contains('show'); if (isOpen) { closeDropdown(); } else { openDropdown(); } });
function openDropdown() { dropdownMenu?.classList.add('show'); toggleBtn?.setAttribute('aria-expanded', 'true'); }
function closeDropdown() { dropdownMenu?.classList.remove('show'); toggleBtn?.setAttribute('aria-expanded', 'false'); }
menuCopy?.addEventListener('click', (e) => { e.stopPropagation(); closeDropdown(); copyToClipboard(() => { triggerCopyFeedback(); }); });
menuView?.addEventListener('click', (e) => { e.stopPropagation(); closeDropdown(); if (dialog) { dialog.showModal(); } });
dialogClose?.addEventListener('click', () => { dialog?.close(); });
dialog?.addEventListener('click', (e) => { const rect = dialog.getBoundingClientRect(); const isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { dialog.close(); } });
dialogCopy?.addEventListener('click', () => { copyToClipboard(() => { triggerDialogCopyFeedback(); }); });
document.addEventListener('click', (e) => { if (dropdownMenu?.classList.contains('show')) { if (!wrapper.querySelector('.copy-dropdown')?.contains(e.target as Node)) { closeDropdown(); } } });
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { closeDropdown(); } }); }); }
setupPageActions(); document.addEventListener('astro:page-load', setupPageActions);</script>
<style> .page-title-wrapper { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 1rem; margin-top: 1rem; margin-bottom: 0.5rem; }
h1 { margin: 0; font-size: var(--sl-text-h1); line-height: var(--sl-line-height-headings); font-weight: 600; color: var(--sl-color-white); flex: 1 1 20rem; }
.page-actions-container { display: flex; align-items: center; position: relative; user-select: none; flex-shrink: 0; }
.copy-dropdown { position: relative; display: inline-block; }
.copy-btn-group { display: inline-flex; align-items: stretch; border: 1px solid var(--sl-color-gray-5); border-radius: 0.5rem; background-color: var(--sl-color-gray-6); transition: border-color 0.15s ease, background-color 0.15s ease; }
.copy-btn-group:hover { border-color: var(--sl-color-gray-4); background-color: var(--sl-color-gray-5); }
.action-btn { display: inline-flex; align-items: center; gap: 0.4rem; background: transparent; border: none; color: var(--sl-color-gray-2); font-size: var(--sl-text-xs); font-weight: 500; padding: 0.35rem 0.65rem; cursor: pointer; line-height: 1; transition: color 0.15s ease; }
.action-btn:hover { color: var(--sl-color-white); }
.copy-page-btn { border-top-left-radius: 0.5rem; border-bottom-left-radius: 0.5rem; }
.dropdown-toggle-btn { border-left: 1px solid var(--sl-color-gray-5); border-top-right-radius: 0.5rem; border-bottom-right-radius: 0.5rem; padding: 0.35rem 0.45rem; }
.dropdown-menu { display: none; position: absolute; top: calc(100% + 0.35rem); right: 0; min-width: 11.5rem; background-color: var(--sl-color-gray-6); border: 1px solid var(--sl-color-gray-5); border-radius: 0.5rem; box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5), 0 8px 10px -6px rgba(0, 0, 0, 0.4); padding: 0.25rem; z-index: 100; }
.dropdown-menu.show { display: block; animation: dropdownFadeIn 0.15s ease-out; }
@keyframes dropdownFadeIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
.menu-item { display: flex; align-items: center; gap: 0.6rem; width: 100%; padding: 0.45rem 0.75rem; font-size: var(--sl-text-xs); color: var(--sl-color-gray-2); background: transparent; border: none; border-radius: 0.35rem; cursor: pointer; text-align: left; transition: background-color 0.12s ease, color 0.12s ease; }
.menu-item:hover { background-color: var(--sl-color-gray-5); color: var(--sl-color-white); }
.markdown-viewer-dialog { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); margin: 0; padding: 0; border: 1px solid var(--sl-color-gray-5); border-radius: 0.75rem; background-color: var(--sl-color-gray-6); color: var(--sl-color-white); max-width: 52rem; width: min(90vw, 52rem); max-height: 85vh; box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.75); z-index: 1000; }
.markdown-viewer-dialog:not([open]) { display: none; }
.markdown-viewer-dialog[open] { display: flex; flex-direction: column; animation: modalFadeIn 0.2s cubic-bezier(0.16, 1, 0.3, 1); }
@keyframes modalFadeIn { from { opacity: 0; transform: translate(-50%, -47%) scale(0.97); } to { opacity: 1; transform: translate(-50%, -50%) scale(1); } }
.markdown-viewer-dialog::backdrop { background-color: rgba(0, 0, 0, 0.75); backdrop-filter: blur(4px); }
.dialog-content { display: flex; flex-direction: column; max-height: 85vh; width: 100%; overflow: hidden; }
.dialog-header { display: flex; align-items: center; justify-content: space-between; padding: 0.85rem 1.25rem; border-bottom: 1px solid var(--sl-color-gray-5); background-color: var(--sl-color-gray-6); }
.dialog-title-group { display: flex; align-items: center; gap: 0.6rem; }
.dialog-title { font-size: var(--sl-text-sm); font-weight: 600; color: var(--sl-color-white); }
.dialog-badge { font-size: 0.7rem; padding: 0.1rem 0.4rem; background-color: var(--sl-color-accent-low); color: var(--sl-color-accent-high); border: 1px solid var(--sl-color-accent); border-radius: 0.25rem; font-family: var(--sl-font-mono); }
.dialog-actions { display: flex; align-items: center; gap: 0.5rem; }
.dialog-copy-btn { border: 1px solid var(--sl-color-gray-5); border-radius: 0.375rem; background-color: var(--sl-color-gray-5); padding: 0.3rem 0.6rem; }
.dialog-copy-btn:hover { background-color: var(--sl-color-gray-4); }
.dialog-close-btn { background: transparent; border: none; color: var(--sl-color-gray-3); cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 0.3rem; border-radius: 0.375rem; transition: color 0.15s ease, background-color 0.15s ease; }
.dialog-close-btn:hover { color: var(--sl-color-white); background-color: var(--sl-color-gray-5); }
.dialog-body { overflow-y: auto; padding: 1.25rem; background-color: var(--sl-color-black); font-family: var(--sl-font-mono); font-size: 0.825rem; line-height: 1.6; }
.dialog-code { margin: 0; white-space: pre-wrap; word-break: break-word; color: var(--sl-color-gray-2); }</style>Korak 8 – Interaktivne početne stranice (index.mdx)
Odjeljak s naslovom “Korak 8 – Interaktivne početne stranice (index.mdx)”Početne stranice koriste predložak splash i komponente LinkCard unutar CardGrid za izravan skok na poglavlja dokumentacije:
import { LinkCard, CardGrid } from '@astrojs/starlight/components';
## Istražite poglavlja vodiča
<CardGrid> <LinkCard title="1. Višeagentski timovi" href="/hr/subagents/" description="Sastavite specijalizirane timove podagenata i spriječite zasićenje kontekstnog prozora." /></CardGrid>Korak 9 – Struktura poglavlja
Odjeljak s naslovom “Korak 9 – Struktura poglavlja”Svako poglavlje pohranjeno je kao zasebna Markdown datoteka unutar direktorija src/content/docs/:
src/content/docs/<poglavlje>.md(slovenski - korijenski jezik)src/content/docs/en/<poglavlje>.md(engleski jezik)src/content/docs/hr/<poglavlje>.md(hrvatski jezik)
Zaglavlje svake datoteke definirano je standardnim YAML formatom:
---title: Naslov poglavljadescription: Kratak sažetak sadržaja u jednoj rečenici.sidebar: order: 1---Korak 10 – Lokalni razvojni poslužitelj
Odjeljak s naslovom “Korak 10 – Lokalni razvojni poslužitelj”Interaktivno pokretanje:
Odjeljak s naslovom “Interaktivno pokretanje:”npm run devPokretanje u pozadini (za automatizirana i agentska okruženja):
Odjeljak s naslovom “Pokretanje u pozadini (za automatizirana i agentska okruženja):”npx astro dev --background- Provjera statusa:
npx astro dev status - Pregled zapisa:
npx astro dev logs - Zaustavljanje poslužitelja:
npx astro dev stop
Korak 11 – Provjera produkcijske izgradnje
Odjeljak s naslovom “Korak 11 – Provjera produkcijske izgradnje”Prije objave uvijek pokrenite produkcijsku izgradnju:
npm run buildProvjerite jesu li sve statičke rute generirane, slike optimizirane te je li Pagefind indeks pretraživanja uspješno stvoren.
Korak 12 – Automatizirana implementacija na Vercel
Odjeljak s naslovom “Korak 12 – Automatizirana implementacija na Vercel”Implementaciju na produkciju provodimo jednom jedinom naredbom putem Vercel CLI-ja:
npm run build && npx vercel --prod --yesNakon završetka provjerite HTTP status objavljene stranice:
curl -s -o /dev/null -w "%{http_code}\n" https://antigravity-docs-teal.vercel.app/hr/starlight-setup/Očekivani izlaz: 200.
Korak 13 – Postavljanje vlastite domene i automatski SSL certifikat putem Vercel CLI-ja
Odjeljak s naslovom “Korak 13 – Postavljanje vlastite domene i automatski SSL certifikat putem Vercel CLI-ja”Zadane Vercel URL adrese (poput https://antigravity-docs-teal.vercel.app) su dugačke i generičke. Za profesionalan dojam dokumentacije povezujemo vlastitu domenu ili poddomenu (npr. antigravity.clawdie.si).
Vercel upravlja globalnom Anycast Edge mrežom s automatskim izdavanjem Let’s Encrypt / ZeroSSL certifikata. Vlastitu domenu možete povezati i verificirati izravno putem terminala bez migracije postojećih imenskih poslužitelja (DNS nameservers).
13.1 – Povezivanje vlastite domene s projektom
Odjeljak s naslovom “13.1 – Povezivanje vlastite domene s projektom”Pomoću naredbe vercel domains add domenu dodajemo u svoj projekt:
npx vercel domains add antigravity.clawdie.si antigravity-docsOvo stvara usmjerivački unos unutar Vercelove globalne CDN mreže koji usmjerava zahtjeve za antigravity.clawdie.si na aktivnu produkcijsku izgradnju.
13.2 – Provjera potrebne DNS konfiguracije
Odjeljak s naslovom “13.2 – Provjera potrebne DNS konfiguracije”Provjerite točne parametre koje je potrebno unijeti u DNS zapise:
npx vercel domains verify antigravity.clawdie.siVercel provjerava javne DNS zapise i prikazuje preporučene opcije:
- Opcija A (A zapis – preporučeno): Usmjerite
antigravityna Vercelov Anycast IP76.76.21.21. - Opcija B (CNAME zapis): Usmjerite
antigravitynacname.vercel-dns.com.(ili jedinstveni CNAME hash).
13.3 – Unos DNS zapisa kod registara domene
Odjeljak s naslovom “13.3 – Unos DNS zapisa kod registara domene”Prijavite se u kontrolnu ploču DNS pružatelja gdje upravljate domenom i unesite zapis:
| Vrsta zapisa | Ime / Poslužitelj (Host) | Vrijednost / Cilj | TTL |
|---|---|---|---|
A |
antigravity |
76.76.21.21 |
3600 (ili Automatski) |
ili CNAME |
antigravity |
cname.vercel-dns.com. |
3600 (ili Automatski) |
13.4 – Automatska verifikacija i izdavanje SSL certifikata
Odjeljak s naslovom “13.4 – Automatska verifikacija i izdavanje SSL certifikata”Nakon propagacije DNS zapisa (obično 1 do 5 minuta), ponovno pokrenite provjeru:
npx vercel domains verify antigravity.clawdie.siOčekivani izlaz:
Status: DNS Configuration ✔ Valid Configuration Project ✔ Verified for antigravity-docsVercel automatski pokreće ACME autorizaciju, izdaje besplatan SSL/TLS certifikat i forsira sigurnu HTTPS enkripciju uz HTTP/2 i HSTS.
13.5 – Provjera vlastite domene uživo
Odjeljak s naslovom “13.5 – Provjera vlastite domene uživo”Potvrdite ispravnost certifikata i odziv rubnog predmeđuspremnika pomoću curl:
curl -s -I https://antigravity.clawdie.si/Očekivana zaglavlja odgovora:
HTTP/2 200server: Vercelstrict-transport-security: max-age=63072000x-vercel-cache: HITKorak 14 – Provjera performansi i optimizacije (Google PageSpeed Insights)
Odjeljak s naslovom “Korak 14 – Provjera performansi i optimizacije (Google PageSpeed Insights)”Za neovisnu provjeru brzine, pristupačnosti i Core Web Vitals metrika testirali smo stranicu Googleovim službenim alatom PageSpeed Insights.
Rezultat uživo: Savršenih 100/100 bodova
Odjeljak s naslovom “Rezultat uživo: Savršenih 100/100 bodova”Testiranje na produkcijskoj domeni (https://antigravity.clawdie.si/) potvrdilo je besprijekornih 100 bodova u sve četiri kategorije:

- Performanse (Performance): 100 — Gotovo trenutačan prikaz najvećeg sadržajnog elementa (LCP) i nula milisekundi blokiranja zahvaljujući Astrovom pristupu bez klijentskog JavaScripta prema zadanim postavkama.
- Pristupačnost (Accessibility): 100 — Ugrađeni visoki kontrasti boja, standardni ARIA atributi i podrška za čitače zaslona.
- Dobre prakse (Best Practices): 100 — Suvremena kompresija slika (Sharp
.webp), forsirani HTTPS i nula zastarjelih API sučelja. - SEO optimizacija: 100 — Automatske metapodatkovne oznake, strukturirane mape web stranice i višejezične oznake
hreflang. - Agentno pregledavanje (Agentic Browsing): 2/2 — Potpuno čitljiva semantička struktura, optimizirana za AI agente i automatizirane indeksere.
Zaključak
Odjeljak s naslovom “Zaključak”Uspješno ste postavili višejezični dokumentacijski portal Astro Starlight, prilagodili ga i objavili na platformi Vercel.
Sljedeći koraci i resursi: