2
0
Files
gitcaddy-server/templates/pages/documentation-first.tmpl
logikonline 22844f6437 fix(pages): use navigation labels for section headings and add defaults
Use LabelValueProps and LabelFeatures from navigation config for section headings instead of hardcoded text. Removes hardcoded subheadlines that don't translate well. Adds default headlines for blog ("Latest Posts"), gallery ("Gallery"), and comparison ("How We Compare") sections when not configured. Improves consistency and translation support across all 8 page templates.
2026-03-17 21:34:34 -04:00

1489 lines
48 KiB
Handlebars

{{template "pages/base_head" .}}
<style>
@import url('https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500&family=Source+Sans+3:wght@300;400;500;600;700&display=swap');
:root {
--df-bg: #faf8f5;
--df-surface: #ffffff;
--df-elevated: #f5f2ed;
--df-text: #2c2c2c;
--df-muted: #6b6560;
--df-dim: #a9a29a;
--df-border: #e8e2d9;
--df-accent: {{if .Config.Theme.PrimaryColor}}{{.Config.Theme.PrimaryColor}}{{else}}#0d7377{{end}};
--df-accent-dark: {{if .Config.Theme.AccentColor}}{{.Config.Theme.AccentColor}}{{else}}#0a5c5f{{end}};
--df-accent-light: color-mix(in srgb, var(--df-accent) 10%, transparent);
--df-accent-medium: color-mix(in srgb, var(--df-accent) 20%, transparent);
--df-warm-shadow: 0 1px 3px rgba(44, 44, 44, 0.06), 0 4px 16px rgba(44, 44, 44, 0.04);
}
html {
scroll-behavior: smooth;
}
html, body.pages-body {
overflow-x: hidden;
background: var(--df-bg) !important;
}
/* Paper-grain noise overlay */
.df-page::before {
content: "";
position: fixed;
inset: 0;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.03'/%3E%3C/svg%3E");
pointer-events: none;
z-index: 9999;
opacity: 0.5;
}
.df-page {
position: relative;
min-height: 100vh;
background: var(--df-bg);
color: var(--df-text);
font-family: 'Source Sans 3', -apple-system, sans-serif;
-webkit-font-smoothing: antialiased;
}
/* Reveal animation system */
.df-reveal {
opacity: 0;
transform: translateY(32px);
transition: opacity 0.8s cubic-bezier(0.16, 1, 0.3, 1), transform 0.8s cubic-bezier(0.16, 1, 0.3, 1);
}
.df-reveal.visible {
opacity: 1;
transform: translateY(0);
}
.df-reveal-delay-1 { transition-delay: 0.1s; }
.df-reveal-delay-2 { transition-delay: 0.2s; }
.df-reveal-delay-3 { transition-delay: 0.3s; }
.df-reveal-delay-4 { transition-delay: 0.4s; }
/* Navigation */
.df-nav {
position: fixed;
top: 0;
left: 0;
right: 0;
padding: 0 40px;
height: 64px;
display: flex;
align-items: center;
justify-content: space-between;
background: rgba(250, 248, 245, 0.92);
backdrop-filter: blur(16px) saturate(180%);
-webkit-backdrop-filter: blur(16px) saturate(180%);
border-bottom: 1px solid var(--df-border);
z-index: 100;
transition: background 0.3s ease;
}
.df-nav-brand {
display: flex;
align-items: center;
gap: 10px;
text-decoration: none;
color: inherit;
}
.df-nav-logo {
width: 30px;
height: 30px;
background: var(--df-accent);
border-radius: 7px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
transition: box-shadow 0.3s ease;
}
.df-nav-brand:hover .df-nav-logo {
box-shadow: 0 2px 12px var(--df-accent-medium);
}
.df-nav-name {
font-family: 'Lora', serif;
font-weight: 600;
font-size: 17px;
letter-spacing: -0.01em;
color: var(--df-text);
}
.df-nav-links {
display: flex;
align-items: center;
gap: 4px;
}
.df-nav-link {
color: var(--df-muted);
text-decoration: none;
font-size: 14px;
font-weight: 500;
padding: 8px 14px;
border-radius: 6px;
transition: all 0.2s ease;
}
.df-nav-link:hover {
color: var(--df-text);
background: var(--df-accent-light);
}
.df-nav-cta {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 16px;
background: var(--df-elevated);
border: 1px solid var(--df-border);
color: var(--df-text);
font-size: 13px;
font-weight: 500;
border-radius: 6px;
text-decoration: none;
transition: all 0.2s ease;
}
.df-nav-cta:hover {
background: var(--df-border);
border-color: var(--df-dim);
}
/* Mobile menu toggle */
.df-menu-toggle {
display: none;
background: none;
border: none;
color: var(--df-muted);
cursor: pointer;
padding: 8px;
}
/* Buttons */
.df-btn-primary {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 14px 28px;
background: var(--df-accent);
color: #fff;
font-weight: 600;
font-size: 15px;
border-radius: 8px;
text-decoration: none;
transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);
border: none;
cursor: pointer;
letter-spacing: 0.01em;
box-shadow: 0 1px 3px rgba(13, 115, 119, 0.2);
}
.df-btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 24px rgba(13, 115, 119, 0.25);
background: var(--df-accent-dark);
}
.df-btn-secondary {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 14px 28px;
background: transparent;
color: var(--df-accent);
font-weight: 600;
font-size: 15px;
border-radius: 8px;
text-decoration: none;
border: 2px solid var(--df-accent);
transition: all 0.25s ease;
}
.df-btn-secondary:hover {
background: var(--df-accent-light);
border-color: var(--df-accent-dark);
}
/* Hero Section */
.df-hero {
position: relative;
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 160px 40px 120px;
text-align: center;
}
.df-hero-content {
position: relative;
z-index: 1;
max-width: 800px;
}
/* Warm badge */
.df-badge {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 18px;
background: var(--df-accent-light);
border: 1px solid var(--df-accent-medium);
border-radius: 100px;
font-family: 'Source Sans 3', sans-serif;
font-size: 13px;
font-weight: 600;
color: var(--df-accent);
margin-bottom: 36px;
letter-spacing: 0.02em;
}
.df-badge-dot {
width: 7px;
height: 7px;
background: var(--df-accent);
border-radius: 50%;
animation: df-pulse 2.5s ease-in-out infinite;
}
@keyframes df-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
.df-hero h1 {
font-family: 'Lora', serif;
font-size: clamp(40px, 6vw, 64px);
font-weight: 700;
line-height: 1.12;
margin-bottom: 24px;
letter-spacing: -0.02em;
color: var(--df-text);
}
.df-hero-sub {
font-size: clamp(17px, 2vw, 20px);
color: var(--df-muted);
line-height: 1.7;
margin-bottom: 44px;
max-width: 560px;
margin-left: auto;
margin-right: auto;
font-weight: 400;
}
.df-hero-ctas {
display: flex;
gap: 14px;
justify-content: center;
margin-bottom: 52px;
}
/* Code block */
.df-code-block {
display: inline-flex;
align-items: center;
gap: 16px;
padding: 16px 24px;
background: var(--df-surface);
border: 1px solid var(--df-border);
border-radius: 10px;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 14px;
max-width: 480px;
margin: 0 auto;
position: relative;
overflow: hidden;
box-shadow: var(--df-warm-shadow);
}
.df-code-block::before {
content: "";
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
background: var(--df-accent);
border-radius: 3px 0 0 3px;
}
.df-code-prompt {
color: var(--df-accent);
font-weight: 600;
user-select: none;
}
.df-code-block code {
color: var(--df-text);
flex: 1;
text-align: left;
}
.df-copy-btn {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
background: var(--df-elevated);
border: 1px solid var(--df-border);
border-radius: 6px;
color: var(--df-muted);
cursor: pointer;
transition: all 0.2s ease;
flex-shrink: 0;
}
.df-copy-btn:hover {
background: var(--df-border);
color: var(--df-text);
}
/* Stats section */
.df-stats {
padding: 64px 40px;
border-top: 1px solid var(--df-border);
border-bottom: 1px solid var(--df-border);
background: var(--df-surface);
}
.df-stats-inner {
display: flex;
justify-content: center;
align-items: center;
max-width: 800px;
margin: 0 auto;
flex-wrap: wrap;
gap: 0;
}
.df-stat-item {
text-align: center;
padding: 0 40px;
position: relative;
}
.df-stat-item:not(:last-child)::after {
content: "";
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
width: 1px;
height: 40px;
background: var(--df-border);
}
.df-stat-value {
font-family: 'Lora', serif;
font-size: 32px;
font-weight: 700;
color: var(--df-text);
letter-spacing: -0.02em;
}
.df-stat-label {
font-size: 13px;
color: var(--df-muted);
margin-top: 6px;
text-transform: uppercase;
letter-spacing: 0.08em;
font-weight: 600;
}
/* Downloads section */
.df-downloads {
padding: 80px 40px;
position: relative;
}
.df-downloads-inner {
max-width: 800px;
margin: 0 auto;
text-align: center;
}
.df-downloads h2 {
font-family: 'Lora', serif;
font-size: clamp(24px, 3vw, 32px);
font-weight: 700;
margin-bottom: 8px;
letter-spacing: -0.02em;
color: var(--df-text);
}
.df-downloads p {
color: var(--df-muted);
margin-bottom: 24px;
font-size: 16px;
}
.df-downloads-grid {
display: flex;
flex-wrap: wrap;
gap: 12px;
justify-content: center;
}
.df-download-item {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
background: var(--df-surface);
border: 1px solid var(--df-border);
border-radius: 8px;
color: var(--df-text);
text-decoration: none;
font-size: 14px;
transition: all 0.2s;
box-shadow: var(--df-warm-shadow);
}
.df-download-item:hover {
border-color: var(--df-accent);
box-shadow: 0 2px 12px var(--df-accent-medium);
}
.df-download-size {
font-size: 12px;
color: var(--df-dim);
}
/* Features / Value Props sections */
.df-features {
padding: 120px 40px;
position: relative;
}
.df-features-inner {
max-width: 1100px;
margin: 0 auto;
}
.df-section-label {
font-family: 'Source Sans 3', sans-serif;
font-size: 13px;
font-weight: 700;
color: var(--df-accent);
text-transform: uppercase;
letter-spacing: 0.12em;
margin-bottom: 16px;
text-align: center;
}
.df-section-header {
text-align: center;
margin-bottom: 64px;
}
.df-section-header h2 {
font-family: 'Lora', serif;
font-size: clamp(28px, 4vw, 42px);
font-weight: 700;
margin-bottom: 16px;
letter-spacing: -0.02em;
color: var(--df-text);
}
.df-section-header p {
font-size: 17px;
color: var(--df-muted);
max-width: 480px;
margin: 0 auto;
font-weight: 400;
line-height: 1.6;
}
.df-features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
.df-feature-card {
padding: 32px;
background: var(--df-surface);
border: 1px solid var(--df-border);
border-radius: 12px;
transition: all 0.35s cubic-bezier(0.16, 1, 0.3, 1);
position: relative;
overflow: hidden;
box-shadow: var(--df-warm-shadow);
}
.df-feature-card:hover {
border-color: var(--df-accent);
transform: translateY(-4px);
box-shadow: 0 8px 32px rgba(44, 44, 44, 0.08);
}
.df-feature-icon {
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
background: var(--df-accent-light);
border: 1px solid var(--df-accent-medium);
border-radius: 10px;
color: var(--df-accent);
margin-bottom: 20px;
}
.df-feature-title {
font-family: 'Lora', serif;
font-size: 18px;
font-weight: 600;
color: var(--df-text);
margin-bottom: 8px;
}
.df-feature-desc {
font-size: 15px;
color: var(--df-muted);
line-height: 1.65;
font-weight: 400;
}
/* Social proof */
.df-social-proof {
padding: 100px 40px;
background: var(--df-surface);
border-top: 1px solid var(--df-border);
border-bottom: 1px solid var(--df-border);
}
.df-social-proof-inner {
max-width: 1100px;
margin: 0 auto;
}
.df-logos {
display: flex;
justify-content: center;
gap: 48px;
margin-bottom: 64px;
flex-wrap: wrap;
}
.df-logo-item {
padding: 10px 20px;
font-family: 'Lora', serif;
font-size: 15px;
font-weight: 600;
color: var(--df-dim);
letter-spacing: 0.01em;
transition: color 0.3s ease;
}
.df-logo-item:hover {
color: var(--df-muted);
}
.df-testimonial {
background: var(--df-bg);
border: 1px solid var(--df-border);
border-radius: 16px;
padding: 48px;
max-width: 680px;
margin: 0 auto;
position: relative;
box-shadow: var(--df-warm-shadow);
}
.df-testimonial-quote {
font-family: 'Lora', serif;
font-size: 20px;
line-height: 1.7;
color: var(--df-text);
margin-bottom: 28px;
font-weight: 400;
font-style: italic;
}
.df-testimonial-author {
font-weight: 600;
color: var(--df-text);
font-size: 15px;
}
.df-testimonial-role {
font-size: 13px;
color: var(--df-muted);
}
/* Pricing */
.df-pricing {
padding: 120px 40px;
}
.df-pricing-inner {
max-width: 1100px;
margin: 0 auto;
}
.df-pricing-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
margin-top: 64px;
}
.df-pricing-card {
background: var(--df-surface);
border: 1px solid var(--df-border);
border-radius: 14px;
padding: 36px;
position: relative;
transition: all 0.35s cubic-bezier(0.16, 1, 0.3, 1);
box-shadow: var(--df-warm-shadow);
}
.df-pricing-card:hover {
border-color: var(--df-dim);
transform: translateY(-4px);
box-shadow: 0 8px 32px rgba(44, 44, 44, 0.08);
}
.df-pricing-card.featured {
border-color: var(--df-accent);
background: linear-gradient(180deg, color-mix(in srgb, var(--df-accent) 3%, var(--df-surface)) 0%, var(--df-surface) 100%);
box-shadow: 0 4px 24px rgba(13, 115, 119, 0.1);
}
.df-pricing-badge {
position: absolute;
top: -11px;
left: 50%;
transform: translateX(-50%);
padding: 5px 14px;
background: var(--df-accent);
color: #fff;
font-size: 11px;
font-weight: 700;
border-radius: 6px;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.df-pricing-name {
font-family: 'Lora', serif;
font-size: 20px;
font-weight: 600;
color: var(--df-text);
margin-bottom: 8px;
}
.df-pricing-price {
font-family: 'Lora', serif;
font-size: 44px;
font-weight: 700;
color: var(--df-text);
line-height: 1;
margin-bottom: 4px;
letter-spacing: -0.03em;
}
.df-pricing-period {
font-size: 14px;
color: var(--df-muted);
margin-bottom: 28px;
}
.df-pricing-features {
list-style: none;
padding: 0;
margin: 0 0 32px 0;
}
.df-pricing-features li {
padding: 10px 0;
border-bottom: 1px solid var(--df-border);
font-size: 14px;
color: var(--df-muted);
display: flex;
align-items: center;
gap: 10px;
}
.df-pricing-features li::before {
content: "\2713";
color: var(--df-accent);
font-weight: 700;
font-size: 14px;
flex-shrink: 0;
}
.df-pricing-features li:last-child {
border-bottom: none;
}
.df-pricing-cta {
display: block;
width: 100%;
padding: 13px 24px;
text-align: center;
background: var(--df-elevated);
color: var(--df-text);
font-weight: 600;
font-size: 14px;
border-radius: 8px;
text-decoration: none;
border: 1px solid var(--df-border);
transition: all 0.25s ease;
}
.df-pricing-cta:hover {
background: var(--df-border);
border-color: var(--df-dim);
}
.df-pricing-card.featured .df-pricing-cta {
background: var(--df-accent);
color: #fff;
border: none;
}
.df-pricing-card.featured .df-pricing-cta:hover {
transform: translateY(-2px);
box-shadow: 0 6px 24px rgba(13, 115, 119, 0.25);
background: var(--df-accent-dark);
}
/* CTA section */
.df-cta-section {
padding: 120px 40px;
text-align: center;
position: relative;
background: var(--df-surface);
border-top: 1px solid var(--df-border);
}
.df-cta-inner {
max-width: 600px;
margin: 0 auto;
position: relative;
z-index: 1;
}
.df-cta-section h2 {
font-family: 'Lora', serif;
font-size: clamp(28px, 4vw, 44px);
font-weight: 700;
margin-bottom: 16px;
letter-spacing: -0.02em;
color: var(--df-text);
}
.df-cta-section p {
font-size: 17px;
color: var(--df-muted);
margin-bottom: 36px;
font-weight: 400;
line-height: 1.6;
}
/* Blog content markdown styles */
.df-blog-content h1, .df-blog-content h2, .df-blog-content h3, .df-blog-content h4 {
color: var(--df-text); font-family: 'Lora', serif; margin: 1.5em 0 0.5em;
}
.df-blog-content h2 { font-size: 1.5em; border-bottom: 1px solid var(--df-border); padding-bottom: 8px; }
.df-blog-content h3 { font-size: 1.25em; }
.df-blog-content a { color: var(--df-accent); text-decoration: underline; }
.df-blog-content a:hover { opacity: 0.8; }
.df-blog-content code { background: var(--df-elevated); padding: 2px 6px; border-radius: 4px; font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; font-size: 0.9em; }
.df-blog-content pre { background: var(--df-elevated); border: 1px solid var(--df-border); border-radius: 8px; padding: 16px; overflow-x: auto; margin: 16px 0; }
.df-blog-content pre code { background: none; padding: 0; }
.df-blog-content blockquote { border-left: 3px solid var(--df-accent); padding-left: 16px; color: var(--df-muted); margin: 16px 0; font-style: italic; }
.df-blog-content img { max-width: 100%; border-radius: 8px; margin: 16px 0; }
.df-blog-content ul, .df-blog-content ol { padding-left: 24px; margin: 12px 0; }
.df-blog-content li { margin: 4px 0; }
.df-blog-content table { border-collapse: collapse; width: 100%; margin: 16px 0; }
.df-blog-content th, .df-blog-content td { border: 1px solid var(--df-border); padding: 8px 12px; text-align: left; }
.df-blog-content th { background: var(--df-elevated); font-weight: 600; }
.df-blog-content hr { border: none; border-top: 1px solid var(--df-border); margin: 24px 0; }
/* Footer */
.df-footer {
padding: 32px 40px;
border-top: 1px solid var(--df-border);
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 24px;
background: var(--df-surface);
}
.df-footer-brand {
display: flex;
align-items: center;
gap: 8px;
}
.df-footer-logo {
width: 22px;
height: 22px;
background: var(--df-accent-light);
border: 1px solid var(--df-accent-medium);
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
color: var(--df-accent);
}
.df-footer-text {
font-size: 13px;
color: var(--df-dim);
}
.df-footer-links {
display: flex;
gap: 24px;
flex-wrap: wrap;
}
.df-footer-link {
color: var(--df-muted);
text-decoration: none;
font-size: 13px;
transition: color 0.2s ease;
}
.df-footer-link:hover {
color: var(--df-text);
}
.df-footer-social {
display: flex;
gap: 12px;
}
.df-footer-social a {
color: var(--df-dim);
transition: color 0.2s ease;
padding: 4px;
}
.df-footer-social a:hover {
color: var(--df-accent);
}
/* Responsive */
@media (max-width: 768px) {
.df-nav { padding: 0 20px; }
.df-nav-links { display: none; }
.df-menu-toggle { display: flex; }
.df-hero { padding: 120px 24px 80px; }
.df-hero-ctas { flex-direction: column; align-items: center; }
.df-btn-primary, .df-btn-secondary { width: 100%; justify-content: center; }
.df-features, .df-social-proof, .df-cta-section, .df-pricing { padding: 80px 24px; }
.df-stats { padding: 48px 24px; }
.df-stat-item { padding: 16px 24px; }
.df-stat-item:not(:last-child)::after { display: none; }
.df-stats-inner { flex-direction: column; }
.df-features-grid { grid-template-columns: 1fr; }
.df-footer { flex-direction: column; text-align: center; padding: 24px; }
.df-testimonial { padding: 32px 24px; }
.df-code-block { font-size: 12px; }
}
@media (max-width: 480px) {
.df-hero h1 { font-size: 32px; }
.df-section-header h2 { font-size: 28px; }
.df-pricing-price { font-size: 36px; }
}
</style>
<div class="df-page">
<!-- Navigation -->
<nav class="df-nav">
<a href="{{.LandingURL}}" class="df-nav-brand">
{{if .LogoURL}}
<img src="{{.LogoURL}}" alt="{{.Config.Brand.Name}}" style="height: 30px; border-radius: 4px;">
{{else}}
<div class="df-nav-logo">
{{svg "octicon-book" 14}}
</div>
{{end}}
<span class="df-nav-name">{{if .Config.Brand.Name}}{{.Config.Brand.Name}}{{else}}{{.Repository.Name}}{{end}}</span>
</a>
<div class="df-nav-links">
{{range .Config.Footer.Links}}
<a href="{{.URL}}" class="df-nav-link">{{.Label}}</a>
{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="df-nav-link">{{if .Config.Navigation.LabelDocs}}{{.Config.Navigation.LabelDocs}}{{else}}Docs{{end}}</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="df-nav-link">{{if .Config.Navigation.LabelAPI}}{{.Config.Navigation.LabelAPI}}{{else}}API{{end}}</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="df-nav-link">{{if .Config.Navigation.LabelReleases}}{{.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="df-nav-link">{{if .Config.Navigation.LabelIssues}}{{.Config.Navigation.LabelIssues}}{{else}}Issues{{end}}</a>{{end}}
{{if .Config.ValueProps}}<a href="{{.LandingURL}}#value-props" class="df-nav-link">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Value Props{{end}}</a>{{end}}
{{if .Config.Features}}<a href="{{.LandingURL}}#features" class="df-nav-link">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="{{.LandingURL}}#pricing" class="df-nav-link">{{if .Config.Navigation.LabelPricing}}{{.Config.Navigation.LabelPricing}}{{else}}Pricing{{end}}</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}{{.LandingURL}}#blog{{end}}" class="df-nav-link">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="{{.LandingURL}}#gallery" class="df-nav-link">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</a>{{end}}
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}<a href="{{.LandingURL}}#comparison" class="df-nav-link">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</a>{{end}}
{{if .Config.Navigation.ShowRepository}}
<a href="{{.RepoURL}}" class="df-nav-cta">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
Repository
</a>
{{end}}
{{if .LangSwitcherEnabled}}
<div class="pages-lang-switcher">
<button class="pages-lang-btn" onclick="this.nextElementSibling.classList.toggle('open')">
{{svg "octicon-globe" 14}} {{index $.LanguageNames .ActiveLang}}
</button>
<div class="pages-lang-dropdown">
{{range .AvailableLanguages}}
<a href="?lang={{.}}" class="pages-lang-option{{if eq . $.ActiveLang}} active{{end}}">{{index $.LanguageNames .}}</a>
{{end}}
</div>
</div>
{{end}}
</div>
<button class="df-menu-toggle" onclick="this.parentElement.querySelector('.df-nav-links').style.display=this.parentElement.querySelector('.df-nav-links').style.display==='flex'?'none':'flex'">
{{svg "octicon-three-bars" 20}}
</button>
</nav>
{{if .PageIsBlogDetail}}
<!-- Blog Detail View -->
<section class="df-hero" style="padding-top: 120px; min-height: auto;">
<div class="df-hero-content" style="max-width: 800px;">
{{if .BlogPost.FeaturedImage}}
<div style="margin: 0 auto 32px; border-radius: 12px; overflow: hidden; border: 1px solid var(--df-border);">
<img src="{{.BlogPost.FeaturedImage.DownloadURL}}" alt="{{.BlogPost.Title}}" style="width: 100%; max-height: 400px; object-fit: cover; display: block;">
</div>
{{end}}
<h1 style="font-family: 'Lora', serif; font-size: 36px; margin-bottom: 8px;">{{.BlogPost.Title}}</h1>
{{if .BlogPost.Subtitle}}<p style="font-size: 1.2rem; color: var(--df-muted); margin-bottom: 24px;">{{.BlogPost.Subtitle}}</p>{{end}}
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 48px; font-size: 14px; color: var(--df-muted);">
{{if .BlogPost.Author}}<span>{{.BlogPost.Author.DisplayName}}</span><span>&middot;</span>{{end}}
<span>{{DateUtils.AbsoluteShort .BlogPost.CreatedUnix}}</span>
{{if .BlogTags}}<span>&middot;</span>{{range .BlogTags}}<span style="background: var(--df-accent-light); border: 1px solid var(--df-accent-medium); padding: 2px 10px; border-radius: 100px; font-size: 12px; color: var(--df-accent);">{{.}}</span> {{end}}{{end}}
</div>
<div class="markup df-blog-content" style="color: var(--df-text); line-height: 1.8; font-size: 18px; text-align: left;">
{{.BlogRenderedContent}}
</div>
<div style="margin-top: 48px; padding-top: 24px; border-top: 1px solid var(--df-border);">
<a href="{{.BlogBaseURL}}" class="df-btn-secondary" data-cta="secondary" style="text-decoration: none;">
{{svg "octicon-arrow-left" 16}} Back to Blog
</a>
</div>
</div>
</section>
{{else if .PageIsBlogList}}
<!-- Blog List View -->
<section class="df-features" style="padding-top: 120px;">
<div class="df-features-inner">
<div class="df-section-header df-reveal">
<div class="df-section-label">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</div>
<h2>{{if .Config.Blog.Headline}}{{.Config.Blog.Headline}}{{else}}All Posts{{end}}</h2>
{{if .Config.Blog.Subheadline}}<p>{{.Config.Blog.Subheadline}}</p>{{end}}
</div>
<div class="df-features-grid">
{{range .BlogListPosts}}
<a href="{{$.BlogBaseURL}}/{{.ID}}" class="df-feature-card df-reveal" style="text-decoration: none; color: inherit; display: flex; flex-direction: column;">
{{if .FeaturedImage}}
<div style="margin: -32px -32px 20px -32px; overflow: hidden; border-radius: 12px 12px 0 0;">
<img src="{{.FeaturedImage.DownloadURL}}" alt="{{.Title}}" style="width: 100%; height: 180px; object-fit: cover; display: block;">
</div>
{{end}}
<h3 class="df-feature-title">{{.Title}}</h3>
{{if and $.Config.Blog.ShowExcerpt .Subtitle}}
<p class="df-feature-desc">{{.Subtitle}}</p>
{{end}}
<div style="margin-top: auto; padding-top: 16px; font-size: 13px; color: var(--df-muted);">
{{if .Author}}{{.Author.DisplayName}} &middot; {{end}}{{DateUtils.AbsoluteShort .CreatedUnix}}
</div>
</a>
{{end}}
</div>
{{if gt .BlogListTotal 9}}
<div style="text-align: center; margin-top: 48px;">
{{template "base/paginate" .}}
</div>
{{end}}
</div>
</section>
{{else}}
<!-- Hero Section -->
<section class="df-hero" id="hero">
<div class="df-hero-content">
<div class="df-badge df-reveal visible">
<span class="df-badge-dot"></span>
{{if .LatestRelease}}v{{.LatestReleaseTag}} released{{else}}Open Source{{end}}
</div>
<h1 class="df-reveal visible df-reveal-delay-1">{{if .Config.Hero.Headline}}{{.Config.Hero.Headline}}{{else}}{{.Repository.Name}}{{end}}</h1>
<p class="df-hero-sub df-reveal visible df-reveal-delay-2">
{{if .Config.Hero.Subheadline}}{{.Config.Hero.Subheadline}}{{else}}{{.Repository.Description}}{{end}}
</p>
<div class="df-hero-ctas df-reveal visible df-reveal-delay-3">
{{if .Config.Hero.PrimaryCTA.Label}}
<a href="{{.Config.Hero.PrimaryCTA.URL}}" class="df-btn-primary" data-cta="primary">
{{.Config.Hero.PrimaryCTA.Label}}
{{svg "octicon-arrow-right" 16}}
</a>
{{end}}
{{if .Config.Hero.SecondaryCTA.Label}}
<a href="{{.Config.Hero.SecondaryCTA.URL}}" class="df-btn-secondary" data-cta="secondary">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
{{.Config.Hero.SecondaryCTA.Label}}
</a>
{{end}}
</div>
{{if or $.GooglePlayID $.AppStoreID}}
<div style="display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; margin-top: 16px;" class="df-reveal visible df-reveal-delay-4">
{{if $.GooglePlayID}}
<a href="https://play.google.com/store/apps/details?id={{$.GooglePlayID}}" target="_blank" rel="noopener" class="df-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3.609 1.814L13.792 12 3.61 22.186a.996.996 0 0 1-.61-.92V2.734a1 1 0 0 1 .609-.92zm10.89 10.893l2.302 2.302-10.937 6.333 8.635-8.635zm3.199-1.4l2.834 1.64a1 1 0 0 1 0 1.726l-2.834 1.64-2.635-2.636 2.635-2.37zM5.864 2.658L16.8 9.99l-2.302 2.302-8.635-8.635z"/></svg>
Google Play
</a>
{{end}}
{{if $.AppStoreID}}
<a href="https://apps.apple.com/app/{{$.AppStoreID}}" target="_blank" rel="noopener" class="df-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>
App Store
</a>
{{end}}
</div>
{{end}}
{{if .Config.Hero.CodeExample}}
<div class="df-code-block df-reveal visible df-reveal-delay-4">
<span class="df-code-prompt">$</span>
<code id="install-cmd">{{.Config.Hero.CodeExample}}</code>
<button class="df-copy-btn" onclick="navigator.clipboard.writeText(document.getElementById('install-cmd').textContent)">
{{svg "octicon-copy" 14}}
</button>
</div>
{{end}}
</div>
</section>
<!-- Stats Section -->
{{if or .Config.Stats (gt .NumStars 0)}}
<section class="df-stats" id="stats">
<div class="df-stats-inner df-reveal">
{{if .Config.Stats}}
{{range .Config.Stats}}
<div class="df-stat-item">
<div class="df-stat-value">{{.Value}}</div>
<div class="df-stat-label">{{.Label}}</div>
</div>
{{end}}
{{else}}
<div class="df-stat-item">
<div class="df-stat-value">{{.NumStars}}</div>
<div class="df-stat-label">Stars</div>
</div>
<div class="df-stat-item">
<div class="df-stat-value">{{.NumForks}}</div>
<div class="df-stat-label">Forks</div>
</div>
{{if .LatestRelease}}
<div class="df-stat-item">
<div class="df-stat-value">v{{.LatestReleaseTag}}</div>
<div class="df-stat-label">Latest</div>
</div>
{{end}}
{{end}}
</div>
</section>
{{end}}
<!-- Downloads Section -->
{{if and .PublicReleases .LatestRelease .LatestRelease.Attachments}}
<section class="df-downloads" id="downloads">
<div class="df-downloads-inner df-reveal">
<h2>Download v{{.LatestReleaseTag}}</h2>
<p>Get the latest release</p>
{{$windowsFiles := newSlice}}{{$macosFiles := newSlice}}{{$linuxFiles := newSlice}}{{$androidFiles := newSlice}}{{$iosFiles := newSlice}}{{$otherFiles := newSlice}}
{{range $att := .LatestRelease.Attachments}}
{{$name := StringUtils.ToLower $att.Name}}
{{if or (StringUtils.Contains $name "android") (StringUtils.HasSuffix $name ".apk") (StringUtils.HasSuffix $name ".aab") (StringUtils.HasSuffix $name ".xapk")}}
{{$androidFiles = Append $androidFiles $att}}
{{else if or (StringUtils.Contains $name "ios") (StringUtils.Contains $name "iphone") (StringUtils.Contains $name "ipad") (StringUtils.HasSuffix $name ".ipa")}}
{{$iosFiles = Append $iosFiles $att}}
{{else if or (StringUtils.Contains $name "windows") (StringUtils.Contains $name "win64") (StringUtils.Contains $name "win32") (StringUtils.Contains $name "-win.") (StringUtils.Contains $name "_win.") (StringUtils.Contains $name "-win-") (StringUtils.Contains $name "_win_") (StringUtils.HasSuffix $name ".exe") (StringUtils.HasSuffix $name ".msi") (StringUtils.HasSuffix $name ".msix") (StringUtils.HasSuffix $name ".msixbundle") (StringUtils.HasSuffix $name ".appx") (StringUtils.HasSuffix $name ".appxbundle")}}
{{$windowsFiles = Append $windowsFiles $att}}
{{else if or (StringUtils.Contains $name "darwin") (StringUtils.Contains $name "macos") (StringUtils.Contains $name "-mac.") (StringUtils.Contains $name "_mac.") (StringUtils.Contains $name "-mac-") (StringUtils.Contains $name "_mac_") (StringUtils.Contains $name "osx") (StringUtils.HasSuffix $name ".dmg") (StringUtils.HasSuffix $name ".pkg")}}
{{$macosFiles = Append $macosFiles $att}}
{{else if or (StringUtils.Contains $name "linux") (StringUtils.Contains $name "-lin.") (StringUtils.Contains $name "_lin.") (StringUtils.Contains $name "-lin-") (StringUtils.Contains $name "_lin_") (StringUtils.HasSuffix $name ".deb") (StringUtils.HasSuffix $name ".rpm") (StringUtils.HasSuffix $name ".appimage") (StringUtils.HasSuffix $name ".flatpak") (StringUtils.HasSuffix $name ".snap")}}
{{$linuxFiles = Append $linuxFiles $att}}
{{else}}
{{$otherFiles = Append $otherFiles $att}}
{{end}}
{{end}}
{{if $windowsFiles}}
<div style="margin-bottom: 24px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 13px; color: var(--df-muted); letter-spacing: 0.08em; text-transform: uppercase; font-weight: 700;">{{svg "octicon-device-desktop" 16}} Windows</h4>
<div class="df-downloads-grid">
{{range $windowsFiles}}<a href="{{$.RepoURL}}/releases/download/{{$.LatestRelease.TagName}}/{{.Name}}" class="df-download-item">{{svg "octicon-download" 16}} {{.Name}} <span class="df-download-size">{{FileSize .Size}}</span></a>{{end}}
</div>
</div>
{{end}}
{{if $macosFiles}}
<div style="margin-bottom: 24px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 13px; color: var(--df-muted); letter-spacing: 0.08em; text-transform: uppercase; font-weight: 700;">{{svg "octicon-device-desktop" 16}} macOS</h4>
<div class="df-downloads-grid">
{{range $macosFiles}}<a href="{{$.RepoURL}}/releases/download/{{$.LatestRelease.TagName}}/{{.Name}}" class="df-download-item">{{svg "octicon-download" 16}} {{.Name}} <span class="df-download-size">{{FileSize .Size}}</span></a>{{end}}
</div>
</div>
{{end}}
{{if $linuxFiles}}
<div style="margin-bottom: 24px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 13px; color: var(--df-muted); letter-spacing: 0.08em; text-transform: uppercase; font-weight: 700;">{{svg "octicon-terminal" 16}} Linux</h4>
<div class="df-downloads-grid">
{{range $linuxFiles}}<a href="{{$.RepoURL}}/releases/download/{{$.LatestRelease.TagName}}/{{.Name}}" class="df-download-item">{{svg "octicon-download" 16}} {{.Name}} <span class="df-download-size">{{FileSize .Size}}</span></a>{{end}}
</div>
</div>
{{end}}
{{if not $.HideMobileReleases}}
{{if $androidFiles}}
<div style="margin-bottom: 24px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 13px; color: var(--df-muted); letter-spacing: 0.08em; text-transform: uppercase; font-weight: 700;">{{svg "octicon-device-mobile" 16}} Android</h4>
<div class="df-downloads-grid">
{{range $androidFiles}}<a href="{{$.RepoURL}}/releases/download/{{$.LatestRelease.TagName}}/{{.Name}}" class="df-download-item">{{svg "octicon-download" 16}} {{.Name}} <span class="df-download-size">{{FileSize .Size}}</span></a>{{end}}
</div>
</div>
{{end}}
{{if $iosFiles}}
<div style="margin-bottom: 24px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 13px; color: var(--df-muted); letter-spacing: 0.08em; text-transform: uppercase; font-weight: 700;">{{svg "octicon-device-mobile" 16}} iOS</h4>
<div class="df-downloads-grid">
{{range $iosFiles}}<a href="{{$.RepoURL}}/releases/download/{{$.LatestRelease.TagName}}/{{.Name}}" class="df-download-item">{{svg "octicon-download" 16}} {{.Name}} <span class="df-download-size">{{FileSize .Size}}</span></a>{{end}}
</div>
</div>
{{end}}
{{end}}
{{if $otherFiles}}
<div style="margin-bottom: 24px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 13px; color: var(--df-muted); letter-spacing: 0.08em; text-transform: uppercase; font-weight: 700;">{{svg "octicon-file" 16}} Other</h4>
<div class="df-downloads-grid">
{{range $otherFiles}}<a href="{{$.RepoURL}}/releases/download/{{$.LatestRelease.TagName}}/{{.Name}}" class="df-download-item">{{svg "octicon-download" 16}} {{.Name}} <span class="df-download-size">{{FileSize .Size}}</span></a>{{end}}
</div>
</div>
{{end}}
</div>
</section>
{{end}}
<!-- Value Props Section -->
{{if .Config.ValueProps}}
<section class="df-features" id="value-props">
<div class="df-features-inner">
<div class="df-section-header df-reveal">
<div class="df-section-label">{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}Why choose us{{end}}</div>
<h2>{{if .Config.Navigation.LabelValueProps}}{{.Config.Navigation.LabelValueProps}}{{else}}{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}{{end}}</h2>
</div>
<div class="df-features-grid">
{{range .Config.ValueProps}}
<div class="df-feature-card df-reveal">
<div class="df-feature-icon">
{{svg (printf "octicon-%s" (or .Icon "check")) 20}}
</div>
<h3 class="df-feature-title">{{.Title}}</h3>
<p class="df-feature-desc">{{.Description}}</p>
</div>
{{end}}
</div>
</div>
</section>
{{end}}
<!-- Features Section -->
{{if .Config.Features}}
<section class="df-features" id="features" style="padding-top: 40px;">
<div class="df-features-inner">
<div class="df-section-header df-reveal">
<div class="df-section-label">{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Capabilities{{end}}</div>
<h2>{{if .Config.Navigation.LabelFeatures}}{{.Config.Navigation.LabelFeatures}}{{else}}Features{{end}}</h2>
</div>
<div class="df-features-grid">
{{range .Config.Features}}
<div class="df-feature-card df-reveal">
<div class="df-feature-icon">
{{svg (printf "octicon-%s" (or .Icon "zap")) 20}}
</div>
<h3 class="df-feature-title">{{.Title}}</h3>
<p class="df-feature-desc">{{.Description}}</p>
</div>
{{end}}
</div>
</div>
</section>
{{end}}
<!-- Social Proof -->
{{if or .Config.SocialProof.Logos .Config.SocialProof.Testimonials}}
<section class="df-social-proof" id="social-proof">
<div class="df-social-proof-inner">
{{if .Config.SocialProof.Logos}}
<div class="df-logos df-reveal">
{{range .Config.SocialProof.Logos}}
<div class="df-logo-item">{{.}}</div>
{{end}}
</div>
{{end}}
{{if .Config.SocialProof.Testimonials}}
<div class="df-testimonials-container">
{{range .Config.SocialProof.Testimonials}}
<div class="df-testimonial df-reveal" style="display: none;">
<p class="df-testimonial-quote">"{{.Quote}}"</p>
<div>
<div class="df-testimonial-author">{{.Author}}</div>
<div class="df-testimonial-role">{{.Role}}</div>
</div>
</div>
{{end}}
</div>
<script>
(function() {
var testimonials = document.querySelectorAll(".df-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="df-pricing" id="pricing">
<div class="df-pricing-inner">
<div class="df-section-header df-reveal">
<div class="df-section-label">{{if .Config.Navigation.LabelPricing}}{{.Config.Navigation.LabelPricing}}{{else}}Pricing{{end}}</div>
<h2>{{if .Config.Pricing.Headline}}{{.Config.Pricing.Headline}}{{else}}Pricing{{end}}</h2>
<p>{{if .Config.Pricing.Subheadline}}{{.Config.Pricing.Subheadline}}{{else}}Choose the plan that works for you{{end}}</p>
</div>
<div class="df-pricing-grid">
{{range .Config.Pricing.Plans}}
<div class="df-pricing-card{{if .Featured}} featured{{end}} df-reveal">
{{if .Featured}}<span class="df-pricing-badge">Popular</span>{{end}}
<div class="df-pricing-name">{{.Name}}</div>
<div class="df-pricing-price">{{.Price}}</div>
<div class="df-pricing-period">{{.Period}}</div>
{{if .Features}}
<ul class="df-pricing-features">
{{range .Features}}<li>{{.}}</li>{{end}}
</ul>
{{end}}
<a href="#" class="df-pricing-cta">{{if .CTA}}{{.CTA}}{{else}}Get Started{{end}}</a>
</div>
{{end}}
</div>
</div>
</section>
{{end}}
<!-- CTA Section -->
{{if .Config.CTASection.Headline}}
<section class="df-cta-section" id="cta">
<div class="df-cta-inner df-reveal">
<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}}{{.RepoURL}}{{end}}" class="df-btn-primary" data-cta="primary" style="padding: 16px 32px; font-size: 15px;">
{{if .Config.CTASection.Button.Label}}{{.Config.CTASection.Button.Label}}{{else}}Get Started{{end}}
{{svg "octicon-arrow-right" 16}}
</a>
</div>
</section>
{{end}}
<!-- Blog Section -->
{{if and .Config.Blog.Enabled .BlogPosts}}
<section class="df-features" id="blog" style="border-top: 1px solid var(--df-border);">
<div class="df-features-inner">
<div class="df-section-header df-reveal">
<div class="df-section-label">{{if .Config.Navigation.LabelBlog}}{{.Config.Navigation.LabelBlog}}{{else}}Blog{{end}}</div>
<h2>{{if .Config.Blog.Headline}}{{.Config.Blog.Headline}}{{else}}Latest Posts{{end}}</h2>
{{if .Config.Blog.Subheadline}}<p>{{.Config.Blog.Subheadline}}</p>{{end}}
</div>
<div class="df-features-grid">
{{range .BlogPosts}}
<a href="{{$.BlogBaseURL}}/{{.ID}}" class="df-feature-card df-reveal" style="text-decoration: none; color: inherit; display: flex; flex-direction: column;">
{{if .FeaturedImage}}
<div style="margin: -32px -32px 20px -32px; overflow: hidden; border-radius: 12px 12px 0 0;">
<img src="{{.FeaturedImage.DownloadURL}}" alt="{{.Title}}" style="width: 100%; height: 180px; object-fit: cover; display: block;">
</div>
{{end}}
<h3 class="df-feature-title">{{.Title}}</h3>
{{if and $.Config.Blog.ShowExcerpt .Subtitle}}
<p class="df-feature-desc">{{.Subtitle}}</p>
{{end}}
<div style="margin-top: auto; padding-top: 16px; font-size: 13px; color: var(--df-muted);">
{{if .Author}}{{.Author.DisplayName}}{{end}} · {{DateUtils.AbsoluteShort .CreatedUnix}}
</div>
</a>
{{end}}
</div>
{{if .Config.Blog.CTAButton.Label}}
<div style="text-align: center; margin-top: 48px;" class="df-reveal">
<a href="{{if .Config.Blog.CTAButton.URL}}{{.Config.Blog.CTAButton.URL}}{{else}}{{.BlogBaseURL}}{{end}}" class="df-btn-secondary" data-cta="secondary">
{{.Config.Blog.CTAButton.Label}}
{{svg "octicon-arrow-right" 16}}
</a>
</div>
{{end}}
</div>
</section>
{{end}}
<!-- Gallery Section -->
{{if and .Config.Gallery.Enabled .GalleryImages}}
<section class="df-features" id="gallery" style="border-top: 1px solid var(--df-border);">
<div class="df-features-inner">
<div class="df-section-header df-reveal">
<div class="df-section-label">{{if .Config.Navigation.LabelGallery}}{{.Config.Navigation.LabelGallery}}{{else}}Gallery{{end}}</div>
<h2>{{if .Config.Gallery.Headline}}{{.Config.Gallery.Headline}}{{else}}Gallery{{end}}</h2>
{{if .Config.Gallery.Subheadline}}<p>{{.Config.Gallery.Subheadline}}</p>{{end}}
</div>
<div style="display: grid; grid-template-columns: repeat({{if .Config.Gallery.Columns}}{{.Config.Gallery.Columns}}{{else}}3{{end}}, 1fr); gap: 16px;">
{{range .GalleryImages}}
<div class="df-feature-card df-reveal" style="padding: 0; overflow: hidden;">
<a href="{{.URL}}" class="pages-gallery-trigger" data-src="{{.URL}}" data-caption="{{if .Caption}}{{.Caption}}{{else}}{{.Name}}{{end}}" style="display: block; cursor: pointer;">
<img src="{{.URL}}" alt="{{if .Caption}}{{.Caption}}{{else}}{{.Name}}{{end}}" style="width: 100%; height: 220px; object-fit: cover; display: block;">
</a>
{{if .Caption}}
<div style="padding: 16px 20px; font-size: 13px; color: var(--df-muted);">{{.Caption}}</div>
{{end}}
</div>
{{end}}
</div>
</div>
</section>
{{end}}
<!-- Comparison Section -->
{{if and .Config.Comparison.Enabled .Config.Comparison.HasData}}
<section class="df-features" id="comparison" style="border-top: 1px solid var(--df-border);">
<div class="df-features-inner">
<div class="df-section-header df-reveal">
<div class="df-section-label">{{if .Config.Navigation.LabelCompare}}{{.Config.Navigation.LabelCompare}}{{else}}Compare{{end}}</div>
<h2>{{if .Config.Comparison.Headline}}{{.Config.Comparison.Headline}}{{else}}How We Compare{{end}}</h2>
{{if .Config.Comparison.Subheadline}}<p>{{.Config.Comparison.Subheadline}}</p>{{end}}
</div>
<div class="df-reveal" style="overflow-x: auto;">
<table style="width: 100%; border-collapse: collapse; font-size: 14px;">
<thead>
<tr>
<th style="text-align: left; padding: 16px 20px; border-bottom: 2px solid var(--df-dim); color: var(--df-muted); font-weight: 500; min-width: 200px;"></th>
{{range .Config.Comparison.Columns}}
<th style="text-align: center; padding: 16px 20px; border-bottom: 2px solid var(--df-dim); font-weight: 600; font-size: 15px;{{if .Highlight}} color: var(--df-accent);{{else}} color: var(--df-text);{{end}}">
{{.Name}}
{{if .Highlight}}<div style="width: 40px; height: 3px; background: var(--df-accent); margin: 8px auto 0; border-radius: 2px;"></div>{{end}}
</th>
{{end}}
</tr>
</thead>
<tbody>
{{range .Config.Comparison.Groups}}
{{if .Name}}
<tr>
<td colspan="4" style="padding: 20px 20px 8px; font-weight: 600; font-size: 13px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--df-muted); border-bottom: 1px solid var(--df-dim);">{{.Name}}</td>
</tr>
{{end}}
{{range .Features}}
<tr style="border-bottom: 1px solid rgba(0,0,0,0.06);">
<td style="padding: 14px 20px; color: var(--df-text);">{{.Name}}</td>
{{range .Values}}
<td style="text-align: center; padding: 14px 20px;">
{{if eq . "true"}}<span style="color: var(--df-accent);">{{svg "octicon-check-circle-fill" 18}}</span>
{{else if eq . "false"}}<span style="color: var(--df-dim);">{{svg "octicon-x-circle" 18}}</span>
{{else}}<span style="color: var(--df-text); font-weight: 500;">{{.}}</span>
{{end}}
</td>
{{end}}
</tr>
{{end}}
{{end}}
</tbody>
</table>
</div>
</div>
</section>
{{end}}
{{end}}{{/* end PageIsBlogDetail / PageIsBlogList / else */}}
<!-- Footer -->
<footer class="df-footer">
<div class="df-footer-brand">
<div class="df-footer-logo">{{svg "octicon-book" 12}}</div>
<span class="df-footer-text">{{if .Config.Footer.Copyright}}{{.Config.Footer.Copyright}}{{else}}&copy; <script>document.write(new Date().getFullYear())</script> {{if .Config.Brand.Name}}{{.Config.Brand.Name}}{{else}}{{.Repository.Name}}{{end}}{{end}}</span>
</div>
{{if .Config.Footer.Social}}
<div class="df-footer-social">
{{range .Config.Footer.Social}}
<a href="{{.URL}}" title="{{.Platform}}">
{{if eq .Platform "twitter"}}{{svg "octicon-mention" 16}}
{{else if eq .Platform "bluesky"}}{{svg "octicon-cloud" 16}}
{{else if eq .Platform "github"}}{{svg "octicon-mark-github" 16}}
{{else if eq .Platform "discord"}}{{svg "octicon-comment-discussion" 16}}
{{else if eq .Platform "linkedin"}}{{svg "octicon-briefcase" 16}}
{{else if eq .Platform "youtube"}}{{svg "octicon-video" 16}}
{{else if eq .Platform "instagram"}}{{svg "octicon-device-camera" 16}}
{{else if eq .Platform "facebook"}}{{svg "octicon-people" 16}}
{{else if eq .Platform "substack"}}{{svg "octicon-note" 16}}
{{else if eq .Platform "threads"}}{{svg "octicon-share" 16}}
{{else if eq .Platform "tiktok"}}{{svg "octicon-play" 16}}
{{else if eq .Platform "reddit"}}{{svg "octicon-hash" 16}}
{{else if eq .Platform "mastodon"}}{{svg "octicon-megaphone" 16}}
{{else if eq .Platform "twitch"}}{{svg "octicon-broadcast" 16}}
{{else if eq .Platform "rss"}}{{svg "octicon-rss" 16}}
{{else}}{{svg "octicon-link-external" 16}}{{end}}
</a>
{{end}}
</div>
{{end}}
<div class="df-footer-links">
{{range .Config.Footer.Links}}
<a href="{{.URL}}" class="df-footer-link">{{.Label}}</a>
{{end}}
{{if .Config.Navigation.ShowRepository}}<a href="{{.RepoURL}}" class="df-footer-link">Repository</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="df-footer-link">Documentation</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="df-footer-link">{{if .Config.Navigation.LabelReleases}}{{.Config.Navigation.LabelReleases}}{{else}}Releases{{end}}</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="df-footer-link">{{if .Config.Navigation.LabelIssues}}{{.Config.Navigation.LabelIssues}}{{else}}Issues{{end}}</a>{{end}}
</div>
</footer>
</div>
<!-- Scroll reveal observer -->
<script>
(function() {
var reveals = document.querySelectorAll('.df-reveal:not(.visible)');
if (!reveals.length) return;
var observer = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
observer.unobserve(entry.target);
}
});
}, { threshold: 0.1, rootMargin: '0px 0px -40px 0px' });
reveals.forEach(function(el) { observer.observe(el); });
})();
</script>
{{template "pages/base_footer" .}}