2
0
Files
gitcaddy-server/templates/pages/open-source-hero.tmpl
logikonline 433214fb91
Some checks failed
Build and Release / Create Release (push) Successful in 0s
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 2m56s
Build and Release / Unit Tests (push) Successful in 8m52s
Build and Release / Lint (push) Successful in 9m21s
Build and Release / Build Binaries (amd64, darwin, macos) (push) Failing after 1s
Build and Release / Build Binaries (arm64, darwin, macos) (push) Failing after 1s
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Successful in 4m54s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Successful in 9h5m27s
Build and Release / Build Binary (linux/arm64) (push) Failing after 13m25s
feat(pages): add app store links to downloads section
Add GooglePlayID and AppStoreID config fields to display app store download buttons on landing pages. Shows "App Stores" section with Google Play and/or App Store badges when IDs are configured. Useful for mobile apps distributed via stores instead of direct downloads. Includes branded SVG icons for both stores. Applies to all four page templates.
2026-03-16 02:04:33 -04:00

1520 lines
46 KiB
Handlebars

{{template "pages/base_head" .}}
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600;700&family=IBM+Plex+Sans:wght@300;400;500;600;700&display=swap');
:root {
--osh-bg: #0a0a0b;
--osh-surface: #111113;
--osh-elevated: #18181b;
--osh-text: #e4e4e7;
--osh-muted: #71717a;
--osh-dim: #3f3f46;
--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}};
--osh-glow: color-mix(in srgb, var(--osh-accent) 15%, transparent);
--osh-glow-strong: color-mix(in srgb, var(--osh-accent) 40%, transparent);
}
html {
scroll-behavior: smooth;
}
html, body.pages-body {
overflow-x: hidden;
background: var(--osh-bg) !important;
}
/* Scan line overlay for terminal feel */
.osh-page::before {
content: "";
position: fixed;
inset: 0;
background: repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0, 0, 0, 0.03) 2px,
rgba(0, 0, 0, 0.03) 4px
);
pointer-events: none;
z-index: 9999;
}
/* Dot grid pattern */
.osh-page::after {
content: "";
position: fixed;
inset: 0;
background-image: radial-gradient(circle, rgba(255,255,255,0.03) 1px, transparent 1px);
background-size: 20px 20px;
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;
-webkit-font-smoothing: antialiased;
}
/* Reveal animation system */
.osh-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);
}
.osh-reveal.visible {
opacity: 1;
transform: translateY(0);
}
.osh-reveal-delay-1 { transition-delay: 0.1s; }
.osh-reveal-delay-2 { transition-delay: 0.2s; }
.osh-reveal-delay-3 { transition-delay: 0.3s; }
.osh-reveal-delay-4 { transition-delay: 0.4s; }
/* Hero gradient with phosphor glow */
.osh-hero-gradient {
position: absolute;
top: -40%;
left: 50%;
transform: translateX(-50%);
width: 140%;
height: 100%;
background: radial-gradient(ellipse at center, var(--osh-glow) 0%, transparent 55%);
pointer-events: none;
animation: osh-breathe 8s ease-in-out infinite;
}
@keyframes osh-breathe {
0%, 100% { opacity: 0.6; transform: translateX(-50%) scale(1); }
50% { opacity: 1; transform: translateX(-50%) scale(1.05); }
}
/* Navigation */
.osh-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(10, 10, 11, 0.85);
backdrop-filter: blur(16px) saturate(180%);
-webkit-backdrop-filter: blur(16px) saturate(180%);
border-bottom: 1px solid rgba(255,255,255,0.04);
z-index: 100;
transition: background 0.3s ease;
}
.osh-nav-brand {
display: flex;
align-items: center;
gap: 10px;
text-decoration: none;
color: inherit;
}
.osh-nav-logo {
width: 30px;
height: 30px;
background: var(--osh-accent);
border-radius: 7px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 20px var(--osh-glow);
transition: box-shadow 0.3s ease;
}
.osh-nav-brand:hover .osh-nav-logo {
box-shadow: 0 0 30px var(--osh-glow-strong);
}
.osh-nav-name {
font-family: 'IBM Plex Mono', monospace;
font-weight: 600;
font-size: 16px;
letter-spacing: -0.02em;
}
.osh-nav-links {
display: flex;
align-items: center;
gap: 8px;
}
.osh-nav-link {
color: var(--osh-muted);
text-decoration: none;
font-size: 13px;
font-weight: 500;
padding: 8px 14px;
border-radius: 6px;
transition: all 0.2s ease;
letter-spacing: 0.01em;
}
.osh-nav-link:hover {
color: var(--osh-text);
background: rgba(255,255,255,0.04);
}
.osh-nav-cta {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 16px;
background: rgba(255,255,255,0.06);
border: 1px solid rgba(255,255,255,0.08);
color: var(--osh-text);
font-size: 13px;
font-weight: 500;
border-radius: 6px;
text-decoration: none;
transition: all 0.2s ease;
}
.osh-nav-cta:hover {
background: rgba(255,255,255,0.1);
border-color: rgba(255,255,255,0.15);
}
/* Mobile menu toggle */
.osh-menu-toggle {
display: none;
background: none;
border: none;
color: var(--osh-muted);
cursor: pointer;
padding: 8px;
}
/* Buttons */
.osh-btn-primary {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 14px 28px;
background: var(--osh-accent);
color: #000;
font-weight: 600;
font-size: 14px;
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 0 0 0 var(--osh-glow);
}
.osh-btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 32px var(--osh-glow-strong), 0 0 0 1px var(--osh-accent);
}
.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: 14px;
border-radius: 8px;
text-decoration: none;
border: 1px solid var(--osh-dim);
transition: all 0.25s ease;
}
.osh-btn-secondary:hover {
background: rgba(255,255,255,0.04);
border-color: rgba(255,255,255,0.2);
}
/* Hero Section */
.osh-hero {
position: relative;
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 140px 40px 100px;
text-align: center;
}
.osh-hero-content {
position: relative;
z-index: 1;
max-width: 800px;
}
/* Terminal-style version badge */
.osh-badge {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 14px;
background: color-mix(in srgb, var(--osh-accent) 8%, transparent);
border: 1px solid color-mix(in srgb, var(--osh-accent) 15%, transparent);
border-radius: 6px;
font-family: 'IBM Plex Mono', monospace;
font-size: 12px;
font-weight: 500;
color: var(--osh-accent);
margin-bottom: 32px;
letter-spacing: 0.02em;
}
.osh-badge-dot {
width: 6px;
height: 6px;
background: var(--osh-accent);
border-radius: 50%;
animation: osh-pulse 2s ease-in-out infinite;
box-shadow: 0 0 8px var(--osh-glow-strong);
}
@keyframes osh-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
.osh-hero h1 {
font-size: clamp(40px, 6vw, 68px);
font-weight: 600;
line-height: 1.08;
margin-bottom: 24px;
letter-spacing: -0.035em;
background: linear-gradient(180deg, #ffffff 0%, #a1a1aa 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.osh-hero-sub {
font-size: clamp(16px, 2vw, 20px);
color: #a1a1aa;
line-height: 1.65;
margin-bottom: 40px;
max-width: 560px;
margin-left: auto;
margin-right: auto;
font-weight: 300;
}
.osh-hero-ctas {
display: flex;
gap: 12px;
justify-content: center;
margin-bottom: 48px;
}
/* Terminal code block */
.osh-code-block {
display: inline-flex;
align-items: center;
gap: 16px;
padding: 16px 20px;
background: var(--osh-surface);
border: 1px solid rgba(255,255,255,0.06);
border-radius: 10px;
font-family: 'IBM Plex Mono', monospace;
font-size: 13px;
max-width: 480px;
margin: 0 auto;
position: relative;
overflow: hidden;
}
/* Subtle left accent bar */
.osh-code-block::before {
content: "";
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
background: var(--osh-accent);
border-radius: 3px 0 0 3px;
}
.osh-code-prompt {
color: var(--osh-accent);
font-weight: 500;
user-select: none;
}
.osh-code-block code {
color: var(--osh-text);
flex: 1;
text-align: left;
}
.osh-copy-btn {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.06);
border-radius: 6px;
color: var(--osh-muted);
cursor: pointer;
transition: all 0.2s ease;
flex-shrink: 0;
}
.osh-copy-btn:hover {
background: rgba(255,255,255,0.08);
color: var(--osh-text);
}
/* Stats section with monospace numbers */
.osh-stats {
padding: 64px 40px;
border-top: 1px solid rgba(255,255,255,0.04);
border-bottom: 1px solid rgba(255,255,255,0.04);
background: var(--osh-surface);
}
.osh-stats-inner {
display: flex;
justify-content: center;
align-items: center;
max-width: 800px;
margin: 0 auto;
flex-wrap: wrap;
gap: 0;
}
.osh-stat-item {
text-align: center;
padding: 0 40px;
position: relative;
}
.osh-stat-item:not(:last-child)::after {
content: "";
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
width: 1px;
height: 40px;
background: rgba(255,255,255,0.06);
}
.osh-stat-value {
font-family: 'IBM Plex Mono', monospace;
font-size: 28px;
font-weight: 600;
color: var(--osh-text);
letter-spacing: -0.02em;
}
.osh-stat-label {
font-size: 12px;
color: var(--osh-muted);
margin-top: 6px;
text-transform: uppercase;
letter-spacing: 0.08em;
font-weight: 500;
}
/* Downloads section */
.osh-downloads {
padding: 80px 40px;
position: relative;
}
.osh-downloads-inner {
max-width: 800px;
margin: 0 auto;
text-align: center;
}
.osh-downloads h2 {
font-size: clamp(24px, 3vw, 32px);
font-weight: 600;
margin-bottom: 8px;
letter-spacing: -0.025em;
}
.osh-downloads p {
color: var(--osh-muted);
margin-bottom: 24px;
font-size: 15px;
}
.osh-downloads-grid {
display: flex;
flex-wrap: wrap;
gap: 12px;
justify-content: center;
}
.osh-download-item {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 8px;
color: var(--osh-text);
text-decoration: none;
font-size: 14px;
transition: all 0.2s;
}
.osh-download-item:hover {
background: rgba(255,255,255,0.08);
border-color: var(--osh-accent);
}
.osh-download-size {
font-size: 12px;
color: var(--osh-muted);
}
/* Feature cards */
.osh-features {
padding: 120px 40px;
position: relative;
}
.osh-features-inner {
max-width: 1100px;
margin: 0 auto;
}
.osh-section-label {
font-family: 'IBM Plex Mono', monospace;
font-size: 12px;
font-weight: 500;
color: var(--osh-accent);
text-transform: uppercase;
letter-spacing: 0.12em;
margin-bottom: 16px;
text-align: center;
}
.osh-section-header {
text-align: center;
margin-bottom: 64px;
}
.osh-section-header h2 {
font-size: clamp(28px, 4vw, 42px);
font-weight: 600;
margin-bottom: 16px;
letter-spacing: -0.025em;
}
.osh-section-header p {
font-size: 17px;
color: var(--osh-muted);
max-width: 480px;
margin: 0 auto;
font-weight: 300;
}
.osh-features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 16px;
}
.osh-feature-card {
padding: 32px;
background: var(--osh-surface);
border: 1px solid rgba(255,255,255,0.04);
border-radius: 12px;
transition: all 0.35s cubic-bezier(0.16, 1, 0.3, 1);
position: relative;
overflow: hidden;
}
.osh-feature-card::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg, transparent, var(--osh-accent), transparent);
opacity: 0;
transition: opacity 0.35s ease;
}
.osh-feature-card:hover {
background: var(--osh-elevated);
border-color: rgba(255,255,255,0.08);
transform: translateY(-4px);
}
.osh-feature-card:hover::before {
opacity: 0.6;
}
.osh-feature-icon {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: color-mix(in srgb, var(--osh-accent) 8%, transparent);
border: 1px solid color-mix(in srgb, var(--osh-accent) 12%, transparent);
border-radius: 8px;
color: var(--osh-accent);
margin-bottom: 20px;
}
.osh-feature-title {
font-size: 16px;
font-weight: 600;
color: #fafafa;
margin-bottom: 8px;
letter-spacing: -0.01em;
}
.osh-feature-desc {
font-size: 14px;
color: #a1a1aa;
line-height: 1.6;
font-weight: 300;
}
/* Social proof */
.osh-social-proof {
padding: 100px 40px;
background: var(--osh-surface);
border-top: 1px solid rgba(255,255,255,0.04);
border-bottom: 1px solid rgba(255,255,255,0.04);
}
.osh-social-proof-inner {
max-width: 1100px;
margin: 0 auto;
}
.osh-logos {
display: flex;
justify-content: center;
gap: 48px;
margin-bottom: 64px;
flex-wrap: wrap;
}
.osh-logo-item {
padding: 10px 20px;
font-family: 'IBM Plex Mono', monospace;
font-size: 13px;
font-weight: 500;
color: var(--osh-dim);
letter-spacing: 0.02em;
transition: color 0.3s ease;
}
.osh-logo-item:hover {
color: var(--osh-muted);
}
.osh-testimonial {
background: var(--osh-bg);
border: 1px solid rgba(255,255,255,0.06);
border-radius: 16px;
padding: 48px;
max-width: 680px;
margin: 0 auto;
position: relative;
}
.osh-testimonial::before {
content: "";
position: absolute;
inset: -1px;
border-radius: 16px;
background: linear-gradient(135deg, color-mix(in srgb, var(--osh-accent) 10%, transparent), transparent 60%);
z-index: -1;
pointer-events: none;
}
.osh-testimonial-quote {
font-size: 20px;
line-height: 1.65;
color: #fafafa;
margin-bottom: 28px;
font-weight: 300;
font-style: italic;
}
.osh-testimonial-author {
font-weight: 600;
color: var(--osh-text);
font-size: 15px;
}
.osh-testimonial-role {
font-size: 13px;
color: var(--osh-muted);
}
/* README */
.osh-readme {
padding: 100px 40px;
max-width: 900px;
margin: 0 auto;
}
.osh-readme-content {
background: var(--osh-surface);
border: 1px solid rgba(255,255,255,0.04);
border-radius: 12px;
padding: 48px;
}
/* Pricing */
.osh-pricing {
padding: 120px 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: 16px;
margin-top: 64px;
}
.osh-pricing-card {
background: var(--osh-surface);
border: 1px solid rgba(255,255,255,0.04);
border-radius: 14px;
padding: 36px;
position: relative;
transition: all 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}
.osh-pricing-card:hover {
border-color: rgba(255,255,255,0.1);
transform: translateY(-4px);
}
.osh-pricing-card.featured {
border-color: color-mix(in srgb, var(--osh-accent) 25%, transparent);
background: linear-gradient(180deg, color-mix(in srgb, var(--osh-accent) 4%, transparent) 0%, var(--osh-surface) 100%);
}
.osh-pricing-badge {
position: absolute;
top: -11px;
left: 50%;
transform: translateX(-50%);
padding: 5px 14px;
background: var(--osh-accent);
color: #000;
font-family: 'IBM Plex Mono', monospace;
font-size: 11px;
font-weight: 600;
border-radius: 6px;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.osh-pricing-name {
font-size: 18px;
font-weight: 600;
color: #fafafa;
margin-bottom: 8px;
}
.osh-pricing-price {
font-family: 'IBM Plex Mono', monospace;
font-size: 44px;
font-weight: 600;
color: var(--osh-text);
line-height: 1;
margin-bottom: 4px;
letter-spacing: -0.03em;
}
.osh-pricing-period {
font-size: 13px;
color: var(--osh-muted);
margin-bottom: 28px;
}
.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.04);
font-size: 13px;
color: #a1a1aa;
display: flex;
align-items: center;
gap: 10px;
}
.osh-pricing-features li::before {
content: "→";
font-family: 'IBM Plex Mono', monospace;
color: var(--osh-accent);
font-size: 12px;
flex-shrink: 0;
}
.osh-pricing-features li:last-child {
border-bottom: none;
}
.osh-pricing-cta {
display: block;
width: 100%;
padding: 13px 24px;
text-align: center;
background: rgba(255,255,255,0.04);
color: var(--osh-text);
font-weight: 600;
font-size: 13px;
border-radius: 8px;
text-decoration: none;
border: 1px solid rgba(255,255,255,0.06);
transition: all 0.25s ease;
letter-spacing: 0.01em;
}
.osh-pricing-cta:hover {
background: rgba(255,255,255,0.08);
border-color: rgba(255,255,255,0.12);
}
.osh-pricing-card.featured .osh-pricing-cta {
background: var(--osh-accent);
color: #000;
border: none;
}
.osh-pricing-card.featured .osh-pricing-cta:hover {
transform: translateY(-2px);
box-shadow: 0 8px 30px var(--osh-glow-strong);
}
/* CTA section */
.osh-cta-section {
padding: 120px 40px;
text-align: center;
position: relative;
}
.osh-cta-section::before {
content: "";
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 120%;
height: 60%;
background: radial-gradient(ellipse at center bottom, var(--osh-glow) 0%, transparent 60%);
pointer-events: none;
}
.osh-cta-inner {
max-width: 600px;
margin: 0 auto;
position: relative;
z-index: 1;
}
.osh-cta-section h2 {
font-size: clamp(28px, 4vw, 44px);
font-weight: 600;
margin-bottom: 16px;
letter-spacing: -0.025em;
}
.osh-cta-section p {
font-size: 17px;
color: var(--osh-muted);
margin-bottom: 36px;
font-weight: 300;
}
/* Footer */
.osh-footer {
padding: 32px 40px;
border-top: 1px solid rgba(255,255,255,0.04);
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 24px;
}
.osh-footer-brand {
display: flex;
align-items: center;
gap: 8px;
}
.osh-footer-logo {
width: 22px;
height: 22px;
background: color-mix(in srgb, var(--osh-accent) 15%, transparent);
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
}
.osh-footer-text {
font-size: 13px;
color: var(--osh-dim);
}
.osh-footer-links {
display: flex;
gap: 24px;
flex-wrap: wrap;
}
.osh-footer-link {
color: var(--osh-muted);
text-decoration: none;
font-size: 13px;
transition: color 0.2s ease;
}
.osh-footer-link:hover {
color: var(--osh-text);
}
.osh-footer-social {
display: flex;
gap: 12px;
}
.osh-footer-social a {
color: var(--osh-dim);
transition: color 0.2s ease;
padding: 4px;
}
.osh-footer-social a:hover {
color: var(--osh-text);
}
/* Responsive */
@media (max-width: 768px) {
.osh-nav { padding: 0 20px; }
.osh-nav-links { display: none; }
.osh-menu-toggle { display: flex; }
.osh-hero { padding: 120px 24px 80px; }
.osh-hero-ctas { flex-direction: column; align-items: center; }
.osh-btn-primary, .osh-btn-secondary { width: 100%; justify-content: center; }
.osh-features, .osh-social-proof, .osh-cta-section, .osh-pricing { padding: 80px 24px; }
.osh-stats { padding: 48px 24px; }
.osh-stat-item { padding: 16px 24px; }
.osh-stat-item:not(:last-child)::after { display: none; }
.osh-stats-inner { flex-direction: column; }
.osh-features-grid { grid-template-columns: 1fr; }
.osh-footer { flex-direction: column; text-align: center; padding: 24px; }
.osh-testimonial { padding: 32px 24px; }
.osh-code-block { font-size: 12px; }
}
@media (max-width: 480px) {
.osh-hero h1 { font-size: 32px; }
.osh-section-header h2 { font-size: 28px; }
.osh-pricing-price { font-size: 36px; }
}
/* Blog content markdown styles */
.osh-blog-content h1, .osh-blog-content h2, .osh-blog-content h3, .osh-blog-content h4 {
color: var(--osh-text); font-family: 'IBM Plex Sans', sans-serif; margin: 1.5em 0 0.5em;
}
.osh-blog-content h2 { font-size: 1.5em; border-bottom: 1px solid var(--osh-dim); padding-bottom: 8px; }
.osh-blog-content h3 { font-size: 1.25em; }
.osh-blog-content a { color: var(--osh-accent); text-decoration: underline; }
.osh-blog-content a:hover { opacity: 0.8; }
.osh-blog-content code { background: var(--osh-elevated); padding: 2px 6px; border-radius: 4px; font-family: 'IBM Plex Mono', monospace; font-size: 0.9em; }
.osh-blog-content pre { background: var(--osh-surface); border: 1px solid var(--osh-dim); border-radius: 8px; padding: 16px; overflow-x: auto; margin: 16px 0; }
.osh-blog-content pre code { background: none; padding: 0; }
.osh-blog-content blockquote { border-left: 3px solid var(--osh-accent); padding-left: 16px; color: var(--osh-muted); margin: 16px 0; font-style: italic; }
.osh-blog-content img { max-width: 100%; border-radius: 8px; margin: 16px 0; }
.osh-blog-content ul, .osh-blog-content ol { padding-left: 24px; margin: 12px 0; }
.osh-blog-content li { margin: 4px 0; }
.osh-blog-content table { border-collapse: collapse; width: 100%; margin: 16px 0; }
.osh-blog-content th, .osh-blog-content td { border: 1px solid var(--osh-dim); padding: 8px 12px; text-align: left; }
.osh-blog-content th { background: var(--osh-surface); font-weight: 600; }
.osh-blog-content hr { border: none; border-top: 1px solid var(--osh-dim); margin: 24px 0; }
</style>
<div class="osh-page">
<!-- Navigation -->
<nav class="osh-nav">
<a href="/" class="osh-nav-brand">
{{if .LogoURL}}
<img src="{{.LogoURL}}" alt="{{.Config.Brand.Name}}" style="height: 30px; border-radius: 4px;">
{{else}}
<div class="osh-nav-logo">
{{svg "octicon-zap" 14}}
</div>
{{end}}
<span class="osh-nav-name">{{if .Config.Brand.Name}}{{.Config.Brand.Name}}{{else}}{{.Repository.Name}}{{end}}</span>
</a>
<div class="osh-nav-links">
{{range .Config.Footer.Links}}
<a href="{{.URL}}" class="osh-nav-link">{{.Label}}</a>
{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="osh-nav-link">Docs</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="osh-nav-link">API</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="osh-nav-link">Releases</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="osh-nav-link">Issues</a>{{end}}
{{if .Config.ValueProps}}<a href="#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}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}#blog{{end}}" class="osh-nav-link">Blog</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="#gallery" class="osh-nav-link">Gallery</a>{{end}}
{{if .Config.Navigation.ShowRepository}}
<a href="{{.RepoURL}}" class="osh-nav-cta">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
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="osh-menu-toggle" onclick="this.parentElement.querySelector('.osh-nav-links').style.display=this.parentElement.querySelector('.osh-nav-links').style.display==='flex'?'none':'flex'">
{{svg "octicon-three-bars" 20}}
</button>
</nav>
{{if .PageIsBlogDetail}}
<!-- Blog Detail View -->
<section class="osh-hero" style="padding-top: 120px; min-height: auto;">
<div class="osh-hero-content" style="max-width: 800px;">
{{if .BlogPost.FeaturedImage}}
<div style="margin: 0 auto 32px; border-radius: 12px; overflow: hidden;">
<img src="{{.BlogPost.FeaturedImage.DownloadURL}}" alt="{{.BlogPost.Title}}" style="width: 100%; max-height: 400px; object-fit: cover; display: block;">
</div>
{{end}}
<h1 style="font-size: 36px; margin-bottom: 8px;">{{.BlogPost.Title}}</h1>
{{if .BlogPost.Subtitle}}<p style="font-size: 1.2rem; color: var(--osh-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(--osh-muted); font-family: 'IBM Plex Mono', monospace;">
{{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(--osh-glow); padding: 2px 8px; border-radius: 4px; font-size: 12px;">{{.}}</span> {{end}}{{end}}
</div>
<div class="markup osh-blog-content" style="color: var(--osh-text); line-height: 1.8; font-size: 16px; text-align: left;">
{{.BlogRenderedContent}}
</div>
<div style="margin-top: 48px; padding-top: 24px; border-top: 1px solid rgba(255,255,255,0.06);">
<a href="{{.BlogBaseURL}}" class="osh-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="osh-features" style="padding-top: 120px;">
<div class="osh-features-inner">
<div class="osh-section-header osh-reveal">
<div class="osh-section-label">Blog</div>
<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="osh-features-grid">
{{range .BlogListPosts}}
<a href="{{$.BlogBaseURL}}/{{.ID}}" class="osh-feature-card osh-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="osh-feature-title">{{.Title}}</h3>
{{if and $.Config.Blog.ShowExcerpt .Subtitle}}
<p class="osh-feature-desc">{{.Subtitle}}</p>
{{end}}
<div style="margin-top: auto; padding-top: 16px; font-size: 12px; color: var(--osh-muted); font-family: 'IBM Plex Mono', monospace;">
{{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="osh-hero">
<div class="osh-hero-gradient"></div>
<div class="osh-hero-content">
<div class="osh-badge osh-reveal visible">
<span class="osh-badge-dot"></span>
{{if .LatestRelease}}v{{.LatestReleaseTag}} released{{else}}Open Source{{end}}
</div>
<h1 class="osh-reveal visible osh-reveal-delay-1">{{if .Config.Hero.Headline}}{{.Config.Hero.Headline}}{{else}}{{.Repository.Name}}{{end}}</h1>
<p class="osh-hero-sub osh-reveal visible osh-reveal-delay-2">
{{if .Config.Hero.Subheadline}}{{.Config.Hero.Subheadline}}{{else}}{{.Repository.Description}}{{end}}
</p>
<div class="osh-hero-ctas osh-reveal visible osh-reveal-delay-3">
{{if .Config.Hero.PrimaryCTA.Label}}
<a href="{{.Config.Hero.PrimaryCTA.URL}}" class="osh-btn-primary" data-cta="primary">
{{.Config.Hero.PrimaryCTA.Label}}
{{svg "octicon-arrow-right" 16}}
</a>
{{else}}
<a href="{{.RepoURL}}" class="osh-btn-primary" data-cta="primary">
Get Started
{{svg "octicon-arrow-right" 16}}
</a>
{{end}}
{{if .Config.Hero.SecondaryCTA.Label}}
<a href="{{.Config.Hero.SecondaryCTA.URL}}" class="osh-btn-secondary" data-cta="secondary">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
{{.Config.Hero.SecondaryCTA.Label}}
</a>
{{else}}
<a href="{{.RepoURL}}" class="osh-btn-secondary" data-cta="secondary">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
View Source
</a>
{{end}}
</div>
{{if .Config.Hero.CodeExample}}
<div class="osh-code-block osh-reveal visible osh-reveal-delay-4">
<span class="osh-code-prompt">$</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" 14}}
</button>
</div>
{{end}}
</div>
</section>
<!-- Stats Section -->
{{if or .Config.Stats (gt .NumStars 0)}}
<section class="osh-stats">
<div class="osh-stats-inner osh-reveal">
{{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{{.LatestReleaseTag}}</div>
<div class="osh-stat-label">Latest</div>
</div>
{{end}}
{{end}}
</div>
</section>
{{end}}
<!-- Downloads Section -->
{{if and .PublicReleases .LatestRelease .LatestRelease.Attachments}}
<section class="osh-downloads">
<div class="osh-downloads-inner osh-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: 14px; color: var(--osh-muted); font-family: 'IBM Plex Mono', monospace; letter-spacing: 0.05em; text-transform: uppercase;">{{svg "octicon-device-desktop" 16}} Windows</h4>
<div class="osh-downloads-grid">
{{range $windowsFiles}}<a href="{{$.RepoURL}}/releases/download/{{$.LatestRelease.TagName}}/{{.Name}}" class="osh-download-item">{{svg "octicon-download" 16}} {{.Name}} <span class="osh-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: 14px; color: var(--osh-muted); font-family: 'IBM Plex Mono', monospace; letter-spacing: 0.05em; text-transform: uppercase;">{{svg "octicon-device-desktop" 16}} macOS</h4>
<div class="osh-downloads-grid">
{{range $macosFiles}}<a href="{{$.RepoURL}}/releases/download/{{$.LatestRelease.TagName}}/{{.Name}}" class="osh-download-item">{{svg "octicon-download" 16}} {{.Name}} <span class="osh-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: 14px; color: var(--osh-muted); font-family: 'IBM Plex Mono', monospace; letter-spacing: 0.05em; text-transform: uppercase;">{{svg "octicon-terminal" 16}} Linux</h4>
<div class="osh-downloads-grid">
{{range $linuxFiles}}<a href="{{$.RepoURL}}/releases/download/{{$.LatestRelease.TagName}}/{{.Name}}" class="osh-download-item">{{svg "octicon-download" 16}} {{.Name}} <span class="osh-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: 14px; color: var(--osh-muted); font-family: 'IBM Plex Mono', monospace; letter-spacing: 0.05em; text-transform: uppercase;">{{svg "octicon-device-mobile" 16}} Android</h4>
<div class="osh-downloads-grid">
{{range $androidFiles}}<a href="{{$.RepoURL}}/releases/download/{{$.LatestRelease.TagName}}/{{.Name}}" class="osh-download-item">{{svg "octicon-download" 16}} {{.Name}} <span class="osh-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: 14px; color: var(--osh-muted); font-family: 'IBM Plex Mono', monospace; letter-spacing: 0.05em; text-transform: uppercase;">{{svg "octicon-device-mobile" 16}} iOS</h4>
<div class="osh-downloads-grid">
{{range $iosFiles}}<a href="{{$.RepoURL}}/releases/download/{{$.LatestRelease.TagName}}/{{.Name}}" class="osh-download-item">{{svg "octicon-download" 16}} {{.Name}} <span class="osh-download-size">{{FileSize .Size}}</span></a>{{end}}
</div>
</div>
{{end}}
{{end}}
{{if or $.GooglePlayID $.AppStoreID}}
<div style="margin-bottom: 24px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 14px; color: var(--osh-muted); font-family: 'IBM Plex Mono', monospace; letter-spacing: 0.05em; text-transform: uppercase;">{{svg "octicon-device-mobile" 16}} App Stores</h4>
<div style="display: flex; gap: 12px; flex-wrap: wrap;">
{{if $.GooglePlayID}}
<a href="https://play.google.com/store/apps/details?id={{$.GooglePlayID}}" target="_blank" rel="noopener" class="osh-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3.609 1.814L13.792 12 3.61 22.186a.996.996 0 0 1-.61-.92V2.734a1 1 0 0 1 .609-.92zm10.89 10.893l2.302 2.302-10.937 6.333 8.635-8.635zm3.199-1.4l2.834 1.64a1 1 0 0 1 0 1.726l-2.834 1.64-2.635-2.636 2.635-2.37zM5.864 2.658L16.8 9.99l-2.302 2.302-8.635-8.635z"/></svg>
Google Play
</a>
{{end}}
{{if $.AppStoreID}}
<a href="https://apps.apple.com/app/{{$.AppStoreID}}" target="_blank" rel="noopener" class="osh-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>
App Store
</a>
{{end}}
</div>
</div>
{{end}}
{{if $otherFiles}}
<div style="margin-bottom: 24px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px; font-size: 14px; color: var(--osh-muted); font-family: 'IBM Plex Mono', monospace; letter-spacing: 0.05em; text-transform: uppercase;">{{svg "octicon-file" 16}} Other</h4>
<div class="osh-downloads-grid">
{{range $otherFiles}}<a href="{{$.RepoURL}}/releases/download/{{$.LatestRelease.TagName}}/{{.Name}}" class="osh-download-item">{{svg "octicon-download" 16}} {{.Name}} <span class="osh-download-size">{{FileSize .Size}}</span></a>{{end}}
</div>
</div>
{{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 osh-reveal">
<div class="osh-section-label">Why choose us</div>
<h2>{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}</h2>
<p>Everything you need to get started quickly.</p>
</div>
<div class="osh-features-grid">
{{range .Config.ValueProps}}
<div class="osh-feature-card osh-reveal">
<div class="osh-feature-icon">
{{svg (printf "octicon-%s" (or .Icon "check")) 20}}
</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 osh-reveal">
<div class="osh-section-label">Capabilities</div>
<h2>Features</h2>
<p>Powerful capabilities at your fingertips.</p>
</div>
<div class="osh-features-grid">
{{range .Config.Features}}
<div class="osh-feature-card osh-reveal">
<div class="osh-feature-icon">
{{svg (printf "octicon-%s" (or .Icon "zap")) 20}}
</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 osh-reveal">
{{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 osh-reveal" 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 osh-reveal">
<div class="osh-section-label">Pricing</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="osh-pricing-grid">
{{range .Config.Pricing.Plans}}
<div class="osh-pricing-card{{if .Featured}} featured{{end}} osh-reveal">
{{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 osh-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="osh-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="osh-features" id="blog" style="border-top: 1px solid rgba(255,255,255,0.04);">
<div class="osh-features-inner">
<div class="osh-section-header osh-reveal">
<div class="osh-section-label">Blog</div>
<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="osh-features-grid">
{{range .BlogPosts}}
<a href="{{$.BlogBaseURL}}/{{.ID}}" class="osh-feature-card osh-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="osh-feature-title">{{.Title}}</h3>
{{if and $.Config.Blog.ShowExcerpt .Subtitle}}
<p class="osh-feature-desc">{{.Subtitle}}</p>
{{end}}
<div style="margin-top: auto; padding-top: 16px; font-size: 12px; color: var(--osh-muted); font-family: 'IBM Plex Mono', monospace;">
{{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="osh-reveal">
<a href="{{if .Config.Blog.CTAButton.URL}}{{.Config.Blog.CTAButton.URL}}{{else}}{{.BlogBaseURL}}{{end}}" class="osh-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="osh-features" id="gallery" style="border-top: 1px solid rgba(255,255,255,0.04);">
<div class="osh-features-inner">
<div class="osh-section-header osh-reveal">
<div class="osh-section-label">Gallery</div>
<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="osh-feature-card osh-reveal" style="padding: 0; overflow: hidden;">
<a href="{{.URL}}" target="_blank" style="display: block;">
<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(--osh-muted);">{{.Caption}}</div>
{{end}}
</div>
{{end}}
</div>
</div>
</section>
{{end}}
{{end}}{{/* end PageIsBlogDetail / PageIsBlogList / else */}}
<!-- Footer -->
<footer class="osh-footer">
<div class="osh-footer-brand">
<div class="osh-footer-logo">{{svg "octicon-zap" 12}}</div>
<span class="osh-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="osh-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="osh-footer-links">
{{range .Config.Footer.Links}}
<a href="{{.URL}}" class="osh-footer-link">{{.Label}}</a>
{{end}}
{{if .Config.Navigation.ShowRepository}}<a href="{{.RepoURL}}" class="osh-footer-link">Repository</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="osh-footer-link">Documentation</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="osh-footer-link">Releases</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="osh-footer-link">Issues</a>{{end}}
</div>
</footer>
</div>
<!-- Scroll reveal observer -->
<script>
(function() {
var reveals = document.querySelectorAll('.osh-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" .}}