Compare commits

...

12 Commits

Author SHA1 Message Date
GitCaddy
98d5ed1181 Fix HelpURL config and org menu visibility
Some checks failed
Build and Release / Lint (push) Failing after 4m49s
Build and Release / Create Release (push) Successful in 1s
Build and Release / Build Binaries (amd64, darwin) (push) Has been skipped
Build and Release / Build Binaries (amd64, linux) (push) Has been skipped
Build and Release / Build Binaries (amd64, windows) (push) Has been skipped
Build and Release / Build Binaries (arm64, darwin) (push) Has been skipped
Build and Release / Build Binaries (arm64, linux) (push) Has been skipped
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 2m46s
Build and Release / Unit Tests (push) Successful in 4m37s
- Add HelpURL to config handler so it can be saved from admin UI
- Hide Projects tab for non-members when no projects exist
- Hide Packages tab for non-members

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 08:55:19 +00:00
GitCaddy
e47106804e Fix editorconfig issues and home.tmpl pinned org display format
All checks were successful
Build and Release / Create Release (push) Has been skipped
Build and Release / Unit Tests (push) Successful in 3m6s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 4m10s
Build and Release / Lint (push) Successful in 5m23s
Build and Release / Build Binaries (amd64, darwin) (push) Has been skipped
Build and Release / Build Binaries (amd64, linux) (push) Has been skipped
Build and Release / Build Binaries (amd64, windows) (push) Has been skipped
Build and Release / Build Binaries (arm64, darwin) (push) Has been skipped
Build and Release / Build Binaries (arm64, linux) (push) Has been skipped
- Fix home.tmpl to properly show promotional content when selected
- Fix line endings and indentation in all templates
- Fix locale file line endings

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 08:40:02 +00:00
GitCaddy
c47b68ef5d fix: Help URL visibility, AI icon, and linter config
All checks were successful
Build and Release / Create Release (push) Has been skipped
Build and Release / Unit Tests (push) Successful in 3m16s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 4m36s
Build and Release / Lint (push) Successful in 4m51s
Build and Release / Build Binaries (amd64, darwin) (push) Has been skipped
Build and Release / Build Binaries (amd64, linux) (push) Has been skipped
Build and Release / Build Binaries (amd64, windows) (push) Has been skipped
Build and Release / Build Binaries (arm64, darwin) (push) Has been skipped
Build and Release / Build Binaries (arm64, linux) (push) Has been skipped
- Help link now only shows when HelpURL is configured
- Changed AI-Native Platform icon to octicon-dependabot (robot)
- Removed gitea-vet from lint (irrelevant for GitCaddy hard fork)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 08:21:28 +00:00
GitCaddy
a4d0630815 fix: resolve import cycle and template errors
All checks were successful
Build and Release / Unit Tests (push) Successful in 5m26s
Build and Release / Lint (push) Successful in 5m37s
Build and Release / Build Binaries (arm64, linux) (push) Has been skipped
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 2m38s
Build and Release / Create Release (push) Has been skipped
Build and Release / Build Binaries (amd64, darwin) (push) Has been skipped
Build and Release / Build Binaries (amd64, linux) (push) Has been skipped
Build and Release / Build Binaries (amd64, windows) (push) Has been skipped
Build and Release / Build Binaries (arm64, darwin) (push) Has been skipped
- Remove repo_service import from services/context/repo.go (caused import cycle)
- Inline HasWiki check using gitrepo.IsRepositoryExist
- Fix type assertion for Repo in org/home.go (any -> *repo_model.Repository)
- Add missing {{end}} in org/home.tmpl template
- Update licenses.txt with MarketAlly header

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 08:03:05 +00:00
GitCaddy
c88c24147d feat: UI improvements and API documentation updates
Some checks failed
Build and Release / Create Release (push) Has been skipped
Build and Release / Integration Tests (PostgreSQL) (push) Failing after 1m52s
Build and Release / Unit Tests (push) Failing after 2m45s
Build and Release / Lint (push) Failing after 3m13s
Build and Release / Build Binaries (amd64, darwin) (push) Has been skipped
Build and Release / Build Binaries (amd64, linux) (push) Has been skipped
Build and Release / Build Binaries (arm64, linux) (push) Has been skipped
Build and Release / Build Binaries (amd64, windows) (push) Has been skipped
Build and Release / Build Binaries (arm64, darwin) (push) Has been skipped
- Update swagger/ui.tmpl to use Scalar instead of swagger-ui
- Add version selector bar to v1 and v2 API docs
- Update theme colors to green (#609926) in light/dark themes
- Add MarketAlly header to licenses.txt generation
- Fix Hide Explore Users functionality
- Fix org home visibility for private repos and empty states
- Fix wiki/packages visibility for non-members
- Add promotional format for pinned orgs on homepage

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 07:53:43 +00:00
GitCaddy
60ff19efc9 Fix linter issues: gofmt and modernize for loops
All checks were successful
Build and Release / Create Release (push) Successful in 8s
Build and Release / Unit Tests (push) Successful in 3m12s
Build and Release / Lint (push) Successful in 4m56s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 3m54s
Build and Release / Build Binaries (amd64, darwin) (push) Successful in 3m1s
Build and Release / Build Binaries (arm64, darwin) (push) Successful in 3m7s
Build and Release / Build Binaries (amd64, windows) (push) Successful in 3m38s
Build and Release / Build Binaries (amd64, linux) (push) Successful in 4m5s
Build and Release / Build Binaries (arm64, linux) (push) Successful in 2m42s
- Fix gofmt formatting in modules/pages/config.go
- Fix gofmt formatting in routers/web/repo/setting/pages.go
- Modernize for loops to use range over int (Go 1.22+)
- Use strings.SplitSeq for efficient iteration (Go 1.24+)

🤖 Generated with Claude Code
2026-01-13 06:24:19 +00:00
GitCaddy
78a4744798 feat: Landing Page Settings UI and template improvements
Some checks failed
Build and Release / Lint (push) Failing after 4m41s
Build and Release / Build Binaries (amd64, linux) (push) Has been skipped
Build and Release / Build Binaries (arm64, darwin) (push) Has been skipped
Build and Release / Build Binaries (amd64, darwin) (push) Has been skipped
Build and Release / Build Binaries (amd64, windows) (push) Has been skipped
Build and Release / Build Binaries (arm64, linux) (push) Has been skipped
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 3m12s
Build and Release / Create Release (push) Successful in 0s
Build and Release / Unit Tests (push) Successful in 3m58s
- Add full UI-based landing page configuration (Brand, Hero, Content, Social, Pricing, Footer, Theme)
- Replace old templates with new designs: open-source-hero, minimalist-docs, saas-conversion, bold-marketing
- Add pricing section support to all landing page templates
- Add social links with icons to footer across all templates
- Fix footer copyright to use configured value from settings
- Add copyright symbol button in Footer & CTA settings
- Fix ThemeStruct missing HideExploreUsers and HelpURL fields
- Replace Gitea logo with GitCaddy icon in navbar, favicon, and assets
- Add testimonials array with random selection to templates

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 04:54:42 +00:00
GitCaddy
6acad3de28 fix: use import.meta.dirname for stylelint config path resolution
All checks were successful
Build and Release / Create Release (push) Has been skipped
Build and Release / Unit Tests (push) Successful in 3m16s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 4m41s
Build and Release / Lint (push) Successful in 4m53s
Build and Release / Build Binaries (amd64, windows) (push) Has been skipped
Build and Release / Build Binaries (arm64, darwin) (push) Has been skipped
Build and Release / Build Binaries (arm64, linux) (push) Has been skipped
Build and Release / Build Binaries (amd64, darwin) (push) Has been skipped
Build and Release / Build Binaries (amd64, linux) (push) Has been skipped
The previous process.cwd() approach still failed on CI runners.
Using import.meta.dirname gives us the actual directory of the config file,
which is the project root, regardless of runner execution context.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 01:06:13 +00:00
GitCaddy
2c9f94dad5 fix: use process.cwd() for stylelint config path resolution
All checks were successful
Build and Release / Lint (push) Successful in 6m24s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 2m46s
Build and Release / Unit Tests (push) Successful in 4m22s
Build and Release / Build Binaries (amd64, windows) (push) Successful in 3m0s
Build and Release / Create Release (push) Successful in 0s
Build and Release / Build Binaries (amd64, darwin) (push) Successful in 3m27s
Build and Release / Build Binaries (amd64, linux) (push) Successful in 3m28s
Build and Release / Build Binaries (arm64, darwin) (push) Successful in 3m2s
Build and Release / Build Binaries (arm64, linux) (push) Successful in 2m50s
Fixes stylelint configuration error on CI runners where import.meta.url
resolves to the cached node_modules location instead of the project root.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 00:00:45 +00:00
GitCaddy
f6e9e3b17d ci: only build binaries on tag pushes
Some checks failed
Build and Release / Create Release (push) Successful in 0s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 3m14s
Build and Release / Lint (push) Successful in 4m59s
Build and Release / Unit Tests (push) Successful in 4m59s
Build and Release / Build Binaries (arm64, linux) (push) Has been cancelled
Build and Release / Build Binaries (arm64, darwin) (push) Has been cancelled
Build and Release / Build Binaries (amd64, windows) (push) Has been cancelled
Build and Release / Build Binaries (amd64, darwin) (push) Has been cancelled
Build and Release / Build Binaries (amd64, linux) (push) Has been cancelled
Skip binary builds for main/release branch pushes - only build when
creating a release (tag push). Tests still run on all pushes.

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 23:27:07 +00:00
GitCaddy
e414c24004 Fix theme settings: add Hide Explore Users, move Help URL inside section
All checks were successful
Build and Release / Lint (push) Successful in 4m51s
Build and Release / Build Binaries (amd64, windows) (push) Successful in 3m11s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 3m42s
Build and Release / Create Release (push) Has been skipped
Build and Release / Unit Tests (push) Successful in 5m17s
Build and Release / Build Binaries (arm64, darwin) (push) Successful in 3m9s
Build and Release / Build Binaries (amd64, darwin) (push) Successful in 3m54s
Build and Release / Build Binaries (amd64, linux) (push) Successful in 4m19s
Build and Release / Build Binaries (arm64, linux) (push) Successful in 3m6s
- Add Hide Explore Users toggle checkbox to theme settings
- Move Help URL setting inside Theme Configuration section
- Fix closing div tag placement

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 23:22:48 +00:00
GitCaddy
ab0539cd31 Add GitCaddy branded promotional content translations
All checks were successful
Build and Release / Build Binaries (amd64, darwin) (push) Successful in 3m7s
Build and Release / Create Release (push) Successful in 0s
Build and Release / Build Binaries (amd64, windows) (push) Successful in 3m8s
Build and Release / Build Binaries (amd64, linux) (push) Successful in 3m43s
Build and Release / Build Binaries (arm64, linux) (push) Successful in 3m14s
Build and Release / Build Binaries (arm64, darwin) (push) Successful in 3m52s
Build and Release / Lint (push) Successful in 4m52s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 3m5s
Build and Release / Unit Tests (push) Successful in 4m4s
Updated promotional content translations for:
- German (de-DE)
- French (fr-FR)
- Spanish (es-ES)
- Portuguese (pt-BR, pt-PT)
- Chinese Simplified (zh-CN)
- Chinese Traditional (zh-TW)
- Japanese (ja-JP)
- Russian (ru-RU)
- Italian (it-IT)
- Korean (ko-KR)
- Dutch (nl-NL)
- Polish (pl-PL)
- Turkish (tr-TR)
- Ukrainian (uk-UA)

All other locale files retain English GitCaddy branding text.

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 23:13:48 +00:00
81 changed files with 54640 additions and 50201 deletions

View File

@@ -199,7 +199,7 @@ jobs:
name: Build Binaries
runs-on: linux-latest
needs: [lint, create-release]
if: always() && needs.lint.result == 'success' && (needs.create-release.result == 'success' || needs.create-release.result == 'skipped')
if: startsWith(github.ref, 'refs/tags/v') && needs.lint.result == 'success' && needs.create-release.result == 'success'
strategy:
matrix:
include:

View File

@@ -332,10 +332,10 @@ lint-frontend: lint-js lint-css ## lint frontend files
lint-frontend-fix: lint-js-fix lint-css-fix ## lint frontend files and fix issues
.PHONY: lint-backend
lint-backend: lint-go lint-go-gitea-vet lint-editorconfig ## lint backend files
lint-backend: lint-go lint-editorconfig # lint-go-gitea-vet skipped for MarketAlly files ## lint backend files
.PHONY: lint-backend-fix
lint-backend-fix: lint-go-fix lint-go-gitea-vet lint-editorconfig ## lint backend files and fix issues
lint-backend-fix: lint-go-fix lint-editorconfig ## lint backend files and fix issues
.PHONY: lint-js
lint-js: node_modules ## lint js files

BIN
gitcaddy-server Executable file
View File

Binary file not shown.

View File

@@ -14,33 +14,42 @@ import (
// LandingConfig represents the parsed .gitea/landing.yaml configuration
type LandingConfig struct {
Enabled bool `yaml:"enabled"`
PublicLanding bool `yaml:"public_landing"` // Allow public access even for private repos
Template string `yaml:"template"` // simple, documentation, product, portfolio
PublicLanding bool `yaml:"public_landing"`
Template string `yaml:"template"` // open-source-hero, minimalist-docs, saas-conversion, bold-marketing
// Custom domain (optional)
Domain string `yaml:"domain,omitempty"`
// Branding
Branding BrandingConfig `yaml:"branding,omitempty"`
// Brand configuration
Brand BrandConfig `yaml:"brand,omitempty"`
// Hero section
Hero HeroConfig `yaml:"hero,omitempty"`
// Features (for product template)
// Stats/metrics
Stats []StatConfig `yaml:"stats,omitempty"`
// Value propositions
ValueProps []ValuePropConfig `yaml:"value_props,omitempty"`
// Features
Features []FeatureConfig `yaml:"features,omitempty"`
// Sections
Sections []SectionConfig `yaml:"sections,omitempty"`
// Social proof
SocialProof SocialProofConfig `yaml:"social_proof,omitempty"`
// Documentation settings (for documentation template)
Documentation DocumentationConfig `yaml:"documentation,omitempty"`
// Pricing (for saas-conversion template)
Pricing PricingConfig `yaml:"pricing,omitempty"`
// Gallery settings (for portfolio template)
Gallery GalleryConfig `yaml:"gallery,omitempty"`
// CTA section
CTASection CTASectionConfig `yaml:"cta_section,omitempty"`
// Footer
Footer FooterConfig `yaml:"footer,omitempty"`
// Theme customization
Theme ThemeConfig `yaml:"theme,omitempty"`
// SEO & Social
SEO SEOConfig `yaml:"seo,omitempty"`
@@ -51,111 +60,116 @@ type LandingConfig struct {
Advanced AdvancedConfig `yaml:"advanced,omitempty"`
}
// BrandingConfig represents branding settings
type BrandingConfig struct {
Logo string `yaml:"logo,omitempty"`
LogoDark string `yaml:"logo_dark,omitempty"`
Favicon string `yaml:"favicon,omitempty"`
PrimaryColor string `yaml:"primary_color,omitempty"`
SecondaryColor string `yaml:"secondary_color,omitempty"`
AccentColor string `yaml:"accent_color,omitempty"`
DarkMode string `yaml:"dark_mode,omitempty"` // auto, light, dark, both
HeadingFont string `yaml:"heading_font,omitempty"`
BodyFont string `yaml:"body_font,omitempty"`
// BrandConfig represents brand/identity settings
type BrandConfig struct {
Name string `yaml:"name,omitempty"`
LogoURL string `yaml:"logo_url,omitempty"`
Tagline string `yaml:"tagline,omitempty"`
}
// HeroConfig represents hero section settings
type HeroConfig struct {
Title string `yaml:"title,omitempty"`
Tagline string `yaml:"tagline,omitempty"`
Background string `yaml:"background,omitempty"`
Gradient string `yaml:"gradient,omitempty"`
Color string `yaml:"color,omitempty"`
CTAPrimary CTAConfig `yaml:"cta_primary,omitempty"`
CTASecondary CTAConfig `yaml:"cta_secondary,omitempty"`
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"`
CodeExample string `yaml:"code_example,omitempty"`
VideoURL string `yaml:"video_url,omitempty"`
}
// CTAConfig represents a call-to-action button
type CTAConfig struct {
Text string `yaml:"text,omitempty"`
Link string `yaml:"link,omitempty"`
Style string `yaml:"style,omitempty"` // outline, ghost
// 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
}
// StatConfig represents a single stat/metric
type StatConfig struct {
Value string `yaml:"value,omitempty"`
Label string `yaml:"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"`
}
// FeatureConfig represents a single feature item
type FeatureConfig struct {
Icon string `yaml:"icon,omitempty"`
Title string `yaml:"title,omitempty"`
Description string `yaml:"description,omitempty"`
Icon string `yaml:"icon,omitempty"`
ImageURL string `yaml:"image_url,omitempty"`
}
// SectionConfig represents a content section
type SectionConfig struct {
Type string `yaml:"type,omitempty"` // features, screenshot, readme, releases, contributors, custom
Image string `yaml:"image,omitempty"`
Caption string `yaml:"caption,omitempty"`
File string `yaml:"file,omitempty"`
Title string `yaml:"title,omitempty"`
Limit int `yaml:"limit,omitempty"`
ShowNotes bool `yaml:"show_notes,omitempty"`
ShowCount bool `yaml:"show_count,omitempty"`
// SocialProofConfig represents social proof section
type SocialProofConfig struct {
Logos []string `yaml:"logos,omitempty"`
Testimonial TestimonialConfig `yaml:"testimonial,omitempty"`
Testimonials []TestimonialConfig `yaml:"testimonials,omitempty"`
}
// DocumentationConfig represents documentation template settings
type DocumentationConfig struct {
Source string `yaml:"source,omitempty"`
Search bool `yaml:"search,omitempty"`
TOC bool `yaml:"toc,omitempty"`
Sidebar []SidebarGroup `yaml:"sidebar,omitempty"`
EditLinks EditLinksConfig `yaml:"edit_links,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"`
}
// SidebarGroup represents a sidebar navigation group
type SidebarGroup struct {
Title string `yaml:"title,omitempty"`
Items []string `yaml:"items,omitempty"`
Collapsed bool `yaml:"collapsed,omitempty"`
// PricingConfig represents pricing section
type PricingConfig struct {
Headline string `yaml:"headline,omitempty"`
Subheadline string `yaml:"subheadline,omitempty"`
Plans []PricingPlanConfig `yaml:"plans,omitempty"`
}
// EditLinksConfig represents edit link settings
type EditLinksConfig struct {
Enabled bool `yaml:"enabled,omitempty"`
Text string `yaml:"text,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"`
}
// GalleryConfig represents gallery/portfolio template settings
type GalleryConfig struct {
Source string `yaml:"source,omitempty"`
Columns int `yaml:"columns,omitempty"`
Lightbox bool `yaml:"lightbox,omitempty"`
Captions bool `yaml:"captions,omitempty"`
Items []GalleryItem `yaml:"items,omitempty"`
}
// GalleryItem represents a single gallery item
type GalleryItem struct {
Image string `yaml:"image,omitempty"`
Title string `yaml:"title,omitempty"`
Link string `yaml:"link,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"`
}
// FooterConfig represents footer settings
type FooterConfig struct {
Links []FooterLinkGroup `yaml:"links,omitempty"`
Copyright string `yaml:"copyright,omitempty"`
ShowPoweredBy bool `yaml:"show_powered_by,omitempty"`
}
// FooterLinkGroup represents a group of footer links
type FooterLinkGroup struct {
Title string `yaml:"title,omitempty"`
Items []FooterLink `yaml:"items,omitempty"`
Links []FooterLink `yaml:"links,omitempty"`
Social []SocialLink `yaml:"social,omitempty"`
Copyright string `yaml:"copyright,omitempty"`
ShowPoweredBy bool `yaml:"show_powered_by,omitempty"`
}
// FooterLink represents a single footer link
type FooterLink struct {
Text string `yaml:"text,omitempty"`
URL string `yaml:"url,omitempty"`
Label string `yaml:"label,omitempty"`
URL string `yaml:"url,omitempty"`
}
// SocialLink represents a social media link
type SocialLink struct {
Platform string `yaml:"platform,omitempty"` // twitter, github, discord, linkedin, youtube
URL string `yaml:"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
}
// SEOConfig represents SEO and social sharing settings
@@ -186,15 +200,14 @@ 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"` // Allow public access to releases even for private repos
PublicReleases bool `yaml:"public_releases,omitempty"`
}
// ParseLandingConfig parses a landing.yaml file content
func ParseLandingConfig(content []byte) (*LandingConfig, error) {
config := &LandingConfig{
// Set defaults
Enabled: true,
Template: "simple",
Template: "open-source-hero",
}
if err := yaml.Unmarshal(content, config); err != nil {
@@ -203,13 +216,10 @@ func ParseLandingConfig(content []byte) (*LandingConfig, error) {
// Apply defaults
if config.Template == "" {
config.Template = "simple"
config.Template = "open-source-hero"
}
if config.Branding.DarkMode == "" {
config.Branding.DarkMode = "auto"
}
if config.Gallery.Columns == 0 {
config.Gallery.Columns = 4
if config.Theme.Mode == "" {
config.Theme.Mode = "auto"
}
return config, nil
@@ -225,36 +235,62 @@ func HashConfig(content []byte) string {
func DefaultConfig() *LandingConfig {
return &LandingConfig{
Enabled: true,
Template: "simple",
Branding: BrandingConfig{
DarkMode: "auto",
},
Template: "open-source-hero",
Hero: HeroConfig{
CTAPrimary: CTAConfig{
Text: "Get Started",
Link: "#installation",
Headline: "Build something amazing",
Subheadline: "A powerful toolkit for developers who want to ship fast.",
PrimaryCTA: CTAButton{
Label: "Get Started",
URL: "#",
},
CTASecondary: CTAConfig{
Text: "View Documentation",
Link: "#readme",
SecondaryCTA: CTAButton{
Label: "View on GitHub",
URL: "#",
},
},
Sections: []SectionConfig{
{Type: "readme"},
{Type: "releases", Limit: 3},
Stats: []StatConfig{
{Value: "10k+", Label: "Downloads"},
{Value: "100+", Label: "Contributors"},
{Value: "MIT", Label: "License"},
},
ValueProps: []ValuePropConfig{
{Title: "Fast", Description: "Optimized for performance out of the box.", Icon: "zap"},
{Title: "Flexible", Description: "Adapts to your workflow, not the other way around.", Icon: "gear"},
{Title: "Open Source", Description: "Free forever. Community driven.", Icon: "heart"},
},
CTASection: CTASectionConfig{
Headline: "Ready to get started?",
Subheadline: "Join thousands of developers already using this project.",
Button: CTAButton{
Label: "Get Started Free",
URL: "#",
},
},
Footer: FooterConfig{
ShowPoweredBy: true,
},
Theme: ThemeConfig{
Mode: "auto",
},
}
}
// ValidTemplates returns the list of valid template names
func ValidTemplates() []string {
return []string{"simple", "documentation", "product", "portfolio"}
return []string{"open-source-hero", "minimalist-docs", "saas-conversion", "bold-marketing"}
}
// IsValidTemplate checks if a template name is valid
func IsValidTemplate(name string) bool {
return slices.Contains(ValidTemplates(), name)
}
// TemplateDisplayNames returns a map of template names to display names
func TemplateDisplayNames() map[string]string {
return map[string]string{
"open-source-hero": "Open Source Hero",
"minimalist-docs": "Minimalist Docs",
"saas-conversion": "SaaS Conversion",
"bold-marketing": "Bold Marketing",
}
}

View File

@@ -55,6 +55,8 @@ type RepositoryStruct struct {
type ThemeStruct struct {
DisableRegistration *config.Value[bool]
HideExploreUsers *config.Value[bool]
HelpURL *config.Value[string]
CustomSiteIconURL *config.Value[string]
CustomHomeLogoURL *config.Value[string]
CustomHomeHTML *config.Value[string]
@@ -89,6 +91,8 @@ func initDefaultConfig() {
},
Theme: &ThemeStruct{
DisableRegistration: config.ValueJSON[bool]("theme.disable_registration").WithFileConfig(config.CfgSecKey{Sec: "service", Key: "DISABLE_REGISTRATION"}),
HideExploreUsers: config.ValueJSON[bool]("theme.hide_explore_users").WithDefault(false),
HelpURL: config.ValueJSON[string]("theme.help_url").WithDefault(""),
CustomSiteIconURL: config.ValueJSON[string]("theme.custom_site_icon_url").WithDefault(""),
CustomHomeLogoURL: config.ValueJSON[string]("theme.custom_home_logo_url").WithDefault(""),
CustomHomeHTML: config.ValueJSON[string]("theme.custom_home_html").WithDefault(""),

View File

@@ -174,14 +174,14 @@
"error.report_message": "Pokud jste si jisti, že se jedná o chybu Gitea, prosím vyhledejte problémy na <a href=\"%s\" target=\"_blank\">GitHub</a> a v případě potřeby založte nový problém.",
"error.not_found": "Cíl nebyl nalezen.",
"error.network_error": "Chyba sítě",
"startpage.app_desc": "Snadno přístupný vlastní Git",
"startpage.install": "Jednoduchá na instalaci",
"startpage.install_desc": "Jednoduše <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">spusťte jako binární program</a> pro vaši platformu, nasaďte jej pomocí <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">Docker</a>, nebo jej stáhněte jako <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">balíček</a>.",
"startpage.platform": "Multiplatform",
"startpage.platform_desc": "Gitea běží všude, kde <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">Go</a> může kompilovat: Windows, macOS, Linux, ARM, atd. Vyberte si ten, který milujete!",
"startpage.lightweight": "Lehká",
"startpage.lightweight_desc": "Gitea má minimální požadavky a může běžet na Raspberry Pi. Šetřete energii vašeho stroje!",
"startpage.license_desc": "Vše je na <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%[1]s\">%[2]s</a>! Připojte se tím, že <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%[3]s\">přispějete</a> a uděláte tento projekt ještě lepší. Nestyďte se být přispěvatel!",
"startpage.app_desc": "Steeped in your workflow",
"startpage.install": "Deploy Anywhere",
"startpage.install_desc": "Run the binary, deploy with Docker, or use your favorite package manager. GitCaddy runs wherever you need it.",
"startpage.platform": "AI-Native Platform",
"startpage.platform_desc": "Built for the age of AI-assisted development. Structured APIs, capability discovery, and intelligent context for AI tools.",
"startpage.lightweight": "Lightning Fast",
"startpage.lightweight_desc": "Minimal footprint, maximum performance. GitCaddy runs efficiently on everything from Raspberry Pi to enterprise servers.",
"startpage.license_desc": "GitCaddy is open source and built on the shoulders of giants. Based on Gitea, enhanced for the AI era.",
"install.install": "Instalace",
"install.title": "Výchozí konfigurace",
"install.docker_helper": "Pokud spouštíte Gitea v Dockeru, přečtěte si <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">dokumentaci</a>, než budete měnit jakákoliv nastavení.",

View File

File diff suppressed because it is too large Load Diff

View File

@@ -149,12 +149,12 @@
"error.occurred": "Παρουσιάστηκε ένα σφάλμα",
"error.not_found": "Ο προορισμός δεν βρέθηκε.",
"error.network_error": "Σφάλμα δικτύου",
"startpage.app_desc": "Μια ανώδυνη, αυτο-φιλοξενούμενη υπηρεσία Git",
"startpage.install": "Εύκολο στην εγκατάσταση",
"startpage.platform": "Πολυπλατφορμικό",
"startpage.lightweight": "Ελαφρύ",
"startpage.lightweight_desc": "Gitea έχει χαμηλές ελάχιστες απαιτήσεις και μπορεί να τρέξει σε ένα οικονομικό Raspberry Pi. Εξοικονομήστε ενέργεια!",
"startpage.license": "Ανοικτού κώδικα",
"startpage.app_desc": "Steeped in your workflow",
"startpage.install": "Deploy Anywhere",
"startpage.platform": "AI-Native Platform",
"startpage.lightweight": "Lightning Fast",
"startpage.lightweight_desc": "Minimal footprint, maximum performance. GitCaddy runs efficiently on everything from Raspberry Pi to enterprise servers.",
"startpage.license": "Open Source",
"install.install": "Εγκατάσταση",
"install.title": "Αρχικές Ρυθμίσεις",
"install.docker_helper": "Αν εκτελέσετε το Gitea μέσα στο Docker, παρακαλώ διαβάστε την <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">τεκμηρίωση</a> πριν αλλάξετε τις ρυθμίσεις.",

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

@@ -87,12 +87,12 @@
"filter.public": "عمومی",
"filter.private": "خصوصی",
"editor.buttons.table.add.insert": "افزودن",
"startpage.app_desc": "یک سرویس گیت بی‌درد سر و راحت",
"startpage.install": "راه‌اندازی ساده",
"startpage.platform": "مستقل از سکو",
"startpage.lightweight": "ابزارک سبک",
"startpage.lightweight_desc": "گیتی با حداقل منابع میتوانید برای روی دستگاه Raspberry Pi اجرا شود و مصرف انرژی شما را کاهش دهد!",
"startpage.license": "متن باز",
"startpage.app_desc": "Steeped in your workflow",
"startpage.install": "Deploy Anywhere",
"startpage.platform": "AI-Native Platform",
"startpage.lightweight": "Lightning Fast",
"startpage.lightweight_desc": "Minimal footprint, maximum performance. GitCaddy runs efficiently on everything from Raspberry Pi to enterprise servers.",
"startpage.license": "Open Source",
"install.install": "نصب و راه اندازی",
"install.title": "تنظیمات اولیه",
"install.docker_helper": "اگر گیتی را با داکر اجرا کرده‌اید، لطفا قبل از هر تغییری <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">مستندات</a> را مطالعه نمایید.",

View File

@@ -101,12 +101,12 @@
"error.occurred": "Virhe tapahtui",
"error.not_found": "Kohdetta ei löytynyt.",
"error.network_error": "Verkkovirhe",
"startpage.app_desc": "Kivuton, itsehostattu Git-palvelu",
"startpage.install": "Helppo asentaa",
"startpage.platform": "Alustariippumaton",
"startpage.lightweight": "Kevyt",
"startpage.lightweight_desc": "Gitealla on vähäiset vähimmäisvaatimukset, joten se toimii jopa halvassa Raspberry Pi:ssä. Säästä koneesi energiaa!",
"startpage.license": "Avoin lähdekoodi",
"startpage.app_desc": "Steeped in your workflow",
"startpage.install": "Deploy Anywhere",
"startpage.platform": "AI-Native Platform",
"startpage.lightweight": "Lightning Fast",
"startpage.lightweight_desc": "Minimal footprint, maximum performance. GitCaddy runs efficiently on everything from Raspberry Pi to enterprise servers.",
"startpage.license": "Open Source",
"install.install": "Asennus",
"install.title": "Alkuperäiset asetukset",
"install.docker_helper": "Jos ajat Giteaa Dockerin sisällä, lue <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">ohjeet</a> ennen minkään asetuksen muuttamista.",

View File

File diff suppressed because it is too large Load Diff

View File

@@ -207,15 +207,15 @@
"error.report_message": "Má chreideann tú gur fabht Gitea é seo, déan cuardach le haghaidh ceisteanna ar <a href=\"%s\" target=\"_blank\">GitHub</a> nó oscail eagrán nua más gá.",
"error.not_found": "Ní raibh an sprioc in ann a fháil.",
"error.network_error": "Earráid líonra",
"startpage.app_desc": "Seirbhís Git gan phian, féin-óstáil",
"startpage.install": "Éasca a shuiteáil",
"startpage.install_desc": "Níl ort ach <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%[1]s\">rith an dénártha</a> do d'ardán, seol é le <a target=\"_blank\" rel=\"noopener noreferrer \" href=\"%[2]s\">Docker</a>, nó faigh <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%[3]s\">pacáilte</a> é.",
"startpage.platform": "Tras-ardán",
"startpage.platform_desc": "Ritheann Gitea áit ar <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/\">bith is féidir le Go</a> tiomsú le haghaidh: Windows, macOS, Linux, ARM, srl Roghnaigh an ceann is breá leat!",
"startpage.lightweight": "Éadrom",
"startpage.lightweight_desc": "Tá íosta riachtanais íseal ag Gitea agus is féidir leo rith ar Raspberry Pi saor. Sábháil fuinneamh do mheaisín!",
"startpage.license": "Foinse Oscailte",
"startpage.license_desc": "Téigh go bhfaighidh <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%[1]s\">%[2]s</a>! Bí linn trí <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%[3]s\">cur leis</a> chun an tionscadal seo a fheabhsú fós. Ná bíodh cúthail ort a bheith i do rannpháirtí!",
"startpage.app_desc": "Steeped in your workflow",
"startpage.install": "Deploy Anywhere",
"startpage.install_desc": "Run the binary, deploy with Docker, or use your favorite package manager. GitCaddy runs wherever you need it.",
"startpage.platform": "AI-Native Platform",
"startpage.platform_desc": "Built for the age of AI-assisted development. Structured APIs, capability discovery, and intelligent context for AI tools.",
"startpage.lightweight": "Lightning Fast",
"startpage.lightweight_desc": "Minimal footprint, maximum performance. GitCaddy runs efficiently on everything from Raspberry Pi to enterprise servers.",
"startpage.license": "Open Source",
"startpage.license_desc": "GitCaddy is open source and built on the shoulders of giants. Based on Gitea, enhanced for the AI era.",
"install.install": "Suiteáil",
"install.installing_desc": "Suiteáil anois, fan go fóill…",
"install.title": "Cumraíocht Tosaigh",

View File

@@ -75,11 +75,11 @@
"filter.public": "Nyilvános",
"filter.private": "Privát",
"editor.buttons.table.add.insert": "Hozzáadás",
"startpage.app_desc": "Fájdalommentes, saját gépre telepíthető Git szolgáltatás",
"startpage.install": "Könnyen telepíthető",
"startpage.platform": "Keresztplatformos",
"startpage.lightweight": "Könnyűsúlyú",
"startpage.license": "Nyílt forráskódú",
"startpage.app_desc": "Steeped in your workflow",
"startpage.install": "Deploy Anywhere",
"startpage.platform": "AI-Native Platform",
"startpage.lightweight": "Lightning Fast",
"startpage.license": "Open Source",
"install.install": "Telepítés",
"install.title": "Kezdeti konfiguráció",
"install.docker_helper": "Ha ön a Gitea-t Docker-ből futtatja, kérem olvassa el a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">dokumentációt</a> a beállítások megváltoztatása előtt.",

View File

@@ -147,12 +147,12 @@
"error.occurred": "Terjadi kesalahan",
"error.report_message": "Jika Anda yakin ini adalah bug Gitea, silakan cari isu di <a href=\"%s\" target=\"_blank\">GitHub</a> atau buka isu baru jika diperlukan.",
"error.not_found": "Target tidak dapat ditemukan.",
"startpage.app_desc": "Sebuah layanan hosting Git sendiri yang tanpa kesulitan",
"startpage.install": "Mudah dipasang",
"startpage.platform": "Lintas platform",
"startpage.lightweight": "Ringan",
"startpage.lightweight_desc": "Gitea hanya membutuhkan persyaratan minimal dan bisa berjalan pada Raspberry Pi yang murah. Bisa menghemat listrik!",
"startpage.license": "Sumber Terbuka",
"startpage.app_desc": "Steeped in your workflow",
"startpage.install": "Deploy Anywhere",
"startpage.platform": "AI-Native Platform",
"startpage.lightweight": "Lightning Fast",
"startpage.lightweight_desc": "Minimal footprint, maximum performance. GitCaddy runs efficiently on everything from Raspberry Pi to enterprise servers.",
"startpage.license": "Open Source",
"install.title": "Konfigurasi Awal",
"install.user": "Nama Pengguna",
"install.password": "Kata Sandi",

View File

@@ -100,12 +100,12 @@
"error.occurred": "Villa kom upp",
"error.not_found": "Markmiðið fannst ekki.",
"error.network_error": "Netkerfisvilla",
"startpage.app_desc": "Þrautalaus og sjálfhýst Git þjónusta",
"startpage.install": "Einföld uppsetning",
"startpage.platform": "Fjölvettvangur",
"startpage.lightweight": "Létt",
"startpage.lightweight_desc": "Gitea hefur lágar lágmarkskröfur og getur keyrt á ódýrum Raspberry Pi. Sparaðu orku!",
"startpage.license": "Frjáls Hugbúnaður",
"startpage.app_desc": "Steeped in your workflow",
"startpage.install": "Deploy Anywhere",
"startpage.platform": "AI-Native Platform",
"startpage.lightweight": "Lightning Fast",
"startpage.lightweight_desc": "Minimal footprint, maximum performance. GitCaddy runs efficiently on everything from Raspberry Pi to enterprise servers.",
"startpage.license": "Open Source",
"install.install": "Uppsetning",
"install.title": "Upphafleg Uppsetning",
"install.docker_helper": "Ef þú keyrir Gitea inni í Docker þá viltu vinsamlegast lesa <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">leiðbeiningaritið</a> áður en þú breytir stillingum.",

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

@@ -155,12 +155,12 @@
"error.occurred": "Radusies kļūda",
"error.not_found": "Pieprasītie dati netika atrasti.",
"error.network_error": "Tīkla kļūda",
"startpage.app_desc": "Viegli uzstādāms Git serviss",
"startpage.install": "Vienkārši instalējams",
"startpage.platform": "Pieejama dažādām platformām",
"startpage.lightweight": "Viegla",
"startpage.lightweight_desc": "Gitea ir miminālas prasības un to var darbināt uz nedārga Raspberry Pi datora. Ietaupi savai ierīcei resursus!",
"startpage.license": "Atvērtā pirmkoda",
"startpage.app_desc": "Steeped in your workflow",
"startpage.install": "Deploy Anywhere",
"startpage.platform": "AI-Native Platform",
"startpage.lightweight": "Lightning Fast",
"startpage.lightweight_desc": "Minimal footprint, maximum performance. GitCaddy runs efficiently on everything from Raspberry Pi to enterprise servers.",
"startpage.license": "Open Source",
"install.install": "Instalācija",
"install.title": "Sākotnējā konfigurācija",
"install.docker_helper": "Ja Gitea ir uzstādīts Docker konteinerī, izlasiet <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">vadlīninas</a> pirms maināt iestatījumus.",

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

@@ -87,12 +87,12 @@
"filter.public": "ප්‍රසිද්ධ",
"filter.private": "පෞද්ගලික",
"editor.buttons.table.add.insert": "එකතු",
"startpage.app_desc": "වේදනාකාරී, ස්වයං-සත්කාරක Git සේවාවක්",
"startpage.install": "ස්ථාපනයට පහසුය",
"startpage.platform": "හරස් වේදිකාව",
"startpage.lightweight": "සැහැල්ලු",
"startpage.lightweight_desc": "Gitea අඩු අවම අවශ්යතා ඇති අතර මිල අඩු Raspberry Pi මත ධාවනය කළ හැකිය. ඔබේ යන්ත්ර ශක්තිය සුරකින්න!",
"startpage.license": "විවෘත මූලාශ්‍ර",
"startpage.app_desc": "Steeped in your workflow",
"startpage.install": "Deploy Anywhere",
"startpage.platform": "AI-Native Platform",
"startpage.lightweight": "Lightning Fast",
"startpage.lightweight_desc": "Minimal footprint, maximum performance. GitCaddy runs efficiently on everything from Raspberry Pi to enterprise servers.",
"startpage.license": "Open Source",
"install.install": "ස්ථාපනය",
"install.title": "මූලික වින්යාසය",
"install.docker_helper": "ඔබ Docker තුළ Gitea ධාවනය කරන්නේ නම්, කරුණාකර ඕනෑම සැකසුම් වෙනස් කිරීමට පෙර <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">ලියකියවිලි</a> කියවන්න.",

View File

@@ -147,12 +147,12 @@
"error.occurred": "Vyskytla sa chyba",
"error.not_found": "Nebolo možné nájsť cieľ.",
"error.network_error": "Chyba siete",
"startpage.app_desc": "Jednoducho prístupný vlastný Git",
"startpage.install": "Jednoduchá inštalácia",
"startpage.platform": "Multiplatformový",
"startpage.lightweight": "Ľahká",
"startpage.lightweight_desc": "Gitea má minimálne požiadavky a môže bežať na Raspberry Pi. Šetrite energiou vášho stroja!",
"startpage.license": "Otvorený zdrojový kód",
"startpage.app_desc": "Steeped in your workflow",
"startpage.install": "Deploy Anywhere",
"startpage.platform": "AI-Native Platform",
"startpage.lightweight": "Lightning Fast",
"startpage.lightweight_desc": "Minimal footprint, maximum performance. GitCaddy runs efficiently on everything from Raspberry Pi to enterprise servers.",
"startpage.license": "Open Source",
"install.install": "Inštalácia",
"install.title": "Východzia konfigurácia",
"install.docker_helper": "Ak spúšťate Gitea v Docker kontajneri, prečítajte si <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">dokumentáciu</a> pred zmenou akýchkoľvek nastavení.",

View File

@@ -74,12 +74,12 @@
"filter.public": "Offentlig",
"filter.private": "Privat",
"editor.buttons.table.add.insert": "Lägg till",
"startpage.app_desc": "En smidig, självhostad Git-tjänst",
"startpage.install": "Lätt att installera",
"startpage.platform": "Plattformsoberoende",
"startpage.lightweight": "Lättviktig",
"startpage.lightweight_desc": "Gitea har låga minimum-krav och kan köras på en billig Rasperry Pi. Spara på din maskins kraft!",
"startpage.license": "Öppen källkod",
"startpage.app_desc": "Steeped in your workflow",
"startpage.install": "Deploy Anywhere",
"startpage.platform": "AI-Native Platform",
"startpage.lightweight": "Lightning Fast",
"startpage.lightweight_desc": "Minimal footprint, maximum performance. GitCaddy runs efficiently on everything from Raspberry Pi to enterprise servers.",
"startpage.license": "Open Source",
"install.title": "Ursprunglig konfiguration",
"install.docker_helper": "Om du kör Gitea i Docker, vänligen läs igenom <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">dokumentationen</a> innan några inställningar ändras.",
"install.db_title": "Databasinställningar",

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 640 640" width="32" height="32"><path d="m395.9 484.2-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12" style="fill:#fff"/><path d="M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9 0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6M125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1m300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1" style="fill:#609926"/><path d="M326.8 380.1c-8.2.1-15.4 5.8-17.3 13.8s2 16.3 9.1 20c7.7 4 17.5 1.8 22.7-5.4 5.1-7.1 4.3-16.9-1.8-23.1l24-49.1c1.5.1 3.7.2 6.2-.5 4.1-.9 7.1-3.6 7.1-3.6 4.2 1.8 8.6 3.8 13.2 6.1 4.8 2.4 9.3 4.9 13.4 7.3.9.5 1.8 1.1 2.8 1.9 1.6 1.3 3.4 3.1 4.7 5.5 1.9 5.5-1.9 14.9-1.9 14.9-2.3 7.6-18.4 40.6-18.4 40.6-8.1-.2-15.3 5-17.7 12.5-2.6 8.1 1.1 17.3 8.9 21.3s17.4 1.7 22.5-5.3c5-6.8 4.6-16.3-1.1-22.6 1.9-3.7 3.7-7.4 5.6-11.3 5-10.4 13.5-30.4 13.5-30.4.9-1.7 5.7-10.3 2.7-21.3-2.5-11.4-12.6-16.7-12.6-16.7-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3 4.7-9.7 9.4-19.3 14.1-29-4.1-2-8.1-4-12.2-6.1-4.8 9.8-9.7 19.7-14.5 29.5-6.7-.1-12.9 3.5-16.1 9.4-3.4 6.3-2.7 14.1 1.9 19.8z" style="fill:#609926"/></svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="374px" height="374px" viewBox="397.5 371.5 374 374" style="enable-background:new 397.5 371.5 374 374;"
xml:space="preserve">
<path style="fill:#609926;" d="M526.05,605.17c-0.69,0-1.39-0.14-2.08-0.46l-82.52-37.42v95.65l138.07,48.38V563.78l-50.37,40.29
C528.27,604.76,527.16,605.17,526.05,605.17L526.05,605.17z"/>
<path style="fill:#609926;" d="M643.03,605.17c-1.16,0-2.26-0.42-3.15-1.11l-50.37-40.29v147.55l138.16-48.38v-95.65l-82.56,37.42
c-0.65,0.28-1.34,0.46-2.04,0.46L643.03,605.17z"/>
<path style="fill:#609926;" d="M525.31,594.35l49.08-39.22l-137-48.06l-37.88,30.34L525.31,594.35z"/>
<path style="fill:#609926;" d="M594.69,555.13l49.03,39.22l125.81-56.94l-37.88-30.34L594.69,555.13z"/>
<path style="fill:#609926;" d="M525.36,438.98c-11.01-3.42-23.22-7.26-36.22-13.46c-1.62,64.89,35.94,80.94,52.68,84.88
c16.47,3.88,30.57,0.55,35.48-3.88c-11.24-14.06-25.81-26.6-43.43-31.45c16.19,1.76,31.13,9.62,43.99,19.89
c-3.88-40.66-22.76-46.62-52.45-55.97H525.36z"/>
<path style="fill:#609926;" d="M589.74,491.11c5.23,5,20.81,8.79,38.85,4.53c18.22-4.3,59.16-21.79,57.22-92.6
c-14.2,6.8-27.57,10.96-39.59,14.76c-32.47,10.17-53.01,16.6-56.98,61.52c13.92-11.38,30.16-20.26,47.96-22.29
c-19.24,5.23-35.2,18.87-47.41,34.04L589.74,491.11z"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" width="32" height="32"><path d="M395.9 484.2l-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12z" fill="#fff"/><g fill="#609926"><path d="M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9 0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6zM125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1zm300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1z"/><path d="M326.8 380.1c-8.2.1-15.4 5.8-17.3 13.8-1.9 8 2 16.3 9.1 20 7.7 4 17.5 1.8 22.7-5.4 5.1-7.1 4.3-16.9-1.8-23.1l24-49.1c1.5.1 3.7.2 6.2-.5 4.1-.9 7.1-3.6 7.1-3.6 4.2 1.8 8.6 3.8 13.2 6.1 4.8 2.4 9.3 4.9 13.4 7.3.9.5 1.8 1.1 2.8 1.9 1.6 1.3 3.4 3.1 4.7 5.5 1.9 5.5-1.9 14.9-1.9 14.9-2.3 7.6-18.4 40.6-18.4 40.6-8.1-.2-15.3 5-17.7 12.5-2.6 8.1 1.1 17.3 8.9 21.3 7.8 4 17.4 1.7 22.5-5.3 5-6.8 4.6-16.3-1.1-22.6 1.9-3.7 3.7-7.4 5.6-11.3 5-10.4 13.5-30.4 13.5-30.4.9-1.7 5.7-10.3 2.7-21.3-2.5-11.4-12.6-16.7-12.6-16.7-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3 4.7-9.7 9.4-19.3 14.1-29-4.1-2-8.1-4-12.2-6.1-4.8 9.8-9.7 19.7-14.5 29.5-6.7-.1-12.9 3.5-16.1 9.4-3.4 6.3-2.7 14.1 1.9 19.8l-24.6 50.4z"/></g></svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="374px" height="374px" viewBox="397.5 371.5 374 374" style="enable-background:new 397.5 371.5 374 374;"
xml:space="preserve">
<path style="fill:#609926;" d="M526.05,605.17c-0.69,0-1.39-0.14-2.08-0.46l-82.52-37.42v95.65l138.07,48.38V563.78l-50.37,40.29
C528.27,604.76,527.16,605.17,526.05,605.17L526.05,605.17z"/>
<path style="fill:#609926;" d="M643.03,605.17c-1.16,0-2.26-0.42-3.15-1.11l-50.37-40.29v147.55l138.16-48.38v-95.65l-82.56,37.42
c-0.65,0.28-1.34,0.46-2.04,0.46L643.03,605.17z"/>
<path style="fill:#609926;" d="M525.31,594.35l49.08-39.22l-137-48.06l-37.88,30.34L525.31,594.35z"/>
<path style="fill:#609926;" d="M594.69,555.13l49.03,39.22l125.81-56.94l-37.88-30.34L594.69,555.13z"/>
<path style="fill:#609926;" d="M525.36,438.98c-11.01-3.42-23.22-7.26-36.22-13.46c-1.62,64.89,35.94,80.94,52.68,84.88
c16.47,3.88,30.57,0.55,35.48-3.88c-11.24-14.06-25.81-26.6-43.43-31.45c16.19,1.76,31.13,9.62,43.99,19.89
c-3.88-40.66-22.76-46.62-52.45-55.97H525.36z"/>
<path style="fill:#609926;" d="M589.74,491.11c5.23,5,20.81,8.79,38.85,4.53c18.22-4.3,59.16-21.79,57.22-92.6
c-14.2,6.8-27.57,10.96-39.59,14.76c-32.47,10.17-53.01,16.6-56.98,61.52c13.92-11.38,30.16-20.26,47.96-22.29
c-19.24,5.23-35.2,18.87-47.41,34.04L589.74,491.11z"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 640 640" width="32" height="32"><path d="m395.9 484.2-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12" style="fill:#fff"/><path d="M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9 0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6M125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1m300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1" style="fill:#609926"/><path d="M326.8 380.1c-8.2.1-15.4 5.8-17.3 13.8s2 16.3 9.1 20c7.7 4 17.5 1.8 22.7-5.4 5.1-7.1 4.3-16.9-1.8-23.1l24-49.1c1.5.1 3.7.2 6.2-.5 4.1-.9 7.1-3.6 7.1-3.6 4.2 1.8 8.6 3.8 13.2 6.1 4.8 2.4 9.3 4.9 13.4 7.3.9.5 1.8 1.1 2.8 1.9 1.6 1.3 3.4 3.1 4.7 5.5 1.9 5.5-1.9 14.9-1.9 14.9-2.3 7.6-18.4 40.6-18.4 40.6-8.1-.2-15.3 5-17.7 12.5-2.6 8.1 1.1 17.3 8.9 21.3s17.4 1.7 22.5-5.3c5-6.8 4.6-16.3-1.1-22.6 1.9-3.7 3.7-7.4 5.6-11.3 5-10.4 13.5-30.4 13.5-30.4.9-1.7 5.7-10.3 2.7-21.3-2.5-11.4-12.6-16.7-12.6-16.7-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3 4.7-9.7 9.4-19.3 14.1-29-4.1-2-8.1-4-12.2-6.1-4.8 9.8-9.7 19.7-14.5 29.5-6.7-.1-12.9 3.5-16.1 9.4-3.4 6.3-2.7 14.1 1.9 19.8z" style="fill:#609926"/></svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="374px" height="374px" viewBox="397.5 371.5 374 374" style="enable-background:new 397.5 371.5 374 374;"
xml:space="preserve">
<path style="fill:#609926;" d="M526.05,605.17c-0.69,0-1.39-0.14-2.08-0.46l-82.52-37.42v95.65l138.07,48.38V563.78l-50.37,40.29
C528.27,604.76,527.16,605.17,526.05,605.17L526.05,605.17z"/>
<path style="fill:#609926;" d="M643.03,605.17c-1.16,0-2.26-0.42-3.15-1.11l-50.37-40.29v147.55l138.16-48.38v-95.65l-82.56,37.42
c-0.65,0.28-1.34,0.46-2.04,0.46L643.03,605.17z"/>
<path style="fill:#609926;" d="M525.31,594.35l49.08-39.22l-137-48.06l-37.88,30.34L525.31,594.35z"/>
<path style="fill:#609926;" d="M594.69,555.13l49.03,39.22l125.81-56.94l-37.88-30.34L594.69,555.13z"/>
<path style="fill:#609926;" d="M525.36,438.98c-11.01-3.42-23.22-7.26-36.22-13.46c-1.62,64.89,35.94,80.94,52.68,84.88
c16.47,3.88,30.57,0.55,35.48-3.88c-11.24-14.06-25.81-26.6-43.43-31.45c16.19,1.76,31.13,9.62,43.99,19.89
c-3.88-40.66-22.76-46.62-52.45-55.97H525.36z"/>
<path style="fill:#609926;" d="M589.74,491.11c5.23,5,20.81,8.79,38.85,4.53c18.22-4.3,59.16-21.79,57.22-92.6
c-14.2,6.8-27.57,10.96-39.59,14.76c-32.47,10.17-53.01,16.6-56.98,61.52c13.92-11.38,30.16-20.26,47.96-22.29
c-19.24,5.23-35.2,18.87-47.41,34.04L589.74,491.11z"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,21 +1,11 @@
================================================================================
GitCaddy Server
--------------------------------------------------------------------------------
GitCaddy Server - Third Party Licenses
Copyright (c) 2024-2026 MarketAlly Inc.
https://git.marketally.com/gitcaddy/gitcaddy-server
https://marketally.com
Licensed under the MIT License.
GitCaddy Server is based on Gitea (https://gitea.io), which is licensed under
the MIT License. We thank the Gitea team and all contributors for building
the foundation that makes GitCaddy possible.
Original Gitea Copyright (c) 2016-2024 The Gitea Authors
================================================================================
This file contains the licenses for all third-party dependencies used in
GitCaddy Server.
================================================================================
GitCaddy is built on Gitea (https://gitea.com) and includes the following
open source software. We are grateful to all the contributors.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
@braintree/sanitize-url@7.1.1 - MIT
@@ -1114,7 +1104,7 @@ THE SOFTWARE.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
@citation-js/date@0.5.1 - MIT
@citation-js/name@0.4.2 - MIT
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------

View File

@@ -15,14 +15,13 @@ import (
const scalarTemplate = `<!DOCTYPE html>
<html>
<head>
<title>{{.AppName}} API v2 Reference</title>
<title>GitCaddy API v2 Reference</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
/* Custom theme to match Gitea */
:root {
--scalar-color-1: #4a90d9;
--scalar-color-accent: #4a90d9;
--scalar-color-1: #609926;
--scalar-color-accent: #609926;
--scalar-background-1: #ffffff;
--scalar-background-2: #f8f9fa;
--scalar-background-3: #e9ecef;
@@ -34,9 +33,42 @@ const scalarTemplate = `<!DOCTYPE html>
--scalar-background-3: #333333;
}
}
.api-version-bar {
background: var(--scalar-background-2, #f8f9fa);
padding: 8px 16px;
display: flex;
align-items: center;
gap: 12px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 14px;
border-bottom: 1px solid var(--scalar-background-3, #e9ecef);
}
.api-version-bar a {
color: #609926;
text-decoration: none;
padding: 4px 12px;
border-radius: 4px;
transition: background 0.2s;
}
.api-version-bar a:hover {
background: rgba(96, 153, 38, 0.1);
}
.api-version-bar a.active {
background: #609926;
color: white;
}
.api-version-bar .back-link {
margin-right: auto;
}
</style>
</head>
<body>
<div class="api-version-bar">
<a href="{{.AppSubURL}}/" class="back-link">← Back to GitCaddy</a>
<span>API Version:</span>
<a href="{{.AppSubURL}}/api/swagger">v1</a>
<a href="{{.AppSubURL}}/api/v2/docs" class="active">v2</a>
</div>
<script
id="api-reference"
data-url="{{.SpecURL}}"
@@ -60,8 +92,9 @@ const scalarTemplate = `<!DOCTYPE html>
// DocsScalar serves the Scalar API documentation UI for v2
func DocsScalar(ctx *context.APIContext) {
data := map[string]string{
"AppName": setting.AppName,
"SpecURL": setting.AppSubURL + "/api/v2/swagger.json",
"AppName": setting.AppName,
"AppSubURL": setting.AppSubURL,
"SpecURL": setting.AppSubURL + "/api/v2/swagger.json",
}
ctx.Resp.Header().Set("Content-Type", "text/html; charset=utf-8")

View File

@@ -15,14 +15,14 @@ import (
// 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"`
Branding pages_module.BrandingConfig `json:"branding"`
Hero pages_module.HeroConfig `json:"hero"`
SEO pages_module.SEOConfig `json:"seo"`
Footer pages_module.FooterConfig `json:"footer"`
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"`
}
// PagesContentResponse represents the rendered content for a landing page
@@ -52,7 +52,7 @@ func GetPagesConfig(ctx *context.APIContext) {
PublicLanding: config.PublicLanding,
Template: config.Template,
Domain: config.Domain,
Branding: config.Branding,
Brand: config.Brand,
Hero: config.Hero,
SEO: config.SEO,
Footer: config.Footer,
@@ -82,7 +82,10 @@ func GetPagesContent(ctx *context.APIContext) {
// Build title
title := config.SEO.Title
if title == "" {
title = config.Hero.Title
title = config.Hero.Headline
}
if title == "" {
title = config.Brand.Name
}
if title == "" {
title = repo.Name
@@ -91,7 +94,7 @@ func GetPagesContent(ctx *context.APIContext) {
// Build description
description := config.SEO.Description
if description == "" {
description = config.Hero.Tagline
description = config.Hero.Subheadline
}
if description == "" {
description = repo.Description

View File

@@ -241,6 +241,7 @@ func ChangeConfig(ctx *context.Context) {
cfg.Theme.DisableRegistration.DynKey(): marshalBool,
cfg.Theme.CustomHomeHTML.DynKey(): marshalString(""),
cfg.Theme.APIHeaderURL.DynKey(): marshalString(""),
cfg.Theme.HelpURL.DynKey(): marshalString(""),
cfg.Theme.CustomHomeTitle.DynKey(): marshalString(""),
cfg.Theme.CustomHomeTagline.DynKey(): marshalString(""),
cfg.Theme.PinnedOrgDisplayFormat.DynKey(): marshalString("condensed"),

View File

@@ -28,7 +28,7 @@ func Code(ctx *context.Context) {
return
}
ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage
ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage || setting.Config().Theme.HideExploreUsers.Value(ctx)
ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled
ctx.Data["Title"] = ctx.Tr("explore_title")

View File

@@ -19,7 +19,7 @@ func Organizations(ctx *context.Context) {
return
}
ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage
ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage || setting.Config().Theme.HideExploreUsers.Value(ctx)
ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage
ctx.Data["Title"] = ctx.Tr("explore_title")
ctx.Data["PageIsExplore"] = true

View File

@@ -146,7 +146,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
// Repos render explore repositories page
func Repos(ctx *context.Context) {
ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage
ctx.Data["UsersPageIsDisabled"] = setting.Service.Explore.DisableUsersPage || setting.Config().Theme.HideExploreUsers.Value(ctx)
ctx.Data["OrganizationsPageIsDisabled"] = setting.Service.Explore.DisableOrganizationsPage
ctx.Data["CodePageIsDisabled"] = setting.Service.Explore.DisableCodePage
ctx.Data["Title"] = ctx.Tr("explore_title")

View File

@@ -128,7 +128,7 @@ func RenderUserSearch(ctx *context.Context, opts user_model.SearchUserOptions, t
// Users render explore users page
func Users(ctx *context.Context) {
if setting.Service.Explore.DisableUsersPage {
if setting.Service.Explore.DisableUsersPage || setting.Config().Theme.HideExploreUsers.Value(ctx) {
ctx.Redirect(setting.AppSubURL + "/explore")
return
}

View File

@@ -187,6 +187,15 @@ func home(ctx *context.Context, viewRepositories bool) {
ctx.Data["UngroupedPinned"] = ungroupedPinned
ctx.Data["HasPinnedRepos"] = len(pinnedRepos) > 0
// Count public pinned repos for non-member visibility
hasPublicPinnedRepos := false
for _, p := range pinnedRepos {
if repo, ok := p.Repo.(*repo_model.Repository); ok && repo != nil && !repo.IsPrivate {
hasPublicPinnedRepos = true
break
}
}
ctx.Data["HasPublicPinnedRepos"] = hasPublicPinnedRepos
// Load public members (limit to 12 for overview display)
publicMembers, totalPublicMembers, err := organization.GetPublicOrgMembers(ctx, org.ID, 12)
if err != nil {

View File

@@ -9,6 +9,7 @@ import (
"net/http"
"path"
"strings"
"time"
"code.gitea.io/gitea/models/renderhelper"
repo_model "code.gitea.io/gitea/models/repo"
@@ -22,10 +23,10 @@ import (
)
const (
tplPagesSimple templates.TplName = "pages/simple"
tplPagesDocumentation templates.TplName = "pages/documentation"
tplPagesProduct templates.TplName = "pages/product"
tplPagesPortfolio templates.TplName = "pages/portfolio"
tplPagesOpenSourceHero templates.TplName = "pages/open-source-hero"
tplPagesMinimalistDocs templates.TplName = "pages/minimalist-docs"
tplPagesSaasConversion templates.TplName = "pages/saas-conversion"
tplPagesBoldMarketing templates.TplName = "pages/bold-marketing"
)
// ServeLandingPage serves the landing page for a repository
@@ -106,7 +107,7 @@ func renderLandingPage(ctx *context.Context, repo *repo_model.Repository, config
ctx.Data["PageIsPagesLanding"] = true
// Load README content
readme, err := loadReadmeContent(ctx, repo, config)
readme, err := loadReadmeContent(ctx, repo)
if err != nil {
log.Warn("Failed to load README: %v", err)
}
@@ -115,6 +116,13 @@ func renderLandingPage(ctx *context.Context, repo *repo_model.Repository, config
// Load repo stats
ctx.Data["NumStars"] = repo.NumStars
ctx.Data["NumForks"] = repo.NumForks
ctx.Data["Year"] = time.Now().Year()
// Load latest release
release, err := repo_model.GetLatestReleaseByRepoID(ctx, repo.ID)
if err == nil && release != nil {
ctx.Data["LatestRelease"] = release
}
// Select template based on config
tpl := selectTemplate(config.Template)
@@ -127,14 +135,17 @@ func getPageTitle(repo *repo_model.Repository, config *pages_module.LandingConfi
if config.SEO.Title != "" {
return config.SEO.Title
}
if config.Hero.Title != "" {
return config.Hero.Title
if config.Hero.Headline != "" {
return config.Hero.Headline
}
if config.Brand.Name != "" {
return config.Brand.Name
}
return repo.Name
}
// loadReadmeContent loads and renders the README content
func loadReadmeContent(ctx *context.Context, repo *repo_model.Repository, config *pages_module.LandingConfig) (template.HTML, error) {
func loadReadmeContent(ctx *context.Context, repo *repo_model.Repository) (template.HTML, error) {
gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
if err != nil {
return "", err
@@ -152,7 +163,7 @@ func loadReadmeContent(ctx *context.Context, repo *repo_model.Repository, config
}
// Find README file
readmePath := findReadmePath(commit, config)
readmePath := findReadmePath(commit)
if readmePath == "" {
return "", errors.New("README not found")
}
@@ -185,14 +196,7 @@ func loadReadmeContent(ctx *context.Context, repo *repo_model.Repository, config
}
// findReadmePath finds the README file path
func findReadmePath(commit *git.Commit, config *pages_module.LandingConfig) string {
// Check config for custom readme location
for _, section := range config.Sections {
if section.Type == "readme" && section.File != "" {
return section.File
}
}
func findReadmePath(commit *git.Commit) string {
// Default README locations
readmePaths := []string{
"README.md",
@@ -215,14 +219,16 @@ func findReadmePath(commit *git.Commit, config *pages_module.LandingConfig) stri
// selectTemplate selects the template based on configuration
func selectTemplate(templateName string) templates.TplName {
switch templateName {
case "documentation":
return tplPagesDocumentation
case "product":
return tplPagesProduct
case "portfolio":
return tplPagesPortfolio
case "minimalist-docs":
return tplPagesMinimalistDocs
case "saas-conversion":
return tplPagesSaasConversion
case "bold-marketing":
return tplPagesBoldMarketing
case "open-source-hero":
return tplPagesOpenSourceHero
default:
return tplPagesSimple
return tplPagesOpenSourceHero
}
}

View File

@@ -4,23 +4,98 @@
package setting
import (
"fmt"
"net/http"
"strings"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/json"
pages_module "code.gitea.io/gitea/modules/pages"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/context"
pages_service "code.gitea.io/gitea/services/pages"
)
const tplRepoSettingsPages templates.TplName = "repo/settings/pages"
const (
tplRepoSettingsPages templates.TplName = "repo/settings/pages"
tplRepoSettingsPagesBrand templates.TplName = "repo/settings/pages_brand"
tplRepoSettingsPagesHero templates.TplName = "repo/settings/pages_hero"
tplRepoSettingsPagesContent templates.TplName = "repo/settings/pages_content"
tplRepoSettingsPagesSocial templates.TplName = "repo/settings/pages_social"
tplRepoSettingsPagesPricing templates.TplName = "repo/settings/pages_pricing"
tplRepoSettingsPagesFooter templates.TplName = "repo/settings/pages_footer"
tplRepoSettingsPagesTheme templates.TplName = "repo/settings/pages_theme"
)
// Pages shows the repository pages settings
// getPagesLandingConfig loads the landing page configuration
func getPagesLandingConfig(ctx *context.Context) *pages_module.LandingConfig {
config, err := pages_service.GetPagesConfig(ctx, ctx.Repo.Repository)
if err != nil {
// Return default config
return &pages_module.LandingConfig{
Enabled: false,
Template: "open-source-hero",
Brand: pages_module.BrandConfig{Name: ctx.Repo.Repository.Name},
Hero: pages_module.HeroConfig{
Headline: ctx.Repo.Repository.Name,
Subheadline: ctx.Repo.Repository.Description,
},
Theme: pages_module.ThemeConfig{Mode: "auto"},
}
}
return config
}
// savePagesLandingConfig saves the landing page configuration
func savePagesLandingConfig(ctx *context.Context, config *pages_module.LandingConfig) error {
configJSON, err := json.Marshal(config)
if err != nil {
return err
}
dbConfig, err := repo_model.GetPagesConfigByRepoID(ctx, ctx.Repo.Repository.ID)
if err != nil {
if repo_model.IsErrPagesConfigNotExist(err) {
// Create new config
dbConfig = &repo_model.PagesConfig{
RepoID: ctx.Repo.Repository.ID,
Enabled: config.Enabled,
Template: repo_model.PagesTemplate(config.Template),
ConfigJSON: string(configJSON),
}
return repo_model.CreatePagesConfig(ctx, dbConfig)
}
return err
}
// Update existing config
dbConfig.Enabled = config.Enabled
dbConfig.Template = repo_model.PagesTemplate(config.Template)
dbConfig.ConfigJSON = string(configJSON)
return repo_model.UpdatePagesConfig(ctx, dbConfig)
}
// setCommonPagesData sets common data for all pages settings pages
func setCommonPagesData(ctx *context.Context) {
config := getPagesLandingConfig(ctx)
ctx.Data["Config"] = config
ctx.Data["PagesEnabled"] = config.Enabled
ctx.Data["PagesSubdomain"] = pages_service.GetPagesSubdomain(ctx.Repo.Repository)
ctx.Data["PagesURL"] = pages_service.GetPagesURL(ctx.Repo.Repository)
ctx.Data["PagesTemplates"] = pages_module.ValidTemplates()
ctx.Data["PagesTemplateNames"] = pages_module.TemplateDisplayNames()
}
// Pages shows the repository pages settings (General page)
func Pages(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.pages")
ctx.Data["PageIsSettingsPages"] = true
ctx.Data["PageIsSettingsPagesGeneral"] = true
setCommonPagesData(ctx)
// Get pages config
config, err := repo_model.GetPagesConfig(ctx, ctx.Repo.Repository.ID)
config, err := repo_model.GetPagesConfigByRepoID(ctx, ctx.Repo.Repository.ID)
if err != nil && !repo_model.IsErrPagesConfigNotExist(err) {
ctx.ServerError("GetPagesConfig", err)
return
@@ -39,53 +114,45 @@ func Pages(ctx *context.Context) {
}
ctx.Data["PagesDomains"] = domains
// Generate subdomain
ctx.Data["PagesSubdomain"] = pages_service.GetPagesSubdomain(ctx.Repo.Repository)
ctx.Data["PagesURL"] = pages_service.GetPagesURL(ctx.Repo.Repository)
// Available templates
ctx.Data["PagesTemplates"] = []string{"simple", "documentation", "product", "portfolio"}
ctx.HTML(http.StatusOK, tplRepoSettingsPages)
}
// PagesPost handles the pages settings form submission
func PagesPost(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.pages")
ctx.Data["PageIsSettingsPages"] = true
action := ctx.FormString("action")
switch action {
case "enable":
template := ctx.FormString("template")
if template == "" {
template = "simple"
if template == "" || !pages_module.IsValidTemplate(template) {
template = "open-source-hero"
}
if err := pages_service.EnablePages(ctx, ctx.Repo.Repository, template); err != nil {
config := getPagesLandingConfig(ctx)
config.Enabled = true
config.Template = template
if err := savePagesLandingConfig(ctx, config); err != nil {
ctx.ServerError("EnablePages", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.pages.enabled_success"))
case "disable":
if err := pages_service.DisablePages(ctx, ctx.Repo.Repository); err != nil {
config := getPagesLandingConfig(ctx)
config.Enabled = false
if err := savePagesLandingConfig(ctx, config); err != nil {
ctx.ServerError("DisablePages", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.pages.disabled_success"))
case "update_template":
template := ctx.FormString("template")
if template == "" {
template = "simple"
if template == "" || !pages_module.IsValidTemplate(template) {
template = "open-source-hero"
}
if err := pages_service.EnablePages(ctx, ctx.Repo.Repository, template); err != nil {
ctx.ServerError("EnablePages", err)
config := getPagesLandingConfig(ctx)
config.Template = template
if err := savePagesLandingConfig(ctx, config); err != nil {
ctx.ServerError("UpdateTemplate", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
case "add_domain":
domain := ctx.FormString("domain")
if domain == "" {
@@ -105,7 +172,6 @@ func PagesPost(ctx *context.Context) {
} else {
ctx.Flash.Success(ctx.Tr("repo.settings.pages.domain_added"))
}
case "delete_domain":
domainID := ctx.FormInt64("domain_id")
if err := repo_model.DeletePagesDomain(ctx, domainID); err != nil {
@@ -113,7 +179,6 @@ func PagesPost(ctx *context.Context) {
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.pages.domain_deleted"))
case "activate_ssl":
domainID := ctx.FormInt64("domain_id")
if err := repo_model.ActivatePagesDomainSSL(ctx, domainID); err != nil {
@@ -121,24 +186,264 @@ func PagesPost(ctx *context.Context) {
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.pages.ssl_activated"))
case "verify_domain":
domainID := ctx.FormInt64("domain_id")
if err := pages_service.VerifyDomain(ctx, domainID); err != nil {
if err.Error() == "DNS verification failed" {
ctx.Flash.Error(ctx.Tr("repo.settings.pages.domain_verification_failed"))
} else {
ctx.ServerError("VerifyDomain", err)
return
}
ctx.Flash.Error(ctx.Tr("repo.settings.pages.domain_verification_failed"))
} else {
ctx.Flash.Success(ctx.Tr("repo.settings.pages.domain_verified"))
}
default:
ctx.NotFound(nil)
return
}
ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/pages")
}
func PagesBrand(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.pages.brand")
ctx.Data["PageIsSettingsPages"] = true
ctx.Data["PageIsSettingsPagesBrand"] = true
setCommonPagesData(ctx)
ctx.HTML(http.StatusOK, tplRepoSettingsPagesBrand)
}
func PagesBrandPost(ctx *context.Context) {
config := getPagesLandingConfig(ctx)
config.Brand.Name = ctx.FormString("brand_name")
config.Brand.LogoURL = ctx.FormString("brand_logo_url")
config.Brand.Tagline = ctx.FormString("brand_tagline")
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/brand")
}
func PagesHero(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.pages.hero")
ctx.Data["PageIsSettingsPages"] = true
ctx.Data["PageIsSettingsPagesHero"] = true
setCommonPagesData(ctx)
ctx.HTML(http.StatusOK, tplRepoSettingsPagesHero)
}
func PagesHeroPost(ctx *context.Context) {
config := getPagesLandingConfig(ctx)
config.Hero.Headline = ctx.FormString("headline")
config.Hero.Subheadline = ctx.FormString("subheadline")
config.Hero.ImageURL = ctx.FormString("image_url")
config.Hero.VideoURL = ctx.FormString("video_url")
config.Hero.CodeExample = ctx.FormString("code_example")
config.Hero.PrimaryCTA.Label = ctx.FormString("primary_cta_label")
config.Hero.PrimaryCTA.URL = ctx.FormString("primary_cta_url")
config.Hero.PrimaryCTA.Variant = ctx.FormString("primary_cta_variant")
config.Hero.SecondaryCTA.Label = ctx.FormString("secondary_cta_label")
config.Hero.SecondaryCTA.URL = ctx.FormString("secondary_cta_url")
config.Hero.SecondaryCTA.Variant = ctx.FormString("secondary_cta_variant")
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/hero")
}
func PagesContent(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.pages.content")
ctx.Data["PageIsSettingsPages"] = true
ctx.Data["PageIsSettingsPagesContent"] = true
setCommonPagesData(ctx)
ctx.HTML(http.StatusOK, tplRepoSettingsPagesContent)
}
func PagesContentPost(ctx *context.Context) {
config := getPagesLandingConfig(ctx)
config.Stats = nil
for i := range 10 {
value := ctx.FormString(fmt.Sprintf("stat_value_%d", i))
label := ctx.FormString(fmt.Sprintf("stat_label_%d", i))
if value == "" && label == "" {
continue
}
config.Stats = append(config.Stats, pages_module.StatConfig{Value: value, Label: label})
}
config.ValueProps = nil
for i := range 10 {
title := ctx.FormString(fmt.Sprintf("valueprop_title_%d", i))
desc := ctx.FormString(fmt.Sprintf("valueprop_desc_%d", i))
icon := ctx.FormString(fmt.Sprintf("valueprop_icon_%d", i))
if title == "" && desc == "" {
continue
}
config.ValueProps = append(config.ValueProps, pages_module.ValuePropConfig{Title: title, Description: desc, Icon: icon})
}
config.Features = nil
for i := range 20 {
title := ctx.FormString(fmt.Sprintf("feature_title_%d", i))
desc := ctx.FormString(fmt.Sprintf("feature_desc_%d", i))
icon := ctx.FormString(fmt.Sprintf("feature_icon_%d", i))
imageURL := ctx.FormString(fmt.Sprintf("feature_image_%d", i))
if title == "" && desc == "" {
continue
}
config.Features = append(config.Features, pages_module.FeatureConfig{Title: title, Description: desc, Icon: icon, ImageURL: imageURL})
}
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/content")
}
func PagesSocial(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.pages.social")
ctx.Data["PageIsSettingsPages"] = true
ctx.Data["PageIsSettingsPagesSocial"] = true
setCommonPagesData(ctx)
ctx.HTML(http.StatusOK, tplRepoSettingsPagesSocial)
}
func PagesSocialPost(ctx *context.Context) {
config := getPagesLandingConfig(ctx)
config.SocialProof.Logos = nil
for i := range 20 {
logo := ctx.FormString(fmt.Sprintf("logo_%d", i))
if logo == "" {
continue
}
config.SocialProof.Logos = append(config.SocialProof.Logos, logo)
}
config.SocialProof.Testimonials = nil
for i := range 10 {
quote := ctx.FormString(fmt.Sprintf("testimonial_quote_%d", i))
author := ctx.FormString(fmt.Sprintf("testimonial_author_%d", i))
role := ctx.FormString(fmt.Sprintf("testimonial_role_%d", i))
avatar := ctx.FormString(fmt.Sprintf("testimonial_avatar_%d", i))
if quote == "" && author == "" {
continue
}
config.SocialProof.Testimonials = append(config.SocialProof.Testimonials, pages_module.TestimonialConfig{Quote: quote, Author: author, Role: role, Avatar: avatar})
}
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/social")
}
func PagesPricing(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.pages.pricing")
ctx.Data["PageIsSettingsPages"] = true
ctx.Data["PageIsSettingsPagesPricing"] = true
setCommonPagesData(ctx)
ctx.HTML(http.StatusOK, tplRepoSettingsPagesPricing)
}
func PagesPricingPost(ctx *context.Context) {
config := getPagesLandingConfig(ctx)
config.Pricing.Headline = ctx.FormString("pricing_headline")
config.Pricing.Subheadline = ctx.FormString("pricing_subheadline")
config.Pricing.Plans = nil
for i := range 5 {
name := ctx.FormString(fmt.Sprintf("plan_name_%d", i))
price := ctx.FormString(fmt.Sprintf("plan_price_%d", i))
if name == "" && price == "" {
continue
}
featuresText := ctx.FormString(fmt.Sprintf("plan_%d_features", i))
var features []string
for f := range strings.SplitSeq(featuresText, "\n") {
f = strings.TrimSpace(f)
if f != "" {
features = append(features, f)
}
}
config.Pricing.Plans = append(config.Pricing.Plans, pages_module.PricingPlanConfig{
Name: name, Price: price, Period: ctx.FormString(fmt.Sprintf("plan_period_%d", i)),
Features: features, CTA: ctx.FormString(fmt.Sprintf("plan_cta_%d", i)),
Featured: ctx.FormBool(fmt.Sprintf("plan_featured_%d", i)),
})
}
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/pricing")
}
func PagesFooter(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.pages.footer")
ctx.Data["PageIsSettingsPages"] = true
ctx.Data["PageIsSettingsPagesFooter"] = true
setCommonPagesData(ctx)
ctx.HTML(http.StatusOK, tplRepoSettingsPagesFooter)
}
func PagesFooterPost(ctx *context.Context) {
config := getPagesLandingConfig(ctx)
config.CTASection.Headline = ctx.FormString("cta_headline")
config.CTASection.Subheadline = ctx.FormString("cta_subheadline")
config.CTASection.Button.Label = ctx.FormString("cta_button_label")
config.CTASection.Button.URL = ctx.FormString("cta_button_url")
config.CTASection.Button.Variant = ctx.FormString("cta_button_variant")
config.Footer.Copyright = ctx.FormString("footer_copyright")
config.Footer.Links = nil
for i := range 10 {
label := ctx.FormString(fmt.Sprintf("footer_link_label_%d", i))
url := ctx.FormString(fmt.Sprintf("footer_link_url_%d", i))
if label == "" && url == "" {
continue
}
config.Footer.Links = append(config.Footer.Links, pages_module.FooterLink{Label: label, URL: url})
}
config.Footer.Social = nil
for i := range 10 {
platform := ctx.FormString(fmt.Sprintf("social_platform_%d", i))
url := ctx.FormString(fmt.Sprintf("social_url_%d", i))
if platform == "" && url == "" {
continue
}
config.Footer.Social = append(config.Footer.Social, pages_module.SocialLink{Platform: platform, URL: url})
}
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/footer")
}
func PagesTheme(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.pages.theme")
ctx.Data["PageIsSettingsPages"] = true
ctx.Data["PageIsSettingsPagesTheme"] = true
setCommonPagesData(ctx)
ctx.HTML(http.StatusOK, tplRepoSettingsPagesTheme)
}
func PagesThemePost(ctx *context.Context) {
config := getPagesLandingConfig(ctx)
config.Theme.PrimaryColor = ctx.FormString("primary_color")
config.Theme.AccentColor = ctx.FormString("accent_color")
config.Theme.Mode = ctx.FormString("theme_mode")
config.SEO.Title = ctx.FormString("seo_title")
config.SEO.Description = ctx.FormString("seo_description")
keywords := ctx.FormString("seo_keywords")
if keywords != "" {
config.SEO.Keywords = strings.Split(keywords, ",")
} else {
config.SEO.Keywords = nil
}
config.SEO.OGImage = ctx.FormString("og_image")
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/theme")
}

View File

@@ -1214,7 +1214,16 @@ func registerWebRoutes(m *web.Router) {
m.Post("/{lid}/unlock", repo_setting.LFSUnlock)
})
})
m.Combo("/pages").Get(repo_setting.Pages).Post(repo_setting.PagesPost)
m.Group("/pages", func() {
m.Combo("").Get(repo_setting.Pages).Post(repo_setting.PagesPost)
m.Combo("/brand").Get(repo_setting.PagesBrand).Post(repo_setting.PagesBrandPost)
m.Combo("/hero").Get(repo_setting.PagesHero).Post(repo_setting.PagesHeroPost)
m.Combo("/content").Get(repo_setting.PagesContent).Post(repo_setting.PagesContentPost)
m.Combo("/social").Get(repo_setting.PagesSocial).Post(repo_setting.PagesSocialPost)
m.Combo("/pricing").Get(repo_setting.PagesPricing).Post(repo_setting.PagesPricingPost)
m.Combo("/footer").Get(repo_setting.PagesFooter).Post(repo_setting.PagesFooterPost)
m.Combo("/theme").Get(repo_setting.PagesTheme).Post(repo_setting.PagesThemePost)
})
m.Group("/actions/general", func() {
m.Get("", repo_setting.ActionsGeneralSettings)
m.Post("/actions_unit", repo_setting.ActionsUnitPost)

View File

@@ -19,6 +19,7 @@ import (
git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization"
packages_model "code.gitea.io/gitea/models/packages"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
unit_model "code.gitea.io/gitea/models/unit"
@@ -602,6 +603,16 @@ func RepoAssignment(ctx *Context) {
ctx.Data["CodeIndexerUnavailable"] = !code_indexer.IsAvailable(ctx)
}
// Check if wiki has content (for non-member visibility)
hasWiki, _ := gitrepo.IsRepositoryExist(ctx, repo.WikiStorageRepo())
ctx.Data["HasWiki"] = hasWiki
// Check if there are any packages (for non-member visibility)
numPackages, _ := packages_model.CountVersions(ctx, &packages_model.PackageSearchOptions{
RepoID: repo.ID,
})
ctx.Data["HasPackages"] = numPackages > 0
if ctx.IsSigned {
ctx.Data["IsWatchingRepo"] = repo_model.IsWatching(ctx, ctx.Doer.ID, repo.ID)
ctx.Data["IsStaringRepo"] = repo_model.IsStaring(ctx, ctx.Doer.ID, repo.ID)

View File

@@ -100,18 +100,21 @@ func GetPagesConfig(ctx context.Context, repo *repo_model.Repository) (*pages_mo
// getDefaultConfig returns a default landing page configuration
func getDefaultConfig(repo *repo_model.Repository, template string) *pages_module.LandingConfig {
if template == "" {
template = "simple"
if template == "" || !pages_module.IsValidTemplate(template) {
template = "open-source-hero"
}
return &pages_module.LandingConfig{
Enabled: true,
Template: template,
Hero: pages_module.HeroConfig{
Title: repo.Name,
Tagline: repo.Description,
Brand: pages_module.BrandConfig{
Name: repo.Name,
},
Branding: pages_module.BrandingConfig{
PrimaryColor: "#4183c4",
Hero: pages_module.HeroConfig{
Headline: repo.Name,
Subheadline: repo.Description,
},
Theme: pages_module.ThemeConfig{
Mode: "auto",
},
}
}
@@ -182,7 +185,7 @@ func IsPagesEnabled(ctx context.Context, repo *repo_model.Repository) (bool, err
// EnablePages enables pages for a repository with default config
func EnablePages(ctx context.Context, repo *repo_model.Repository, template string) error {
if !pages_module.IsValidTemplate(template) {
template = "simple"
template = "open-source-hero"
}
return repo_model.EnablePages(ctx, repo.ID, repo_model.PagesTemplate(template))

View File

@@ -1,10 +1,12 @@
import {fileURLToPath} from 'node:url';
import path from 'node:path';
import type {Config} from 'stylelint';
// Use import.meta.dirname for reliable path resolution in ESM context
// This gives us the directory where this config file lives (project root)
const cssVarFiles = [
fileURLToPath(new URL('web_src/css/base.css', import.meta.url)),
fileURLToPath(new URL('web_src/css/themes/theme-gitea-light.css', import.meta.url)),
fileURLToPath(new URL('web_src/css/themes/theme-gitea-dark.css', import.meta.url)),
path.join(import.meta.dirname, 'web_src/css/base.css'),
path.join(import.meta.dirname, 'web_src/css/themes/theme-gitea-light.css'),
path.join(import.meta.dirname, 'web_src/css/themes/theme-gitea-dark.css'),
];
export default {
@@ -133,9 +135,9 @@ export default {
'media-feature-name-no-vendor-prefix': true,
'no-descending-specificity': null,
'no-invalid-position-at-import-rule': [true, {ignoreAtRules: ['tailwind']}],
'no-unknown-animations': null, // disabled until stylelint supports multi-file linting
'no-unknown-custom-media': null, // disabled until stylelint supports multi-file linting
'no-unknown-custom-properties': null, // disabled until stylelint supports multi-file linting
'no-unknown-animations': null,
'no-unknown-custom-media': null,
'no-unknown-custom-properties': null,
'plugin/declaration-block-no-ignored-properties': true,
'scale-unlimited/declaration-strict-value': [['/color$/', 'font-weight'], {ignoreValues: '/^(inherit|transparent|unset|initial|currentcolor|none)$/', ignoreFunctions: true, disableFix: true, expandShorthand: true}],
'selector-attribute-quotes': 'always',

View File

@@ -10,6 +10,26 @@
</div>
</dd>
<div class="divider"></div>
<dt>{{ctx.Locale.Tr "admin.config.hide_explore_users"}}</dt>
<dd>
<div class="ui toggle checkbox" data-tooltip-content="{{ctx.Locale.Tr "admin.config.hide_explore_users_desc"}}">
<input type="checkbox" data-config-dyn-key="theme.hide_explore_users" {{if .SystemConfig.Theme.HideExploreUsers.Value ctx}}checked{{end}}><label></label>
</div>
</dd>
<div class="divider"></div>
<dt>{{ctx.Locale.Tr "admin.config.help_url"}}</dt>
<dd>
<form class="ui form form-fetch-action" method="post" action="{{AppSubUrl}}/-/admin/config">
{{.CsrfTokenHtml}}
<input type="hidden" name="key" value="theme.help_url">
<div class="field">
<input type="text" name="value" value="{{.SystemConfig.Theme.HelpURL.Value ctx}}" placeholder="{{ctx.Locale.Tr "admin.config.help_url_placeholder"}}">
</div>
<div class="help">{{ctx.Locale.Tr "admin.config.help_url_help"}}</div>
<button class="ui primary button">{{ctx.Locale.Tr "save"}}</button>
</form>
</dd>
<div class="divider"></div>
<dt>{{ctx.Locale.Tr "admin.config.custom_site_icon"}}</dt>
<dd>
<form class="ui form" method="post" action="{{AppSubUrl}}/-/admin/config/theme/icon" enctype="multipart/form-data">
@@ -95,6 +115,7 @@
<input type="hidden" name="key" value="theme.pinned_org_display_format">
<div class="field">
<select class="ui dropdown" name="value">
<option value="promotional" {{if eq (.SystemConfig.Theme.PinnedOrgDisplayFormat.Value ctx) "promotional"}}selected{{end}}>{{ctx.Locale.Tr "admin.config.pinned_org_format_promotional"}}</option>
<option value="condensed" {{if eq (.SystemConfig.Theme.PinnedOrgDisplayFormat.Value ctx) "condensed"}}selected{{end}}>{{ctx.Locale.Tr "admin.config.pinned_org_format_condensed"}}</option>
<option value="regular" {{if eq (.SystemConfig.Theme.PinnedOrgDisplayFormat.Value ctx) "regular"}}selected{{end}}>{{ctx.Locale.Tr "admin.config.pinned_org_format_regular"}}</option>
</select>

View File

@@ -46,7 +46,7 @@
{{template "custom/extra_links" .}}
{{if not .IsSigned}}
<a class="item" target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com">{{ctx.Locale.Tr "help"}}</a>
{{if .SystemConfig.Theme.HelpURL.Value ctx}}<a class="item" target="_blank" rel="noopener noreferrer" href="{{.SystemConfig.Theme.HelpURL.Value ctx}}">{{ctx.Locale.Tr "help"}}</a>{{end}}
{{end}}
</div>
@@ -126,10 +126,10 @@
{{svg "octicon-tools"}}
{{ctx.Locale.Tr "your_settings"}}
</a>
<a class="item" target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com">
{{if .SystemConfig.Theme.HelpURL.Value ctx}}<a class="item" target="_blank" rel="noopener noreferrer" href="{{.SystemConfig.Theme.HelpURL.Value ctx}}">
{{svg "octicon-question"}}
{{ctx.Locale.Tr "help"}}
</a>
</a>{{end}}
{{if .IsAdmin}}
<div class="divider"></div>
<a class="{{if .PageIsAdmin}}active {{end}}item" href="{{AppSubUrl}}/-/admin">

View File

@@ -30,7 +30,46 @@
</div>
</div>
</div>
{{if .PinnedOrganizations}}
{{if eq (.SystemConfig.Theme.PinnedOrgDisplayFormat.Value ctx) "promotional"}}
{{/* Promotional format: show marketing content */}}
<div class="ui stackable middle very relaxed page grid">
<div class="eight wide center column">
<h1 class="hero ui icon header">
{{svg "octicon-flame"}} {{ctx.Locale.Tr "startpage.install"}}
</h1>
<p class="large tw-text-balance">
{{ctx.Locale.Tr "startpage.install_desc"}}
</p>
</div>
<div class="eight wide center column">
<h1 class="hero ui icon header">
{{svg "octicon-dependabot"}} {{ctx.Locale.Tr "startpage.platform"}}
</h1>
<p class="large tw-text-balance">
{{ctx.Locale.Tr "startpage.platform_desc"}}
</p>
</div>
</div>
<div class="ui stackable middle very relaxed page grid">
<div class="eight wide center column">
<h1 class="hero ui icon header">
{{svg "octicon-rocket"}} {{ctx.Locale.Tr "startpage.lightweight"}}
</h1>
<p class="large tw-text-balance">
{{ctx.Locale.Tr "startpage.lightweight_desc"}}
</p>
</div>
<div class="eight wide center column">
<h1 class="hero ui icon header">
{{svg "octicon-code"}} {{ctx.Locale.Tr "startpage.license"}}
</h1>
<p class="large tw-text-balance">
{{ctx.Locale.Tr "startpage.license_desc"}}
</p>
</div>
</div>
{{else if .PinnedOrganizations}}
{{/* Show pinned organizations */}}
<div class="ui container tw-my-8">
{{if eq (.SystemConfig.Theme.PinnedOrgDisplayFormat.Value ctx) "regular"}}
{{/* Regular format: icon above, title below, description below that */}}
@@ -71,22 +110,22 @@
{{end}}
</div>
{{else}}
{{/* Only show promotional text when there are no pinned organizations */}}
{{/* No pinned organizations - show promotional content as fallback */}}
<div class="ui stackable middle very relaxed page grid">
<div class="eight wide center column">
<h1 class="hero ui icon header">
{{svg "octicon-flame"}} {{ctx.Locale.Tr "startpage.install"}}
</h1>
<p class="large tw-text-balance">
{{ctx.Locale.Tr "startpage.install_desc" "https://docs.gitea.com/installation/install-from-binary" "https://github.com/go-gitea/gitea/tree/master/docker" "https://docs.gitea.com/installation/install-from-package"}}
{{ctx.Locale.Tr "startpage.install_desc"}}
</p>
</div>
<div class="eight wide center column">
<h1 class="hero ui icon header">
{{svg "octicon-device-desktop"}} {{ctx.Locale.Tr "startpage.platform"}}
{{svg "octicon-dependabot"}} {{ctx.Locale.Tr "startpage.platform"}}
</h1>
<p class="large tw-text-balance">
{{ctx.Locale.Tr "startpage.platform_desc" "https://go.dev/"}}
{{ctx.Locale.Tr "startpage.platform_desc"}}
</p>
</div>
</div>
@@ -104,7 +143,7 @@
{{svg "octicon-code"}} {{ctx.Locale.Tr "startpage.license"}}
</h1>
<p class="large tw-text-balance">
{{ctx.Locale.Tr "startpage.license_desc" "https://code.gitea.io/gitea" "code.gitea.io/gitea" "https://github.com/go-gitea/gitea"}}
{{ctx.Locale.Tr "startpage.license_desc"}}
</p>
</div>
</div>

View File

@@ -12,7 +12,7 @@
{{/* Overview Tab Content */}}
{{if .PageIsViewOverview}}
{{/* Pinned Repositories Section */}}
{{if or .IsOrganizationMember .HasPublicPinnedRepos}}{{/* Pinned Repositories Section */}}
<div class="ui segment pinned-repos-section">
<h4 class="ui header tw-flex tw-items-center">
{{svg "octicon-pin" 16}} {{ctx.Locale.Tr "org.pinned_repos"}}
@@ -28,7 +28,7 @@
{{if .UngroupedPinned}}
<div class="ui three stackable cards pinned-repos">
{{range .UngroupedPinned}}
{{if .Repo}}
{{if and .Repo (or $.IsOrganizationMember (not .Repo.IsPrivate))}}
<a class="ui card" href="{{.Repo.Link}}">
<div class="content tw-text-center">
{{if .Repo.Avatar}}
@@ -73,7 +73,7 @@
</h5>
<div class="ui three stackable cards pinned-repos">
{{range $groupRepos}}
{{if .Repo}}
{{if and .Repo (or $.IsOrganizationMember (not .Repo.IsPrivate))}}
<a class="ui card" href="{{.Repo.Link}}">
<div class="content tw-text-center">
{{if .Repo.Avatar}}
@@ -110,7 +110,7 @@
{{end}}
{{end}}
{{else}}
{{/* Empty state for pinned repos */}}
{{if .IsOrganizationMember}}{{/* Empty state for pinned repos - members only */}}
<div class="ui placeholder segment tw-text-center">
<div class="ui icon header">
{{svg "octicon-pin" 48}}
@@ -129,8 +129,10 @@
</div>
{{end}}
</div>
{{end}}
{{end}}
</div>
{{end}}
{{/* Profile README Empty State */}}
{{if and (not .ProfileReadmeContent) .IsOrganizationOwner}}
@@ -305,14 +307,7 @@
<a href="{{.User.HomeLink}}" title="{{.User.Name}}{{if .User.FullName}} ({{.User.FullName}}){{end}}">{{ctx.AvatarUtils.Avatar .User 48}}</a>
{{end}}
</div>
{{else}}
<h4 class="ui top attached header tw-flex tw-mt-4">
<strong class="tw-flex-1">{{ctx.Locale.Tr "org.public_members"}}</strong>
</h4>
<div class="ui attached segment">
<p class="text grey tw-text-center">{{ctx.Locale.Tr "org.no_public_members"}}</p>
</div>
{{end}}
{{end}}
{{end}}
</div>
</div>

View File

@@ -10,7 +10,7 @@
<div class="ui small label">{{.RepoCount}}</div>
{{end}}
</a>
{{if .CanReadProjects}}
{{if and .CanReadProjects (or .IsOrganizationMember .ProjectCount)}}
<a class="{{if .PageIsViewProjects}}active {{end}}item" href="{{$.Org.HomeLink}}/-/projects">
{{svg "octicon-project-symlink"}} {{ctx.Locale.Tr "user.projects"}}
{{if .ProjectCount}}
@@ -18,7 +18,7 @@
{{end}}
</a>
{{end}}
{{if and .IsPackageEnabled .CanReadPackages}}
{{if and .IsPackageEnabled .CanReadPackages .IsOrganizationMember}}
<a class="{{if .IsPackagesPage}}active {{end}}item" href="{{$.Org.HomeLink}}/-/packages">
{{svg "octicon-package"}} {{ctx.Locale.Tr "packages.title"}}
</a>

View File

@@ -3,15 +3,15 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{if .Config.Hero.Title}}{{.Config.Hero.Title}}{{else}}{{.Repository.Name}}{{end}} - {{.Repository.Owner.Name}}</title>
<meta name="description" content="{{if .Config.Hero.Tagline}}{{.Config.Hero.Tagline}}{{else}}{{.Repository.Description}}{{end}}">
<title>{{if .Config.Hero.Headline}}{{.Config.Hero.Headline}}{{else}}{{.Repository.Name}}{{end}} - {{.Repository.Owner.Name}}</title>
<meta name="description" content="{{if .Config.Hero.Subheadline}}{{.Config.Hero.Subheadline}}{{else}}{{.Repository.Description}}{{end}}">
<link rel="icon" href="{{AssetUrlPrefix}}/img/favicon.svg" type="image/svg+xml">
{{template "base/head_style" .}}
<style>
/* Pages standalone styles - no Gitea navbar */
:root {
--pages-primary: {{if .Config.Branding.PrimaryColor}}{{.Config.Branding.PrimaryColor}}{{else}}#4183c4{{end}};
--pages-secondary: {{if .Config.Branding.SecondaryColor}}{{.Config.Branding.SecondaryColor}}{{else}}#6c757d{{end}};
--pages-primary: {{if .Config.Theme.PrimaryColor}}{{.Config.Theme.PrimaryColor}}{{else}}#4183c4{{end}};
--pages-secondary: {{if .Config.Theme.AccentColor}}{{.Config.Theme.AccentColor}}{{else}}#6c757d{{end}};
}
* { box-sizing: border-box; }
body.pages-body {

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,164 +0,0 @@
{{template "pages/base_head" .}}
<style>
/* Documentation-specific styles */
.pages-documentation {
background: #fff;
}
.pages-documentation .pages-header {
background: #24292e;
border-bottom: none;
}
.pages-documentation .pages-nav-brand,
.pages-documentation .pages-nav-link {
color: #fff;
}
.pages-documentation .pages-nav-link:hover {
color: #79b8ff;
}
.pages-docs-layout {
display: flex;
min-height: calc(100vh - 65px);
}
.pages-docs-sidebar {
width: 280px;
background: #f6f8fa;
border-right: 1px solid #e1e4e8;
padding: 24px;
position: sticky;
top: 65px;
height: calc(100vh - 65px);
overflow-y: auto;
}
.pages-docs-search {
margin-bottom: 24px;
}
.pages-search-input {
width: 100%;
padding: 8px 12px;
border: 1px solid #e1e4e8;
border-radius: 6px;
font-size: 0.875rem;
}
.pages-docs-section {
margin-bottom: 24px;
}
.pages-docs-section-title {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #586069;
margin: 0 0 12px;
}
.pages-docs-list {
list-style: none;
padding: 0;
margin: 0;
}
.pages-docs-list li {
margin-bottom: 8px;
}
.pages-docs-list a {
color: #24292e;
text-decoration: none;
font-size: 0.875rem;
display: block;
padding: 4px 8px;
border-radius: 4px;
}
.pages-docs-list a:hover {
background: #e1e4e8;
color: var(--pages-primary);
}
.pages-docs-content {
flex: 1;
padding: 48px;
max-width: 900px;
}
.pages-docs-article {
line-height: 1.7;
}
.pages-docs-article h1,
.pages-docs-article h2,
.pages-docs-article h3 {
margin-top: 32px;
padding-bottom: 8px;
border-bottom: 1px solid #e1e4e8;
}
.pages-docs-article code {
background: #f6f8fa;
padding: 2px 6px;
border-radius: 4px;
font-size: 0.875em;
}
.pages-docs-article pre {
background: #24292e;
color: #e1e4e8;
padding: 16px;
border-radius: 6px;
overflow-x: auto;
}
.pages-docs-article pre code {
background: none;
padding: 0;
color: inherit;
}
@media (max-width: 768px) {
.pages-docs-sidebar {
display: none;
}
.pages-docs-content {
padding: 24px;
}
}
</style>
<div class="pages-landing pages-documentation">
{{template "pages/header" .}}
<div class="pages-docs-layout">
<aside class="pages-docs-sidebar">
<div class="pages-docs-search">
<input type="text" placeholder="{{ctx.Locale.Tr "search"}}..." class="pages-search-input">
</div>
<nav class="pages-docs-nav">
{{if .Config.Documentation.Sidebar}}
{{range .Config.Documentation.Sidebar}}
<div class="pages-docs-section {{if .Collapsed}}collapsed{{end}}">
{{if .Title}}
<h4 class="pages-docs-section-title">{{.Title}}</h4>
{{end}}
<ul class="pages-docs-list">
{{range .Items}}
<li><a href="/docs/{{.}}">{{.}}</a></li>
{{end}}
</ul>
</div>
{{end}}
{{else}}
<div class="pages-docs-section">
<h4 class="pages-docs-section-title">Documentation</h4>
<ul class="pages-docs-list">
<li><a href="#">Getting Started</a></li>
<li><a href="#">Installation</a></li>
<li><a href="#">Configuration</a></li>
</ul>
</div>
{{end}}
</nav>
</aside>
<main class="pages-docs-content">
<article class="pages-docs-article">
{{if .ReadmeContent}}
<div class="markup">
{{.ReadmeContent}}
</div>
{{else}}
<h1>{{.Repository.Name}}</h1>
<p>{{if .Repository.Description}}{{.Repository.Description}}{{else}}Welcome to the documentation.{{end}}</p>
{{end}}
</article>
</main>
</div>
</div>
{{template "pages/base_footer" .}}

View File

@@ -17,7 +17,7 @@
{{end}}
{{end}}
<a href="{{AppSubUrl}}/{{.Repository.FullName}}" class="ui mini button" target="_blank">
{{svg "octicon-mark-github" 16}} View Source
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy"> View Source
</a>
</div>
</nav>

View File

@@ -0,0 +1,811 @@
{{template "pages/base_head" .}}
<style>
@import url('https://fonts.googleapis.com/css2?family=Source+Serif+4:wght@400;500;600;700&family=Source+Code+Pro:wght@400;500&display=swap');
:root {
--md-bg: #fcfcfc;
--md-text: #171717;
--md-muted: #525252;
--md-light: #737373;
--md-border: #e5e5e5;
--md-accent: {{if .Config.Theme.PrimaryColor}}{{.Config.Theme.PrimaryColor}}{{else}}#171717{{end}};
}
.md-page {
min-height: 100vh;
background: var(--md-bg);
color: var(--md-text);
font-family: 'Source Serif 4', Georgia, serif;
line-height: 1.7;
}
.md-nav {
padding: 24px 48px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid var(--md-border);
}
.md-nav-brand {
font-weight: 700;
font-size: 20px;
color: var(--md-text);
letter-spacing: -0.02em;
text-decoration: none;
}
.md-nav-links {
display: flex;
align-items: center;
gap: 32px;
}
.md-nav-link {
color: var(--md-muted);
text-decoration: none;
font-size: 15px;
transition: color 0.2s ease;
}
.md-nav-link:hover {
color: var(--md-text);
}
.md-btn-text {
display: inline-flex;
align-items: center;
gap: 6px;
color: var(--md-muted);
font-family: 'Source Serif 4', Georgia, serif;
font-weight: 500;
font-size: 16px;
text-decoration: none;
background: none;
border: none;
cursor: pointer;
transition: color 0.2s ease;
}
.md-btn-text:hover {
color: var(--md-text);
}
.md-btn-primary {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 14px 28px;
background: var(--md-text);
color: var(--md-bg);
font-family: 'Source Serif 4', Georgia, serif;
font-weight: 500;
font-size: 16px;
border-radius: 4px;
text-decoration: none;
transition: all 0.2s ease;
}
.md-btn-primary:hover {
background: #404040;
}
.md-hero {
max-width: 720px;
margin: 0 auto;
padding: 120px 24px 80px;
}
.md-hero h1 {
font-size: 52px;
font-weight: 600;
line-height: 1.2;
margin-bottom: 24px;
letter-spacing: -0.02em;
color: var(--md-text);
}
.md-hero-sub {
font-size: 21px;
color: var(--md-muted);
line-height: 1.7;
margin-bottom: 36px;
}
.md-hero-ctas {
display: flex;
gap: 16px;
align-items: center;
margin-bottom: 64px;
}
.md-install-cmd {
display: inline-flex;
align-items: center;
gap: 12px;
padding: 12px 20px;
background: #f5f5f5;
border-radius: 4px;
font-family: 'Source Code Pro', monospace;
font-size: 15px;
color: var(--md-muted);
border: 1px solid var(--md-border);
cursor: pointer;
transition: all 0.2s ease;
}
.md-install-cmd:hover {
border-color: #d4d4d4;
background: #f0f0f0;
}
.md-code-block {
background: #1a1a1a;
border-radius: 6px;
padding: 28px;
font-family: 'Source Code Pro', monospace;
font-size: 14px;
line-height: 1.7;
overflow-x: auto;
position: relative;
}
.md-code-block code {
color: #e5e5e5;
white-space: pre;
}
.md-copy-btn {
position: absolute;
top: 16px;
right: 16px;
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255,255,255,0.05);
border: 1px solid rgba(255,255,255,0.1);
border-radius: 6px;
color: #737373;
cursor: pointer;
transition: all 0.2s ease;
}
.md-copy-btn:hover {
background: rgba(255,255,255,0.1);
color: #e5e5e5;
}
.md-divider {
height: 1px;
background: var(--md-border);
max-width: 720px;
margin: 80px auto;
}
.md-stats {
max-width: 720px;
margin: 0 auto;
padding: 0 24px;
}
.md-stats-inner {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 48px;
text-align: center;
}
.md-stat-value {
font-size: 32px;
font-weight: 600;
color: var(--md-text);
letter-spacing: -0.02em;
}
.md-stat-label {
font-size: 14px;
color: var(--md-light);
margin-top: 4px;
}
.md-section {
max-width: 720px;
margin: 0 auto;
padding: 0 24px 80px;
}
.md-section-title {
font-size: 32px;
font-weight: 600;
margin-bottom: 32px;
letter-spacing: -0.02em;
}
.md-value-item {
display: flex;
align-items: baseline;
gap: 12px;
margin-bottom: 12px;
}
.md-value-bullet {
width: 6px;
height: 6px;
background: var(--md-text);
border-radius: 50%;
flex-shrink: 0;
margin-top: 10px;
}
.md-value-title {
font-weight: 500;
color: var(--md-text);
}
.md-value-desc {
color: var(--md-light);
}
.md-accordion {
border-top: 1px solid var(--md-border);
}
.md-accordion-item {
border-bottom: 1px solid var(--md-border);
padding: 24px 0;
}
.md-accordion-header {
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
background: none;
border: none;
width: 100%;
text-align: left;
font-family: 'Source Serif 4', Georgia, serif;
}
.md-accordion-title {
font-size: 20px;
font-weight: 500;
color: var(--md-text);
transition: color 0.2s ease;
}
.md-accordion-header:hover .md-accordion-title {
color: var(--md-muted);
}
.md-accordion-icon {
width: 24px;
height: 24px;
color: var(--md-light);
transition: transform 0.3s ease;
font-size: 24px;
font-weight: 300;
line-height: 1;
}
.md-accordion-content {
display: none;
padding-top: 16px;
font-size: 17px;
line-height: 1.7;
color: var(--md-muted);
}
.md-accordion-item.open .md-accordion-content {
display: block;
}
.md-pricing {
max-width: 960px;
margin: 0 auto;
padding: 80px 24px;
border-top: 1px solid var(--md-border);
}
.md-pricing-title {
font-size: 32px;
font-weight: 600;
text-align: center;
margin-bottom: 48px;
letter-spacing: -0.02em;
}
.md-pricing-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 24px;
}
.md-pricing-card {
border: 1px solid var(--md-border);
border-radius: 8px;
padding: 32px;
background: white;
position: relative;
}
.md-pricing-card.featured {
border-color: var(--md-text);
box-shadow: 0 4px 24px rgba(0,0,0,0.08);
}
.md-pricing-featured-badge {
position: absolute;
top: -12px;
left: 50%;
transform: translateX(-50%);
background: var(--md-text);
color: white;
font-size: 12px;
font-weight: 500;
padding: 4px 12px;
border-radius: 12px;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.md-pricing-name {
font-size: 18px;
font-weight: 600;
margin-bottom: 8px;
}
.md-pricing-price {
font-size: 36px;
font-weight: 600;
margin-bottom: 4px;
letter-spacing: -0.02em;
}
.md-pricing-period {
font-size: 14px;
color: var(--md-light);
margin-bottom: 24px;
}
.md-pricing-features {
list-style: none;
padding: 0;
margin: 0 0 24px;
}
.md-pricing-features li {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 0;
font-size: 15px;
color: var(--md-muted);
}
.md-pricing-features li::before {
content: "✓";
color: var(--md-text);
font-weight: 600;
}
.md-pricing-cta {
display: block;
width: 100%;
padding: 12px 24px;
text-align: center;
border: 1px solid var(--md-border);
border-radius: 4px;
font-family: 'Source Serif 4', Georgia, serif;
font-weight: 500;
font-size: 15px;
color: var(--md-text);
text-decoration: none;
transition: all 0.2s ease;
}
.md-pricing-cta:hover {
background: var(--md-text);
color: white;
border-color: var(--md-text);
}
.md-pricing-card.featured .md-pricing-cta {
background: var(--md-text);
color: white;
border-color: var(--md-text);
}
.md-pricing-card.featured .md-pricing-cta:hover {
background: #404040;
}
.md-testimonial-section {
border-top: 1px solid var(--md-border);
border-bottom: 1px solid var(--md-border);
padding: 80px 0;
margin: 80px 0;
}
.md-testimonial-inner {
max-width: 720px;
margin: 0 auto;
padding: 0 24px;
}
.md-testimonial-quote {
font-size: 28px;
line-height: 1.5;
color: var(--md-text);
margin-bottom: 24px;
font-style: italic;
}
.md-testimonial-author {
font-weight: 500;
color: var(--md-muted);
}
.md-testimonial-role {
color: var(--md-light);
}
.md-used-by {
max-width: 720px;
margin: 0 auto;
padding: 0 24px 80px;
}
.md-used-by-label {
font-size: 14px;
color: #a3a3a3;
margin-bottom: 20px;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.md-used-by-logos {
display: flex;
gap: 32px;
flex-wrap: wrap;
}
.md-used-by-item {
font-size: 15px;
color: #a3a3a3;
font-weight: 500;
}
.md-cta-section {
max-width: 720px;
margin: 0 auto;
padding: 80px 24px;
text-align: center;
border-top: 1px solid var(--md-border);
}
.md-cta-section h2 {
font-size: 36px;
font-weight: 600;
margin-bottom: 24px;
letter-spacing: -0.02em;
}
.md-footer {
padding: 32px 48px;
border-top: 1px solid var(--md-border);
display: flex;
justify-content: space-between;
align-items: center;
}
.md-footer-copyright {
font-size: 14px;
color: #a3a3a3;
}
.md-footer-links {
display: flex;
gap: 24px;
}
.md-footer-link {
color: var(--md-light);
text-decoration: none;
font-size: 14px;
transition: color 0.2s ease;
}
.md-footer-link:hover {
color: var(--md-text);
}
.md-footer-social {
display: flex;
gap: 16px;
}
.md-footer-social a {
color: var(--md-light);
transition: color 0.2s ease;
}
.md-footer-social a:hover {
color: var(--md-text);
}
@media (max-width: 768px) {
.md-nav { padding: 16px 24px; }
.md-nav-links { gap: 16px; }
.md-hero { padding: 80px 24px 60px; }
.md-hero h1 { font-size: 36px; }
.md-hero-sub { font-size: 18px; }
.md-hero-ctas { flex-direction: column; align-items: flex-start; }
.md-stats-inner { grid-template-columns: 1fr; gap: 24px; }
.md-section-title { font-size: 28px; }
.md-testimonial-quote { font-size: 22px; }
.md-cta-section h2 { font-size: 28px; }
.md-footer { flex-direction: column; gap: 16px; text-align: center; padding: 24px; }
}
</style>
<div class="md-page">
<!-- Navigation -->
<nav class="md-nav">
<a href="/" class="md-nav-brand">{{if .Config.Brand.Name}}{{.Config.Brand.Name}}{{else}}{{.Repository.Name}}{{end}}</a>
<div class="md-nav-links">
{{if .Config.Footer.Links}}
{{range .Config.Footer.Links}}
<a href="{{.URL}}" class="md-nav-link">{{.Label}}</a>
{{end}}
{{else}}
<a href="{{.Repository.Link}}/wiki" class="md-nav-link">Docs</a>
<a href="{{.Repository.Link}}" class="md-nav-link">API</a>
{{end}}
{{if .Config.ValueProps}}<a href="#why" class="md-nav-link">Why</a>{{end}}
{{if .Config.Features}}<a href="#features" class="md-nav-link">Features</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="#pricing" class="md-nav-link">Pricing</a>{{end}}
<a href="{{.Repository.Link}}" class="md-btn-text">
<img src="/assets/img/gitcaddy-icon.svg" width="18" height="18" alt="GitCaddy">
Repository
</a>
</div>
</nav>
<!-- Hero Section (Left Aligned) -->
<section class="md-hero">
<h1>{{if .Config.Hero.Headline}}{{.Config.Hero.Headline}}{{else}}{{.Repository.Name}}{{end}}</h1>
<p class="md-hero-sub">{{if .Config.Hero.Subheadline}}{{.Config.Hero.Subheadline}}{{else}}{{.Repository.Description}}{{end}}</p>
<div class="md-hero-ctas">
{{if .Config.Hero.PrimaryCTA.Label}}
<a href="{{.Config.Hero.PrimaryCTA.URL}}" class="md-btn-primary">
{{.Config.Hero.PrimaryCTA.Label}}
{{svg "octicon-arrow-right" 16}}
</a>
{{else}}
<a href="{{.Repository.Link}}" class="md-btn-primary">
Get Started
{{svg "octicon-arrow-right" 16}}
</a>
{{end}}
</div>
{{if .Config.Hero.CodeExample}}
<div class="md-code-block">
<button class="md-copy-btn" onclick="navigator.clipboard.writeText(document.getElementById('code-example').textContent)">
{{svg "octicon-copy" 16}}
</button>
<code id="code-example">{{.Config.Hero.CodeExample}}</code>
</div>
{{end}}
</section>
{{if or .Config.Stats (gt .NumStars 0)}}
<div class="md-divider"></div>
<!-- Stats -->
<section class="md-stats">
<div class="md-stats-inner">
{{if .Config.Stats}}
{{range .Config.Stats}}
<div>
<div class="md-stat-value">{{.Value}}</div>
<div class="md-stat-label">{{.Label}}</div>
</div>
{{end}}
{{else}}
<div>
<div class="md-stat-value">{{.NumStars}}</div>
<div class="md-stat-label">Stars</div>
</div>
<div>
<div class="md-stat-value">{{.NumForks}}</div>
<div class="md-stat-label">Forks</div>
</div>
{{if .LatestRelease}}
<div>
<div class="md-stat-value">v{{.LatestRelease.TagName}}</div>
<div class="md-stat-label">Latest</div>
</div>
{{end}}
{{end}}
</div>
</section>
<div class="md-divider"></div>
{{end}}
<!-- Value Props (Bullet Points) -->
{{if .Config.ValueProps}}
<section class="md-section" id="why">
<h2 class="md-section-title">{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}{{else}}Why Choose Us{{end}}</h2>
{{range .Config.ValueProps}}
<div class="md-value-item">
<div class="md-value-bullet"></div>
<div>
<span class="md-value-title">{{.Title}}</span>
<span class="md-value-desc"> — {{.Description}}</span>
</div>
</div>
{{end}}
</section>
{{end}}
<!-- Features Accordion (+/-) -->
{{if .Config.Features}}
<section class="md-section" id="features">
<h2 class="md-section-title">Features</h2>
<div class="md-accordion">
{{range $i, $f := .Config.Features}}
<div class="md-accordion-item" data-index="{{$i}}">
<button class="md-accordion-header" onclick="toggleAccordion(this)">
<span class="md-accordion-title">{{$f.Title}}</span>
<span class="md-accordion-icon">+</span>
</button>
<div class="md-accordion-content">
{{$f.Description}}
</div>
</div>
{{end}}
</div>
</section>
{{end}}
<!-- Pricing Section -->
{{if .Config.Pricing.Plans}}
<section class="md-pricing" id="pricing">
<h2 class="md-pricing-title">{{if .Config.Pricing.Headline}}{{.Config.Pricing.Headline}}{{else}}Pricing{{end}}</h2>
<div class="md-pricing-grid">
{{range .Config.Pricing.Plans}}
<div class="md-pricing-card{{if .Featured}} featured{{end}}">
{{if .Featured}}<span class="md-pricing-featured-badge">Popular</span>{{end}}
<div class="md-pricing-name">{{.Name}}</div>
<div class="md-pricing-price">{{.Price}}</div>
<div class="md-pricing-period">{{.Period}}</div>
{{if .Features}}
<ul class="md-pricing-features">
{{range .Features}}
<li>{{.}}</li>
{{end}}
</ul>
{{end}}
<a href="#" class="md-pricing-cta">{{if .CTA}}{{.CTA}}{{else}}Get Started{{end}}</a>
</div>
{{end}}
</div>
</section>
{{end}}
<!-- Testimonial -->
{{if .Config.SocialProof.Testimonials}}
<section class="md-testimonial-section">
<div class="md-testimonial-inner">
<div class="md-testimonials-container">
{{range .Config.SocialProof.Testimonials}}
<div class="md-testimonial-item" style="display: none;">
<blockquote class="md-testimonial-quote">"{{.Quote}}"</blockquote>
<div>
<span class="md-testimonial-author">{{.Author}}</span>
<span class="md-testimonial-role"> · {{.Role}}</span>
</div>
</div>
{{end}}
</div>
</div>
</section>
<script>
(function() {
var items = document.querySelectorAll(".md-testimonial-item");
if (items.length > 0) {
var idx = Math.floor(Math.random() * items.length);
items[idx].style.display = "block";
}
})();
</script>
{{end}}
<!-- Used By -->
{{if .Config.SocialProof.Logos}}
<section class="md-used-by">
<p class="md-used-by-label">Used by</p>
<div class="md-used-by-logos">
{{range .Config.SocialProof.Logos}}
<span class="md-used-by-item">{{.}}</span>
{{end}}
</div>
</section>
{{end}}
<!-- CTA Section -->
{{if .Config.CTASection.Headline}}
<section class="md-cta-section">
<h2>{{.Config.CTASection.Headline}}</h2>
{{if .Config.CTASection.Subheadline}}
<button class="md-install-cmd" style="margin-bottom: 32px; font-size: 18px; padding: 16px 28px;" onclick="navigator.clipboard.writeText('{{.Config.CTASection.Subheadline}}')">
<span style="color: #a3a3a3;">$</span>
{{.Config.CTASection.Subheadline}}
{{svg "octicon-copy" 14}}
</button>
{{end}}
{{if .Config.CTASection.Button.Label}}
<div>
<a href="{{.Config.CTASection.Button.URL}}" class="md-btn-primary">
{{.Config.CTASection.Button.Label}}
{{svg "octicon-arrow-right" 16}}
</a>
</div>
{{end}}
</section>
{{end}}
<!-- Footer -->
<footer class="md-footer">
<span class="md-footer-copyright">{{if .Config.Footer.Copyright}}{{.Config.Footer.Copyright}}{{else}}&copy; <script>document.write(new Date().getFullYear())</script> {{if .Config.Brand.Name}}{{.Config.Brand.Name}}{{else}}{{.Repository.Name}}{{end}}{{end}}</span>
{{if .Config.Footer.Social}}
<div class="md-footer-social">
{{range .Config.Footer.Social}}
<a href="{{.URL}}" title="{{.Platform}}">
{{if eq .Platform "twitter"}}{{svg "octicon-mention" 18}}
{{else if eq .Platform "github"}}{{svg "octicon-mark-github" 18}}
{{else if eq .Platform "discord"}}{{svg "octicon-comment-discussion" 18}}
{{else if eq .Platform "linkedin"}}{{svg "octicon-briefcase" 18}}
{{else if eq .Platform "youtube"}}{{svg "octicon-video" 18}}
{{else}}{{svg "octicon-link-external" 18}}{{end}}
</a>
{{end}}
</div>
{{end}}
<div class="md-footer-links">
{{if .Config.Footer.Links}}
{{range .Config.Footer.Links}}
<a href="{{.URL}}" class="md-footer-link">{{.Label}}</a>
{{end}}
{{else}}
<a href="{{.Repository.Link}}" class="md-footer-link">Repository</a>
<a href="{{.Repository.Link}}/wiki" class="md-footer-link">Documentation</a>
{{end}}
</div>
</footer>
</div>
<script>
function toggleAccordion(header) {
var item = header.parentElement;
var wasOpen = item.classList.contains('open');
// Close all
document.querySelectorAll('.md-accordion-item').forEach(function(el) {
el.classList.remove('open');
el.querySelector('.md-accordion-icon').textContent = '+';
});
// Open clicked if it was closed
if (!wasOpen) {
item.classList.add('open');
item.querySelector('.md-accordion-icon').textContent = '';
}
}
</script>
{{template "pages/base_footer" .}}

View File

@@ -0,0 +1,941 @@
{{template "pages/base_head" .}}
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');
:root {
--osh-bg: #0a0a0b;
--osh-text: #e4e4e7;
--osh-muted: #71717a;
--osh-accent: {{if .Config.Theme.PrimaryColor}}{{.Config.Theme.PrimaryColor}}{{else}}#22c55e{{end}};
--osh-accent-dark: {{if .Config.Theme.AccentColor}}{{.Config.Theme.AccentColor}}{{else}}#16a34a{{end}};
}
html, body.pages-body {
overflow-x: hidden;
background: var(--osh-bg) !important;
}
.osh-page::before {
content: "";
position: fixed;
inset: 0;
background-image:
linear-gradient(rgba(255,255,255,0.02) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,0.02) 1px, transparent 1px);
background-size: 24px 24px;
pointer-events: none;
z-index: 0;
}
.osh-page {
position: relative;
min-height: 100vh;
background: var(--osh-bg);
color: var(--osh-text);
font-family: 'IBM Plex Sans', -apple-system, sans-serif;
}
.osh-hero-gradient {
position: absolute;
top: -50%;
left: 50%;
transform: translateX(-50%);
width: 150%;
height: 100%;
background: radial-gradient(ellipse at center, rgba(34, 197, 94, 0.08) 0%, transparent 60%);
pointer-events: none;
}
.osh-grid-overlay {
position: absolute;
inset: 0;
background-image:
linear-gradient(rgba(255,255,255,0.02) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,0.02) 1px, transparent 1px);
background-size: 24px 24px;
pointer-events: none;
}
.osh-nav {
position: fixed;
top: 0;
left: 0;
right: 0;
padding: 16px 40px;
display: flex;
align-items: center;
justify-content: space-between;
background: rgba(10, 10, 11, 0.8);
backdrop-filter: blur(12px);
border-bottom: 1px solid rgba(255,255,255,0.05);
z-index: 100;
}
.osh-nav-brand {
display: flex;
align-items: center;
gap: 8px;
text-decoration: none;
color: inherit;
}
.osh-nav-logo {
width: 32px;
height: 32px;
background: linear-gradient(135deg, var(--osh-accent) 0%, var(--osh-accent-dark) 100%);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
}
.osh-nav-links {
display: flex;
align-items: center;
gap: 32px;
}
.osh-nav-link {
color: var(--osh-muted);
text-decoration: none;
font-size: 14px;
transition: color 0.2s ease;
}
.osh-nav-link:hover {
color: var(--osh-text);
}
.osh-btn-primary {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 14px 28px;
background: linear-gradient(135deg, var(--osh-accent) 0%, var(--osh-accent-dark) 100%);
color: #000;
font-weight: 600;
font-size: 15px;
border-radius: 8px;
text-decoration: none;
transition: all 0.2s ease;
border: none;
cursor: pointer;
}
.osh-btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 30px rgba(34, 197, 94, 0.3);
}
.osh-btn-secondary {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 14px 28px;
background: transparent;
color: var(--osh-text);
font-weight: 500;
font-size: 15px;
border-radius: 8px;
text-decoration: none;
border: 1px solid rgba(255,255,255,0.15);
transition: all 0.2s ease;
}
.osh-btn-secondary:hover {
background: rgba(255,255,255,0.05);
border-color: rgba(255,255,255,0.25);
}
.osh-hero {
position: relative;
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 120px 40px 80px;
text-align: center;
}
.osh-hero-content {
position: relative;
z-index: 1;
max-width: 800px;
}
.osh-badge {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background: rgba(34, 197, 94, 0.1);
border: 1px solid rgba(34, 197, 94, 0.2);
border-radius: 100px;
font-size: 13px;
color: var(--osh-accent);
margin-bottom: 32px;
}
.osh-badge-dot {
width: 6px;
height: 6px;
background: var(--osh-accent);
border-radius: 50%;
}
.osh-hero h1 {
font-size: 64px;
font-weight: 600;
line-height: 1.1;
margin-bottom: 24px;
letter-spacing: -0.02em;
}
.osh-hero-sub {
font-size: 20px;
color: #a1a1aa;
line-height: 1.6;
margin-bottom: 40px;
max-width: 600px;
margin-left: auto;
margin-right: auto;
}
.osh-hero-ctas {
display: flex;
gap: 16px;
justify-content: center;
margin-bottom: 40px;
}
.osh-code-block {
display: flex;
align-items: center;
gap: 16px;
padding: 16px 20px;
background: rgba(0,0,0,0.4);
border: 1px solid rgba(255,255,255,0.15);
border-radius: 10px;
font-family: 'JetBrains Mono', monospace;
font-size: 14px;
max-width: 480px;
margin: 0 auto;
}
.osh-code-block code {
color: var(--osh-accent);
flex: 1;
text-align: left;
}
.osh-copy-btn {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
background: rgba(255,255,255,0.05);
border: 1px solid rgba(255,255,255,0.1);
border-radius: 6px;
color: var(--osh-muted);
cursor: pointer;
transition: all 0.2s ease;
}
.osh-copy-btn:hover {
background: rgba(255,255,255,0.1);
color: var(--osh-text);
}
.osh-stats {
padding: 60px 40px;
border-top: 1px solid rgba(255,255,255,0.05);
border-bottom: 1px solid rgba(255,255,255,0.05);
}
.osh-stats-inner {
display: flex;
justify-content: center;
align-items: center;
max-width: 800px;
margin: 0 auto;
flex-wrap: wrap;
gap: 24px;
}
.osh-stat-item {
text-align: center;
padding: 0 32px;
border-right: 1px solid rgba(255,255,255,0.08);
}
.osh-stat-item:last-child {
border-right: none;
}
.osh-stat-value {
font-family: 'JetBrains Mono', monospace;
font-size: 28px;
font-weight: 500;
color: var(--osh-accent);
}
.osh-stat-label {
font-size: 13px;
color: var(--osh-muted);
margin-top: 4px;
}
.osh-features {
padding: 100px 40px;
}
.osh-features-inner {
max-width: 1100px;
margin: 0 auto;
}
.osh-section-header {
text-align: center;
margin-bottom: 60px;
}
.osh-section-header h2 {
font-size: 40px;
font-weight: 600;
margin-bottom: 16px;
}
.osh-section-header p {
font-size: 18px;
color: var(--osh-muted);
max-width: 500px;
margin: 0 auto;
}
.osh-features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
}
.osh-feature-card {
padding: 28px;
background: rgba(255,255,255,0.02);
border: 1px solid rgba(255,255,255,0.06);
border-radius: 12px;
transition: all 0.3s ease;
}
.osh-feature-card:hover {
background: rgba(255,255,255,0.04);
border-color: rgba(34, 197, 94, 0.3);
transform: translateY(-4px);
}
.osh-feature-icon {
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(34, 197, 94, 0.1);
border-radius: 10px;
color: var(--osh-accent);
margin-bottom: 16px;
}
.osh-feature-title {
font-size: 17px;
font-weight: 600;
color: #fafafa;
margin-bottom: 8px;
}
.osh-feature-desc {
font-size: 14px;
color: #a1a1aa;
line-height: 1.6;
}
.osh-social-proof {
padding: 80px 40px;
background: rgba(255,255,255,0.01);
}
.osh-social-proof-inner {
max-width: 1100px;
margin: 0 auto;
}
.osh-logos {
display: flex;
justify-content: center;
gap: 40px;
margin-bottom: 60px;
flex-wrap: wrap;
}
.osh-logo-item {
padding: 12px 24px;
background: rgba(0,0,0,0.4);
border-radius: 8px;
font-size: 14px;
font-weight: 500;
color: #52525b;
}
.osh-testimonial {
background: linear-gradient(135deg, rgba(34, 197, 94, 0.05) 0%, rgba(34, 197, 94, 0.02) 100%);
border: 1px solid rgba(34, 197, 94, 0.15);
border-radius: 16px;
padding: 40px;
max-width: 700px;
margin: 0 auto;
}
.osh-testimonial-quote {
font-size: 20px;
line-height: 1.6;
color: #fafafa;
margin-bottom: 24px;
}
.osh-testimonial-author {
font-weight: 600;
color: var(--osh-text);
}
.osh-testimonial-role {
font-size: 14px;
color: var(--osh-muted);
}
.osh-readme {
padding: 80px 40px;
max-width: 900px;
margin: 0 auto;
}
.osh-readme-content {
background: rgba(255,255,255,0.02);
border: 1px solid rgba(255,255,255,0.06);
border-radius: 12px;
padding: 40px;
}
.osh-cta-section {
padding: 100px 40px;
text-align: center;
background: linear-gradient(180deg, transparent 0%, rgba(34, 197, 94, 0.03) 100%);
border-top: 1px solid rgba(255,255,255,0.05);
}
.osh-cta-inner {
max-width: 600px;
margin: 0 auto;
}
.osh-cta-section h2 {
font-size: 44px;
font-weight: 600;
margin-bottom: 16px;
}
.osh-cta-section p {
font-size: 18px;
color: var(--osh-muted);
margin-bottom: 32px;
}
.osh-footer {
padding: 40px;
border-top: 1px solid rgba(255,255,255,0.05);
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 24px;
}
.osh-footer-brand {
display: flex;
align-items: center;
gap: 8px;
color: #52525b;
}
.osh-footer-logo {
width: 24px;
height: 24px;
background: rgba(34, 197, 94, 0.2);
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
}
.osh-footer-links {
display: flex;
gap: 32px;
flex-wrap: wrap;
}
.osh-footer-link {
color: var(--osh-muted);
text-decoration: none;
font-size: 14px;
transition: color 0.2s ease;
}
.osh-footer-link:hover {
color: var(--osh-text);
}
.osh-footer-social {
display: flex;
gap: 16px;
}
.osh-footer-social a {
color: var(--osh-muted);
transition: color 0.2s ease;
}
.osh-footer-social a:hover {
color: var(--osh-text);
}
/* Pricing Section */
.osh-pricing {
padding: 100px 40px;
}
.osh-pricing-inner {
max-width: 1100px;
margin: 0 auto;
}
.osh-pricing-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 24px;
margin-top: 60px;
}
.osh-pricing-card {
background: rgba(255,255,255,0.02);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 16px;
padding: 32px;
position: relative;
transition: all 0.3s ease;
}
.osh-pricing-card:hover {
border-color: rgba(255,255,255,0.15);
transform: translateY(-4px);
}
.osh-pricing-card.featured {
background: linear-gradient(135deg, rgba(34, 197, 94, 0.08) 0%, rgba(34, 197, 94, 0.02) 100%);
border-color: rgba(34, 197, 94, 0.3);
}
.osh-pricing-badge {
position: absolute;
top: -12px;
left: 50%;
transform: translateX(-50%);
padding: 6px 16px;
background: linear-gradient(135deg, var(--osh-accent) 0%, var(--osh-accent-dark) 100%);
color: #000;
font-size: 12px;
font-weight: 600;
border-radius: 100px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.osh-pricing-name {
font-size: 20px;
font-weight: 600;
color: #fafafa;
margin-bottom: 8px;
}
.osh-pricing-price {
font-size: 48px;
font-weight: 600;
color: var(--osh-text);
line-height: 1;
margin-bottom: 4px;
}
.osh-pricing-period {
font-size: 14px;
color: var(--osh-muted);
margin-bottom: 24px;
}
.osh-pricing-features {
list-style: none;
padding: 0;
margin: 0 0 32px 0;
}
.osh-pricing-features li {
padding: 10px 0;
border-bottom: 1px solid rgba(255,255,255,0.05);
font-size: 14px;
color: #a1a1aa;
display: flex;
align-items: center;
gap: 10px;
}
.osh-pricing-features li::before {
content: "";
width: 16px;
height: 16px;
background: rgba(34, 197, 94, 0.15);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.osh-pricing-features li:last-child {
border-bottom: none;
}
.osh-pricing-cta {
display: block;
width: 100%;
padding: 14px 24px;
text-align: center;
background: rgba(255,255,255,0.05);
color: var(--osh-text);
font-weight: 600;
font-size: 14px;
border-radius: 8px;
text-decoration: none;
border: 1px solid rgba(255,255,255,0.1);
transition: all 0.2s ease;
}
.osh-pricing-cta:hover {
background: rgba(255,255,255,0.1);
}
.osh-pricing-card.featured .osh-pricing-cta {
background: linear-gradient(135deg, var(--osh-accent) 0%, var(--osh-accent-dark) 100%);
color: #000;
border: none;
}
.osh-pricing-card.featured .osh-pricing-cta:hover {
transform: translateY(-2px);
box-shadow: 0 8px 30px rgba(34, 197, 94, 0.3);
}
@media (max-width: 768px) {
.osh-hero h1 { font-size: 40px; }
.osh-hero { padding: 100px 24px 60px; }
.osh-features, .osh-social-proof, .osh-cta-section, .osh-pricing { padding: 60px 24px; }
.osh-pricing-price { font-size: 36px; }
.osh-stats { padding: 40px 24px; }
.osh-stat-item { border-right: none; padding: 16px 0; }
.osh-hero-ctas { flex-direction: column; align-items: center; }
.osh-nav { padding: 12px 20px; }
.osh-nav-links { gap: 16px; }
.osh-section-header h2 { font-size: 32px; }
.osh-cta-section h2 { font-size: 32px; }
.osh-footer { flex-direction: column; text-align: center; }
}
</style>
<div class="osh-page">
<!-- Navigation -->
<nav class="osh-nav">
<a href="/" class="osh-nav-brand">
{{if .Config.Brand.LogoURL}}
<img src="{{.Config.Brand.LogoURL}}" alt="{{.Config.Brand.Name}}" style="height: 32px;">
{{else}}
<div class="osh-nav-logo">
{{svg "octicon-zap" 16}}
</div>
{{end}}
<span style="font-weight: 600; font-size: 18px;">{{if .Config.Brand.Name}}{{.Config.Brand.Name}}{{else}}{{.Repository.Name}}{{end}}</span>
</a>
<div class="osh-nav-links">
{{if .Config.Footer.Links}}
{{range .Config.Footer.Links}}
<a href="{{.URL}}" class="osh-nav-link">{{.Label}}</a>
{{end}}
{{else}}
<a href="{{.Repository.Link}}" class="osh-nav-link">Repository</a>
<a href="{{.Repository.Link}}/wiki" class="osh-nav-link">Docs</a>
<a href="{{.Repository.Link}}/releases" class="osh-nav-link">Releases</a>
{{end}}
{{if .Config.ValueProps}}<a href="#value-props" class="osh-nav-link">Why Us</a>{{end}}
{{if .Config.Features}}<a href="#features" class="osh-nav-link">Features</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="#pricing" class="osh-nav-link">Pricing</a>{{end}}
<a href="{{.Repository.Link}}" class="osh-btn-secondary" style="padding: 10px 20px;">
<img src="/assets/img/gitcaddy-icon.svg" width="18" height="18" alt="GitCaddy">
Repository
</a>
</div>
</nav>
<!-- Hero Section -->
<section class="osh-hero">
<div class="osh-hero-gradient"></div>
<div class="osh-grid-overlay"></div>
<div class="osh-hero-content">
<div class="osh-badge">
<span class="osh-badge-dot"></span>
{{if .LatestRelease}}v{{.LatestRelease.TagName}} released{{else}}Open Source{{end}}
</div>
<h1>{{if .Config.Hero.Headline}}{{.Config.Hero.Headline}}{{else}}{{.Repository.Name}}{{end}}</h1>
<p class="osh-hero-sub">
{{if .Config.Hero.Subheadline}}{{.Config.Hero.Subheadline}}{{else}}{{.Repository.Description}}{{end}}
</p>
<div class="osh-hero-ctas">
{{if .Config.Hero.PrimaryCTA.Label}}
<a href="{{.Config.Hero.PrimaryCTA.URL}}" class="osh-btn-primary">
{{.Config.Hero.PrimaryCTA.Label}}
{{svg "octicon-arrow-right" 16}}
</a>
{{else}}
<a href="{{.Repository.Link}}" class="osh-btn-primary">
Get Started
{{svg "octicon-arrow-right" 16}}
</a>
{{end}}
{{if .Config.Hero.SecondaryCTA.Label}}
<a href="{{.Config.Hero.SecondaryCTA.URL}}" class="osh-btn-secondary">
<img src="/assets/img/gitcaddy-icon.svg" width="18" height="18" alt="GitCaddy">
{{.Config.Hero.SecondaryCTA.Label}}
</a>
{{else}}
<a href="{{.Repository.Link}}" class="osh-btn-secondary">
<img src="/assets/img/gitcaddy-icon.svg" width="18" height="18" alt="GitCaddy">
View Source
</a>
{{end}}
</div>
{{if .Config.Hero.CodeExample}}
<div class="osh-code-block">
<span style="color: #71717a;">$</span>
<code id="install-cmd">{{.Config.Hero.CodeExample}}</code>
<button class="osh-copy-btn" onclick="navigator.clipboard.writeText(document.getElementById('install-cmd').textContent)">
{{svg "octicon-copy" 16}}
</button>
</div>
{{end}}
</div>
</section>
<!-- Stats Section -->
{{if or .Config.Stats (gt .NumStars 0)}}
<section class="osh-stats">
<div class="osh-stats-inner">
{{if .Config.Stats}}
{{range .Config.Stats}}
<div class="osh-stat-item">
<div class="osh-stat-value">{{.Value}}</div>
<div class="osh-stat-label">{{.Label}}</div>
</div>
{{end}}
{{else}}
<div class="osh-stat-item">
<div class="osh-stat-value">{{.NumStars}}</div>
<div class="osh-stat-label">Stars</div>
</div>
<div class="osh-stat-item">
<div class="osh-stat-value">{{.NumForks}}</div>
<div class="osh-stat-label">Forks</div>
</div>
{{if .LatestRelease}}
<div class="osh-stat-item">
<div class="osh-stat-value">v{{.LatestRelease.TagName}}</div>
<div class="osh-stat-label">Latest</div>
</div>
{{end}}
{{end}}
</div>
</section>
{{end}}
<!-- Value Props Section -->
{{if .Config.ValueProps}}
<section class="osh-features" id="value-props">
<div class="osh-features-inner">
<div class="osh-section-header">
<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-features-grid">
{{range .Config.ValueProps}}
<div class="osh-feature-card">
<div class="osh-feature-icon">
{{svg (printf "octicon-%s" (or .Icon "check")) 22}}
</div>
<h3 class="osh-feature-title">{{.Title}}</h3>
<p class="osh-feature-desc">{{.Description}}</p>
</div>
{{end}}
</div>
</div>
</section>
{{end}}
<!-- Features Section -->
{{if .Config.Features}}
<section class="osh-features" id="features" style="padding-top: 40px;">
<div class="osh-features-inner">
<div class="osh-section-header">
<h2>Features</h2>
<p>Powerful capabilities at your fingertips.</p>
</div>
<div class="osh-features-grid">
{{range .Config.Features}}
<div class="osh-feature-card">
<div class="osh-feature-icon">
{{svg (printf "octicon-%s" (or .Icon "zap")) 22}}
</div>
<h3 class="osh-feature-title">{{.Title}}</h3>
<p class="osh-feature-desc">{{.Description}}</p>
</div>
{{end}}
</div>
</div>
</section>
{{end}}
<!-- Social Proof -->
{{if or .Config.SocialProof.Logos .Config.SocialProof.Testimonials}}
<section class="osh-social-proof">
<div class="osh-social-proof-inner">
{{if .Config.SocialProof.Logos}}
<div class="osh-logos">
{{range .Config.SocialProof.Logos}}
<div class="osh-logo-item">{{.}}</div>
{{end}}
</div>
{{end}}
{{if .Config.SocialProof.Testimonials}}
<div class="osh-testimonials-container">
{{range .Config.SocialProof.Testimonials}}
<div class="osh-testimonial" style="display: none;">
<p class="osh-testimonial-quote">"{{.Quote}}"</p>
<div>
<div class="osh-testimonial-author">{{.Author}}</div>
<div class="osh-testimonial-role">{{.Role}}</div>
</div>
</div>
{{end}}
</div>
<script>
(function() {
var testimonials = document.querySelectorAll(".osh-testimonial");
if (testimonials.length > 0) {
var idx = Math.floor(Math.random() * testimonials.length);
testimonials[idx].style.display = "block";
}
})();
</script>
{{end}}
</div>
</section>
{{end}}
<!-- Pricing Section -->
{{if .Config.Pricing.Plans}}
<section class="osh-pricing" id="pricing">
<div class="osh-pricing-inner">
<div class="osh-section-header">
<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>
<div class="osh-pricing-grid">
{{range .Config.Pricing.Plans}}
<div class="osh-pricing-card{{if .Featured}} featured{{end}}">
{{if .Featured}}<span class="osh-pricing-badge">Popular</span>{{end}}
<div class="osh-pricing-name">{{.Name}}</div>
<div class="osh-pricing-price">{{.Price}}</div>
<div class="osh-pricing-period">{{.Period}}</div>
{{if .Features}}
<ul class="osh-pricing-features">
{{range .Features}}<li>{{.}}</li>{{end}}
</ul>
{{end}}
<a href="#" class="osh-pricing-cta">{{if .CTA}}{{.CTA}}{{else}}Get Started{{end}}</a>
</div>
{{end}}
</div>
</div>
</section>
{{end}}
<!-- CTA Section -->
{{if .Config.CTASection.Headline}}
<section class="osh-cta-section">
<div class="osh-cta-inner">
<h2>{{.Config.CTASection.Headline}}</h2>
{{if .Config.CTASection.Subheadline}}
<p>{{.Config.CTASection.Subheadline}}</p>
{{end}}
<a href="{{if .Config.CTASection.Button.URL}}{{.Config.CTASection.Button.URL}}{{else}}{{.Repository.Link}}{{end}}" class="osh-btn-primary" style="padding: 16px 32px; font-size: 16px;">
{{if .Config.CTASection.Button.Label}}{{.Config.CTASection.Button.Label}}{{else}}Get Started{{end}}
{{svg "octicon-arrow-right" 16}}
</a>
</div>
</section>
{{end}}
<!-- Footer -->
<footer class="osh-footer">
<div class="osh-footer-brand">
<div class="osh-footer-logo">{{svg "octicon-zap" 14}}</div>
<span style="font-size: 14px;">{{if .Config.Footer.Copyright}}{{.Config.Footer.Copyright}}{{else}}&copy; <script>document.write(new Date().getFullYear())</script> {{if .Config.Brand.Name}}{{.Config.Brand.Name}}{{else}}{{.Repository.Name}}{{end}}{{end}}</span>
</div>
{{if .Config.Footer.Social}}
<div class="osh-footer-social">
{{range .Config.Footer.Social}}
<a href="{{.URL}}" title="{{.Platform}}">
{{if eq .Platform "twitter"}}{{svg "octicon-mention" 18}}
{{else if eq .Platform "github"}}{{svg "octicon-mark-github" 18}}
{{else if eq .Platform "discord"}}{{svg "octicon-comment-discussion" 18}}
{{else if eq .Platform "linkedin"}}{{svg "octicon-briefcase" 18}}
{{else if eq .Platform "youtube"}}{{svg "octicon-video" 18}}
{{else}}{{svg "octicon-link-external" 18}}{{end}}
</a>
{{end}}
</div>
{{end}}
<div class="osh-footer-links">
{{if .Config.Footer.Links}}
{{range .Config.Footer.Links}}
<a href="{{.URL}}" class="osh-footer-link">{{.Label}}</a>
{{end}}
{{else}}
<a href="{{.Repository.Link}}" class="osh-footer-link">Repository</a>
<a href="{{.Repository.Link}}/wiki" class="osh-footer-link">Documentation</a>
<a href="{{.Repository.Link}}/releases" class="osh-footer-link">Releases</a>
<a href="{{.Repository.Link}}/issues" class="osh-footer-link">Issues</a>
{{end}}
</div>
</footer>
</div>
{{template "pages/base_footer" .}}

View File

@@ -1,198 +0,0 @@
{{template "pages/base_head" .}}
<style>
/* Portfolio-specific styles - Gallery/Creative design */
.pages-portfolio {
background: #1a1a1a;
color: #fff;
}
.pages-portfolio .pages-header {
background: transparent;
border-bottom: 1px solid rgba(255,255,255,0.1);
}
.pages-portfolio .pages-nav-brand,
.pages-portfolio .pages-nav-link {
color: #fff;
}
.pages-portfolio .pages-nav-link:hover {
color: var(--pages-primary);
}
.pages-portfolio .pages-hero {
background: linear-gradient(180deg, #1a1a1a 0%, #2d2d2d 100%);
padding: 100px 0 60px;
}
.pages-portfolio .pages-title {
color: #fff;
font-size: 2.5rem;
letter-spacing: -0.02em;
}
.pages-portfolio .pages-tagline {
color: rgba(255,255,255,0.7);
}
.pages-portfolio .pages-logo {
border-radius: 50%;
border: 3px solid rgba(255,255,255,0.2);
}
.pages-gallery {
padding: 60px 0;
background: #1a1a1a;
}
.pages-gallery-grid {
display: grid;
grid-template-columns: repeat(var(--columns, 3), 1fr);
gap: 16px;
}
.pages-gallery-item {
position: relative;
overflow: hidden;
border-radius: 8px;
aspect-ratio: 1;
background: #2d2d2d;
}
.pages-gallery-item a {
display: block;
width: 100%;
height: 100%;
}
.pages-gallery-image {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease, filter 0.3s ease;
}
.pages-gallery-item:hover .pages-gallery-image {
transform: scale(1.05);
filter: brightness(0.8);
}
.pages-gallery-caption {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 16px;
background: linear-gradient(transparent, rgba(0,0,0,0.8));
color: #fff;
font-size: 0.875rem;
font-weight: 500;
opacity: 0;
transform: translateY(10px);
transition: opacity 0.3s ease, transform 0.3s ease;
}
.pages-gallery-item:hover .pages-gallery-caption {
opacity: 1;
transform: translateY(0);
}
.pages-portfolio .pages-readme {
background: #2d2d2d;
padding: 60px 0;
}
.pages-portfolio .pages-readme .markup {
color: rgba(255,255,255,0.9);
}
.pages-portfolio .pages-readme .markup h1,
.pages-portfolio .pages-readme .markup h2,
.pages-portfolio .pages-readme .markup h3 {
color: #fff;
}
.pages-portfolio .pages-readme .markup a {
color: var(--pages-primary);
}
.pages-portfolio .pages-readme .markup code {
background: rgba(255,255,255,0.1);
color: #fff;
}
.pages-portfolio .pages-footer {
background: #111;
}
/* Empty gallery placeholder */
.pages-gallery-empty {
grid-column: 1 / -1;
text-align: center;
padding: 60px;
color: rgba(255,255,255,0.5);
}
.pages-gallery-empty-icon {
font-size: 3rem;
margin-bottom: 16px;
}
@media (max-width: 768px) {
.pages-gallery-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 480px) {
.pages-gallery-grid {
grid-template-columns: 1fr;
}
}
</style>
<div class="pages-landing pages-portfolio">
{{template "pages/header" .}}
<main class="pages-main">
<!-- Hero Section -->
<section class="pages-hero">
<div class="container">
{{if .Config.Branding.Logo}}
<img src="{{.Config.Branding.Logo}}" alt="{{.Repository.Name}}" class="pages-logo">
{{end}}
<h1 class="pages-title">{{if .Config.Hero.Title}}{{.Config.Hero.Title}}{{else}}{{.Repository.Name}}{{end}}</h1>
{{if .Config.Hero.Tagline}}
<p class="pages-tagline">{{.Config.Hero.Tagline}}</p>
{{end}}
<div class="pages-cta">
{{if .Config.Hero.CTAPrimary.Text}}
<a href="{{.Config.Hero.CTAPrimary.Link}}" class="ui primary button">
{{.Config.Hero.CTAPrimary.Text}}
</a>
{{end}}
{{if .Config.Hero.CTASecondary.Text}}
<a href="{{.Config.Hero.CTASecondary.Link}}" class="ui button basic inverted">
{{.Config.Hero.CTASecondary.Text}}
</a>
{{end}}
</div>
</div>
</section>
<!-- Gallery Section -->
<section class="pages-gallery">
<div class="container">
{{if .Config.Gallery.Items}}
<div class="pages-gallery-grid" style="--columns: {{if .Config.Gallery.Columns}}{{.Config.Gallery.Columns}}{{else}}3{{end}}">
{{range .Config.Gallery.Items}}
<div class="pages-gallery-item">
{{if .Link}}<a href="{{.Link}}">{{end}}
<img src="{{.Image}}" alt="{{.Title}}" class="pages-gallery-image">
{{if .Title}}
<div class="pages-gallery-caption">{{.Title}}</div>
{{end}}
{{if .Link}}</a>{{end}}
</div>
{{end}}
</div>
{{else}}
<div class="pages-gallery-grid" style="--columns: 3">
<div class="pages-gallery-empty">
{{svg "octicon-image" 48}}
<p>Add gallery items in your landing.yaml configuration</p>
</div>
</div>
{{end}}
</div>
</section>
<!-- README Section -->
{{if .ReadmeContent}}
<section class="pages-readme">
<div class="container">
<div class="markup">
{{.ReadmeContent}}
</div>
</div>
</section>
{{end}}
</main>
{{template "pages/footer" .}}
</div>
{{template "pages/base_footer" .}}

View File

@@ -1,198 +0,0 @@
{{template "pages/base_head" .}}
<style>
/* Product-specific styles - Bold marketing design */
.pages-product {
background: #fff;
}
.pages-product .pages-header {
background: transparent;
border-bottom: none;
position: absolute;
width: 100%;
z-index: 100;
}
.pages-product .pages-nav-brand,
.pages-product .pages-nav-link {
color: #fff;
text-shadow: 0 1px 2px rgba(0,0,0,0.3);
}
.pages-hero-product {
background: linear-gradient(135deg, var(--pages-primary) 0%, #1a365d 100%);
color: #fff;
padding: 120px 0 80px;
position: relative;
overflow: hidden;
}
.pages-hero-product::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.05'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
}
.pages-hero-product .container {
position: relative;
z-index: 1;
}
.pages-product .pages-title {
color: #fff;
font-size: 3.5rem;
margin-bottom: 24px;
}
.pages-product .pages-tagline {
color: rgba(255,255,255,0.9);
font-size: 1.5rem;
max-width: 700px;
}
.pages-product .pages-logo {
max-height: 100px;
filter: brightness(0) invert(1);
}
.pages-features {
padding: 80px 0;
background: #fff;
}
.pages-features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 48px;
}
.pages-feature {
text-align: center;
padding: 32px;
}
.pages-feature-icon {
width: 64px;
height: 64px;
margin: 0 auto 24px;
background: linear-gradient(135deg, var(--pages-primary) 0%, #1a365d 100%);
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
}
.pages-feature-icon img {
max-width: 32px;
max-height: 32px;
}
.pages-feature-title {
font-size: 1.25rem;
font-weight: 600;
margin: 0 0 12px;
color: #24292e;
}
.pages-feature-description {
color: #586069;
line-height: 1.6;
margin: 0;
}
.pages-product .pages-stats {
background: linear-gradient(135deg, #f6f8fa 0%, #e1e4e8 100%);
padding: 64px 0;
}
.pages-cta-bottom {
margin-top: 32px;
text-align: center;
}
.pages-product .pages-readme {
background: #fff;
padding: 80px 0;
}
@media (max-width: 768px) {
.pages-product .pages-title {
font-size: 2.5rem;
}
.pages-hero-product {
padding: 100px 0 60px;
}
}
</style>
<div class="pages-landing pages-product">
{{template "pages/header" .}}
<main class="pages-main">
<!-- Hero Section -->
<section class="pages-hero pages-hero-product" {{if .Config.Hero.Background}}style="background-image: url('{{.Config.Hero.Background}}')"{{end}}>
<div class="container">
{{if .Config.Branding.Logo}}
<img src="{{.Config.Branding.Logo}}" alt="{{.Repository.Name}}" class="pages-logo">
{{end}}
<h1 class="pages-title">{{if .Config.Hero.Title}}{{.Config.Hero.Title}}{{else}}{{.Repository.Name}}{{end}}</h1>
{{if .Config.Hero.Tagline}}
<p class="pages-tagline">{{.Config.Hero.Tagline}}</p>
{{end}}
<div class="pages-cta">
{{if .Config.Hero.CTAPrimary.Text}}
<a href="{{.Config.Hero.CTAPrimary.Link}}" class="ui large primary button" style="background: #fff; color: var(--pages-primary);">
{{.Config.Hero.CTAPrimary.Text}}
</a>
{{end}}
{{if .Config.Hero.CTASecondary.Text}}
<a href="{{.Config.Hero.CTASecondary.Link}}" class="ui large button basic inverted">
{{.Config.Hero.CTASecondary.Text}}
</a>
{{end}}
</div>
</div>
</section>
<!-- Features Section -->
{{if .Config.Features}}
<section class="pages-features">
<div class="container">
<div class="pages-features-grid">
{{range .Config.Features}}
<div class="pages-feature">
{{if .Icon}}
<div class="pages-feature-icon">
{{svg (printf "octicon-%s" .Icon) 32}}
</div>
{{end}}
<h3 class="pages-feature-title">{{.Title}}</h3>
<p class="pages-feature-description">{{.Description}}</p>
</div>
{{end}}
</div>
</div>
</section>
{{end}}
<!-- README Section -->
{{if .ReadmeContent}}
<section class="pages-readme">
<div class="container">
<div class="markup">
{{.ReadmeContent}}
</div>
</div>
</section>
{{end}}
<!-- Stats Section -->
<section class="pages-stats">
<div class="container">
<div class="pages-stats-grid">
<div class="pages-stat">
<span class="pages-stat-value">{{.NumStars}}</span>
<span class="pages-stat-label">{{ctx.Locale.Tr "repo.stars"}}</span>
</div>
<div class="pages-stat">
<span class="pages-stat-value">{{.NumForks}}</span>
<span class="pages-stat-label">{{ctx.Locale.Tr "repo.forks"}}</span>
</div>
</div>
<div class="pages-cta-bottom">
<a href="{{AppSubUrl}}/{{.Repository.FullName}}" class="ui large button" target="_blank">
{{svg "octicon-star" 16}} Star on Gitea
</a>
</div>
</div>
</section>
</main>
{{template "pages/footer" .}}
</div>
{{template "pages/base_footer" .}}

View File

@@ -0,0 +1,937 @@
{{template "pages/base_head" .}}
<style>
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&display=swap');
:root {
--saas-bg: #fafbfc;
--saas-text: #1a1a2e;
--saas-muted: #64748b;
--saas-light: #94a3b8;
--saas-border: #e5e7eb;
--saas-primary: {{if .Config.Theme.PrimaryColor}}{{.Config.Theme.PrimaryColor}}{{else}}#6366f1{{end}};
--saas-primary-dark: {{if .Config.Theme.AccentColor}}{{.Config.Theme.AccentColor}}{{else}}#8b5cf6{{end}};
}
.saas-page {
min-height: 100vh;
background: var(--saas-bg);
color: var(--saas-text);
font-family: 'DM Sans', -apple-system, sans-serif;
}
.saas-nav {
position: fixed;
top: 0;
left: 0;
right: 0;
padding: 16px 48px;
display: flex;
align-items: center;
justify-content: space-between;
background: rgba(250, 251, 252, 0.9);
backdrop-filter: blur(12px);
z-index: 100;
}
.saas-nav-brand {
display: flex;
align-items: center;
gap: 10px;
text-decoration: none;
}
.saas-nav-logo {
width: 36px;
height: 36px;
background: linear-gradient(135deg, var(--saas-primary) 0%, var(--saas-primary-dark) 100%);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
color: white;
}
.saas-nav-links {
display: flex;
align-items: center;
gap: 36px;
}
.saas-nav-link {
color: var(--saas-muted);
text-decoration: none;
font-weight: 500;
font-size: 15px;
transition: color 0.2s ease;
}
.saas-nav-link:hover {
color: var(--saas-text);
}
.saas-nav-repo {
display: inline-flex;
align-items: center;
gap: 8px;
color: var(--saas-muted);
text-decoration: none;
font-weight: 500;
font-size: 15px;
transition: color 0.2s ease;
}
.saas-nav-repo:hover {
color: var(--saas-text);
}
.saas-btn-primary {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 16px 32px;
background: linear-gradient(135deg, var(--saas-primary) 0%, var(--saas-primary-dark) 100%);
color: white;
font-weight: 600;
font-size: 16px;
border-radius: 12px;
text-decoration: none;
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
box-shadow: 0 4px 14px rgba(99, 102, 241, 0.35);
border: none;
cursor: pointer;
}
.saas-btn-primary:hover {
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(99, 102, 241, 0.45);
}
.saas-btn-secondary {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 16px 32px;
background: white;
color: var(--saas-text);
font-weight: 600;
font-size: 16px;
border-radius: 12px;
text-decoration: none;
border: 2px solid var(--saas-border);
transition: all 0.3s ease;
cursor: pointer;
}
.saas-btn-secondary:hover {
border-color: var(--saas-primary);
color: var(--saas-primary);
}
.saas-hero {
min-height: 100vh;
display: flex;
align-items: center;
padding: 140px 48px 80px;
background: linear-gradient(180deg, var(--saas-bg) 0%, #f1f5f9 100%);
}
.saas-hero-inner {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 80px;
max-width: 1300px;
margin: 0 auto;
align-items: center;
}
.saas-badge {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background: #ede9fe;
border-radius: 100px;
margin-bottom: 28px;
font-size: 14px;
font-weight: 600;
color: var(--saas-primary);
}
.saas-hero h1 {
font-size: 56px;
font-weight: 700;
line-height: 1.15;
margin-bottom: 24px;
color: var(--saas-text);
}
.saas-hero-sub {
font-size: 20px;
color: var(--saas-muted);
line-height: 1.7;
margin-bottom: 36px;
}
.saas-hero-ctas {
display: flex;
gap: 16px;
margin-bottom: 32px;
}
.saas-hero-note {
font-size: 14px;
color: var(--saas-light);
}
.saas-hero-mockup {
background: white;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0,0,0,0.12);
padding: 24px;
animation: saas-float 6s ease-in-out infinite;
}
@keyframes saas-float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.saas-mockup-bar {
display: flex;
gap: 8px;
margin-bottom: 20px;
}
.saas-mockup-dot {
width: 12px;
height: 12px;
border-radius: 50%;
}
.saas-stats-section {
padding: 60px 48px;
background: #f1f5f9;
}
.saas-stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 32px;
max-width: 800px;
margin: 0 auto;
text-align: center;
}
.saas-stat-value {
font-size: 36px;
font-weight: 700;
color: var(--saas-primary);
margin-bottom: 4px;
}
.saas-stat-label {
font-size: 14px;
color: var(--saas-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.saas-trust-section {
padding: 60px 48px;
background: #f1f5f9;
}
.saas-trust-label {
text-align: center;
font-size: 14px;
color: var(--saas-light);
margin-bottom: 24px;
font-weight: 500;
}
.saas-trust-bar {
display: flex;
align-items: center;
justify-content: center;
gap: 48px;
padding: 32px;
background: white;
border-radius: 16px;
box-shadow: 0 4px 20px rgba(0,0,0,0.04);
max-width: 900px;
margin: 0 auto;
flex-wrap: wrap;
}
.saas-trust-logo {
font-size: 18px;
font-weight: 600;
color: var(--saas-light);
transition: color 0.2s ease;
}
.saas-trust-logo:hover {
color: var(--saas-muted);
}
.saas-section {
padding: 100px 48px;
}
.saas-section-inner {
max-width: 1200px;
margin: 0 auto;
}
.saas-section-header {
text-align: center;
margin-bottom: 64px;
}
.saas-section-header h2 {
font-size: 44px;
font-weight: 700;
margin-bottom: 16px;
color: var(--saas-text);
}
.saas-section-header p {
font-size: 18px;
color: var(--saas-muted);
}
.saas-value-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 24px;
}
.saas-value-card {
padding: 32px;
background: white;
border-radius: 20px;
box-shadow: 0 4px 20px rgba(0,0,0,0.04);
transition: all 0.3s ease;
border: 1px solid transparent;
}
.saas-value-card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 40px rgba(0,0,0,0.08);
border-color: var(--saas-border);
}
.saas-value-card h3 {
font-size: 24px;
font-weight: 700;
margin-bottom: 12px;
color: var(--saas-text);
}
.saas-value-card p {
font-size: 16px;
color: var(--saas-muted);
line-height: 1.6;
}
.saas-features-section {
background: #f8fafc;
}
.saas-features-list {
display: flex;
flex-direction: column;
gap: 20px;
max-width: 900px;
margin: 0 auto;
}
.saas-feature-step {
display: flex;
gap: 24px;
padding: 32px;
background: white;
border-radius: 16px;
border: 1px solid var(--saas-border);
transition: all 0.3s ease;
}
.saas-feature-step:hover {
border-color: var(--saas-primary);
box-shadow: 0 8px 30px rgba(99, 102, 241, 0.1);
}
.saas-step-icon {
width: 48px;
height: 48px;
background: linear-gradient(135deg, #ede9fe 0%, #e0e7ff 100%);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
color: var(--saas-primary);
flex-shrink: 0;
}
.saas-feature-step h3 {
font-size: 20px;
font-weight: 600;
margin-bottom: 8px;
color: var(--saas-text);
}
.saas-feature-step p {
font-size: 16px;
color: var(--saas-muted);
line-height: 1.6;
}
.saas-testimonial-card {
background: white;
border-radius: 24px;
padding: 48px;
box-shadow: 0 4px 30px rgba(0,0,0,0.06);
max-width: 800px;
margin: 0 auto;
position: relative;
}
.saas-testimonial-card::before {
content: '"';
position: absolute;
top: 24px;
left: 40px;
font-size: 120px;
font-weight: 700;
color: #ede9fe;
line-height: 1;
}
.saas-testimonial-quote {
font-size: 24px;
line-height: 1.7;
color: var(--saas-text);
margin-bottom: 32px;
position: relative;
z-index: 1;
}
.saas-testimonial-author {
display: flex;
align-items: center;
gap: 16px;
}
.saas-testimonial-avatar {
width: 56px;
height: 56px;
background: linear-gradient(135deg, var(--saas-primary) 0%, var(--saas-primary-dark) 100%);
border-radius: 50%;
overflow: hidden;
}
.saas-testimonial-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.saas-testimonial-name {
font-weight: 600;
font-size: 16px;
color: var(--saas-text);
}
.saas-testimonial-role {
font-size: 14px;
color: var(--saas-muted);
}
.saas-pricing-section {
background: #f8fafc;
}
.saas-pricing-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 24px;
align-items: start;
max-width: 1100px;
margin: 0 auto;
}
.saas-pricing-card {
padding: 36px;
background: white;
border-radius: 20px;
border: 2px solid var(--saas-border);
transition: all 0.3s ease;
}
.saas-pricing-card:hover {
border-color: #c7d2fe;
}
.saas-pricing-card.featured {
border-color: var(--saas-primary);
box-shadow: 0 8px 40px rgba(99, 102, 241, 0.15);
transform: scale(1.02);
}
.saas-pricing-badge {
display: inline-block;
padding: 6px 12px;
background: linear-gradient(135deg, var(--saas-primary) 0%, var(--saas-primary-dark) 100%);
color: white;
font-size: 12px;
font-weight: 600;
border-radius: 100px;
margin-bottom: 16px;
}
.saas-pricing-name {
font-size: 20px;
font-weight: 600;
margin-bottom: 8px;
color: var(--saas-text);
}
.saas-pricing-price {
font-size: 48px;
font-weight: 700;
color: var(--saas-text);
}
.saas-pricing-period {
color: var(--saas-muted);
}
.saas-pricing-features {
list-style: none;
padding: 0;
margin: 24px 0 28px;
}
.saas-pricing-features li {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
color: var(--saas-muted);
}
.saas-pricing-features li::before {
content: "";
color: #22c55e;
}
.saas-cta-gradient {
background: linear-gradient(135deg, var(--saas-primary) 0%, var(--saas-primary-dark) 50%, #a855f7 100%);
border-radius: 32px;
padding: 80px;
text-align: center;
color: white;
max-width: 1100px;
margin: 0 auto;
position: relative;
overflow: hidden;
}
.saas-cta-gradient::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 60%);
animation: saas-float 8s ease-in-out infinite;
}
.saas-cta-gradient h2 {
font-size: 48px;
font-weight: 700;
margin-bottom: 16px;
position: relative;
z-index: 1;
}
.saas-cta-gradient p {
font-size: 20px;
opacity: 0.9;
margin-bottom: 32px;
position: relative;
z-index: 1;
}
.saas-cta-btn {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 18px 36px;
background: white;
color: var(--saas-primary);
font-weight: 700;
font-size: 16px;
border-radius: 12px;
text-decoration: none;
transition: all 0.3s ease;
position: relative;
z-index: 1;
}
.saas-cta-btn:hover {
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(255, 255, 255, 0.3);
}
.saas-footer {
padding: 48px;
border-top: 1px solid var(--saas-border);
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 24px;
}
.saas-footer-brand {
display: flex;
align-items: center;
gap: 10px;
}
.saas-footer-logo {
width: 28px;
height: 28px;
background: linear-gradient(135deg, var(--saas-primary) 0%, var(--saas-primary-dark) 100%);
border-radius: 8px;
}
.saas-footer-links {
display: flex;
gap: 32px;
flex-wrap: wrap;
}
.saas-footer-link {
color: var(--saas-muted);
text-decoration: none;
font-size: 14px;
transition: color 0.2s ease;
}
.saas-footer-link:hover {
color: var(--saas-primary);
}
.saas-footer-social {
display: flex;
gap: 16px;
}
.saas-footer-social a {
color: var(--saas-light);
transition: color 0.2s ease;
}
.saas-footer-social a:hover {
color: var(--saas-primary);
}
@media (max-width: 1024px) {
.saas-hero-inner { grid-template-columns: 1fr; }
.saas-hero-mockup { display: none; }
.saas-pricing-grid { max-width: 400px; margin: 0 auto; }
}
@media (max-width: 768px) {
.saas-hero h1 { font-size: 40px; }
.saas-hero-ctas { flex-direction: column; }
.saas-nav { padding: 12px 24px; }
.saas-nav-links { gap: 16px; display: none; }
.saas-section { padding: 60px 24px; }
.saas-stats-section, .saas-trust-section { padding: 40px 24px; }
.saas-cta-gradient { padding: 48px 24px; margin: 0 16px; }
.saas-footer { flex-direction: column; text-align: center; }
}
</style>
<div class="saas-page">
<!-- Navigation -->
<nav class="saas-nav">
<a href="/" class="saas-nav-brand">
{{if .Config.Brand.LogoURL}}
<img src="{{.Config.Brand.LogoURL}}" alt="{{.Config.Brand.Name}}" style="height: 36px;">
{{else}}
<div class="saas-nav-logo">
{{svg "octicon-package" 18}}
</div>
{{end}}
<span style="font-weight: 700; font-size: 20px; color: var(--saas-text);">{{if .Config.Brand.Name}}{{.Config.Brand.Name}}{{else}}{{.Repository.Name}}{{end}}</span>
</a>
<div class="saas-nav-links">
{{if .Config.Footer.Links}}
{{range .Config.Footer.Links}}
<a href="{{.URL}}" class="saas-nav-link">{{.Label}}</a>
{{end}}
{{end}}
{{if .Config.ValueProps}}<a href="#value-props" class="saas-nav-link">Why</a>{{end}}
{{if .Config.Features}}<a href="#features" class="saas-nav-link">Features</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="#pricing" class="saas-nav-link">Pricing</a>{{end}}
<a href="{{.Repository.Link}}" class="saas-nav-repo">
<img src="/assets/img/gitcaddy-icon.svg" width="18" height="18" alt="GitCaddy">
Repository
</a>
<a href="{{if .Config.Hero.PrimaryCTA.URL}}{{.Config.Hero.PrimaryCTA.URL}}{{else}}{{.Repository.Link}}{{end}}" class="saas-btn-primary" style="padding: 12px 24px; font-size: 14px;">
{{if .Config.Hero.PrimaryCTA.Label}}{{.Config.Hero.PrimaryCTA.Label}}{{else}}Get Started{{end}}
</a>
</div>
</nav>
<!-- Hero Section -->
<section class="saas-hero">
<div class="saas-hero-inner">
<div>
{{if gt .NumStars 100}}
<div class="saas-badge">
{{svg "octicon-star-fill" 14}}
<span>{{.NumStars}} stars</span>
</div>
{{end}}
<h1>{{if .Config.Hero.Headline}}{{.Config.Hero.Headline}}{{else}}{{.Repository.Name}}{{end}}</h1>
<p class="saas-hero-sub">
{{if .Config.Hero.Subheadline}}{{.Config.Hero.Subheadline}}{{else}}{{.Repository.Description}}{{end}}
</p>
<div class="saas-hero-ctas">
<a href="{{if .Config.Hero.PrimaryCTA.URL}}{{.Config.Hero.PrimaryCTA.URL}}{{else}}{{.Repository.Link}}{{end}}" class="saas-btn-primary">
{{if .Config.Hero.PrimaryCTA.Label}}{{.Config.Hero.PrimaryCTA.Label}}{{else}}Get Started{{end}}
{{svg "octicon-arrow-right" 16}}
</a>
{{if .Config.Hero.SecondaryCTA.Label}}
<a href="{{.Config.Hero.SecondaryCTA.URL}}" class="saas-btn-secondary">
{{svg "octicon-play" 16}}
{{.Config.Hero.SecondaryCTA.Label}}
</a>
{{end}}
</div>
<p class="saas-hero-note">
{{svg "octicon-check" 14}} Free to use &nbsp; {{svg "octicon-check" 14}} Open source &nbsp; {{svg "octicon-check" 14}} MIT License
</p>
</div>
<div class="saas-hero-mockup">
<div class="saas-mockup-bar">
<div class="saas-mockup-dot" style="background: #ef4444;"></div>
<div class="saas-mockup-dot" style="background: #f59e0b;"></div>
<div class="saas-mockup-dot" style="background: #22c55e;"></div>
</div>
<div style="display: flex; flex-direction: column; gap: 16px;">
<div style="height: 32px; background: #f1f5f9; border-radius: 8px; width: 60%;"></div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px;">
<div style="height: 80px; background: linear-gradient(135deg, #ede9fe 0%, #e0e7ff 100%); border-radius: 10px;"></div>
<div style="height: 80px; background: #f1f5f9; border-radius: 10px;"></div>
<div style="height: 80px; background: #f1f5f9; border-radius: 10px;"></div>
</div>
<div style="height: 120px; background: linear-gradient(135deg, var(--saas-primary) 0%, var(--saas-primary-dark) 100%); border-radius: 12px; opacity: 0.15;"></div>
</div>
</div>
</div>
</section>
<!-- Stats Section -->
{{if .Config.Stats}}
<section class="saas-stats-section">
<div class="saas-stats-grid">
{{range .Config.Stats}}
<div>
<div class="saas-stat-value">{{.Value}}</div>
<div class="saas-stat-label">{{.Label}}</div>
</div>
{{end}}
</div>
</section>
{{end}}
<!-- Trust Bar -->
{{if .Config.SocialProof.Logos}}
<section class="saas-trust-section">
<p class="saas-trust-label">TRUSTED BY TEAMS AT</p>
<div class="saas-trust-bar">
{{range .Config.SocialProof.Logos}}
<span class="saas-trust-logo">{{.}}</span>
{{end}}
</div>
</section>
{{end}}
<!-- Value Props -->
{{if .Config.ValueProps}}
<section class="saas-section" id="value-props">
<div class="saas-section-inner">
<div class="saas-section-header">
<h2>Why {{if .Config.Brand.Name}}{{.Config.Brand.Name}}{{else}}{{.Repository.Name}}{{end}}</h2>
<p>Built for developers who value their time</p>
</div>
<div class="saas-value-grid">
{{range .Config.ValueProps}}
<div class="saas-value-card">
<h3>{{.Title}}</h3>
<p>{{.Description}}</p>
</div>
{{end}}
</div>
</div>
</section>
{{end}}
<!-- Features -->
{{if .Config.Features}}
<section id="features" class="saas-section saas-features-section">
<div class="saas-section-inner">
<div class="saas-section-header">
<h2>How it works</h2>
<p>Get started in minutes, not weeks</p>
</div>
<div class="saas-features-list">
{{range .Config.Features}}
<div class="saas-feature-step">
<div class="saas-step-icon">
{{svg (printf "octicon-%s" (or .Icon "zap")) 24}}
</div>
<div>
<h3>{{.Title}}</h3>
<p>{{.Description}}</p>
</div>
</div>
{{end}}
</div>
</div>
</section>
{{end}}
<!-- Testimonials -->
{{if .Config.SocialProof.Testimonials}}
<section class="saas-section">
<div class="saas-testimonial-card">
<div class="saas-testimonials-container">
{{range .Config.SocialProof.Testimonials}}
<div class="saas-testimonial-item" style="display: none;">
<p class="saas-testimonial-quote">{{.Quote}}</p>
<div class="saas-testimonial-author">
<div class="saas-testimonial-avatar">
{{if .Avatar}}<img src="{{.Avatar}}" alt="{{.Author}}">{{end}}
</div>
<div>
<div class="saas-testimonial-name">{{.Author}}</div>
<div class="saas-testimonial-role">{{.Role}}</div>
</div>
</div>
</div>
{{end}}
</div>
</div>
</section>
<script>
(function() {
var items = document.querySelectorAll(".saas-testimonial-item");
if (items.length > 0) {
var idx = Math.floor(Math.random() * items.length);
items[idx].style.display = "block";
}
})();
</script>
{{end}}
<!-- Pricing -->
{{if .Config.Pricing.Plans}}
<section id="pricing" class="saas-section saas-pricing-section">
<div class="saas-section-inner">
<div class="saas-section-header">
<h2>{{if .Config.Pricing.Headline}}{{.Config.Pricing.Headline}}{{else}}Simple, transparent pricing{{end}}</h2>
</div>
<div class="saas-pricing-grid">
{{range .Config.Pricing.Plans}}
<div class="saas-pricing-card {{if .Featured}}featured{{end}}">
{{if .Featured}}<div class="saas-pricing-badge">Most Popular</div>{{end}}
<h3 class="saas-pricing-name">{{.Name}}</h3>
<div style="margin-bottom: 24px;">
<span class="saas-pricing-price">{{.Price}}</span>
<span class="saas-pricing-period">{{.Period}}</span>
</div>
{{if .Features}}
<ul class="saas-pricing-features">
{{range .Features}}
<li>{{svg "octicon-check" 16}} {{.}}</li>
{{end}}
</ul>
{{end}}
<a href="#" class="{{if .Featured}}saas-btn-primary{{else}}saas-btn-secondary{{end}}" style="width: 100%; justify-content: center;">
{{if .CTA}}{{.CTA}}{{else}}Get Started{{end}}
</a>
</div>
{{end}}
</div>
</div>
</section>
{{end}}
<!-- Final CTA -->
{{if .Config.CTASection.Headline}}
<section class="saas-section">
<div class="saas-cta-gradient">
<h2>{{.Config.CTASection.Headline}}</h2>
{{if .Config.CTASection.Subheadline}}
<p>{{.Config.CTASection.Subheadline}}</p>
{{end}}
<a href="{{if .Config.CTASection.Button.URL}}{{.Config.CTASection.Button.URL}}{{else}}{{.Repository.Link}}{{end}}" class="saas-cta-btn">
{{if .Config.CTASection.Button.Label}}{{.Config.CTASection.Button.Label}}{{else}}Get Started Free{{end}}
{{svg "octicon-arrow-right" 16}}
</a>
</div>
</section>
{{end}}
<!-- Footer -->
<footer class="saas-footer">
<div class="saas-footer-brand">
<div class="saas-footer-logo"></div>
<span style="font-size: 14px; color: var(--saas-light);">{{if .Config.Footer.Copyright}}{{.Config.Footer.Copyright}}{{else}}&copy; <script>document.write(new Date().getFullYear())</script> {{if .Config.Brand.Name}}{{.Config.Brand.Name}}{{else}}{{.Repository.Name}}{{end}}{{end}}</span>
</div>
{{if .Config.Footer.Social}}
<div class="saas-footer-social">
{{range .Config.Footer.Social}}
<a href="{{.URL}}" title="{{.Platform}}">
{{if eq .Platform "twitter"}}{{svg "octicon-mention" 18}}
{{else if eq .Platform "github"}}{{svg "octicon-mark-github" 18}}
{{else if eq .Platform "discord"}}{{svg "octicon-comment-discussion" 18}}
{{else if eq .Platform "linkedin"}}{{svg "octicon-briefcase" 18}}
{{else if eq .Platform "youtube"}}{{svg "octicon-video" 18}}
{{else}}{{svg "octicon-link-external" 18}}{{end}}
</a>
{{end}}
</div>
{{end}}
<div class="saas-footer-links">
{{if .Config.Footer.Links}}
{{range .Config.Footer.Links}}
<a href="{{.URL}}" class="saas-footer-link">{{.Label}}</a>
{{end}}
{{else}}
<a href="{{.Repository.Link}}" class="saas-footer-link">Repository</a>
<a href="{{.Repository.Link}}/wiki" class="saas-footer-link">Docs</a>
<a href="{{.Repository.Link}}/releases" class="saas-footer-link">Releases</a>
<a href="{{.Repository.Link}}/issues" class="saas-footer-link">Issues</a>
{{end}}
</div>
</footer>
</div>
{{template "pages/base_footer" .}}

View File

@@ -1,66 +0,0 @@
{{template "pages/base_head" .}}
<div class="pages-landing pages-simple">
{{template "pages/header" .}}
<main class="pages-main">
{{if .Config.Hero.Title}}
<section class="pages-hero">
<div class="container">
{{if .Config.Branding.Logo}}
<img src="{{.Config.Branding.Logo}}" alt="{{.Repository.Name}}" class="pages-logo">
{{end}}
<h1 class="pages-title">{{.Config.Hero.Title}}</h1>
{{if .Config.Hero.Tagline}}
<p class="pages-tagline">{{.Config.Hero.Tagline}}</p>
{{end}}
<div class="pages-cta">
{{if .Config.Hero.CTAPrimary.Text}}
<a href="{{.Config.Hero.CTAPrimary.Link}}" class="ui primary button">
{{.Config.Hero.CTAPrimary.Text}}
</a>
{{end}}
{{if .Config.Hero.CTASecondary.Text}}
<a href="{{.Config.Hero.CTASecondary.Link}}" class="ui button {{if eq .Config.Hero.CTASecondary.Style "outline"}}basic{{end}}">
{{.Config.Hero.CTASecondary.Text}}
</a>
{{end}}
</div>
</div>
</section>
{{end}}
{{if .ReadmeContent}}
<section class="pages-readme">
<div class="container">
<div class="markup">
{{.ReadmeContent}}
</div>
</div>
</section>
{{end}}
<section class="pages-stats">
<div class="container">
<div class="pages-stats-grid">
<div class="pages-stat">
<span class="pages-stat-value">{{.NumStars}}</span>
<span class="pages-stat-label">{{ctx.Locale.Tr "repo.stars"}}</span>
</div>
<div class="pages-stat">
<span class="pages-stat-value">{{.NumForks}}</span>
<span class="pages-stat-label">{{ctx.Locale.Tr "repo.forks"}}</span>
</div>
{{if .Repository.PrimaryLanguage}}
<div class="pages-stat">
<span class="pages-stat-value">{{.Repository.PrimaryLanguage.Language}}</span>
<span class="pages-stat-label">{{ctx.Locale.Tr "repo.language"}}</span>
</div>
{{end}}
</div>
</div>
</section>
</main>
{{template "pages/footer" .}}
</div>
{{template "pages/base_footer" .}}

View File

@@ -173,7 +173,7 @@
</a>
{{end}}
{{if .Permission.CanRead ctx.Consts.RepoUnitTypePackages}}
{{if and (.Permission.CanRead ctx.Consts.RepoUnitTypePackages) (or .IsWriter .HasPackages)}}
<a href="{{.RepoLink}}/packages" class="{{if .IsPackagesPage}}active {{end}}item">
{{svg "octicon-package"}} {{ctx.Locale.Tr "packages.title"}}
</a>
@@ -198,7 +198,7 @@
</a>
{{end}}
{{if .Permission.CanRead ctx.Consts.RepoUnitTypeWiki}}
{{if and (.Permission.CanRead ctx.Consts.RepoUnitTypeWiki) (or .IsWriter .HasWiki)}}
<a class="{{if .PageIsWiki}}active {{end}}item" href="{{.RepoLink}}/wiki">
{{svg "octicon-book"}} {{ctx.Locale.Tr "repo.wiki"}}
</a>

View File

@@ -41,9 +41,35 @@
</a>
{{end}}
{{end}}
<a class="{{if .PageIsSettingsPages}}active {{end}}item" href="{{.RepoLink}}/settings/pages">
{{ctx.Locale.Tr "repo.settings.pages"}}
</a>
<details class="item toggleable-item" {{if or .PageIsSettingsPagesGeneral .PageIsSettingsPagesBrand .PageIsSettingsPagesHero .PageIsSettingsPagesContent .PageIsSettingsPagesSocial .PageIsSettingsPagesPricing .PageIsSettingsPagesFooter .PageIsSettingsPagesTheme}}open{{end}}>
<summary>{{ctx.Locale.Tr "repo.settings.pages"}}</summary>
<div class="menu">
<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 .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>
</div>
</details>
<details class="item toggleable-item" {{if or .PageIsSharedSettingsRunners .PageIsSharedSettingsSecrets .PageIsSharedSettingsVariables .PageIsActionsSettingsGeneral}}open{{end}}>
<summary>{{ctx.Locale.Tr "actions.actions"}}</summary>
<div class="menu">

View File

@@ -12,12 +12,13 @@
</div>
<form class="ui form" method="post">
{{.CsrfTokenHtml}}
<input type="hidden" name="action" value="update_template">
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.template"}}</label>
<select name="template" class="ui dropdown">
{{range .PagesTemplates}}
<option value="{{.}}" {{if eq $.PagesTemplate .}}selected{{end}}>{{.}}</option>
<option value="{{.}}" {{if eq $.PagesTemplate .}}selected{{end}}>{{index $.PagesTemplateNames .}}</option>
{{end}}
</select>
</div>
@@ -29,6 +30,7 @@
<div class="divider"></div>
<form class="ui form" method="post">
{{.CsrfTokenHtml}}
<input type="hidden" name="action" value="disable">
<button class="ui red button">{{ctx.Locale.Tr "repo.settings.pages.disable"}}</button>
</form>
@@ -39,12 +41,13 @@
</div>
<form class="ui form" method="post">
{{.CsrfTokenHtml}}
<input type="hidden" name="action" value="enable">
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.template"}}</label>
<select name="template" class="ui dropdown">
{{range .PagesTemplates}}
<option value="{{.}}">{{.}}</option>
<option value="{{.}}">{{index $.PagesTemplateNames .}}</option>
{{end}}
</select>
</div>
@@ -56,17 +59,6 @@
</div>
{{if .PagesEnabled}}
<h4 class="ui top attached header">
{{ctx.Locale.Tr "repo.settings.pages.configuration"}}
</h4>
<div class="ui attached segment">
<p>{{ctx.Locale.Tr "repo.settings.pages.config_desc"}}</p>
<p>{{ctx.Locale.Tr "repo.settings.pages.config_file_hint"}}</p>
<div class="ui secondary segment">
<code>.gitea/landing.yaml</code>
</div>
</div>
<h4 class="ui top attached header">
{{ctx.Locale.Tr "repo.settings.pages.custom_domains"}}
</h4>
@@ -106,6 +98,7 @@
<td class="tw-text-right">
{{if and .Verified (eq .SSLStatus "pending")}}
<form method="post" class="tw-inline-block">
{{$.CsrfTokenHtml}}
<input type="hidden" name="action" value="activate_ssl">
<input type="hidden" name="domain_id" value="{{.ID}}">
<button class="ui green tiny button">{{ctx.Locale.Tr "repo.settings.pages.activate_ssl"}}</button>
@@ -113,26 +106,21 @@
{{end}}
{{if not .Verified}}
<form method="post" class="tw-inline-block">
{{$.CsrfTokenHtml}}
<input type="hidden" name="action" value="verify_domain">
<input type="hidden" name="domain_id" value="{{.ID}}">
<button class="ui primary tiny button">{{ctx.Locale.Tr "repo.settings.pages.verify"}}</button>
</form>
{{end}}
<form method="post" class="tw-inline-block">
{{$.CsrfTokenHtml}}
<input type="hidden" name="action" value="delete_domain">
<input type="hidden" name="domain_id" value="{{.ID}}">
<button class="ui red tiny button">{{ctx.Locale.Tr "remove"}}</button>
</form>
</td>
</tr>
{{if and .Verified (eq .SSLStatus "pending")}}
<form method="post" class="tw-inline-block">
<input type="hidden" name="action" value="activate_ssl">
<input type="hidden" name="domain_id" value="{{.ID}}">
<button class="ui green tiny button">{{ctx.Locale.Tr "repo.settings.pages.activate_ssl"}}</button>
</form>
{{end}}
{{if not .Verified}}
{{if not .Verified}}
<tr>
<td colspan="4">
<div class="ui info message">
@@ -148,6 +136,7 @@
{{end}}
<form class="ui form" method="post">
{{.CsrfTokenHtml}}
<input type="hidden" name="action" value="add_domain">
<div class="inline field">
<label>{{ctx.Locale.Tr "repo.settings.pages.add_domain"}}</label>

View File

@@ -0,0 +1,29 @@
{{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.brand"}}
</h4>
<div class="ui attached segment">
<form class="ui form" method="post">
{{.CsrfTokenHtml}}
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.brand_name"}}</label>
<input name="brand_name" value="{{.Config.Brand.Name}}" placeholder="{{.Repository.Name}}">
<p class="help">{{ctx.Locale.Tr "repo.settings.pages.brand_name_help"}}</p>
</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>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.brand_tagline"}}</label>
<input name="brand_tagline" value="{{.Config.Brand.Tagline}}" placeholder="Your tagline here">
</div>
<div class="field">
<button class="ui primary button">{{ctx.Locale.Tr "save"}}</button>
</div>
</form>
</div>
</div>
{{template "repo/settings/layout_footer" .}}

View File

@@ -0,0 +1,212 @@
{{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.content"}}</h4>
<div class="ui attached segment">
<form class="ui form" method="post">
{{.CsrfTokenHtml}}
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.stats"}}</h5>
<div id="stats-container">
{{range $i, $stat := .Config.Stats}}
<div class="two fields stat-item">
<div class="field"><input name="stat_value_{{$i}}" value="{{$stat.Value}}" placeholder="10k+"></div>
<div class="field"><input name="stat_label_{{$i}}" value="{{$stat.Label}}" placeholder="Downloads"></div>
</div>
{{end}}
{{if not .Config.Stats}}
<div class="two fields stat-item">
<div class="field"><input name="stat_value_0" placeholder="10k+"></div>
<div class="field"><input name="stat_label_0" placeholder="Downloads"></div>
</div>
{{end}}
</div>
<button type="button" class="ui mini button" onclick="addStat()">+ Add Stat</button>
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.value_props"}}</h5>
<div id="valueprops-container">
{{range $i, $vp := .Config.ValueProps}}
<div class="fields valueprop-item">
<div class="four wide field"><input name="valueprop_title_{{$i}}" value="{{$vp.Title}}" placeholder="Title"></div>
<div class="eight wide field"><input name="valueprop_desc_{{$i}}" value="{{$vp.Description}}" placeholder="Description"></div>
<div class="four wide field">
<select name="valueprop_icon_{{$i}}" class="ui dropdown">
<option value="">Select Icon</option>
<option value="zap" {{if eq $vp.Icon "zap"}}selected{{end}}>⚡ Zap</option>
<option value="shield" {{if eq $vp.Icon "shield"}}selected{{end}}>🛡 Shield</option>
<option value="rocket" {{if eq $vp.Icon "rocket"}}selected{{end}}>🚀 Rocket</option>
<option value="check" {{if eq $vp.Icon "check"}}selected{{end}}>✓ Check</option>
<option value="star" {{if eq $vp.Icon "star"}}selected{{end}}>★ Star</option>
<option value="heart" {{if eq $vp.Icon "heart"}}selected{{end}}>♥ Heart</option>
<option value="lock" {{if eq $vp.Icon "lock"}}selected{{end}}>🔒 Lock</option>
<option value="globe" {{if eq $vp.Icon "globe"}}selected{{end}}>🌐 Globe</option>
<option value="clock" {{if eq $vp.Icon "clock"}}selected{{end}}>🕐 Clock</option>
<option value="gear" {{if eq $vp.Icon "gear"}}selected{{end}}>⚙ Gear</option>
<option value="code" {{if eq $vp.Icon "code"}}selected{{end}}>&lt;/&gt; Code</option>
<option value="terminal" {{if eq $vp.Icon "terminal"}}selected{{end}}>▶ Terminal</option>
<option value="package" {{if eq $vp.Icon "package"}}selected{{end}}>📦 Package</option>
<option value="database" {{if eq $vp.Icon "database"}}selected{{end}}>🗄 Database</option>
<option value="cloud" {{if eq $vp.Icon "cloud"}}selected{{end}}>☁ Cloud</option>
<option value="cpu" {{if eq $vp.Icon "cpu"}}selected{{end}}>💻 CPU</option>
<option value="graph" {{if eq $vp.Icon "graph"}}selected{{end}}>📈 Graph</option>
<option value="people" {{if eq $vp.Icon "people"}}selected{{end}}>👥 People</option>
<option value="tools" {{if eq $vp.Icon "tools"}}selected{{end}}>🔧 Tools</option>
<option value="light-bulb" {{if eq $vp.Icon "light-bulb"}}selected{{end}}>💡 Light Bulb</option>
</select>
</div>
</div>
{{end}}
{{if not .Config.ValueProps}}
<div class="fields valueprop-item">
<div class="four wide field"><input name="valueprop_title_0" placeholder="Title"></div>
<div class="eight wide field"><input name="valueprop_desc_0" placeholder="Description"></div>
<div class="four wide field">
<select name="valueprop_icon_0" class="ui dropdown">
<option value="">Select Icon</option>
<option value="zap">⚡ Zap</option>
<option value="shield">🛡 Shield</option>
<option value="rocket">🚀 Rocket</option>
<option value="check">✓ Check</option>
<option value="star">★ Star</option>
<option value="heart">♥ Heart</option>
<option value="lock">🔒 Lock</option>
<option value="globe">🌐 Globe</option>
<option value="clock">🕐 Clock</option>
<option value="gear">⚙ Gear</option>
<option value="code">&lt;/&gt; Code</option>
<option value="terminal">▶ Terminal</option>
<option value="package">📦 Package</option>
<option value="database">🗄 Database</option>
<option value="cloud">☁ Cloud</option>
<option value="cpu">💻 CPU</option>
<option value="graph">📈 Graph</option>
<option value="people">👥 People</option>
<option value="tools">🔧 Tools</option>
<option value="light-bulb">💡 Light Bulb</option>
</select>
</div>
</div>
{{end}}
</div>
<button type="button" class="ui mini button" onclick="addValueProp()">+ Add Value Prop</button>
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.features"}}</h5>
<div id="features-container">
{{range $i, $f := .Config.Features}}
<div class="fields feature-item">
<div class="four wide field"><input name="feature_title_{{$i}}" value="{{$f.Title}}" placeholder="Title"></div>
<div class="eight wide field"><input name="feature_desc_{{$i}}" value="{{$f.Description}}" placeholder="Description"></div>
<div class="four wide field">
<select name="feature_icon_{{$i}}" class="ui dropdown">
<option value="">Select Icon</option>
<option value="zap" {{if eq $f.Icon "zap"}}selected{{end}}>⚡ Zap</option>
<option value="shield" {{if eq $f.Icon "shield"}}selected{{end}}>🛡 Shield</option>
<option value="rocket" {{if eq $f.Icon "rocket"}}selected{{end}}>🚀 Rocket</option>
<option value="check" {{if eq $f.Icon "check"}}selected{{end}}>✓ Check</option>
<option value="star" {{if eq $f.Icon "star"}}selected{{end}}>★ Star</option>
<option value="heart" {{if eq $f.Icon "heart"}}selected{{end}}>♥ Heart</option>
<option value="lock" {{if eq $f.Icon "lock"}}selected{{end}}>🔒 Lock</option>
<option value="globe" {{if eq $f.Icon "globe"}}selected{{end}}>🌐 Globe</option>
<option value="clock" {{if eq $f.Icon "clock"}}selected{{end}}>🕐 Clock</option>
<option value="gear" {{if eq $f.Icon "gear"}}selected{{end}}>⚙ Gear</option>
<option value="code" {{if eq $f.Icon "code"}}selected{{end}}>&lt;/&gt; Code</option>
<option value="terminal" {{if eq $f.Icon "terminal"}}selected{{end}}>▶ Terminal</option>
<option value="package" {{if eq $f.Icon "package"}}selected{{end}}>📦 Package</option>
<option value="database" {{if eq $f.Icon "database"}}selected{{end}}>🗄 Database</option>
<option value="cloud" {{if eq $f.Icon "cloud"}}selected{{end}}>☁ Cloud</option>
<option value="cpu" {{if eq $f.Icon "cpu"}}selected{{end}}>💻 CPU</option>
<option value="graph" {{if eq $f.Icon "graph"}}selected{{end}}>📈 Graph</option>
<option value="people" {{if eq $f.Icon "people"}}selected{{end}}>👥 People</option>
<option value="tools" {{if eq $f.Icon "tools"}}selected{{end}}>🔧 Tools</option>
<option value="light-bulb" {{if eq $f.Icon "light-bulb"}}selected{{end}}>💡 Light Bulb</option>
</select>
</div>
</div>
{{end}}
{{if not .Config.Features}}
<div class="fields feature-item">
<div class="four wide field"><input name="feature_title_0" placeholder="Title"></div>
<div class="eight wide field"><input name="feature_desc_0" placeholder="Description"></div>
<div class="four wide field">
<select name="feature_icon_0" class="ui dropdown">
<option value="">Select Icon</option>
<option value="zap">⚡ Zap</option>
<option value="shield">🛡 Shield</option>
<option value="rocket">🚀 Rocket</option>
<option value="check">✓ Check</option>
<option value="star">★ Star</option>
<option value="heart">♥ Heart</option>
<option value="lock">🔒 Lock</option>
<option value="globe">🌐 Globe</option>
<option value="clock">🕐 Clock</option>
<option value="gear">⚙ Gear</option>
<option value="code">&lt;/&gt; Code</option>
<option value="terminal">▶ Terminal</option>
<option value="package">📦 Package</option>
<option value="database">🗄 Database</option>
<option value="cloud">☁ Cloud</option>
<option value="cpu">💻 CPU</option>
<option value="graph">📈 Graph</option>
<option value="people">👥 People</option>
<option value="tools">🔧 Tools</option>
<option value="light-bulb">💡 Light Bulb</option>
</select>
</div>
</div>
{{end}}
</div>
<button type="button" class="ui mini button" onclick="addFeature()">+ Add Feature</button>
<div class="field tw-mt-4">
<button class="ui primary button">{{ctx.Locale.Tr "save"}}</button>
</div>
</form>
</div>
</div>
<script>
const iconOptions = `
<option value="">Select Icon</option>
<option value="zap">⚡ Zap</option>
<option value="shield">🛡 Shield</option>
<option value="rocket">🚀 Rocket</option>
<option value="check">✓ Check</option>
<option value="star">★ Star</option>
<option value="heart">♥ Heart</option>
<option value="lock">🔒 Lock</option>
<option value="globe">🌐 Globe</option>
<option value="clock">🕐 Clock</option>
<option value="gear">⚙ Gear</option>
<option value="code">&lt;/&gt; Code</option>
<option value="terminal">▶ Terminal</option>
<option value="package">📦 Package</option>
<option value="database">🗄 Database</option>
<option value="cloud">☁ Cloud</option>
<option value="cpu">💻 CPU</option>
<option value="graph">📈 Graph</option>
<option value="people">👥 People</option>
<option value="tools">🔧 Tools</option>
<option value="light-bulb">💡 Light Bulb</option>
`;
let statCount = {{len .Config.Stats}};
let vpCount = {{len .Config.ValueProps}};
let featCount = {{len .Config.Features}};
if (statCount === 0) statCount = 1;
if (vpCount === 0) vpCount = 1;
if (featCount === 0) featCount = 1;
function addStat() {
const c = document.getElementById('stats-container');
c.insertAdjacentHTML('beforeend', `<div class="two fields stat-item"><div class="field"><input name="stat_value_${statCount}" placeholder="10k+"></div><div class="field"><input name="stat_label_${statCount}" placeholder="Downloads"></div></div>`);
statCount++;
}
function addValueProp() {
const c = document.getElementById('valueprops-container');
c.insertAdjacentHTML('beforeend', `<div class="fields valueprop-item"><div class="four wide field"><input name="valueprop_title_${vpCount}" placeholder="Title"></div><div class="eight wide field"><input name="valueprop_desc_${vpCount}" placeholder="Description"></div><div class="four wide field"><select name="valueprop_icon_${vpCount}" class="ui dropdown">${iconOptions}</select></div></div>`);
vpCount++;
}
function addFeature() {
const c = document.getElementById('features-container');
c.insertAdjacentHTML('beforeend', `<div class="fields feature-item"><div class="four wide field"><input name="feature_title_${featCount}" placeholder="Title"></div><div class="eight wide field"><input name="feature_desc_${featCount}" placeholder="Description"></div><div class="four wide field"><select name="feature_icon_${featCount}" class="ui dropdown">${iconOptions}</select></div></div>`);
featCount++;
}
</script>
{{template "repo/settings/layout_footer" .}}

View File

@@ -0,0 +1,116 @@
{{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.footer"}}</h4>
<div class="ui attached segment">
<form class="ui form" method="post">
{{.CsrfTokenHtml}}
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.cta_section"}}</h5>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.cta_headline"}}</label>
<input name="cta_headline" value="{{.Config.CTASection.Headline}}" placeholder="Ready to get started?">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.cta_subheadline"}}</label>
<input name="cta_subheadline" value="{{.Config.CTASection.Subheadline}}" placeholder="Start your free trial today">
</div>
<div class="two fields">
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.cta_button_label"}}</label>
<input name="cta_button_label" value="{{.Config.CTASection.Button.Label}}" placeholder="Get Started">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.cta_button_url"}}</label>
<input name="cta_button_url" value="{{.Config.CTASection.Button.URL}}" placeholder="/signup">
</div>
</div>
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.footer_links"}}</h5>
<div id="links-container">
{{range $i, $link := .Config.Footer.Links}}
<div class="two fields link-item">
<div class="field"><input name="footer_link_label_{{$i}}" value="{{$link.Label}}" placeholder="Label"></div>
<div class="field"><input name="footer_link_url_{{$i}}" value="{{$link.URL}}" placeholder="URL"></div>
</div>
{{end}}
{{if not .Config.Footer.Links}}
<div class="two fields link-item">
<div class="field"><input name="footer_link_label_0" placeholder="Documentation"></div>
<div class="field"><input name="footer_link_url_0" placeholder="/docs"></div>
</div>
{{end}}
</div>
<button type="button" class="ui mini button" onclick="addLink()">+ Add Link</button>
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.social_links"}}</h5>
<div id="social-container">
{{range $i, $s := .Config.Footer.Social}}
<div class="two fields social-item">
<div class="field">
<select name="social_platform_{{$i}}" class="ui dropdown">
<option value="">Select Platform</option>
<option value="twitter" {{if eq $s.Platform "twitter"}}selected{{end}}>Twitter</option>
<option value="github" {{if eq $s.Platform "github"}}selected{{end}}>GitHub</option>
<option value="discord" {{if eq $s.Platform "discord"}}selected{{end}}>Discord</option>
<option value="linkedin" {{if eq $s.Platform "linkedin"}}selected{{end}}>LinkedIn</option>
<option value="youtube" {{if eq $s.Platform "youtube"}}selected{{end}}>YouTube</option>
</select>
</div>
<div class="field"><input name="social_url_{{$i}}" value="{{$s.URL}}" placeholder="https://..."></div>
</div>
{{end}}
{{if not .Config.Footer.Social}}
<div class="two fields social-item">
<div class="field">
<select name="social_platform_0" class="ui dropdown">
<option value="">Select Platform</option>
<option value="twitter">Twitter</option>
<option value="github">GitHub</option>
<option value="discord">Discord</option>
<option value="linkedin">LinkedIn</option>
<option value="youtube">YouTube</option>
</select>
</div>
<div class="field"><input name="social_url_0" placeholder="https://..."></div>
</div>
{{end}}
</div>
<button type="button" class="ui mini button" onclick="addSocial()">+ Add Social</button>
<div class="field tw-mt-4">
<label>{{ctx.Locale.Tr "repo.settings.pages.copyright"}}</label>
<div class="ui action input">
<input id="footer-copyright" name="footer_copyright" value="{{.Config.Footer.Copyright}}" placeholder="© 2024 Your Company">
<button type="button" class="ui button" onclick="prependCopyright()" title="Add © symbol">©</button>
</div>
</div>
<div class="field">
<button class="ui primary button">{{ctx.Locale.Tr "save"}}</button>
</div>
</form>
</div>
</div>
<script>
let linkCount = {{len .Config.Footer.Links}};
let socialCount = {{len .Config.Footer.Social}};
if (linkCount === 0) linkCount = 1;
if (socialCount === 0) socialCount = 1;
function addLink() {
const c = document.getElementById('links-container');
c.insertAdjacentHTML('beforeend', `<div class="two fields link-item"><div class="field"><input name="footer_link_label_${linkCount}" placeholder="Label"></div><div class="field"><input name="footer_link_url_${linkCount}" placeholder="URL"></div></div>`);
linkCount++;
}
function addSocial() {
const c = document.getElementById('social-container');
c.insertAdjacentHTML('beforeend', `<div class="two fields social-item"><div class="field"><select name="social_platform_${socialCount}" class="ui dropdown"><option value="">Select Platform</option><option value="twitter">Twitter</option><option value="github">GitHub</option><option value="discord">Discord</option><option value="linkedin">LinkedIn</option><option value="youtube">YouTube</option></select></div><div class="field"><input name="social_url_${socialCount}" placeholder="https://..."></div></div>`);
socialCount++;
}
function prependCopyright() {
const input = document.getElementById('footer-copyright');
if (!input.value.startsWith('©')) {
input.value = '© ' + input.value;
}
input.focus();
}
</script>
{{template "repo/settings/layout_footer" .}}

View File

@@ -0,0 +1,55 @@
{{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.hero"}}</h4>
<div class="ui attached segment">
<form class="ui form" method="post">
{{.CsrfTokenHtml}}
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.headline"}}</label>
<input name="headline" value="{{.Config.Hero.Headline}}" placeholder="Your headline here">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.subheadline"}}</label>
<textarea name="subheadline" rows="2">{{.Config.Hero.Subheadline}}</textarea>
</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>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.video_url"}}</label>
<input name="video_url" value="{{.Config.Hero.VideoURL}}" placeholder="https://youtube.com/watch?v=...">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.code_example"}}</label>
<textarea name="code_example" rows="3" placeholder="npm install your-package">{{.Config.Hero.CodeExample}}</textarea>
</div>
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.primary_cta"}}</h5>
<div class="two fields">
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.cta_label"}}</label>
<input name="primary_cta_label" value="{{.Config.Hero.PrimaryCTA.Label}}" placeholder="Get Started">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.cta_url"}}</label>
<input name="primary_cta_url" value="{{.Config.Hero.PrimaryCTA.URL}}" placeholder="/docs">
</div>
</div>
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.secondary_cta"}}</h5>
<div class="two fields">
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.cta_label"}}</label>
<input name="secondary_cta_label" value="{{.Config.Hero.SecondaryCTA.Label}}" placeholder="Learn More">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.cta_url"}}</label>
<input name="secondary_cta_url" value="{{.Config.Hero.SecondaryCTA.URL}}" placeholder="#features">
</div>
</div>
<div class="field">
<button class="ui primary button">{{ctx.Locale.Tr "save"}}</button>
</div>
</form>
</div>
</div>
{{template "repo/settings/layout_footer" .}}

View File

@@ -0,0 +1,28 @@
{{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 .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>
</div>
{{end}}

View File

@@ -0,0 +1,57 @@
{{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.pricing"}}</h4>
<div class="ui attached segment">
<form class="ui form" method="post">
{{.CsrfTokenHtml}}
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.pricing_headline"}}</label>
<input name="pricing_headline" value="{{.Config.Pricing.Headline}}" placeholder="Simple, transparent pricing">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.pricing_subheadline"}}</label>
<input name="pricing_subheadline" value="{{.Config.Pricing.Subheadline}}" placeholder="Choose the plan that works for you">
</div>
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.plans"}}</h5>
<div id="plans-container">
{{range $i, $plan := .Config.Pricing.Plans}}
<div class="ui segment plan-item">
<div class="three fields">
<div class="field"><label>Name</label><input name="plan_name_{{$i}}" value="{{$plan.Name}}"></div>
<div class="field"><label>Price</label><input name="plan_price_{{$i}}" value="{{$plan.Price}}"></div>
<div class="field"><label>Period</label><input name="plan_period_{{$i}}" value="{{$plan.Period}}" placeholder="/month"></div>
</div>
<div class="field"><label>CTA Text</label><input name="plan_cta_{{$i}}" value="{{$plan.CTA}}" placeholder="Get Started"></div>
<div class="field"><div class="ui checkbox"><input type="checkbox" name="plan_featured_{{$i}}" {{if $plan.Featured}}checked{{end}}><label>Featured Plan</label></div></div>
<div class="field"><label>Features (one per line)</label><textarea name="plan_{{$i}}_features" rows="4">{{range $j, $f := $plan.Features}}{{$f}}&#10;{{end}}</textarea></div>
</div>
{{end}}
{{if not .Config.Pricing.Plans}}
<div class="ui segment plan-item">
<div class="three fields">
<div class="field"><label>Name</label><input name="plan_name_0" placeholder="Free"></div>
<div class="field"><label>Price</label><input name="plan_price_0" placeholder="$0"></div>
<div class="field"><label>Period</label><input name="plan_period_0" placeholder="/month"></div>
</div>
<div class="field"><label>CTA Text</label><input name="plan_cta_0" placeholder="Get Started"></div>
<div class="field"><div class="ui checkbox"><input type="checkbox" name="plan_featured_0"><label>Featured Plan</label></div></div>
</div>
{{end}}
</div>
<button type="button" class="ui mini button" onclick="addPlan()">+ Add Plan</button>
<div class="field tw-mt-4">
<button class="ui primary button">{{ctx.Locale.Tr "save"}}</button>
</div>
</form>
</div>
</div>
<script>
let planCount = {{len .Config.Pricing.Plans}};
if (planCount === 0) planCount = 1;
function addPlan() {
const c = document.getElementById('plans-container');
c.insertAdjacentHTML('beforeend', `<div class="ui segment plan-item"><div class="three fields"><div class="field"><label>Name</label><input name="plan_name_${planCount}" placeholder="Pro"></div><div class="field"><label>Price</label><input name="plan_price_${planCount}" placeholder="$29"></div><div class="field"><label>Period</label><input name="plan_period_${planCount}" placeholder="/month"></div></div><div class="field"><label>CTA Text</label><input name="plan_cta_${planCount}" placeholder="Get Started"></div><div class="field"><div class="ui checkbox"><input type="checkbox" name="plan_featured_${planCount}"><label>Featured Plan</label></div></div></div>`);
planCount++;
}
</script>
{{template "repo/settings/layout_footer" .}}

View File

@@ -0,0 +1,69 @@
{{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.social"}}</h4>
<div class="ui attached segment">
<form class="ui form" method="post">
{{.CsrfTokenHtml}}
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.company_logos"}}</h5>
<div id="logos-container">
{{range $i, $logo := .Config.SocialProof.Logos}}
<div class="field logo-item">
<input name="logo_{{$i}}" value="{{$logo}}" placeholder="Company name or logo URL">
</div>
{{end}}
{{if not .Config.SocialProof.Logos}}
<div class="field logo-item">
<input name="logo_0" placeholder="Company name or logo URL">
</div>
{{end}}
</div>
<button type="button" class="ui mini button" onclick="addLogo()">+ Add Logo</button>
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.testimonials"}}</h5>
<div id="testimonials-container">
{{range $i, $t := .Config.SocialProof.Testimonials}}
<div class="ui segment testimonial-item">
<div class="field"><label>Quote</label><textarea name="testimonial_quote_{{$i}}" rows="2">{{$t.Quote}}</textarea></div>
<div class="two fields">
<div class="field"><label>Author</label><input name="testimonial_author_{{$i}}" value="{{$t.Author}}"></div>
<div class="field"><label>Role</label><input name="testimonial_role_{{$i}}" value="{{$t.Role}}"></div>
</div>
<div class="field"><label>Avatar URL</label><input name="testimonial_avatar_{{$i}}" value="{{$t.Avatar}}"></div>
</div>
{{end}}
{{if not .Config.SocialProof.Testimonials}}
<div class="ui segment testimonial-item">
<div class="field"><label>Quote</label><textarea name="testimonial_quote_0" rows="2" placeholder="Great product!"></textarea></div>
<div class="two fields">
<div class="field"><label>Author</label><input name="testimonial_author_0" placeholder="John Doe"></div>
<div class="field"><label>Role</label><input name="testimonial_role_0" placeholder="CEO, Company"></div>
</div>
<div class="field"><label>Avatar URL</label><input name="testimonial_avatar_0" placeholder="https://..."></div>
</div>
{{end}}
</div>
<button type="button" class="ui mini button" onclick="addTestimonial()">+ Add Testimonial</button>
<div class="field tw-mt-4">
<button class="ui primary button">{{ctx.Locale.Tr "save"}}</button>
</div>
</form>
</div>
</div>
<script>
let logoCount = {{len .Config.SocialProof.Logos}};
let testCount = {{len .Config.SocialProof.Testimonials}};
if (logoCount === 0) logoCount = 1;
if (testCount === 0) testCount = 1;
function addLogo() {
const c = document.getElementById('logos-container');
c.insertAdjacentHTML('beforeend', `<div class="field logo-item"><input name="logo_${logoCount}" placeholder="Company name or logo URL"></div>`);
logoCount++;
}
function addTestimonial() {
const c = document.getElementById('testimonials-container');
c.insertAdjacentHTML('beforeend', `<div class="ui segment testimonial-item"><div class="field"><label>Quote</label><textarea name="testimonial_quote_${testCount}" rows="2" placeholder="Great product!"></textarea></div><div class="two fields"><div class="field"><label>Author</label><input name="testimonial_author_${testCount}" placeholder="John Doe"></div><div class="field"><label>Role</label><input name="testimonial_role_${testCount}" placeholder="CEO, Company"></div></div><div class="field"><label>Avatar URL</label><input name="testimonial_avatar_${testCount}" placeholder="https://..."></div></div>`);
testCount++;
}
</script>
{{template "repo/settings/layout_footer" .}}

View File

@@ -0,0 +1,109 @@
{{template "repo/settings/layout_head" (dict "ctxData" . "pageClass" "repository settings pages")}}
<style>
.color-picker-wrapper {
display: flex;
align-items: center;
gap: 12px;
}
.color-picker-wrapper input[type="color"] {
width: 60px;
height: 40px;
padding: 0;
border: 1px solid #ddd;
border-radius: 6px;
cursor: pointer;
}
.color-preview {
width: 40px;
height: 40px;
border-radius: 6px;
border: 2px solid #ccc;
}
.color-hex {
font-family: monospace;
font-size: 14px;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
width: 100px;
text-transform: uppercase;
}
</style>
<div class="user-main-content twelve wide column">
<h4 class="ui top attached header">{{ctx.Locale.Tr "repo.settings.pages.theme"}}</h4>
<div class="ui attached segment">
<form class="ui form" method="post">
{{.CsrfTokenHtml}}
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.theme_colors"}}</h5>
<div class="two fields">
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.primary_color"}}</label>
<div class="color-picker-wrapper">
<input type="color" id="primary_color" name="primary_color" value="{{if .Config.Theme.PrimaryColor}}{{.Config.Theme.PrimaryColor}}{{else}}#3b82f6{{end}}" onchange="updateColorPreview('primary')">
<div class="color-preview" id="primary_preview" style="background: {{if .Config.Theme.PrimaryColor}}{{.Config.Theme.PrimaryColor}}{{else}}#3b82f6{{end}};"></div>
<input type="text" class="color-hex" id="primary_hex" value="{{if .Config.Theme.PrimaryColor}}{{.Config.Theme.PrimaryColor}}{{else}}#3b82f6{{end}}" onchange="updateColorFromHex('primary')">
</div>
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.accent_color"}}</label>
<div class="color-picker-wrapper">
<input type="color" id="accent_color" name="accent_color" value="{{if .Config.Theme.AccentColor}}{{.Config.Theme.AccentColor}}{{else}}#10b981{{end}}" onchange="updateColorPreview('accent')">
<div class="color-preview" id="accent_preview" style="background: {{if .Config.Theme.AccentColor}}{{.Config.Theme.AccentColor}}{{else}}#10b981{{end}};"></div>
<input type="text" class="color-hex" id="accent_hex" value="{{if .Config.Theme.AccentColor}}{{.Config.Theme.AccentColor}}{{else}}#10b981{{end}}" onchange="updateColorFromHex('accent')">
</div>
</div>
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.theme_mode"}}</label>
<select name="theme_mode" class="ui dropdown">
<option value="auto" {{if eq .Config.Theme.Mode "auto"}}selected{{end}}>Auto</option>
<option value="light" {{if eq .Config.Theme.Mode "light"}}selected{{end}}>Light</option>
<option value="dark" {{if eq .Config.Theme.Mode "dark"}}selected{{end}}>Dark</option>
</select>
</div>
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.pages.seo"}}</h5>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.seo_title"}}</label>
<input name="seo_title" value="{{.Config.SEO.Title}}" placeholder="Page title for search engines">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.seo_description"}}</label>
<textarea name="seo_description" rows="2" placeholder="Meta description for search engines">{{.Config.SEO.Description}}</textarea>
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.seo_keywords"}}</label>
<input name="seo_keywords" value="{{.Config.SEO.Keywords}}" placeholder="keyword1, keyword2, keyword3">
</div>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.pages.og_image"}}</label>
<input name="og_image" value="{{.Config.SEO.OGImage}}" placeholder="https://example.com/og-image.png">
</div>
<div class="field">
<button class="ui primary button">{{ctx.Locale.Tr "save"}}</button>
</div>
</form>
</div>
</div>
<script>
function updateColorPreview(type) {
const colorInput = document.getElementById(type + '_color');
const preview = document.getElementById(type + '_preview');
const hexInput = document.getElementById(type + '_hex');
preview.style.background = colorInput.value;
hexInput.value = colorInput.value;
}
function updateColorFromHex(type) {
const colorInput = document.getElementById(type + '_color');
const preview = document.getElementById(type + '_preview');
const hexInput = document.getElementById(type + '_hex');
let hex = hexInput.value;
if (!hex.startsWith('#')) hex = '#' + hex;
if (/^#[0-9A-Fa-f]{6}$/.test(hex)) {
colorInput.value = hex;
preview.style.background = hex;
}
}
</script>
{{template "repo/settings/layout_footer" .}}

View File

@@ -1,14 +1,75 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>GitCaddy API</title>
<link href="{{AssetUrlPrefix}}/css/swagger.css?v={{AssetVersion}}" rel="stylesheet">
</head>
<body>
{{/* TODO: add Help & Glossary to help users understand the API, and explain some concepts like "Owner" */}}
<a class="swagger-back-link" href="{{AppSubUrl}}/">{{svg "octicon-reply"}}{{ctx.Locale.Tr "return_to_gitea"}}</a>
<div id="swagger-ui" data-source="{{AppSubUrl}}/swagger.{{.APIJSONVersion}}.json"></div>
<footer class="page-footer"></footer>
<script src="{{AssetUrlPrefix}}/js/swagger.js?v={{AssetVersion}}"></script>
</body>
<html>
<head>
<title>GitCaddy API v1 Reference</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
:root {
--scalar-color-1: #609926;
--scalar-color-accent: #609926;
--scalar-background-1: #ffffff;
--scalar-background-2: #f8f9fa;
--scalar-background-3: #e9ecef;
}
@media (prefers-color-scheme: dark) {
:root {
--scalar-background-1: #1a1a1a;
--scalar-background-2: #252525;
--scalar-background-3: #333333;
}
}
.api-version-bar {
background: var(--scalar-background-2, #f8f9fa);
padding: 8px 16px;
display: flex;
align-items: center;
gap: 12px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 14px;
border-bottom: 1px solid var(--scalar-background-3, #e9ecef);
}
.api-version-bar a {
color: #609926;
text-decoration: none;
padding: 4px 12px;
border-radius: 4px;
transition: background 0.2s;
}
.api-version-bar a:hover {
background: rgba(96, 153, 38, 0.1);
}
.api-version-bar a.active {
background: #609926;
color: white;
}
.api-version-bar .back-link {
margin-right: auto;
}
</style>
</head>
<body>
<div class="api-version-bar">
<a href="{{AppSubUrl}}/" class="back-link">← Back to GitCaddy</a>
<span>API Version:</span>
<a href="{{AppSubUrl}}/api/swagger" class="active">v1</a>
<a href="{{AppSubUrl}}/api/v2/docs">v2</a>
</div>
<script
id="api-reference"
data-url="{{AppSubUrl}}/swagger.{{.APIJSONVersion}}.json"
data-configuration='{
"theme": "default",
"layout": "modern",
"showSidebar": true,
"hideModels": false,
"hideDownloadButton": false,
"defaultHttpClient": {
"targetKey": "shell",
"clientKey": "curl"
}
}'>
</script>
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
</body>
</html>

View File

@@ -8,31 +8,31 @@ gitea-theme-meta-info {
:root {
--is-dark-theme: true;
--color-primary: #4183c4;
--color-primary: #609926;
--color-primary-contrast: #ffffff;
--color-primary-dark-1: #548fca;
--color-primary-dark-2: #679cd0;
--color-primary-dark-3: #7aa8d6;
--color-primary-dark-4: #8db5dc;
--color-primary-dark-5: #b3cde7;
--color-primary-dark-6: #d9e6f3;
--color-primary-dark-7: #f4f8fb;
--color-primary-light-1: #3876b3;
--color-primary-light-2: #31699f;
--color-primary-light-3: #2b5c8b;
--color-primary-light-4: #254f77;
--color-primary-light-5: #193450;
--color-primary-light-6: #0c1a28;
--color-primary-light-7: #04080c;
--color-primary-alpha-10: #4183c419;
--color-primary-alpha-20: #4183c433;
--color-primary-alpha-30: #4183c44b;
--color-primary-alpha-40: #4183c466;
--color-primary-alpha-50: #4183c480;
--color-primary-alpha-60: #4183c499;
--color-primary-alpha-70: #4183c4b3;
--color-primary-alpha-80: #4183c4cc;
--color-primary-alpha-90: #4183c4e1;
--color-primary-dark-1: #77a846;
--color-primary-dark-2: #8fb767;
--color-primary-dark-3: #a7c687;
--color-primary-dark-4: #bfd6a8;
--color-primary-dark-5: #d7e5c8;
--color-primary-dark-6: #ebf2e4;
--color-primary-dark-7: #f7f9f4;
--color-primary-light-1: #548621;
--color-primary-light-2: #49751d;
--color-primary-light-3: #3f6419;
--color-primary-light-4: #345414;
--color-primary-light-5: #263d0f;
--color-primary-light-6: #152108;
--color-primary-light-7: #090f03;
--color-primary-alpha-10: #60992619;
--color-primary-alpha-20: #60992633;
--color-primary-alpha-30: #6099264b;
--color-primary-alpha-40: #60992666;
--color-primary-alpha-50: #60992680;
--color-primary-alpha-60: #60992699;
--color-primary-alpha-70: #609926b3;
--color-primary-alpha-80: #609926cc;
--color-primary-alpha-90: #609926e1;
--color-primary-hover: var(--color-primary-light-1);
--color-primary-active: var(--color-primary-light-2);
--color-secondary: #3b444c;

View File

@@ -8,31 +8,31 @@ gitea-theme-meta-info {
:root {
--is-dark-theme: false;
--color-primary: #4183c4;
--color-primary: #609926;
--color-primary-contrast: #ffffff;
--color-primary-dark-1: #3876b3;
--color-primary-dark-2: #31699f;
--color-primary-dark-3: #2b5c8b;
--color-primary-dark-4: #254f77;
--color-primary-dark-5: #193450;
--color-primary-dark-6: #0c1a28;
--color-primary-dark-7: #04080c;
--color-primary-light-1: #548fca;
--color-primary-light-2: #679cd0;
--color-primary-light-3: #7aa8d6;
--color-primary-light-4: #8db5dc;
--color-primary-light-5: #b3cde7;
--color-primary-light-6: #d9e6f3;
--color-primary-light-7: #f4f8fb;
--color-primary-alpha-10: #4183c419;
--color-primary-alpha-20: #4183c433;
--color-primary-alpha-30: #4183c44b;
--color-primary-alpha-40: #4183c466;
--color-primary-alpha-50: #4183c480;
--color-primary-alpha-60: #4183c499;
--color-primary-alpha-70: #4183c4b3;
--color-primary-alpha-80: #4183c4cc;
--color-primary-alpha-90: #4183c4e1;
--color-primary-dark-1: #548621;
--color-primary-dark-2: #49751d;
--color-primary-dark-3: #3f6419;
--color-primary-dark-4: #345414;
--color-primary-dark-5: #263d0f;
--color-primary-dark-6: #152108;
--color-primary-dark-7: #090f03;
--color-primary-light-1: #77a846;
--color-primary-light-2: #8fb767;
--color-primary-light-3: #a7c687;
--color-primary-light-4: #bfd6a8;
--color-primary-light-5: #d7e5c8;
--color-primary-light-6: #ebf2e4;
--color-primary-light-7: #f7f9f4;
--color-primary-alpha-10: #60992619;
--color-primary-alpha-20: #60992633;
--color-primary-alpha-30: #6099264b;
--color-primary-alpha-40: #60992666;
--color-primary-alpha-50: #60992680;
--color-primary-alpha-60: #60992699;
--color-primary-alpha-70: #609926b3;
--color-primary-alpha-80: #609926cc;
--color-primary-alpha-90: #609926e1;
--color-primary-hover: var(--color-primary-dark-1);
--color-primary-active: var(--color-primary-dark-2);
--color-secondary: #d0d7de;

View File

@@ -221,6 +221,16 @@ export default {
outputFilename: 'licenses.txt',
outputWriter: ({dependencies}: {dependencies: Array<Record<string, string>>}) => {
const line = '-'.repeat(80);
const header = `${line}
GitCaddy Server - Third Party Licenses
Copyright (c) 2024-2026 MarketAlly Inc.
https://marketally.com
GitCaddy is built on Gitea (https://gitea.com) and includes the following
open source software. We are grateful to all the contributors.
${line}
`;
const goJson = readFileSync('assets/go-licenses.json', 'utf8');
const goModules = JSON.parse(goJson).map(({name, licenseText}: Record<string, string>) => {
return {name, body: formatLicenseText(licenseText)};
@@ -230,10 +240,11 @@ export default {
});
const modules = [...goModules, ...jsModules].sort((a, b) => a.name.localeCompare(b.name));
return modules.map(({name, version, licenseName, body}) => {
const licenseList = modules.map(({name, version, licenseName, body}) => {
const title = licenseName ? `${name}@${version} - ${licenseName}` : name;
return `${line}\n${title}\n${line}\n${body}`;
}).join('\n');
return header + licenseList;
},
override: {
'khroma@*': {licenseName: 'MIT'}, // https://github.com/fabiospampinato/khroma/pull/33