Postavitev in uvedba Astro Starlight
Ta priročnik vas korak za korakom vodi skozi vzpostavitev, nastavitev in uvedbo visoko zmogljive večjezične dokumentacije s pomočjo ogrodij Astro in Starlight ter platforme Vercel. Na koncu boste imeli produkcijsko pripravljeno dokumentacijsko spletno mesto z interaktivno navigacijo, samodejno optimizacijo slik, lokalnim iskalnikom Pagefind in avtomatizirano uvedbo v oblak.
Načela, ki jim sledi ta vodnik
Razdelek z naslovom “Načela, ki jim sledi ta vodnik”V skladu s standardi tehničnega pisanja:
- Brez izpuščanja korakov: Vsak ukaz, ustvarjena datoteka in konfiguracijska vrstica so natančno navedeni.
- Kontekst pred izvedbo: Tehnični izrazi in arhitekturni razlogi so pojasnjeni pred vnašanjem ukazov.
- Prikaži, ne le povej: Vključene so celotne, delujoče konfiguracijske datoteke namesto nepopolnih izsekov.
- Preizkušeno in preverjeno: Vsa navodila neposredno odražajo preverjeno postavitev, ki poganja prav to spletno mesto.
Predpogoji
Razdelek z naslovom “Predpogoji”Pred začetkom se prepričajte, da imate pripravljeno naslednje:
- Node.js: Nameščena različica 20.x ali novejša (
node -v) - Upravitelj paketov: npm (različica 10.x ali novejša) ali pnpm
- Račun Vercel: Brezplačen račun na vercel.com
- Vercel CLI: Dostopen prek
npx vercelali nameščen globalno (npm install -g vercel) - Terminalski dostop: Lupina v operacijskih sistemih Linux (Debian/Ubuntu), macOS ali Windows WSL2
- Urejevalnik besedila: VS Code, Cursor, Nano ali vaš priljubljeni urejevalnik
Terminologija in ograde
Razdelek z naslovom “Terminologija in ograde”V tem vodniku uporabljamo naslednje ograde za spremenljivke:
| Ograda | Pomen | Primer |
|---|---|---|
<ime_projekta> |
Ime mape ali repozitorija dokumentacije | antigravity-docs |
<produkcijski_url> |
Dodeljen produkcijski naslov na Vercelu | https://antigravity-docs-teal.vercel.app |
<koda_jezika> |
Dvočrkovna koda jezika po standardu ISO | sl, en, hr |
<github_repo_url> |
Povezava do vašega Git repozitorija | https://github.com/google-gemini |
1. korak – Nastavitev ogrodja projekta (Scaffolding)
Razdelek z naslovom “1. korak – Nastavitev ogrodja projekta (Scaffolding)”Nov projekt Astro inicializiramo z uradno predlogo Starlight. Da se izognemo interaktivnim terminalskim vprašanjem v avtomatiziranih ali agentnih okoljih, uporabimo neinteraktivna stikala.
V terminalu zaženite:
npm create astro@latest antigravity-docs -- \ --template starlight \ --yes \ --install \ --no-git \ --typescript strictPomen stikal:
Razdelek z naslovom “Pomen stikal:”--template starlight: Inicializira projekt z uradno predlogo in vtičniki Starlight.--yes: Samodejno sprejme privzete nastavitve brez ustavljanja.--install: Takoj namesti vse osnovne odvisnosti prek upravitelja npm.--no-git: Prepreči ustvarjanje praznega repozitorija Git, če že delate znotraj obstoječega repozitorija.--typescript strict: Omogoči strogo preverjanje tipov TypeScript za varno delo s shemami vsebin.
Premaknite se v mapo projekta:
cd antigravity-docs2. korak – Namestitev produkcijskih odvisnosti
Razdelek z naslovom “2. korak – Namestitev produkcijskih odvisnosti”Poleg jedra Starlight namestimo knjižnico Sharp. Astro jo uporablja za hitro procesiranje, stiskanje in samodejno pretvorbo slik v sodobni format .webp med gradnjo.
Namestite Sharp:
npm install sharpCelotna datoteka package.json
Razdelek z naslovom “Celotna datoteka package.json”Preverite, da vaša datoteka package.json vsebuje ustrezne zagonske skripte in odvisnosti:
{ "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" }}3. korak – Nastavitev sheme vsebin (src/content.config.ts)
Razdelek z naslovom “3. korak – Nastavitev sheme vsebin (src/content.config.ts)”Astro 5 uvaja nov mehanizem Content Layer API. V mapi src/content.config.ts definiramo zbirko docs in zbirko i18n za prevode vmesnika.
Ustvarite datoteko 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() }),};4. korak – Glavna konfiguracija (astro.config.mjs)
Razdelek z naslovom “4. korak – Glavna konfiguracija (astro.config.mjs)”Datoteka astro.config.mjs upravlja identiteto spletnega mesta, večjezične poti, navigacijo v stranski vrstici in sloge CSS.
4.1 – Večjezična arhitektura usmerjanja
Razdelek z naslovom “4.1 – Večjezična arhitektura usmerjanja”V naši nastavitvi uporabljamo tri jezike:
- Koren (
/): Slovenščina (sl) je privzeti jezik v korenu. - Angleščina (
/en/): Angleška dokumentacija. - Hrvaščina (
/hr/): Hrvaška dokumentacija.
4.2 – Celotna datoteka astro.config.mjs
Razdelek z naslovom “4.2 – Celotna datoteka astro.config.mjs”Ustvarite ali posodobite 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. Življenjski kavlji', slug: 'hooks', translations: { en: '8. Lifecycle Hooks', hr: '8. Kuke životnog ciklusa (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' } }, ], }, ], }), ],});5. korak – Prevod uporabniškega vmesnika (src/content/i18n/)
Razdelek z naslovom “5. korak – Prevod uporabniškega vmesnika (src/content/i18n/)”Za slovenski in hrvaški jezik ustvarimo slovarja v mapi src/content/i18n/ z lokaliziranimi nizi za iskalnik, kazalo vsebine, izbirnik teme in navigacijo.
5.1 – Slovenski slovar (src/content/i18n/sl.json)
Razdelek z naslovom “5.1 – Slovenski slovar (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 – Hrvaški slovar (src/content/i18n/hr.json)
Razdelek z naslovom “5.2 – Hrvaški slovar (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}}”"}6. korak – Prilagoditev slogov CSS (src/styles/custom.css)
Razdelek z naslovom “6. korak – Prilagoditev slogov CSS (src/styles/custom.css)”V privzeti postavitvi Starlight postavi izbirnik jezika pred ikono GitHub. V naši prilagoditvi izbirnik jezika (SI, EN, HR) premaknemo na sredino med ikono GitHub in stikalom za temo s pomočjo lastnosti order v Flexboxu:
/* Postavitev izbire jezika med ikone socialnih omrežij (GitHub) in izbiro 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;}
/* Prilagoditev za mobilni meni */.mobile-preferences .social-icons { order: 1;}
.mobile-preferences starlight-lang-select { order: 2;}
.mobile-preferences starlight-theme-select { order: 3;}7. korak – Vercel komponenta za kopiranje strani in ogled Markdown kode (src/components/PageTitle.astro)
Razdelek z naslovom “7. korak – Vercel komponenta za kopiranje strani in ogled Markdown kode (src/components/PageTitle.astro)”Sodobne dokumentacijske strani (kot je uradna dokumentacija Vercel) imajo na vrhu vsake strani interaktivni gumb, ki obiskovalcem in razvijalcem omogoča takojšnje kopiranje izvorne kode Markdown v odložišče ali ogled surove kode v pojavnem oknu (modal dialog).
Starlight omogoča prepis privzetih komponent uporabniškega vmesnika prek polja components v astro.config.mjs. S prepisom komponente PageTitle ohranimo standardni naslov <h1>, hkrati pa v glavo dodamo interaktivni gumb za kopiranje in predogled brez potrebe po klicanju zunanjih vmesnikov API ali nepotrebnem klientskem prenosu JavaScripta (zero-hydration).
Arhitekturne posebnosti:
Razdelek z naslovom “Arhitekturne posebnosti:”- Branje na strežniku v času gradnje (Build-time extraction): V času statične gradnje (
npm run build) Astrova komponenta prekAstro.locals.starlightRoute.entry.filePathin Node.js modulanode:fsprebere datoteko neposredno z diska ter surovo Markdown kodo vstavi v neaktiven HTML element<template class="raw-markdown-source">. - Dostopno modalno okno
<dialog>: Za prikaz Markdown kode uporabimo standardni HTML element<dialog>s klicemdialog.showModal(), ki zagotavlja dostopnost, navigacijo s tipkovnico in preprečuje premikanje ozadja. - Zanesljivo centriranje na sredino zaslona: Da pojavno okno ne obtiči v zgornjem levem kotu flexbnih elementov, uporabimo fiksno centriranje glede na celotno vidno polje (viewport):
position: fixed;top: 50%;left: 50%;transform: translate(-50%, -50%);margin: 0;width: min(90vw, 52rem);max-height: 85vh;
- Večjezična podpora: Besedila na gumbih in v modalnem oknu se samodejno prilagajajo glede na trenutni jezik strani (
sl,en,hr). - Združljivost s prehodi med stranmi (View Transitions): Poslušalci dogodkov so registrirani na dogodek
document.addEventListener('astro:page-load', ...), kar zagotavlja delovanje gumbov tudi pri navigaciji brez celotnega ponovnega nalaganja strani.
Implementacija:
Razdelek z naslovom “Implementacija:”Ustvarite komponento 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-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>8. korak – Interaktivne vstopne strani (index.mdx)
Razdelek z naslovom “8. korak – Interaktivne vstopne strani (index.mdx)”Vsaka vstopna stran uporablja predlogo splash in komponente LinkCard znotraj CardGrid za neposreden skok na poglavja:
import { LinkCard, CardGrid } from '@astrojs/starlight/components';
## Raziščite poglavja vodnika
<CardGrid> <LinkCard title="1. Večagentne ekipe" href="/subagents/" description="Sestavite specializirane ekipe podagentov in preprečite nasičenje kontekstnega okna." /></CardGrid>9. korak – Struktura poglavij
Razdelek z naslovom “9. korak – Struktura poglavij”Vsako poglavje je shranjeno kot samostojna datoteka Markdown v mapi src/content/docs/:
src/content/docs/<poglavje>.md(korenski jezik - slovenščina)src/content/docs/en/<poglavje>.md(angleščina)src/content/docs/hr/<poglavje>.md(hrvaščina)
Vsaka datoteka vsebuje glavo YAML:
---title: Naslov poglavjadescription: Kratek enovrstični povzetek vsebine.sidebar: order: 1---10. korak – Lokalni razvojni strežnik
Razdelek z naslovom “10. korak – Lokalni razvojni strežnik”Interaktivni zagon:
Razdelek z naslovom “Interaktivni zagon:”npm run devZagon v ozadju (za avtomatizirana okolja):
Razdelek z naslovom “Zagon v ozadju (za avtomatizirana okolja):”npx astro dev --background- Preverjanje stanja:
npx astro dev status - Dnevniki v živo:
npx astro dev logs - Zaustavitev strežnika:
npx astro dev stop
11. korak – Lokalno preverjanje gradnje
Razdelek z naslovom “11. korak – Lokalno preverjanje gradnje”Pred objavo vedno zaženite produkcijsko gradnjo:
npm run buildPreverite, da se uspešno ustvarijo vse statične strani, optimizirajo slike in zgradi iskalni indeks Pagefind.
12. korak – Samodejna uvedba na Vercel
Razdelek z naslovom “12. korak – Samodejna uvedba na Vercel”Spletno mesto uvedemo v produkcijo z enim samim ukazom prek Vercel CLI:
npm run build && npx vercel --prod --yesPo zaključku preverite odziv spletnega mesta:
curl -s -o /dev/null -w "%{http_code}\n" https://antigravity-docs-teal.vercel.app/starlight-setup/Pričakovan izpis: 200.
13. korak – Nastavitev lastne domene in samodejni certifikat SSL prek Vercel CLI
Razdelek z naslovom “13. korak – Nastavitev lastne domene in samodejni certifikat SSL prek Vercel CLI”Privzeti naslovi Vercela (npr. https://antigravity-docs-teal.vercel.app) so dolgi in generični. Za profesionalno podobo dokumentacije povežemo lastno domeno ali poddomeno (npr. antigravity.clawdie.si).
Vercel upravlja globalno omrežje Anycast Edge s samodejnim nameščanjem certifikatov Let’s Encrypt / ZeroSSL. Lastno domeno lahko povežete in preverite neposredno prek terminala, ne da bi morali premikati obstoječe imenske strežnike (DNS nameservers).
13.1 – Povezava lastne domene s projektom
Razdelek z naslovom “13.1 – Povezava lastne domene s projektom”Z ukazom vercel domains add domeno dodamo v svoj projekt:
npx vercel domains add antigravity.clawdie.si antigravity-docsTo ustvari usmerjevalni vnos v Vercelovem globalnem omrežju CDN, ki zahteve za antigravity.clawdie.si preusmeri na aktivno produkcijsko gradnjo.
13.2 – Preverjanje zahtevane konfiguracije DNS
Razdelek z naslovom “13.2 – Preverjanje zahtevane konfiguracije DNS”Z ukazom za preverjanje pridobimo natančna navodila za vnos DNS zapisov:
npx vercel domains verify antigravity.clawdie.siVercel preveri javne zapise DNS in prikaže priporočene možnosti:
- Možnost A (zapis A – priporočeno): Usmerite
antigravityna Vercelov Anycast IP76.76.21.21. - Možnost B (zapis CNAME): Usmerite
antigravitynacname.vercel-dns.com.(ali unikatni razpršeni zapis CNAME).
13.3 – Vnos zapisov DNS pri registrarju domene
Razdelek z naslovom “13.3 – Vnos zapisov DNS pri registrarju domene”Prijavite se v nadzorno ploščo ponudnika DNS, kjer gostujete domeno, in dodajte zapis:
| Tip zapisa | Ime / Gostitelj (Host) | Vrednost / Cilj | TTL |
|---|---|---|---|
A |
antigravity |
76.76.21.21 |
3600 (ali Samodejno) |
ali CNAME |
antigravity |
cname.vercel-dns.com. |
3600 (ali Samodejno) |
13.4 – Samodejno preverjanje in izdaja certifikata SSL
Razdelek z naslovom “13.4 – Samodejno preverjanje in izdaja certifikata SSL”Ko se zapisi DNS uveljavijo (običajno v 1 do 5 minutah), ponovno zaženite preverjanje:
npx vercel domains verify antigravity.clawdie.siPričakovan izpis:
Status: DNS Configuration ✔ Valid Configuration Project ✔ Verified for antigravity-docsVercel samodejno sproži overitev ACME, izda brezplačen varnostni certifikat SSL/TLS ter uveljavi varno povezavo HTTPS s protokolom HTTP/2 in HSTS.
13.5 – Preverjanje lastne domene v živo
Razdelek z naslovom “13.5 – Preverjanje lastne domene v živo”Preverite veljavnost certifikata SSL in odziv robnega predpomnilnika z ukazom curl:
curl -s -I https://antigravity.clawdie.si/Pričakovane glave odgovora:
HTTP/2 200server: Vercelstrict-transport-security: max-age=63072000x-vercel-cache: HIT14. korak – Preverjanje zmogljivosti in optimizacije (Google PageSpeed Insights)
Razdelek z naslovom “14. korak – Preverjanje zmogljivosti in optimizacije (Google PageSpeed Insights)”Za neodvisno preverjanje hitrosti, dostopnosti in metrik Core Web Vitals smo spletno mesto preizkusili z Googlovim uradnim orodjem PageSpeed Insights.
Rezultat v živo: Popolna ocena 100/100
Razdelek z naslovom “Rezultat v živo: Popolna ocena 100/100”Preizkus na produkcijski domeni (https://antigravity.clawdie.si/) je potrdil brezhibnih 100 točk v vseh štirih kategorijah:

- Zmogljivost (Performance): 100 — Bliskovit prikaz največjega vsebinskega elementa (LCP) in ničelno blokiranje zahvaljujoč temu, da Astro privzeto ne pošilja nepotrebne kode JavaScript.
- Dostopnost (Accessibility): 100 — Vgrajeni visoki barvni kontrasti, standardni atributi ARIA in podpora za bralnike zaslona.
- Dobre prakse (Best Practices): 100 — Sodobno stiskanje slik (Sharp
.webp), prisilni HTTPS in nič zastarelih vmesnikov API. - Optimizacija za iskalnike (SEO): 100 — Samodejne metapodatkovne oznake, strukturirani zemljevidi spletnega mesta in večjezične oznake
hreflang. - Agentno brskanje (Agentic Browsing): 2/2 — Popolnoma berljiva semantična struktura, prilagojena za bralnike umetne inteligence in iskalne pajke.
Zaključek
Razdelek z naslovom “Zaključek”Uspešno ste vzpostavili večjezični dokumentacijski portal Astro Starlight, ga prilagodili in objavili na platformi Vercel.
Naslednji koraki in viri: