2
0

28 Commits

Author SHA1 Message Date
c5daac3366 feat(mcp): add stats, value props, and CTA tools for landing pages
All checks were successful
Build and Release / Lint (push) Successful in 5m21s
Build and Release / Create Release (push) Successful in 0s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Successful in 4m21s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Successful in 4m32s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 12m1s
Build and Release / Build Binary (linux/arm64) (push) Successful in 7m55s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Successful in 9h7m31s
Build and Release / Unit Tests (push) Successful in 14m12s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Successful in 12m51s
Implements three new MCP tools for landing page management: update_landing_stats for stat counters, update_landing_value_props for value proposition cards, and update_landing_cta for bottom call-to-action section. Each tool supports structured data with validation and integrates with existing config save flow.
2026-04-05 12:49:19 -04:00
916211004d docs(mcp): add explanation to nolint directive
All checks were successful
Build and Release / Create Release (push) Successful in 0s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 3m21s
Build and Release / Unit Tests (push) Successful in 15m52s
Build and Release / Lint (push) Successful in 16m53s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Successful in 4m18s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Successful in 4m49s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Successful in 8m12s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Successful in 9h7m30s
Build and Release / Build Binary (linux/arm64) (push) Successful in 7m19s
Clarifies why unparam is suppressed on toolListLandingTemplates with inline comment explaining interface requirement.
2026-04-05 09:09:01 -04:00
02fdc1a194 chore(mcp): suppress unparam linter warning for toolListLandingTemplates
Some checks failed
Build and Release / Create Release (push) Has been skipped
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 4m27s
Build and Release / Unit Tests (push) Successful in 5m15s
Build and Release / Lint (push) Failing after 10m56s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Has been skipped
Build and Release / Build Binaries (amd64, darwin, macos) (push) Has been skipped
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Has been skipped
Build and Release / Build Binaries (arm64, darwin, macos) (push) Has been skipped
Build and Release / Build Binary (linux/arm64) (push) Has been skipped
Adds nolint:unparam directive to toolListLandingTemplates. Function signature must match tool handler interface even though parameters are unused.
2026-04-05 08:45:31 -04:00
1b0bba09b9 style(mcp): use errors.New for static error messages
Some checks failed
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 3m19s
Build and Release / Create Release (push) Successful in 0s
Build and Release / Lint (push) Failing after 11m49s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Has been skipped
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Has been skipped
Build and Release / Build Binaries (amd64, darwin, macos) (push) Has been skipped
Build and Release / Build Binaries (arm64, darwin, macos) (push) Has been skipped
Build and Release / Build Binary (linux/arm64) (push) Has been skipped
Build and Release / Unit Tests (push) Successful in 12m52s
Replaces fmt.Errorf with errors.New for static error message in resolveOwnerRepo. Marks unused context and args parameters with underscore in toolListLandingTemplates.
2026-04-05 04:03:02 -04:00
0c0d1c1493 feat(mcp): add landing page management tools to MCP server
Some checks failed
Build and Release / Create Release (push) Successful in 1s
Build and Release / Unit Tests (push) Successful in 3m42s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 9m25s
Build and Release / Lint (push) Failing after 10m12s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Has been skipped
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Has been skipped
Build and Release / Build Binaries (amd64, darwin, macos) (push) Has been skipped
Build and Release / Build Binaries (arm64, darwin, macos) (push) Has been skipped
Build and Release / Build Binary (linux/arm64) (push) Has been skipped
Creates mcp_pages.go with 10 new tools for managing repository landing pages via Claude Code. Supports getting/updating brand, hero, pricing, comparison, features, social proof, SEO, and theme sections. Includes template listing and enable/disable functionality. Integrates with existing pages service and registers tools in handleToolsList and handleToolsCall.
2026-04-05 03:39:54 -04:00
9461599b57 fix(api): populate Repo field in release API responses
All checks were successful
Build and Release / Unit Tests (push) Successful in 7m8s
Build and Release / Create Release (push) Successful in 0s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 7m33s
Build and Release / Lint (push) Successful in 7m50s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Successful in 4m31s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Successful in 4m39s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Successful in 9h5m34s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Successful in 12m17s
Build and Release / Build Binary (linux/arm64) (push) Successful in 33m20s
Set release.Repo before converting to API format in all v2 release endpoints (CheckAppUpdate, ListReleasesV2, GetReleaseV2, GetLatestReleaseV2). Ensures repository information is available in API responses
2026-04-03 23:57:29 -04:00
414560f470 fix(ci): isolate GOMODCACHE per job to prevent cache conflicts
Some checks failed
Build and Release / Create Release (push) Successful in 0s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 3m1s
Build and Release / Unit Tests (push) Successful in 22m31s
Build and Release / Lint (push) Successful in 24m45s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Successful in 5m37s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Successful in 5m40s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Successful in 9h7m8s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Successful in 13m32s
Build and Release / Build Binary (linux/arm64) (push) Failing after 30m51s
Move GOMODCACHE from global env to job-level env with unique paths per job. Prevents race conditions and cache corruption when jobs run in parallel, especially after the gotextdiff mirror replacement.
2026-03-30 09:47:38 -04:00
b43345986a refactor(pages): remove unused app store fields from advanced settings
Some checks failed
Build and Release / Unit Tests (push) Successful in 6m20s
Build and Release / Create Release (push) Successful in 0s
Build and Release / Lint (push) Successful in 6m31s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Failing after 9h1m42s
Build and Release / Integration Tests (PostgreSQL) (push) Has been cancelled
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Has been cancelled
Build and Release / Build Binaries (amd64, darwin, macos) (push) Has been cancelled
Build and Release / Build Binaries (arm64, darwin, macos) (push) Has been cancelled
Build and Release / Build Binary (linux/arm64) (push) Has been cancelled
2026-03-30 09:32:58 -04:00
7fbbd26b20 refactor(ui): consolidate pages navigation into main settings navbar
Some checks failed
Build and Release / Unit Tests (push) Successful in 7m33s
Build and Release / Create Release (push) Successful in 0s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 7m40s
Build and Release / Lint (push) Successful in 7m57s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Failing after 9h1m36s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Successful in 7m8s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Successful in 7m21s
Build and Release / Build Binary (linux/arm64) (push) Successful in 7m37s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Successful in 20m50s
Remove redundant pages_nav.tmpl and integrate the advanced pages link directly into the main settings navbar. Reduces template duplication while maintaining the same navigation structure.
2026-03-30 03:23:26 -04:00
b26bf4bfe8 fix(ci): replace deleted gotextdiff dependency with mirror
Some checks failed
Build and Release / Create Release (push) Has been skipped
Build and Release / Unit Tests (push) Failing after 1m41s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 3m32s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Has been cancelled
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Has been cancelled
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Has been cancelled
Build and Release / Build Binaries (arm64, darwin, macos) (push) Has been cancelled
Build and Release / Build Binary (linux/arm64) (push) Has been cancelled
Build and Release / Lint (push) Has been cancelled
The upstream github.com/hexops/gotextdiff repository was deleted. Replace with internal mirror and add go clean -modcache to all CI dependency installation steps to ensure clean builds with the new module path.
2026-03-30 03:12:19 -04:00
242ebf2dc1 chore(ci): bump Go version to 1.25.5
Some checks failed
Build and Release / Unit Tests (push) Successful in 6m13s
Build and Release / Create Release (push) Successful in 0s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 7m1s
Build and Release / Lint (push) Successful in 7m20s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Failing after 3m20s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Failing after 3m19s
Build and Release / Build Binary (linux/arm64) (push) Failing after 2m22s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Successful in 9h10m55s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Successful in 13m34s
2026-03-30 02:28:27 -04:00
46f7570d25 feat(pages): add static route configuration for direct file serving
Some checks failed
Build and Release / Create Release (push) Has been skipped
Build and Release / Unit Tests (push) Failing after 47s
Build and Release / Integration Tests (PostgreSQL) (push) Failing after 1m29s
Build and Release / Lint (push) Failing after 2m16s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Has been skipped
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Has been skipped
Build and Release / Build Binaries (amd64, darwin, macos) (push) Has been skipped
Build and Release / Build Binaries (arm64, darwin, macos) (push) Has been skipped
Build and Release / Build Binary (linux/arm64) (push) Has been skipped
Allow repository pages to serve files directly at specific URL paths instead of rendering the landing page. Supports exact paths (/badge.svg) and glob patterns (/schema/*). Add advanced settings UI and API endpoints for managing static routes alongside existing redirects and custom code options.
2026-03-30 02:07:41 -04:00
48aab974fe fix(pages): populate defaults before translation overlay
Some checks failed
Build and Release / Create Release (push) Successful in 0s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 2m48s
Build and Release / Unit Tests (push) Successful in 8m44s
Build and Release / Lint (push) Successful in 9m14s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Failing after 0s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Successful in 3m49s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Successful in 4m52s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Successful in 9h4m56s
Build and Release / Build Binary (linux/arm64) (push) Failing after 17m26s
Add ensureTemplateDefaults function that fills navigation labels and section headlines with template-specific defaults before applying translation overlay. Ensures these fields are present in base config JSON so translations can override them via deep-merge. Fixes issue where translated labels wouldn't apply because base config had empty strings instead of default values. Prevents templates from falling back to hardcoded English text.
2026-03-18 00:05:05 -04:00
965ef8966f feat(pages): add navigation label translation support
All checks were successful
Build and Release / Create Release (push) Successful in 0s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 4m4s
Build and Release / Unit Tests (push) Successful in 4m38s
Build and Release / Lint (push) Successful in 6m26s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Successful in 3m52s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Successful in 4m52s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Successful in 9h5m22s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Successful in 3m38s
Build and Release / Build Binary (linux/arm64) (push) Successful in 7m46s
Add translation support for navigation section labels (Value Props, Features, Pricing, Blog, Gallery, Compare, etc.). Adds TemplateDefaultLabels function that returns template-specific creative names (e.g., "Systems Analysis" for value props in Architecture Deep Dive). Auto-applies defaults when enabling pages or changing templates. Includes UI fields in languages settings and translation JSON serialization. Enables full localization of section headings.
2026-03-17 23:34:29 -04:00
17028589c8 fix(i18n): use native language names in display map
Some checks failed
Build and Release / Create Release (push) Has been skipped
Build and Release / Build Binaries (amd64, darwin, macos) (push) Has been cancelled
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Has been cancelled
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Has been cancelled
Build and Release / Integration Tests (PostgreSQL) (push) Has been cancelled
Build and Release / Build Binaries (arm64, darwin, macos) (push) Has been cancelled
Build and Release / Lint (push) Has been cancelled
Build and Release / Build Binary (linux/arm64) (push) Has been cancelled
Build and Release / Unit Tests (push) Has been cancelled
Replace English language names with native names for better UX (e.g., "日本語" instead of "Japanese", "Español" instead of "Espanol"). Adds proper diacritics and uses native scripts. Makes language selection more intuitive for non-English speakers.
2026-03-17 21:50:15 -04:00
80096bfbf9 feat(ui): add loading indicators for AI generation actions
Some checks failed
Build and Release / Create Release (push) Successful in 0s
Build and Release / Unit Tests (push) Successful in 3m24s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 7m26s
Build and Release / Lint (push) Successful in 8m44s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Failing after 0s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Successful in 3m39s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Successful in 4m53s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Successful in 9h4m28s
Build and Release / Build Binary (linux/arm64) (push) Failing after 12m10s
Show loading spinner and message when AI content generation or translation is in progress. Disables submit button and hides form to prevent duplicate submissions. Adds Copilot icon to AI buttons. Marks file inputs with data-ays-ignore to prevent "unsaved changes" warnings. Improves UX by providing visual feedback during long-running AI operations.
2026-03-17 21:42:39 -04:00
22844f6437 fix(pages): use navigation labels for section headings and add defaults
Use LabelValueProps and LabelFeatures from navigation config for section headings instead of hardcoded text. Removes hardcoded subheadlines that don't translate well. Adds default headlines for blog ("Latest Posts"), gallery ("Gallery"), and comparison ("How We Compare") sections when not configured. Improves consistency and translation support across all 8 page templates.
2026-03-17 21:34:34 -04:00
737e323fcb perf(pages): parallelize bulk AI translation
Run AI translations for multiple languages concurrently using goroutines and sync.WaitGroup. Protects shared counters with mutex. Significantly reduces total translation time when translating many languages. For example, translating 10 languages now takes ~10 seconds instead of ~100 seconds (assuming 10s per language).
2026-03-17 21:24:05 -04:00
00024298d0 feat(pages): add JSON struct tags to pages config types
Some checks failed
Build and Release / Create Release (push) Successful in 0s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 2m53s
Build and Release / Unit Tests (push) Successful in 8m48s
Build and Release / Lint (push) Successful in 9m12s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Failing after 1s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Successful in 4m35s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Successful in 4m50s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Successful in 9h5m2s
Build and Release / Build Binary (linux/arm64) (push) Failing after 14m53s
Add json struct tags to all LandingConfig types for proper JSON serialization in v2 API. Uses omitempty for primitives and omitzero for structs to exclude empty values from JSON output. Enables clean JSON responses from GET /repos/{owner}/{repo}/pages/config API endpoint.
2026-03-17 20:27:34 -04:00
b27dd0cda8 refactor(pages): use maps.Copy in deepMergeArrays
Some checks failed
Build and Release / Create Release (push) Successful in 1s
Build and Release / Unit Tests (push) Successful in 3m22s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 7m53s
Build and Release / Lint (push) Successful in 9m1s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Failing after 0s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Successful in 3m53s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Successful in 5m14s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Successful in 9h5m32s
Build and Release / Build Binary (linux/arm64) (push) Failing after 14m38s
Replace manual map copy loop with maps.Copy from Go 1.21 stdlib. Cleaner and potentially more efficient. Pre-allocates merged map with correct capacity.
2026-03-17 11:55:18 -04:00
43e490d933 feat(i18n): add bulk AI translation for all languages
Some checks failed
Build and Release / Create Release (push) Successful in 1s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 3m21s
Build and Release / Lint (push) Failing after 8m46s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Has been skipped
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Has been skipped
Build and Release / Unit Tests (push) Successful in 8m49s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Has been skipped
Build and Release / Build Binaries (arm64, darwin, macos) (push) Has been skipped
Build and Release / Build Binary (linux/arm64) (push) Has been skipped
Add "Translate All (AI)" button to pages language settings that translates all configured languages in one operation. Shows success/partial success messages with counts. Adds locale keys for all 29 languages. Also removes trailing newlines from locale files for consistency.
2026-03-17 11:35:20 -04:00
d02f25c0ba fix(i18n): remove BOM from locale files
All checks were successful
Build and Release / Create Release (push) Successful in 0s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 4m7s
Build and Release / Unit Tests (push) Successful in 4m38s
Build and Release / Lint (push) Successful in 6m35s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Successful in 3m59s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Successful in 4m55s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Successful in 9h5m19s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Successful in 3m58s
Build and Release / Build Binary (linux/arm64) (push) Successful in 7m32s
Remove UTF-8 BOM (byte order mark) from 14 locale JSON files. The BOM character (U+FEFF) at the start of files can cause parsing issues in some JSON parsers and is not needed for UTF-8 files. Affects ko-KR, lv-LV, nl-NL, pl-PL, pt-BR, pt-PT, ru-RU, si-LK, sk-SK, sv-SE, tr-TR, uk-UA, zh-CN, zh-TW.
2026-03-17 09:01:01 -04:00
12341079e1 feat(i18n): add translation keys for new pages sections
Some checks failed
Build and Release / Create Release (push) Successful in 1s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 2m41s
Build and Release / Unit Tests (push) Successful in 8m55s
Build and Release / Lint (push) Successful in 9m23s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Failing after 1m4s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Failing after 9h0m35s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Failing after 50s
Build and Release / Build Binary (linux/arm64) (push) Failing after 32s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Failing after 54s
Add locale keys for translating new landing page sections: gallery, comparison, blog, and expanded fields for stats, pricing, testimonials, and footer. Adds section headers and field labels for translation UI. Includes keys for all 29 supported languages. Enables full localization of new pages features added in recent commits.
2026-03-17 03:52:30 -04:00
c5e35e3466 refactor(pages): move app store badges to hero section and add section IDs
Move Google Play and App Store badges from downloads section to hero section for better visibility. Removes redundant display when both downloads and app stores are present. Add id attributes to major sections (hero, stats, downloads, social-proof, cta, etc.) for anchor link navigation. Applies to all 9 page templates. Improves UX for mobile app landing pages.
2026-03-17 03:25:14 -04:00
c0fcf16794 style(api): sort imports in pages API
All checks were successful
Build and Release / Create Release (push) Successful in 1s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 2m44s
Build and Release / Unit Tests (push) Successful in 8m45s
Build and Release / Lint (push) Successful in 9m18s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Successful in 3m53s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Successful in 4m46s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Successful in 9h5m13s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Successful in 3m45s
Build and Release / Build Binary (linux/arm64) (push) Successful in 8m38s
2026-03-17 01:27:51 -04:00
c5786aab2b fix(api): use internal json module in pages API
Some checks failed
Build and Release / Create Release (push) Successful in 0s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 2m48s
Build and Release / Lint (push) Failing after 8m37s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Has been skipped
Build and Release / Build Binaries (amd64, darwin, macos) (push) Has been skipped
Build and Release / Build Binaries (arm64, darwin, macos) (push) Has been skipped
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Has been skipped
Build and Release / Build Binary (linux/arm64) (push) Has been skipped
Build and Release / Unit Tests (push) Successful in 8m53s
Replace stdlib encoding/json with internal json module for consistency with rest of codebase. Ensures proper handling of edge cases and custom marshaling behavior.
2026-03-17 01:15:01 -04:00
fe5b504b97 feat(api): add v2 API endpoints for landing page configuration
Some checks failed
Build and Release / Create Release (push) Successful in 0s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 2m44s
Build and Release / Lint (push) Failing after 8m24s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Has been skipped
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Has been skipped
Build and Release / Build Binaries (amd64, darwin, macos) (push) Has been skipped
Build and Release / Build Binaries (arm64, darwin, macos) (push) Has been skipped
Build and Release / Build Binary (linux/arm64) (push) Has been skipped
Build and Release / Unit Tests (push) Successful in 8m43s
Add comprehensive REST API for managing landing page configuration programmatically. Includes GET /repos/{owner}/{repo}/pages/config to retrieve full config, PUT to replace entire config, PATCH to update specific sections. Supports all config sections: brand, hero, stats, features, pricing, blog, gallery, comparison, etc. Adds UpdatePagesConfigOption and related structs for API payloads. Includes error codes for pages validation. Enables headless/automated landing page management.
2026-03-17 00:52:18 -04:00
a1f477a381 fix(ui): add missing pages settings nav items and cleanup labels
Some checks failed
Build and Release / Create Release (push) Successful in 1s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 2m53s
Build and Release / Unit Tests (push) Successful in 8m43s
Build and Release / Lint (push) Successful in 9m20s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Failing after 1s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Successful in 4m30s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Successful in 4m44s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Successful in 9h4m59s
Build and Release / Build Binary (linux/arm64) (push) Failing after 24m13s
Add Comparison and Languages items to pages settings sidebar navigation. Fix sidebar not staying open when on those pages. Remove redundant labels from URL input fields that appear after "or use a URL" dividers (logo, favicon, hero image). Improves navigation consistency and UI clarity.
2026-03-17 00:14:48 -04:00
64 changed files with 5029 additions and 864 deletions

View File

@@ -16,7 +16,7 @@ env:
GOPRIVATE: git.marketally.com
GONOSUMDB: git.marketally.com
GOTOOLCHAIN: local
GO_VERSION: "1.25.0"
GO_VERSION: "1.25.5"
NODE_VERSION: "22"
jobs:
@@ -24,6 +24,8 @@ jobs:
lint:
name: Lint
runs-on: linux-latest
env:
GOMODCACHE: /tmp/gomod-${{ github.run_id }}-lint
steps:
- name: Checkout code
uses: actions/checkout@v4
@@ -51,7 +53,8 @@ jobs:
run: npm install -g pnpm
- name: Install dependencies
run: make deps-frontend deps-backend
run: |
make deps-frontend deps-backend
- name: Run Go linter
run: make lint-go
@@ -64,6 +67,8 @@ jobs:
test-unit:
name: Unit Tests
runs-on: linux-latest
env:
GOMODCACHE: /tmp/gomod-${{ github.run_id }}-unit
steps:
- name: Checkout code
uses: actions/checkout@v4
@@ -83,7 +88,8 @@ jobs:
cache: false
- name: Install dependencies
run: go mod download
run: |
go mod download
- name: Run unit tests
run: |
@@ -99,6 +105,8 @@ jobs:
test-pgsql:
name: Integration Tests (PostgreSQL)
runs-on: linux-latest
env:
GOMODCACHE: /tmp/gomod-${{ github.run_id }}-pgsql
services:
pgsql:
image: postgres:15
@@ -140,7 +148,8 @@ jobs:
run: npm install -g pnpm
- name: Install dependencies
run: make deps-frontend deps-backend
run: |
make deps-frontend deps-backend
- name: Build frontend
run: make frontend
@@ -378,7 +387,8 @@ jobs:
- name: Install dependencies (Unix)
if: matrix.goos != 'windows'
run: make deps-frontend deps-backend
run: |
make deps-frontend deps-backend
- name: Install dependencies (Windows)
if: matrix.goos == 'windows'

3
go.mod
View File

@@ -326,6 +326,9 @@ replace github.com/go-ini/ini => github.com/go-ini/ini v1.66.6
// Use GitCaddy fork with capability support
replace code.gitea.io/actions-proto-go => git.marketally.com/gitcaddy/actions-proto-go v0.5.8
// Mirror of deleted github.com/hexops/gotextdiff
replace github.com/hexops/gotextdiff => git.marketally.com/mirrors/gotextdiff v1.0.3
// Vault plugin - use local directory for development
replace git.marketally.com/gitcaddy/gitcaddy-vault => ../gitcaddy-vault

4
go.sum
View File

@@ -31,6 +31,8 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
git.marketally.com/gitcaddy/actions-proto-go v0.5.8 h1:MBipeHvY6A0jcobvziUtzgatZTrV4fs/HE1rPQxREN4=
git.marketally.com/gitcaddy/actions-proto-go v0.5.8/go.mod h1:RPu21UoRD3zSAujoZR6LJwuVNa2uFRBveadslczCRfQ=
git.marketally.com/mirrors/gotextdiff v1.0.3 h1:Mxf+YurdCHT4y1GNiZCTDWYtVXSxhlLUeG7g7i9Za70=
git.marketally.com/mirrors/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
gitea.com/gitea/act v0.261.7-0.20251003180512-ac6e4b751763 h1:ohdxegvslDEllZmRNDqpKun6L4Oq81jNdEDtGgHEV2c=
gitea.com/gitea/act v0.261.7-0.20251003180512-ac6e4b751763/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:BAFmdZpRW7zMQZQDClaCWobRj9uL1MR3MzpCVJvc5s4=
@@ -490,8 +492,6 @@ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=

View File

@@ -170,6 +170,13 @@ const (
WishlistVoteBudget ErrorCode = "WISHLIST_VOTE_BUDGET_EXCEEDED"
)
// Pages errors (PAGES_)
const (
PagesNotConfigured ErrorCode = "PAGES_NOT_CONFIGURED"
PagesNotEnabled ErrorCode = "PAGES_NOT_ENABLED"
PagesInvalidTemplate ErrorCode = "PAGES_INVALID_TEMPLATE"
)
// AI errors (AI_)
const (
AIDisabled ErrorCode = "AI_DISABLED"
@@ -310,6 +317,11 @@ var errorCatalog = map[ErrorCode]errorInfo{
WishlistDisabled: {"Wishlist is disabled for this repository", http.StatusForbidden},
WishlistVoteBudget: {"Vote budget exceeded for this repository", http.StatusConflict},
// Pages errors
PagesNotConfigured: {"Landing page is not configured for this repository", http.StatusNotFound},
PagesNotEnabled: {"Landing page is not enabled for this repository", http.StatusForbidden},
PagesInvalidTemplate: {"Invalid landing page template name", http.StatusBadRequest},
// AI errors
AIDisabled: {"AI features are disabled", http.StatusForbidden},
AIUnitNotEnabled: {"AI unit is not enabled for this repository", http.StatusForbidden},

View File

@@ -13,80 +13,80 @@ import (
// LandingConfig represents the parsed .gitea/landing.yaml configuration
type LandingConfig struct {
Enabled bool `yaml:"enabled"`
PublicLanding bool `yaml:"public_landing"`
Template string `yaml:"template"` // open-source-hero, minimalist-docs, saas-conversion, bold-marketing
Enabled bool `yaml:"enabled" json:"enabled"`
PublicLanding bool `yaml:"public_landing" json:"public_landing"`
Template string `yaml:"template" json:"template"` // open-source-hero, minimalist-docs, saas-conversion, bold-marketing
// Custom domain (optional)
Domain string `yaml:"domain,omitempty"`
Domain string `yaml:"domain,omitempty" json:"domain,omitempty"`
// Brand configuration
Brand BrandConfig `yaml:"brand,omitempty"`
Brand BrandConfig `yaml:"brand,omitempty" json:"brand,omitzero"`
// Hero section
Hero HeroConfig `yaml:"hero,omitempty"`
Hero HeroConfig `yaml:"hero,omitempty" json:"hero,omitzero"`
// Stats/metrics
Stats []StatConfig `yaml:"stats,omitempty"`
Stats []StatConfig `yaml:"stats,omitempty" json:"stats,omitempty"`
// Value propositions
ValueProps []ValuePropConfig `yaml:"value_props,omitempty"`
ValueProps []ValuePropConfig `yaml:"value_props,omitempty" json:"value_props,omitempty"`
// Features
Features []FeatureConfig `yaml:"features,omitempty"`
Features []FeatureConfig `yaml:"features,omitempty" json:"features,omitempty"`
// Social proof
SocialProof SocialProofConfig `yaml:"social_proof,omitempty"`
SocialProof SocialProofConfig `yaml:"social_proof,omitempty" json:"social_proof,omitzero"`
// Pricing (for saas-conversion template)
Pricing PricingConfig `yaml:"pricing,omitempty"`
Pricing PricingConfig `yaml:"pricing,omitempty" json:"pricing,omitzero"`
// CTA section
CTASection CTASectionConfig `yaml:"cta_section,omitempty"`
CTASection CTASectionConfig `yaml:"cta_section,omitempty" json:"cta_section,omitzero"`
// Blog section
Blog BlogSectionConfig `yaml:"blog,omitempty"`
Blog BlogSectionConfig `yaml:"blog,omitempty" json:"blog,omitzero"`
// Gallery section
Gallery GallerySectionConfig `yaml:"gallery,omitempty"`
Gallery GallerySectionConfig `yaml:"gallery,omitempty" json:"gallery,omitzero"`
// Comparison section
Comparison ComparisonSectionConfig `yaml:"comparison,omitempty"`
Comparison ComparisonSectionConfig `yaml:"comparison,omitempty" json:"comparison,omitzero"`
// Navigation visibility
Navigation NavigationConfig `yaml:"navigation,omitempty"`
Navigation NavigationConfig `yaml:"navigation,omitempty" json:"navigation,omitzero"`
// Footer
Footer FooterConfig `yaml:"footer,omitempty"`
Footer FooterConfig `yaml:"footer,omitempty" json:"footer,omitzero"`
// Theme customization
Theme ThemeConfig `yaml:"theme,omitempty"`
Theme ThemeConfig `yaml:"theme,omitempty" json:"theme,omitzero"`
// SEO & Social
SEO SEOConfig `yaml:"seo,omitempty"`
SEO SEOConfig `yaml:"seo,omitempty" json:"seo,omitzero"`
// Analytics
Analytics AnalyticsConfig `yaml:"analytics,omitempty"`
Analytics AnalyticsConfig `yaml:"analytics,omitempty" json:"analytics,omitzero"`
// Advanced settings
Advanced AdvancedConfig `yaml:"advanced,omitempty"`
Advanced AdvancedConfig `yaml:"advanced,omitempty" json:"advanced,omitzero"`
// A/B testing experiments
Experiments ExperimentConfig `yaml:"experiments,omitempty"`
Experiments ExperimentConfig `yaml:"experiments,omitempty" json:"experiments,omitzero"`
// Multi-language support
I18n I18nConfig `yaml:"i18n,omitempty"`
I18n I18nConfig `yaml:"i18n,omitempty" json:"i18n,omitzero"`
}
// BrandConfig represents brand/identity settings
type BrandConfig struct {
Name string `yaml:"name,omitempty"`
LogoURL string `yaml:"logo_url,omitempty"`
UploadedLogo string `yaml:"uploaded_logo,omitempty"`
LogoSource string `yaml:"logo_source,omitempty"` // "url" (default), "repo", or "org" — selects avatar source
Tagline string `yaml:"tagline,omitempty"`
FaviconURL string `yaml:"favicon_url,omitempty"`
UploadedFavicon string `yaml:"uploaded_favicon,omitempty"`
Name string `yaml:"name,omitempty" json:"name,omitempty"`
LogoURL string `yaml:"logo_url,omitempty" json:"logo_url,omitempty"`
UploadedLogo string `yaml:"uploaded_logo,omitempty" json:"uploaded_logo,omitempty"`
LogoSource string `yaml:"logo_source,omitempty" json:"logo_source,omitempty"` // "url" (default), "repo", or "org" — selects avatar source
Tagline string `yaml:"tagline,omitempty" json:"tagline,omitempty"`
FaviconURL string `yaml:"favicon_url,omitempty" json:"favicon_url,omitempty"`
UploadedFavicon string `yaml:"uploaded_favicon,omitempty" json:"uploaded_favicon,omitempty"`
}
// ResolvedLogoURL returns the uploaded logo path or the external URL.
@@ -107,14 +107,14 @@ func (b *BrandConfig) ResolvedFaviconURL() string {
// HeroConfig represents hero section settings
type HeroConfig struct {
Headline string `yaml:"headline,omitempty"`
Subheadline string `yaml:"subheadline,omitempty"`
PrimaryCTA CTAButton `yaml:"primary_cta,omitempty"`
SecondaryCTA CTAButton `yaml:"secondary_cta,omitempty"`
ImageURL string `yaml:"image_url,omitempty"`
UploadedImage string `yaml:"uploaded_image,omitempty"` // filename in repo-avatars storage
CodeExample string `yaml:"code_example,omitempty"`
VideoURL string `yaml:"video_url,omitempty"`
Headline string `yaml:"headline,omitempty" json:"headline,omitempty"`
Subheadline string `yaml:"subheadline,omitempty" json:"subheadline,omitempty"`
PrimaryCTA CTAButton `yaml:"primary_cta,omitempty" json:"primary_cta,omitzero"`
SecondaryCTA CTAButton `yaml:"secondary_cta,omitempty" json:"secondary_cta,omitzero"`
ImageURL string `yaml:"image_url,omitempty" json:"image_url,omitempty"`
UploadedImage string `yaml:"uploaded_image,omitempty" json:"uploaded_image,omitempty"` // filename in repo-avatars storage
CodeExample string `yaml:"code_example,omitempty" json:"code_example,omitempty"`
VideoURL string `yaml:"video_url,omitempty" json:"video_url,omitempty"`
}
// ResolvedImageURL returns the effective hero image URL, preferring uploaded image over URL.
@@ -127,97 +127,97 @@ func (h *HeroConfig) ResolvedImageURL() string {
// CTAButton represents a call-to-action button
type CTAButton struct {
Label string `yaml:"label,omitempty"`
URL string `yaml:"url,omitempty"`
Variant string `yaml:"variant,omitempty"` // primary, secondary, outline, text
Label string `yaml:"label,omitempty" json:"label,omitempty"`
URL string `yaml:"url,omitempty" json:"url,omitempty"`
Variant string `yaml:"variant,omitempty" json:"variant,omitempty"` // primary, secondary, outline, text
}
// StatConfig represents a single stat/metric
type StatConfig struct {
Value string `yaml:"value,omitempty"`
Label string `yaml:"label,omitempty"`
Value string `yaml:"value,omitempty" json:"value,omitempty"`
Label string `yaml:"label,omitempty" json:"label,omitempty"`
}
// ValuePropConfig represents a value proposition
type ValuePropConfig struct {
Title string `yaml:"title,omitempty"`
Description string `yaml:"description,omitempty"`
Icon string `yaml:"icon,omitempty"`
Title string `yaml:"title,omitempty" json:"title,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Icon string `yaml:"icon,omitempty" json:"icon,omitempty"`
}
// FeatureConfig represents a single feature item
type FeatureConfig struct {
Title string `yaml:"title,omitempty"`
Description string `yaml:"description,omitempty"`
Icon string `yaml:"icon,omitempty"`
ImageURL string `yaml:"image_url,omitempty"`
Title string `yaml:"title,omitempty" json:"title,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Icon string `yaml:"icon,omitempty" json:"icon,omitempty"`
ImageURL string `yaml:"image_url,omitempty" json:"image_url,omitempty"`
}
// SocialProofConfig represents social proof section
type SocialProofConfig struct {
Logos []string `yaml:"logos,omitempty"`
Testimonial TestimonialConfig `yaml:"testimonial,omitempty"`
Testimonials []TestimonialConfig `yaml:"testimonials,omitempty"`
Logos []string `yaml:"logos,omitempty" json:"logos,omitempty"`
Testimonial TestimonialConfig `yaml:"testimonial,omitempty" json:"testimonial,omitzero"`
Testimonials []TestimonialConfig `yaml:"testimonials,omitempty" json:"testimonials,omitempty"`
}
// TestimonialConfig represents a testimonial
type TestimonialConfig struct {
Quote string `yaml:"quote,omitempty"`
Author string `yaml:"author,omitempty"`
Role string `yaml:"role,omitempty"`
Avatar string `yaml:"avatar,omitempty"`
Quote string `yaml:"quote,omitempty" json:"quote,omitempty"`
Author string `yaml:"author,omitempty" json:"author,omitempty"`
Role string `yaml:"role,omitempty" json:"role,omitempty"`
Avatar string `yaml:"avatar,omitempty" json:"avatar,omitempty"`
}
// PricingConfig represents pricing section
type PricingConfig struct {
Headline string `yaml:"headline,omitempty"`
Subheadline string `yaml:"subheadline,omitempty"`
Plans []PricingPlanConfig `yaml:"plans,omitempty"`
Headline string `yaml:"headline,omitempty" json:"headline,omitempty"`
Subheadline string `yaml:"subheadline,omitempty" json:"subheadline,omitempty"`
Plans []PricingPlanConfig `yaml:"plans,omitempty" json:"plans,omitempty"`
}
// PricingPlanConfig represents a pricing plan
type PricingPlanConfig struct {
Name string `yaml:"name,omitempty"`
Price string `yaml:"price,omitempty"`
Period string `yaml:"period,omitempty"`
Features []string `yaml:"features,omitempty"`
CTA string `yaml:"cta,omitempty"`
Featured bool `yaml:"featured,omitempty"`
Name string `yaml:"name,omitempty" json:"name,omitempty"`
Price string `yaml:"price,omitempty" json:"price,omitempty"`
Period string `yaml:"period,omitempty" json:"period,omitempty"`
Features []string `yaml:"features,omitempty" json:"features,omitempty"`
CTA string `yaml:"cta,omitempty" json:"cta,omitempty"`
Featured bool `yaml:"featured,omitempty" json:"featured,omitempty"`
}
// CTASectionConfig represents the final CTA section
type CTASectionConfig struct {
Headline string `yaml:"headline,omitempty"`
Subheadline string `yaml:"subheadline,omitempty"`
Button CTAButton `yaml:"button,omitempty"`
Headline string `yaml:"headline,omitempty" json:"headline,omitempty"`
Subheadline string `yaml:"subheadline,omitempty" json:"subheadline,omitempty"`
Button CTAButton `yaml:"button,omitempty" json:"button,omitzero"`
}
// BlogSectionConfig represents blog section settings on the landing page
type BlogSectionConfig struct {
Enabled bool `yaml:"enabled,omitempty"`
Headline string `yaml:"headline,omitempty"`
Subheadline string `yaml:"subheadline,omitempty"`
MaxPosts int `yaml:"max_posts,omitempty"` // default 3
ShowExcerpt bool `yaml:"show_excerpt,omitempty"` // show subtitle as excerpt
CTAButton CTAButton `yaml:"cta_button,omitempty"` // "View All Posts" link
Enabled bool `yaml:"enabled,omitempty" json:"enabled,omitempty"`
Headline string `yaml:"headline,omitempty" json:"headline,omitempty"`
Subheadline string `yaml:"subheadline,omitempty" json:"subheadline,omitempty"`
MaxPosts int `yaml:"max_posts,omitempty" json:"max_posts,omitempty"` // default 3
ShowExcerpt bool `yaml:"show_excerpt,omitempty" json:"show_excerpt,omitempty"` // show subtitle as excerpt
CTAButton CTAButton `yaml:"cta_button,omitempty" json:"cta_button,omitzero"` // "View All Posts" link
}
// GallerySectionConfig represents gallery section settings on the landing page
type GallerySectionConfig struct {
Enabled bool `yaml:"enabled,omitempty"`
Headline string `yaml:"headline,omitempty"`
Subheadline string `yaml:"subheadline,omitempty"`
MaxImages int `yaml:"max_images,omitempty"` // default 6
Columns int `yaml:"columns,omitempty"` // grid columns, default 3
Enabled bool `yaml:"enabled,omitempty" json:"enabled,omitempty"`
Headline string `yaml:"headline,omitempty" json:"headline,omitempty"`
Subheadline string `yaml:"subheadline,omitempty" json:"subheadline,omitempty"`
MaxImages int `yaml:"max_images,omitempty" json:"max_images,omitempty"` // default 6
Columns int `yaml:"columns,omitempty" json:"columns,omitempty"` // grid columns, default 3
}
// ComparisonSectionConfig represents a feature comparison matrix section
type ComparisonSectionConfig struct {
Enabled bool `yaml:"enabled,omitempty"`
Headline string `yaml:"headline,omitempty"`
Subheadline string `yaml:"subheadline,omitempty"`
Columns []ComparisonColumnConfig `yaml:"columns,omitempty"`
Groups []ComparisonGroupConfig `yaml:"groups,omitempty"`
Enabled bool `yaml:"enabled,omitempty" json:"enabled,omitempty"`
Headline string `yaml:"headline,omitempty" json:"headline,omitempty"`
Subheadline string `yaml:"subheadline,omitempty" json:"subheadline,omitempty"`
Columns []ComparisonColumnConfig `yaml:"columns,omitempty" json:"columns,omitempty"`
Groups []ComparisonGroupConfig `yaml:"groups,omitempty" json:"groups,omitempty"`
}
// HasData returns true if the comparison section has columns and at least one feature
@@ -235,88 +235,99 @@ func (c *ComparisonSectionConfig) HasData() bool {
// ComparisonColumnConfig represents a column header in the comparison table
type ComparisonColumnConfig struct {
Name string `yaml:"name,omitempty"`
Highlight bool `yaml:"highlight,omitempty"`
Name string `yaml:"name,omitempty" json:"name,omitempty"`
Highlight bool `yaml:"highlight,omitempty" json:"highlight,omitempty"`
}
// ComparisonGroupConfig represents a group of features in the comparison table
type ComparisonGroupConfig struct {
Name string `yaml:"name,omitempty"`
Features []ComparisonFeatureConfig `yaml:"features,omitempty"`
Name string `yaml:"name,omitempty" json:"name,omitempty"`
Features []ComparisonFeatureConfig `yaml:"features,omitempty" json:"features,omitempty"`
}
// ComparisonFeatureConfig represents a single feature row in the comparison table
type ComparisonFeatureConfig struct {
Name string `yaml:"name,omitempty"`
Values []string `yaml:"values,omitempty"` // "true"/"false" for check/x, anything else displayed as text
Name string `yaml:"name,omitempty" json:"name,omitempty"`
Values []string `yaml:"values,omitempty" json:"values,omitempty"` // "true"/"false" for check/x, anything else displayed as text
}
// NavigationConfig controls which built-in navigation links appear in the header and footer
type NavigationConfig struct {
ShowDocs bool `yaml:"show_docs,omitempty"`
ShowAPI bool `yaml:"show_api,omitempty"`
ShowRepository bool `yaml:"show_repository,omitempty"`
ShowReleases bool `yaml:"show_releases,omitempty"`
ShowIssues bool `yaml:"show_issues,omitempty"`
ShowDocs bool `yaml:"show_docs,omitempty" json:"show_docs,omitempty"`
ShowAPI bool `yaml:"show_api,omitempty" json:"show_api,omitempty"`
ShowRepository bool `yaml:"show_repository,omitempty" json:"show_repository,omitempty"`
ShowReleases bool `yaml:"show_releases,omitempty" json:"show_releases,omitempty"`
ShowIssues bool `yaml:"show_issues,omitempty" json:"show_issues,omitempty"`
// Translatable labels for nav items and section headers (defaults to English)
LabelValueProps string `yaml:"label_value_props,omitempty" json:"label_value_props,omitempty"`
LabelFeatures string `yaml:"label_features,omitempty" json:"label_features,omitempty"`
LabelPricing string `yaml:"label_pricing,omitempty" json:"label_pricing,omitempty"`
LabelBlog string `yaml:"label_blog,omitempty" json:"label_blog,omitempty"`
LabelGallery string `yaml:"label_gallery,omitempty" json:"label_gallery,omitempty"`
LabelCompare string `yaml:"label_compare,omitempty" json:"label_compare,omitempty"`
LabelDocs string `yaml:"label_docs,omitempty" json:"label_docs,omitempty"`
LabelReleases string `yaml:"label_releases,omitempty" json:"label_releases,omitempty"`
LabelAPI string `yaml:"label_api,omitempty" json:"label_api,omitempty"`
LabelIssues string `yaml:"label_issues,omitempty" json:"label_issues,omitempty"`
}
// FooterConfig represents footer settings
type FooterConfig struct {
Links []FooterLink `yaml:"links,omitempty"`
Social []SocialLink `yaml:"social,omitempty"`
Copyright string `yaml:"copyright,omitempty"`
ShowPoweredBy bool `yaml:"show_powered_by,omitempty"`
Links []FooterLink `yaml:"links,omitempty" json:"links,omitempty"`
Social []SocialLink `yaml:"social,omitempty" json:"social,omitempty"`
Copyright string `yaml:"copyright,omitempty" json:"copyright,omitempty"`
ShowPoweredBy bool `yaml:"show_powered_by,omitempty" json:"show_powered_by,omitempty"`
}
// FooterLink represents a single footer link
type FooterLink struct {
Label string `yaml:"label,omitempty"`
URL string `yaml:"url,omitempty"`
Label string `yaml:"label,omitempty" json:"label,omitempty"`
URL string `yaml:"url,omitempty" json:"url,omitempty"`
}
// SocialLink represents a social media link
type SocialLink struct {
Platform string `yaml:"platform,omitempty"` // bluesky, discord, facebook, github, instagram, linkedin, mastodon, reddit, rss, substack, threads, tiktok, twitch, twitter, youtube
URL string `yaml:"url,omitempty"`
Platform string `yaml:"platform,omitempty" json:"platform,omitempty"` // bluesky, discord, facebook, github, instagram, linkedin, mastodon, reddit, rss, substack, threads, tiktok, twitch, twitter, youtube
URL string `yaml:"url,omitempty" json:"url,omitempty"`
}
// ThemeConfig represents theme customization
type ThemeConfig struct {
PrimaryColor string `yaml:"primary_color,omitempty"`
AccentColor string `yaml:"accent_color,omitempty"`
Mode string `yaml:"mode,omitempty"` // light, dark, auto
PrimaryColor string `yaml:"primary_color,omitempty" json:"primary_color,omitempty"`
AccentColor string `yaml:"accent_color,omitempty" json:"accent_color,omitempty"`
Mode string `yaml:"mode,omitempty" json:"mode,omitempty"` // light, dark, auto
}
// SEOConfig represents SEO and social sharing settings
type SEOConfig struct {
Title string `yaml:"title,omitempty"`
Description string `yaml:"description,omitempty"`
Keywords []string `yaml:"keywords,omitempty"`
OGImage string `yaml:"og_image,omitempty"`
UseMediaKitOG bool `yaml:"use_media_kit_og,omitempty"`
TwitterCard string `yaml:"twitter_card,omitempty"`
TwitterSite string `yaml:"twitter_site,omitempty"`
Title string `yaml:"title,omitempty" json:"title,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Keywords []string `yaml:"keywords,omitempty" json:"keywords,omitempty"`
OGImage string `yaml:"og_image,omitempty" json:"og_image,omitempty"`
UseMediaKitOG bool `yaml:"use_media_kit_og,omitempty" json:"use_media_kit_og,omitempty"`
TwitterCard string `yaml:"twitter_card,omitempty" json:"twitter_card,omitempty"`
TwitterSite string `yaml:"twitter_site,omitempty" json:"twitter_site,omitempty"`
}
// AnalyticsConfig represents analytics settings
type AnalyticsConfig struct {
Plausible string `yaml:"plausible,omitempty"`
Umami UmamiConfig `yaml:"umami,omitempty"`
GoogleAnalytics string `yaml:"google_analytics,omitempty"`
Plausible string `yaml:"plausible,omitempty" json:"plausible,omitempty"`
Umami UmamiConfig `yaml:"umami,omitempty" json:"umami,omitzero"`
GoogleAnalytics string `yaml:"google_analytics,omitempty" json:"google_analytics,omitempty"`
}
// UmamiConfig represents Umami analytics settings
type UmamiConfig struct {
WebsiteID string `yaml:"website_id,omitempty"`
URL string `yaml:"url,omitempty"`
WebsiteID string `yaml:"website_id,omitempty" json:"website_id,omitempty"`
URL string `yaml:"url,omitempty" json:"url,omitempty"`
}
// ExperimentConfig represents A/B testing experiment settings
type ExperimentConfig struct {
Enabled bool `yaml:"enabled,omitempty"`
AutoOptimize bool `yaml:"auto_optimize,omitempty"`
MinImpressions int `yaml:"min_impressions,omitempty"`
ApprovalRequired bool `yaml:"approval_required,omitempty"`
Enabled bool `yaml:"enabled,omitempty" json:"enabled,omitempty"`
AutoOptimize bool `yaml:"auto_optimize,omitempty" json:"auto_optimize,omitempty"`
MinImpressions int `yaml:"min_impressions,omitempty" json:"min_impressions,omitempty"`
ApprovalRequired bool `yaml:"approval_required,omitempty" json:"approval_required,omitempty"`
}
// I18nConfig represents multi-language settings for the landing page
@@ -325,36 +336,37 @@ type I18nConfig struct {
Languages []string `yaml:"languages,omitempty" json:"languages,omitempty"`
}
// LanguageDisplayNames returns a map of language codes to display names
// LanguageDisplayNames returns a map of language codes to native display names
func LanguageDisplayNames() map[string]string {
return map[string]string{
"en": "English",
"es": "Espanol",
"es": "Español",
"de": "Deutsch",
"fr": "Francais",
"ja": "Japanese",
"zh": "Chinese",
"pt": "Portugues",
"ru": "Russian",
"ko": "Korean",
"fr": "Français",
"ja": "日本語",
"zh": "中文",
"pt": "Português",
"ru": "Русский",
"ko": "한국어",
"it": "Italiano",
"hi": "Hindi",
"ar": "Arabic",
"hi": "हिन्दी",
"ar": "العربية",
"nl": "Nederlands",
"pl": "Polski",
"tr": "Turkish",
"tr": "Türkçe",
}
}
// AdvancedConfig represents advanced settings
type AdvancedConfig struct {
CustomCSS string `yaml:"custom_css,omitempty"`
CustomHead string `yaml:"custom_head,omitempty"`
Redirects map[string]string `yaml:"redirects,omitempty"`
PublicReleases bool `yaml:"public_releases,omitempty"`
HideMobileReleases bool `yaml:"hide_mobile_releases,omitempty"`
GooglePlayID string `yaml:"google_play_id,omitempty"`
AppStoreID string `yaml:"app_store_id,omitempty"`
CustomCSS string `yaml:"custom_css,omitempty" json:"custom_css,omitempty"`
CustomHead string `yaml:"custom_head,omitempty" json:"custom_head,omitempty"`
Redirects map[string]string `yaml:"redirects,omitempty" json:"redirects,omitempty"`
StaticRoutes []string `yaml:"static_routes,omitempty" json:"static_routes,omitempty"`
PublicReleases bool `yaml:"public_releases,omitempty" json:"public_releases,omitempty"`
HideMobileReleases bool `yaml:"hide_mobile_releases,omitempty" json:"hide_mobile_releases,omitempty"`
GooglePlayID string `yaml:"google_play_id,omitempty" json:"google_play_id,omitempty"`
AppStoreID string `yaml:"app_store_id,omitempty" json:"app_store_id,omitempty"`
}
// ParseLandingConfig parses a landing.yaml file content
@@ -458,3 +470,65 @@ func TemplateDisplayNames() map[string]string {
"architecture-deep-dive": "Architecture Deep Dive",
}
}
// TemplateDefaultLabels returns the template-specific default section labels.
// These are the creative names each template uses for its sections.
func TemplateDefaultLabels(template string) NavigationConfig {
switch template {
case "architecture-deep-dive":
return NavigationConfig{
LabelValueProps: "Systems Analysis",
LabelFeatures: "Technical Specifications",
LabelPricing: "Resource Allocation",
LabelBlog: "Dispatches",
LabelGallery: "Visual Index",
LabelCompare: "Compare",
}
case "bold-marketing":
return NavigationConfig{
LabelValueProps: "Why choose this",
LabelFeatures: "Capabilities",
LabelPricing: "Investment",
LabelBlog: "Blog",
LabelGallery: "Gallery",
LabelCompare: "Compare",
}
case "minimalist-docs":
return NavigationConfig{
LabelValueProps: "Why choose this",
LabelFeatures: "Capabilities",
LabelPricing: "Investment",
LabelBlog: "Blog",
LabelGallery: "Gallery",
LabelCompare: "Compare",
}
case "open-source-hero":
return NavigationConfig{
LabelValueProps: "Why choose us",
LabelFeatures: "Capabilities",
LabelPricing: "Pricing",
LabelBlog: "Blog",
LabelGallery: "Gallery",
LabelCompare: "Compare",
}
case "saas-conversion":
return NavigationConfig{
LabelValueProps: "Why",
LabelFeatures: "Features",
LabelPricing: "Pricing",
LabelBlog: "Blog",
LabelGallery: "Gallery",
LabelCompare: "Compare",
}
default:
// developer-tool, documentation-first, visual-showcase, cli-terminal
return NavigationConfig{
LabelValueProps: "Why choose us",
LabelFeatures: "Capabilities",
LabelPricing: "Pricing",
LabelBlog: "Blog",
LabelGallery: "Gallery",
LabelCompare: "Compare",
}
}
}

View File

@@ -47,3 +47,231 @@ type PagesInfo struct {
Config *PagesConfig `json:"config"`
Domains []*PagesDomain `json:"domains,omitempty"`
}
// ---------------------------------------------------------------------------
// v2 Landing Page Configuration API structs
// ---------------------------------------------------------------------------
// UpdatePagesConfigOption represents the full landing page config update.
// For PATCH, only non-nil fields are applied. For PUT, all fields replace existing config.
type UpdatePagesConfigOption struct {
Enabled *bool `json:"enabled"`
PublicLanding *bool `json:"public_landing"`
Template *string `json:"template"`
Brand *UpdatePagesBrandOption `json:"brand"`
Hero *UpdatePagesHeroOption `json:"hero"`
Stats *[]PagesStatOption `json:"stats"`
ValueProps *[]PagesValuePropOption `json:"value_props"`
Features *[]PagesFeatureOption `json:"features"`
SocialProof *UpdatePagesSocialOption `json:"social_proof"`
Pricing *UpdatePagesPricingOption `json:"pricing"`
CTASection *UpdatePagesCTAOption `json:"cta_section"`
Blog *UpdatePagesBlogOption `json:"blog"`
Gallery *UpdatePagesGalleryOption `json:"gallery"`
Comparison *UpdatePagesComparisonOption `json:"comparison"`
Navigation *UpdatePagesNavOption `json:"navigation"`
Footer *UpdatePagesFooterOption `json:"footer"`
Theme *UpdatePagesThemeOption `json:"theme"`
SEO *UpdatePagesSEOOption `json:"seo"`
Advanced *UpdatePagesAdvancedOption `json:"advanced"`
}
// UpdatePagesBrandOption represents brand section update
type UpdatePagesBrandOption struct {
Name *string `json:"name"`
LogoURL *string `json:"logo_url"`
Tagline *string `json:"tagline"`
FaviconURL *string `json:"favicon_url"`
}
// UpdatePagesHeroOption represents hero section update
type UpdatePagesHeroOption struct {
Headline *string `json:"headline"`
Subheadline *string `json:"subheadline"`
ImageURL *string `json:"image_url"`
VideoURL *string `json:"video_url"`
CodeExample *string `json:"code_example"`
PrimaryCTA *PagesCTAButtonOption `json:"primary_cta"`
SecondaryCTA *PagesCTAButtonOption `json:"secondary_cta"`
}
// PagesCTAButtonOption represents a CTA button
type PagesCTAButtonOption struct {
Label *string `json:"label"`
URL *string `json:"url"`
Variant *string `json:"variant"`
}
// PagesStatOption represents a stat item
type PagesStatOption struct {
Value string `json:"value"`
Label string `json:"label"`
}
// PagesValuePropOption represents a value proposition item
type PagesValuePropOption struct {
Title string `json:"title"`
Description string `json:"description"`
Icon string `json:"icon"`
}
// PagesFeatureOption represents a feature item
type PagesFeatureOption struct {
Title string `json:"title"`
Description string `json:"description"`
Icon string `json:"icon"`
ImageURL string `json:"image_url"`
}
// UpdatePagesSocialOption represents social proof section update
type UpdatePagesSocialOption struct {
Logos *[]string `json:"logos"`
Testimonials *[]PagesTestimonialOption `json:"testimonials"`
}
// PagesTestimonialOption represents a testimonial item
type PagesTestimonialOption struct {
Quote string `json:"quote"`
Author string `json:"author"`
Role string `json:"role"`
Avatar string `json:"avatar"`
}
// UpdatePagesPricingOption represents pricing section update
type UpdatePagesPricingOption struct {
Headline *string `json:"headline"`
Subheadline *string `json:"subheadline"`
Plans *[]PagesPricingPlanOption `json:"plans"`
}
// PagesPricingPlanOption represents a pricing plan item
type PagesPricingPlanOption struct {
Name string `json:"name"`
Price string `json:"price"`
Period string `json:"period"`
Features []string `json:"features"`
CTA string `json:"cta"`
Featured bool `json:"featured"`
}
// UpdatePagesCTAOption represents CTA section update
type UpdatePagesCTAOption struct {
Headline *string `json:"headline"`
Subheadline *string `json:"subheadline"`
Button *PagesCTAButtonOption `json:"button"`
}
// UpdatePagesBlogOption represents blog section update
type UpdatePagesBlogOption struct {
Enabled *bool `json:"enabled"`
Headline *string `json:"headline"`
Subheadline *string `json:"subheadline"`
MaxPosts *int `json:"max_posts"`
}
// UpdatePagesGalleryOption represents gallery section update
type UpdatePagesGalleryOption struct {
Enabled *bool `json:"enabled"`
Headline *string `json:"headline"`
Subheadline *string `json:"subheadline"`
MaxImages *int `json:"max_images"`
Columns *int `json:"columns"`
}
// UpdatePagesComparisonOption represents comparison section update
type UpdatePagesComparisonOption struct {
Enabled *bool `json:"enabled"`
Headline *string `json:"headline"`
Subheadline *string `json:"subheadline"`
Columns *[]PagesComparisonColumnOption `json:"columns"`
Groups *[]PagesComparisonGroupOption `json:"groups"`
}
// PagesComparisonColumnOption represents a comparison column
type PagesComparisonColumnOption struct {
Name string `json:"name"`
Highlight bool `json:"highlight"`
}
// PagesComparisonGroupOption represents a comparison group
type PagesComparisonGroupOption struct {
Name string `json:"name"`
Features []PagesComparisonFeatureOption `json:"features"`
}
// PagesComparisonFeatureOption represents a comparison feature row
type PagesComparisonFeatureOption struct {
Name string `json:"name"`
Values []string `json:"values"`
}
// UpdatePagesNavOption represents navigation section update
type UpdatePagesNavOption struct {
ShowDocs *bool `json:"show_docs"`
ShowAPI *bool `json:"show_api"`
ShowRepository *bool `json:"show_repository"`
ShowReleases *bool `json:"show_releases"`
ShowIssues *bool `json:"show_issues"`
}
// UpdatePagesFooterOption represents footer section update
type UpdatePagesFooterOption struct {
Copyright *string `json:"copyright"`
ShowPoweredBy *bool `json:"show_powered_by"`
Links *[]PagesFooterLinkOption `json:"links"`
Social *[]PagesSocialLinkOption `json:"social"`
CTASection *UpdatePagesCTAOption `json:"cta_section"`
}
// PagesFooterLinkOption represents a footer link
type PagesFooterLinkOption struct {
Label string `json:"label"`
URL string `json:"url"`
}
// PagesSocialLinkOption represents a social link
type PagesSocialLinkOption struct {
Platform string `json:"platform"`
URL string `json:"url"`
}
// UpdatePagesThemeOption represents theme section update
type UpdatePagesThemeOption struct {
PrimaryColor *string `json:"primary_color"`
AccentColor *string `json:"accent_color"`
Mode *string `json:"mode"`
}
// UpdatePagesSEOOption represents SEO section update
type UpdatePagesSEOOption struct {
Title *string `json:"title"`
Description *string `json:"description"`
Keywords *[]string `json:"keywords"`
OGImage *string `json:"og_image"`
UseMediaKitOG *bool `json:"use_media_kit_og"`
TwitterCard *string `json:"twitter_card"`
TwitterSite *string `json:"twitter_site"`
}
// UpdatePagesAdvancedOption represents advanced settings update
type UpdatePagesAdvancedOption struct {
CustomCSS *string `json:"custom_css"`
CustomHead *string `json:"custom_head"`
StaticRoutes *[]string `json:"static_routes"`
PublicReleases *bool `json:"public_releases"`
HideMobileReleases *bool `json:"hide_mobile_releases"`
GooglePlayID *string `json:"google_play_id"`
AppStoreID *string `json:"app_store_id"`
}
// UpdatePagesContentOption bundles content-page sections for PUT /config/content
type UpdatePagesContentOption struct {
Blog *UpdatePagesBlogOption `json:"blog"`
Gallery *UpdatePagesGalleryOption `json:"gallery"`
ComparisonEnabled *bool `json:"comparison_enabled"`
Stats *[]PagesStatOption `json:"stats"`
ValueProps *[]PagesValuePropOption `json:"value_props"`
Features *[]PagesFeatureOption `json:"features"`
Navigation *UpdatePagesNavOption `json:"navigation"`
Advanced *UpdatePagesAdvancedOption `json:"advanced"`
}

View File

@@ -4042,6 +4042,9 @@
"repo.settings.pages.translations": "P\u0159eklady",
"repo.settings.pages.ai_translate": "AI p\u0159eklad",
"repo.settings.pages.ai_translate_success": "P\u0159eklad byl \u00fasp\u011b\u0161n\u011b vygenerov\u00e1n AI. Zkontrolujte a upravte dle pot\u0159eby.",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "Smazat",
"repo.settings.pages.save_translation": "Ulo\u017eit p\u0159eklad",
"repo.settings.pages.translation_saved": "P\u0159eklad \u00fasp\u011b\u0161n\u011b ulo\u017een.",
@@ -4054,6 +4057,43 @@
"repo.settings.pages.trans_cta_headline": "Nadpis sekce CTA",
"repo.settings.pages.trans_cta_subheadline": "Podnadpis sekce CTA",
"repo.settings.pages.trans_cta_button": "\u0160t\u00edtek tla\u010d\u00edtka CTA",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault Plugin Not Installed",
"repo.vault.plugin_not_installed_desc": "The Vault plugin is not installed on this server. Contact your administrator to enable secrets management.",
"repo.vault.secret_limit_reached": "Secret limit reached. Your current tier allows %d secrets per repository. Upgrade to Pro for unlimited secrets.",
@@ -4353,4 +4393,4 @@
"actions.runners.waiting_jobs": "Čekající úlohy",
"actions.runners.back_to_runners": "Zpět na runnery",
"actions.runners.no_waiting_jobs": "Žádné úlohy nečekají na tento label"
}
}

View File

@@ -4248,6 +4248,9 @@
"repo.settings.pages.translations": "\u00dcbersetzungen",
"repo.settings.pages.ai_translate": "KI-\u00dcbersetzung",
"repo.settings.pages.ai_translate_success": "\u00dcbersetzung wurde erfolgreich von der KI generiert. \u00dcberpr\u00fcfen und bearbeiten Sie sie nach Bedarf.",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "L\u00f6schen",
"repo.settings.pages.save_translation": "\u00dcbersetzung speichern",
"repo.settings.pages.translation_saved": "\u00dcbersetzung erfolgreich gespeichert.",
@@ -4260,6 +4263,43 @@
"repo.settings.pages.trans_cta_headline": "CTA-Abschnitt \u00dcberschrift",
"repo.settings.pages.trans_cta_subheadline": "CTA-Abschnitt Unter\u00fcberschrift",
"repo.settings.pages.trans_cta_button": "CTA-Button-Beschriftung",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault-Plugin nicht installiert",
"repo.vault.plugin_not_installed_desc": "Das Vault-Plugin ist auf diesem Server nicht verfügbar",
"repo.vault.secret_limit_reached": "Geheimnis-Limit erreicht",
@@ -4429,4 +4469,4 @@
"actions.runners.waiting_jobs": "Wartende Jobs",
"actions.runners.back_to_runners": "Zurück zu Runners",
"actions.runners.no_waiting_jobs": "Keine Jobs warten auf dieses Label"
}
}

View File

@@ -3735,6 +3735,9 @@
"repo.settings.pages.translations": "Μεταφράσεις",
"repo.settings.pages.ai_translate": "Μετάφραση AI",
"repo.settings.pages.ai_translate_success": "Η μετάφραση δημιουργήθηκε επιτυχώς από το AI. Ελέγξτε και επεξεργαστείτε όπως χρειάζεται.",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "Διαγραφή",
"repo.settings.pages.save_translation": "Αποθήκευση μετάφρασης",
"repo.settings.pages.translation_saved": "Η μετάφραση αποθηκεύτηκε επιτυχώς.",
@@ -3747,6 +3750,43 @@
"repo.settings.pages.trans_cta_headline": "Τίτλος ενότητας CTA",
"repo.settings.pages.trans_cta_subheadline": "Υπότιτλος ενότητας CTA",
"repo.settings.pages.trans_cta_button": "Ετικέτα κουμπιού CTA",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault Plugin Not Installed",
"repo.vault.plugin_not_installed_desc": "The Vault plugin is not installed on this server. Contact your administrator to enable secrets management.",
"repo.vault.secret_limit_reached": "Secret limit reached. Your current tier allows %d secrets per repository. Upgrade to Pro for unlimited secrets.",
@@ -4046,4 +4086,4 @@
"actions.runners.waiting_jobs": "Αναμονή εργασιών",
"actions.runners.back_to_runners": "Πίσω στους Runners",
"actions.runners.no_waiting_jobs": "Δεν υπάρχουν εργασίες σε αναμονή για αυτή την ετικέτα"
}
}

View File

@@ -4572,6 +4572,7 @@
"repo.settings.pages.ai_generate": "AI Content Generator",
"repo.settings.pages.ai_generate_desc": "Automatically generate landing page content (headline, features, stats, CTAs) from your repository's README and metadata using AI.",
"repo.settings.pages.ai_generate_button": "Generate Content with AI",
"repo.settings.pages.ai_generating": "Generating content with AI\u2026 This may take a moment.",
"repo.settings.pages.ai_generate_success": "Landing page content has been generated successfully. Review and customize it in the other tabs.",
"repo.settings.pages.ai_generate_failed": "Failed to generate content with AI. Please try again later or configure the content manually.",
"repo.settings.pages.languages": "Languages",
@@ -4584,6 +4585,10 @@
"repo.settings.pages.translations": "Translations",
"repo.settings.pages.ai_translate": "AI Translate",
"repo.settings.pages.ai_translate_success": "Translation has been generated successfully by AI. Review and edit as needed.",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.ai_translating": "Translating with AI\u2026 This may take a moment.",
"repo.settings.pages.delete_translation": "Delete",
"repo.settings.pages.save_translation": "Save Translation",
"repo.settings.pages.translation_saved": "Translation saved successfully.",
@@ -4596,6 +4601,57 @@
"repo.settings.pages.trans_cta_headline": "CTA Section Headline",
"repo.settings.pages.trans_cta_subheadline": "CTA Section Subheadline",
"repo.settings.pages.trans_cta_button": "CTA Button Label",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_section_navigation": "Navigation Labels",
"repo.settings.pages.trans_nav_label": "Label",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.settings.pages.advanced": "Advanced",
"repo.settings.pages.static_routes": "Static Routes",
"repo.settings.pages.static_routes_desc": "Serve files directly from your repository at specific URL paths instead of rendering the landing page. Use exact paths (/badge.svg) or glob patterns (/schema/*).",
"repo.settings.pages.add_route": "Add Route",
"repo.settings.pages.redirects": "Redirects",
"repo.settings.pages.redirects_desc": "Redirect specific paths to other URLs. The source path must start with /.",
"repo.settings.pages.add_redirect": "Add Redirect",
"repo.settings.pages.custom_code": "Custom Code",
"repo.settings.pages.custom_css": "Custom CSS",
"repo.settings.pages.custom_head": "Custom Head HTML",
"repo.settings.pages.app_stores": "App Stores",
"repo.settings.pages.hide_mobile_releases": "Hide Mobile Releases",
"repo.vault": "Vault",
"repo.vault.secrets": "Secrets",
"repo.vault.new_secret": "New Secret",

View File

@@ -3840,6 +3840,9 @@
"repo.settings.pages.translations": "Traducciones",
"repo.settings.pages.ai_translate": "Traducir con IA",
"repo.settings.pages.ai_translate_success": "La traducci\u00f3n se ha generado correctamente con IA. Rev\u00edsala y ed\u00edtala seg\u00fan sea necesario.",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "Eliminar",
"repo.settings.pages.save_translation": "Guardar traducci\u00f3n",
"repo.settings.pages.translation_saved": "Traducci\u00f3n guardada correctamente.",
@@ -3852,6 +3855,43 @@
"repo.settings.pages.trans_cta_headline": "T\u00edtulo de la secci\u00f3n CTA",
"repo.settings.pages.trans_cta_subheadline": "Subt\u00edtulo de la secci\u00f3n CTA",
"repo.settings.pages.trans_cta_button": "Etiqueta del bot\u00f3n CTA",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault Plugin Not Installed",
"repo.vault.plugin_not_installed_desc": "The Vault plugin is not installed on this server. Contact your administrator to enable secrets management.",
"repo.vault.secret_limit_reached": "Secret limit reached. Your current tier allows %d secrets per repository. Upgrade to Pro for unlimited secrets.",
@@ -4021,4 +4061,4 @@
"actions.runners.waiting_jobs": "Trabajos en espera",
"actions.runners.back_to_runners": "Volver a Runners",
"actions.runners.no_waiting_jobs": "No hay trabajos esperando para esta etiqueta"
}
}

View File

@@ -2935,6 +2935,9 @@
"repo.settings.pages.translations": "ترجمه‌ها",
"repo.settings.pages.ai_translate": "ترجمه هوش مصنوعی",
"repo.settings.pages.ai_translate_success": "ترجمه با موفقیت توسط هوش مصنوعی تولید شد. در صورت نیاز بررسی و ویرایش کنید.",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "حذف",
"repo.settings.pages.save_translation": "ذخیره ترجمه",
"repo.settings.pages.translation_saved": "ترجمه با موفقیت ذخیره شد.",
@@ -2947,6 +2950,43 @@
"repo.settings.pages.trans_cta_headline": "عنوان بخش فراخوان",
"repo.settings.pages.trans_cta_subheadline": "زیرعنوان بخش فراخوان",
"repo.settings.pages.trans_cta_button": "برچسب دکمه فراخوان",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault Plugin Not Installed",
"repo.vault.plugin_not_installed_desc": "The Vault plugin is not installed on this server. Contact your administrator to enable secrets management.",
"repo.vault.secret_limit_reached": "Secret limit reached. Your current tier allows %d secrets per repository. Upgrade to Pro for unlimited secrets.",
@@ -3264,4 +3304,4 @@
"actions.runners.waiting_jobs": "کارهای در انتظار",
"actions.runners.back_to_runners": "بازگشت به اجراکننده‌ها",
"actions.runners.no_waiting_jobs": "هیچ کاری برای این برچسب در انتظار نیست"
}
}

View File

@@ -2233,6 +2233,9 @@
"repo.settings.pages.translations": "K\u00e4\u00e4nn\u00f6kset",
"repo.settings.pages.ai_translate": "AI-k\u00e4\u00e4nn\u00f6s",
"repo.settings.pages.ai_translate_success": "K\u00e4\u00e4nn\u00f6s on luotu onnistuneesti AI:n avulla. Tarkista ja muokkaa tarvittaessa.",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "Poista",
"repo.settings.pages.save_translation": "Tallenna k\u00e4\u00e4nn\u00f6s",
"repo.settings.pages.translation_saved": "K\u00e4\u00e4nn\u00f6s tallennettu onnistuneesti.",
@@ -2245,6 +2248,43 @@
"repo.settings.pages.trans_cta_headline": "CTA-osion otsikko",
"repo.settings.pages.trans_cta_subheadline": "CTA-osion alaotsikko",
"repo.settings.pages.trans_cta_button": "CTA-painikkeen teksti",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault Plugin Not Installed",
"repo.vault.plugin_not_installed_desc": "The Vault plugin is not installed on this server. Contact your administrator to enable secrets management.",
"repo.vault.secret_limit_reached": "Secret limit reached. Your current tier allows %d secrets per repository. Upgrade to Pro for unlimited secrets.",
@@ -2544,4 +2584,4 @@
"actions.runners.waiting_jobs": "Odottavat työt",
"actions.runners.back_to_runners": "Takaisin ajajiin",
"actions.runners.no_waiting_jobs": "Ei odottavia töitä tälle tunnisteelle"
}
}

View File

@@ -4186,6 +4186,9 @@
"repo.settings.pages.translations": "Traductions",
"repo.settings.pages.ai_translate": "Traduction IA",
"repo.settings.pages.ai_translate_success": "La traduction a \u00e9t\u00e9 g\u00e9n\u00e9r\u00e9e avec succ\u00e8s par l'IA. V\u00e9rifiez et modifiez si n\u00e9cessaire.",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "Supprimer",
"repo.settings.pages.save_translation": "Enregistrer la traduction",
"repo.settings.pages.translation_saved": "Traduction enregistr\u00e9e avec succ\u00e8s.",
@@ -4198,6 +4201,43 @@
"repo.settings.pages.trans_cta_headline": "Titre de la section CTA",
"repo.settings.pages.trans_cta_subheadline": "Sous-titre de la section CTA",
"repo.settings.pages.trans_cta_button": "Libell\u00e9 du bouton CTA",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault Plugin Not Installed",
"repo.vault.plugin_not_installed_desc": "The Vault plugin is not installed on this server. Contact your administrator to enable secrets management.",
"repo.vault.secret_limit_reached": "Secret limit reached. Your current tier allows %d secrets per repository. Upgrade to Pro for unlimited secrets.",
@@ -4367,4 +4407,4 @@
"actions.runners.waiting_jobs": "Tâches en attente",
"actions.runners.back_to_runners": "Retour aux runners",
"actions.runners.no_waiting_jobs": "Aucune tâche en attente pour ce label"
}
}

View File

@@ -4115,6 +4115,9 @@
"repo.settings.pages.translations": "Aistriúcháin",
"repo.settings.pages.ai_translate": "Aistriúchán AI",
"repo.settings.pages.ai_translate_success": "Ghin AI an t-aistriúchán go rathúil. Athbhreithnigh agus cuir in eagar de réir mar is gá.",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "Scrios",
"repo.settings.pages.save_translation": "Sábháil aistriúchán",
"repo.settings.pages.translation_saved": "Sábháladh an t-aistriúchán go rathúil.",
@@ -4127,6 +4130,43 @@
"repo.settings.pages.trans_cta_headline": "Ceannlíne rannóg CTA",
"repo.settings.pages.trans_cta_subheadline": "Fo-cheannlíne rannóg CTA",
"repo.settings.pages.trans_cta_button": "Lipéad cnaipe CTA",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault Plugin Not Installed",
"repo.vault.plugin_not_installed_desc": "The Vault plugin is not installed on this server. Contact your administrator to enable secrets management.",
"repo.vault.secret_limit_reached": "Secret limit reached. Your current tier allows %d secrets per repository. Upgrade to Pro for unlimited secrets.",
@@ -4413,4 +4453,4 @@
"actions.runners.waiting_jobs": "Jabanna ag Fanacht",
"actions.runners.back_to_runners": "Ar ais go Ritheoirí",
"actions.runners.no_waiting_jobs": "Níl aon jabanna ag fanacht ar an lipéad seo"
}
}

View File

@@ -4021,6 +4021,9 @@
"repo.settings.pages.translations": "\u0905\u0928\u0941\u0935\u093e\u0926",
"repo.settings.pages.ai_translate": "AI \u0905\u0928\u0941\u0935\u093e\u0926",
"repo.settings.pages.ai_translate_success": "AI \u0926\u094d\u0935\u093e\u0930\u093e \u0905\u0928\u0941\u0935\u093e\u0926 \u0938\u092b\u0932\u0924\u093e\u092a\u0942\u0930\u094d\u0935\u0915 \u091c\u0928\u0930\u0947\u091f \u0915\u093f\u092f\u093e \u0917\u092f\u093e \u0939\u0948\u0964 \u0906\u0935\u0936\u094d\u092f\u0915\u0924\u093e\u0928\u0941\u0938\u093e\u0930 \u0938\u092e\u0940\u0915\u094d\u0937\u093e \u0915\u0930\u0947\u0902 \u0914\u0930 \u0938\u0902\u092a\u093e\u0926\u093f\u0924 \u0915\u0930\u0947\u0902\u0964",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "\u0939\u091f\u093e\u090f\u0901",
"repo.settings.pages.save_translation": "\u0905\u0928\u0941\u0935\u093e\u0926 \u0938\u0939\u0947\u091c\u0947\u0902",
"repo.settings.pages.translation_saved": "\u0905\u0928\u0941\u0935\u093e\u0926 \u0938\u092b\u0932\u0924\u093e\u092a\u0942\u0930\u094d\u0935\u0915 \u0938\u0939\u0947\u091c\u093e \u0917\u092f\u093e\u0964",
@@ -4033,6 +4036,43 @@
"repo.settings.pages.trans_cta_headline": "CTA \u0905\u0928\u0941\u092d\u093e\u0917 \u0936\u0940\u0930\u094d\u0937\u0915",
"repo.settings.pages.trans_cta_subheadline": "CTA \u0905\u0928\u0941\u092d\u093e\u0917 \u0909\u092a\u0936\u0940\u0930\u094d\u0937\u0915",
"repo.settings.pages.trans_cta_button": "CTA \u092c\u091f\u0928 \u0932\u0947\u092c\u0932",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault": "वॉल्ट",
"repo.vault.secrets": "Secrets",
"repo.vault.new_secret": "New Secret",
@@ -4415,4 +4455,4 @@
"actions.runners.waiting_jobs": "प्रतीक्षारत कार्य",
"actions.runners.back_to_runners": "रनर्स पर वापस जाएं",
"actions.runners.no_waiting_jobs": "इस लेबल के लिए कोई कार्य प्रतीक्षा में नहीं है"
}
}

View File

@@ -2150,6 +2150,9 @@
"repo.settings.pages.translations": "Ford\u00edt\u00e1sok",
"repo.settings.pages.ai_translate": "AI ford\u00edt\u00e1s",
"repo.settings.pages.ai_translate_success": "A ford\u00edt\u00e1st az AI sikeresen gener\u00e1lta. Tekintse \u00e1t \u00e9s szerkessze sz\u00fcks\u00e9g szerint.",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "T\u00f6rl\u00e9s",
"repo.settings.pages.save_translation": "Ford\u00edt\u00e1s ment\u00e9se",
"repo.settings.pages.translation_saved": "Ford\u00edt\u00e1s sikeresen mentve.",
@@ -2162,6 +2165,43 @@
"repo.settings.pages.trans_cta_headline": "CTA szekci\u00f3 c\u00edmsor",
"repo.settings.pages.trans_cta_subheadline": "CTA szekci\u00f3 alc\u00edmsor",
"repo.settings.pages.trans_cta_button": "CTA gomb c\u00edmke",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault Plugin Not Installed",
"repo.vault.plugin_not_installed_desc": "The Vault plugin is not installed on this server. Contact your administrator to enable secrets management.",
"repo.vault.secret_limit_reached": "Secret limit reached. Your current tier allows %d secrets per repository. Upgrade to Pro for unlimited secrets.",
@@ -2461,4 +2501,4 @@
"actions.runners.waiting_jobs": "Várakozó feladatok",
"actions.runners.back_to_runners": "Vissza a futtatókhoz",
"actions.runners.no_waiting_jobs": "Nincsenek várakozó feladatok ehhez a címkéhez"
}
}

View File

@@ -1974,6 +1974,9 @@
"repo.settings.pages.translations": "Terjemahan",
"repo.settings.pages.ai_translate": "Terjemahan AI",
"repo.settings.pages.ai_translate_success": "Terjemahan telah berhasil dibuat oleh AI. Tinjau dan edit sesuai kebutuhan.",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "Hapus",
"repo.settings.pages.save_translation": "Simpan terjemahan",
"repo.settings.pages.translation_saved": "Terjemahan berhasil disimpan.",
@@ -1986,6 +1989,43 @@
"repo.settings.pages.trans_cta_headline": "Judul bagian CTA",
"repo.settings.pages.trans_cta_subheadline": "Subjudul bagian CTA",
"repo.settings.pages.trans_cta_button": "Label tombol CTA",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault Plugin Not Installed",
"repo.vault.plugin_not_installed_desc": "The Vault plugin is not installed on this server. Contact your administrator to enable secrets management.",
"repo.vault.secret_limit_reached": "Secret limit reached. Your current tier allows %d secrets per repository. Upgrade to Pro for unlimited secrets.",
@@ -2272,4 +2312,4 @@
"actions.runners.waiting_jobs": "Pekerjaan Menunggu",
"actions.runners.back_to_runners": "Kembali ke Runner",
"actions.runners.no_waiting_jobs": "Tidak ada pekerjaan yang menunggu untuk label ini"
}
}

View File

@@ -1862,6 +1862,9 @@
"repo.settings.pages.translations": "Þýðingar",
"repo.settings.pages.ai_translate": "AI þýðing",
"repo.settings.pages.ai_translate_success": "Þýðing hefur verið búin til af AI. Skoðaðu og breyttu eftir þörfum.",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "Eyða",
"repo.settings.pages.save_translation": "Vista þýðingu",
"repo.settings.pages.translation_saved": "Þýðing vistuð.",
@@ -1874,6 +1877,43 @@
"repo.settings.pages.trans_cta_headline": "Fyrirsögn CTA hluta",
"repo.settings.pages.trans_cta_subheadline": "Undirfyrirsögn CTA hluta",
"repo.settings.pages.trans_cta_button": "Merki CTA hnapps",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault Plugin Not Installed",
"repo.vault.plugin_not_installed_desc": "The Vault plugin is not installed on this server. Contact your administrator to enable secrets management.",
"repo.vault.secret_limit_reached": "Secret limit reached. Your current tier allows %d secrets per repository. Upgrade to Pro for unlimited secrets.",
@@ -2160,4 +2200,4 @@
"actions.runners.waiting_jobs": "Bíðandi verk",
"actions.runners.back_to_runners": "Til baka í keyrsluforrit",
"actions.runners.no_waiting_jobs": "Engin verk bíða eftir þessu merki"
}
}

View File

@@ -3286,6 +3286,9 @@
"repo.settings.pages.translations": "Traduzioni",
"repo.settings.pages.ai_translate": "Traduzione IA",
"repo.settings.pages.ai_translate_success": "La traduzione \u00e8 stata generata con successo dall'IA. Rivedi e modifica secondo necessit\u00e0.",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "Elimina",
"repo.settings.pages.save_translation": "Salva traduzione",
"repo.settings.pages.translation_saved": "Traduzione salvata con successo.",
@@ -3298,6 +3301,43 @@
"repo.settings.pages.trans_cta_headline": "Titolo sezione CTA",
"repo.settings.pages.trans_cta_subheadline": "Sottotitolo sezione CTA",
"repo.settings.pages.trans_cta_button": "Etichetta pulsante CTA",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault Plugin Not Installed",
"repo.vault.plugin_not_installed_desc": "The Vault plugin is not installed on this server. Contact your administrator to enable secrets management.",
"repo.vault.secret_limit_reached": "Secret limit reached. Your current tier allows %d secrets per repository. Upgrade to Pro for unlimited secrets.",
@@ -3467,4 +3507,4 @@
"actions.runners.waiting_jobs": "Lavori in attesa",
"actions.runners.back_to_runners": "Torna ai runner",
"actions.runners.no_waiting_jobs": "Nessun lavoro in attesa per questa etichetta"
}
}

View File

@@ -4230,6 +4230,9 @@
"repo.settings.pages.translations": "\u7ffb\u8a33",
"repo.settings.pages.ai_translate": "AI\u7ffb\u8a33",
"repo.settings.pages.ai_translate_success": "AI\u306b\u3088\u308b\u7ffb\u8a33\u304c\u6b63\u5e38\u306b\u751f\u6210\u3055\u308c\u307e\u3057\u305f\u3002\u5fc5\u8981\u306b\u5fdc\u3058\u3066\u78ba\u8a8d\u30fb\u7de8\u96c6\u3057\u3066\u304f\u3060\u3055\u3044\u3002",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "\u524a\u9664",
"repo.settings.pages.save_translation": "\u7ffb\u8a33\u3092\u4fdd\u5b58",
"repo.settings.pages.translation_saved": "\u7ffb\u8a33\u304c\u6b63\u5e38\u306b\u4fdd\u5b58\u3055\u308c\u307e\u3057\u305f\u3002",
@@ -4242,6 +4245,43 @@
"repo.settings.pages.trans_cta_headline": "CTA\u30bb\u30af\u30b7\u30e7\u30f3\u898b\u51fa\u3057",
"repo.settings.pages.trans_cta_subheadline": "CTA\u30bb\u30af\u30b7\u30e7\u30f3\u30b5\u30d6\u898b\u51fa\u3057",
"repo.settings.pages.trans_cta_button": "CTA\u30dc\u30bf\u30f3\u30e9\u30d9\u30eb",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault Plugin Not Installed",
"repo.vault.plugin_not_installed_desc": "The Vault plugin is not installed on this server. Contact your administrator to enable secrets management.",
"repo.vault.secret_limit_reached": "Secret limit reached. Your current tier allows %d secrets per repository. Upgrade to Pro for unlimited secrets.",
@@ -4411,4 +4451,4 @@
"actions.runners.waiting_jobs": "待機中のジョブ",
"actions.runners.back_to_runners": "ランナーに戻る",
"actions.runners.no_waiting_jobs": "このラベルを待機しているジョブはありません"
}
}

View File

@@ -2245,6 +2245,9 @@
"repo.settings.pages.translations": "\ubc88\uc5ed",
"repo.settings.pages.ai_translate": "AI \ubc88\uc5ed",
"repo.settings.pages.ai_translate_success": "AI\uac00 \ubc88\uc5ed\uc744 \uc131\uacf5\uc801\uc73c\ub85c \uc0dd\uc131\ud588\uc2b5\ub2c8\ub2e4. \ud544\uc694\uc5d0 \ub530\ub77c \ud655\uc778\ud558\uace0 \ud3b8\uc9d1\ud558\uc138\uc694.",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "\uc0ad\uc81c",
"repo.settings.pages.save_translation": "\ubc88\uc5ed \uc800\uc7a5",
"repo.settings.pages.translation_saved": "\ubc88\uc5ed\uc774 \uc131\uacf5\uc801\uc73c\ub85c \uc800\uc7a5\ub418\uc5c8\uc2b5\ub2c8\ub2e4.",
@@ -2257,6 +2260,43 @@
"repo.settings.pages.trans_cta_headline": "CTA \uc139\uc158 \uc81c\ubaa9",
"repo.settings.pages.trans_cta_subheadline": "CTA \uc139\uc158 \ubd80\uc81c\ubaa9",
"repo.settings.pages.trans_cta_button": "CTA \ubc84\ud2bc \ub77c\ubca8",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault Plugin Not Installed",
"repo.vault.plugin_not_installed_desc": "The Vault plugin is not installed on this server. Contact your administrator to enable secrets management.",
"repo.vault.secret_limit_reached": "Secret limit reached. Your current tier allows %d secrets per repository. Upgrade to Pro for unlimited secrets.",
@@ -2426,4 +2466,4 @@
"actions.runners.waiting_jobs": "대기 중인 작업",
"actions.runners.back_to_runners": "러너로 돌아가기",
"actions.runners.no_waiting_jobs": "이 레이블을 기다리는 작업이 없습니다"
}
}

View File

@@ -3756,6 +3756,9 @@
"repo.settings.pages.translations": "Tulkojumi",
"repo.settings.pages.ai_translate": "AI tulkojums",
"repo.settings.pages.ai_translate_success": "Tulkojums veiksmīgi ģenerēts ar AI. Pārskatiet un rediģējiet pēc nepieciešamības.",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "Dzēst",
"repo.settings.pages.save_translation": "Saglabāt tulkojumu",
"repo.settings.pages.translation_saved": "Tulkojums veiksmīgi saglabāts.",
@@ -3768,6 +3771,43 @@
"repo.settings.pages.trans_cta_headline": "CTA sadaļas virsraksts",
"repo.settings.pages.trans_cta_subheadline": "CTA sadaļas apakšvirsraksts",
"repo.settings.pages.trans_cta_button": "CTA pogas etiķete",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault Plugin Not Installed",
"repo.vault.plugin_not_installed_desc": "The Vault plugin is not installed on this server. Contact your administrator to enable secrets management.",
"repo.vault.secret_limit_reached": "Secret limit reached. Your current tier allows %d secrets per repository. Upgrade to Pro for unlimited secrets.",
@@ -4054,4 +4094,4 @@
"actions.runners.waiting_jobs": "Gaidošie darbi",
"actions.runners.back_to_runners": "Atpakaļ pie izpildītājiem",
"actions.runners.no_waiting_jobs": "Nav darbu, kas gaida šo etiķeti"
}
}

View File

@@ -2904,6 +2904,9 @@
"repo.settings.pages.translations": "Vertalingen",
"repo.settings.pages.ai_translate": "AI-vertaling",
"repo.settings.pages.ai_translate_success": "Vertaling is succesvol gegenereerd door AI. Controleer en bewerk indien nodig.",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "Verwijderen",
"repo.settings.pages.save_translation": "Vertaling opslaan",
"repo.settings.pages.translation_saved": "Vertaling succesvol opgeslagen.",
@@ -2916,6 +2919,43 @@
"repo.settings.pages.trans_cta_headline": "CTA-sectietitel",
"repo.settings.pages.trans_cta_subheadline": "CTA-sectie ondertitel",
"repo.settings.pages.trans_cta_button": "CTA-knoplabel",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault Plugin Not Installed",
"repo.vault.plugin_not_installed_desc": "The Vault plugin is not installed on this server. Contact your administrator to enable secrets management.",
"repo.vault.secret_limit_reached": "Secret limit reached. Your current tier allows %d secrets per repository. Upgrade to Pro for unlimited secrets.",
@@ -3215,4 +3255,4 @@
"actions.runners.waiting_jobs": "Wachtende taken",
"actions.runners.back_to_runners": "Terug naar runners",
"actions.runners.no_waiting_jobs": "Geen taken wachten op dit label"
}
}

View File

@@ -2875,6 +2875,9 @@
"repo.settings.pages.translations": "T\u0142umaczenia",
"repo.settings.pages.ai_translate": "T\u0142umaczenie AI",
"repo.settings.pages.ai_translate_success": "T\u0142umaczenie zosta\u0142o wygenerowane przez AI. Sprawd\u017a i edytuj w razie potrzeby.",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "Usu\u0144",
"repo.settings.pages.save_translation": "Zapisz t\u0142umaczenie",
"repo.settings.pages.translation_saved": "T\u0142umaczenie zapisane pomy\u015blnie.",
@@ -2887,6 +2890,43 @@
"repo.settings.pages.trans_cta_headline": "Nag\u0142\u00f3wek sekcji CTA",
"repo.settings.pages.trans_cta_subheadline": "Podnag\u0142\u00f3wek sekcji CTA",
"repo.settings.pages.trans_cta_button": "Etykieta przycisku CTA",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault Plugin Not Installed",
"repo.vault.plugin_not_installed_desc": "The Vault plugin is not installed on this server. Contact your administrator to enable secrets management.",
"repo.vault.secret_limit_reached": "Secret limit reached. Your current tier allows %d secrets per repository. Upgrade to Pro for unlimited secrets.",
@@ -3186,4 +3226,4 @@
"actions.runners.waiting_jobs": "Oczekujące zadania",
"actions.runners.back_to_runners": "Powrót do runnerów",
"actions.runners.no_waiting_jobs": "Brak zadań oczekujących na tę etykietę"
}
}

View File

@@ -4028,6 +4028,9 @@
"repo.settings.pages.translations": "Tradu\u00e7\u00f5es",
"repo.settings.pages.ai_translate": "Tradu\u00e7\u00e3o com IA",
"repo.settings.pages.ai_translate_success": "A tradu\u00e7\u00e3o foi gerada com sucesso pela IA. Revise e edite conforme necess\u00e1rio.",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "Excluir",
"repo.settings.pages.save_translation": "Salvar tradu\u00e7\u00e3o",
"repo.settings.pages.translation_saved": "Tradu\u00e7\u00e3o salva com sucesso.",
@@ -4040,6 +4043,43 @@
"repo.settings.pages.trans_cta_headline": "T\u00edtulo da se\u00e7\u00e3o CTA",
"repo.settings.pages.trans_cta_subheadline": "Subt\u00edtulo da se\u00e7\u00e3o CTA",
"repo.settings.pages.trans_cta_button": "R\u00f3tulo do bot\u00e3o CTA",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault Plugin Not Installed",
"repo.vault.plugin_not_installed_desc": "The Vault plugin is not installed on this server. Contact your administrator to enable secrets management.",
"repo.vault.secret_limit_reached": "Secret limit reached. Your current tier allows %d secrets per repository. Upgrade to Pro for unlimited secrets.",
@@ -4208,4 +4248,4 @@
"actions.runners.waiting_jobs": "Trabalhos em espera",
"actions.runners.back_to_runners": "Voltar para Runners",
"actions.runners.no_waiting_jobs": "Nenhum trabalho aguardando para este rótulo"
}
}

View File

@@ -4117,6 +4117,9 @@
"repo.settings.pages.translations": "Tradu\u00e7\u00f5es",
"repo.settings.pages.ai_translate": "Tradu\u00e7\u00e3o com IA",
"repo.settings.pages.ai_translate_success": "A tradu\u00e7\u00e3o foi gerada com sucesso pela IA. Reveja e edite conforme necess\u00e1rio.",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "Eliminar",
"repo.settings.pages.save_translation": "Guardar tradu\u00e7\u00e3o",
"repo.settings.pages.translation_saved": "Tradu\u00e7\u00e3o guardada com sucesso.",
@@ -4129,6 +4132,43 @@
"repo.settings.pages.trans_cta_headline": "T\u00edtulo da sec\u00e7\u00e3o CTA",
"repo.settings.pages.trans_cta_subheadline": "Subt\u00edtulo da sec\u00e7\u00e3o CTA",
"repo.settings.pages.trans_cta_button": "Etiqueta do bot\u00e3o CTA",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault Plugin Not Installed",
"repo.vault.plugin_not_installed_desc": "The Vault plugin is not installed on this server. Contact your administrator to enable secrets management.",
"repo.vault.secret_limit_reached": "Secret limit reached. Your current tier allows %d secrets per repository. Upgrade to Pro for unlimited secrets.",
@@ -4427,4 +4467,4 @@
"actions.runners.waiting_jobs": "Trabalhos em espera",
"actions.runners.back_to_runners": "Voltar aos Runners",
"actions.runners.no_waiting_jobs": "Nenhum trabalho à espera para esta etiqueta"
}
}

View File

@@ -3849,6 +3849,9 @@
"repo.settings.pages.translations": "\u041f\u0435\u0440\u0435\u0432\u043e\u0434\u044b",
"repo.settings.pages.ai_translate": "\u0418\u0418-\u043f\u0435\u0440\u0435\u0432\u043e\u0434",
"repo.settings.pages.ai_translate_success": "\u041f\u0435\u0440\u0435\u0432\u043e\u0434 \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d \u0418\u0418. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0438 \u043e\u0442\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0439\u0442\u0435 \u043f\u0440\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438.",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c",
"repo.settings.pages.save_translation": "\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043f\u0435\u0440\u0435\u0432\u043e\u0434",
"repo.settings.pages.translation_saved": "\u041f\u0435\u0440\u0435\u0432\u043e\u0434 \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0451\u043d.",
@@ -3861,6 +3864,43 @@
"repo.settings.pages.trans_cta_headline": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u0440\u0430\u0437\u0434\u0435\u043b\u0430 CTA",
"repo.settings.pages.trans_cta_subheadline": "\u041f\u043e\u0434\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u0440\u0430\u0437\u0434\u0435\u043b\u0430 CTA",
"repo.settings.pages.trans_cta_button": "\u041c\u0435\u0442\u043a\u0430 \u043a\u043d\u043e\u043f\u043a\u0438 CTA",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault Plugin Not Installed",
"repo.vault.plugin_not_installed_desc": "The Vault plugin is not installed on this server. Contact your administrator to enable secrets management.",
"repo.vault.secret_limit_reached": "Secret limit reached. Your current tier allows %d secrets per repository. Upgrade to Pro for unlimited secrets.",
@@ -4017,4 +4057,4 @@
"actions.runners.waiting_jobs": "Ожидающие задачи",
"actions.runners.back_to_runners": "Назад к раннерам",
"actions.runners.no_waiting_jobs": "Нет задач, ожидающих эту метку"
}
}

View File

@@ -2921,6 +2921,9 @@
"repo.settings.pages.translations": "පරිවර්තන",
"repo.settings.pages.ai_translate": "AI පරිවර්තනය",
"repo.settings.pages.ai_translate_success": "AI විසින් පරිවර්තනය සාර්ථකව උත්පාදනය කරන ලදී. අවශ්‍ය පරිදි සමාලෝචනය කර සංස්කරණය කරන්න.",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "මකන්න",
"repo.settings.pages.save_translation": "පරිවර්තනය සුරකින්න",
"repo.settings.pages.translation_saved": "පරිවර්තනය සාර්ථකව සුරැකිණි.",
@@ -2933,6 +2936,43 @@
"repo.settings.pages.trans_cta_headline": "CTA අංශ මාතෘකාව",
"repo.settings.pages.trans_cta_subheadline": "CTA අංශ උපමාතෘකාව",
"repo.settings.pages.trans_cta_button": "CTA බොත්තම් ලේබලය",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault Plugin Not Installed",
"repo.vault.plugin_not_installed_desc": "The Vault plugin is not installed on this server. Contact your administrator to enable secrets management.",
"repo.vault.secret_limit_reached": "Secret limit reached. Your current tier allows %d secrets per repository. Upgrade to Pro for unlimited secrets.",
@@ -3219,4 +3259,4 @@
"actions.runners.waiting_jobs": "බලා සිටින කාර්යයන්",
"actions.runners.back_to_runners": "ධාවකයන් වෙත ආපසු",
"actions.runners.no_waiting_jobs": "මෙම ලේබලය සඳහා බලා සිටින කාර්යයන් නොමැත"
}
}

View File

@@ -1947,6 +1947,9 @@
"repo.settings.pages.translations": "Preklady",
"repo.settings.pages.ai_translate": "AI preklad",
"repo.settings.pages.ai_translate_success": "Preklad bol \u00faspe\u0161ne vygenerovan\u00fd AI. Skontrolujte a upravte pod\u013ea potreby.",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "Odstr\u00e1ni\u0165",
"repo.settings.pages.save_translation": "Ulo\u017ei\u0165 preklad",
"repo.settings.pages.translation_saved": "Preklad \u00faspe\u0161ne ulo\u017een\u00fd.",
@@ -1959,6 +1962,43 @@
"repo.settings.pages.trans_cta_headline": "Nadpis sekcie CTA",
"repo.settings.pages.trans_cta_subheadline": "Podnadpis sekcie CTA",
"repo.settings.pages.trans_cta_button": "\u0160t\u00edtok tla\u010didla CTA",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault Plugin Not Installed",
"repo.vault.plugin_not_installed_desc": "The Vault plugin is not installed on this server. Contact your administrator to enable secrets management.",
"repo.vault.secret_limit_reached": "Secret limit reached. Your current tier allows %d secrets per repository. Upgrade to Pro for unlimited secrets.",
@@ -2245,4 +2285,4 @@
"actions.runners.waiting_jobs": "Čakajúce úlohy",
"actions.runners.back_to_runners": "Späť na bežcov",
"actions.runners.no_waiting_jobs": "Žiadne úlohy nečakajú na tento štítok"
}
}

View File

@@ -2505,6 +2505,9 @@
"repo.settings.pages.translations": "\u00d6vers\u00e4ttningar",
"repo.settings.pages.ai_translate": "AI-\u00f6vers\u00e4ttning",
"repo.settings.pages.ai_translate_success": "\u00d6vers\u00e4ttningen har genererats av AI. Granska och redigera vid behov.",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "Ta bort",
"repo.settings.pages.save_translation": "Spara \u00f6vers\u00e4ttning",
"repo.settings.pages.translation_saved": "\u00d6vers\u00e4ttningen har sparats.",
@@ -2517,6 +2520,43 @@
"repo.settings.pages.trans_cta_headline": "CTA-sektionsrubrik",
"repo.settings.pages.trans_cta_subheadline": "CTA-sektions underrubrik",
"repo.settings.pages.trans_cta_button": "CTA-knappsetikett",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault Plugin Not Installed",
"repo.vault.plugin_not_installed_desc": "The Vault plugin is not installed on this server. Contact your administrator to enable secrets management.",
"repo.vault.secret_limit_reached": "Secret limit reached. Your current tier allows %d secrets per repository. Upgrade to Pro for unlimited secrets.",
@@ -2816,4 +2856,4 @@
"actions.runners.waiting_jobs": "Väntande jobb",
"actions.runners.back_to_runners": "Tillbaka till runners",
"actions.runners.no_waiting_jobs": "Inga jobb väntar på denna etikett"
}
}

View File

@@ -4114,6 +4114,9 @@
"repo.settings.pages.translations": "\u00c7eviriler",
"repo.settings.pages.ai_translate": "AI \u00c7eviri",
"repo.settings.pages.ai_translate_success": "\u00c7eviri AI taraf\u0131ndan ba\u015far\u0131yla olu\u015fturuldu. Gerekti\u011fi gibi inceleyin ve d\u00fczenleyin.",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "Sil",
"repo.settings.pages.save_translation": "\u00c7eviriyi kaydet",
"repo.settings.pages.translation_saved": "\u00c7eviri ba\u015far\u0131yla kaydedildi.",
@@ -4126,6 +4129,43 @@
"repo.settings.pages.trans_cta_headline": "CTA b\u00f6l\u00fcm ba\u015fl\u0131\u011f\u0131",
"repo.settings.pages.trans_cta_subheadline": "CTA b\u00f6l\u00fcm alt ba\u015fl\u0131\u011f\u0131",
"repo.settings.pages.trans_cta_button": "CTA d\u00fc\u011fme etiketi",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault Plugin Not Installed",
"repo.vault.plugin_not_installed_desc": "The Vault plugin is not installed on this server. Contact your administrator to enable secrets management.",
"repo.vault.secret_limit_reached": "Secret limit reached. Your current tier allows %d secrets per repository. Upgrade to Pro for unlimited secrets.",
@@ -4425,4 +4465,4 @@
"actions.runners.waiting_jobs": "Bekleyen İşler",
"actions.runners.back_to_runners": "Çalıştırıcılara Dön",
"actions.runners.no_waiting_jobs": "Bu etiket için bekleyen iş yok"
}
}

View File

@@ -3899,6 +3899,9 @@
"repo.settings.pages.translations": "\u041f\u0435\u0440\u0435\u043a\u043b\u0430\u0434\u0438",
"repo.settings.pages.ai_translate": "\u0428\u0406-\u043f\u0435\u0440\u0435\u043a\u043b\u0430\u0434",
"repo.settings.pages.ai_translate_success": "\u041f\u0435\u0440\u0435\u043a\u043b\u0430\u0434 \u0443\u0441\u043f\u0456\u0448\u043d\u043e \u0437\u0433\u0435\u043d\u0435\u0440\u043e\u0432\u0430\u043d\u043e \u0428\u0406. \u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u0442\u0430 \u0432\u0456\u0434\u0440\u0435\u0434\u0430\u0433\u0443\u0439\u0442\u0435 \u0437\u0430 \u043f\u043e\u0442\u0440\u0435\u0431\u0438.",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "\u0412\u0438\u0434\u0430\u043b\u0438\u0442\u0438",
"repo.settings.pages.save_translation": "\u0417\u0431\u0435\u0440\u0435\u0433\u0442\u0438 \u043f\u0435\u0440\u0435\u043a\u043b\u0430\u0434",
"repo.settings.pages.translation_saved": "\u041f\u0435\u0440\u0435\u043a\u043b\u0430\u0434 \u0443\u0441\u043f\u0456\u0448\u043d\u043e \u0437\u0431\u0435\u0440\u0435\u0436\u0435\u043d\u043e.",
@@ -3911,6 +3914,43 @@
"repo.settings.pages.trans_cta_headline": "\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u0440\u043e\u0437\u0434\u0456\u043b\u0443 CTA",
"repo.settings.pages.trans_cta_subheadline": "\u041f\u0456\u0434\u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a \u0440\u043e\u0437\u0434\u0456\u043b\u0443 CTA",
"repo.settings.pages.trans_cta_button": "\u041c\u0456\u0442\u043a\u0430 \u043a\u043d\u043e\u043f\u043a\u0438 CTA",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault Plugin Not Installed",
"repo.vault.plugin_not_installed_desc": "The Vault plugin is not installed on this server. Contact your administrator to enable secrets management.",
"repo.vault.secret_limit_reached": "Secret limit reached. Your current tier allows %d secrets per repository. Upgrade to Pro for unlimited secrets.",
@@ -4197,4 +4237,4 @@
"actions.runners.waiting_jobs": "Очікуючі завдання",
"actions.runners.back_to_runners": "Назад до раннерів",
"actions.runners.no_waiting_jobs": "Немає завдань, що очікують цю мітку"
}
}

View File

@@ -4248,6 +4248,9 @@
"repo.settings.pages.translations": "\u7ffb\u8bd1",
"repo.settings.pages.ai_translate": "AI\u7ffb\u8bd1",
"repo.settings.pages.ai_translate_success": "AI\u5df2\u6210\u529f\u751f\u6210\u7ffb\u8bd1\u3002\u8bf7\u6839\u636e\u9700\u8981\u67e5\u770b\u548c\u7f16\u8f91\u3002",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "\u5220\u9664",
"repo.settings.pages.save_translation": "\u4fdd\u5b58\u7ffb\u8bd1",
"repo.settings.pages.translation_saved": "\u7ffb\u8bd1\u5df2\u6210\u529f\u4fdd\u5b58\u3002",
@@ -4260,6 +4263,43 @@
"repo.settings.pages.trans_cta_headline": "\u884c\u52a8\u53f7\u53ec\u533a\u57df\u6807\u9898",
"repo.settings.pages.trans_cta_subheadline": "\u884c\u52a8\u53f7\u53ec\u533a\u57df\u526f\u6807\u9898",
"repo.settings.pages.trans_cta_button": "\u884c\u52a8\u53f7\u53ec\u6309\u94ae\u6807\u7b7e",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault Plugin Not Installed",
"repo.vault.plugin_not_installed_desc": "The Vault plugin is not installed on this server. Contact your administrator to enable secrets management.",
"repo.vault.secret_limit_reached": "Secret limit reached. Your current tier allows %d secrets per repository. Upgrade to Pro for unlimited secrets.",
@@ -4429,4 +4469,4 @@
"actions.runners.waiting_jobs": "等待中的任务",
"actions.runners.back_to_runners": "返回运行器",
"actions.runners.no_waiting_jobs": "没有等待此标签的任务"
}
}

View File

@@ -4156,6 +4156,9 @@
"repo.settings.pages.translations": "\u7ffb\u8b6f",
"repo.settings.pages.ai_translate": "AI\u7ffb\u8b6f",
"repo.settings.pages.ai_translate_success": "AI\u5df2\u6210\u529f\u7522\u751f\u7ffb\u8b6f\u3002\u8acb\u6839\u64da\u9700\u8981\u6aa2\u8996\u548c\u7de8\u8f2f\u3002",
"repo.settings.pages.ai_translate_all": "Translate All (AI)",
"repo.settings.pages.ai_translate_all_success": "Successfully translated %d languages.",
"repo.settings.pages.ai_translate_all_partial": "Translated %d of %d languages. %d failed.",
"repo.settings.pages.delete_translation": "\u522a\u9664",
"repo.settings.pages.save_translation": "\u5132\u5b58\u7ffb\u8b6f",
"repo.settings.pages.translation_saved": "\u7ffb\u8b6f\u5df2\u6210\u529f\u5132\u5b58\u3002",
@@ -4168,6 +4171,43 @@
"repo.settings.pages.trans_cta_headline": "\u884c\u52d5\u547c\u7c72\u5340\u57df\u6a19\u984c",
"repo.settings.pages.trans_cta_subheadline": "\u884c\u52d5\u547c\u7c72\u5340\u57df\u526f\u6a19\u984c",
"repo.settings.pages.trans_cta_button": "\u884c\u52d5\u547c\u7c72\u6309\u9215\u6a19\u7c64",
"repo.settings.pages.trans_section_brand": "Brand",
"repo.settings.pages.trans_section_hero": "Hero",
"repo.settings.pages.trans_section_stats": "Stats",
"repo.settings.pages.trans_section_value_props": "Value Propositions",
"repo.settings.pages.trans_section_features": "Features",
"repo.settings.pages.trans_section_testimonials": "Testimonials",
"repo.settings.pages.trans_section_pricing": "Pricing",
"repo.settings.pages.trans_section_cta": "Call to Action",
"repo.settings.pages.trans_section_blog": "Blog",
"repo.settings.pages.trans_section_gallery": "Gallery",
"repo.settings.pages.trans_section_comparison": "Comparison",
"repo.settings.pages.trans_section_footer": "Footer",
"repo.settings.pages.trans_section_seo": "SEO",
"repo.settings.pages.trans_brand_name": "Brand Name",
"repo.settings.pages.trans_brand_tagline": "Tagline",
"repo.settings.pages.trans_stat_value": "Value",
"repo.settings.pages.trans_stat_label": "Label",
"repo.settings.pages.trans_title": "Title",
"repo.settings.pages.trans_description": "Description",
"repo.settings.pages.trans_quote": "Quote",
"repo.settings.pages.trans_role": "Role",
"repo.settings.pages.trans_pricing_headline": "Pricing Headline",
"repo.settings.pages.trans_pricing_subheadline": "Pricing Subheadline",
"repo.settings.pages.trans_plan_name": "Plan Name",
"repo.settings.pages.trans_plan_period": "Period",
"repo.settings.pages.trans_plan_cta": "Plan Button",
"repo.settings.pages.trans_blog_headline": "Blog Headline",
"repo.settings.pages.trans_blog_subheadline": "Blog Subheadline",
"repo.settings.pages.trans_blog_cta": "Blog Button",
"repo.settings.pages.trans_gallery_headline": "Gallery Headline",
"repo.settings.pages.trans_gallery_subheadline": "Gallery Subheadline",
"repo.settings.pages.trans_comparison_headline": "Comparison Headline",
"repo.settings.pages.trans_comparison_subheadline": "Comparison Subheadline",
"repo.settings.pages.trans_footer_copyright": "Copyright",
"repo.settings.pages.trans_footer_link": "Link Label",
"repo.settings.pages.trans_seo_title": "SEO Title",
"repo.settings.pages.trans_seo_description": "SEO Description",
"repo.vault.plugin_not_installed": "Vault Plugin Not Installed",
"repo.vault.plugin_not_installed_desc": "The Vault plugin is not installed on this server. Contact your administrator to enable secrets management.",
"repo.vault.secret_limit_reached": "Secret limit reached. Your current tier allows %d secrets per repository. Upgrade to Pro for unlimited secrets.",
@@ -4337,4 +4377,4 @@
"actions.runners.waiting_jobs": "等待中的工作",
"actions.runners.back_to_runners": "返回執行器",
"actions.runners.no_waiting_jobs": "沒有等待此標籤的工作"
}
}

View File

@@ -171,10 +171,25 @@ func Routes() *web.Router {
// Upload instructions endpoint
m.Get("/upload/instructions", GetUploadInstructions)
// Public landing page API - for private repos with public_landing enabled
// Landing page API
m.Group("/repos/{owner}/{repo}/pages", func() {
// Public read endpoints
m.Get("/config", repoAssignmentWithPublicAccess(), GetPagesConfig)
m.Get("/content", repoAssignmentWithPublicAccess(), GetPagesContent)
// Write endpoints (require auth + repo admin)
m.Group("/config", func() {
m.Put("", web.Bind(api.UpdatePagesConfigOption{}), UpdatePagesConfig)
m.Patch("", web.Bind(api.UpdatePagesConfigOption{}), PatchPagesConfig)
m.Put("/brand", web.Bind(api.UpdatePagesBrandOption{}), UpdatePagesBrand)
m.Put("/hero", web.Bind(api.UpdatePagesHeroOption{}), UpdatePagesHero)
m.Put("/content", web.Bind(api.UpdatePagesContentOption{}), UpdatePagesContentSection)
m.Put("/comparison", web.Bind(api.UpdatePagesComparisonOption{}), UpdatePagesComparison)
m.Put("/social", web.Bind(api.UpdatePagesSocialOption{}), UpdatePagesSocial)
m.Put("/pricing", web.Bind(api.UpdatePagesPricingOption{}), UpdatePagesPricing)
m.Put("/footer", web.Bind(api.UpdatePagesFooterOption{}), UpdatePagesFooter)
m.Put("/theme", web.Bind(api.UpdatePagesThemeOption{}), UpdatePagesTheme)
}, repoAssignment(), reqToken())
})
// Blog v2 API - repository blog endpoints

View File

@@ -685,9 +685,10 @@ func handleInitialize(ctx *context_service.APIContext, req *MCPRequest) {
}
func handleToolsList(ctx *context_service.APIContext, req *MCPRequest) {
allTools := make([]MCPTool, 0, len(mcpTools)+len(mcpAITools))
allTools := make([]MCPTool, 0, len(mcpTools)+len(mcpAITools)+len(mcpPagesTools))
allTools = append(allTools, mcpTools...)
allTools = append(allTools, mcpAITools...)
allTools = append(allTools, mcpPagesTools...)
result := MCPToolsListResult{Tools: allTools}
sendMCPResult(ctx, req.ID, result)
}
@@ -759,6 +760,35 @@ func handleToolsCall(ctx *context_service.APIContext, req *MCPRequest) {
result, err = toolListIssues(ctx, params.Arguments)
case "get_issue":
result, err = toolGetIssue(ctx, params.Arguments)
// Landing Pages tools
case "get_landing_config":
result, err = toolGetLandingConfig(ctx, params.Arguments)
case "list_landing_templates":
result, err = toolListLandingTemplates(ctx, params.Arguments)
case "enable_landing_page":
result, err = toolEnableLandingPage(ctx, params.Arguments)
case "update_landing_brand":
result, err = toolUpdateLandingBrand(ctx, params.Arguments)
case "update_landing_hero":
result, err = toolUpdateLandingHero(ctx, params.Arguments)
case "update_landing_pricing":
result, err = toolUpdateLandingPricing(ctx, params.Arguments)
case "update_landing_comparison":
result, err = toolUpdateLandingComparison(ctx, params.Arguments)
case "update_landing_features":
result, err = toolUpdateLandingFeatures(ctx, params.Arguments)
case "update_landing_social_proof":
result, err = toolUpdateLandingSocialProof(ctx, params.Arguments)
case "update_landing_seo":
result, err = toolUpdateLandingSEO(ctx, params.Arguments)
case "update_landing_theme":
result, err = toolUpdateLandingTheme(ctx, params.Arguments)
case "update_landing_stats":
result, err = toolUpdateLandingStats(ctx, params.Arguments)
case "update_landing_value_props":
result, err = toolUpdateLandingValueProps(ctx, params.Arguments)
case "update_landing_cta":
result, err = toolUpdateLandingCTA(ctx, params.Arguments)
default:
sendMCPError(ctx, req.ID, -32602, "Unknown tool", params.Name)
return

794
routers/api/v2/mcp_pages.go Normal file
View File

@@ -0,0 +1,794 @@
// Copyright 2026 MarketAlly. All rights reserved.
// SPDX-License-Identifier: MIT
package v2
import (
"errors"
"fmt"
repo_model "code.gitcaddy.com/server/v3/models/repo"
user_model "code.gitcaddy.com/server/v3/models/user"
"code.gitcaddy.com/server/v3/modules/json"
pages_module "code.gitcaddy.com/server/v3/modules/pages"
"code.gitcaddy.com/server/v3/services/context"
pages_service "code.gitcaddy.com/server/v3/services/pages"
)
// Landing Pages MCP Tools
var mcpPagesTools = []MCPTool{
{
Name: "get_landing_config",
Description: "Get the full landing page configuration for a repository. Returns all sections: brand, hero, pricing, comparison, features, social proof, SEO, and more.",
InputSchema: map[string]any{
"type": "object",
"required": []string{"owner", "repo"},
"properties": map[string]any{
"owner": map[string]any{"type": "string", "description": "Repository owner"},
"repo": map[string]any{"type": "string", "description": "Repository name"},
},
},
},
{
Name: "list_landing_templates",
Description: "List available landing page templates with display names. Templates include: open-source-hero, saas-conversion, bold-marketing, developer-tool, and more.",
InputSchema: map[string]any{
"type": "object",
"properties": map[string]any{},
},
},
{
Name: "enable_landing_page",
Description: "Enable or disable the landing page for a repository. Optionally set a template.",
InputSchema: map[string]any{
"type": "object",
"required": []string{"owner", "repo", "enabled"},
"properties": map[string]any{
"owner": map[string]any{"type": "string", "description": "Repository owner"},
"repo": map[string]any{"type": "string", "description": "Repository name"},
"enabled": map[string]any{"type": "boolean", "description": "Enable or disable"},
"template": map[string]any{"type": "string", "description": "Template name (e.g., 'saas-conversion', 'open-source-hero')"},
},
},
},
{
Name: "update_landing_brand",
Description: "Update the brand section of a landing page: name, logo URL, tagline, favicon.",
InputSchema: map[string]any{
"type": "object",
"required": []string{"owner", "repo"},
"properties": map[string]any{
"owner": map[string]any{"type": "string", "description": "Repository owner"},
"repo": map[string]any{"type": "string", "description": "Repository name"},
"name": map[string]any{"type": "string", "description": "Brand name"},
"logo_url": map[string]any{"type": "string", "description": "Logo image URL"},
"tagline": map[string]any{"type": "string", "description": "Brand tagline"},
"favicon_url": map[string]any{"type": "string", "description": "Favicon URL"},
},
},
},
{
Name: "update_landing_hero",
Description: "Update the hero section: headline, subheadline, CTA buttons, hero image or video URL.",
InputSchema: map[string]any{
"type": "object",
"required": []string{"owner", "repo"},
"properties": map[string]any{
"owner": map[string]any{"type": "string", "description": "Repository owner"},
"repo": map[string]any{"type": "string", "description": "Repository name"},
"headline": map[string]any{"type": "string", "description": "Main headline"},
"subheadline": map[string]any{"type": "string", "description": "Supporting text"},
"image_url": map[string]any{"type": "string", "description": "Hero image URL"},
"video_url": map[string]any{"type": "string", "description": "Hero video URL"},
"primary_cta_label": map[string]any{"type": "string", "description": "Primary CTA button label"},
"primary_cta_url": map[string]any{"type": "string", "description": "Primary CTA button URL"},
"secondary_cta_label": map[string]any{"type": "string", "description": "Secondary CTA button label"},
"secondary_cta_url": map[string]any{"type": "string", "description": "Secondary CTA button URL"},
},
},
},
{
Name: "update_landing_pricing",
Description: "Update the pricing section with plans. Each plan has a name, price, period, feature list, CTA, and optional 'featured' flag.",
InputSchema: map[string]any{
"type": "object",
"required": []string{"owner", "repo", "plans"},
"properties": map[string]any{
"owner": map[string]any{"type": "string", "description": "Repository owner"},
"repo": map[string]any{"type": "string", "description": "Repository name"},
"headline": map[string]any{"type": "string", "description": "Pricing section headline"},
"subheadline": map[string]any{"type": "string", "description": "Pricing section subheadline"},
"plans": map[string]any{
"type": "array",
"items": map[string]any{
"type": "object",
"properties": map[string]any{
"name": map[string]any{"type": "string"},
"price": map[string]any{"type": "string"},
"period": map[string]any{"type": "string"},
"features": map[string]any{"type": "array", "items": map[string]any{"type": "string"}},
"cta": map[string]any{"type": "string"},
"featured": map[string]any{"type": "boolean"},
},
},
"description": "Array of pricing plans",
},
},
},
},
{
Name: "update_landing_comparison",
Description: "Update the feature comparison matrix. Define columns (products/tiers) and groups of features with per-column values.",
InputSchema: map[string]any{
"type": "object",
"required": []string{"owner", "repo"},
"properties": map[string]any{
"owner": map[string]any{"type": "string", "description": "Repository owner"},
"repo": map[string]any{"type": "string", "description": "Repository name"},
"enabled": map[string]any{"type": "boolean", "description": "Enable comparison section"},
"headline": map[string]any{"type": "string", "description": "Section headline"},
"subheadline": map[string]any{"type": "string", "description": "Section subheadline"},
"columns": map[string]any{
"type": "array",
"items": map[string]any{"type": "string"},
"description": "Column headers (e.g., ['Free', 'Pro', 'Enterprise'])",
},
"groups": map[string]any{
"type": "array",
"items": map[string]any{
"type": "object",
"properties": map[string]any{
"name": map[string]any{"type": "string"},
"features": map[string]any{
"type": "array",
"items": map[string]any{
"type": "object",
"properties": map[string]any{
"name": map[string]any{"type": "string"},
"values": map[string]any{"type": "array", "items": map[string]any{"type": "string"}},
},
},
},
},
},
"description": "Feature groups with per-column values",
},
},
},
},
{
Name: "update_landing_features",
Description: "Update the features section with title, description, and optional icon/image for each feature.",
InputSchema: map[string]any{
"type": "object",
"required": []string{"owner", "repo", "features"},
"properties": map[string]any{
"owner": map[string]any{"type": "string", "description": "Repository owner"},
"repo": map[string]any{"type": "string", "description": "Repository name"},
"features": map[string]any{
"type": "array",
"items": map[string]any{
"type": "object",
"properties": map[string]any{
"title": map[string]any{"type": "string"},
"description": map[string]any{"type": "string"},
"icon": map[string]any{"type": "string"},
"image_url": map[string]any{"type": "string"},
},
},
"description": "Array of features",
},
},
},
},
{
Name: "update_landing_social_proof",
Description: "Update testimonials and client logos for social proof.",
InputSchema: map[string]any{
"type": "object",
"required": []string{"owner", "repo"},
"properties": map[string]any{
"owner": map[string]any{"type": "string", "description": "Repository owner"},
"repo": map[string]any{"type": "string", "description": "Repository name"},
"logos": map[string]any{
"type": "array",
"items": map[string]any{"type": "string"},
"description": "Client/partner logo URLs",
},
"testimonials": map[string]any{
"type": "array",
"items": map[string]any{
"type": "object",
"properties": map[string]any{
"quote": map[string]any{"type": "string"},
"author": map[string]any{"type": "string"},
"role": map[string]any{"type": "string"},
"avatar": map[string]any{"type": "string"},
},
},
"description": "Testimonials",
},
},
},
},
{
Name: "update_landing_seo",
Description: "Update SEO metadata: title, description, keywords, Open Graph image, Twitter card settings.",
InputSchema: map[string]any{
"type": "object",
"required": []string{"owner", "repo"},
"properties": map[string]any{
"owner": map[string]any{"type": "string", "description": "Repository owner"},
"repo": map[string]any{"type": "string", "description": "Repository name"},
"title": map[string]any{"type": "string", "description": "SEO title"},
"description": map[string]any{"type": "string", "description": "SEO description"},
"keywords": map[string]any{"type": "array", "items": map[string]any{"type": "string"}, "description": "SEO keywords"},
"og_image": map[string]any{"type": "string", "description": "Open Graph image URL"},
"twitter_card": map[string]any{"type": "string", "description": "Twitter card type (summary, summary_large_image)"},
"twitter_site": map[string]any{"type": "string", "description": "Twitter @handle"},
},
},
},
{
Name: "update_landing_theme",
Description: "Update the visual theme: primary color, accent color, light/dark/auto mode.",
InputSchema: map[string]any{
"type": "object",
"required": []string{"owner", "repo"},
"properties": map[string]any{
"owner": map[string]any{"type": "string", "description": "Repository owner"},
"repo": map[string]any{"type": "string", "description": "Repository name"},
"primary_color": map[string]any{"type": "string", "description": "Primary brand color (hex, e.g., '#512BD4')"},
"accent_color": map[string]any{"type": "string", "description": "Accent color (hex)"},
"mode": map[string]any{"type": "string", "enum": []string{"light", "dark", "auto"}, "description": "Color mode"},
},
},
},
{
Name: "update_landing_stats",
Description: "Update the stats counters displayed on the landing page. Each stat has a value and label.",
InputSchema: map[string]any{
"type": "object",
"required": []string{"owner", "repo", "stats"},
"properties": map[string]any{
"owner": map[string]any{"type": "string", "description": "Repository owner"},
"repo": map[string]any{"type": "string", "description": "Repository name"},
"stats": map[string]any{
"type": "array",
"items": map[string]any{
"type": "object",
"properties": map[string]any{
"value": map[string]any{"type": "string"},
"label": map[string]any{"type": "string"},
},
},
"description": "Array of stat counters (e.g., [{value: '15+', label: 'Tools'}])",
},
},
},
},
{
Name: "update_landing_value_props",
Description: "Update the value propositions section. Each value prop has a title, description, and icon.",
InputSchema: map[string]any{
"type": "object",
"required": []string{"owner", "repo", "value_props"},
"properties": map[string]any{
"owner": map[string]any{"type": "string", "description": "Repository owner"},
"repo": map[string]any{"type": "string", "description": "Repository name"},
"value_props": map[string]any{
"type": "array",
"items": map[string]any{
"type": "object",
"properties": map[string]any{
"title": map[string]any{"type": "string"},
"description": map[string]any{"type": "string"},
"icon": map[string]any{"type": "string"},
},
},
"description": "Array of value propositions",
},
},
},
},
{
Name: "update_landing_cta",
Description: "Update the call-to-action section at the bottom of the page with headline, subheadline, and button.",
InputSchema: map[string]any{
"type": "object",
"required": []string{"owner", "repo"},
"properties": map[string]any{
"owner": map[string]any{"type": "string", "description": "Repository owner"},
"repo": map[string]any{"type": "string", "description": "Repository name"},
"headline": map[string]any{"type": "string", "description": "CTA headline"},
"subheadline": map[string]any{"type": "string", "description": "CTA subheadline"},
"button_label": map[string]any{"type": "string", "description": "Button text"},
"button_url": map[string]any{"type": "string", "description": "Button URL"},
},
},
},
}
// ── Tool Implementations ──────────────────────────────────
func toolGetLandingConfig(ctx *context.APIContext, args map[string]any) (any, error) {
owner, repo, err := resolveOwnerRepo(args)
if err != nil {
return nil, err
}
repoObj, err := getRepoByOwnerAndName(ctx, owner, repo)
if err != nil {
return nil, err
}
config, err := pages_service.GetPagesConfig(ctx, repoObj)
if err != nil {
return nil, fmt.Errorf("get pages config: %w", err)
}
if config == nil {
return map[string]any{"enabled": false, "message": "No landing page configured"}, nil
}
return buildFullResponse(config), nil
}
func toolListLandingTemplates(_ *context.APIContext, _ map[string]any) (any, error) { //nolint:unparam // signature must match tool handler type
templates := pages_module.ValidTemplates()
displayNames := pages_module.TemplateDisplayNames()
result := make([]map[string]string, 0, len(templates))
for _, t := range templates {
result = append(result, map[string]string{
"id": t,
"name": displayNames[t],
})
}
return map[string]any{"templates": result}, nil
}
func toolEnableLandingPage(ctx *context.APIContext, args map[string]any) (any, error) {
owner, repo, err := resolveOwnerRepo(args)
if err != nil {
return nil, err
}
repoObj, err := getRepoByOwnerAndName(ctx, owner, repo)
if err != nil {
return nil, err
}
enabled, _ := args["enabled"].(bool)
template, _ := args["template"].(string)
if enabled {
if template == "" {
template = "open-source-hero"
}
if !pages_module.IsValidTemplate(template) {
return nil, fmt.Errorf("invalid template: %s", template)
}
if err := pages_service.EnablePages(ctx, repoObj, template); err != nil {
return nil, fmt.Errorf("enable pages: %w", err)
}
return map[string]any{"enabled": true, "template": template}, nil
}
if err := pages_service.DisablePages(ctx, repoObj); err != nil {
return nil, fmt.Errorf("disable pages: %w", err)
}
return map[string]any{"enabled": false}, nil
}
func toolUpdateLandingBrand(ctx *context.APIContext, args map[string]any) (any, error) {
config, repoObj, err := getConfigForUpdate(ctx, args)
if err != nil {
return nil, err
}
if v, ok := args["name"].(string); ok {
config.Brand.Name = v
}
if v, ok := args["logo_url"].(string); ok {
config.Brand.LogoURL = v
}
if v, ok := args["tagline"].(string); ok {
config.Brand.Tagline = v
}
if v, ok := args["favicon_url"].(string); ok {
config.Brand.FaviconURL = v
}
return saveAndReturn(ctx, repoObj, config, "brand")
}
func toolUpdateLandingHero(ctx *context.APIContext, args map[string]any) (any, error) {
config, repoObj, err := getConfigForUpdate(ctx, args)
if err != nil {
return nil, err
}
if v, ok := args["headline"].(string); ok {
config.Hero.Headline = v
}
if v, ok := args["subheadline"].(string); ok {
config.Hero.Subheadline = v
}
if v, ok := args["image_url"].(string); ok {
config.Hero.ImageURL = v
}
if v, ok := args["video_url"].(string); ok {
config.Hero.VideoURL = v
}
if v, ok := args["primary_cta_label"].(string); ok {
config.Hero.PrimaryCTA.Label = v
}
if v, ok := args["primary_cta_url"].(string); ok {
config.Hero.PrimaryCTA.URL = v
}
if v, ok := args["secondary_cta_label"].(string); ok {
config.Hero.SecondaryCTA.Label = v
}
if v, ok := args["secondary_cta_url"].(string); ok {
config.Hero.SecondaryCTA.URL = v
}
return saveAndReturn(ctx, repoObj, config, "hero")
}
func toolUpdateLandingPricing(ctx *context.APIContext, args map[string]any) (any, error) {
config, repoObj, err := getConfigForUpdate(ctx, args)
if err != nil {
return nil, err
}
if v, ok := args["headline"].(string); ok {
config.Pricing.Headline = v
}
if v, ok := args["subheadline"].(string); ok {
config.Pricing.Subheadline = v
}
if plans, ok := args["plans"].([]any); ok {
config.Pricing.Plans = nil
for _, p := range plans {
if pm, ok := p.(map[string]any); ok {
plan := pages_module.PricingPlanConfig{
Name: strVal(pm, "name"),
Price: strVal(pm, "price"),
Period: strVal(pm, "period"),
CTA: strVal(pm, "cta"),
Featured: boolVal(pm, "featured"),
}
if features, ok := pm["features"].([]any); ok {
for _, f := range features {
if s, ok := f.(string); ok {
plan.Features = append(plan.Features, s)
}
}
}
config.Pricing.Plans = append(config.Pricing.Plans, plan)
}
}
}
return saveAndReturn(ctx, repoObj, config, "pricing")
}
func toolUpdateLandingComparison(ctx *context.APIContext, args map[string]any) (any, error) {
config, repoObj, err := getConfigForUpdate(ctx, args)
if err != nil {
return nil, err
}
if v, ok := args["enabled"].(bool); ok {
config.Comparison.Enabled = v
}
if v, ok := args["headline"].(string); ok {
config.Comparison.Headline = v
}
if v, ok := args["subheadline"].(string); ok {
config.Comparison.Subheadline = v
}
if cols, ok := args["columns"].([]any); ok {
config.Comparison.Columns = nil
for _, c := range cols {
if s, ok := c.(string); ok {
config.Comparison.Columns = append(config.Comparison.Columns, pages_module.ComparisonColumnConfig{Name: s})
} else if cm, ok := c.(map[string]any); ok {
config.Comparison.Columns = append(config.Comparison.Columns, pages_module.ComparisonColumnConfig{
Name: strVal(cm, "name"),
Highlight: boolVal(cm, "highlight"),
})
}
}
}
if groups, ok := args["groups"].([]any); ok {
config.Comparison.Groups = nil
for _, g := range groups {
if gm, ok := g.(map[string]any); ok {
group := pages_module.ComparisonGroupConfig{Name: strVal(gm, "name")}
if features, ok := gm["features"].([]any); ok {
for _, f := range features {
if fm, ok := f.(map[string]any); ok {
feature := pages_module.ComparisonFeatureConfig{Name: strVal(fm, "name")}
if vals, ok := fm["values"].([]any); ok {
for _, v := range vals {
if s, ok := v.(string); ok {
feature.Values = append(feature.Values, s)
}
}
}
group.Features = append(group.Features, feature)
}
}
}
config.Comparison.Groups = append(config.Comparison.Groups, group)
}
}
}
return saveAndReturn(ctx, repoObj, config, "comparison")
}
func toolUpdateLandingFeatures(ctx *context.APIContext, args map[string]any) (any, error) {
config, repoObj, err := getConfigForUpdate(ctx, args)
if err != nil {
return nil, err
}
if features, ok := args["features"].([]any); ok {
config.Features = nil
for _, f := range features {
if fm, ok := f.(map[string]any); ok {
config.Features = append(config.Features, pages_module.FeatureConfig{
Title: strVal(fm, "title"),
Description: strVal(fm, "description"),
Icon: strVal(fm, "icon"),
ImageURL: strVal(fm, "image_url"),
})
}
}
}
return saveAndReturn(ctx, repoObj, config, "features")
}
func toolUpdateLandingSocialProof(ctx *context.APIContext, args map[string]any) (any, error) {
config, repoObj, err := getConfigForUpdate(ctx, args)
if err != nil {
return nil, err
}
if logos, ok := args["logos"].([]any); ok {
config.SocialProof.Logos = nil
for _, l := range logos {
if s, ok := l.(string); ok {
config.SocialProof.Logos = append(config.SocialProof.Logos, s)
}
}
}
if testimonials, ok := args["testimonials"].([]any); ok {
config.SocialProof.Testimonials = nil
for _, t := range testimonials {
if tm, ok := t.(map[string]any); ok {
config.SocialProof.Testimonials = append(config.SocialProof.Testimonials, pages_module.TestimonialConfig{
Quote: strVal(tm, "quote"),
Author: strVal(tm, "author"),
Role: strVal(tm, "role"),
Avatar: strVal(tm, "avatar"),
})
}
}
}
return saveAndReturn(ctx, repoObj, config, "social_proof")
}
func toolUpdateLandingSEO(ctx *context.APIContext, args map[string]any) (any, error) {
config, repoObj, err := getConfigForUpdate(ctx, args)
if err != nil {
return nil, err
}
if v, ok := args["title"].(string); ok {
config.SEO.Title = v
}
if v, ok := args["description"].(string); ok {
config.SEO.Description = v
}
if v, ok := args["og_image"].(string); ok {
config.SEO.OGImage = v
}
if v, ok := args["twitter_card"].(string); ok {
config.SEO.TwitterCard = v
}
if v, ok := args["twitter_site"].(string); ok {
config.SEO.TwitterSite = v
}
if keywords, ok := args["keywords"].([]any); ok {
config.SEO.Keywords = nil
for _, k := range keywords {
if s, ok := k.(string); ok {
config.SEO.Keywords = append(config.SEO.Keywords, s)
}
}
}
return saveAndReturn(ctx, repoObj, config, "seo")
}
func toolUpdateLandingTheme(ctx *context.APIContext, args map[string]any) (any, error) {
config, repoObj, err := getConfigForUpdate(ctx, args)
if err != nil {
return nil, err
}
if v, ok := args["primary_color"].(string); ok {
config.Theme.PrimaryColor = v
}
if v, ok := args["accent_color"].(string); ok {
config.Theme.AccentColor = v
}
if v, ok := args["mode"].(string); ok {
config.Theme.Mode = v
}
return saveAndReturn(ctx, repoObj, config, "theme")
}
func toolUpdateLandingStats(ctx *context.APIContext, args map[string]any) (any, error) {
config, repoObj, err := getConfigForUpdate(ctx, args)
if err != nil {
return nil, err
}
if stats, ok := args["stats"].([]any); ok {
config.Stats = nil
for _, s := range stats {
if sm, ok := s.(map[string]any); ok {
config.Stats = append(config.Stats, pages_module.StatConfig{
Value: strVal(sm, "value"),
Label: strVal(sm, "label"),
})
}
}
}
return saveAndReturn(ctx, repoObj, config, "stats")
}
func toolUpdateLandingValueProps(ctx *context.APIContext, args map[string]any) (any, error) {
config, repoObj, err := getConfigForUpdate(ctx, args)
if err != nil {
return nil, err
}
if vps, ok := args["value_props"].([]any); ok {
config.ValueProps = nil
for _, v := range vps {
if vm, ok := v.(map[string]any); ok {
config.ValueProps = append(config.ValueProps, pages_module.ValuePropConfig{
Title: strVal(vm, "title"),
Description: strVal(vm, "description"),
Icon: strVal(vm, "icon"),
})
}
}
}
return saveAndReturn(ctx, repoObj, config, "value_props")
}
func toolUpdateLandingCTA(ctx *context.APIContext, args map[string]any) (any, error) {
config, repoObj, err := getConfigForUpdate(ctx, args)
if err != nil {
return nil, err
}
if v, ok := args["headline"].(string); ok {
config.CTASection.Headline = v
}
if v, ok := args["subheadline"].(string); ok {
config.CTASection.Subheadline = v
}
if v, ok := args["button_label"].(string); ok {
config.CTASection.Button.Label = v
}
if v, ok := args["button_url"].(string); ok {
config.CTASection.Button.URL = v
}
return saveAndReturn(ctx, repoObj, config, "cta_section")
}
// ── Helpers ──────────────────────────────────
func getConfigForUpdate(ctx *context.APIContext, args map[string]any) (*pages_module.LandingConfig, *repo_model.Repository, error) {
owner, repo, err := resolveOwnerRepo(args)
if err != nil {
return nil, nil, err
}
repoObj, err := getRepoByOwnerAndName(ctx, owner, repo)
if err != nil {
return nil, nil, err
}
config, err := pages_service.GetPagesConfig(ctx, repoObj)
if err != nil {
return nil, nil, fmt.Errorf("get config: %w", err)
}
if config == nil {
config = pages_module.DefaultConfig()
}
return config, repoObj, nil
}
func saveAndReturn(ctx *context.APIContext, repo *repo_model.Repository, config *pages_module.LandingConfig, section string) (any, error) {
configJSON, _ := json.Marshal(config)
hash := pages_module.HashConfig(configJSON)
existing, _ := repo_model.GetPagesConfigByRepoID(ctx, repo.ID)
if existing != nil {
existing.ConfigJSON = string(configJSON)
existing.ConfigHash = hash
existing.Template = repo_model.PagesTemplate(config.Template)
existing.Enabled = config.Enabled
if err := repo_model.UpdatePagesConfig(ctx, existing); err != nil {
return nil, fmt.Errorf("save config: %w", err)
}
} else {
if err := repo_model.CreatePagesConfig(ctx, &repo_model.PagesConfig{
RepoID: repo.ID,
Enabled: config.Enabled,
Template: repo_model.PagesTemplate(config.Template),
ConfigJSON: string(configJSON),
ConfigHash: hash,
}); err != nil {
return nil, fmt.Errorf("create config: %w", err)
}
}
return map[string]any{
"success": true,
"section": section,
"message": fmt.Sprintf("Updated %s section", section),
}, nil
}
func strVal(m map[string]any, key string) string {
if v, ok := m[key].(string); ok {
return v
}
return ""
}
func boolVal(m map[string]any, key string) bool {
if v, ok := m[key].(bool); ok {
return v
}
return false
}
func resolveOwnerRepo(args map[string]any) (string, string, error) {
owner, _ := args["owner"].(string)
repo, _ := args["repo"].(string)
if owner == "" || repo == "" {
return "", "", errors.New("owner and repo are required")
}
return owner, repo, nil
}
func getRepoByOwnerAndName(ctx *context.APIContext, owner, repo string) (*repo_model.Repository, error) {
ownerObj, err := user_model.GetUserByName(ctx, owner)
if err != nil {
return nil, fmt.Errorf("owner not found: %s", owner)
}
repoObj, err := repo_model.GetRepositoryByName(ctx, ownerObj.ID, repo)
if err != nil {
return nil, fmt.Errorf("repo not found: %s/%s", owner, repo)
}
return repoObj, nil
}

View File

@@ -7,22 +7,39 @@ import (
"net/http"
repo_model "code.gitcaddy.com/server/v3/models/repo"
apierrors "code.gitcaddy.com/server/v3/modules/errors"
"code.gitcaddy.com/server/v3/modules/git"
"code.gitcaddy.com/server/v3/modules/json"
pages_module "code.gitcaddy.com/server/v3/modules/pages"
api "code.gitcaddy.com/server/v3/modules/structs"
"code.gitcaddy.com/server/v3/modules/web"
"code.gitcaddy.com/server/v3/services/context"
pages_service "code.gitcaddy.com/server/v3/services/pages"
)
// PagesConfigResponse represents the pages configuration for a repository
type PagesConfigResponse struct {
Enabled bool `json:"enabled"`
PublicLanding bool `json:"public_landing"`
Template string `json:"template"`
Domain string `json:"domain,omitempty"`
Brand pages_module.BrandConfig `json:"brand"`
Hero pages_module.HeroConfig `json:"hero"`
SEO pages_module.SEOConfig `json:"seo"`
Footer pages_module.FooterConfig `json:"footer"`
// PagesFullConfigResponse represents the complete landing page configuration
type PagesFullConfigResponse struct {
Enabled bool `json:"enabled"`
PublicLanding bool `json:"public_landing"`
Template string `json:"template"`
Domain string `json:"domain,omitempty"`
Brand pages_module.BrandConfig `json:"brand"`
Hero pages_module.HeroConfig `json:"hero"`
Stats []pages_module.StatConfig `json:"stats"`
ValueProps []pages_module.ValuePropConfig `json:"value_props"`
Features []pages_module.FeatureConfig `json:"features"`
SocialProof pages_module.SocialProofConfig `json:"social_proof"`
Pricing pages_module.PricingConfig `json:"pricing"`
CTASection pages_module.CTASectionConfig `json:"cta_section"`
Blog pages_module.BlogSectionConfig `json:"blog"`
Gallery pages_module.GallerySectionConfig `json:"gallery"`
Comparison pages_module.ComparisonSectionConfig `json:"comparison"`
Navigation pages_module.NavigationConfig `json:"navigation"`
Footer pages_module.FooterConfig `json:"footer"`
Theme pages_module.ThemeConfig `json:"theme"`
SEO pages_module.SEOConfig `json:"seo"`
Analytics pages_module.AnalyticsConfig `json:"analytics"`
Advanced pages_module.AdvancedConfig `json:"advanced"`
}
// PagesContentResponse represents the rendered content for a landing page
@@ -32,7 +49,433 @@ type PagesContentResponse struct {
Readme string `json:"readme,omitempty"`
}
// GetPagesConfig returns the pages configuration for a repository
// ---------------------------------------------------------------------------
// Shared helpers
// ---------------------------------------------------------------------------
func buildFullResponse(config *pages_module.LandingConfig) *PagesFullConfigResponse {
return &PagesFullConfigResponse{
Enabled: config.Enabled,
PublicLanding: config.PublicLanding,
Template: config.Template,
Domain: config.Domain,
Brand: config.Brand,
Hero: config.Hero,
Stats: config.Stats,
ValueProps: config.ValueProps,
Features: config.Features,
SocialProof: config.SocialProof,
Pricing: config.Pricing,
CTASection: config.CTASection,
Blog: config.Blog,
Gallery: config.Gallery,
Comparison: config.Comparison,
Navigation: config.Navigation,
Footer: config.Footer,
Theme: config.Theme,
SEO: config.SEO,
Analytics: config.Analytics,
Advanced: config.Advanced,
}
}
func getPagesConfigAPI(ctx *context.APIContext) (*pages_module.LandingConfig, bool) {
config, err := pages_service.GetPagesConfig(ctx, ctx.Repo.Repository)
if err != nil {
ctx.APIErrorWithCode(apierrors.PagesNotConfigured)
return nil, false
}
return config, true
}
func savePagesConfigAPI(ctx *context.APIContext, config *pages_module.LandingConfig) bool {
configJSON, err := json.Marshal(config)
if err != nil {
ctx.APIErrorInternal(err)
return false
}
dbConfig, err := repo_model.GetPagesConfigByRepoID(ctx, ctx.Repo.Repository.ID)
if err != nil {
if repo_model.IsErrPagesConfigNotExist(err) {
dbConfig = &repo_model.PagesConfig{
RepoID: ctx.Repo.Repository.ID,
Enabled: config.Enabled,
Template: repo_model.PagesTemplate(config.Template),
ConfigJSON: string(configJSON),
}
if err := repo_model.CreatePagesConfig(ctx, dbConfig); err != nil {
ctx.APIErrorInternal(err)
return false
}
return true
}
ctx.APIErrorInternal(err)
return false
}
dbConfig.Enabled = config.Enabled
dbConfig.Template = repo_model.PagesTemplate(config.Template)
dbConfig.ConfigJSON = string(configJSON)
if err := repo_model.UpdatePagesConfig(ctx, dbConfig); err != nil {
ctx.APIErrorInternal(err)
return false
}
return true
}
func requirePagesAdmin(ctx *context.APIContext) bool {
if !ctx.Repo.Permission.IsAdmin() && !ctx.IsUserSiteAdmin() {
ctx.APIErrorWithCode(apierrors.PermRepoAdminRequired)
return false
}
return true
}
// ---------------------------------------------------------------------------
// Apply helpers — map API option structs to config structs
// ---------------------------------------------------------------------------
func applyCTAButton(dst *pages_module.CTAButton, src *api.PagesCTAButtonOption) {
if src == nil {
return
}
if src.Label != nil {
dst.Label = *src.Label
}
if src.URL != nil {
dst.URL = *src.URL
}
if src.Variant != nil {
dst.Variant = *src.Variant
}
}
func applyBrand(dst *pages_module.BrandConfig, src *api.UpdatePagesBrandOption) {
if src.Name != nil {
dst.Name = *src.Name
}
if src.LogoURL != nil {
dst.LogoURL = *src.LogoURL
}
if src.Tagline != nil {
dst.Tagline = *src.Tagline
}
if src.FaviconURL != nil {
dst.FaviconURL = *src.FaviconURL
}
}
func applyHero(dst *pages_module.HeroConfig, src *api.UpdatePagesHeroOption) {
if src.Headline != nil {
dst.Headline = *src.Headline
}
if src.Subheadline != nil {
dst.Subheadline = *src.Subheadline
}
if src.ImageURL != nil {
dst.ImageURL = *src.ImageURL
}
if src.VideoURL != nil {
dst.VideoURL = *src.VideoURL
}
if src.CodeExample != nil {
dst.CodeExample = *src.CodeExample
}
applyCTAButton(&dst.PrimaryCTA, src.PrimaryCTA)
applyCTAButton(&dst.SecondaryCTA, src.SecondaryCTA)
}
func applyStats(config *pages_module.LandingConfig, src []api.PagesStatOption) {
config.Stats = make([]pages_module.StatConfig, len(src))
for i, s := range src {
config.Stats[i] = pages_module.StatConfig{Value: s.Value, Label: s.Label}
}
}
func applyValueProps(config *pages_module.LandingConfig, src []api.PagesValuePropOption) {
config.ValueProps = make([]pages_module.ValuePropConfig, len(src))
for i, v := range src {
config.ValueProps[i] = pages_module.ValuePropConfig{Title: v.Title, Description: v.Description, Icon: v.Icon}
}
}
func applyFeatures(config *pages_module.LandingConfig, src []api.PagesFeatureOption) {
config.Features = make([]pages_module.FeatureConfig, len(src))
for i, f := range src {
config.Features[i] = pages_module.FeatureConfig{Title: f.Title, Description: f.Description, Icon: f.Icon, ImageURL: f.ImageURL}
}
}
func applySocial(dst *pages_module.SocialProofConfig, src *api.UpdatePagesSocialOption) {
if src.Logos != nil {
dst.Logos = *src.Logos
}
if src.Testimonials != nil {
dst.Testimonials = make([]pages_module.TestimonialConfig, len(*src.Testimonials))
for i, t := range *src.Testimonials {
dst.Testimonials[i] = pages_module.TestimonialConfig{Quote: t.Quote, Author: t.Author, Role: t.Role, Avatar: t.Avatar}
}
}
}
func applyPricing(dst *pages_module.PricingConfig, src *api.UpdatePagesPricingOption) {
if src.Headline != nil {
dst.Headline = *src.Headline
}
if src.Subheadline != nil {
dst.Subheadline = *src.Subheadline
}
if src.Plans != nil {
dst.Plans = make([]pages_module.PricingPlanConfig, len(*src.Plans))
for i, p := range *src.Plans {
dst.Plans[i] = pages_module.PricingPlanConfig{
Name: p.Name, Price: p.Price, Period: p.Period,
Features: p.Features, CTA: p.CTA, Featured: p.Featured,
}
}
}
}
func applyCTASection(dst *pages_module.CTASectionConfig, src *api.UpdatePagesCTAOption) {
if src.Headline != nil {
dst.Headline = *src.Headline
}
if src.Subheadline != nil {
dst.Subheadline = *src.Subheadline
}
applyCTAButton(&dst.Button, src.Button)
}
func applyBlog(dst *pages_module.BlogSectionConfig, src *api.UpdatePagesBlogOption) {
if src.Enabled != nil {
dst.Enabled = *src.Enabled
}
if src.Headline != nil {
dst.Headline = *src.Headline
}
if src.Subheadline != nil {
dst.Subheadline = *src.Subheadline
}
if src.MaxPosts != nil {
dst.MaxPosts = *src.MaxPosts
}
}
func applyGallery(dst *pages_module.GallerySectionConfig, src *api.UpdatePagesGalleryOption) {
if src.Enabled != nil {
dst.Enabled = *src.Enabled
}
if src.Headline != nil {
dst.Headline = *src.Headline
}
if src.Subheadline != nil {
dst.Subheadline = *src.Subheadline
}
if src.MaxImages != nil {
dst.MaxImages = *src.MaxImages
}
if src.Columns != nil {
dst.Columns = *src.Columns
}
}
func applyComparison(dst *pages_module.ComparisonSectionConfig, src *api.UpdatePagesComparisonOption) {
if src.Enabled != nil {
dst.Enabled = *src.Enabled
}
if src.Headline != nil {
dst.Headline = *src.Headline
}
if src.Subheadline != nil {
dst.Subheadline = *src.Subheadline
}
if src.Columns != nil {
dst.Columns = make([]pages_module.ComparisonColumnConfig, len(*src.Columns))
for i, c := range *src.Columns {
dst.Columns[i] = pages_module.ComparisonColumnConfig{Name: c.Name, Highlight: c.Highlight}
}
}
if src.Groups != nil {
dst.Groups = make([]pages_module.ComparisonGroupConfig, len(*src.Groups))
for i, g := range *src.Groups {
features := make([]pages_module.ComparisonFeatureConfig, len(g.Features))
for j, f := range g.Features {
features[j] = pages_module.ComparisonFeatureConfig{Name: f.Name, Values: f.Values}
}
dst.Groups[i] = pages_module.ComparisonGroupConfig{Name: g.Name, Features: features}
}
}
}
func applyNavigation(dst *pages_module.NavigationConfig, src *api.UpdatePagesNavOption) {
if src.ShowDocs != nil {
dst.ShowDocs = *src.ShowDocs
}
if src.ShowAPI != nil {
dst.ShowAPI = *src.ShowAPI
}
if src.ShowRepository != nil {
dst.ShowRepository = *src.ShowRepository
}
if src.ShowReleases != nil {
dst.ShowReleases = *src.ShowReleases
}
if src.ShowIssues != nil {
dst.ShowIssues = *src.ShowIssues
}
}
func applyFooter(dst *pages_module.FooterConfig, ctaDst *pages_module.CTASectionConfig, src *api.UpdatePagesFooterOption) {
if src.Copyright != nil {
dst.Copyright = *src.Copyright
}
if src.ShowPoweredBy != nil {
dst.ShowPoweredBy = *src.ShowPoweredBy
}
if src.Links != nil {
dst.Links = make([]pages_module.FooterLink, len(*src.Links))
for i, l := range *src.Links {
dst.Links[i] = pages_module.FooterLink{Label: l.Label, URL: l.URL}
}
}
if src.Social != nil {
dst.Social = make([]pages_module.SocialLink, len(*src.Social))
for i, s := range *src.Social {
dst.Social[i] = pages_module.SocialLink{Platform: s.Platform, URL: s.URL}
}
}
if src.CTASection != nil {
applyCTASection(ctaDst, src.CTASection)
}
}
func applyTheme(dst *pages_module.ThemeConfig, src *api.UpdatePagesThemeOption) {
if src.PrimaryColor != nil {
dst.PrimaryColor = *src.PrimaryColor
}
if src.AccentColor != nil {
dst.AccentColor = *src.AccentColor
}
if src.Mode != nil {
dst.Mode = *src.Mode
}
}
func applySEO(dst *pages_module.SEOConfig, src *api.UpdatePagesSEOOption) {
if src.Title != nil {
dst.Title = *src.Title
}
if src.Description != nil {
dst.Description = *src.Description
}
if src.Keywords != nil {
dst.Keywords = *src.Keywords
}
if src.OGImage != nil {
dst.OGImage = *src.OGImage
}
if src.UseMediaKitOG != nil {
dst.UseMediaKitOG = *src.UseMediaKitOG
}
if src.TwitterCard != nil {
dst.TwitterCard = *src.TwitterCard
}
if src.TwitterSite != nil {
dst.TwitterSite = *src.TwitterSite
}
}
func applyAdvanced(dst *pages_module.AdvancedConfig, src *api.UpdatePagesAdvancedOption) {
if src.CustomCSS != nil {
dst.CustomCSS = *src.CustomCSS
}
if src.CustomHead != nil {
dst.CustomHead = *src.CustomHead
}
if src.StaticRoutes != nil {
dst.StaticRoutes = *src.StaticRoutes
}
if src.PublicReleases != nil {
dst.PublicReleases = *src.PublicReleases
}
if src.HideMobileReleases != nil {
dst.HideMobileReleases = *src.HideMobileReleases
}
if src.GooglePlayID != nil {
dst.GooglePlayID = *src.GooglePlayID
}
if src.AppStoreID != nil {
dst.AppStoreID = *src.AppStoreID
}
}
// applyFullConfig applies all non-nil sections from the update option to the config
func applyFullConfig(config *pages_module.LandingConfig, form *api.UpdatePagesConfigOption) {
if form.Enabled != nil {
config.Enabled = *form.Enabled
}
if form.PublicLanding != nil {
config.PublicLanding = *form.PublicLanding
}
if form.Template != nil {
config.Template = *form.Template
}
if form.Brand != nil {
applyBrand(&config.Brand, form.Brand)
}
if form.Hero != nil {
applyHero(&config.Hero, form.Hero)
}
if form.Stats != nil {
applyStats(config, *form.Stats)
}
if form.ValueProps != nil {
applyValueProps(config, *form.ValueProps)
}
if form.Features != nil {
applyFeatures(config, *form.Features)
}
if form.SocialProof != nil {
applySocial(&config.SocialProof, form.SocialProof)
}
if form.Pricing != nil {
applyPricing(&config.Pricing, form.Pricing)
}
if form.CTASection != nil {
applyCTASection(&config.CTASection, form.CTASection)
}
if form.Blog != nil {
applyBlog(&config.Blog, form.Blog)
}
if form.Gallery != nil {
applyGallery(&config.Gallery, form.Gallery)
}
if form.Comparison != nil {
applyComparison(&config.Comparison, form.Comparison)
}
if form.Navigation != nil {
applyNavigation(&config.Navigation, form.Navigation)
}
if form.Footer != nil {
applyFooter(&config.Footer, &config.CTASection, form.Footer)
}
if form.Theme != nil {
applyTheme(&config.Theme, form.Theme)
}
if form.SEO != nil {
applySEO(&config.SEO, form.SEO)
}
if form.Advanced != nil {
applyAdvanced(&config.Advanced, form.Advanced)
}
}
// ---------------------------------------------------------------------------
// GET endpoints
// ---------------------------------------------------------------------------
// GetPagesConfig returns the full pages configuration for a repository
// GET /api/v2/repos/{owner}/{repo}/pages/config
func GetPagesConfig(ctx *context.APIContext) {
repo := ctx.Repo.Repository
@@ -43,22 +486,11 @@ func GetPagesConfig(ctx *context.APIContext) {
config, err := pages_service.GetPagesConfig(ctx, repo)
if err != nil {
ctx.APIErrorNotFound("Pages not configured")
ctx.APIErrorWithCode(apierrors.PagesNotConfigured)
return
}
response := &PagesConfigResponse{
Enabled: config.Enabled,
PublicLanding: config.PublicLanding,
Template: config.Template,
Domain: config.Domain,
Brand: config.Brand,
Hero: config.Hero,
SEO: config.SEO,
Footer: config.Footer,
}
ctx.JSON(http.StatusOK, response)
ctx.JSON(http.StatusOK, buildFullResponse(config))
}
// GetPagesContent returns the rendered content for a repository's landing page
@@ -72,14 +504,12 @@ func GetPagesContent(ctx *context.APIContext) {
config, err := pages_service.GetPagesConfig(ctx, repo)
if err != nil || !config.Enabled {
ctx.APIErrorNotFound("Pages not enabled")
ctx.APIErrorWithCode(apierrors.PagesNotEnabled)
return
}
// Load README content
readme := loadReadmeContent(ctx, repo)
// Build title
title := config.SEO.Title
if title == "" {
title = config.Hero.Headline
@@ -91,7 +521,6 @@ func GetPagesContent(ctx *context.APIContext) {
title = repo.Name
}
// Build description
description := config.SEO.Description
if description == "" {
description = config.Hero.Subheadline
@@ -100,15 +529,265 @@ func GetPagesContent(ctx *context.APIContext) {
description = repo.Description
}
response := &PagesContentResponse{
ctx.JSON(http.StatusOK, &PagesContentResponse{
Title: title,
Description: description,
Readme: readme,
})
}
// ---------------------------------------------------------------------------
// PUT /config — replace full config
// ---------------------------------------------------------------------------
// UpdatePagesConfig replaces the entire landing page configuration
// PUT /api/v2/repos/{owner}/{repo}/pages/config
func UpdatePagesConfig(ctx *context.APIContext) {
if !requirePagesAdmin(ctx) {
return
}
config, ok := getPagesConfigAPI(ctx)
if !ok {
return
}
ctx.JSON(http.StatusOK, response)
form := web.GetForm(ctx).(*api.UpdatePagesConfigOption)
if form.Template != nil && !pages_module.IsValidTemplate(*form.Template) {
ctx.APIErrorWithCode(apierrors.PagesInvalidTemplate)
return
}
applyFullConfig(config, form)
if !savePagesConfigAPI(ctx, config) {
return
}
ctx.JSON(http.StatusOK, buildFullResponse(config))
}
// ---------------------------------------------------------------------------
// PATCH /config — partial merge
// ---------------------------------------------------------------------------
// PatchPagesConfig partially updates the landing page configuration
// PATCH /api/v2/repos/{owner}/{repo}/pages/config
func PatchPagesConfig(ctx *context.APIContext) {
if !requirePagesAdmin(ctx) {
return
}
config, ok := getPagesConfigAPI(ctx)
if !ok {
return
}
form := web.GetForm(ctx).(*api.UpdatePagesConfigOption)
if form.Template != nil && !pages_module.IsValidTemplate(*form.Template) {
ctx.APIErrorWithCode(apierrors.PagesInvalidTemplate)
return
}
applyFullConfig(config, form)
if !savePagesConfigAPI(ctx, config) {
return
}
ctx.JSON(http.StatusOK, buildFullResponse(config))
}
// ---------------------------------------------------------------------------
// Section PUT endpoints
// ---------------------------------------------------------------------------
// UpdatePagesBrand updates the brand section
// PUT /api/v2/repos/{owner}/{repo}/pages/config/brand
func UpdatePagesBrand(ctx *context.APIContext) {
if !requirePagesAdmin(ctx) {
return
}
config, ok := getPagesConfigAPI(ctx)
if !ok {
return
}
form := web.GetForm(ctx).(*api.UpdatePagesBrandOption)
applyBrand(&config.Brand, form)
if !savePagesConfigAPI(ctx, config) {
return
}
ctx.JSON(http.StatusOK, buildFullResponse(config))
}
// UpdatePagesHero updates the hero section
// PUT /api/v2/repos/{owner}/{repo}/pages/config/hero
func UpdatePagesHero(ctx *context.APIContext) {
if !requirePagesAdmin(ctx) {
return
}
config, ok := getPagesConfigAPI(ctx)
if !ok {
return
}
form := web.GetForm(ctx).(*api.UpdatePagesHeroOption)
applyHero(&config.Hero, form)
if !savePagesConfigAPI(ctx, config) {
return
}
ctx.JSON(http.StatusOK, buildFullResponse(config))
}
// UpdatePagesContentSection updates the content section (blog, gallery, stats, features, nav, etc.)
// PUT /api/v2/repos/{owner}/{repo}/pages/config/content
func UpdatePagesContentSection(ctx *context.APIContext) {
if !requirePagesAdmin(ctx) {
return
}
config, ok := getPagesConfigAPI(ctx)
if !ok {
return
}
form := web.GetForm(ctx).(*api.UpdatePagesContentOption)
if form.Blog != nil {
applyBlog(&config.Blog, form.Blog)
}
if form.Gallery != nil {
applyGallery(&config.Gallery, form.Gallery)
}
if form.ComparisonEnabled != nil {
config.Comparison.Enabled = *form.ComparisonEnabled
}
if form.Stats != nil {
applyStats(config, *form.Stats)
}
if form.ValueProps != nil {
applyValueProps(config, *form.ValueProps)
}
if form.Features != nil {
applyFeatures(config, *form.Features)
}
if form.Navigation != nil {
applyNavigation(&config.Navigation, form.Navigation)
}
if form.Advanced != nil {
applyAdvanced(&config.Advanced, form.Advanced)
}
if !savePagesConfigAPI(ctx, config) {
return
}
ctx.JSON(http.StatusOK, buildFullResponse(config))
}
// UpdatePagesComparison updates the comparison section
// PUT /api/v2/repos/{owner}/{repo}/pages/config/comparison
func UpdatePagesComparison(ctx *context.APIContext) {
if !requirePagesAdmin(ctx) {
return
}
config, ok := getPagesConfigAPI(ctx)
if !ok {
return
}
form := web.GetForm(ctx).(*api.UpdatePagesComparisonOption)
applyComparison(&config.Comparison, form)
if !savePagesConfigAPI(ctx, config) {
return
}
ctx.JSON(http.StatusOK, buildFullResponse(config))
}
// UpdatePagesSocial updates the social proof section
// PUT /api/v2/repos/{owner}/{repo}/pages/config/social
func UpdatePagesSocial(ctx *context.APIContext) {
if !requirePagesAdmin(ctx) {
return
}
config, ok := getPagesConfigAPI(ctx)
if !ok {
return
}
form := web.GetForm(ctx).(*api.UpdatePagesSocialOption)
applySocial(&config.SocialProof, form)
if !savePagesConfigAPI(ctx, config) {
return
}
ctx.JSON(http.StatusOK, buildFullResponse(config))
}
// UpdatePagesPricing updates the pricing section
// PUT /api/v2/repos/{owner}/{repo}/pages/config/pricing
func UpdatePagesPricing(ctx *context.APIContext) {
if !requirePagesAdmin(ctx) {
return
}
config, ok := getPagesConfigAPI(ctx)
if !ok {
return
}
form := web.GetForm(ctx).(*api.UpdatePagesPricingOption)
applyPricing(&config.Pricing, form)
if !savePagesConfigAPI(ctx, config) {
return
}
ctx.JSON(http.StatusOK, buildFullResponse(config))
}
// UpdatePagesFooter updates the footer and CTA section
// PUT /api/v2/repos/{owner}/{repo}/pages/config/footer
func UpdatePagesFooter(ctx *context.APIContext) {
if !requirePagesAdmin(ctx) {
return
}
config, ok := getPagesConfigAPI(ctx)
if !ok {
return
}
form := web.GetForm(ctx).(*api.UpdatePagesFooterOption)
applyFooter(&config.Footer, &config.CTASection, form)
if !savePagesConfigAPI(ctx, config) {
return
}
ctx.JSON(http.StatusOK, buildFullResponse(config))
}
// UpdatePagesTheme updates the theme and SEO section
// PUT /api/v2/repos/{owner}/{repo}/pages/config/theme
func UpdatePagesTheme(ctx *context.APIContext) {
if !requirePagesAdmin(ctx) {
return
}
config, ok := getPagesConfigAPI(ctx)
if !ok {
return
}
form := web.GetForm(ctx).(*api.UpdatePagesThemeOption)
applyTheme(&config.Theme, form)
if !savePagesConfigAPI(ctx, config) {
return
}
ctx.JSON(http.StatusOK, buildFullResponse(config))
}
// ---------------------------------------------------------------------------
// Utility
// ---------------------------------------------------------------------------
// loadReadmeContent loads the README content from the repository
func loadReadmeContent(ctx *context.APIContext, repo *repo_model.Repository) string {
gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
@@ -127,7 +806,6 @@ func loadReadmeContent(ctx *context.APIContext, repo *repo_model.Repository) str
return ""
}
// Try common README paths
readmePaths := []string{
"README.md",
"readme.md",

View File

@@ -148,6 +148,7 @@ func CheckAppUpdate(ctx *context.APIContext) {
ctx.APIErrorInternal(err)
return
}
latestRelease.Repo = repo
// Find the appropriate asset for this platform/arch
downloadURL, platformInfo := findUpdateAsset(latestRelease, platform, arch)
@@ -346,6 +347,7 @@ func ListReleasesV2(ctx *context.APIContext) {
// Convert to API format
apiReleases := make([]*api.Release, 0, len(releases))
for _, release := range releases {
release.Repo = repo
apiReleases = append(apiReleases, convertToAPIRelease(repo, release))
}
@@ -388,6 +390,7 @@ func GetReleaseV2(ctx *context.APIContext) {
return
}
release.Repo = repo
ctx.JSON(http.StatusOK, convertToAPIRelease(repo, release))
}
@@ -436,6 +439,7 @@ func GetLatestReleaseV2(ctx *context.APIContext) {
return
}
release.Repo = repo
ctx.JSON(http.StatusOK, convertToAPIRelease(repo, release))
}

View File

@@ -14,6 +14,7 @@ import (
_ "image/jpeg" // register JPEG decoder for social card background images
_ "image/png" // register PNG decoder for social card background images
"io"
"maps"
"math/big"
"net/http"
"path"
@@ -75,6 +76,15 @@ func ServeLandingPage(ctx *context.Context) {
}
}
// Check for static route file serving
if len(config.Advanced.StaticRoutes) > 0 {
cleanPath := path.Clean(requestPath)
if matchesStaticRoute(cleanPath, config.Advanced.StaticRoutes) {
serveStaticRouteFile(ctx, repo, cleanPath)
return
}
}
// Handle event tracking POST
if ctx.Req.Method == http.MethodPost && (requestPath == "/pages/events" || requestPath == "/pages/events/") {
servePageEvent(ctx, repo)
@@ -592,6 +602,89 @@ func serveCustomDomainAsset(ctx *context.Context, repo *repo_model.Repository, a
serveRepoFileAsset(ctx, commit, assetPath)
}
// matchesStaticRoute checks if a request path matches any configured static route pattern.
// Supports exact matches (/badge.svg) and glob patterns (/schema/*).
func matchesStaticRoute(requestPath string, routes []string) bool {
for _, route := range routes {
if route == requestPath {
return true
}
// Handle glob patterns like /schema/*
if matched, _ := path.Match(route, requestPath); matched {
return true
}
// Handle prefix patterns: /schema/* should match /schema/sub/deep.json
if strings.HasSuffix(route, "/*") {
prefix := strings.TrimSuffix(route, "*")
if strings.HasPrefix(requestPath, prefix) {
return true
}
}
}
return false
}
// serveStaticRouteFile serves a file directly from the repo tree for static route matches.
// The request path maps directly to the repo root (e.g., /schema/v0.1.json → schema/v0.1.json).
func serveStaticRouteFile(ctx *context.Context, repo *repo_model.Repository, requestPath string) {
// Strip leading slash to get repo-relative path
repoPath := strings.TrimPrefix(requestPath, "/")
if repoPath == "" {
ctx.NotFound(errors.New("empty static route path"))
return
}
// Security: never serve .gitea config files
if strings.HasPrefix(repoPath, ".gitea/") || strings.HasPrefix(repoPath, ".gitea\\") {
ctx.NotFound(errors.New("config files are not served"))
return
}
gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
if err != nil {
ctx.NotFound(err)
return
}
defer gitRepo.Close()
branch := repo.DefaultBranch
if branch == "" {
branch = "main"
}
commit, err := gitRepo.GetBranchCommit(branch)
if err != nil {
ctx.NotFound(err)
return
}
entry, err := commit.GetTreeEntryByPath(repoPath)
if err != nil {
ctx.NotFound(err)
return
}
// Only serve blobs, not trees
if !entry.IsRegular() {
ctx.NotFound(errors.New("path is not a file"))
return
}
reader, err := entry.Blob().DataAsync()
if err != nil {
ctx.ServerError("Failed to read static route file", err)
return
}
defer reader.Close()
ext := path.Ext(repoPath)
contentType := getContentType(ext)
ctx.Resp.Header().Set("Content-Type", contentType)
ctx.Resp.Header().Set("Cache-Control", "public, max-age=3600")
ctx.Resp.Header().Set("Content-Length", strconv.FormatInt(entry.Blob().Size(), 10))
_, _ = io.Copy(ctx.Resp, reader)
}
// serveSocialPreview generates and serves the social card image for a repo
// on custom domain / subdomain requests.
func serveSocialPreview(ctx *context.Context, repo *repo_model.Repository) {
@@ -1070,6 +1163,8 @@ func deepMergeConfig(base *pages_module.LandingConfig, overrideJSON string) (*pa
}
// deepMerge recursively merges src into dst.
// Maps are merged recursively; arrays are merged element-wise (preserving
// base fields like icons that the translation overlay may omit).
func deepMerge(dst, src map[string]any) map[string]any {
for key, srcVal := range src {
if dstVal, ok := dst[key]; ok {
@@ -1080,12 +1175,45 @@ func deepMerge(dst, src map[string]any) map[string]any {
dst[key] = deepMerge(dstMap, srcMap)
continue
}
// Both are arrays: merge element-wise
srcArr, srcIsArr := srcVal.([]any)
dstArr, dstIsArr := dstVal.([]any)
if srcIsArr && dstIsArr {
dst[key] = deepMergeArrays(dstArr, srcArr)
continue
}
}
dst[key] = srcVal
}
return dst
}
// deepMergeArrays merges two arrays element-wise. For each index, if both
// elements are maps, they are deep-merged (so translation fields override
// base fields while preserving untranslated fields like icons). Otherwise
// the source element replaces the base.
func deepMergeArrays(dst, src []any) []any {
result := make([]any, max(len(dst), len(src)))
for i := range result {
if i < len(src) && i < len(dst) {
srcMap, srcIsMap := src[i].(map[string]any)
dstMap, dstIsMap := dst[i].(map[string]any)
if srcIsMap && dstIsMap {
merged := make(map[string]any, len(dstMap))
maps.Copy(merged, dstMap)
result[i] = deepMerge(merged, srcMap)
} else {
result[i] = src[i]
}
} else if i < len(src) {
result[i] = src[i]
} else {
result[i] = dst[i]
}
}
return result
}
// detectPageLanguage determines the active language for a landing page.
// Priority: ?lang= query param > pages_lang cookie > Accept-Language header > default.
func detectPageLanguage(ctx *context.Context, config *pages_module.LandingConfig) string {
@@ -1138,6 +1266,11 @@ func detectPageLanguage(ctx *context.Context, config *pages_module.LandingConfig
// applyLanguageOverlay loads the translation for the detected language and merges it onto config.
// Sets template data for the language switcher and returns the (possibly merged) config.
func applyLanguageOverlay(ctx *context.Context, repo *repo_model.Repository, config *pages_module.LandingConfig) *pages_module.LandingConfig {
// Ensure navigation labels and section headlines are populated with
// template-specific defaults so they are present in the base config
// for deep-merge (and so templates never fall back to hardcoded English).
ensureTemplateDefaults(config)
if len(config.I18n.Languages) == 0 {
return config
}
@@ -1179,6 +1312,44 @@ func applyLanguageOverlay(ctx *context.Context, repo *repo_model.Repository, con
return merged
}
// ensureTemplateDefaults fills in empty navigation labels and section headlines
// with template-specific defaults so they are always present for serialization
// and deep-merge. This ensures the base config JSON contains these keys, making
// them available for the translation overlay to override.
func ensureTemplateDefaults(config *pages_module.LandingConfig) {
defaults := pages_module.TemplateDefaultLabels(config.Template)
nav := &config.Navigation
if nav.LabelValueProps == "" {
nav.LabelValueProps = defaults.LabelValueProps
}
if nav.LabelFeatures == "" {
nav.LabelFeatures = defaults.LabelFeatures
}
if nav.LabelPricing == "" {
nav.LabelPricing = defaults.LabelPricing
}
if nav.LabelBlog == "" {
nav.LabelBlog = defaults.LabelBlog
}
if nav.LabelGallery == "" {
nav.LabelGallery = defaults.LabelGallery
}
if nav.LabelCompare == "" {
nav.LabelCompare = defaults.LabelCompare
}
// Section headlines — fill empty headlines with sensible defaults
// so they appear in the base JSON and can be overridden by translations.
if config.Blog.Enabled && config.Blog.Headline == "" {
config.Blog.Headline = "Latest Posts"
}
if config.Gallery.Enabled && config.Gallery.Headline == "" {
config.Gallery.Headline = "Gallery"
}
if config.Comparison.Enabled && config.Comparison.Headline == "" {
config.Comparison.Headline = "How We Compare"
}
}
// ApproveExperiment handles the email approval link for an A/B test experiment
func ApproveExperiment(ctx *context.Context) {
handleExperimentAction(ctx, true)

View File

@@ -10,6 +10,7 @@ import (
"net/http"
"slices"
"strings"
"sync"
pages_model "code.gitcaddy.com/server/v3/models/pages"
repo_model "code.gitcaddy.com/server/v3/models/repo"
@@ -36,6 +37,7 @@ const (
tplRepoSettingsPagesFooter templates.TplName = "repo/settings/pages_footer"
tplRepoSettingsPagesTheme templates.TplName = "repo/settings/pages_theme"
tplRepoSettingsPagesLanguages templates.TplName = "repo/settings/pages_languages"
tplRepoSettingsPagesAdvanced templates.TplName = "repo/settings/pages_advanced"
)
// getPagesLandingConfig loads the landing page configuration
@@ -86,6 +88,31 @@ func savePagesLandingConfig(ctx *context.Context, config *pages_module.LandingCo
return repo_model.UpdatePagesConfig(ctx, dbConfig)
}
// applyTemplateDefaultLabels populates Navigation label fields with
// template-specific defaults. Existing non-empty labels are preserved.
func applyTemplateDefaultLabels(config *pages_module.LandingConfig) {
defaults := pages_module.TemplateDefaultLabels(config.Template)
nav := &config.Navigation
if nav.LabelValueProps == "" {
nav.LabelValueProps = defaults.LabelValueProps
}
if nav.LabelFeatures == "" {
nav.LabelFeatures = defaults.LabelFeatures
}
if nav.LabelPricing == "" {
nav.LabelPricing = defaults.LabelPricing
}
if nav.LabelBlog == "" {
nav.LabelBlog = defaults.LabelBlog
}
if nav.LabelGallery == "" {
nav.LabelGallery = defaults.LabelGallery
}
if nav.LabelCompare == "" {
nav.LabelCompare = defaults.LabelCompare
}
}
// setCommonPagesData sets common data for all pages settings pages
func setCommonPagesData(ctx *context.Context) {
config := getPagesLandingConfig(ctx)
@@ -140,6 +167,7 @@ func PagesPost(ctx *context.Context) {
config := getPagesLandingConfig(ctx)
config.Enabled = true
config.Template = template
applyTemplateDefaultLabels(config)
if err := savePagesLandingConfig(ctx, config); err != nil {
ctx.ServerError("EnablePages", err)
return
@@ -160,6 +188,7 @@ func PagesPost(ctx *context.Context) {
}
config := getPagesLandingConfig(ctx)
config.Template = template
applyTemplateDefaultLabels(config)
if err := savePagesLandingConfig(ctx, config); err != nil {
ctx.ServerError("UpdateTemplate", err)
return
@@ -716,19 +745,100 @@ func PagesThemePost(ctx *context.Context) {
ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/pages/theme")
}
// TranslationView is a flattened view of a translation for the settings UI
// TranslationView is a flattened view of a translation for the settings UI.
// Slice fields are padded to match the base config array lengths.
type TranslationView struct {
Headline string
Subheadline string
PrimaryCTA string
SecondaryCTA string
// Brand
BrandName string
BrandTagline string
// Hero
Headline string
Subheadline string
PrimaryCTA string
SecondaryCTA string
// Stats (parallel to Config.Stats)
StatsValues []string
StatsLabels []string
// Value Props (parallel to Config.ValueProps)
ValuePropTitles []string
ValuePropDescs []string
// Features (parallel to Config.Features)
FeatureTitles []string
FeatureDescs []string
// Testimonials (parallel to Config.SocialProof.Testimonials)
TestimonialQuotes []string
TestimonialRoles []string
// Pricing
PricingHeadline string
PricingSubheadline string
PlanNames []string
PlanPeriods []string
PlanCTAs []string
// CTA Section
CTAHeadline string
CTASubheadline string
CTAButton string
// Blog
BlogHeadline string
BlogSubheadline string
BlogCTAButton string
// Gallery
GalleryHeadline string
GallerySubheadline string
// Comparison
ComparisonHeadline string
ComparisonSubheadline string
// Footer
FooterCopyright string
FooterLinkLabels []string
// SEO
SEOTitle string
SEODescription string
// Navigation labels
NavLabelValueProps string
NavLabelFeatures string
NavLabelPricing string
NavLabelBlog string
NavLabelGallery string
NavLabelCompare string
NavLabelDocs string
NavLabelReleases string
NavLabelAPI string
NavLabelIssues string
}
// parseTranslationView parses a PagesTranslation into a flat view for the template
func parseTranslationView(t *pages_model.Translation) *TranslationView {
// overlayString extracts a string from a map
func overlayString(m map[string]any, key string) string {
if v, ok := m[key].(string); ok {
return v
}
return ""
}
// overlayStringSlice extracts a []string-field slice from an overlay array of objects.
// Each array element is a map; fieldKey is the key to extract from each map.
func overlayStringSlice(overlay map[string]any, arrayKey, fieldKey string, padLen int) []string {
result := make([]string, padLen)
arr, ok := overlay[arrayKey].([]any)
if !ok {
return result
}
for i, item := range arr {
if i >= padLen {
break
}
if m, ok := item.(map[string]any); ok {
if v, ok := m[fieldKey].(string); ok {
result[i] = v
}
}
}
return result
}
// parseTranslationView parses a PagesTranslation into a flat view for the template.
// config is used to determine array lengths for padding slices.
func parseTranslationView(t *pages_model.Translation, config *pages_module.LandingConfig) *TranslationView {
if t == nil || t.ConfigJSON == "" {
return nil
}
@@ -738,37 +848,149 @@ func parseTranslationView(t *pages_model.Translation) *TranslationView {
}
view := &TranslationView{}
// Brand
if brand, ok := overlay["brand"].(map[string]any); ok {
view.BrandName = overlayString(brand, "name")
view.BrandTagline = overlayString(brand, "tagline")
}
// Hero
if hero, ok := overlay["hero"].(map[string]any); ok {
if v, ok := hero["headline"].(string); ok {
view.Headline = v
}
if v, ok := hero["subheadline"].(string); ok {
view.Subheadline = v
}
view.Headline = overlayString(hero, "headline")
view.Subheadline = overlayString(hero, "subheadline")
if cta, ok := hero["primary_cta"].(map[string]any); ok {
if v, ok := cta["label"].(string); ok {
view.PrimaryCTA = v
}
view.PrimaryCTA = overlayString(cta, "label")
}
if cta, ok := hero["secondary_cta"].(map[string]any); ok {
if v, ok := cta["label"].(string); ok {
view.SecondaryCTA = v
view.SecondaryCTA = overlayString(cta, "label")
}
}
// Stats
view.StatsValues = overlayStringSlice(overlay, "stats", "value", len(config.Stats))
view.StatsLabels = overlayStringSlice(overlay, "stats", "label", len(config.Stats))
// Value Props
view.ValuePropTitles = overlayStringSlice(overlay, "value_props", "title", len(config.ValueProps))
view.ValuePropDescs = overlayStringSlice(overlay, "value_props", "description", len(config.ValueProps))
// Features
view.FeatureTitles = overlayStringSlice(overlay, "features", "title", len(config.Features))
view.FeatureDescs = overlayStringSlice(overlay, "features", "description", len(config.Features))
// Testimonials (stored under social_proof.testimonials)
view.TestimonialQuotes = make([]string, len(config.SocialProof.Testimonials))
view.TestimonialRoles = make([]string, len(config.SocialProof.Testimonials))
if sp, ok := overlay["social_proof"].(map[string]any); ok {
if arr, ok := sp["testimonials"].([]any); ok {
for i, item := range arr {
if i >= len(config.SocialProof.Testimonials) {
break
}
if m, ok := item.(map[string]any); ok {
view.TestimonialQuotes[i] = overlayString(m, "quote")
view.TestimonialRoles[i] = overlayString(m, "role")
}
}
}
}
// Pricing
if pricing, ok := overlay["pricing"].(map[string]any); ok {
view.PricingHeadline = overlayString(pricing, "headline")
view.PricingSubheadline = overlayString(pricing, "subheadline")
if plans, ok := pricing["plans"].([]any); ok {
view.PlanNames = make([]string, len(config.Pricing.Plans))
view.PlanPeriods = make([]string, len(config.Pricing.Plans))
view.PlanCTAs = make([]string, len(config.Pricing.Plans))
for i, item := range plans {
if i >= len(config.Pricing.Plans) {
break
}
if m, ok := item.(map[string]any); ok {
view.PlanNames[i] = overlayString(m, "name")
view.PlanPeriods[i] = overlayString(m, "period")
view.PlanCTAs[i] = overlayString(m, "cta")
}
}
} else {
view.PlanNames = make([]string, len(config.Pricing.Plans))
view.PlanPeriods = make([]string, len(config.Pricing.Plans))
view.PlanCTAs = make([]string, len(config.Pricing.Plans))
}
} else {
view.PlanNames = make([]string, len(config.Pricing.Plans))
view.PlanPeriods = make([]string, len(config.Pricing.Plans))
view.PlanCTAs = make([]string, len(config.Pricing.Plans))
}
// CTA Section
if ctaSec, ok := overlay["cta_section"].(map[string]any); ok {
if v, ok := ctaSec["headline"].(string); ok {
view.CTAHeadline = v
}
if v, ok := ctaSec["subheadline"].(string); ok {
view.CTASubheadline = v
}
view.CTAHeadline = overlayString(ctaSec, "headline")
view.CTASubheadline = overlayString(ctaSec, "subheadline")
if btn, ok := ctaSec["button"].(map[string]any); ok {
if v, ok := btn["label"].(string); ok {
view.CTAButton = v
view.CTAButton = overlayString(btn, "label")
}
}
// Blog
if blog, ok := overlay["blog"].(map[string]any); ok {
view.BlogHeadline = overlayString(blog, "headline")
view.BlogSubheadline = overlayString(blog, "subheadline")
if btn, ok := blog["cta_button"].(map[string]any); ok {
view.BlogCTAButton = overlayString(btn, "label")
}
}
// Gallery
if gallery, ok := overlay["gallery"].(map[string]any); ok {
view.GalleryHeadline = overlayString(gallery, "headline")
view.GallerySubheadline = overlayString(gallery, "subheadline")
}
// Comparison
if comp, ok := overlay["comparison"].(map[string]any); ok {
view.ComparisonHeadline = overlayString(comp, "headline")
view.ComparisonSubheadline = overlayString(comp, "subheadline")
}
// Footer
view.FooterLinkLabels = make([]string, len(config.Footer.Links))
if footer, ok := overlay["footer"].(map[string]any); ok {
view.FooterCopyright = overlayString(footer, "copyright")
if links, ok := footer["links"].([]any); ok {
for i, item := range links {
if i >= len(config.Footer.Links) {
break
}
if m, ok := item.(map[string]any); ok {
view.FooterLinkLabels[i] = overlayString(m, "label")
}
}
}
}
// SEO
if seo, ok := overlay["seo"].(map[string]any); ok {
view.SEOTitle = overlayString(seo, "title")
view.SEODescription = overlayString(seo, "description")
}
// Navigation labels
if nav, ok := overlay["navigation"].(map[string]any); ok {
view.NavLabelValueProps = overlayString(nav, "label_value_props")
view.NavLabelFeatures = overlayString(nav, "label_features")
view.NavLabelPricing = overlayString(nav, "label_pricing")
view.NavLabelBlog = overlayString(nav, "label_blog")
view.NavLabelGallery = overlayString(nav, "label_gallery")
view.NavLabelCompare = overlayString(nav, "label_compare")
view.NavLabelDocs = overlayString(nav, "label_docs")
view.NavLabelReleases = overlayString(nav, "label_releases")
view.NavLabelAPI = overlayString(nav, "label_api")
view.NavLabelIssues = overlayString(nav, "label_issues")
}
return view
}
@@ -776,6 +998,19 @@ func parseTranslationView(t *pages_model.Translation) *TranslationView {
func buildTranslationJSON(ctx *context.Context) string {
overlay := map[string]any{}
// Brand
brand := map[string]any{}
if v := ctx.FormString("trans_brand_name"); v != "" {
brand["name"] = v
}
if v := ctx.FormString("trans_brand_tagline"); v != "" {
brand["tagline"] = v
}
if len(brand) > 0 {
overlay["brand"] = brand
}
// Hero
hero := map[string]any{}
if v := ctx.FormString("trans_headline"); v != "" {
hero["headline"] = v
@@ -793,6 +1028,103 @@ func buildTranslationJSON(ctx *context.Context) string {
overlay["hero"] = hero
}
// Stats (indexed)
var stats []map[string]any
for i := range 20 {
v := ctx.FormString(fmt.Sprintf("trans_stat_%d_value", i))
l := ctx.FormString(fmt.Sprintf("trans_stat_%d_label", i))
if v == "" && l == "" {
// Check if there are more by looking ahead
if ctx.FormString(fmt.Sprintf("trans_stat_%d_value", i+1)) == "" &&
ctx.FormString(fmt.Sprintf("trans_stat_%d_label", i+1)) == "" {
break
}
}
stats = append(stats, map[string]any{"value": v, "label": l})
}
if len(stats) > 0 {
overlay["stats"] = stats
}
// Value Props (indexed)
var valueProps []map[string]any
for i := range 20 {
t := ctx.FormString(fmt.Sprintf("trans_vp_%d_title", i))
d := ctx.FormString(fmt.Sprintf("trans_vp_%d_desc", i))
if t == "" && d == "" {
if ctx.FormString(fmt.Sprintf("trans_vp_%d_title", i+1)) == "" &&
ctx.FormString(fmt.Sprintf("trans_vp_%d_desc", i+1)) == "" {
break
}
}
valueProps = append(valueProps, map[string]any{"title": t, "description": d})
}
if len(valueProps) > 0 {
overlay["value_props"] = valueProps
}
// Features (indexed)
var features []map[string]any
for i := range 20 {
t := ctx.FormString(fmt.Sprintf("trans_feat_%d_title", i))
d := ctx.FormString(fmt.Sprintf("trans_feat_%d_desc", i))
if t == "" && d == "" {
if ctx.FormString(fmt.Sprintf("trans_feat_%d_title", i+1)) == "" &&
ctx.FormString(fmt.Sprintf("trans_feat_%d_desc", i+1)) == "" {
break
}
}
features = append(features, map[string]any{"title": t, "description": d})
}
if len(features) > 0 {
overlay["features"] = features
}
// Testimonials (indexed)
var testimonials []map[string]any
for i := range 20 {
q := ctx.FormString(fmt.Sprintf("trans_test_%d_quote", i))
r := ctx.FormString(fmt.Sprintf("trans_test_%d_role", i))
if q == "" && r == "" {
if ctx.FormString(fmt.Sprintf("trans_test_%d_quote", i+1)) == "" &&
ctx.FormString(fmt.Sprintf("trans_test_%d_role", i+1)) == "" {
break
}
}
testimonials = append(testimonials, map[string]any{"quote": q, "role": r})
}
if len(testimonials) > 0 {
overlay["social_proof"] = map[string]any{"testimonials": testimonials}
}
// Pricing
pricing := map[string]any{}
if v := ctx.FormString("trans_pricing_headline"); v != "" {
pricing["headline"] = v
}
if v := ctx.FormString("trans_pricing_subheadline"); v != "" {
pricing["subheadline"] = v
}
var plans []map[string]any
for i := range 10 {
n := ctx.FormString(fmt.Sprintf("trans_plan_%d_name", i))
p := ctx.FormString(fmt.Sprintf("trans_plan_%d_period", i))
c := ctx.FormString(fmt.Sprintf("trans_plan_%d_cta", i))
if n == "" && p == "" && c == "" {
if ctx.FormString(fmt.Sprintf("trans_plan_%d_name", i+1)) == "" {
break
}
}
plans = append(plans, map[string]any{"name": n, "period": p, "cta": c})
}
if len(plans) > 0 {
pricing["plans"] = plans
}
if len(pricing) > 0 {
overlay["pricing"] = pricing
}
// CTA Section
ctaSec := map[string]any{}
if v := ctx.FormString("trans_cta_headline"); v != "" {
ctaSec["headline"] = v
@@ -807,6 +1139,94 @@ func buildTranslationJSON(ctx *context.Context) string {
overlay["cta_section"] = ctaSec
}
// Blog
blog := map[string]any{}
if v := ctx.FormString("trans_blog_headline"); v != "" {
blog["headline"] = v
}
if v := ctx.FormString("trans_blog_subheadline"); v != "" {
blog["subheadline"] = v
}
if v := ctx.FormString("trans_blog_cta"); v != "" {
blog["cta_button"] = map[string]any{"label": v}
}
if len(blog) > 0 {
overlay["blog"] = blog
}
// Gallery
gallery := map[string]any{}
if v := ctx.FormString("trans_gallery_headline"); v != "" {
gallery["headline"] = v
}
if v := ctx.FormString("trans_gallery_subheadline"); v != "" {
gallery["subheadline"] = v
}
if len(gallery) > 0 {
overlay["gallery"] = gallery
}
// Comparison
comp := map[string]any{}
if v := ctx.FormString("trans_comparison_headline"); v != "" {
comp["headline"] = v
}
if v := ctx.FormString("trans_comparison_subheadline"); v != "" {
comp["subheadline"] = v
}
if len(comp) > 0 {
overlay["comparison"] = comp
}
// Footer
footer := map[string]any{}
if v := ctx.FormString("trans_footer_copyright"); v != "" {
footer["copyright"] = v
}
var footerLinks []map[string]any
for i := range 20 {
l := ctx.FormString(fmt.Sprintf("trans_footer_link_%d", i))
if l == "" {
if ctx.FormString(fmt.Sprintf("trans_footer_link_%d", i+1)) == "" {
break
}
}
footerLinks = append(footerLinks, map[string]any{"label": l})
}
if len(footerLinks) > 0 {
footer["links"] = footerLinks
}
if len(footer) > 0 {
overlay["footer"] = footer
}
// SEO
seo := map[string]any{}
if v := ctx.FormString("trans_seo_title"); v != "" {
seo["title"] = v
}
if v := ctx.FormString("trans_seo_description"); v != "" {
seo["description"] = v
}
if len(seo) > 0 {
overlay["seo"] = seo
}
// Navigation labels
nav := map[string]any{}
for _, key := range []string{
"label_value_props", "label_features", "label_pricing",
"label_blog", "label_gallery", "label_compare",
"label_docs", "label_releases", "label_api", "label_issues",
} {
if v := ctx.FormString("trans_nav_" + key); v != "" {
nav[key] = v
}
}
if len(nav) > 0 {
overlay["navigation"] = nav
}
if len(overlay) == 0 {
return ""
}
@@ -823,18 +1243,19 @@ func PagesLanguages(ctx *context.Context) {
config := getPagesLandingConfig(ctx)
ctx.Data["LanguageNames"] = pages_module.LanguageDisplayNames()
// Build a function to check if a language is enabled
enabledLangs := config.I18n.Languages
ctx.Data["IsLangEnabled"] = func(code string) bool {
return slices.Contains(enabledLangs, code)
// Build a map to check if a language is enabled
enabledLangsMap := make(map[string]bool)
for _, code := range config.I18n.Languages {
enabledLangsMap[code] = true
}
ctx.Data["EnabledLangs"] = enabledLangsMap
// Load translations into a map[lang]*TranslationView
translationMap := make(map[string]*TranslationView)
translations, err := pages_model.GetTranslationsByRepoID(ctx, ctx.Repo.Repository.ID)
if err == nil {
for _, t := range translations {
translationMap[t.Lang] = parseTranslationView(t)
translationMap[t.Lang] = parseTranslationView(t, config)
}
}
ctx.Data["TranslationMap"] = translationMap
@@ -953,6 +1374,73 @@ func PagesLanguagesPost(ctx *context.Context) {
}
}
ctx.Flash.Success(ctx.Tr("repo.settings.pages.ai_translate_success"))
case "ai_translate_all":
defaultLang := config.I18n.DefaultLang
if defaultLang == "" {
defaultLang = "en"
}
var (
mu sync.Mutex
successCount int
failCount int
wg sync.WaitGroup
)
repoID := ctx.Repo.Repository.ID
repo := ctx.Repo.Repository
for _, lang := range config.I18n.Languages {
if lang == defaultLang {
continue
}
wg.Add(1)
go func(lang string) {
defer wg.Done()
translated, err := pages_service.TranslateLandingPageContent(ctx, repo, config, lang)
if err != nil {
log.Error("AI translation failed for %s: %v", lang, err)
mu.Lock()
failCount++
mu.Unlock()
return
}
mu.Lock()
defer mu.Unlock()
existing, err := pages_model.GetTranslation(ctx, repoID, lang)
if err != nil {
log.Error("GetTranslation failed for %s: %v", lang, err)
failCount++
return
}
if existing != nil {
existing.ConfigJSON = translated
existing.AutoGenerated = true
if err := pages_model.UpdateTranslation(ctx, existing); err != nil {
log.Error("UpdateTranslation failed for %s: %v", lang, err)
failCount++
return
}
} else {
t := &pages_model.Translation{
RepoID: repoID,
Lang: lang,
ConfigJSON: translated,
AutoGenerated: true,
}
if err := pages_model.CreateTranslation(ctx, t); err != nil {
log.Error("CreateTranslation failed for %s: %v", lang, err)
failCount++
return
}
}
successCount++
}(lang)
}
wg.Wait()
if failCount == 0 {
ctx.Flash.Success(ctx.Tr("repo.settings.pages.ai_translate_all_success", successCount))
} else {
ctx.Flash.Warning(ctx.Tr("repo.settings.pages.ai_translate_all_partial", successCount, successCount+failCount, failCount))
}
}
ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/pages/languages")
@@ -992,3 +1480,64 @@ func loadRawReadme(ctx *context.Context, repo *repo_model.Repository) string {
}
return ""
}
// PagesAdvanced renders the advanced settings page
func PagesAdvanced(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.pages.advanced")
ctx.Data["PageIsSettingsPages"] = true
ctx.Data["PageIsSettingsPagesAdvanced"] = true
setCommonPagesData(ctx)
ctx.HTML(http.StatusOK, tplRepoSettingsPagesAdvanced)
}
// PagesAdvancedPost handles the advanced settings form submission
func PagesAdvancedPost(ctx *context.Context) {
config := getPagesLandingConfig(ctx)
// Parse static routes
var routes []string
for i := range 100 {
route := strings.TrimSpace(ctx.FormString(fmt.Sprintf("static_route_%d", i)))
if route != "" {
routes = append(routes, route)
}
}
config.Advanced.StaticRoutes = routes
// Parse redirects
redirects := make(map[string]string)
for i := range 100 {
from := strings.TrimSpace(ctx.FormString(fmt.Sprintf("redirect_from_%d", i)))
to := strings.TrimSpace(ctx.FormString(fmt.Sprintf("redirect_to_%d", i)))
if from != "" && to != "" {
redirects[from] = to
}
}
// Also handle redirects keyed by path (from existing entries)
for key, vals := range ctx.Req.Form {
if strings.HasPrefix(key, "redirect_from_/") {
from := strings.TrimSpace(vals[0])
toKey := "redirect_to_" + strings.TrimPrefix(key, "redirect_from_")
to := strings.TrimSpace(ctx.Req.FormValue(toKey))
if from != "" && to != "" {
redirects[from] = to
}
}
}
if len(redirects) > 0 {
config.Advanced.Redirects = redirects
} else {
config.Advanced.Redirects = nil
}
// Parse remaining fields
config.Advanced.CustomCSS = ctx.FormString("custom_css")
config.Advanced.CustomHead = ctx.FormString("custom_head")
if err := savePagesLandingConfig(ctx, config); err != nil {
ctx.ServerError("SavePagesConfig", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.pages.saved"))
ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/pages/advanced")
}

View File

@@ -1347,6 +1347,7 @@ func registerWebRoutes(m *web.Router) {
m.Combo("/footer").Get(repo_setting.PagesFooter).Post(repo_setting.PagesFooterPost)
m.Combo("/theme").Get(repo_setting.PagesTheme).Post(repo_setting.PagesThemePost)
m.Combo("/languages").Get(repo_setting.PagesLanguages).Post(repo_setting.PagesLanguagesPost)
m.Combo("/advanced").Get(repo_setting.PagesAdvanced).Post(repo_setting.PagesAdvancedPost)
})
m.Group("/actions/general", func() {
m.Get("", repo_setting.ActionsGeneralSettings)

View File

@@ -219,25 +219,160 @@ func TranslateLandingPageContent(ctx context.Context, repo *repo_model.Repositor
// buildTranslatableContent extracts translatable text from a config for the AI
func buildTranslatableContent(config *pages_module.LandingConfig) string {
data, _ := json.Marshal(map[string]any{
"hero": map[string]any{
"headline": config.Hero.Headline,
"subheadline": config.Hero.Subheadline,
"primary_cta": map[string]string{"label": config.Hero.PrimaryCTA.Label},
"secondary_cta": map[string]string{"label": config.Hero.SecondaryCTA.Label},
},
"stats": config.Stats,
"value_props": config.ValueProps,
"features": config.Features,
"cta_section": map[string]any{
"headline": config.CTASection.Headline,
"subheadline": config.CTASection.Subheadline,
"button": map[string]string{"label": config.CTASection.Button.Label},
},
"seo": map[string]any{
content := map[string]any{}
// Brand
if config.Brand.Name != "" || config.Brand.Tagline != "" {
content["brand"] = map[string]any{
"name": config.Brand.Name,
"tagline": config.Brand.Tagline,
}
}
// Hero
content["hero"] = map[string]any{
"headline": config.Hero.Headline,
"subheadline": config.Hero.Subheadline,
"primary_cta": map[string]string{"label": config.Hero.PrimaryCTA.Label},
"secondary_cta": map[string]string{"label": config.Hero.SecondaryCTA.Label},
}
// Stats, Value Props, Features (already struct-serializable)
if len(config.Stats) > 0 {
content["stats"] = config.Stats
}
if len(config.ValueProps) > 0 {
content["value_props"] = config.ValueProps
}
if len(config.Features) > 0 {
content["features"] = config.Features
}
// Testimonials
if len(config.SocialProof.Testimonials) > 0 {
testimonials := make([]map[string]string, 0, len(config.SocialProof.Testimonials))
for _, t := range config.SocialProof.Testimonials {
testimonials = append(testimonials, map[string]string{
"quote": t.Quote,
"role": t.Role,
})
}
content["social_proof"] = map[string]any{"testimonials": testimonials}
}
// Pricing
if config.Pricing.Headline != "" || len(config.Pricing.Plans) > 0 {
pricing := map[string]any{
"headline": config.Pricing.Headline,
"subheadline": config.Pricing.Subheadline,
}
if len(config.Pricing.Plans) > 0 {
plans := make([]map[string]string, 0, len(config.Pricing.Plans))
for _, p := range config.Pricing.Plans {
plans = append(plans, map[string]string{
"name": p.Name,
"period": p.Period,
"cta": p.CTA,
})
}
pricing["plans"] = plans
}
content["pricing"] = pricing
}
// CTA Section
content["cta_section"] = map[string]any{
"headline": config.CTASection.Headline,
"subheadline": config.CTASection.Subheadline,
"button": map[string]string{"label": config.CTASection.Button.Label},
}
// Blog
if config.Blog.Enabled {
blogHeadline := config.Blog.Headline
if blogHeadline == "" {
blogHeadline = "Latest Posts"
}
blog := map[string]any{
"headline": blogHeadline,
"subheadline": config.Blog.Subheadline,
}
if config.Blog.CTAButton.Label != "" {
blog["cta_button"] = map[string]string{"label": config.Blog.CTAButton.Label}
}
content["blog"] = blog
}
// Gallery
if config.Gallery.Enabled {
galleryHeadline := config.Gallery.Headline
if galleryHeadline == "" {
galleryHeadline = "Gallery"
}
content["gallery"] = map[string]any{
"headline": galleryHeadline,
"subheadline": config.Gallery.Subheadline,
}
}
// Comparison
if config.Comparison.Enabled {
compHeadline := config.Comparison.Headline
if compHeadline == "" {
compHeadline = "How We Compare"
}
content["comparison"] = map[string]any{
"headline": compHeadline,
"subheadline": config.Comparison.Subheadline,
}
}
// Footer
if config.Footer.Copyright != "" || len(config.Footer.Links) > 0 {
footer := map[string]any{
"copyright": config.Footer.Copyright,
}
if len(config.Footer.Links) > 0 {
links := make([]map[string]string, 0, len(config.Footer.Links))
for _, l := range config.Footer.Links {
links = append(links, map[string]string{"label": l.Label})
}
footer["links"] = links
}
content["footer"] = footer
}
// SEO
if config.SEO.Title != "" || config.SEO.Description != "" {
content["seo"] = map[string]any{
"title": config.SEO.Title,
"description": config.SEO.Description,
},
})
}
}
// Navigation labels (for translating nav items and section headers)
// Use template-specific defaults so AI translates the correct terms
// (e.g. "Systems Analysis" for architecture-deep-dive, not generic "Value Props")
defaults := pages_module.TemplateDefaultLabels(config.Template)
labelOrDefault := func(label, def string) string {
if label != "" {
return label
}
return def
}
content["navigation"] = map[string]any{
"label_value_props": labelOrDefault(config.Navigation.LabelValueProps, defaults.LabelValueProps),
"label_features": labelOrDefault(config.Navigation.LabelFeatures, defaults.LabelFeatures),
"label_pricing": labelOrDefault(config.Navigation.LabelPricing, defaults.LabelPricing),
"label_blog": labelOrDefault(config.Navigation.LabelBlog, defaults.LabelBlog),
"label_gallery": labelOrDefault(config.Navigation.LabelGallery, defaults.LabelGallery),
"label_compare": labelOrDefault(config.Navigation.LabelCompare, defaults.LabelCompare),
"label_docs": labelOrDefault(config.Navigation.LabelDocs, "Docs"),
"label_releases": labelOrDefault(config.Navigation.LabelReleases, "Releases"),
"label_api": labelOrDefault(config.Navigation.LabelAPI, "API"),
"label_issues": labelOrDefault(config.Navigation.LabelIssues, "Issues"),
}
data, _ := json.Marshal(content)
return string(data)
}

View File

@@ -1006,16 +1006,16 @@
{{range .Config.Footer.Links}}
<a href="{{.URL}}" class="ad-nav-link">{{.Label}}</a>
{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="ad-nav-link">Docs</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="ad-nav-link">API</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="ad-nav-link">Releases</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="ad-nav-link">Issues</a>{{end}}
{{if .Config.ValueProps}}<a href="{{.LandingURL}}#value-props" class="ad-nav-link">Value Props</a>{{end}}
{{if .Config.Features}}<a href="{{.LandingURL}}#features" class="ad-nav-link">Features</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="{{.LandingURL}}#pricing" class="ad-nav-link">Pricing</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="ad-nav-link">Blog</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="ad-nav-link">Gallery</a>{{end}}
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="ad-nav-link">Compare</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="ad-nav-link">{{if .Config.Navigation.LabelDocs}}{{.Config.Navigation.LabelDocs}}{{else}}Docs{{end}}</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="ad-nav-link">{{if .Config.Navigation.LabelAPI}}{{.Config.Navigation.LabelAPI}}{{else}}API{{end}}</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="ad-nav-link">{{if .Config.Navigation.LabelReleases}}{{.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="ad-nav-link">{{if .Config.Navigation.LabelIssues}}{{.Config.Navigation.LabelIssues}}{{else}}Issues{{end}}</a>{{end}}
{{if .Config.ValueProps}}<a href="{{.LandingURL}}#value-props" class="ad-nav-link">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Value Props{{end}}</a>{{end}}
{{if .Config.Features}}<a href="{{.LandingURL}}#features" class="ad-nav-link">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="{{.LandingURL}}#pricing" class="ad-nav-link">{{if .Config.Navigation.LabelPricing}}{{.Config.Navigation.LabelPricing}}{{else}}Pricing{{end}}</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="ad-nav-link">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="ad-nav-link">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</a>{{end}}
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="ad-nav-link">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</a>{{end}}
{{if .Config.Navigation.ShowRepository}}
<a href="{{.RepoURL}}" class="ad-nav-cta">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
@@ -1071,7 +1071,7 @@
<section class="ad-features" style="padding-top: 120px;">
<div class="ad-features-inner">
<div class="ad-section-header ad-reveal">
<div class="ad-section-label">Blog</div>
<div class="ad-section-label">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</div>
<h2>{{if .Config.Blog.Headline}}{{.Config.Blog.Headline}}{{else}}All Posts{{end}}</h2>
{{if .Config.Blog.Subheadline}}<p>{{.Config.Blog.Subheadline}}</p>{{end}}
</div>
@@ -1102,7 +1102,7 @@
</section>
{{else}}
<!-- Hero Section -->
<section class="ad-hero">
<section class="ad-hero" id="hero">
<div class="ad-hero-content">
<div class="ad-badge ad-reveal visible">
<span class="ad-badge-dot"></span>
@@ -1123,11 +1123,6 @@
{{.Config.Hero.PrimaryCTA.Label}}
{{svg "octicon-arrow-right" 16}}
</a>
{{else}}
<a href="{{.RepoURL}}" class="ad-btn-primary" data-cta="primary">
Get Started
{{svg "octicon-arrow-right" 16}}
</a>
{{end}}
{{if .Config.Hero.SecondaryCTA.Label}}
@@ -1135,14 +1130,26 @@
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
{{.Config.Hero.SecondaryCTA.Label}}
</a>
{{else}}
<a href="{{.RepoURL}}" class="ad-btn-secondary" data-cta="secondary">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
View Source
</a>
{{end}}
</div>
{{if or $.GooglePlayID $.AppStoreID}}
<div style="display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; margin-top: 16px;" class="ad-reveal visible ad-reveal-delay-4">
{{if $.GooglePlayID}}
<a href="https://play.google.com/store/apps/details?id={{$.GooglePlayID}}" target="_blank" rel="noopener" class="ad-download-item" style="display: inline-flex; align-items: center; gap: 10px; clip-path: none;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3.609 1.814L13.792 12 3.61 22.186a.996.996 0 0 1-.61-.92V2.734a1 1 0 0 1 .609-.92zm10.89 10.893l2.302 2.302-10.937 6.333 8.635-8.635zm3.199-1.4l2.834 1.64a1 1 0 0 1 0 1.726l-2.834 1.64-2.635-2.636 2.635-2.37zM5.864 2.658L16.8 9.99l-2.302 2.302-8.635-8.635z"/></svg>
Google Play
</a>
{{end}}
{{if $.AppStoreID}}
<a href="https://apps.apple.com/app/{{$.AppStoreID}}" target="_blank" rel="noopener" class="ad-download-item" style="display: inline-flex; align-items: center; gap: 10px; clip-path: none;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>
App Store
</a>
{{end}}
</div>
{{end}}
{{if .Config.Hero.CodeExample}}
<div class="ad-code-block ad-reveal visible ad-reveal-delay-4">
<span class="ad-code-prompt">$</span>
@@ -1157,7 +1164,7 @@
<!-- Stats Section -->
{{if or .Config.Stats (gt .NumStars 0)}}
<section class="ad-stats">
<section class="ad-stats" id="stats">
<div class="ad-stats-inner ad-reveal">
{{if .Config.Stats}}
{{range .Config.Stats}}
@@ -1188,7 +1195,7 @@
<!-- Downloads Section -->
{{if and .PublicReleases .LatestRelease .LatestRelease.Attachments}}
<section class="ad-downloads">
<section class="ad-downloads" id="downloads">
<div class="ad-downloads-inner ad-reveal">
<h2>Download v{{.LatestReleaseTag}}</h2>
<p>Get the latest release</p>
@@ -1251,25 +1258,6 @@
</div>
{{end}}
{{end}}
{{if or $.GooglePlayID $.AppStoreID}}
<div style="margin-bottom: 24px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 13px; color: var(--ad-muted); font-family: 'Barlow Condensed', sans-serif; letter-spacing: 0.12em; text-transform: uppercase;">{{svg "octicon-device-mobile" 16}} App Stores</h4>
<div style="display: flex; gap: 12px; flex-wrap: wrap;">
{{if $.GooglePlayID}}
<a href="https://play.google.com/store/apps/details?id={{$.GooglePlayID}}" target="_blank" rel="noopener" class="ad-download-item" style="display: inline-flex; align-items: center; gap: 10px; clip-path: none;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3.609 1.814L13.792 12 3.61 22.186a.996.996 0 0 1-.61-.92V2.734a1 1 0 0 1 .609-.92zm10.89 10.893l2.302 2.302-10.937 6.333 8.635-8.635zm3.199-1.4l2.834 1.64a1 1 0 0 1 0 1.726l-2.834 1.64-2.635-2.636 2.635-2.37zM5.864 2.658L16.8 9.99l-2.302 2.302-8.635-8.635z"/></svg>
Google Play
</a>
{{end}}
{{if $.AppStoreID}}
<a href="https://apps.apple.com/app/{{$.AppStoreID}}" target="_blank" rel="noopener" class="ad-download-item" style="display: inline-flex; align-items: center; gap: 10px; clip-path: none;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>
App Store
</a>
{{end}}
</div>
</div>
{{end}}
{{if $otherFiles}}
<div style="margin-bottom: 24px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 13px; color: var(--ad-muted); font-family: 'Barlow Condensed', sans-serif; letter-spacing: 0.12em; text-transform: uppercase;">{{svg "octicon-file" 16}} Other</h4>
@@ -1287,10 +1275,9 @@
<section class="ad-features" id="value-props">
<div class="ad-features-inner">
<div class="ad-section-header ad-reveal">
<div class="ad-section-label">Systems Analysis</div>
<h2>{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}</h2>
<p>Everything you need to get started quickly.</p>
</div>
<div class="ad-section-label">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Systems Analysis{{end}}</div>
<h2>{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}{{end}}</h2>
</div>
<div class="ad-features-grid">
{{range .Config.ValueProps}}
<div class="ad-feature-card ad-reveal">
@@ -1311,10 +1298,9 @@
<section class="ad-features" id="features" style="padding-top: 40px;">
<div class="ad-features-inner">
<div class="ad-section-header ad-reveal">
<div class="ad-section-label">Technical Specifications</div>
<h2>Features</h2>
<p>Powerful capabilities at your fingertips.</p>
</div>
<div class="ad-section-label">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Technical Specifications{{end}}</div>
<h2>{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</h2>
</div>
<div class="ad-features-grid">
{{range .Config.Features}}
<div class="ad-feature-card ad-reveal">
@@ -1332,7 +1318,7 @@
<!-- Social Proof -->
{{if or .Config.SocialProof.Logos .Config.SocialProof.Testimonials}}
<section class="ad-social-proof">
<section class="ad-social-proof" id="social-proof">
<div class="ad-social-proof-inner">
{{if .Config.SocialProof.Logos}}
<div class="ad-logos ad-reveal">
@@ -1372,7 +1358,7 @@
<section class="ad-pricing" id="pricing">
<div class="ad-pricing-inner">
<div class="ad-section-header ad-reveal">
<div class="ad-section-label">Resource Allocation</div>
<div class="ad-section-label">{{if .Config.Navigation.LabelPricing}}{{.Config.Navigation.LabelPricing}}{{else}}Resource Allocation{{end}}</div>
<h2>{{if .Config.Pricing.Headline}}{{.Config.Pricing.Headline}}{{else}}Pricing{{end}}</h2>
<p>{{if .Config.Pricing.Subheadline}}{{.Config.Pricing.Subheadline}}{{else}}Choose the plan that works for you{{end}}</p>
</div>
@@ -1398,7 +1384,7 @@
<!-- CTA Section -->
{{if .Config.CTASection.Headline}}
<section class="ad-cta-section">
<section class="ad-cta-section" id="cta">
<div class="ad-cta-inner ad-reveal">
<h2>{{.Config.CTASection.Headline}}</h2>
{{if .Config.CTASection.Subheadline}}
@@ -1417,7 +1403,7 @@
<section class="ad-features" id="blog" style="border-top: 1px solid var(--ad-dim);">
<div class="ad-features-inner">
<div class="ad-section-header ad-reveal">
<div class="ad-section-label">Dispatches</div>
<div class="ad-section-label">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Dispatches{{end}}</div>
<h2>{{if .Config.Blog.Headline}}{{.Config.Blog.Headline}}{{else}}Latest Posts{{end}}</h2>
{{if .Config.Blog.Subheadline}}<p>{{.Config.Blog.Subheadline}}</p>{{end}}
</div>
@@ -1456,7 +1442,7 @@
<section class="ad-features" id="gallery" style="border-top: 1px solid var(--ad-dim);">
<div class="ad-features-inner">
<div class="ad-section-header ad-reveal">
<div class="ad-section-label">Visual Index</div>
<div class="ad-section-label">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Visual Index{{end}}</div>
<h2>{{if .Config.Gallery.Headline}}{{.Config.Gallery.Headline}}{{else}}Gallery{{end}}</h2>
{{if .Config.Gallery.Subheadline}}<p>{{.Config.Gallery.Subheadline}}</p>{{end}}
</div>
@@ -1481,7 +1467,7 @@
<section class="ad-features" id="comparison" style="border-top: 1px solid var(--ad-dim);">
<div class="ad-features-inner">
<div class="ad-section-header ad-reveal">
<div class="ad-section-label">Compare</div>
<div class="ad-section-label">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</div>
<h2>{{if .Config.Comparison.Headline}}{{.Config.Comparison.Headline}}{{else}}How We Compare{{end}}</h2>
{{if .Config.Comparison.Subheadline}}<p>{{.Config.Comparison.Subheadline}}</p>{{end}}
</div>
@@ -1564,8 +1550,8 @@
{{end}}
{{if .Config.Navigation.ShowRepository}}<a href="{{.RepoURL}}" class="ad-footer-link">Repository</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="ad-footer-link">Documentation</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="ad-footer-link">Releases</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="ad-footer-link">Issues</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="ad-footer-link">{{if .Config.Navigation.LabelReleases}}{{.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="ad-footer-link">{{if .Config.Navigation.LabelIssues}}{{.Config.Navigation.LabelIssues}}{{else}}Issues{{end}}</a>{{end}}
</div>
</footer>
</div>

View File

@@ -28,6 +28,7 @@
.nb-reveal-delay-1 { transition-delay: 0.1s; }
.nb-reveal-delay-2 { transition-delay: 0.2s; }
.nb-reveal-delay-3 { transition-delay: 0.3s; }
.nb-reveal-delay-4 { transition-delay: 0.4s; }
.nb-page {
min-height: 100vh;
@@ -1128,16 +1129,16 @@
{{range .Config.Footer.Links}}
<a href="{{.URL}}" class="nb-nav-link">{{.Label}}</a>
{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="nb-nav-link">Docs</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="nb-nav-link">API</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="nb-nav-link">Releases</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="nb-nav-link">Issues</a>{{end}}
{{if .Config.ValueProps}}<a href="{{.LandingURL}}#value-props" class="nb-nav-link">Why</a>{{end}}
{{if .Config.Features}}<a href="{{.LandingURL}}#features" class="nb-nav-link">Features</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="{{.LandingURL}}#pricing" class="nb-nav-link">Pricing</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="nb-nav-link">Blog</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="nb-nav-link">Gallery</a>{{end}}
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="nb-nav-link">Compare</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="nb-nav-link">{{if .Config.Navigation.LabelDocs}}{{.Config.Navigation.LabelDocs}}{{else}}Docs{{end}}</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="nb-nav-link">{{if .Config.Navigation.LabelAPI}}{{.Config.Navigation.LabelAPI}}{{else}}API{{end}}</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="nb-nav-link">{{if .Config.Navigation.LabelReleases}}{{.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="nb-nav-link">{{if .Config.Navigation.LabelIssues}}{{.Config.Navigation.LabelIssues}}{{else}}Issues{{end}}</a>{{end}}
{{if .Config.ValueProps}}<a href="{{.LandingURL}}#value-props" class="nb-nav-link">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Why{{end}}</a>{{end}}
{{if .Config.Features}}<a href="{{.LandingURL}}#features" class="nb-nav-link">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="{{.LandingURL}}#pricing" class="nb-nav-link">{{if .Config.Navigation.LabelPricing}}{{.Config.Navigation.LabelPricing}}{{else}}Pricing{{end}}</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="nb-nav-link">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="nb-nav-link">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</a>{{end}}
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="nb-nav-link">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</a>{{end}}
{{if .Config.Navigation.ShowRepository}}
<a href="{{.RepoURL}}" class="nb-nav-repo">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
@@ -1168,16 +1169,16 @@
{{range .Config.Footer.Links}}
<a href="{{.URL}}" class="nb-nav-link">{{.Label}}</a>
{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="nb-nav-link">Docs</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="nb-nav-link">API</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="nb-nav-link">Releases</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="nb-nav-link">Issues</a>{{end}}
{{if .Config.ValueProps}}<a href="{{.LandingURL}}#value-props" class="nb-nav-link">Why</a>{{end}}
{{if .Config.Features}}<a href="{{.LandingURL}}#features" class="nb-nav-link">Features</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="{{.LandingURL}}#pricing" class="nb-nav-link">Pricing</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="nb-nav-link">Blog</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="nb-nav-link">Gallery</a>{{end}}
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="nb-nav-link">Compare</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="nb-nav-link">{{if .Config.Navigation.LabelDocs}}{{.Config.Navigation.LabelDocs}}{{else}}Docs{{end}}</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="nb-nav-link">{{if .Config.Navigation.LabelAPI}}{{.Config.Navigation.LabelAPI}}{{else}}API{{end}}</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="nb-nav-link">{{if .Config.Navigation.LabelReleases}}{{.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="nb-nav-link">{{if .Config.Navigation.LabelIssues}}{{.Config.Navigation.LabelIssues}}{{else}}Issues{{end}}</a>{{end}}
{{if .Config.ValueProps}}<a href="{{.LandingURL}}#value-props" class="nb-nav-link">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Why{{end}}</a>{{end}}
{{if .Config.Features}}<a href="{{.LandingURL}}#features" class="nb-nav-link">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="{{.LandingURL}}#pricing" class="nb-nav-link">{{if .Config.Navigation.LabelPricing}}{{.Config.Navigation.LabelPricing}}{{else}}Pricing{{end}}</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="nb-nav-link">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="nb-nav-link">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</a>{{end}}
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="nb-nav-link">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</a>{{end}}
{{if .Config.Navigation.ShowRepository}}
<a href="{{.RepoURL}}" class="nb-nav-repo">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
@@ -1217,7 +1218,7 @@
<section class="nb-features" style="padding-top: 120px;">
<div class="nb-features-inner">
<div class="nb-section-header nb-reveal">
<div class="nb-section-label">Blog</div>
<div class="nb-section-label">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</div>
<h2>{{if .Config.Blog.Headline}}{{.Config.Blog.Headline}}{{else}}All Posts{{end}}</h2>
{{if .Config.Blog.Subheadline}}<p>{{.Config.Blog.Subheadline}}</p>{{end}}
</div>
@@ -1248,7 +1249,7 @@
</section>
{{else}}
<!-- Hero Section -->
<section class="nb-hero">
<section class="nb-hero" id="hero">
<div class="nb-hero-accent nb-hero-accent-1"></div>
<div class="nb-hero-accent nb-hero-accent-2"></div>
<div class="nb-hero-accent nb-hero-accent-3">*</div>
@@ -1273,12 +1274,29 @@
</a>
{{end}}
</div>
{{if or $.GooglePlayID $.AppStoreID}}
<div style="display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; margin-top: 16px;" class="nb-reveal nb-reveal-delay-4">
{{if $.GooglePlayID}}
<a href="https://play.google.com/store/apps/details?id={{$.GooglePlayID}}" target="_blank" rel="noopener" class="nb-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3.609 1.814L13.792 12 3.61 22.186a.996.996 0 0 1-.61-.92V2.734a1 1 0 0 1 .609-.92zm10.89 10.893l2.302 2.302-10.937 6.333 8.635-8.635zm3.199-1.4l2.834 1.64a1 1 0 0 1 0 1.726l-2.834 1.64-2.635-2.636 2.635-2.37zM5.864 2.658L16.8 9.99l-2.302 2.302-8.635-8.635z"/></svg>
Google Play
</a>
{{end}}
{{if $.AppStoreID}}
<a href="https://apps.apple.com/app/{{$.AppStoreID}}" target="_blank" rel="noopener" class="nb-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>
App Store
</a>
{{end}}
</div>
{{end}}
</div>
</section>
{{if .Config.Stats}}
<!-- Stats Section -->
<section class="nb-stats">
<section class="nb-stats" id="stats">
<div class="nb-stats-grid">
{{range .Config.Stats}}
<div class="nb-stat-card nb-reveal">
@@ -1291,7 +1309,7 @@
{{end}}
{{if and .PublicReleases .LatestRelease .LatestRelease.Attachments}}
<section class="nb-downloads">
<section class="nb-downloads" id="downloads">
<div class="nb-downloads-inner">
<div class="nb-downloads-header nb-reveal">
<div class="nb-section-label">Download</div>
@@ -1357,25 +1375,6 @@
</div>
{{end}}
{{end}}
{{if or $.GooglePlayID $.AppStoreID}}
<div style="margin-bottom: 24px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 13px; color: var(--nb-muted); text-transform: uppercase; letter-spacing: 0.1em;">{{svg "octicon-device-mobile" 16}} App Stores</h4>
<div style="display: flex; gap: 12px; flex-wrap: wrap;">
{{if $.GooglePlayID}}
<a href="https://play.google.com/store/apps/details?id={{$.GooglePlayID}}" target="_blank" rel="noopener" class="nb-download-item nb-reveal" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3.609 1.814L13.792 12 3.61 22.186a.996.996 0 0 1-.61-.92V2.734a1 1 0 0 1 .609-.92zm10.89 10.893l2.302 2.302-10.937 6.333 8.635-8.635zm3.199-1.4l2.834 1.64a1 1 0 0 1 0 1.726l-2.834 1.64-2.635-2.636 2.635-2.37zM5.864 2.658L16.8 9.99l-2.302 2.302-8.635-8.635z"/></svg>
Google Play
</a>
{{end}}
{{if $.AppStoreID}}
<a href="https://apps.apple.com/app/{{$.AppStoreID}}" target="_blank" rel="noopener" class="nb-download-item nb-reveal" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>
App Store
</a>
{{end}}
</div>
</div>
{{end}}
{{if $otherFiles}}
<div style="margin-bottom: 24px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 13px; color: var(--nb-muted); text-transform: uppercase; letter-spacing: 0.1em;">{{svg "octicon-file" 16}} Other</h4>
@@ -1390,7 +1389,7 @@
{{if .Config.SocialProof.Logos}}
<!-- Trust Bar -->
<section class="nb-trust-bar">
<section class="nb-trust-bar" id="social-proof">
<p class="nb-trust-label nb-reveal">Trusted by the best</p>
<div class="nb-trust-logos nb-reveal nb-reveal-delay-1">
{{range .Config.SocialProof.Logos}}
@@ -1404,10 +1403,9 @@
<!-- Value Props Section -->
<section class="nb-value-props" id="value-props">
<div class="nb-section-header nb-reveal">
<div class="nb-section-label">Why choose this</div>
<div class="nb-section-label">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Why choose this{{end}}</div>
<h2>Unlock your <span class="nb-glow-primary">potential</span></h2>
<p>Everything you need to create without limits</p>
</div>
</div>
<div class="nb-value-grid">
{{range .Config.ValueProps}}
<div class="nb-value-tile nb-reveal">
@@ -1426,7 +1424,7 @@
<!-- Features Section -->
<section class="nb-features" id="features">
<div class="nb-section-header nb-reveal">
<div class="nb-section-label">Capabilities</div>
<div class="nb-section-label">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Capabilities{{end}}</div>
<h2>Packed with <span class="nb-glow-text">power</span></h2>
</div>
<div class="nb-feature-list">
@@ -1447,7 +1445,7 @@
<!-- Pricing Section -->
<section class="nb-pricing" id="pricing">
<div class="nb-section-header nb-reveal">
<div class="nb-section-label">Investment</div>
<div class="nb-section-label">{{if .Config.Navigation.LabelPricing}}{{.Config.Navigation.LabelPricing}}{{else}}Investment{{end}}</div>
<h2>{{if .Config.Pricing.Headline}}{{.Config.Pricing.Headline}}{{else}}Simple <span class="nb-glow-text">Pricing</span>{{end}}</h2>
<p>Choose the plan that works for you</p>
</div>
@@ -1474,7 +1472,7 @@
<!-- Testimonials Section -->
{{if .Config.SocialProof.Testimonials}}
<section class="nb-testimonial-section">
<section class="nb-testimonial-section" id="testimonials">
<div class="nb-testimonial-block nb-reveal">
<span class="nb-quote-mark">&ldquo;</span>
<div class="nb-testimonials-container">
@@ -1508,7 +1506,7 @@
{{if .Config.CTASection.Headline}}
<!-- CTA Banner -->
<section class="nb-cta-section">
<section class="nb-cta-section" id="cta">
<div class="nb-cta-banner nb-reveal">
<h2>{{.Config.CTASection.Headline}}</h2>
{{if .Config.CTASection.Subheadline}}
@@ -1528,7 +1526,7 @@
{{if and .Config.Blog.Enabled .BlogPosts}}
<section class="nb-features" id="blog" style="border-top: 1px solid var(--nb-border-hard);">
<div class="nb-section-header nb-reveal">
<div class="nb-section-label">Blog</div>
<div class="nb-section-label">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</div>
<h2>{{if .Config.Blog.Headline}}<span class="nb-glow-text">{{.Config.Blog.Headline}}</span>{{else}}Latest <span class="nb-glow-text">Posts</span>{{end}}</h2>
{{if .Config.Blog.Subheadline}}<p>{{.Config.Blog.Subheadline}}</p>{{end}}
</div>
@@ -1566,7 +1564,7 @@
{{if and .Config.Gallery.Enabled .GalleryImages}}
<section class="nb-features" id="gallery" style="border-top: 1px solid var(--nb-border-hard);">
<div class="nb-section-header nb-reveal">
<div class="nb-section-label">Gallery</div>
<div class="nb-section-label">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</div>
<h2><span class="nb-glow-text">{{if .Config.Gallery.Headline}}{{.Config.Gallery.Headline}}{{else}}Gallery{{end}}</span></h2>
{{if .Config.Gallery.Subheadline}}<p>{{.Config.Gallery.Subheadline}}</p>{{end}}
</div>
@@ -1590,7 +1588,7 @@
<section class="nb-features" id="comparison" style="border-top: 1px solid var(--nb-border);">
<div class="nb-features-inner">
<div class="nb-section-header nb-reveal">
<div class="nb-section-label">Compare</div>
<div class="nb-section-label">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</div>
<h2>{{if .Config.Comparison.Headline}}{{.Config.Comparison.Headline}}{{else}}How We Compare{{end}}</h2>
{{if .Config.Comparison.Subheadline}}<p>{{.Config.Comparison.Subheadline}}</p>{{end}}
</div>
@@ -1676,9 +1674,9 @@
<a href="{{.URL}}" class="nb-footer-link">{{.Label}}</a>
{{end}}
{{if .Config.Navigation.ShowRepository}}<a href="{{.RepoURL}}" class="nb-footer-link">Repository</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="nb-footer-link">Docs</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="nb-footer-link">Releases</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="nb-footer-link">Issues</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="nb-footer-link">{{if .Config.Navigation.LabelDocs}}{{.Config.Navigation.LabelDocs}}{{else}}Docs{{end}}</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="nb-footer-link">{{if .Config.Navigation.LabelReleases}}{{.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="nb-footer-link">{{if .Config.Navigation.LabelIssues}}{{.Config.Navigation.LabelIssues}}{{else}}Issues{{end}}</a>{{end}}
</div>
</footer>
</div>

View File

@@ -1012,16 +1012,16 @@
{{range .Config.Footer.Links}}
<a href="{{.URL}}" class="ct-nav-link">{{.Label}}</a>
{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="ct-nav-link">Docs</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="ct-nav-link">API</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="ct-nav-link">Releases</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="ct-nav-link">Issues</a>{{end}}
{{if .Config.ValueProps}}<a href="{{.LandingURL}}#value-props" class="ct-nav-link">Value Props</a>{{end}}
{{if .Config.Features}}<a href="{{.LandingURL}}#features" class="ct-nav-link">Features</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="{{.LandingURL}}#pricing" class="ct-nav-link">Pricing</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="ct-nav-link">Blog</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="ct-nav-link">Gallery</a>{{end}}
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="ct-nav-link">Compare</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="ct-nav-link">{{if .Config.Navigation.LabelDocs}}{{.Config.Navigation.LabelDocs}}{{else}}Docs{{end}}</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="ct-nav-link">{{if .Config.Navigation.LabelAPI}}{{.Config.Navigation.LabelAPI}}{{else}}API{{end}}</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="ct-nav-link">{{if .Config.Navigation.LabelReleases}}{{.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="ct-nav-link">{{if .Config.Navigation.LabelIssues}}{{.Config.Navigation.LabelIssues}}{{else}}Issues{{end}}</a>{{end}}
{{if .Config.ValueProps}}<a href="{{.LandingURL}}#value-props" class="ct-nav-link">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Value Props{{end}}</a>{{end}}
{{if .Config.Features}}<a href="{{.LandingURL}}#features" class="ct-nav-link">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="{{.LandingURL}}#pricing" class="ct-nav-link">{{if .Config.Navigation.LabelPricing}}{{.Config.Navigation.LabelPricing}}{{else}}Pricing{{end}}</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="ct-nav-link">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="ct-nav-link">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</a>{{end}}
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="ct-nav-link">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</a>{{end}}
{{if .Config.Navigation.ShowRepository}}
<a href="{{.RepoURL}}" class="ct-nav-cta">
<img src="/assets/img/gitcaddy-icon.svg" width="14" height="14" alt="GitCaddy">
@@ -1077,7 +1077,7 @@
<section class="ct-features" style="padding-top: 120px;">
<div class="ct-features-inner">
<div class="ct-section-header ct-reveal">
<div class="ct-section-label">Blog</div>
<div class="ct-section-label">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</div>
<h2>{{if .Config.Blog.Headline}}{{.Config.Blog.Headline}}{{else}}All Posts{{end}}</h2>
{{if .Config.Blog.Subheadline}}<p>{{.Config.Blog.Subheadline}}</p>{{end}}
</div>
@@ -1108,7 +1108,7 @@
</section>
{{else}}
<!-- Hero Section -->
<section class="ct-hero">
<section class="ct-hero" id="hero">
<div class="ct-hero-glow"></div>
<div class="ct-hero-vignette"></div>
@@ -1130,11 +1130,6 @@
{{.Config.Hero.PrimaryCTA.Label}}
{{svg "octicon-arrow-right" 16}}
</a>
{{else}}
<a href="{{.RepoURL}}" class="ct-btn-primary" data-cta="primary">
Get Started
{{svg "octicon-arrow-right" 16}}
</a>
{{end}}
{{if .Config.Hero.SecondaryCTA.Label}}
@@ -1142,14 +1137,26 @@
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
{{.Config.Hero.SecondaryCTA.Label}}
</a>
{{else}}
<a href="{{.RepoURL}}" class="ct-btn-secondary" data-cta="secondary">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
View Source
</a>
{{end}}
</div>
{{if or $.GooglePlayID $.AppStoreID}}
<div style="display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; margin-top: 16px;" class="ct-reveal visible ct-reveal-delay-4">
{{if $.GooglePlayID}}
<a href="https://play.google.com/store/apps/details?id={{$.GooglePlayID}}" target="_blank" rel="noopener" class="ct-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3.609 1.814L13.792 12 3.61 22.186a.996.996 0 0 1-.61-.92V2.734a1 1 0 0 1 .609-.92zm10.89 10.893l2.302 2.302-10.937 6.333 8.635-8.635zm3.199-1.4l2.834 1.64a1 1 0 0 1 0 1.726l-2.834 1.64-2.635-2.636 2.635-2.37zM5.864 2.658L16.8 9.99l-2.302 2.302-8.635-8.635z"/></svg>
Google Play
</a>
{{end}}
{{if $.AppStoreID}}
<a href="https://apps.apple.com/app/{{$.AppStoreID}}" target="_blank" rel="noopener" class="ct-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>
App Store
</a>
{{end}}
</div>
{{end}}
{{if .Config.Hero.CodeExample}}
<div class="ct-code-block ct-reveal visible ct-reveal-delay-4">
<div class="ct-code-titlebar">
@@ -1172,7 +1179,7 @@
<!-- Stats Section -->
{{if or .Config.Stats (gt .NumStars 0)}}
<section class="ct-stats">
<section class="ct-stats" id="stats">
<div class="ct-stats-inner ct-reveal">
{{if .Config.Stats}}
{{range .Config.Stats}}
@@ -1203,7 +1210,7 @@
<!-- Downloads Section -->
{{if and .PublicReleases .LatestRelease .LatestRelease.Attachments}}
<section class="ct-downloads">
<section class="ct-downloads" id="downloads">
<div class="ct-downloads-inner ct-reveal">
<h2>Download v{{.LatestReleaseTag}}</h2>
<p>Get the latest release</p>
@@ -1266,25 +1273,6 @@
</div>
{{end}}
{{end}}
{{if or $.GooglePlayID $.AppStoreID}}
<div style="margin-bottom: 24px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 12px; color: var(--ct-muted); letter-spacing: 0.08em; text-transform: uppercase;">{{svg "octicon-device-mobile" 16}} App Stores</h4>
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
{{if $.GooglePlayID}}
<a href="https://play.google.com/store/apps/details?id={{$.GooglePlayID}}" target="_blank" rel="noopener" class="ct-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3.609 1.814L13.792 12 3.61 22.186a.996.996 0 0 1-.61-.92V2.734a1 1 0 0 1 .609-.92zm10.89 10.893l2.302 2.302-10.937 6.333 8.635-8.635zm3.199-1.4l2.834 1.64a1 1 0 0 1 0 1.726l-2.834 1.64-2.635-2.636 2.635-2.37zM5.864 2.658L16.8 9.99l-2.302 2.302-8.635-8.635z"/></svg>
Google Play
</a>
{{end}}
{{if $.AppStoreID}}
<a href="https://apps.apple.com/app/{{$.AppStoreID}}" target="_blank" rel="noopener" class="ct-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>
App Store
</a>
{{end}}
</div>
</div>
{{end}}
{{if $otherFiles}}
<div style="margin-bottom: 24px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 12px; color: var(--ct-muted); letter-spacing: 0.08em; text-transform: uppercase;">{{svg "octicon-file" 16}} Other</h4>
@@ -1302,10 +1290,9 @@
<section class="ct-features" id="value-props">
<div class="ct-features-inner">
<div class="ct-section-header ct-reveal">
<div class="ct-section-label">Why choose us</div>
<h2>{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}</h2>
<p>Everything you need to get started quickly.</p>
</div>
<div class="ct-section-label">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Why choose us{{end}}</div>
<h2>{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}{{end}}</h2>
</div>
<div class="ct-features-grid">
{{range .Config.ValueProps}}
<div class="ct-feature-card ct-reveal">
@@ -1326,10 +1313,9 @@
<section class="ct-features" id="features" style="padding-top: 40px;">
<div class="ct-features-inner">
<div class="ct-section-header ct-reveal">
<div class="ct-section-label">Capabilities</div>
<h2>Features</h2>
<p>Powerful capabilities at your fingertips.</p>
</div>
<div class="ct-section-label">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Capabilities{{end}}</div>
<h2>{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</h2>
</div>
<div class="ct-features-grid">
{{range .Config.Features}}
<div class="ct-feature-card ct-reveal">
@@ -1347,7 +1333,7 @@
<!-- Social Proof -->
{{if or .Config.SocialProof.Logos .Config.SocialProof.Testimonials}}
<section class="ct-social-proof">
<section class="ct-social-proof" id="social-proof">
<div class="ct-social-proof-inner">
{{if .Config.SocialProof.Logos}}
<div class="ct-logos ct-reveal">
@@ -1387,7 +1373,7 @@
<section class="ct-pricing" id="pricing">
<div class="ct-pricing-inner">
<div class="ct-section-header ct-reveal">
<div class="ct-section-label">Pricing</div>
<div class="ct-section-label">{{if .Config.Navigation.LabelPricing}}{{.Config.Navigation.LabelPricing}}{{else}}Pricing{{end}}</div>
<h2>{{if .Config.Pricing.Headline}}{{.Config.Pricing.Headline}}{{else}}Pricing{{end}}</h2>
<p>{{if .Config.Pricing.Subheadline}}{{.Config.Pricing.Subheadline}}{{else}}Choose the plan that works for you{{end}}</p>
</div>
@@ -1413,7 +1399,7 @@
<!-- CTA Section -->
{{if .Config.CTASection.Headline}}
<section class="ct-cta-section">
<section class="ct-cta-section" id="cta">
<div class="ct-cta-inner ct-reveal">
<h2>{{.Config.CTASection.Headline}}</h2>
{{if .Config.CTASection.Subheadline}}
@@ -1432,7 +1418,7 @@
<section class="ct-features" id="blog" style="border-top: 1px solid var(--ct-dim);">
<div class="ct-features-inner">
<div class="ct-section-header ct-reveal">
<div class="ct-section-label">Blog</div>
<div class="ct-section-label">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</div>
<h2>{{if .Config.Blog.Headline}}{{.Config.Blog.Headline}}{{else}}Latest Posts{{end}}</h2>
{{if .Config.Blog.Subheadline}}<p>{{.Config.Blog.Subheadline}}</p>{{end}}
</div>
@@ -1471,7 +1457,7 @@
<section class="ct-features" id="gallery" style="border-top: 1px solid var(--ct-dim);">
<div class="ct-features-inner">
<div class="ct-section-header ct-reveal">
<div class="ct-section-label">Gallery</div>
<div class="ct-section-label">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</div>
<h2>{{if .Config.Gallery.Headline}}{{.Config.Gallery.Headline}}{{else}}Gallery{{end}}</h2>
{{if .Config.Gallery.Subheadline}}<p>{{.Config.Gallery.Subheadline}}</p>{{end}}
</div>
@@ -1496,7 +1482,7 @@
<section class="ct-features" id="comparison" style="border-top: 1px solid var(--ct-dim);">
<div class="ct-features-inner">
<div class="ct-section-header ct-reveal">
<div class="ct-section-label">Compare</div>
<div class="ct-section-label">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</div>
<h2>{{if .Config.Comparison.Headline}}{{.Config.Comparison.Headline}}{{else}}How We Compare{{end}}</h2>
{{if .Config.Comparison.Subheadline}}<p>{{.Config.Comparison.Subheadline}}</p>{{end}}
</div>
@@ -1579,8 +1565,8 @@
{{end}}
{{if .Config.Navigation.ShowRepository}}<a href="{{.RepoURL}}" class="ct-footer-link">Repository</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="ct-footer-link">Documentation</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="ct-footer-link">Releases</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="ct-footer-link">Issues</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="ct-footer-link">{{if .Config.Navigation.LabelReleases}}{{.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="ct-footer-link">{{if .Config.Navigation.LabelIssues}}{{.Config.Navigation.LabelIssues}}{{else}}Issues{{end}}</a>{{end}}
</div>
</footer>
</div>

View File

@@ -895,16 +895,16 @@
{{range .Config.Footer.Links}}
<a href="{{.URL}}" class="dt-nav-link">{{.Label}}</a>
{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="dt-nav-link">Docs</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="dt-nav-link">API</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="dt-nav-link">Releases</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="dt-nav-link">Issues</a>{{end}}
{{if .Config.ValueProps}}<a href="{{.LandingURL}}#value-props" class="dt-nav-link">Value Props</a>{{end}}
{{if .Config.Features}}<a href="{{.LandingURL}}#features" class="dt-nav-link">Features</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="{{.LandingURL}}#pricing" class="dt-nav-link">Pricing</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="dt-nav-link">Blog</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="dt-nav-link">Gallery</a>{{end}}
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="dt-nav-link">Compare</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="dt-nav-link">{{if .Config.Navigation.LabelDocs}}{{.Config.Navigation.LabelDocs}}{{else}}Docs{{end}}</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="dt-nav-link">{{if .Config.Navigation.LabelAPI}}{{.Config.Navigation.LabelAPI}}{{else}}API{{end}}</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="dt-nav-link">{{if .Config.Navigation.LabelReleases}}{{.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="dt-nav-link">{{if .Config.Navigation.LabelIssues}}{{.Config.Navigation.LabelIssues}}{{else}}Issues{{end}}</a>{{end}}
{{if .Config.ValueProps}}<a href="{{.LandingURL}}#value-props" class="dt-nav-link">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Value Props{{end}}</a>{{end}}
{{if .Config.Features}}<a href="{{.LandingURL}}#features" class="dt-nav-link">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="{{.LandingURL}}#pricing" class="dt-nav-link">{{if .Config.Navigation.LabelPricing}}{{.Config.Navigation.LabelPricing}}{{else}}Pricing{{end}}</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="dt-nav-link">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="dt-nav-link">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</a>{{end}}
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="dt-nav-link">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</a>{{end}}
{{if .Config.Navigation.ShowRepository}}
<a href="{{.RepoURL}}" class="dt-nav-cta">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
@@ -960,7 +960,7 @@
<section class="dt-features" style="padding-top: 100px;">
<div class="dt-features-inner">
<div class="dt-section-header dt-reveal">
<div class="dt-section-label">Blog</div>
<div class="dt-section-label">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</div>
<h2>{{if .Config.Blog.Headline}}{{.Config.Blog.Headline}}{{else}}All Posts{{end}}</h2>
{{if .Config.Blog.Subheadline}}<p>{{.Config.Blog.Subheadline}}</p>{{end}}
</div>
@@ -991,7 +991,7 @@
</section>
{{else}}
<!-- Hero Section -->
<section class="dt-hero">
<section class="dt-hero" id="hero">
<div class="dt-hero-content">
<div class="dt-badge dt-reveal visible">
<span class="dt-badge-label">status</span>
@@ -1011,11 +1011,6 @@
{{.Config.Hero.PrimaryCTA.Label}}
{{svg "octicon-arrow-right" 16}}
</a>
{{else}}
<a href="{{.RepoURL}}" class="dt-btn-primary" data-cta="primary">
Get Started
{{svg "octicon-arrow-right" 16}}
</a>
{{end}}
{{if .Config.Hero.SecondaryCTA.Label}}
@@ -1023,14 +1018,26 @@
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
{{.Config.Hero.SecondaryCTA.Label}}
</a>
{{else}}
<a href="{{.RepoURL}}" class="dt-btn-secondary" data-cta="secondary">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
View Source
</a>
{{end}}
</div>
{{if or $.GooglePlayID $.AppStoreID}}
<div style="display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; margin-top: 16px;" class="dt-reveal visible dt-reveal-delay-4">
{{if $.GooglePlayID}}
<a href="https://play.google.com/store/apps/details?id={{$.GooglePlayID}}" target="_blank" rel="noopener" class="dt-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3.609 1.814L13.792 12 3.61 22.186a.996.996 0 0 1-.61-.92V2.734a1 1 0 0 1 .609-.92zm10.89 10.893l2.302 2.302-10.937 6.333 8.635-8.635zm3.199-1.4l2.834 1.64a1 1 0 0 1 0 1.726l-2.834 1.64-2.635-2.636 2.635-2.37zM5.864 2.658L16.8 9.99l-2.302 2.302-8.635-8.635z"/></svg>
Google Play
</a>
{{end}}
{{if $.AppStoreID}}
<a href="https://apps.apple.com/app/{{$.AppStoreID}}" target="_blank" rel="noopener" class="dt-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>
App Store
</a>
{{end}}
</div>
{{end}}
{{if .Config.Hero.CodeExample}}
<div class="dt-code-block dt-reveal visible dt-reveal-delay-4">
<span class="dt-code-prompt">$</span>
@@ -1045,7 +1052,7 @@
<!-- Stats Section -->
{{if or .Config.Stats (gt .NumStars 0)}}
<section class="dt-stats">
<section class="dt-stats" id="stats">
<div class="dt-stats-inner dt-reveal">
{{if .Config.Stats}}
{{range .Config.Stats}}
@@ -1076,7 +1083,7 @@
<!-- Downloads Section -->
{{if and .PublicReleases .LatestRelease .LatestRelease.Attachments}}
<section class="dt-downloads">
<section class="dt-downloads" id="downloads">
<div class="dt-downloads-inner dt-reveal">
<h2>Download v{{.LatestReleaseTag}}</h2>
<p>Get the latest release</p>
@@ -1139,25 +1146,6 @@
</div>
{{end}}
{{end}}
{{if or $.GooglePlayID $.AppStoreID}}
<div style="margin-bottom: 24px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 12px; color: var(--dt-muted); font-family: 'JetBrains Mono', monospace; letter-spacing: 0.08em; text-transform: uppercase;">{{svg "octicon-device-mobile" 16}} App Stores</h4>
<div style="display: flex; gap: 12px; flex-wrap: wrap;">
{{if $.GooglePlayID}}
<a href="https://play.google.com/store/apps/details?id={{$.GooglePlayID}}" target="_blank" rel="noopener" class="dt-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3.609 1.814L13.792 12 3.61 22.186a.996.996 0 0 1-.61-.92V2.734a1 1 0 0 1 .609-.92zm10.89 10.893l2.302 2.302-10.937 6.333 8.635-8.635zm3.199-1.4l2.834 1.64a1 1 0 0 1 0 1.726l-2.834 1.64-2.635-2.636 2.635-2.37zM5.864 2.658L16.8 9.99l-2.302 2.302-8.635-8.635z"/></svg>
Google Play
</a>
{{end}}
{{if $.AppStoreID}}
<a href="https://apps.apple.com/app/{{$.AppStoreID}}" target="_blank" rel="noopener" class="dt-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>
App Store
</a>
{{end}}
</div>
</div>
{{end}}
{{if $otherFiles}}
<div style="margin-bottom: 24px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 12px; color: var(--dt-muted); font-family: 'JetBrains Mono', monospace; letter-spacing: 0.08em; text-transform: uppercase;">{{svg "octicon-file" 16}} Other</h4>
@@ -1175,10 +1163,9 @@
<section class="dt-features" id="value-props">
<div class="dt-features-inner">
<div class="dt-section-header dt-reveal">
<div class="dt-section-label">Why choose us</div>
<h2>{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}</h2>
<p>Everything you need to get started quickly.</p>
</div>
<div class="dt-section-label">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Why choose us{{end}}</div>
<h2>{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}{{end}}</h2>
</div>
<div class="dt-features-grid">
{{range .Config.ValueProps}}
<div class="dt-feature-card dt-reveal">
@@ -1199,10 +1186,9 @@
<section class="dt-features" id="features" style="padding-top: 40px;">
<div class="dt-features-inner">
<div class="dt-section-header dt-reveal">
<div class="dt-section-label">Capabilities</div>
<h2>Features</h2>
<p>Powerful capabilities at your fingertips.</p>
</div>
<div class="dt-section-label">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Capabilities{{end}}</div>
<h2>{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</h2>
</div>
<div class="dt-features-grid">
{{range .Config.Features}}
<div class="dt-feature-card dt-reveal">
@@ -1220,7 +1206,7 @@
<!-- Social Proof -->
{{if or .Config.SocialProof.Logos .Config.SocialProof.Testimonials}}
<section class="dt-social-proof">
<section class="dt-social-proof" id="social-proof">
<div class="dt-social-proof-inner">
{{if .Config.SocialProof.Logos}}
<div class="dt-logos dt-reveal">
@@ -1260,7 +1246,7 @@
<section class="dt-pricing" id="pricing">
<div class="dt-pricing-inner">
<div class="dt-section-header dt-reveal">
<div class="dt-section-label">Pricing</div>
<div class="dt-section-label">{{if .Config.Navigation.LabelPricing}}{{.Config.Navigation.LabelPricing}}{{else}}Pricing{{end}}</div>
<h2>{{if .Config.Pricing.Headline}}{{.Config.Pricing.Headline}}{{else}}Pricing{{end}}</h2>
<p>{{if .Config.Pricing.Subheadline}}{{.Config.Pricing.Subheadline}}{{else}}Choose the plan that works for you{{end}}</p>
</div>
@@ -1286,7 +1272,7 @@
<!-- CTA Section -->
{{if .Config.CTASection.Headline}}
<section class="dt-cta-section">
<section class="dt-cta-section" id="cta">
<div class="dt-cta-inner dt-reveal">
<h2>{{.Config.CTASection.Headline}}</h2>
{{if .Config.CTASection.Subheadline}}
@@ -1305,7 +1291,7 @@
<section class="dt-features" id="blog" style="border-top: 1px solid var(--dt-dim);">
<div class="dt-features-inner">
<div class="dt-section-header dt-reveal">
<div class="dt-section-label">Blog</div>
<div class="dt-section-label">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</div>
<h2>{{if .Config.Blog.Headline}}{{.Config.Blog.Headline}}{{else}}Latest Posts{{end}}</h2>
{{if .Config.Blog.Subheadline}}<p>{{.Config.Blog.Subheadline}}</p>{{end}}
</div>
@@ -1344,7 +1330,7 @@
<section class="dt-features" id="gallery" style="border-top: 1px solid var(--dt-dim);">
<div class="dt-features-inner">
<div class="dt-section-header dt-reveal">
<div class="dt-section-label">Gallery</div>
<div class="dt-section-label">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</div>
<h2>{{if .Config.Gallery.Headline}}{{.Config.Gallery.Headline}}{{else}}Gallery{{end}}</h2>
{{if .Config.Gallery.Subheadline}}<p>{{.Config.Gallery.Subheadline}}</p>{{end}}
</div>
@@ -1369,7 +1355,7 @@
<section class="dt-features" id="comparison" style="border-top: 1px solid var(--dt-dim);">
<div class="dt-features-inner">
<div class="dt-section-header dt-reveal">
<div class="dt-section-label">Compare</div>
<div class="dt-section-label">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</div>
<h2>{{if .Config.Comparison.Headline}}{{.Config.Comparison.Headline}}{{else}}How We Compare{{end}}</h2>
{{if .Config.Comparison.Subheadline}}<p>{{.Config.Comparison.Subheadline}}</p>{{end}}
</div>
@@ -1452,8 +1438,8 @@
{{end}}
{{if .Config.Navigation.ShowRepository}}<a href="{{.RepoURL}}" class="dt-footer-link">Repository</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="dt-footer-link">Documentation</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="dt-footer-link">Releases</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="dt-footer-link">Issues</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="dt-footer-link">{{if .Config.Navigation.LabelReleases}}{{.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="dt-footer-link">{{if .Config.Navigation.LabelIssues}}{{.Config.Navigation.LabelIssues}}{{else}}Issues{{end}}</a>{{end}}
</div>
</footer>
</div>

View File

@@ -920,16 +920,16 @@
{{range .Config.Footer.Links}}
<a href="{{.URL}}" class="df-nav-link">{{.Label}}</a>
{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="df-nav-link">Docs</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="df-nav-link">API</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="df-nav-link">Releases</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="df-nav-link">Issues</a>{{end}}
{{if .Config.ValueProps}}<a href="{{.LandingURL}}#value-props" class="df-nav-link">Value Props</a>{{end}}
{{if .Config.Features}}<a href="{{.LandingURL}}#features" class="df-nav-link">Features</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="{{.LandingURL}}#pricing" class="df-nav-link">Pricing</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="df-nav-link">Blog</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="df-nav-link">Gallery</a>{{end}}
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="df-nav-link">Compare</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="df-nav-link">{{if .Config.Navigation.LabelDocs}}{{.Config.Navigation.LabelDocs}}{{else}}Docs{{end}}</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="df-nav-link">{{if .Config.Navigation.LabelAPI}}{{.Config.Navigation.LabelAPI}}{{else}}API{{end}}</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="df-nav-link">{{if .Config.Navigation.LabelReleases}}{{.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="df-nav-link">{{if .Config.Navigation.LabelIssues}}{{.Config.Navigation.LabelIssues}}{{else}}Issues{{end}}</a>{{end}}
{{if .Config.ValueProps}}<a href="{{.LandingURL}}#value-props" class="df-nav-link">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Value Props{{end}}</a>{{end}}
{{if .Config.Features}}<a href="{{.LandingURL}}#features" class="df-nav-link">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="{{.LandingURL}}#pricing" class="df-nav-link">{{if .Config.Navigation.LabelPricing}}{{.Config.Navigation.LabelPricing}}{{else}}Pricing{{end}}</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="df-nav-link">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="df-nav-link">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</a>{{end}}
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="df-nav-link">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</a>{{end}}
{{if .Config.Navigation.ShowRepository}}
<a href="{{.RepoURL}}" class="df-nav-cta">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
@@ -985,7 +985,7 @@
<section class="df-features" style="padding-top: 120px;">
<div class="df-features-inner">
<div class="df-section-header df-reveal">
<div class="df-section-label">Blog</div>
<div class="df-section-label">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</div>
<h2>{{if .Config.Blog.Headline}}{{.Config.Blog.Headline}}{{else}}All Posts{{end}}</h2>
{{if .Config.Blog.Subheadline}}<p>{{.Config.Blog.Subheadline}}</p>{{end}}
</div>
@@ -1016,7 +1016,7 @@
</section>
{{else}}
<!-- Hero Section -->
<section class="df-hero">
<section class="df-hero" id="hero">
<div class="df-hero-content">
<div class="df-badge df-reveal visible">
<span class="df-badge-dot"></span>
@@ -1035,11 +1035,6 @@
{{.Config.Hero.PrimaryCTA.Label}}
{{svg "octicon-arrow-right" 16}}
</a>
{{else}}
<a href="{{.RepoURL}}" class="df-btn-primary" data-cta="primary">
Get Started
{{svg "octicon-arrow-right" 16}}
</a>
{{end}}
{{if .Config.Hero.SecondaryCTA.Label}}
@@ -1047,14 +1042,26 @@
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
{{.Config.Hero.SecondaryCTA.Label}}
</a>
{{else}}
<a href="{{.RepoURL}}" class="df-btn-secondary" data-cta="secondary">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
View Source
</a>
{{end}}
</div>
{{if or $.GooglePlayID $.AppStoreID}}
<div style="display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; margin-top: 16px;" class="df-reveal visible df-reveal-delay-4">
{{if $.GooglePlayID}}
<a href="https://play.google.com/store/apps/details?id={{$.GooglePlayID}}" target="_blank" rel="noopener" class="df-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3.609 1.814L13.792 12 3.61 22.186a.996.996 0 0 1-.61-.92V2.734a1 1 0 0 1 .609-.92zm10.89 10.893l2.302 2.302-10.937 6.333 8.635-8.635zm3.199-1.4l2.834 1.64a1 1 0 0 1 0 1.726l-2.834 1.64-2.635-2.636 2.635-2.37zM5.864 2.658L16.8 9.99l-2.302 2.302-8.635-8.635z"/></svg>
Google Play
</a>
{{end}}
{{if $.AppStoreID}}
<a href="https://apps.apple.com/app/{{$.AppStoreID}}" target="_blank" rel="noopener" class="df-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>
App Store
</a>
{{end}}
</div>
{{end}}
{{if .Config.Hero.CodeExample}}
<div class="df-code-block df-reveal visible df-reveal-delay-4">
<span class="df-code-prompt">$</span>
@@ -1069,7 +1076,7 @@
<!-- Stats Section -->
{{if or .Config.Stats (gt .NumStars 0)}}
<section class="df-stats">
<section class="df-stats" id="stats">
<div class="df-stats-inner df-reveal">
{{if .Config.Stats}}
{{range .Config.Stats}}
@@ -1100,7 +1107,7 @@
<!-- Downloads Section -->
{{if and .PublicReleases .LatestRelease .LatestRelease.Attachments}}
<section class="df-downloads">
<section class="df-downloads" id="downloads">
<div class="df-downloads-inner df-reveal">
<h2>Download v{{.LatestReleaseTag}}</h2>
<p>Get the latest release</p>
@@ -1163,25 +1170,6 @@
</div>
{{end}}
{{end}}
{{if or $.GooglePlayID $.AppStoreID}}
<div style="margin-bottom: 24px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 13px; color: var(--df-muted); letter-spacing: 0.08em; text-transform: uppercase; font-weight: 700;">{{svg "octicon-device-mobile" 16}} App Stores</h4>
<div style="display: flex; gap: 12px; flex-wrap: wrap;">
{{if $.GooglePlayID}}
<a href="https://play.google.com/store/apps/details?id={{$.GooglePlayID}}" target="_blank" rel="noopener" class="df-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3.609 1.814L13.792 12 3.61 22.186a.996.996 0 0 1-.61-.92V2.734a1 1 0 0 1 .609-.92zm10.89 10.893l2.302 2.302-10.937 6.333 8.635-8.635zm3.199-1.4l2.834 1.64a1 1 0 0 1 0 1.726l-2.834 1.64-2.635-2.636 2.635-2.37zM5.864 2.658L16.8 9.99l-2.302 2.302-8.635-8.635z"/></svg>
Google Play
</a>
{{end}}
{{if $.AppStoreID}}
<a href="https://apps.apple.com/app/{{$.AppStoreID}}" target="_blank" rel="noopener" class="df-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>
App Store
</a>
{{end}}
</div>
</div>
{{end}}
{{if $otherFiles}}
<div style="margin-bottom: 24px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 13px; color: var(--df-muted); letter-spacing: 0.08em; text-transform: uppercase; font-weight: 700;">{{svg "octicon-file" 16}} Other</h4>
@@ -1199,10 +1187,9 @@
<section class="df-features" id="value-props">
<div class="df-features-inner">
<div class="df-section-header df-reveal">
<div class="df-section-label">Why choose us</div>
<h2>{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}</h2>
<p>Everything you need to get started quickly.</p>
</div>
<div class="df-section-label">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Why choose us{{end}}</div>
<h2>{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}{{end}}</h2>
</div>
<div class="df-features-grid">
{{range .Config.ValueProps}}
<div class="df-feature-card df-reveal">
@@ -1223,10 +1210,9 @@
<section class="df-features" id="features" style="padding-top: 40px;">
<div class="df-features-inner">
<div class="df-section-header df-reveal">
<div class="df-section-label">Capabilities</div>
<h2>Features</h2>
<p>Powerful capabilities at your fingertips.</p>
</div>
<div class="df-section-label">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Capabilities{{end}}</div>
<h2>{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</h2>
</div>
<div class="df-features-grid">
{{range .Config.Features}}
<div class="df-feature-card df-reveal">
@@ -1244,7 +1230,7 @@
<!-- Social Proof -->
{{if or .Config.SocialProof.Logos .Config.SocialProof.Testimonials}}
<section class="df-social-proof">
<section class="df-social-proof" id="social-proof">
<div class="df-social-proof-inner">
{{if .Config.SocialProof.Logos}}
<div class="df-logos df-reveal">
@@ -1284,7 +1270,7 @@
<section class="df-pricing" id="pricing">
<div class="df-pricing-inner">
<div class="df-section-header df-reveal">
<div class="df-section-label">Pricing</div>
<div class="df-section-label">{{if .Config.Navigation.LabelPricing}}{{.Config.Navigation.LabelPricing}}{{else}}Pricing{{end}}</div>
<h2>{{if .Config.Pricing.Headline}}{{.Config.Pricing.Headline}}{{else}}Pricing{{end}}</h2>
<p>{{if .Config.Pricing.Subheadline}}{{.Config.Pricing.Subheadline}}{{else}}Choose the plan that works for you{{end}}</p>
</div>
@@ -1310,7 +1296,7 @@
<!-- CTA Section -->
{{if .Config.CTASection.Headline}}
<section class="df-cta-section">
<section class="df-cta-section" id="cta">
<div class="df-cta-inner df-reveal">
<h2>{{.Config.CTASection.Headline}}</h2>
{{if .Config.CTASection.Subheadline}}
@@ -1329,7 +1315,7 @@
<section class="df-features" id="blog" style="border-top: 1px solid var(--df-border);">
<div class="df-features-inner">
<div class="df-section-header df-reveal">
<div class="df-section-label">Blog</div>
<div class="df-section-label">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</div>
<h2>{{if .Config.Blog.Headline}}{{.Config.Blog.Headline}}{{else}}Latest Posts{{end}}</h2>
{{if .Config.Blog.Subheadline}}<p>{{.Config.Blog.Subheadline}}</p>{{end}}
</div>
@@ -1368,7 +1354,7 @@
<section class="df-features" id="gallery" style="border-top: 1px solid var(--df-border);">
<div class="df-features-inner">
<div class="df-section-header df-reveal">
<div class="df-section-label">Gallery</div>
<div class="df-section-label">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</div>
<h2>{{if .Config.Gallery.Headline}}{{.Config.Gallery.Headline}}{{else}}Gallery{{end}}</h2>
{{if .Config.Gallery.Subheadline}}<p>{{.Config.Gallery.Subheadline}}</p>{{end}}
</div>
@@ -1393,7 +1379,7 @@
<section class="df-features" id="comparison" style="border-top: 1px solid var(--df-border);">
<div class="df-features-inner">
<div class="df-section-header df-reveal">
<div class="df-section-label">Compare</div>
<div class="df-section-label">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</div>
<h2>{{if .Config.Comparison.Headline}}{{.Config.Comparison.Headline}}{{else}}How We Compare{{end}}</h2>
{{if .Config.Comparison.Subheadline}}<p>{{.Config.Comparison.Subheadline}}</p>{{end}}
</div>
@@ -1476,8 +1462,8 @@
{{end}}
{{if .Config.Navigation.ShowRepository}}<a href="{{.RepoURL}}" class="df-footer-link">Repository</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="df-footer-link">Documentation</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="df-footer-link">Releases</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="df-footer-link">Issues</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="df-footer-link">{{if .Config.Navigation.LabelReleases}}{{.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="df-footer-link">{{if .Config.Navigation.LabelIssues}}{{.Config.Navigation.LabelIssues}}{{else}}Issues{{end}}</a>{{end}}
</div>
</footer>
</div>

View File

@@ -11,9 +11,9 @@
<div class="pages-footer-column">
<ul class="pages-footer-list">
{{if .Config.Navigation.ShowRepository}}<li><a href="{{AppSubUrl}}/{{.Repository.FullName}}">Repository</a></li>{{end}}
{{if .Config.Navigation.ShowDocs}}<li><a href="{{AppSubUrl}}/{{.Repository.FullName}}/wiki">Documentation</a></li>{{end}}
{{if .Config.Navigation.ShowReleases}}<li><a href="{{AppSubUrl}}/{{.Repository.FullName}}/releases">Releases</a></li>{{end}}
{{if .Config.Navigation.ShowIssues}}<li><a href="{{AppSubUrl}}/{{.Repository.FullName}}/issues">Issues</a></li>{{end}}
{{if .Config.Navigation.ShowDocs}}<li><a href="{{AppSubUrl}}/{{.Repository.FullName}}/wiki">{{if .Config.Navigation.LabelDocs}}{{.Config.Navigation.LabelDocs}}{{else}}Documentation{{end}}</a></li>{{end}}
{{if .Config.Navigation.ShowReleases}}<li><a href="{{AppSubUrl}}/{{.Repository.FullName}}/releases">{{if .Config.Navigation.LabelReleases}}{{.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}</a></li>{{end}}
{{if .Config.Navigation.ShowIssues}}<li><a href="{{AppSubUrl}}/{{.Repository.FullName}}/issues">{{if .Config.Navigation.LabelIssues}}{{.Config.Navigation.LabelIssues}}{{else}}Issues{{end}}</a></li>{{end}}
</ul>
</div>
</div>

View File

@@ -12,10 +12,10 @@
{{range .Config.Footer.Links}}
<a href="{{.URL}}" class="pages-nav-link">{{.Label}}</a>
{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{AppSubUrl}}/{{.Repository.FullName}}/wiki" class="pages-nav-link">Docs</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{AppSubUrl}}/{{.Repository.FullName}}/swagger" class="pages-nav-link">API</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{AppSubUrl}}/{{.Repository.FullName}}/releases" class="pages-nav-link">Releases</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{AppSubUrl}}/{{.Repository.FullName}}/issues" class="pages-nav-link">Issues</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{AppSubUrl}}/{{.Repository.FullName}}/wiki" class="pages-nav-link">{{if .Config.Navigation.LabelDocs}}{{.Config.Navigation.LabelDocs}}{{else}}Docs{{end}}</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{AppSubUrl}}/{{.Repository.FullName}}/swagger" class="pages-nav-link">{{if .Config.Navigation.LabelAPI}}{{.Config.Navigation.LabelAPI}}{{else}}API{{end}}</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{AppSubUrl}}/{{.Repository.FullName}}/releases" class="pages-nav-link">{{if .Config.Navigation.LabelReleases}}{{.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{AppSubUrl}}/{{.Repository.FullName}}/issues" class="pages-nav-link">{{if .Config.Navigation.LabelIssues}}{{.Config.Navigation.LabelIssues}}{{else}}Issues{{end}}</a>{{end}}
{{if .Config.Navigation.ShowRepository}}
<a href="{{AppSubUrl}}/{{.Repository.FullName}}" class="ui mini button" target="_blank">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy"> View Source

View File

@@ -995,16 +995,16 @@
{{range .Config.Footer.Links}}
<a href="{{.URL}}" class="ea-nav-link">{{.Label}}</a>
{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="ea-nav-link">Docs</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="ea-nav-link">API</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="ea-nav-link">Releases</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="ea-nav-link">Issues</a>{{end}}
{{if .Config.ValueProps}}<a href="{{.LandingURL}}#why" class="ea-nav-link">Why</a>{{end}}
{{if .Config.Features}}<a href="{{.LandingURL}}#features" class="ea-nav-link">Features</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="{{.LandingURL}}#pricing" class="ea-nav-link">Pricing</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="ea-nav-link">Blog</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="ea-nav-link">Gallery</a>{{end}}
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="ea-nav-link">Compare</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="ea-nav-link">{{if .Config.Navigation.LabelDocs}}{{.Config.Navigation.LabelDocs}}{{else}}Docs{{end}}</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="ea-nav-link">{{if .Config.Navigation.LabelAPI}}{{.Config.Navigation.LabelAPI}}{{else}}API{{end}}</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="ea-nav-link">{{if .Config.Navigation.LabelReleases}}{{.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="ea-nav-link">{{if .Config.Navigation.LabelIssues}}{{.Config.Navigation.LabelIssues}}{{else}}Issues{{end}}</a>{{end}}
{{if .Config.ValueProps}}<a href="{{.LandingURL}}#why" class="ea-nav-link">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Why{{end}}</a>{{end}}
{{if .Config.Features}}<a href="{{.LandingURL}}#features" class="ea-nav-link">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="{{.LandingURL}}#pricing" class="ea-nav-link">{{if .Config.Navigation.LabelPricing}}{{.Config.Navigation.LabelPricing}}{{else}}Pricing{{end}}</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="ea-nav-link">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="ea-nav-link">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</a>{{end}}
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="ea-nav-link">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</a>{{end}}
{{if .Config.Navigation.ShowRepository}}
<a href="{{.RepoURL}}" class="ea-btn-text">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
@@ -1030,16 +1030,16 @@
{{range .Config.Footer.Links}}
<a href="{{.URL}}" class="ea-nav-link">{{.Label}}</a>
{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="ea-nav-link">Docs</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="ea-nav-link">API</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="ea-nav-link">Releases</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="ea-nav-link">Issues</a>{{end}}
{{if .Config.ValueProps}}<a href="{{.LandingURL}}#why" class="ea-nav-link">Why</a>{{end}}
{{if .Config.Features}}<a href="{{.LandingURL}}#features" class="ea-nav-link">Features</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="{{.LandingURL}}#pricing" class="ea-nav-link">Pricing</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="ea-nav-link">Blog</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="ea-nav-link">Gallery</a>{{end}}
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="ea-nav-link">Compare</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="ea-nav-link">{{if .Config.Navigation.LabelDocs}}{{.Config.Navigation.LabelDocs}}{{else}}Docs{{end}}</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="ea-nav-link">{{if .Config.Navigation.LabelAPI}}{{.Config.Navigation.LabelAPI}}{{else}}API{{end}}</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="ea-nav-link">{{if .Config.Navigation.LabelReleases}}{{.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="ea-nav-link">{{if .Config.Navigation.LabelIssues}}{{.Config.Navigation.LabelIssues}}{{else}}Issues{{end}}</a>{{end}}
{{if .Config.ValueProps}}<a href="{{.LandingURL}}#why" class="ea-nav-link">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Why{{end}}</a>{{end}}
{{if .Config.Features}}<a href="{{.LandingURL}}#features" class="ea-nav-link">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="{{.LandingURL}}#pricing" class="ea-nav-link">{{if .Config.Navigation.LabelPricing}}{{.Config.Navigation.LabelPricing}}{{else}}Pricing{{end}}</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="ea-nav-link">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="ea-nav-link">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</a>{{end}}
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="ea-nav-link">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</a>{{end}}
{{if .Config.Navigation.ShowRepository}}
<a href="{{.RepoURL}}" class="ea-btn-text">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
@@ -1079,7 +1079,7 @@
<section class="ea-section" style="padding-top: 100px;">
<div class="ea-section-inner">
<div class="ea-section-header ea-reveal">
<div class="ea-section-eyebrow">Blog</div>
<div class="ea-section-eyebrow">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</div>
<h2>{{if .Config.Blog.Headline}}{{.Config.Blog.Headline}}{{else}}All Posts{{end}}</h2>
{{if .Config.Blog.Subheadline}}<p>{{.Config.Blog.Subheadline}}</p>{{end}}
</div>
@@ -1112,7 +1112,7 @@
</section>
{{else}}
<!-- Hero Section -->
<section class="ea-hero">
<section class="ea-hero" id="hero">
<div class="ea-reveal">
<div class="ea-hero-eyebrow">{{if .Config.Brand.Name}}{{.Config.Brand.Name}}{{else}}Open Source{{end}}</div>
</div>
@@ -1125,14 +1125,26 @@
{{.Config.Hero.PrimaryCTA.Label}}
{{svg "octicon-arrow-right" 14}}
</a>
{{else}}
<a href="{{.RepoURL}}" class="ea-btn-primary" data-cta="primary">
Get Started
{{svg "octicon-arrow-right" 14}}
</a>
{{end}}
</div>
{{if or $.GooglePlayID $.AppStoreID}}
<div style="display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; margin-top: 16px;" class="ea-reveal ea-reveal-delay-4">
{{if $.GooglePlayID}}
<a href="https://play.google.com/store/apps/details?id={{$.GooglePlayID}}" target="_blank" rel="noopener" class="ea-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M3.609 1.814L13.792 12 3.61 22.186a.996.996 0 0 1-.61-.92V2.734a1 1 0 0 1 .609-.92zm10.89 10.893l2.302 2.302-10.937 6.333 8.635-8.635zm3.199-1.4l2.834 1.64a1 1 0 0 1 0 1.726l-2.834 1.64-2.635-2.636 2.635-2.37zM5.864 2.658L16.8 9.99l-2.302 2.302-8.635-8.635z"/></svg>
Google Play
</a>
{{end}}
{{if $.AppStoreID}}
<a href="https://apps.apple.com/app/{{$.AppStoreID}}" target="_blank" rel="noopener" class="ea-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>
App Store
</a>
{{end}}
</div>
{{end}}
{{if .Config.Hero.CodeExample}}
<div class="ea-code-block ea-reveal ea-reveal-delay-4">
<button class="ea-copy-btn" onclick="navigator.clipboard.writeText(document.getElementById('code-example').textContent)">
@@ -1146,7 +1158,7 @@
{{if or .Config.Stats (gt .NumStars 0)}}
<div class="ea-rule"><div class="ea-rule-inner"></div></div>
<!-- Stats -->
<section class="ea-stats">
<section class="ea-stats" id="stats">
<div class="ea-stats-inner">
{{if .Config.Stats}}
{{range .Config.Stats}}
@@ -1177,7 +1189,7 @@
{{end}}
{{if and .PublicReleases .LatestRelease .LatestRelease.Attachments}}
<section class="ea-downloads">
<section class="ea-downloads" id="downloads">
<div class="ea-section-label ea-reveal">Downloads</div>
<h2 class="ea-section-title ea-reveal">v{{.LatestReleaseTag}}</h2>
<p style="color: var(--ea-muted); font-size: 17px; margin-bottom: 28px;" class="ea-reveal">Get the latest release</p>
@@ -1240,25 +1252,6 @@
</div>
{{end}}
{{end}}
{{if or $.GooglePlayID $.AppStoreID}}
<div style="margin-bottom: 20px;" class="ea-reveal">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px; font-family: 'IBM Plex Mono', monospace; font-size: 11px; color: var(--ea-light); letter-spacing: 0.08em; text-transform: uppercase;">{{svg "octicon-device-mobile" 14}} App Stores</h4>
<div style="display: flex; gap: 12px; flex-wrap: wrap;">
{{if $.GooglePlayID}}
<a href="https://play.google.com/store/apps/details?id={{$.GooglePlayID}}" target="_blank" rel="noopener" class="ea-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M3.609 1.814L13.792 12 3.61 22.186a.996.996 0 0 1-.61-.92V2.734a1 1 0 0 1 .609-.92zm10.89 10.893l2.302 2.302-10.937 6.333 8.635-8.635zm3.199-1.4l2.834 1.64a1 1 0 0 1 0 1.726l-2.834 1.64-2.635-2.636 2.635-2.37zM5.864 2.658L16.8 9.99l-2.302 2.302-8.635-8.635z"/></svg>
Google Play
</a>
{{end}}
{{if $.AppStoreID}}
<a href="https://apps.apple.com/app/{{$.AppStoreID}}" target="_blank" rel="noopener" class="ea-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>
App Store
</a>
{{end}}
</div>
</div>
{{end}}
{{if $otherFiles}}
<div style="margin-bottom: 20px;" class="ea-reveal">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px; font-family: 'IBM Plex Mono', monospace; font-size: 11px; color: var(--ea-light); letter-spacing: 0.08em; text-transform: uppercase;">{{svg "octicon-file" 14}} Other</h4>
@@ -1274,8 +1267,8 @@
<!-- Value Props -->
{{if .Config.ValueProps}}
<section class="ea-section" id="why">
<div class="ea-section-label ea-reveal">Why choose this</div>
<h2 class="ea-section-title ea-reveal">{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}{{else}}Why Choose Us{{end}}</h2>
<div class="ea-section-label ea-reveal">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Why choose this{{end}}</div>
<h2 class="ea-section-title ea-reveal">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}{{else}}Why Choose Us{{end}}{{end}}</h2>
{{range $i, $v := .Config.ValueProps}}
<div class="ea-value-item ea-reveal">
<div class="ea-value-number">{{$i | printf "%d"}}</div>
@@ -1291,8 +1284,8 @@
<!-- Features Accordion -->
{{if .Config.Features}}
<section class="ea-section" id="features">
<div class="ea-section-label ea-reveal">Capabilities</div>
<h2 class="ea-section-title ea-reveal">Features</h2>
<div class="ea-section-label ea-reveal">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Capabilities{{end}}</div>
<h2 class="ea-section-title ea-reveal">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</h2>
<div class="ea-accordion ea-reveal">
{{range $i, $f := .Config.Features}}
<div class="ea-accordion-item" data-index="{{$i}}">
@@ -1312,7 +1305,7 @@
<!-- Pricing Section -->
{{if .Config.Pricing.Plans}}
<section class="ea-pricing" id="pricing">
<div class="ea-section-label ea-reveal" style="max-width: 780px; margin: 0 auto 20px;">Investment</div>
<div class="ea-section-label ea-reveal" style="max-width: 780px; margin: 0 auto 20px;">{{if .Config.Navigation.LabelPricing}}{{.Config.Navigation.LabelPricing}}{{else}}Investment{{end}}</div>
<h2 class="ea-pricing-title ea-reveal">{{if .Config.Pricing.Headline}}{{.Config.Pricing.Headline}}{{else}}Pricing{{end}}</h2>
<div class="ea-pricing-grid ea-reveal">
{{range .Config.Pricing.Plans}}
@@ -1337,7 +1330,7 @@
<!-- Testimonial -->
{{if .Config.SocialProof.Testimonials}}
<section class="ea-testimonial-section">
<section class="ea-testimonial-section" id="testimonials">
<div class="ea-testimonial-inner">
<div class="ea-testimonials-container">
{{range .Config.SocialProof.Testimonials}}
@@ -1369,7 +1362,7 @@
<!-- Used By -->
{{if .Config.SocialProof.Logos}}
<section class="ea-used-by">
<section class="ea-used-by" id="social-proof">
<div class="ea-section-label ea-reveal">Trusted by</div>
<div class="ea-used-by-logos ea-reveal ea-reveal-delay-1">
{{range .Config.SocialProof.Logos}}
@@ -1381,7 +1374,7 @@
<!-- CTA Section -->
{{if .Config.CTASection.Headline}}
<section class="ea-cta-section">
<section class="ea-cta-section" id="cta">
<h2 class="ea-reveal">{{.Config.CTASection.Headline}}</h2>
{{if .Config.CTASection.Subheadline}}
<button class="ea-install-cmd ea-reveal ea-reveal-delay-1" onclick="navigator.clipboard.writeText('{{.Config.CTASection.Subheadline}}')">
@@ -1404,7 +1397,7 @@
<!-- Blog Section -->
{{if and .Config.Blog.Enabled .BlogPosts}}
<section class="ea-section" id="blog">
<div class="ea-section-label ea-reveal">Blog</div>
<div class="ea-section-label ea-reveal">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</div>
<h2 class="ea-section-title ea-reveal">{{if .Config.Blog.Headline}}{{.Config.Blog.Headline}}{{else}}Latest Posts{{end}}</h2>
{{if .Config.Blog.Subheadline}}<p style="color: var(--ea-muted); font-size: 17px; margin-bottom: 32px;" class="ea-reveal">{{.Config.Blog.Subheadline}}</p>{{end}}
{{range .BlogPosts}}
@@ -1439,7 +1432,7 @@
<!-- Gallery Section -->
{{if and .Config.Gallery.Enabled .GalleryImages}}
<section class="ea-section" id="gallery">
<div class="ea-section-label ea-reveal">Gallery</div>
<div class="ea-section-label ea-reveal">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</div>
<h2 class="ea-section-title ea-reveal">{{if .Config.Gallery.Headline}}{{.Config.Gallery.Headline}}{{else}}Gallery{{end}}</h2>
{{if .Config.Gallery.Subheadline}}<p style="color: var(--ea-muted); font-size: 17px; margin-bottom: 32px;" class="ea-reveal">{{.Config.Gallery.Subheadline}}</p>{{end}}
<div style="display: grid; grid-template-columns: repeat({{if .Config.Gallery.Columns}}{{.Config.Gallery.Columns}}{{else}}3{{end}}, 1fr); gap: 16px;">
@@ -1462,7 +1455,7 @@
<section class="ea-section" id="comparison" style="border-top: 1px solid var(--ea-border);">
<div class="ea-section-inner">
<div class="ea-section-header ea-reveal">
<div class="ea-section-label">Compare</div>
<div class="ea-section-label">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</div>
<h2>{{if .Config.Comparison.Headline}}{{.Config.Comparison.Headline}}{{else}}How We Compare{{end}}</h2>
{{if .Config.Comparison.Subheadline}}<p>{{.Config.Comparison.Subheadline}}</p>{{end}}
</div>
@@ -1542,8 +1535,8 @@
{{end}}
{{if .Config.Navigation.ShowRepository}}<a href="{{.RepoURL}}" class="ea-footer-link">Repository</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="ea-footer-link">Documentation</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="ea-footer-link">Releases</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="ea-footer-link">Issues</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="ea-footer-link">{{if .Config.Navigation.LabelReleases}}{{.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="ea-footer-link">{{if .Config.Navigation.LabelIssues}}{{.Config.Navigation.LabelIssues}}{{else}}Issues{{end}}</a>{{end}}
</div>
</footer>
</div>

View File

@@ -986,16 +986,16 @@
{{range .Config.Footer.Links}}
<a href="{{.URL}}" class="osh-nav-link">{{.Label}}</a>
{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="osh-nav-link">Docs</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="osh-nav-link">API</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="osh-nav-link">Releases</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="osh-nav-link">Issues</a>{{end}}
{{if .Config.ValueProps}}<a href="{{.LandingURL}}#value-props" class="osh-nav-link">Why Us</a>{{end}}
{{if .Config.Features}}<a href="{{.LandingURL}}#features" class="osh-nav-link">Features</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="{{.LandingURL}}#pricing" class="osh-nav-link">Pricing</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="osh-nav-link">Blog</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="osh-nav-link">Gallery</a>{{end}}
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="osh-nav-link">Compare</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="osh-nav-link">{{if .Config.Navigation.LabelDocs}}{{.Config.Navigation.LabelDocs}}{{else}}Docs{{end}}</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="osh-nav-link">{{if .Config.Navigation.LabelAPI}}{{.Config.Navigation.LabelAPI}}{{else}}API{{end}}</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="osh-nav-link">{{if .Config.Navigation.LabelReleases}}{{.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="osh-nav-link">{{if .Config.Navigation.LabelIssues}}{{.Config.Navigation.LabelIssues}}{{else}}Issues{{end}}</a>{{end}}
{{if .Config.ValueProps}}<a href="{{.LandingURL}}#value-props" class="osh-nav-link">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Why Us{{end}}</a>{{end}}
{{if .Config.Features}}<a href="{{.LandingURL}}#features" class="osh-nav-link">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="{{.LandingURL}}#pricing" class="osh-nav-link">{{if .Config.Navigation.LabelPricing}}{{.Config.Navigation.LabelPricing}}{{else}}Pricing{{end}}</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="osh-nav-link">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="osh-nav-link">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</a>{{end}}
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="osh-nav-link">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</a>{{end}}
{{if .Config.Navigation.ShowRepository}}
<a href="{{.RepoURL}}" class="osh-nav-cta">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
@@ -1051,7 +1051,7 @@
<section class="osh-features" style="padding-top: 120px;">
<div class="osh-features-inner">
<div class="osh-section-header osh-reveal">
<div class="osh-section-label">Blog</div>
<div class="osh-section-label">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</div>
<h2>{{if .Config.Blog.Headline}}{{.Config.Blog.Headline}}{{else}}All Posts{{end}}</h2>
{{if .Config.Blog.Subheadline}}<p>{{.Config.Blog.Subheadline}}</p>{{end}}
</div>
@@ -1082,7 +1082,7 @@
</section>
{{else}}
<!-- Hero Section -->
<section class="osh-hero">
<section class="osh-hero" id="hero">
<div class="osh-hero-gradient"></div>
<div class="osh-hero-content">
@@ -1103,11 +1103,6 @@
{{.Config.Hero.PrimaryCTA.Label}}
{{svg "octicon-arrow-right" 16}}
</a>
{{else}}
<a href="{{.RepoURL}}" class="osh-btn-primary" data-cta="primary">
Get Started
{{svg "octicon-arrow-right" 16}}
</a>
{{end}}
{{if .Config.Hero.SecondaryCTA.Label}}
@@ -1115,14 +1110,26 @@
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
{{.Config.Hero.SecondaryCTA.Label}}
</a>
{{else}}
<a href="{{.RepoURL}}" class="osh-btn-secondary" data-cta="secondary">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
View Source
</a>
{{end}}
</div>
{{if or $.GooglePlayID $.AppStoreID}}
<div style="display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; margin-top: 16px;" class="osh-reveal visible osh-reveal-delay-4">
{{if $.GooglePlayID}}
<a href="https://play.google.com/store/apps/details?id={{$.GooglePlayID}}" target="_blank" rel="noopener" class="osh-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3.609 1.814L13.792 12 3.61 22.186a.996.996 0 0 1-.61-.92V2.734a1 1 0 0 1 .609-.92zm10.89 10.893l2.302 2.302-10.937 6.333 8.635-8.635zm3.199-1.4l2.834 1.64a1 1 0 0 1 0 1.726l-2.834 1.64-2.635-2.636 2.635-2.37zM5.864 2.658L16.8 9.99l-2.302 2.302-8.635-8.635z"/></svg>
Google Play
</a>
{{end}}
{{if $.AppStoreID}}
<a href="https://apps.apple.com/app/{{$.AppStoreID}}" target="_blank" rel="noopener" class="osh-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>
App Store
</a>
{{end}}
</div>
{{end}}
{{if .Config.Hero.CodeExample}}
<div class="osh-code-block osh-reveal visible osh-reveal-delay-4">
<span class="osh-code-prompt">$</span>
@@ -1137,7 +1144,7 @@
<!-- Stats Section -->
{{if or .Config.Stats (gt .NumStars 0)}}
<section class="osh-stats">
<section class="osh-stats" id="stats">
<div class="osh-stats-inner osh-reveal">
{{if .Config.Stats}}
{{range .Config.Stats}}
@@ -1168,7 +1175,7 @@
<!-- Downloads Section -->
{{if and .PublicReleases .LatestRelease .LatestRelease.Attachments}}
<section class="osh-downloads">
<section class="osh-downloads" id="downloads">
<div class="osh-downloads-inner osh-reveal">
<h2>Download v{{.LatestReleaseTag}}</h2>
<p>Get the latest release</p>
@@ -1231,25 +1238,6 @@
</div>
{{end}}
{{end}}
{{if or $.GooglePlayID $.AppStoreID}}
<div style="margin-bottom: 24px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 14px; color: var(--osh-muted); font-family: 'IBM Plex Mono', monospace; letter-spacing: 0.05em; text-transform: uppercase;">{{svg "octicon-device-mobile" 16}} App Stores</h4>
<div style="display: flex; gap: 12px; flex-wrap: wrap;">
{{if $.GooglePlayID}}
<a href="https://play.google.com/store/apps/details?id={{$.GooglePlayID}}" target="_blank" rel="noopener" class="osh-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3.609 1.814L13.792 12 3.61 22.186a.996.996 0 0 1-.61-.92V2.734a1 1 0 0 1 .609-.92zm10.89 10.893l2.302 2.302-10.937 6.333 8.635-8.635zm3.199-1.4l2.834 1.64a1 1 0 0 1 0 1.726l-2.834 1.64-2.635-2.636 2.635-2.37zM5.864 2.658L16.8 9.99l-2.302 2.302-8.635-8.635z"/></svg>
Google Play
</a>
{{end}}
{{if $.AppStoreID}}
<a href="https://apps.apple.com/app/{{$.AppStoreID}}" target="_blank" rel="noopener" class="osh-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>
App Store
</a>
{{end}}
</div>
</div>
{{end}}
{{if $otherFiles}}
<div style="margin-bottom: 24px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 14px; color: var(--osh-muted); font-family: 'IBM Plex Mono', monospace; letter-spacing: 0.05em; text-transform: uppercase;">{{svg "octicon-file" 16}} Other</h4>
@@ -1267,10 +1255,9 @@
<section class="osh-features" id="value-props">
<div class="osh-features-inner">
<div class="osh-section-header osh-reveal">
<div class="osh-section-label">Why choose us</div>
<h2>{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}</h2>
<p>Everything you need to get started quickly.</p>
</div>
<div class="osh-section-label">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Why choose us{{end}}</div>
<h2>{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}{{end}}</h2>
</div>
<div class="osh-features-grid">
{{range .Config.ValueProps}}
<div class="osh-feature-card osh-reveal">
@@ -1291,10 +1278,9 @@
<section class="osh-features" id="features" style="padding-top: 40px;">
<div class="osh-features-inner">
<div class="osh-section-header osh-reveal">
<div class="osh-section-label">Capabilities</div>
<h2>Features</h2>
<p>Powerful capabilities at your fingertips.</p>
</div>
<div class="osh-section-label">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Capabilities{{end}}</div>
<h2>{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</h2>
</div>
<div class="osh-features-grid">
{{range .Config.Features}}
<div class="osh-feature-card osh-reveal">
@@ -1312,7 +1298,7 @@
<!-- Social Proof -->
{{if or .Config.SocialProof.Logos .Config.SocialProof.Testimonials}}
<section class="osh-social-proof">
<section class="osh-social-proof" id="social-proof">
<div class="osh-social-proof-inner">
{{if .Config.SocialProof.Logos}}
<div class="osh-logos osh-reveal">
@@ -1352,7 +1338,7 @@
<section class="osh-pricing" id="pricing">
<div class="osh-pricing-inner">
<div class="osh-section-header osh-reveal">
<div class="osh-section-label">Pricing</div>
<div class="osh-section-label">{{if .Config.Navigation.LabelPricing}}{{.Config.Navigation.LabelPricing}}{{else}}Pricing{{end}}</div>
<h2>{{if .Config.Pricing.Headline}}{{.Config.Pricing.Headline}}{{else}}Pricing{{end}}</h2>
<p>{{if .Config.Pricing.Subheadline}}{{.Config.Pricing.Subheadline}}{{else}}Choose the plan that works for you{{end}}</p>
</div>
@@ -1378,7 +1364,7 @@
<!-- CTA Section -->
{{if .Config.CTASection.Headline}}
<section class="osh-cta-section">
<section class="osh-cta-section" id="cta">
<div class="osh-cta-inner osh-reveal">
<h2>{{.Config.CTASection.Headline}}</h2>
{{if .Config.CTASection.Subheadline}}
@@ -1397,7 +1383,7 @@
<section class="osh-features" id="blog" style="border-top: 1px solid rgba(255,255,255,0.04);">
<div class="osh-features-inner">
<div class="osh-section-header osh-reveal">
<div class="osh-section-label">Blog</div>
<div class="osh-section-label">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</div>
<h2>{{if .Config.Blog.Headline}}{{.Config.Blog.Headline}}{{else}}Latest Posts{{end}}</h2>
{{if .Config.Blog.Subheadline}}<p>{{.Config.Blog.Subheadline}}</p>{{end}}
</div>
@@ -1436,7 +1422,7 @@
<section class="osh-features" id="gallery" style="border-top: 1px solid rgba(255,255,255,0.04);">
<div class="osh-features-inner">
<div class="osh-section-header osh-reveal">
<div class="osh-section-label">Gallery</div>
<div class="osh-section-label">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</div>
<h2>{{if .Config.Gallery.Headline}}{{.Config.Gallery.Headline}}{{else}}Gallery{{end}}</h2>
{{if .Config.Gallery.Subheadline}}<p>{{.Config.Gallery.Subheadline}}</p>{{end}}
</div>
@@ -1461,7 +1447,7 @@
<section class="osh-features" id="comparison" style="border-top: 1px solid rgba(255,255,255,0.04);">
<div class="osh-features-inner">
<div class="osh-section-header osh-reveal">
<div class="osh-section-label">Compare</div>
<div class="osh-section-label">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</div>
<h2>{{if .Config.Comparison.Headline}}{{.Config.Comparison.Headline}}{{else}}How We Compare{{end}}</h2>
{{if .Config.Comparison.Subheadline}}<p>{{.Config.Comparison.Subheadline}}</p>{{end}}
</div>
@@ -1544,8 +1530,8 @@
{{end}}
{{if .Config.Navigation.ShowRepository}}<a href="{{.RepoURL}}" class="osh-footer-link">Repository</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="osh-footer-link">Documentation</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="osh-footer-link">Releases</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="osh-footer-link">Issues</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="osh-footer-link">{{if .Config.Navigation.LabelReleases}}{{.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="osh-footer-link">{{if .Config.Navigation.LabelIssues}}{{.Config.Navigation.LabelIssues}}{{else}}Issues{{end}}</a>{{end}}
</div>
</footer>
</div>

View File

@@ -30,6 +30,7 @@
.gm-reveal-delay-1 { transition-delay: 0.12s; }
.gm-reveal-delay-2 { transition-delay: 0.24s; }
.gm-reveal-delay-3 { transition-delay: 0.36s; }
.gm-reveal-delay-4 { transition-delay: 0.48s; }
.gm-page {
min-height: 100vh;
@@ -1103,16 +1104,16 @@
{{range .Config.Footer.Links}}
<a href="{{.URL}}" class="gm-nav-link">{{.Label}}</a>
{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="gm-nav-link">Docs</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="gm-nav-link">API</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="gm-nav-link">Releases</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="gm-nav-link">Issues</a>{{end}}
{{if .Config.ValueProps}}<a href="{{.LandingURL}}#value-props" class="gm-nav-link">Why</a>{{end}}
{{if .Config.Features}}<a href="{{.LandingURL}}#features" class="gm-nav-link">Features</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="{{.LandingURL}}#pricing" class="gm-nav-link">Pricing</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="gm-nav-link">Blog</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="gm-nav-link">Gallery</a>{{end}}
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="gm-nav-link">Compare</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="gm-nav-link">{{if .Config.Navigation.LabelDocs}}{{.Config.Navigation.LabelDocs}}{{else}}Docs{{end}}</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="gm-nav-link">{{if .Config.Navigation.LabelAPI}}{{.Config.Navigation.LabelAPI}}{{else}}API{{end}}</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="gm-nav-link">{{if .Config.Navigation.LabelReleases}}{{.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="gm-nav-link">{{if .Config.Navigation.LabelIssues}}{{.Config.Navigation.LabelIssues}}{{else}}Issues{{end}}</a>{{end}}
{{if .Config.ValueProps}}<a href="{{.LandingURL}}#value-props" class="gm-nav-link">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Why{{end}}</a>{{end}}
{{if .Config.Features}}<a href="{{.LandingURL}}#features" class="gm-nav-link">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="{{.LandingURL}}#pricing" class="gm-nav-link">{{if .Config.Navigation.LabelPricing}}{{.Config.Navigation.LabelPricing}}{{else}}Pricing{{end}}</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="gm-nav-link">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="gm-nav-link">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</a>{{end}}
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="gm-nav-link">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</a>{{end}}
{{if .Config.Navigation.ShowRepository}}
<a href="{{.RepoURL}}" class="gm-nav-repo">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
@@ -1131,9 +1132,11 @@
</div>
</div>
{{end}}
<a href="{{if .Config.Hero.PrimaryCTA.URL}}{{.Config.Hero.PrimaryCTA.URL}}{{else}}{{.RepoURL}}{{end}}" class="gm-btn-primary" data-cta="primary" style="padding: 10px 20px; font-size: 13px;">
<span>{{if .Config.Hero.PrimaryCTA.Label}}{{.Config.Hero.PrimaryCTA.Label}}{{else}}Get Started{{end}}</span>
{{if .Config.Hero.PrimaryCTA.Label}}
<a href="{{.Config.Hero.PrimaryCTA.URL}}" class="gm-btn-primary" data-cta="primary" style="padding: 10px 20px; font-size: 13px;">
<span>{{.Config.Hero.PrimaryCTA.Label}}</span>
</a>
{{end}}
</div>
<button class="gm-mobile-toggle" onclick="document.getElementById('gm-mobile-nav').classList.toggle('open')">Menu</button>
</nav>
@@ -1141,16 +1144,16 @@
{{range .Config.Footer.Links}}
<a href="{{.URL}}" class="gm-nav-link">{{.Label}}</a>
{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="gm-nav-link">Docs</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="gm-nav-link">API</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="gm-nav-link">Releases</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="gm-nav-link">Issues</a>{{end}}
{{if .Config.ValueProps}}<a href="{{.LandingURL}}#value-props" class="gm-nav-link">Why</a>{{end}}
{{if .Config.Features}}<a href="{{.LandingURL}}#features" class="gm-nav-link">Features</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="{{.LandingURL}}#pricing" class="gm-nav-link">Pricing</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="gm-nav-link">Blog</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="gm-nav-link">Gallery</a>{{end}}
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="gm-nav-link">Compare</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="gm-nav-link">{{if .Config.Navigation.LabelDocs}}{{.Config.Navigation.LabelDocs}}{{else}}Docs{{end}}</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="gm-nav-link">{{if .Config.Navigation.LabelAPI}}{{.Config.Navigation.LabelAPI}}{{else}}API{{end}}</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="gm-nav-link">{{if .Config.Navigation.LabelReleases}}{{.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="gm-nav-link">{{if .Config.Navigation.LabelIssues}}{{.Config.Navigation.LabelIssues}}{{else}}Issues{{end}}</a>{{end}}
{{if .Config.ValueProps}}<a href="{{.LandingURL}}#value-props" class="gm-nav-link">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Why{{end}}</a>{{end}}
{{if .Config.Features}}<a href="{{.LandingURL}}#features" class="gm-nav-link">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="{{.LandingURL}}#pricing" class="gm-nav-link">{{if .Config.Navigation.LabelPricing}}{{.Config.Navigation.LabelPricing}}{{else}}Pricing{{end}}</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="gm-nav-link">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="gm-nav-link">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</a>{{end}}
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="gm-nav-link">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</a>{{end}}
{{if .Config.Navigation.ShowRepository}}
<a href="{{.RepoURL}}" class="gm-nav-repo">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
@@ -1190,7 +1193,7 @@
<section class="gm-section" style="padding-top: 120px;">
<div class="gm-section-inner">
<div class="gm-section-header gm-reveal">
<div class="gm-section-eyebrow">Blog</div>
<div class="gm-section-eyebrow">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</div>
<h2>{{if .Config.Blog.Headline}}{{.Config.Blog.Headline}}{{else}}All Posts{{end}}</h2>
{{if .Config.Blog.Subheadline}}<p>{{.Config.Blog.Subheadline}}</p>{{end}}
</div>
@@ -1221,7 +1224,7 @@
</section>
{{else}}
<!-- Hero Section -->
<section class="gm-hero">
<section class="gm-hero" id="hero">
<div class="gm-hero-inner">
<div>
{{if gt .NumStars 100}}
@@ -1238,12 +1241,14 @@
</p>
<div class="gm-hero-ctas gm-reveal gm-reveal-delay-3">
<a href="{{if .Config.Hero.PrimaryCTA.URL}}{{.Config.Hero.PrimaryCTA.URL}}{{else}}{{.RepoURL}}{{end}}" class="gm-btn-primary" data-cta="primary">
{{if .Config.Hero.PrimaryCTA.Label}}
<a href="{{.Config.Hero.PrimaryCTA.URL}}" class="gm-btn-primary" data-cta="primary">
<span>
{{if .Config.Hero.PrimaryCTA.Label}}{{.Config.Hero.PrimaryCTA.Label}}{{else}}Get Started{{end}}
{{.Config.Hero.PrimaryCTA.Label}}
{{svg "octicon-arrow-right" 16}}
</span>
</a>
{{end}}
{{if .Config.Hero.SecondaryCTA.Label}}
<a href="{{.Config.Hero.SecondaryCTA.URL}}" class="gm-btn-secondary" data-cta="secondary">
{{svg "octicon-play" 16}}
@@ -1252,6 +1257,23 @@
{{end}}
</div>
{{if or $.GooglePlayID $.AppStoreID}}
<div style="display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; margin-top: 16px;" class="gm-reveal gm-reveal-delay-4">
{{if $.GooglePlayID}}
<a href="https://play.google.com/store/apps/details?id={{$.GooglePlayID}}" target="_blank" rel="noopener" class="gm-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3.609 1.814L13.792 12 3.61 22.186a.996.996 0 0 1-.61-.92V2.734a1 1 0 0 1 .609-.92zm10.89 10.893l2.302 2.302-10.937 6.333 8.635-8.635zm3.199-1.4l2.834 1.64a1 1 0 0 1 0 1.726l-2.834 1.64-2.635-2.636 2.635-2.37zM5.864 2.658L16.8 9.99l-2.302 2.302-8.635-8.635z"/></svg>
Google Play
</a>
{{end}}
{{if $.AppStoreID}}
<a href="https://apps.apple.com/app/{{$.AppStoreID}}" target="_blank" rel="noopener" class="gm-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>
App Store
</a>
{{end}}
</div>
{{end}}
<div class="gm-hero-note gm-reveal gm-reveal-delay-3">
<span>{{svg "octicon-check" 14}} Free to use</span>
<span>{{svg "octicon-check" 14}} Open source</span>
@@ -1280,7 +1302,7 @@
<!-- Stats Section -->
{{if .Config.Stats}}
<section class="gm-stats">
<section class="gm-stats" id="stats">
<div class="gm-stats-grid">
{{range .Config.Stats}}
<div class="gm-stat-card gm-reveal">
@@ -1293,7 +1315,7 @@
{{end}}
{{if and .PublicReleases .LatestRelease .LatestRelease.Attachments}}
<section class="gm-downloads">
<section class="gm-downloads" id="downloads">
<div class="gm-downloads-inner">
<div class="gm-downloads-header gm-reveal">
<h2>Download v{{.LatestReleaseTag}}</h2>
@@ -1358,25 +1380,6 @@
</div>
{{end}}
{{end}}
{{if or $.GooglePlayID $.AppStoreID}}
<div style="margin-bottom: 24px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 14px; color: var(--gm-light); font-weight: 600;">{{svg "octicon-device-mobile" 16}} App Stores</h4>
<div style="display: flex; gap: 12px; flex-wrap: wrap;">
{{if $.GooglePlayID}}
<a href="https://play.google.com/store/apps/details?id={{$.GooglePlayID}}" target="_blank" rel="noopener" class="gm-download-item gm-reveal" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3.609 1.814L13.792 12 3.61 22.186a.996.996 0 0 1-.61-.92V2.734a1 1 0 0 1 .609-.92zm10.89 10.893l2.302 2.302-10.937 6.333 8.635-8.635zm3.199-1.4l2.834 1.64a1 1 0 0 1 0 1.726l-2.834 1.64-2.635-2.636 2.635-2.37zM5.864 2.658L16.8 9.99l-2.302 2.302-8.635-8.635z"/></svg>
Google Play
</a>
{{end}}
{{if $.AppStoreID}}
<a href="https://apps.apple.com/app/{{$.AppStoreID}}" target="_blank" rel="noopener" class="gm-download-item gm-reveal" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>
App Store
</a>
{{end}}
</div>
</div>
{{end}}
{{if $otherFiles}}
<div style="margin-bottom: 24px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 14px; color: var(--gm-light); font-weight: 600;">{{svg "octicon-file" 16}} Other</h4>
@@ -1391,7 +1394,7 @@
<!-- Trust Bar -->
{{if .Config.SocialProof.Logos}}
<section class="gm-trust">
<section class="gm-trust" id="social-proof">
<p class="gm-trust-label gm-reveal">Trusted by teams at</p>
<div class="gm-trust-bar gm-reveal gm-reveal-delay-1">
{{range .Config.SocialProof.Logos}}
@@ -1450,7 +1453,7 @@
<!-- Testimonials -->
{{if .Config.SocialProof.Testimonials}}
<section class="gm-section">
<section class="gm-section" id="testimonials">
<div class="gm-testimonial-card">
<div class="gm-testimonials-container">
{{range .Config.SocialProof.Testimonials}}
@@ -1518,7 +1521,7 @@
<!-- Final CTA -->
{{if .Config.CTASection.Headline}}
<section class="gm-section">
<section class="gm-section" id="cta">
<div class="gm-cta-gradient gm-reveal">
<h2>{{.Config.CTASection.Headline}}</h2>
{{if .Config.CTASection.Subheadline}}
@@ -1601,7 +1604,7 @@
<section class="gm-section" id="comparison" style="border-top: 1px solid rgba(255,255,255,0.06);">
<div class="gm-section-inner">
<div class="gm-section-header gm-reveal">
<div class="gm-section-label">Compare</div>
<div class="gm-section-label">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</div>
<h2>{{if .Config.Comparison.Headline}}{{.Config.Comparison.Headline}}{{else}}How We Compare{{end}}</h2>
{{if .Config.Comparison.Subheadline}}<p>{{.Config.Comparison.Subheadline}}</p>{{end}}
</div>
@@ -1683,9 +1686,9 @@
<a href="{{.URL}}" class="gm-footer-link">{{.Label}}</a>
{{end}}
{{if .Config.Navigation.ShowRepository}}<a href="{{.RepoURL}}" class="gm-footer-link">Repository</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="gm-footer-link">Docs</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="gm-footer-link">Releases</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="gm-footer-link">Issues</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="gm-footer-link">{{if .Config.Navigation.LabelDocs}}{{.Config.Navigation.LabelDocs}}{{else}}Docs{{end}}</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="gm-footer-link">{{if .Config.Navigation.LabelReleases}}{{.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="gm-footer-link">{{if .Config.Navigation.LabelIssues}}{{.Config.Navigation.LabelIssues}}{{else}}Issues{{end}}</a>{{end}}
</div>
</footer>
</div>

View File

@@ -1022,16 +1022,16 @@
{{range .Config.Footer.Links}}
<a href="{{.URL}}" class="vs-nav-link">{{.Label}}</a>
{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="vs-nav-link">Docs</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="vs-nav-link">API</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="vs-nav-link">Releases</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="vs-nav-link">Issues</a>{{end}}
{{if .Config.ValueProps}}<a href="{{.LandingURL}}#value-props" class="vs-nav-link">Value Props</a>{{end}}
{{if .Config.Features}}<a href="{{.LandingURL}}#features" class="vs-nav-link">Features</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="{{.LandingURL}}#pricing" class="vs-nav-link">Pricing</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="vs-nav-link">Blog</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="vs-nav-link">Gallery</a>{{end}}
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="vs-nav-link">Compare</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="vs-nav-link">{{if .Config.Navigation.LabelDocs}}{{.Config.Navigation.LabelDocs}}{{else}}Docs{{end}}</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="vs-nav-link">{{if .Config.Navigation.LabelAPI}}{{.Config.Navigation.LabelAPI}}{{else}}API{{end}}</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="vs-nav-link">{{if .Config.Navigation.LabelReleases}}{{.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="vs-nav-link">{{if .Config.Navigation.LabelIssues}}{{.Config.Navigation.LabelIssues}}{{else}}Issues{{end}}</a>{{end}}
{{if .Config.ValueProps}}<a href="{{.LandingURL}}#value-props" class="vs-nav-link">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Value Props{{end}}</a>{{end}}
{{if .Config.Features}}<a href="{{.LandingURL}}#features" class="vs-nav-link">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="{{.LandingURL}}#pricing" class="vs-nav-link">{{if .Config.Navigation.LabelPricing}}{{.Config.Navigation.LabelPricing}}{{else}}Pricing{{end}}</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="vs-nav-link">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="vs-nav-link">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</a>{{end}}
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="vs-nav-link">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</a>{{end}}
{{if .Config.Navigation.ShowRepository}}
<a href="{{.RepoURL}}" class="vs-nav-cta">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
@@ -1087,7 +1087,7 @@
<section class="vs-features" style="padding-top: 140px;">
<div class="vs-features-inner">
<div class="vs-section-header vs-reveal">
<div class="vs-section-label">Blog</div>
<div class="vs-section-label">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</div>
<h2>{{if .Config.Blog.Headline}}{{.Config.Blog.Headline}}{{else}}All Posts{{end}}</h2>
{{if .Config.Blog.Subheadline}}<p>{{.Config.Blog.Subheadline}}</p>{{end}}
</div>
@@ -1118,7 +1118,7 @@
</section>
{{else}}
<!-- Hero Section -->
<section class="vs-hero">
<section class="vs-hero" id="hero">
<div class="vs-hero-mesh">
<div class="vs-hero-blob-1"></div>
<div class="vs-hero-blob-2"></div>
@@ -1143,11 +1143,6 @@
{{.Config.Hero.PrimaryCTA.Label}}
{{svg "octicon-arrow-right" 16}}
</a>
{{else}}
<a href="{{.RepoURL}}" class="vs-btn-primary" data-cta="primary">
Get Started
{{svg "octicon-arrow-right" 16}}
</a>
{{end}}
{{if .Config.Hero.SecondaryCTA.Label}}
@@ -1155,14 +1150,26 @@
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
{{.Config.Hero.SecondaryCTA.Label}}
</a>
{{else}}
<a href="{{.RepoURL}}" class="vs-btn-secondary" data-cta="secondary">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
View Source
</a>
{{end}}
</div>
{{if or $.GooglePlayID $.AppStoreID}}
<div style="display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; margin-top: 16px;" class="vs-reveal visible vs-reveal-delay-4">
{{if $.GooglePlayID}}
<a href="https://play.google.com/store/apps/details?id={{$.GooglePlayID}}" target="_blank" rel="noopener" class="vs-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3.609 1.814L13.792 12 3.61 22.186a.996.996 0 0 1-.61-.92V2.734a1 1 0 0 1 .609-.92zm10.89 10.893l2.302 2.302-10.937 6.333 8.635-8.635zm3.199-1.4l2.834 1.64a1 1 0 0 1 0 1.726l-2.834 1.64-2.635-2.636 2.635-2.37zM5.864 2.658L16.8 9.99l-2.302 2.302-8.635-8.635z"/></svg>
Google Play
</a>
{{end}}
{{if $.AppStoreID}}
<a href="https://apps.apple.com/app/{{$.AppStoreID}}" target="_blank" rel="noopener" class="vs-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>
App Store
</a>
{{end}}
</div>
{{end}}
{{if .Config.Hero.CodeExample}}
<div class="vs-code-block vs-reveal visible vs-reveal-delay-4">
<span class="vs-code-prompt">$</span>
@@ -1177,7 +1184,7 @@
<!-- Stats Section -->
{{if or .Config.Stats (gt .NumStars 0)}}
<section class="vs-stats">
<section class="vs-stats" id="stats">
<div class="vs-stats-inner vs-reveal">
{{if .Config.Stats}}
{{range .Config.Stats}}
@@ -1208,7 +1215,7 @@
<!-- Downloads Section -->
{{if and .PublicReleases .LatestRelease .LatestRelease.Attachments}}
<section class="vs-downloads">
<section class="vs-downloads" id="downloads">
<div class="vs-downloads-inner vs-reveal">
<h2>Download v{{.LatestReleaseTag}}</h2>
<p>Get the latest release</p>
@@ -1271,25 +1278,6 @@
</div>
{{end}}
{{end}}
{{if or $.GooglePlayID $.AppStoreID}}
<div style="margin-bottom: 28px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 14px; font-size: 13px; color: var(--vs-muted); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600;">{{svg "octicon-device-mobile" 16}} App Stores</h4>
<div style="display: flex; gap: 14px; flex-wrap: wrap; justify-content: center;">
{{if $.GooglePlayID}}
<a href="https://play.google.com/store/apps/details?id={{$.GooglePlayID}}" target="_blank" rel="noopener" class="vs-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3.609 1.814L13.792 12 3.61 22.186a.996.996 0 0 1-.61-.92V2.734a1 1 0 0 1 .609-.92zm10.89 10.893l2.302 2.302-10.937 6.333 8.635-8.635zm3.199-1.4l2.834 1.64a1 1 0 0 1 0 1.726l-2.834 1.64-2.635-2.636 2.635-2.37zM5.864 2.658L16.8 9.99l-2.302 2.302-8.635-8.635z"/></svg>
Google Play
</a>
{{end}}
{{if $.AppStoreID}}
<a href="https://apps.apple.com/app/{{$.AppStoreID}}" target="_blank" rel="noopener" class="vs-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>
App Store
</a>
{{end}}
</div>
</div>
{{end}}
{{if $otherFiles}}
<div style="margin-bottom: 28px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 14px; font-size: 13px; color: var(--vs-muted); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600;">{{svg "octicon-file" 16}} Other</h4>
@@ -1307,10 +1295,9 @@
<section class="vs-features" id="value-props" style="background: var(--vs-surface);">
<div class="vs-features-inner">
<div class="vs-section-header vs-reveal">
<div class="vs-section-label">Why choose us</div>
<h2>{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}</h2>
<p>Everything you need to get started quickly.</p>
</div>
<div class="vs-section-label">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Why choose us{{end}}</div>
<h2>{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}{{end}}</h2>
</div>
<div class="vs-features-grid">
{{range .Config.ValueProps}}
<div class="vs-feature-card vs-reveal">
@@ -1331,10 +1318,9 @@
<section class="vs-features" id="features" style="padding-top: 40px;">
<div class="vs-features-inner">
<div class="vs-section-header vs-reveal">
<div class="vs-section-label">Capabilities</div>
<h2>Features</h2>
<p>Powerful capabilities at your fingertips.</p>
</div>
<div class="vs-section-label">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Capabilities{{end}}</div>
<h2>{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</h2>
</div>
<div class="vs-features-grid">
{{range .Config.Features}}
<div class="vs-feature-card vs-reveal">
@@ -1352,7 +1338,7 @@
<!-- Social Proof -->
{{if or .Config.SocialProof.Logos .Config.SocialProof.Testimonials}}
<section class="vs-social-proof">
<section class="vs-social-proof" id="social-proof">
<div class="vs-social-proof-inner">
{{if .Config.SocialProof.Logos}}
<div class="vs-logos vs-reveal">
@@ -1392,7 +1378,7 @@
<section class="vs-pricing" id="pricing">
<div class="vs-pricing-inner">
<div class="vs-section-header vs-reveal">
<div class="vs-section-label">Pricing</div>
<div class="vs-section-label">{{if .Config.Navigation.LabelPricing}}{{.Config.Navigation.LabelPricing}}{{else}}Pricing{{end}}</div>
<h2>{{if .Config.Pricing.Headline}}{{.Config.Pricing.Headline}}{{else}}Pricing{{end}}</h2>
<p>{{if .Config.Pricing.Subheadline}}{{.Config.Pricing.Subheadline}}{{else}}Choose the plan that works for you{{end}}</p>
</div>
@@ -1418,7 +1404,7 @@
<!-- CTA Section -->
{{if .Config.CTASection.Headline}}
<section class="vs-cta-section">
<section class="vs-cta-section" id="cta">
<div class="vs-cta-inner vs-reveal">
<h2>{{.Config.CTASection.Headline}}</h2>
{{if .Config.CTASection.Subheadline}}
@@ -1437,7 +1423,7 @@
<section class="vs-features" id="blog" style="border-top: 1px solid var(--vs-border); background: var(--vs-surface);">
<div class="vs-features-inner">
<div class="vs-section-header vs-reveal">
<div class="vs-section-label">Blog</div>
<div class="vs-section-label">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</div>
<h2>{{if .Config.Blog.Headline}}{{.Config.Blog.Headline}}{{else}}Latest Posts{{end}}</h2>
{{if .Config.Blog.Subheadline}}<p>{{.Config.Blog.Subheadline}}</p>{{end}}
</div>
@@ -1476,7 +1462,7 @@
<section class="vs-features" id="gallery" style="border-top: 1px solid var(--vs-border);">
<div class="vs-features-inner">
<div class="vs-section-header vs-reveal">
<div class="vs-section-label">Gallery</div>
<div class="vs-section-label">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</div>
<h2>{{if .Config.Gallery.Headline}}{{.Config.Gallery.Headline}}{{else}}Gallery{{end}}</h2>
{{if .Config.Gallery.Subheadline}}<p>{{.Config.Gallery.Subheadline}}</p>{{end}}
</div>
@@ -1501,7 +1487,7 @@
<section class="vs-features" id="comparison" style="border-top: 1px solid var(--vs-border);">
<div class="vs-features-inner">
<div class="vs-section-header vs-reveal">
<div class="vs-section-label">Compare</div>
<div class="vs-section-label">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</div>
<h2>{{if .Config.Comparison.Headline}}{{.Config.Comparison.Headline}}{{else}}How We Compare{{end}}</h2>
{{if .Config.Comparison.Subheadline}}<p>{{.Config.Comparison.Subheadline}}</p>{{end}}
</div>
@@ -1584,8 +1570,8 @@
{{end}}
{{if .Config.Navigation.ShowRepository}}<a href="{{.RepoURL}}" class="vs-footer-link">Repository</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="vs-footer-link">Documentation</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="vs-footer-link">Releases</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="vs-footer-link">Issues</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="vs-footer-link">{{if .Config.Navigation.LabelReleases}}{{.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="vs-footer-link">{{if .Config.Navigation.LabelIssues}}{{.Config.Navigation.LabelIssues}}{{else}}Issues{{end}}</a>{{end}}
</div>
</footer>
</div>

View File

@@ -53,7 +53,7 @@
</a>
{{end}}
{{end}}
<details class="item toggleable-item" {{if or .PageIsSettingsPagesGeneral .PageIsSettingsPagesBrand .PageIsSettingsPagesHero .PageIsSettingsPagesContent .PageIsSettingsPagesSocial .PageIsSettingsPagesPricing .PageIsSettingsPagesFooter .PageIsSettingsPagesTheme}}open{{end}}>
<details class="item toggleable-item" {{if or .PageIsSettingsPagesGeneral .PageIsSettingsPagesBrand .PageIsSettingsPagesHero .PageIsSettingsPagesContent .PageIsSettingsPagesComparison .PageIsSettingsPagesSocial .PageIsSettingsPagesPricing .PageIsSettingsPagesFooter .PageIsSettingsPagesTheme .PageIsSettingsPagesLanguages .PageIsSettingsPagesAdvanced}}open{{end}}>
<summary>{{ctx.Locale.Tr "repo.settings.pages"}}</summary>
<div class="menu">
<a class="{{if .PageIsSettingsPagesGeneral}}active {{end}}item" href="{{.RepoLink}}/settings/pages">
@@ -68,6 +68,9 @@
<a class="{{if .PageIsSettingsPagesContent}}active {{end}}item" href="{{.RepoLink}}/settings/pages/content">
{{ctx.Locale.Tr "repo.settings.pages.content"}}
</a>
<a class="{{if .PageIsSettingsPagesComparison}}active {{end}}item" href="{{.RepoLink}}/settings/pages/comparison">
{{ctx.Locale.Tr "repo.settings.pages.comparison"}}
</a>
<a class="{{if .PageIsSettingsPagesSocial}}active {{end}}item" href="{{.RepoLink}}/settings/pages/social">
{{ctx.Locale.Tr "repo.settings.pages.social"}}
</a>
@@ -80,6 +83,12 @@
<a class="{{if .PageIsSettingsPagesTheme}}active {{end}}item" href="{{.RepoLink}}/settings/pages/theme">
{{ctx.Locale.Tr "repo.settings.pages.theme"}}
</a>
<a class="{{if .PageIsSettingsPagesLanguages}}active {{end}}item" href="{{.RepoLink}}/settings/pages/languages">
{{ctx.Locale.Tr "repo.settings.pages.languages"}}
</a>
<a class="{{if .PageIsSettingsPagesAdvanced}}active {{end}}item" href="{{.RepoLink}}/settings/pages/advanced">
{{ctx.Locale.Tr "repo.settings.pages.advanced"}}
</a>
</div>
</details>
<details class="item toggleable-item" {{if or .PageIsSharedSettingsRunners .PageIsSharedSettingsSecrets .PageIsSharedSettingsVariables .PageIsActionsSettingsGeneral}}open{{end}}>

View File

@@ -29,15 +29,19 @@
{{if .AIEnabled}}
<div class="divider"></div>
<form class="ui form" method="post">
<form class="ui form" method="post" id="ai-generate-form">
{{.CsrfTokenHtml}}
<input type="hidden" name="action" value="ai_generate">
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.ai_generate"}}</label>
<p class="help">{{ctx.Locale.Tr "repo.settings.pages.ai_generate_desc"}}</p>
</div>
<div class="field">
<button class="ui purple button">{{ctx.Locale.Tr "repo.settings.pages.ai_generate_button"}}</button>
<div class="field" id="ai-generate-btn-field">
<button class="ui purple button" type="submit">{{svg "octicon-copilot" 16}} {{ctx.Locale.Tr "repo.settings.pages.ai_generate_button"}}</button>
</div>
<div id="ai-generate-loading" class="tw-hidden tw-py-4">
<div class="ui active centered inline loader"></div>
<p class="tw-text-center tw-mt-2" style="color:var(--color-text-light);">{{ctx.Locale.Tr "repo.settings.pages.ai_generating"}}</p>
</div>
</form>
{{end}}
@@ -169,4 +173,17 @@
</div>
{{end}}
</div>
<script>
(function() {
var form = document.getElementById('ai-generate-form');
if (!form) return;
form.addEventListener('submit', function() {
var btn = form.querySelector('button[type="submit"]');
btn.classList.add('loading', 'disabled');
btn.disabled = true;
document.getElementById('ai-generate-btn-field').classList.add('tw-hidden');
document.getElementById('ai-generate-loading').classList.remove('tw-hidden');
});
})();
</script>
{{template "repo/settings/layout_footer" .}}

View File

@@ -0,0 +1,82 @@
{{template "repo/settings/layout_head" (dict "ctxData" . "pageClass" "repository settings pages")}}
<div class="user-main-content twelve wide column">
<h4 class="ui top attached header">{{ctx.Locale.Tr "repo.settings.pages.advanced"}}</h4>
<div class="ui attached segment">
<form class="ui form" method="post">
{{.CsrfTokenHtml}}
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.static_routes"}}</h5>
<p class="help">{{ctx.Locale.Tr "repo.settings.pages.static_routes_desc"}}</p>
<div id="routes-container">
{{range $i, $route := .Config.Advanced.StaticRoutes}}
<div class="field route-item tw-flex tw-items-center tw-gap-2">
<input name="static_route_{{$i}}" value="{{$route}}" placeholder="/schema/*" class="tw-flex-1">
<button type="button" class="ui red mini icon button" onclick="this.closest('.route-item').remove()">{{svg "octicon-trash" 14}}</button>
</div>
{{end}}
{{if not .Config.Advanced.StaticRoutes}}
<div class="field route-item tw-flex tw-items-center tw-gap-2">
<input name="static_route_0" placeholder="/schema/*" class="tw-flex-1">
<button type="button" class="ui red mini icon button" onclick="this.closest('.route-item').remove()">{{svg "octicon-trash" 14}}</button>
</div>
{{end}}
</div>
<button type="button" class="ui mini button tw-mb-4" onclick="addRoute()">+ {{ctx.Locale.Tr "repo.settings.pages.add_route"}}</button>
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.redirects"}}</h5>
<p class="help">{{ctx.Locale.Tr "repo.settings.pages.redirects_desc"}}</p>
<div id="redirects-container">
{{range $path, $target := .Config.Advanced.Redirects}}
<div class="two fields redirect-item">
<div class="field"><input name="redirect_from_{{$path}}" value="{{$path}}" placeholder="/old-path"></div>
<div class="field tw-flex tw-items-center tw-gap-2">
<input name="redirect_to_{{$path}}" value="{{$target}}" placeholder="https://new-url" class="tw-flex-1">
<button type="button" class="ui red mini icon button" onclick="this.closest('.redirect-item').remove()">{{svg "octicon-trash" 14}}</button>
</div>
</div>
{{end}}
{{if not .Config.Advanced.Redirects}}
<div class="two fields redirect-item">
<div class="field"><input name="redirect_from_0" placeholder="/old-path"></div>
<div class="field tw-flex tw-items-center tw-gap-2">
<input name="redirect_to_0" placeholder="https://new-url" class="tw-flex-1">
<button type="button" class="ui red mini icon button" onclick="this.closest('.redirect-item').remove()">{{svg "octicon-trash" 14}}</button>
</div>
</div>
{{end}}
</div>
<button type="button" class="ui mini button tw-mb-4" onclick="addRedirect()">+ {{ctx.Locale.Tr "repo.settings.pages.add_redirect"}}</button>
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.custom_code"}}</h5>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.custom_css"}}</label>
<textarea name="custom_css" rows="4" placeholder="body { }">{{.Config.Advanced.CustomCSS}}</textarea>
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.custom_head"}}</label>
<textarea name="custom_head" rows="4" placeholder="<meta ...>">{{.Config.Advanced.CustomHead}}</textarea>
</div>
<div class="field">
<button class="ui primary button">{{ctx.Locale.Tr "save"}}</button>
</div>
</form>
</div>
</div>
<script>
let routeCount = {{len .Config.Advanced.StaticRoutes}};
let redirectCount = {{len .Config.Advanced.Redirects}};
if (routeCount === 0) routeCount = 1;
if (redirectCount === 0) redirectCount = 1;
function addRoute() {
const c = document.getElementById('routes-container');
c.insertAdjacentHTML('beforeend', `<div class="field route-item tw-flex tw-items-center tw-gap-2"><input name="static_route_${routeCount}" placeholder="/schema/*" class="tw-flex-1"><button type="button" class="ui red mini icon button" onclick="this.closest('.route-item').remove()">{{svg "octicon-trash" 14}}</button></div>`);
routeCount++;
}
function addRedirect() {
const c = document.getElementById('redirects-container');
c.insertAdjacentHTML('beforeend', `<div class="two fields redirect-item"><div class="field"><input name="redirect_from_${redirectCount}" placeholder="/old-path"></div><div class="field tw-flex tw-items-center tw-gap-2"><input name="redirect_to_${redirectCount}" placeholder="https://new-url" class="tw-flex-1"><button type="button" class="ui red mini icon button" onclick="this.closest('.redirect-item').remove()">{{svg "octicon-trash" 14}}</button></div></div>`);
redirectCount++;
}
</script>
{{template "repo/settings/layout_footer" .}}

View File

@@ -26,7 +26,7 @@
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.brand_upload_logo"}}</label>
<div class="tw-flex tw-gap-2 tw-items-center">
<input type="file" name="brand_logo" accept="image/jpeg,image/png,image/webp,image/gif" form="upload-logo-form">
<input type="file" name="brand_logo" accept="image/jpeg,image/png,image/webp,image/gif" form="upload-logo-form" data-ays-ignore="true">
<button class="ui primary button" type="submit" form="upload-logo-form">
{{svg "octicon-upload" 16}} {{ctx.Locale.Tr "repo.settings.pages.brand_upload_btn"}}
</button>
@@ -35,7 +35,6 @@
</div>
<div class="ui horizontal divider">{{ctx.Locale.Tr "repo.settings.pages.brand_or"}}</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.brand_logo_url"}}</label>
<input name="brand_logo_url" value="{{.Config.Brand.LogoURL}}" placeholder="https://example.com/logo.svg">
<p class="help">{{ctx.Locale.Tr "repo.settings.pages.brand_logo_url_help"}}</p>
</div>
@@ -60,7 +59,7 @@
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.brand_upload_favicon"}}</label>
<div class="tw-flex tw-gap-2 tw-items-center">
<input type="file" name="brand_favicon" accept="image/jpeg,image/png,image/webp,image/gif,image/x-icon,image/svg+xml" form="upload-favicon-form">
<input type="file" name="brand_favicon" accept="image/jpeg,image/png,image/webp,image/gif,image/x-icon,image/svg+xml" form="upload-favicon-form" data-ays-ignore="true">
<button class="ui primary button" type="submit" form="upload-favicon-form">
{{svg "octicon-upload" 16}} {{ctx.Locale.Tr "repo.settings.pages.brand_upload_btn"}}
</button>
@@ -69,7 +68,6 @@
</div>
<div class="ui horizontal divider">{{ctx.Locale.Tr "repo.settings.pages.brand_or"}}</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.brand_favicon_url"}}</label>
<input name="brand_favicon_url" value="{{.Config.Brand.FaviconURL}}" placeholder="https://example.com/favicon.ico">
<p class="help">{{ctx.Locale.Tr "repo.settings.pages.brand_favicon_url_help"}}</p>
</div>

View File

@@ -123,7 +123,6 @@
<label>{{ctx.Locale.Tr "repo.settings.pages.comparison_enabled_desc"}}</label>
</div>
</div>
<p class="help">{{ctx.Locale.Tr "repo.settings.pages.comparison_help_link"}}</p>
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.stats"}}</h5>
<div id="stats-container">

View File

@@ -35,9 +35,9 @@
</div>
<div class="ui horizontal divider">{{ctx.Locale.Tr "repo.settings.pages.hero_or"}}</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.image_url"}}</label>
<input name="image_url" value="{{.Config.Hero.ImageURL}}" placeholder="https://example.com/hero.png">
</div>
<br>
{{end}}
<div class="field">

View File

@@ -25,7 +25,7 @@
{{range $code, $name := .LanguageNames}}
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="languages" value="{{$code}}" {{if $.IsLangEnabled $code}}checked{{end}}>
<input type="checkbox" name="languages" value="{{$code}}" {{if index $.EnabledLangs $code}}checked{{end}}>
<label>{{$name}} ({{$code}})</label>
</div>
</div>
@@ -44,31 +44,44 @@
{{ctx.Locale.Tr "repo.settings.pages.translations"}}
</h4>
<div class="ui attached segment">
{{/* Action bar: language selector + buttons */}}
<div class="tw-flex tw-flex-wrap tw-gap-2 tw-items-center tw-mb-4">
<select id="lang-selector" class="ui dropdown" style="min-width:200px;">
{{range .Config.I18n.Languages}}
{{if ne . $.Config.I18n.DefaultLang}}
<option value="{{.}}">{{index $.LanguageNames .}} ({{.}}) {{if index $.TranslationMap .}}{{end}}</option>
{{end}}
{{end}}
</select>
{{if .AIEnabled}}
<form method="post" class="tw-inline-block" id="ai-translate-form">
{{.CsrfTokenHtml}}
<input type="hidden" name="action" value="ai_translate">
<input type="hidden" name="target_lang" id="ai-translate-lang" value="">
<button class="ui purple tiny button" type="submit">{{svg "octicon-copilot" 14}} {{ctx.Locale.Tr "repo.settings.pages.ai_translate"}}</button>
</form>
<form method="post" class="tw-inline-block" id="ai-translate-all-form">
{{.CsrfTokenHtml}}
<input type="hidden" name="action" value="ai_translate_all">
<button class="ui purple tiny button" type="submit">{{svg "octicon-copilot" 14}} {{ctx.Locale.Tr "repo.settings.pages.ai_translate_all"}}</button>
</form>
{{end}}
<form method="post" class="tw-inline-block" id="delete-translation-form" style="display:none;">
{{.CsrfTokenHtml}}
<input type="hidden" name="action" value="delete_translation">
<input type="hidden" name="target_lang" id="delete-translate-lang" value="">
<button class="ui red tiny button" type="submit">{{svg "octicon-trash" 14}} {{ctx.Locale.Tr "repo.settings.pages.delete_translation"}}</button>
</form>
</div>
<div id="ai-translate-loading" class="tw-hidden tw-py-4">
<div class="ui active centered inline loader"></div>
<p class="tw-text-center tw-mt-2" style="color:var(--color-text-light);">{{ctx.Locale.Tr "repo.settings.pages.ai_translating"}}</p>
</div>
{{/* Language panels (one per non-default language, only one visible at a time) */}}
{{range .Config.I18n.Languages}}
{{if ne . $.Config.I18n.DefaultLang}}
<div class="ui segment">
<div class="tw-flex tw-justify-between tw-items-center tw-mb-4">
<h5 class="tw-m-0">{{index $.LanguageNames .}} ({{.}})</h5>
<div>
{{if $.AIEnabled}}
<form method="post" class="tw-inline-block">
{{$.CsrfTokenHtml}}
<input type="hidden" name="action" value="ai_translate">
<input type="hidden" name="target_lang" value="{{.}}">
<button class="ui purple tiny button">{{ctx.Locale.Tr "repo.settings.pages.ai_translate"}}</button>
</form>
{{end}}
{{if index $.TranslationMap .}}
<form method="post" class="tw-inline-block">
{{$.CsrfTokenHtml}}
<input type="hidden" name="action" value="delete_translation">
<input type="hidden" name="target_lang" value="{{.}}">
<button class="ui red tiny button">{{ctx.Locale.Tr "repo.settings.pages.delete_translation"}}</button>
</form>
{{end}}
</div>
</div>
<div class="lang-panel" id="lang-panel-{{.}}" style="display:none;">
<form class="ui form" method="post">
{{$.CsrfTokenHtml}}
<input type="hidden" name="action" value="save_translation">
@@ -76,6 +89,25 @@
{{$trans := index $.TranslationMap .}}
{{/* Brand */}}
{{if or $.Config.Brand.Name $.Config.Brand.Tagline}}
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_brand"}}</h5>
{{if $.Config.Brand.Name}}
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_brand_name"}}</label>
<input name="trans_brand_name" value="{{if $trans}}{{$trans.BrandName}}{{end}}" placeholder="{{$.Config.Brand.Name}}">
</div>
{{end}}
{{if $.Config.Brand.Tagline}}
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_brand_tagline"}}</label>
<input name="trans_brand_tagline" value="{{if $trans}}{{$trans.BrandTagline}}{{end}}" placeholder="{{$.Config.Brand.Tagline}}">
</div>
{{end}}
{{end}}
{{/* Hero */}}
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_hero"}}</h5>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_headline"}}</label>
<input name="trans_headline" value="{{if $trans}}{{$trans.Headline}}{{end}}" placeholder="{{$.Config.Hero.Headline}}">
@@ -84,14 +116,110 @@
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_subheadline"}}</label>
<input name="trans_subheadline" value="{{if $trans}}{{$trans.Subheadline}}{{end}}" placeholder="{{$.Config.Hero.Subheadline}}">
</div>
<div class="two fields">
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_primary_cta"}}</label>
<input name="trans_primary_cta" value="{{if $trans}}{{$trans.PrimaryCTA}}{{end}}" placeholder="{{$.Config.Hero.PrimaryCTA.Label}}">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_secondary_cta"}}</label>
<input name="trans_secondary_cta" value="{{if $trans}}{{$trans.SecondaryCTA}}{{end}}" placeholder="{{$.Config.Hero.SecondaryCTA.Label}}">
</div>
</div>
{{/* Stats */}}
{{if $.Config.Stats}}
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_stats"}}</h5>
{{range $i, $s := $.Config.Stats}}
<div class="two fields">
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_stat_value"}} — <em>{{$s.Value}}</em></label>
<input name="trans_stat_{{$i}}_value" value="{{if $trans}}{{index $trans.StatsValues $i}}{{end}}" placeholder="{{$s.Value}}">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_stat_label"}} — <em>{{$s.Label}}</em></label>
<input name="trans_stat_{{$i}}_label" value="{{if $trans}}{{index $trans.StatsLabels $i}}{{end}}" placeholder="{{$s.Label}}">
</div>
</div>
{{end}}
{{end}}
{{/* Value Props */}}
{{if $.Config.ValueProps}}
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_value_props"}}</h5>
{{range $i, $vp := $.Config.ValueProps}}
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_primary_cta"}}</label>
<input name="trans_primary_cta" value="{{if $trans}}{{$trans.PrimaryCTA}}{{end}}" placeholder="{{$.Config.Hero.PrimaryCTA.Label}}">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_title"}} — <em>{{$vp.Title}}</em></label>
<input name="trans_vp_{{$i}}_title" value="{{if $trans}}{{index $trans.ValuePropTitles $i}}{{end}}" placeholder="{{$vp.Title}}">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_secondary_cta"}}</label>
<input name="trans_secondary_cta" value="{{if $trans}}{{$trans.SecondaryCTA}}{{end}}" placeholder="{{$.Config.Hero.SecondaryCTA.Label}}">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_description"}}</label>
<input name="trans_vp_{{$i}}_desc" value="{{if $trans}}{{index $trans.ValuePropDescs $i}}{{end}}" placeholder="{{$vp.Description}}">
</div>
{{end}}
{{end}}
{{/* Features */}}
{{if $.Config.Features}}
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_features"}}</h5>
{{range $i, $f := $.Config.Features}}
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_title"}} — <em>{{$f.Title}}</em></label>
<input name="trans_feat_{{$i}}_title" value="{{if $trans}}{{index $trans.FeatureTitles $i}}{{end}}" placeholder="{{$f.Title}}">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_description"}}</label>
<input name="trans_feat_{{$i}}_desc" value="{{if $trans}}{{index $trans.FeatureDescs $i}}{{end}}" placeholder="{{$f.Description}}">
</div>
{{end}}
{{end}}
{{/* Testimonials */}}
{{if $.Config.SocialProof.Testimonials}}
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_testimonials"}}</h5>
{{range $i, $t := $.Config.SocialProof.Testimonials}}
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_quote"}} — <em>{{$t.Author}}</em></label>
<textarea name="trans_test_{{$i}}_quote" rows="2" placeholder="{{$t.Quote}}">{{if $trans}}{{index $trans.TestimonialQuotes $i}}{{end}}</textarea>
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_role"}}</label>
<input name="trans_test_{{$i}}_role" value="{{if $trans}}{{index $trans.TestimonialRoles $i}}{{end}}" placeholder="{{$t.Role}}">
</div>
{{end}}
{{end}}
{{/* Pricing */}}
{{if or $.Config.Pricing.Headline $.Config.Pricing.Plans}}
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_pricing"}}</h5>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_pricing_headline"}}</label>
<input name="trans_pricing_headline" value="{{if $trans}}{{$trans.PricingHeadline}}{{end}}" placeholder="{{$.Config.Pricing.Headline}}">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_pricing_subheadline"}}</label>
<input name="trans_pricing_subheadline" value="{{if $trans}}{{$trans.PricingSubheadline}}{{end}}" placeholder="{{$.Config.Pricing.Subheadline}}">
</div>
{{range $i, $p := $.Config.Pricing.Plans}}
<div class="three fields">
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_plan_name"}} — <em>{{$p.Name}}</em></label>
<input name="trans_plan_{{$i}}_name" value="{{if $trans}}{{index $trans.PlanNames $i}}{{end}}" placeholder="{{$p.Name}}">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_plan_period"}}</label>
<input name="trans_plan_{{$i}}_period" value="{{if $trans}}{{index $trans.PlanPeriods $i}}{{end}}" placeholder="{{$p.Period}}">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_plan_cta"}}</label>
<input name="trans_plan_{{$i}}_cta" value="{{if $trans}}{{index $trans.PlanCTAs $i}}{{end}}" placeholder="{{$p.CTA}}">
</div>
</div>
{{end}}
{{end}}
{{/* CTA Section */}}
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_cta"}}</h5>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_cta_headline"}}</label>
<input name="trans_cta_headline" value="{{if $trans}}{{$trans.CTAHeadline}}{{end}}" placeholder="{{$.Config.CTASection.Headline}}">
@@ -105,6 +233,128 @@
<input name="trans_cta_button" value="{{if $trans}}{{$trans.CTAButton}}{{end}}" placeholder="{{$.Config.CTASection.Button.Label}}">
</div>
{{/* Blog */}}
{{if $.Config.Blog.Enabled}}
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_blog"}}</h5>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_blog_headline"}}</label>
<input name="trans_blog_headline" value="{{if $trans}}{{$trans.BlogHeadline}}{{end}}" placeholder="{{if $.Config.Blog.Headline}}{{$.Config.Blog.Headline}}{{else}}Latest Posts{{end}}">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_blog_subheadline"}}</label>
<input name="trans_blog_subheadline" value="{{if $trans}}{{$trans.BlogSubheadline}}{{end}}" placeholder="{{$.Config.Blog.Subheadline}}">
</div>
{{if $.Config.Blog.CTAButton.Label}}
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_blog_cta"}}</label>
<input name="trans_blog_cta" value="{{if $trans}}{{$trans.BlogCTAButton}}{{end}}" placeholder="{{$.Config.Blog.CTAButton.Label}}">
</div>
{{end}}
{{end}}
{{/* Gallery */}}
{{if $.Config.Gallery.Enabled}}
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_gallery"}}</h5>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_gallery_headline"}}</label>
<input name="trans_gallery_headline" value="{{if $trans}}{{$trans.GalleryHeadline}}{{end}}" placeholder="{{if $.Config.Gallery.Headline}}{{$.Config.Gallery.Headline}}{{else}}Gallery{{end}}">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_gallery_subheadline"}}</label>
<input name="trans_gallery_subheadline" value="{{if $trans}}{{$trans.GallerySubheadline}}{{end}}" placeholder="{{$.Config.Gallery.Subheadline}}">
</div>
{{end}}
{{/* Comparison */}}
{{if $.Config.Comparison.Enabled}}
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_comparison"}}</h5>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_comparison_headline"}}</label>
<input name="trans_comparison_headline" value="{{if $trans}}{{$trans.ComparisonHeadline}}{{end}}" placeholder="{{if $.Config.Comparison.Headline}}{{$.Config.Comparison.Headline}}{{else}}How We Compare{{end}}">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_comparison_subheadline"}}</label>
<input name="trans_comparison_subheadline" value="{{if $trans}}{{$trans.ComparisonSubheadline}}{{end}}" placeholder="{{$.Config.Comparison.Subheadline}}">
</div>
{{end}}
{{/* Footer */}}
{{if or $.Config.Footer.Copyright $.Config.Footer.Links}}
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_footer"}}</h5>
{{if $.Config.Footer.Copyright}}
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_footer_copyright"}}</label>
<input name="trans_footer_copyright" value="{{if $trans}}{{$trans.FooterCopyright}}{{end}}" placeholder="{{$.Config.Footer.Copyright}}">
</div>
{{end}}
{{range $i, $link := $.Config.Footer.Links}}
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_footer_link"}} — <em>{{$link.Label}}</em></label>
<input name="trans_footer_link_{{$i}}" value="{{if $trans}}{{index $trans.FooterLinkLabels $i}}{{end}}" placeholder="{{$link.Label}}">
</div>
{{end}}
{{end}}
{{/* SEO */}}
{{if or $.Config.SEO.Title $.Config.SEO.Description}}
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_seo"}}</h5>
{{if $.Config.SEO.Title}}
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_seo_title"}}</label>
<input name="trans_seo_title" value="{{if $trans}}{{$trans.SEOTitle}}{{end}}" placeholder="{{$.Config.SEO.Title}}">
</div>
{{end}}
{{if $.Config.SEO.Description}}
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_seo_description"}}</label>
<textarea name="trans_seo_description" rows="2" placeholder="{{$.Config.SEO.Description}}">{{if $trans}}{{$trans.SEODescription}}{{end}}</textarea>
</div>
{{end}}
{{end}}
{{/* Navigation Labels */}}
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.trans_section_navigation"}}</h5>
<div class="two fields">
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_nav_label"}} — <em>{{$.Config.Navigation.LabelValueProps}}</em></label>
<input name="trans_nav_label_value_props" value="{{if $trans}}{{$trans.NavLabelValueProps}}{{end}}" placeholder="{{$.Config.Navigation.LabelValueProps}}">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_nav_label"}} — <em>{{$.Config.Navigation.LabelFeatures}}</em></label>
<input name="trans_nav_label_features" value="{{if $trans}}{{$trans.NavLabelFeatures}}{{end}}" placeholder="{{$.Config.Navigation.LabelFeatures}}">
</div>
</div>
<div class="two fields">
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_nav_label"}} — <em>{{$.Config.Navigation.LabelBlog}}</em></label>
<input name="trans_nav_label_blog" value="{{if $trans}}{{$trans.NavLabelBlog}}{{end}}" placeholder="{{$.Config.Navigation.LabelBlog}}">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_nav_label"}} — <em>{{$.Config.Navigation.LabelGallery}}</em></label>
<input name="trans_nav_label_gallery" value="{{if $trans}}{{$trans.NavLabelGallery}}{{end}}" placeholder="{{$.Config.Navigation.LabelGallery}}">
</div>
</div>
<div class="two fields">
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_nav_label"}} — <em>{{$.Config.Navigation.LabelPricing}}</em></label>
<input name="trans_nav_label_pricing" value="{{if $trans}}{{$trans.NavLabelPricing}}{{end}}" placeholder="{{$.Config.Navigation.LabelPricing}}">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_nav_label"}} — <em>{{$.Config.Navigation.LabelCompare}}</em></label>
<input name="trans_nav_label_compare" value="{{if $trans}}{{$trans.NavLabelCompare}}{{end}}" placeholder="{{$.Config.Navigation.LabelCompare}}">
</div>
</div>
<div class="two fields">
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_nav_label"}} — <em>{{if $.Config.Navigation.LabelDocs}}{{$.Config.Navigation.LabelDocs}}{{else}}Docs{{end}}</em></label>
<input name="trans_nav_label_docs" value="{{if $trans}}{{$trans.NavLabelDocs}}{{end}}" placeholder="{{if $.Config.Navigation.LabelDocs}}{{$.Config.Navigation.LabelDocs}}{{else}}Docs{{end}}">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.trans_nav_label"}} — <em>{{if $.Config.Navigation.LabelReleases}}{{$.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}</em></label>
<input name="trans_nav_label_releases" value="{{if $trans}}{{$trans.NavLabelReleases}}{{end}}" placeholder="{{if $.Config.Navigation.LabelReleases}}{{$.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}">
</div>
</div>
<div class="field">
<button class="ui primary tiny button">{{ctx.Locale.Tr "repo.settings.pages.save_translation"}}</button>
</div>
@@ -115,4 +365,54 @@
</div>
{{end}}
</div>
<script>
(function() {
var selector = document.getElementById('lang-selector');
if (!selector) return;
var aiForm = document.getElementById('ai-translate-form');
var aiLangInput = document.getElementById('ai-translate-lang');
var deleteForm = document.getElementById('delete-translation-form');
var deleteLangInput = document.getElementById('delete-translate-lang');
function showPanel() {
var lang = selector.value;
document.querySelectorAll('.lang-panel').forEach(function(p) { p.style.display = 'none'; });
var panel = document.getElementById('lang-panel-' + lang);
if (panel) panel.style.display = '';
// Update AI translate target
if (aiLangInput) aiLangInput.value = lang;
// Update delete button target + visibility
if (deleteLangInput) deleteLangInput.value = lang;
if (deleteForm) {
// Show delete button only if this language has a translation
var hasTranslation = selector.options[selector.selectedIndex].textContent.indexOf('\u2713') !== -1;
deleteForm.style.display = hasTranslation ? '' : 'none';
}
}
selector.addEventListener('change', showPanel);
// Show first language on load
showPanel();
// Loading spinners for AI translate buttons
function showTranslateLoading(form) {
if (!form) return;
form.addEventListener('submit', function() {
var btn = form.querySelector('button[type="submit"]');
btn.classList.add('loading', 'disabled');
btn.disabled = true;
// Hide all lang panels and action bar buttons, show loader
document.querySelectorAll('.lang-panel').forEach(function(p) { p.style.display = 'none'; });
var loading = document.getElementById('ai-translate-loading');
if (loading) loading.classList.remove('tw-hidden');
});
}
showTranslateLoading(document.getElementById('ai-translate-form'));
showTranslateLoading(document.getElementById('ai-translate-all-form'));
})();
</script>
{{template "repo/settings/layout_footer" .}}

View File

@@ -1,34 +0,0 @@
{{if .PagesEnabled}}
<div class="ui secondary pointing menu tw-mb-4">
<a class="{{if .PageIsSettingsPagesGeneral}}active {{end}}item" href="{{.RepoLink}}/settings/pages">
{{ctx.Locale.Tr "repo.settings.pages.general"}}
</a>
<a class="{{if .PageIsSettingsPagesBrand}}active {{end}}item" href="{{.RepoLink}}/settings/pages/brand">
{{ctx.Locale.Tr "repo.settings.pages.brand"}}
</a>
<a class="{{if .PageIsSettingsPagesHero}}active {{end}}item" href="{{.RepoLink}}/settings/pages/hero">
{{ctx.Locale.Tr "repo.settings.pages.hero"}}
</a>
<a class="{{if .PageIsSettingsPagesContent}}active {{end}}item" href="{{.RepoLink}}/settings/pages/content">
{{ctx.Locale.Tr "repo.settings.pages.content"}}
</a>
<a class="{{if .PageIsSettingsPagesComparison}}active {{end}}item" href="{{.RepoLink}}/settings/pages/comparison">
{{ctx.Locale.Tr "repo.settings.pages.comparison"}}
</a>
<a class="{{if .PageIsSettingsPagesSocial}}active {{end}}item" href="{{.RepoLink}}/settings/pages/social">
{{ctx.Locale.Tr "repo.settings.pages.social"}}
</a>
<a class="{{if .PageIsSettingsPagesPricing}}active {{end}}item" href="{{.RepoLink}}/settings/pages/pricing">
{{ctx.Locale.Tr "repo.settings.pages.pricing"}}
</a>
<a class="{{if .PageIsSettingsPagesFooter}}active {{end}}item" href="{{.RepoLink}}/settings/pages/footer">
{{ctx.Locale.Tr "repo.settings.pages.footer"}}
</a>
<a class="{{if .PageIsSettingsPagesTheme}}active {{end}}item" href="{{.RepoLink}}/settings/pages/theme">
{{ctx.Locale.Tr "repo.settings.pages.theme"}}
</a>
<a class="{{if .PageIsSettingsPagesLanguages}}active {{end}}item" href="{{.RepoLink}}/settings/pages/languages">
{{ctx.Locale.Tr "repo.settings.pages.languages"}}
</a>
</div>
{{end}}

View File

@@ -97,7 +97,7 @@
<input name="og_image" value="{{.Config.SEO.OGImage}}" placeholder="https://example.com/og-image.png">
</div>
<div id="og-image-preview" {{if not .Config.SEO.UseMediaKitOG}}style="display:none"{{end}}>
<img src="{{.Repository.Link}}/social-preview" style="max-width:400px;border-radius:8px;border:1px solid #ddd;margin-top:8px;">
<img id="og-preview-img" src="{{.Repository.Link}}/social-preview?v={{.Repository.UpdatedUnix}}" style="max-width:400px;border-radius:8px;border:1px solid #ddd;margin-top:8px;">
</div>
</div>
@@ -170,6 +170,10 @@
document.getElementById('use-media-kit-og').addEventListener('change', function() {
document.getElementById('og-image-manual').style.display = this.checked ? 'none' : '';
document.getElementById('og-image-preview').style.display = this.checked ? '' : 'none';
if (this.checked) {
const img = document.getElementById('og-preview-img');
img.src = img.src.replace(/[?&]v=\d+/, '') + '?v=' + Date.now();
}
});
function updateColorPreview(type) {