2
0
Files
gitcaddy-server/templates/pages/visual-showcase.tmpl
logikonline f3eba7dd34 feat(pages): add 5 new landing page templates
Add Documentation First, Developer Tool, Visual Showcase, CLI Terminal, and Architecture Deep Dive templates. Brings total templates to 9. Each template has unique design language and target audience: Documentation First for docs-heavy projects, Developer Tool for technical products, Visual Showcase for design/media projects, CLI Terminal for command-line tools, Architecture Deep Dive for technical deep-dives. Updates template display names for clarity.
2026-03-16 22:18:53 -04:00

1559 lines
48 KiB
Handlebars

{{template "pages/base_head" .}}
<style>
@import url('https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,300;1,9..40,400&display=swap');
:root {
--vs-bg: #ffffff;
--vs-surface: #f8f9fb;
--vs-text: #1a1a2e;
--vs-muted: #6b7280;
--vs-border: #e5e7eb;
--vs-primary-start: {{if .Config.Theme.PrimaryColor}}{{.Config.Theme.PrimaryColor}}{{else}}#ff9a76{{end}};
--vs-primary-end: {{if .Config.Theme.AccentColor}}{{.Config.Theme.AccentColor}}{{else}}#c084fc{{end}};
--vs-accent: #7c3aed;
--vs-gradient: linear-gradient(135deg, var(--vs-primary-start), var(--vs-primary-end));
--vs-shadow-sm: 0 1px 3px rgba(0,0,0,0.04);
--vs-shadow: 0 4px 24px rgba(0,0,0,0.06);
--vs-shadow-lg: 0 12px 48px rgba(0,0,0,0.1);
--vs-radius: 16px;
--vs-radius-pill: 999px;
}
html {
scroll-behavior: smooth;
}
html, body.pages-body {
overflow-x: hidden;
background: var(--vs-bg) !important;
}
.vs-page {
position: relative;
min-height: 100vh;
background: var(--vs-bg);
color: var(--vs-text);
font-family: 'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Reveal animation system */
.vs-reveal {
opacity: 0;
transform: translateY(40px);
transition: opacity 0.7s cubic-bezier(0.34, 1.56, 0.64, 1), transform 0.7s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.vs-reveal.visible {
opacity: 1;
transform: translateY(0);
}
.vs-reveal-delay-1 { transition-delay: 0.1s; }
.vs-reveal-delay-2 { transition-delay: 0.2s; }
.vs-reveal-delay-3 { transition-delay: 0.3s; }
.vs-reveal-delay-4 { transition-delay: 0.4s; }
/* Navigation */
.vs-nav {
position: fixed;
top: 0;
left: 0;
right: 0;
padding: 0 48px;
height: 72px;
display: flex;
align-items: center;
justify-content: space-between;
background: rgba(255, 255, 255, 0.72);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border-bottom: 1px solid var(--vs-border);
z-index: 100;
transition: background 0.3s ease;
}
.vs-nav-brand {
display: flex;
align-items: center;
gap: 12px;
text-decoration: none;
color: inherit;
}
.vs-nav-logo {
width: 34px;
height: 34px;
background: var(--vs-gradient);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
box-shadow: 0 2px 12px rgba(255, 154, 118, 0.3);
transition: box-shadow 0.3s ease, transform 0.3s ease;
}
.vs-nav-brand:hover .vs-nav-logo {
box-shadow: 0 4px 20px rgba(255, 154, 118, 0.45);
transform: scale(1.05);
}
.vs-nav-name {
font-family: 'DM Serif Display', serif;
font-weight: 400;
font-size: 18px;
letter-spacing: -0.01em;
color: var(--vs-text);
}
.vs-nav-links {
display: flex;
align-items: center;
gap: 4px;
}
.vs-nav-link {
color: var(--vs-muted);
text-decoration: none;
font-size: 14px;
font-weight: 500;
padding: 8px 16px;
border-radius: var(--vs-radius-pill);
transition: all 0.2s ease;
}
.vs-nav-link:hover {
color: var(--vs-text);
background: var(--vs-surface);
}
.vs-nav-cta {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 20px;
background: var(--vs-gradient);
color: #fff;
font-size: 14px;
font-weight: 600;
border-radius: var(--vs-radius-pill);
text-decoration: none;
transition: all 0.25s ease;
box-shadow: 0 2px 12px rgba(255, 154, 118, 0.25);
}
.vs-nav-cta:hover {
box-shadow: 0 4px 20px rgba(255, 154, 118, 0.4);
transform: translateY(-1px);
}
/* Mobile menu toggle */
.vs-menu-toggle {
display: none;
background: none;
border: none;
color: var(--vs-muted);
cursor: pointer;
padding: 8px;
}
/* Buttons */
.vs-btn-primary {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 16px 32px;
background: var(--vs-gradient);
color: #fff;
font-weight: 600;
font-size: 15px;
border-radius: var(--vs-radius-pill);
text-decoration: none;
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
border: none;
cursor: pointer;
box-shadow: 0 4px 20px rgba(255, 154, 118, 0.3);
}
.vs-btn-primary:hover {
transform: translateY(-3px);
box-shadow: 0 8px 32px rgba(255, 154, 118, 0.45);
}
.vs-btn-secondary {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 16px 32px;
background: #fff;
color: var(--vs-accent);
font-weight: 600;
font-size: 15px;
border-radius: var(--vs-radius-pill);
text-decoration: none;
border: 1.5px solid var(--vs-border);
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.vs-btn-secondary:hover {
background: var(--vs-surface);
border-color: var(--vs-accent);
transform: translateY(-3px);
box-shadow: 0 8px 32px rgba(124, 58, 237, 0.12);
}
/* Hero Section */
.vs-hero {
position: relative;
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 160px 48px 120px;
text-align: center;
overflow: hidden;
}
/* Hero gradient mesh background */
.vs-hero-mesh {
position: absolute;
top: -30%;
left: 50%;
transform: translateX(-50%);
width: 160%;
height: 120%;
pointer-events: none;
z-index: 0;
}
.vs-hero-blob-1 {
position: absolute;
top: 10%;
left: 15%;
width: 500px;
height: 500px;
background: radial-gradient(circle, rgba(255, 154, 118, 0.12) 0%, transparent 70%);
border-radius: 50%;
filter: blur(60px);
animation: vs-float-1 12s ease-in-out infinite;
}
.vs-hero-blob-2 {
position: absolute;
top: 5%;
right: 10%;
width: 600px;
height: 600px;
background: radial-gradient(circle, rgba(192, 132, 252, 0.1) 0%, transparent 70%);
border-radius: 50%;
filter: blur(60px);
animation: vs-float-2 15s ease-in-out infinite;
}
.vs-hero-blob-3 {
position: absolute;
bottom: 10%;
left: 40%;
width: 400px;
height: 400px;
background: radial-gradient(circle, rgba(124, 58, 237, 0.06) 0%, transparent 70%);
border-radius: 50%;
filter: blur(60px);
animation: vs-float-3 10s ease-in-out infinite;
}
@keyframes vs-float-1 {
0%, 100% { transform: translate(0, 0); }
50% { transform: translate(30px, -20px); }
}
@keyframes vs-float-2 {
0%, 100% { transform: translate(0, 0); }
50% { transform: translate(-25px, 25px); }
}
@keyframes vs-float-3 {
0%, 100% { transform: translate(0, 0); }
50% { transform: translate(20px, 15px); }
}
.vs-hero-content {
position: relative;
z-index: 1;
max-width: 860px;
}
/* Badge */
.vs-badge {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 18px;
background: var(--vs-surface);
border: 1px solid var(--vs-border);
border-radius: var(--vs-radius-pill);
font-size: 13px;
font-weight: 500;
color: var(--vs-accent);
margin-bottom: 36px;
}
.vs-badge-dot {
width: 7px;
height: 7px;
background: var(--vs-gradient);
border-radius: 50%;
animation: vs-pulse 2.5s ease-in-out infinite;
}
@keyframes vs-pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(0.8); }
}
.vs-hero h1 {
font-family: 'DM Serif Display', serif;
font-size: clamp(44px, 7vw, 80px);
font-weight: 400;
line-height: 1.05;
margin-bottom: 28px;
letter-spacing: -0.02em;
color: var(--vs-text);
}
.vs-hero h1 .vs-gradient-text {
background: var(--vs-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.vs-hero-sub {
font-size: clamp(17px, 2vw, 21px);
color: var(--vs-muted);
line-height: 1.7;
margin-bottom: 44px;
max-width: 580px;
margin-left: auto;
margin-right: auto;
font-weight: 400;
}
.vs-hero-ctas {
display: flex;
gap: 16px;
justify-content: center;
margin-bottom: 56px;
}
/* Code block */
.vs-code-block {
display: inline-flex;
align-items: center;
gap: 16px;
padding: 18px 24px;
background: var(--vs-surface);
border: 1px solid var(--vs-border);
border-radius: 14px;
font-family: 'DM Sans', monospace;
font-size: 14px;
max-width: 500px;
margin: 0 auto;
position: relative;
box-shadow: var(--vs-shadow);
}
.vs-code-prompt {
background: var(--vs-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-weight: 600;
user-select: none;
}
.vs-code-block code {
color: var(--vs-text);
flex: 1;
text-align: left;
font-family: 'DM Sans', monospace;
}
.vs-copy-btn {
display: flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
background: #fff;
border: 1px solid var(--vs-border);
border-radius: 8px;
color: var(--vs-muted);
cursor: pointer;
transition: all 0.2s ease;
flex-shrink: 0;
}
.vs-copy-btn:hover {
background: var(--vs-surface);
color: var(--vs-accent);
border-color: var(--vs-accent);
}
/* Stats section */
.vs-stats {
padding: 72px 48px;
background: var(--vs-surface);
border-top: 1px solid var(--vs-border);
border-bottom: 1px solid var(--vs-border);
}
.vs-stats-inner {
display: flex;
justify-content: center;
align-items: center;
max-width: 900px;
margin: 0 auto;
flex-wrap: wrap;
gap: 0;
}
.vs-stat-item {
text-align: center;
padding: 0 48px;
position: relative;
}
.vs-stat-item:not(:last-child)::after {
content: "";
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
width: 1px;
height: 48px;
background: var(--vs-border);
}
.vs-stat-value {
font-family: 'DM Serif Display', serif;
font-size: 36px;
font-weight: 400;
color: var(--vs-text);
letter-spacing: -0.02em;
}
.vs-stat-label {
font-size: 13px;
color: var(--vs-muted);
margin-top: 6px;
text-transform: uppercase;
letter-spacing: 0.08em;
font-weight: 500;
}
/* Downloads section */
.vs-downloads {
padding: 96px 48px;
position: relative;
}
.vs-downloads-inner {
max-width: 900px;
margin: 0 auto;
text-align: center;
}
.vs-downloads h2 {
font-family: 'DM Serif Display', serif;
font-size: clamp(26px, 3vw, 36px);
font-weight: 400;
margin-bottom: 8px;
letter-spacing: -0.015em;
}
.vs-downloads p {
color: var(--vs-muted);
margin-bottom: 32px;
font-size: 16px;
}
.vs-downloads-grid {
display: flex;
flex-wrap: wrap;
gap: 12px;
justify-content: center;
}
.vs-download-item {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 12px 22px;
background: #fff;
border: 1px solid var(--vs-border);
border-radius: 12px;
color: var(--vs-text);
text-decoration: none;
font-size: 14px;
font-weight: 500;
transition: all 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
box-shadow: var(--vs-shadow-sm);
}
.vs-download-item:hover {
background: var(--vs-surface);
border-color: var(--vs-primary-start);
transform: translateY(-2px);
box-shadow: var(--vs-shadow);
}
.vs-download-size {
font-size: 12px;
color: var(--vs-muted);
}
/* Features / Value Props section */
.vs-features {
padding: 120px 48px;
position: relative;
}
.vs-features-inner {
max-width: 1140px;
margin: 0 auto;
}
.vs-section-label {
font-size: 13px;
font-weight: 600;
background: var(--vs-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-transform: uppercase;
letter-spacing: 0.12em;
margin-bottom: 16px;
text-align: center;
}
.vs-section-header {
text-align: center;
margin-bottom: 72px;
}
.vs-section-header h2 {
font-family: 'DM Serif Display', serif;
font-size: clamp(30px, 4vw, 48px);
font-weight: 400;
margin-bottom: 16px;
letter-spacing: -0.015em;
color: var(--vs-text);
}
.vs-section-header p {
font-size: 18px;
color: var(--vs-muted);
max-width: 520px;
margin: 0 auto;
font-weight: 400;
line-height: 1.6;
}
.vs-features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
.vs-feature-card {
padding: 36px;
background: #fff;
border: 1px solid var(--vs-border);
border-radius: var(--vs-radius);
transition: all 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
position: relative;
box-shadow: var(--vs-shadow);
}
.vs-feature-card:hover {
transform: translateY(-6px);
box-shadow: var(--vs-shadow-lg);
border-color: transparent;
}
.vs-feature-icon {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background: var(--vs-gradient);
border-radius: 12px;
color: #fff;
margin-bottom: 22px;
}
.vs-feature-title {
font-family: 'DM Serif Display', serif;
font-size: 18px;
font-weight: 400;
color: var(--vs-text);
margin-bottom: 10px;
}
.vs-feature-desc {
font-size: 15px;
color: var(--vs-muted);
line-height: 1.65;
font-weight: 400;
}
/* Social proof */
.vs-social-proof {
padding: 100px 48px;
background: var(--vs-surface);
border-top: 1px solid var(--vs-border);
border-bottom: 1px solid var(--vs-border);
}
.vs-social-proof-inner {
max-width: 1140px;
margin: 0 auto;
}
.vs-logos {
display: flex;
justify-content: center;
gap: 48px;
margin-bottom: 64px;
flex-wrap: wrap;
}
.vs-logo-item {
padding: 10px 24px;
font-size: 15px;
font-weight: 600;
color: var(--vs-border);
letter-spacing: 0.02em;
transition: color 0.3s ease;
}
.vs-logo-item:hover {
color: var(--vs-muted);
}
.vs-testimonial {
background: #fff;
border: 1px solid var(--vs-border);
border-radius: 24px;
padding: 56px;
max-width: 720px;
margin: 0 auto;
position: relative;
box-shadow: var(--vs-shadow);
}
.vs-testimonial::before {
content: "\201C";
position: absolute;
top: 24px;
left: 36px;
font-family: 'DM Serif Display', serif;
font-size: 80px;
line-height: 1;
background: var(--vs-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
opacity: 0.3;
pointer-events: none;
}
.vs-testimonial-quote {
font-family: 'DM Serif Display', serif;
font-size: 22px;
line-height: 1.6;
color: var(--vs-text);
margin-bottom: 28px;
font-weight: 400;
font-style: italic;
}
.vs-testimonial-author {
font-weight: 600;
color: var(--vs-text);
font-size: 16px;
}
.vs-testimonial-role {
font-size: 14px;
color: var(--vs-muted);
}
/* Pricing */
.vs-pricing {
padding: 120px 48px;
}
.vs-pricing-inner {
max-width: 1140px;
margin: 0 auto;
}
.vs-pricing-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
margin-top: 72px;
}
.vs-pricing-card {
background: #fff;
border: 1px solid var(--vs-border);
border-radius: 20px;
padding: 40px;
position: relative;
transition: all 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
box-shadow: var(--vs-shadow);
}
.vs-pricing-card:hover {
transform: translateY(-6px);
box-shadow: var(--vs-shadow-lg);
}
.vs-pricing-card.featured {
border-color: transparent;
background: linear-gradient(180deg, rgba(255, 154, 118, 0.04) 0%, #fff 40%);
box-shadow: 0 4px 24px rgba(255, 154, 118, 0.15), 0 0 0 1px rgba(255, 154, 118, 0.2);
}
.vs-pricing-badge {
position: absolute;
top: -13px;
left: 50%;
transform: translateX(-50%);
padding: 6px 18px;
background: var(--vs-gradient);
color: #fff;
font-size: 12px;
font-weight: 600;
border-radius: var(--vs-radius-pill);
text-transform: uppercase;
letter-spacing: 0.06em;
}
.vs-pricing-name {
font-family: 'DM Serif Display', serif;
font-size: 20px;
font-weight: 400;
color: var(--vs-text);
margin-bottom: 8px;
}
.vs-pricing-price {
font-family: 'DM Serif Display', serif;
font-size: 52px;
font-weight: 400;
color: var(--vs-text);
line-height: 1;
margin-bottom: 4px;
letter-spacing: -0.02em;
}
.vs-pricing-period {
font-size: 14px;
color: var(--vs-muted);
margin-bottom: 32px;
}
.vs-pricing-features {
list-style: none;
padding: 0;
margin: 0 0 36px 0;
}
.vs-pricing-features li {
padding: 11px 0;
border-bottom: 1px solid var(--vs-border);
font-size: 14px;
color: var(--vs-muted);
display: flex;
align-items: center;
gap: 10px;
}
.vs-pricing-features li::before {
content: "";
width: 18px;
height: 18px;
background: var(--vs-gradient);
border-radius: 50%;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
background-image: var(--vs-gradient);
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='%23fff' d='M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z'/%3E%3C/svg%3E");
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='%23fff' d='M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z'/%3E%3C/svg%3E");
-webkit-mask-size: 12px;
mask-size: 12px;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-position: center;
mask-position: center;
}
.vs-pricing-features li:last-child {
border-bottom: none;
}
.vs-pricing-cta {
display: block;
width: 100%;
padding: 14px 24px;
text-align: center;
background: var(--vs-surface);
color: var(--vs-text);
font-weight: 600;
font-size: 14px;
border-radius: var(--vs-radius-pill);
text-decoration: none;
border: 1px solid var(--vs-border);
transition: all 0.25s ease;
}
.vs-pricing-cta:hover {
background: var(--vs-text);
color: #fff;
border-color: var(--vs-text);
}
.vs-pricing-card.featured .vs-pricing-cta {
background: var(--vs-gradient);
color: #fff;
border: none;
box-shadow: 0 4px 16px rgba(255, 154, 118, 0.3);
}
.vs-pricing-card.featured .vs-pricing-cta:hover {
transform: translateY(-2px);
box-shadow: 0 8px 32px rgba(255, 154, 118, 0.45);
}
/* CTA section */
.vs-cta-section {
padding: 120px 48px;
text-align: center;
position: relative;
overflow: hidden;
}
.vs-cta-section::before {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 800px;
height: 800px;
background: radial-gradient(circle, rgba(255, 154, 118, 0.08) 0%, rgba(192, 132, 252, 0.04) 40%, transparent 70%);
pointer-events: none;
border-radius: 50%;
}
.vs-cta-inner {
max-width: 640px;
margin: 0 auto;
position: relative;
z-index: 1;
}
.vs-cta-section h2 {
font-family: 'DM Serif Display', serif;
font-size: clamp(30px, 4vw, 48px);
font-weight: 400;
margin-bottom: 16px;
letter-spacing: -0.015em;
}
.vs-cta-section p {
font-size: 18px;
color: var(--vs-muted);
margin-bottom: 40px;
font-weight: 400;
line-height: 1.6;
}
/* Blog content markdown styles */
.vs-blog-content h1, .vs-blog-content h2, .vs-blog-content h3, .vs-blog-content h4 {
color: var(--vs-text); font-family: 'DM Serif Display', serif; margin: 1.5em 0 0.5em; font-weight: 400;
}
.vs-blog-content h1 { font-size: 2em; }
.vs-blog-content h2 { font-size: 1.5em; border-bottom: 1px solid var(--vs-border); padding-bottom: 10px; }
.vs-blog-content h3 { font-size: 1.25em; }
.vs-blog-content h4 { font-size: 1.1em; }
.vs-blog-content a { color: var(--vs-accent); text-decoration: underline; text-underline-offset: 2px; }
.vs-blog-content a:hover { opacity: 0.8; }
.vs-blog-content code { background: var(--vs-surface); padding: 2px 8px; border-radius: 6px; font-size: 0.9em; border: 1px solid var(--vs-border); }
.vs-blog-content pre { background: var(--vs-surface); border: 1px solid var(--vs-border); border-radius: 12px; padding: 20px; overflow-x: auto; margin: 20px 0; }
.vs-blog-content pre code { background: none; padding: 0; border: none; }
.vs-blog-content blockquote { border-left: 3px solid var(--vs-primary-start); padding-left: 20px; color: var(--vs-muted); margin: 20px 0; font-style: italic; }
.vs-blog-content img { max-width: 100%; border-radius: 12px; margin: 20px 0; box-shadow: var(--vs-shadow); }
.vs-blog-content ul, .vs-blog-content ol { padding-left: 24px; margin: 14px 0; }
.vs-blog-content li { margin: 6px 0; line-height: 1.7; }
.vs-blog-content table { border-collapse: collapse; width: 100%; margin: 20px 0; border-radius: 8px; overflow: hidden; }
.vs-blog-content th, .vs-blog-content td { border: 1px solid var(--vs-border); padding: 10px 14px; text-align: left; }
.vs-blog-content th { background: var(--vs-surface); font-weight: 600; }
.vs-blog-content hr { border: none; border-top: 1px solid var(--vs-border); margin: 32px 0; }
/* Footer */
.vs-footer {
padding: 40px 48px;
border-top: 1px solid var(--vs-border);
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 24px;
background: var(--vs-surface);
}
.vs-footer-brand {
display: flex;
align-items: center;
gap: 10px;
}
.vs-footer-logo {
width: 24px;
height: 24px;
background: var(--vs-gradient);
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
}
.vs-footer-text {
font-size: 13px;
color: var(--vs-muted);
}
.vs-footer-links {
display: flex;
gap: 28px;
flex-wrap: wrap;
}
.vs-footer-link {
color: var(--vs-muted);
text-decoration: none;
font-size: 14px;
font-weight: 500;
transition: color 0.2s ease;
}
.vs-footer-link:hover {
color: var(--vs-text);
}
.vs-footer-social {
display: flex;
gap: 12px;
}
.vs-footer-social a {
color: var(--vs-muted);
transition: all 0.2s ease;
padding: 6px;
border-radius: 8px;
}
.vs-footer-social a:hover {
color: var(--vs-accent);
background: var(--vs-surface);
}
/* Responsive */
@media (max-width: 768px) {
.vs-nav { padding: 0 20px; }
.vs-nav-links { display: none; }
.vs-menu-toggle { display: flex; }
.vs-hero { padding: 120px 24px 80px; }
.vs-hero-ctas { flex-direction: column; align-items: center; }
.vs-btn-primary, .vs-btn-secondary { width: 100%; justify-content: center; }
.vs-features, .vs-social-proof, .vs-cta-section, .vs-pricing { padding: 80px 24px; }
.vs-downloads { padding: 64px 24px; }
.vs-stats { padding: 48px 24px; }
.vs-stat-item { padding: 16px 24px; }
.vs-stat-item:not(:last-child)::after { display: none; }
.vs-stats-inner { flex-direction: column; }
.vs-features-grid { grid-template-columns: 1fr; }
.vs-footer { flex-direction: column; text-align: center; padding: 28px 24px; }
.vs-testimonial { padding: 36px 24px; }
.vs-code-block { font-size: 13px; }
.vs-hero-blob-1, .vs-hero-blob-2, .vs-hero-blob-3 { width: 300px; height: 300px; }
}
@media (max-width: 480px) {
.vs-hero h1 { font-size: 36px; }
.vs-section-header h2 { font-size: 30px; }
.vs-pricing-price { font-size: 40px; }
.vs-testimonial-quote { font-size: 18px; }
}
</style>
<div class="vs-page">
<!-- Navigation -->
<nav class="vs-nav">
<a href="/" class="vs-nav-brand">
{{if .LogoURL}}
<img src="{{.LogoURL}}" alt="{{.Config.Brand.Name}}" style="height: 34px; border-radius: 8px;">
{{else}}
<div class="vs-nav-logo">
{{svg "octicon-zap" 16}}
</div>
{{end}}
<span class="vs-nav-name">{{if .Config.Brand.Name}}{{.Config.Brand.Name}}{{else}}{{.Repository.Name}}{{end}}</span>
</a>
<div class="vs-nav-links">
{{range .Config.Footer.Links}}
<a href="{{.URL}}" class="vs-nav-link">{{.Label}}</a>
{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="vs-nav-link">Docs</a>{{end}}
{{if .Config.Navigation.ShowAPI}}<a href="{{.RepoURL}}/swagger" class="vs-nav-link">API</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="vs-nav-link">Releases</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="vs-nav-link">Issues</a>{{end}}
{{if .Config.ValueProps}}<a href="#value-props" class="vs-nav-link">Value Props</a>{{end}}
{{if .Config.Features}}<a href="#features" class="vs-nav-link">Features</a>{{end}}
{{if .Config.Pricing.Plans}}<a href="#pricing" class="vs-nav-link">Pricing</a>{{end}}
{{if .Config.Blog.Enabled}}<a href="{{if .BlogBaseURL}}{{.BlogBaseURL}}{{else}}#blog{{end}}" class="vs-nav-link">Blog</a>{{end}}
{{if .Config.Gallery.Enabled}}<a href="#gallery" class="vs-nav-link">Gallery</a>{{end}}
{{if .Config.Navigation.ShowRepository}}
<a href="{{.RepoURL}}" class="vs-nav-cta">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
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="vs-menu-toggle" onclick="this.parentElement.querySelector('.vs-nav-links').style.display=this.parentElement.querySelector('.vs-nav-links').style.display==='flex'?'none':'flex'">
{{svg "octicon-three-bars" 20}}
</button>
</nav>
{{if .PageIsBlogDetail}}
<!-- Blog Detail View -->
<section class="vs-hero" style="padding-top: 140px; min-height: auto;">
<div class="vs-hero-content" style="max-width: 800px;">
{{if .BlogPost.FeaturedImage}}
<div style="margin: 0 auto 36px; border-radius: 20px; overflow: hidden; box-shadow: var(--vs-shadow-lg);">
<img src="{{.BlogPost.FeaturedImage.DownloadURL}}" alt="{{.BlogPost.Title}}" style="width: 100%; max-height: 420px; object-fit: cover; display: block;">
</div>
{{end}}
<h1 style="font-family: 'DM Serif Display', serif; font-size: 40px; margin-bottom: 10px; font-weight: 400;">{{.BlogPost.Title}}</h1>
{{if .BlogPost.Subtitle}}<p style="font-size: 1.2rem; color: var(--vs-muted); margin-bottom: 28px;">{{.BlogPost.Subtitle}}</p>{{end}}
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 48px; font-size: 14px; color: var(--vs-muted);">
{{if .BlogPost.Author}}<span style="font-weight: 600; color: var(--vs-text);">{{.BlogPost.Author.DisplayName}}</span><span>&middot;</span>{{end}}
<span>{{DateUtils.AbsoluteShort .BlogPost.CreatedUnix}}</span>
{{if .BlogTags}}<span>&middot;</span>{{range .BlogTags}}<span style="background: linear-gradient(135deg, rgba(255,154,118,0.1), rgba(192,132,252,0.1)); padding: 3px 10px; border-radius: 999px; font-size: 12px; font-weight: 500;">{{.}}</span> {{end}}{{end}}
</div>
<div class="markup vs-blog-content" style="color: var(--vs-text); line-height: 1.8; font-size: 18px; text-align: left;">
{{.BlogRenderedContent}}
</div>
<div style="margin-top: 56px; padding-top: 28px; border-top: 1px solid var(--vs-border);">
<a href="{{.BlogBaseURL}}" class="vs-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="vs-features" style="padding-top: 140px;">
<div class="vs-features-inner">
<div class="vs-section-header vs-reveal">
<div class="vs-section-label">Blog</div>
<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="vs-features-grid">
{{range .BlogListPosts}}
<a href="{{$.BlogBaseURL}}/{{.ID}}" class="vs-feature-card vs-reveal" style="text-decoration: none; color: inherit; display: flex; flex-direction: column;">
{{if .FeaturedImage}}
<div style="margin: -36px -36px 22px -36px; overflow: hidden; border-radius: 16px 16px 0 0;">
<img src="{{.FeaturedImage.DownloadURL}}" alt="{{.Title}}" style="width: 100%; height: 200px; object-fit: cover; display: block;">
</div>
{{end}}
<h3 class="vs-feature-title">{{.Title}}</h3>
{{if and $.Config.Blog.ShowExcerpt .Subtitle}}
<p class="vs-feature-desc">{{.Subtitle}}</p>
{{end}}
<div style="margin-top: auto; padding-top: 16px; font-size: 13px; color: var(--vs-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="vs-hero">
<div class="vs-hero-mesh">
<div class="vs-hero-blob-1"></div>
<div class="vs-hero-blob-2"></div>
<div class="vs-hero-blob-3"></div>
</div>
<div class="vs-hero-content">
<div class="vs-badge vs-reveal visible">
<span class="vs-badge-dot"></span>
{{if .LatestRelease}}v{{.LatestReleaseTag}} released{{else}}Open Source{{end}}
</div>
<h1 class="vs-reveal visible vs-reveal-delay-1">{{if .Config.Hero.Headline}}<span class="vs-gradient-text">{{.Config.Hero.Headline}}</span>{{else}}{{.Repository.Name}}{{end}}</h1>
<p class="vs-hero-sub vs-reveal visible vs-reveal-delay-2">
{{if .Config.Hero.Subheadline}}{{.Config.Hero.Subheadline}}{{else}}{{.Repository.Description}}{{end}}
</p>
<div class="vs-hero-ctas vs-reveal visible vs-reveal-delay-3">
{{if .Config.Hero.PrimaryCTA.Label}}
<a href="{{.Config.Hero.PrimaryCTA.URL}}" class="vs-btn-primary" data-cta="primary">
{{.Config.Hero.PrimaryCTA.Label}}
{{svg "octicon-arrow-right" 16}}
</a>
{{else}}
<a href="{{.RepoURL}}" class="vs-btn-primary" data-cta="primary">
Get Started
{{svg "octicon-arrow-right" 16}}
</a>
{{end}}
{{if .Config.Hero.SecondaryCTA.Label}}
<a href="{{.Config.Hero.SecondaryCTA.URL}}" class="vs-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="vs-btn-secondary" data-cta="secondary">
<img src="/assets/img/gitcaddy-icon.svg" width="16" height="16" alt="GitCaddy">
View Source
</a>
{{end}}
</div>
{{if .Config.Hero.CodeExample}}
<div class="vs-code-block vs-reveal visible vs-reveal-delay-4">
<span class="vs-code-prompt">$</span>
<code id="install-cmd">{{.Config.Hero.CodeExample}}</code>
<button class="vs-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="vs-stats">
<div class="vs-stats-inner vs-reveal">
{{if .Config.Stats}}
{{range .Config.Stats}}
<div class="vs-stat-item">
<div class="vs-stat-value">{{.Value}}</div>
<div class="vs-stat-label">{{.Label}}</div>
</div>
{{end}}
{{else}}
<div class="vs-stat-item">
<div class="vs-stat-value">{{.NumStars}}</div>
<div class="vs-stat-label">Stars</div>
</div>
<div class="vs-stat-item">
<div class="vs-stat-value">{{.NumForks}}</div>
<div class="vs-stat-label">Forks</div>
</div>
{{if .LatestRelease}}
<div class="vs-stat-item">
<div class="vs-stat-value">v{{.LatestReleaseTag}}</div>
<div class="vs-stat-label">Latest</div>
</div>
{{end}}
{{end}}
</div>
</section>
{{end}}
<!-- Downloads Section -->
{{if and .PublicReleases .LatestRelease .LatestRelease.Attachments}}
<section class="vs-downloads">
<div class="vs-downloads-inner vs-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: 28px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 14px; font-size: 13px; color: var(--vs-muted); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600;">{{svg "octicon-device-desktop" 16}} Windows</h4>
<div class="vs-downloads-grid">
{{range $windowsFiles}}<a href="{{$.RepoURL}}/releases/download/{{$.LatestRelease.TagName}}/{{.Name}}" class="vs-download-item">{{svg "octicon-download" 16}} {{.Name}} <span class="vs-download-size">{{FileSize .Size}}</span></a>{{end}}
</div>
</div>
{{end}}
{{if $macosFiles}}
<div style="margin-bottom: 28px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 14px; font-size: 13px; color: var(--vs-muted); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600;">{{svg "octicon-device-desktop" 16}} macOS</h4>
<div class="vs-downloads-grid">
{{range $macosFiles}}<a href="{{$.RepoURL}}/releases/download/{{$.LatestRelease.TagName}}/{{.Name}}" class="vs-download-item">{{svg "octicon-download" 16}} {{.Name}} <span class="vs-download-size">{{FileSize .Size}}</span></a>{{end}}
</div>
</div>
{{end}}
{{if $linuxFiles}}
<div style="margin-bottom: 28px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 14px; font-size: 13px; color: var(--vs-muted); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600;">{{svg "octicon-terminal" 16}} Linux</h4>
<div class="vs-downloads-grid">
{{range $linuxFiles}}<a href="{{$.RepoURL}}/releases/download/{{$.LatestRelease.TagName}}/{{.Name}}" class="vs-download-item">{{svg "octicon-download" 16}} {{.Name}} <span class="vs-download-size">{{FileSize .Size}}</span></a>{{end}}
</div>
</div>
{{end}}
{{if not $.HideMobileReleases}}
{{if $androidFiles}}
<div style="margin-bottom: 28px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 14px; font-size: 13px; color: var(--vs-muted); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600;">{{svg "octicon-device-mobile" 16}} Android</h4>
<div class="vs-downloads-grid">
{{range $androidFiles}}<a href="{{$.RepoURL}}/releases/download/{{$.LatestRelease.TagName}}/{{.Name}}" class="vs-download-item">{{svg "octicon-download" 16}} {{.Name}} <span class="vs-download-size">{{FileSize .Size}}</span></a>{{end}}
</div>
</div>
{{end}}
{{if $iosFiles}}
<div style="margin-bottom: 28px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 14px; font-size: 13px; color: var(--vs-muted); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600;">{{svg "octicon-device-mobile" 16}} iOS</h4>
<div class="vs-downloads-grid">
{{range $iosFiles}}<a href="{{$.RepoURL}}/releases/download/{{$.LatestRelease.TagName}}/{{.Name}}" class="vs-download-item">{{svg "octicon-download" 16}} {{.Name}} <span class="vs-download-size">{{FileSize .Size}}</span></a>{{end}}
</div>
</div>
{{end}}
{{end}}
{{if or $.GooglePlayID $.AppStoreID}}
<div style="margin-bottom: 28px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 14px; font-size: 13px; color: var(--vs-muted); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600;">{{svg "octicon-device-mobile" 16}} App Stores</h4>
<div style="display: flex; gap: 14px; flex-wrap: wrap; justify-content: center;">
{{if $.GooglePlayID}}
<a href="https://play.google.com/store/apps/details?id={{$.GooglePlayID}}" target="_blank" rel="noopener" class="vs-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M3.609 1.814L13.792 12 3.61 22.186a.996.996 0 0 1-.61-.92V2.734a1 1 0 0 1 .609-.92zm10.89 10.893l2.302 2.302-10.937 6.333 8.635-8.635zm3.199-1.4l2.834 1.64a1 1 0 0 1 0 1.726l-2.834 1.64-2.635-2.636 2.635-2.37zM5.864 2.658L16.8 9.99l-2.302 2.302-8.635-8.635z"/></svg>
Google Play
</a>
{{end}}
{{if $.AppStoreID}}
<a href="https://apps.apple.com/app/{{$.AppStoreID}}" target="_blank" rel="noopener" class="vs-download-item" style="display: inline-flex; align-items: center; gap: 10px;">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z"/></svg>
App Store
</a>
{{end}}
</div>
</div>
{{end}}
{{if $otherFiles}}
<div style="margin-bottom: 28px;">
<h4 style="display: flex; align-items: center; gap: 8px; margin-bottom: 14px; font-size: 13px; color: var(--vs-muted); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600;">{{svg "octicon-file" 16}} Other</h4>
<div class="vs-downloads-grid">
{{range $otherFiles}}<a href="{{$.RepoURL}}/releases/download/{{$.LatestRelease.TagName}}/{{.Name}}" class="vs-download-item">{{svg "octicon-download" 16}} {{.Name}} <span class="vs-download-size">{{FileSize .Size}}</span></a>{{end}}
</div>
</div>
{{end}}
</div>
</section>
{{end}}
<!-- Value Props Section -->
{{if .Config.ValueProps}}
<section class="vs-features" id="value-props" style="background: var(--vs-surface);">
<div class="vs-features-inner">
<div class="vs-section-header vs-reveal">
<div class="vs-section-label">Why choose us</div>
<h2>{{if .Config.Brand.Name}}Why {{.Config.Brand.Name}}?{{else}}Why Choose Us{{end}}</h2>
<p>Everything you need to get started quickly.</p>
</div>
<div class="vs-features-grid">
{{range .Config.ValueProps}}
<div class="vs-feature-card vs-reveal">
<div class="vs-feature-icon">
{{svg (printf "octicon-%s" (or .Icon "check")) 20}}
</div>
<h3 class="vs-feature-title">{{.Title}}</h3>
<p class="vs-feature-desc">{{.Description}}</p>
</div>
{{end}}
</div>
</div>
</section>
{{end}}
<!-- Features Section -->
{{if .Config.Features}}
<section class="vs-features" id="features" style="padding-top: 40px;">
<div class="vs-features-inner">
<div class="vs-section-header vs-reveal">
<div class="vs-section-label">Capabilities</div>
<h2>Features</h2>
<p>Powerful capabilities at your fingertips.</p>
</div>
<div class="vs-features-grid">
{{range .Config.Features}}
<div class="vs-feature-card vs-reveal">
<div class="vs-feature-icon">
{{svg (printf "octicon-%s" (or .Icon "zap")) 20}}
</div>
<h3 class="vs-feature-title">{{.Title}}</h3>
<p class="vs-feature-desc">{{.Description}}</p>
</div>
{{end}}
</div>
</div>
</section>
{{end}}
<!-- Social Proof -->
{{if or .Config.SocialProof.Logos .Config.SocialProof.Testimonials}}
<section class="vs-social-proof">
<div class="vs-social-proof-inner">
{{if .Config.SocialProof.Logos}}
<div class="vs-logos vs-reveal">
{{range .Config.SocialProof.Logos}}
<div class="vs-logo-item">{{.}}</div>
{{end}}
</div>
{{end}}
{{if .Config.SocialProof.Testimonials}}
<div class="vs-testimonials-container">
{{range .Config.SocialProof.Testimonials}}
<div class="vs-testimonial vs-reveal" style="display: none;">
<p class="vs-testimonial-quote">"{{.Quote}}"</p>
<div>
<div class="vs-testimonial-author">{{.Author}}</div>
<div class="vs-testimonial-role">{{.Role}}</div>
</div>
</div>
{{end}}
</div>
<script>
(function() {
var testimonials = document.querySelectorAll(".vs-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="vs-pricing" id="pricing">
<div class="vs-pricing-inner">
<div class="vs-section-header vs-reveal">
<div class="vs-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="vs-pricing-grid">
{{range .Config.Pricing.Plans}}
<div class="vs-pricing-card{{if .Featured}} featured{{end}} vs-reveal">
{{if .Featured}}<span class="vs-pricing-badge">Popular</span>{{end}}
<div class="vs-pricing-name">{{.Name}}</div>
<div class="vs-pricing-price">{{.Price}}</div>
<div class="vs-pricing-period">{{.Period}}</div>
{{if .Features}}
<ul class="vs-pricing-features">
{{range .Features}}<li>{{.}}</li>{{end}}
</ul>
{{end}}
<a href="#" class="vs-pricing-cta">{{if .CTA}}{{.CTA}}{{else}}Get Started{{end}}</a>
</div>
{{end}}
</div>
</div>
</section>
{{end}}
<!-- CTA Section -->
{{if .Config.CTASection.Headline}}
<section class="vs-cta-section">
<div class="vs-cta-inner vs-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="vs-btn-primary" data-cta="primary" style="padding: 18px 36px; font-size: 16px;">
{{if .Config.CTASection.Button.Label}}{{.Config.CTASection.Button.Label}}{{else}}Get Started{{end}}
{{svg "octicon-arrow-right" 16}}
</a>
</div>
</section>
{{end}}
<!-- Blog Section -->
{{if and .Config.Blog.Enabled .BlogPosts}}
<section class="vs-features" id="blog" style="border-top: 1px solid var(--vs-border); background: var(--vs-surface);">
<div class="vs-features-inner">
<div class="vs-section-header vs-reveal">
<div class="vs-section-label">Blog</div>
<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="vs-features-grid">
{{range .BlogPosts}}
<a href="{{$.BlogBaseURL}}/{{.ID}}" class="vs-feature-card vs-reveal" style="text-decoration: none; color: inherit; display: flex; flex-direction: column;">
{{if .FeaturedImage}}
<div style="margin: -36px -36px 22px -36px; overflow: hidden; border-radius: 16px 16px 0 0;">
<img src="{{.FeaturedImage.DownloadURL}}" alt="{{.Title}}" style="width: 100%; height: 200px; object-fit: cover; display: block;">
</div>
{{end}}
<h3 class="vs-feature-title">{{.Title}}</h3>
{{if and $.Config.Blog.ShowExcerpt .Subtitle}}
<p class="vs-feature-desc">{{.Subtitle}}</p>
{{end}}
<div style="margin-top: auto; padding-top: 16px; font-size: 13px; color: var(--vs-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="vs-reveal">
<a href="{{if .Config.Blog.CTAButton.URL}}{{.Config.Blog.CTAButton.URL}}{{else}}{{.BlogBaseURL}}{{end}}" class="vs-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="vs-features" id="gallery" style="border-top: 1px solid var(--vs-border);">
<div class="vs-features-inner">
<div class="vs-section-header vs-reveal">
<div class="vs-section-label">Gallery</div>
<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: 20px;">
{{range .GalleryImages}}
<div class="vs-feature-card vs-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: 240px; object-fit: cover; display: block;">
</a>
{{if .Caption}}
<div style="padding: 18px 22px; font-size: 14px; color: var(--vs-muted);">{{.Caption}}</div>
{{end}}
</div>
{{end}}
</div>
</div>
</section>
{{end}}
{{end}}{{/* end PageIsBlogDetail / PageIsBlogList / else */}}
<!-- Footer -->
<footer class="vs-footer">
<div class="vs-footer-brand">
<div class="vs-footer-logo">{{svg "octicon-zap" 12}}</div>
<span class="vs-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="vs-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="vs-footer-links">
{{range .Config.Footer.Links}}
<a href="{{.URL}}" class="vs-footer-link">{{.Label}}</a>
{{end}}
{{if .Config.Navigation.ShowRepository}}<a href="{{.RepoURL}}" class="vs-footer-link">Repository</a>{{end}}
{{if .Config.Navigation.ShowDocs}}<a href="{{.RepoURL}}/wiki" class="vs-footer-link">Documentation</a>{{end}}
{{if .Config.Navigation.ShowReleases}}<a href="{{.RepoURL}}/releases" class="vs-footer-link">Releases</a>{{end}}
{{if .Config.Navigation.ShowIssues}}<a href="{{.RepoURL}}/issues" class="vs-footer-link">Issues</a>{{end}}
</div>
</footer>
</div>
<!-- Scroll reveal observer -->
<script>
(function() {
var reveals = document.querySelectorAll('.vs-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" .}}