Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60ff19efc9 | ||
|
|
78a4744798 | ||
|
|
6acad3de28 |
@@ -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",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(""),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 |
@@ -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 |
@@ -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 |
@@ -1,22 +1,3 @@
|
||||
================================================================================
|
||||
GitCaddy Server
|
||||
Copyright (c) 2024-2026 MarketAlly Inc.
|
||||
https://git.marketally.com/gitcaddy/gitcaddy-server
|
||||
|
||||
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.
|
||||
|
||||
================================================================================
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
@braintree/sanitize-url@7.1.1 - MIT
|
||||
--------------------------------------------------------------------------------
|
||||
@@ -1114,7 +1095,7 @@ THE SOFTWARE.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
@citation-js/date@0.5.1 - MIT
|
||||
@citation-js/name@0.4.2 - MIT
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
@@ -1330,30 +1311,6 @@ furnished to do so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
--------------------------------------------------------------------------------
|
||||
@github/combobox-nav@2.3.1 - MIT
|
||||
--------------------------------------------------------------------------------
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 GitHub
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
@@ -1399,6 +1356,29 @@ the following conditions:
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
--------------------------------------------------------------------------------
|
||||
@github/paste-markdown@1.5.3 - MIT
|
||||
--------------------------------------------------------------------------------
|
||||
Copyright (c) 2018 GitHub, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import path from 'node:path';
|
||||
import type {Config} from 'stylelint';
|
||||
|
||||
// Use process.cwd() for more reliable path resolution across different environments
|
||||
// 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 = [
|
||||
path.join(process.cwd(), 'web_src/css/base.css'),
|
||||
path.join(process.cwd(), 'web_src/css/themes/theme-gitea-light.css'),
|
||||
path.join(process.cwd(), 'web_src/css/themes/theme-gitea-dark.css'),
|
||||
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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
1000
templates/pages/bold-marketing.tmpl
Normal file
1000
templates/pages/bold-marketing.tmpl
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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" .}}
|
||||
@@ -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>
|
||||
|
||||
811
templates/pages/minimalist-docs.tmpl
Normal file
811
templates/pages/minimalist-docs.tmpl
Normal 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}}© <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" .}}
|
||||
941
templates/pages/open-source-hero.tmpl
Normal file
941
templates/pages/open-source-hero.tmpl
Normal 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}}© <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" .}}
|
||||
@@ -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" .}}
|
||||
@@ -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" .}}
|
||||
937
templates/pages/saas-conversion.tmpl
Normal file
937
templates/pages/saas-conversion.tmpl
Normal 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 {{svg "octicon-check" 14}} Open source {{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}}© <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" .}}
|
||||
@@ -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" .}}
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
29
templates/repo/settings/pages_brand.tmpl
Normal file
29
templates/repo/settings/pages_brand.tmpl
Normal 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" .}}
|
||||
212
templates/repo/settings/pages_content.tmpl
Normal file
212
templates/repo/settings/pages_content.tmpl
Normal 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}}></> 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"></> 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}}></> 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"></> 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"></> 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" .}}
|
||||
116
templates/repo/settings/pages_footer.tmpl
Normal file
116
templates/repo/settings/pages_footer.tmpl
Normal 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" .}}
|
||||
55
templates/repo/settings/pages_hero.tmpl
Normal file
55
templates/repo/settings/pages_hero.tmpl
Normal 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" .}}
|
||||
28
templates/repo/settings/pages_nav.tmpl
Normal file
28
templates/repo/settings/pages_nav.tmpl
Normal 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}}
|
||||
57
templates/repo/settings/pages_pricing.tmpl
Normal file
57
templates/repo/settings/pages_pricing.tmpl
Normal 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}} {{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" .}}
|
||||
69
templates/repo/settings/pages_social.tmpl
Normal file
69
templates/repo/settings/pages_social.tmpl
Normal 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" .}}
|
||||
109
templates/repo/settings/pages_theme.tmpl
Normal file
109
templates/repo/settings/pages_theme.tmpl
Normal 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" .}}
|
||||
Reference in New Issue
Block a user