mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 07:41:27 +00:00
feat(site): standalone landing page for Vercel
Static single-file page in site/ matching the app theme (dark #0c0d0f, lime #b4e34a, self-hosted Inter + JetBrains Mono). Real product screenshots, install one-liner with copy button, template marquee, tabbed gallery. No build step; deployed via vercel deploy --prod. Live: https://containr-lyart.vercel.app Authored By: TDvorak <[email protected]> Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
84b102a2c6
commit
2b38406a63
10 files changed
+779
No files matched your search
@@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0" y1="0" x2="64" y2="64" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#cdf06f"/>
|
||||
<stop offset="1" stop-color="#7fb524"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="64" height="64" rx="15" fill="url(#bg)"/>
|
||||
<g fill="none" stroke="#131507" stroke-width="4.2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="15" y="18" width="34" height="26" rx="4"/>
|
||||
<path d="M23 18v26M31 18v26M39 18v26"/>
|
||||
<path d="M20 51h24"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 582 B |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+765
@@ -0,0 +1,765 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Containr - Self-hosted platform for containerized services</title>
|
||||
<meta name="description" content="Containr turns any Docker host into a full deployment platform. Visual project canvases, git-push builds, managed databases, preview environments, and HA failover on your own hardware.">
|
||||
<meta property="og:title" content="Containr">
|
||||
<meta property="og:description" content="Railway-style project canvases on your own hardware. Open source, MIT licensed.">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://containr-lyart.vercel.app/">
|
||||
<meta property="og:image" content="https://containr-lyart.vercel.app/screenshots/canvas.png">
|
||||
<link rel="canonical" href="https://containr-lyart.vercel.app/">
|
||||
<meta name="theme-color" content="#0c0d0f">
|
||||
<link rel="icon" type="image/svg+xml" href="containr.svg">
|
||||
<script>document.documentElement.classList.add('js');</script>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url('fonts/inter-latin.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url('fonts/inter-latin-ext.woff2') format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: normal;
|
||||
font-weight: 400 800;
|
||||
font-display: swap;
|
||||
src: url('fonts/jbmono-latin.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: normal;
|
||||
font-weight: 400 800;
|
||||
font-display: swap;
|
||||
src: url('fonts/jbmono-latin-ext.woff2') format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg-void: #0c0d0f;
|
||||
--bg-base: #0e0f12;
|
||||
--surface-card: #131518;
|
||||
--surface-card-hover: #1a1d22;
|
||||
--surface-muted: #1a1d22;
|
||||
--border-subtle: rgba(255, 255, 255, 0.06);
|
||||
--border-default: rgba(255, 255, 255, 0.1);
|
||||
--border-strong: rgba(255, 255, 255, 0.18);
|
||||
--text-primary: #ecedf0;
|
||||
--text-secondary: #8b919e;
|
||||
--text-tertiary: #575d68;
|
||||
--accent: #b4e34a;
|
||||
--accent-dim: rgba(180, 227, 74, 0.12);
|
||||
--accent-on: #131507;
|
||||
--mono: 'JetBrains Mono', 'SF Mono', Consolas, monospace;
|
||||
--sans: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
--radius: 14px;
|
||||
--radius-sm: 9px;
|
||||
}
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
html { scroll-behavior: smooth; }
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: var(--bg-void);
|
||||
color: var(--text-secondary);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
line-height: 1.5;
|
||||
}
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: radial-gradient(1000px 480px at 50% -8%, rgba(180, 227, 74, 0.05), transparent 60%);
|
||||
pointer-events: none;
|
||||
}
|
||||
::selection { background: rgba(180, 227, 74, 0.25); color: var(--text-primary); }
|
||||
|
||||
a { color: inherit; text-decoration: none; }
|
||||
img { display: block; max-width: 100%; }
|
||||
.wrap { max-width: 1120px; margin: 0 auto; padding: 0 24px; }
|
||||
|
||||
/* ---------- nav ---------- */
|
||||
nav {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 50;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
background: rgba(14, 15, 18, 0.82);
|
||||
backdrop-filter: blur(14px);
|
||||
-webkit-backdrop-filter: blur(14px);
|
||||
}
|
||||
.nav-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 60px;
|
||||
gap: 28px;
|
||||
}
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 9px;
|
||||
font-weight: 800;
|
||||
font-size: 15px;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.brand img { width: 26px; height: 26px; border-radius: 7px; }
|
||||
.brand em { font-style: normal; color: var(--accent); }
|
||||
.nav-links { display: flex; gap: 4px; margin-left: 8px; }
|
||||
.nav-links a {
|
||||
font-size: 13px;
|
||||
padding: 6px 12px;
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text-secondary);
|
||||
transition: color 0.15s, background 0.15s;
|
||||
}
|
||||
.nav-links a:hover { color: var(--text-primary); background: var(--surface-muted); }
|
||||
.nav-right { margin-left: auto; display: flex; align-items: center; gap: 10px; }
|
||||
.gh-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--border-subtle);
|
||||
color: var(--text-secondary);
|
||||
transition: color 0.15s, border-color 0.15s;
|
||||
}
|
||||
.gh-link:hover { color: var(--text-primary); border-color: var(--border-strong); }
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
padding: 8px 16px;
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: transform 0.15s, border-color 0.15s, background 0.15s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.btn-primary { background: var(--accent); color: var(--accent-on); }
|
||||
.btn-primary:hover { transform: translateY(-1px); }
|
||||
.btn-ghost { border-color: var(--border-default); color: var(--text-secondary); }
|
||||
.btn-ghost:hover { color: var(--text-primary); border-color: var(--border-strong); }
|
||||
|
||||
/* ---------- hero ---------- */
|
||||
.hero { padding: 84px 0 0; }
|
||||
.pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-tertiary);
|
||||
border: 1px solid var(--border-subtle);
|
||||
background: var(--surface-card);
|
||||
padding: 6px 14px;
|
||||
border-radius: 999px;
|
||||
}
|
||||
.pill svg { color: var(--accent); }
|
||||
.hero h1 {
|
||||
margin-top: 22px;
|
||||
font-size: clamp(34px, 5.4vw, 58px);
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.035em;
|
||||
line-height: 1.06;
|
||||
color: var(--text-primary);
|
||||
max-width: 22ch;
|
||||
}
|
||||
.hero h1 em { font-style: normal; color: var(--accent); }
|
||||
.hero .sub {
|
||||
margin-top: 18px;
|
||||
font-size: 15.5px;
|
||||
line-height: 1.65;
|
||||
max-width: 46ch;
|
||||
}
|
||||
.hero-cta { display: flex; gap: 12px; margin-top: 30px; flex-wrap: wrap; }
|
||||
.hero-cta .btn { font-size: 14px; padding: 11px 22px; }
|
||||
|
||||
.shot-frame {
|
||||
margin-top: 56px;
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface-card);
|
||||
overflow: hidden;
|
||||
box-shadow: 0 30px 80px -30px rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
.shot-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 9px 14px;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
.shot-bar .addr {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
background: var(--bg-void);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 6px;
|
||||
padding: 3px 0;
|
||||
max-width: 320px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.shot-frame img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; object-position: top; }
|
||||
|
||||
/* ---------- install ---------- */
|
||||
.install { padding: 72px 0 0; }
|
||||
.install-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 28px;
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: var(--radius);
|
||||
background: var(--bg-base);
|
||||
padding: 26px 30px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.install-copy h2 { font-size: 17px; font-weight: 700; color: var(--text-primary); letter-spacing: -0.01em; }
|
||||
.install-copy p { font-size: 13px; margin-top: 4px; }
|
||||
.cmd {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
font-family: var(--mono);
|
||||
font-size: 12.5px;
|
||||
background: var(--bg-void);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: var(--radius-sm);
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
}
|
||||
.cmd code {
|
||||
padding: 11px 14px;
|
||||
color: var(--text-primary);
|
||||
white-space: nowrap;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.cmd code::-webkit-scrollbar { display: none; }
|
||||
.cmd code::before { content: '$ '; color: var(--accent); }
|
||||
.copy-btn {
|
||||
border: none;
|
||||
border-left: 1px solid var(--border-subtle);
|
||||
background: transparent;
|
||||
color: var(--text-tertiary);
|
||||
padding: 11px 13px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: color 0.15s, background 0.15s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.copy-btn:hover { color: var(--accent); background: var(--surface-muted); }
|
||||
|
||||
/* ---------- sections ---------- */
|
||||
section { padding: 96px 0 0; }
|
||||
.sec-head { margin-bottom: 40px; }
|
||||
.sec-head h2 {
|
||||
font-size: clamp(24px, 3vw, 32px);
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.025em;
|
||||
color: var(--text-primary);
|
||||
max-width: 22ch;
|
||||
}
|
||||
.sec-head p { margin-top: 10px; font-size: 14.5px; max-width: 56ch; }
|
||||
|
||||
/* ---------- bento ---------- */
|
||||
.bento {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
gap: 14px;
|
||||
}
|
||||
.cell {
|
||||
grid-column: span 2;
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface-card);
|
||||
padding: 22px;
|
||||
transition: border-color 0.15s, transform 0.15s;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.cell:hover { border-color: var(--border-default); transform: translateY(-2px); }
|
||||
.cell.wide { grid-column: span 4; display: flex; gap: 26px; align-items: stretch; }
|
||||
.cell h3 { font-size: 14.5px; font-weight: 700; color: var(--text-primary); letter-spacing: -0.01em; }
|
||||
.cell p { font-size: 12.5px; line-height: 1.6; margin-top: 8px; color: var(--text-tertiary); }
|
||||
.cell .icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
background: var(--accent-dim);
|
||||
color: var(--accent);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.cell.wide .icon { margin-bottom: 12px; }
|
||||
.cell-media {
|
||||
flex: 0 0 46%;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border-subtle);
|
||||
align-self: center;
|
||||
}
|
||||
.cell-media img { width: 100%; height: 100%; object-fit: cover; object-position: left top; }
|
||||
.cell.tinted { background: linear-gradient(160deg, rgba(180, 227, 74, 0.09), rgba(180, 227, 74, 0.02) 55%), var(--surface-card); }
|
||||
.chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 14px; }
|
||||
.chips span {
|
||||
font-family: var(--mono);
|
||||
font-size: 10.5px;
|
||||
color: var(--text-secondary);
|
||||
background: var(--bg-void);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 6px;
|
||||
padding: 3px 8px;
|
||||
}
|
||||
.snippet {
|
||||
margin-top: 14px;
|
||||
font-family: var(--mono);
|
||||
font-size: 11.5px;
|
||||
background: var(--bg-void);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 8px;
|
||||
padding: 10px 12px;
|
||||
color: var(--text-secondary);
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.snippet b { color: var(--accent); font-weight: 500; }
|
||||
|
||||
/* ---------- screenshots ---------- */
|
||||
.shot-tabs { display: flex; gap: 6px; margin-bottom: 18px; flex-wrap: wrap; }
|
||||
.shot-tab {
|
||||
font-family: var(--mono);
|
||||
font-size: 11.5px;
|
||||
color: var(--text-tertiary);
|
||||
background: transparent;
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 999px;
|
||||
padding: 7px 15px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.shot-tab:hover { color: var(--text-secondary); border-color: var(--border-default); }
|
||||
.shot-tab.on { color: var(--accent); border-color: rgba(180, 227, 74, 0.4); background: var(--accent-dim); }
|
||||
.gallery img { width: 100%; border-radius: var(--radius); border: 1px solid var(--border-default); }
|
||||
.gallery .cap { font-size: 12.5px; color: var(--text-tertiary); margin-top: 12px; }
|
||||
|
||||
/* ---------- templates marquee ---------- */
|
||||
.marquee {
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: var(--radius);
|
||||
background: var(--bg-base);
|
||||
padding: 18px 0;
|
||||
mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
|
||||
-webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
|
||||
}
|
||||
.marquee-track {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
width: max-content;
|
||||
animation: scroll 46s linear infinite;
|
||||
}
|
||||
@keyframes scroll { to { transform: translateX(-50%); } }
|
||||
.tpl {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 12.5px;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
border: 1px solid var(--border-subtle);
|
||||
background: var(--surface-card);
|
||||
border-radius: 999px;
|
||||
padding: 8px 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.tpl img { width: 15px; height: 15px; }
|
||||
|
||||
/* ---------- access ---------- */
|
||||
.access {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 0;
|
||||
border-top: 1px solid var(--border-subtle);
|
||||
}
|
||||
.access > div { padding: 28px 28px 4px 0; }
|
||||
.access > div + div { padding-left: 28px; border-left: 1px solid var(--border-subtle); }
|
||||
.access h3 {
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--accent);
|
||||
}
|
||||
.access ul { list-style: none; margin-top: 14px; }
|
||||
.access li { font-size: 13px; padding: 5px 0; color: var(--text-secondary); }
|
||||
.access li::before { content: '- '; color: var(--text-tertiary); }
|
||||
|
||||
/* ---------- cta ---------- */
|
||||
.cta { text-align: center; padding: 110px 0; }
|
||||
.cta h2 {
|
||||
font-size: clamp(30px, 4.5vw, 46px);
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.03em;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.cta p { margin: 14px auto 0; font-size: 14.5px; max-width: 46ch; }
|
||||
.cta .hero-cta { justify-content: center; }
|
||||
|
||||
/* ---------- footer ---------- */
|
||||
footer { border-top: 1px solid var(--border-subtle); }
|
||||
.foot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 26px 0;
|
||||
font-size: 12.5px;
|
||||
color: var(--text-tertiary);
|
||||
flex-wrap: wrap;
|
||||
gap: 14px;
|
||||
}
|
||||
.foot .l { display: flex; align-items: center; gap: 8px; }
|
||||
.foot img { width: 18px; height: 18px; border-radius: 5px; }
|
||||
.foot nav { display: flex; gap: 20px; position: static; border: none; background: none; backdrop-filter: none; -webkit-backdrop-filter: none; }
|
||||
.foot nav a:hover { color: var(--text-primary); }
|
||||
|
||||
/* ---------- reveal (JS-gated: content stays visible without JS) ---------- */
|
||||
.js .reveal { opacity: 0; transform: translateY(16px); transition: opacity 0.55s ease, transform 0.55s ease; }
|
||||
.js .reveal.in { opacity: 1; transform: none; }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html { scroll-behavior: auto; }
|
||||
.marquee-track { animation: none; }
|
||||
.reveal { opacity: 1; transform: none; transition: none; }
|
||||
.btn-primary:hover, .cell:hover { transform: none; }
|
||||
}
|
||||
|
||||
/* ---------- responsive ---------- */
|
||||
@media (max-width: 860px) {
|
||||
.nav-links { display: none; }
|
||||
.bento { grid-template-columns: repeat(2, 1fr); }
|
||||
.cell, .cell.wide { grid-column: span 2; }
|
||||
.cell.wide { flex-direction: column; }
|
||||
.cell-media { flex: none; }
|
||||
.access { grid-template-columns: 1fr; }
|
||||
.access > div { padding: 22px 0; }
|
||||
.access > div + div { padding-left: 0; border-left: none; border-top: 1px solid var(--border-subtle); }
|
||||
section { padding: 72px 0 0; }
|
||||
.hero { padding-top: 56px; }
|
||||
}
|
||||
@media (max-width: 520px) {
|
||||
.bento { grid-template-columns: 1fr; }
|
||||
.cell, .cell.wide { grid-column: span 1; }
|
||||
.install-inner { padding: 20px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav>
|
||||
<div class="wrap nav-inner">
|
||||
<a href="#" class="brand">
|
||||
<img src="containr.svg" alt="Containr logo">
|
||||
contain<em>r</em>
|
||||
</a>
|
||||
<div class="nav-links">
|
||||
<a href="#features">Features</a>
|
||||
<a href="#product">Product</a>
|
||||
<a href="#templates">Templates</a>
|
||||
<a href="https://github.com/Dvorinka/Containr/tree/main/docs/guides" target="_blank" rel="noopener">Docs</a>
|
||||
</div>
|
||||
<div class="nav-right">
|
||||
<a class="gh-link" href="https://github.com/Dvorinka/Containr" target="_blank" rel="noopener" aria-label="Containr on GitHub">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg>
|
||||
</a>
|
||||
<a class="btn btn-primary" href="#install">Get started</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<header class="hero">
|
||||
<div class="wrap">
|
||||
<span class="pill">
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"><rect x="3" y="7" width="18" height="12" rx="2"/><path d="M8 7v12M13 7v12M18 7v12M6 22h12"/></svg>
|
||||
Self-hosted PaaS · MIT license
|
||||
</span>
|
||||
<h1>Deploy containers on <em>your own cloud</em></h1>
|
||||
<p class="sub">Containr turns a Docker host into a full deployment platform. Git builds, managed databases, previews, failover.</p>
|
||||
<div class="hero-cta">
|
||||
<a class="btn btn-primary" href="#install">
|
||||
Get started
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
|
||||
</a>
|
||||
<a class="btn btn-ghost" href="https://github.com/Dvorinka/Containr" target="_blank" rel="noopener">View on GitHub</a>
|
||||
</div>
|
||||
|
||||
<div class="shot-frame reveal">
|
||||
<div class="shot-bar"><span class="addr">localhost:3000/canvas</span></div>
|
||||
<img src="screenshots/canvas.png" alt="Containr project canvas with two connected services" fetchpriority="high">
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="install" id="install">
|
||||
<div class="wrap">
|
||||
<div class="install-inner reveal">
|
||||
<div class="install-copy">
|
||||
<h2>One command, running stack</h2>
|
||||
<p>Requires Docker and openssl. Installs into ./containr, never overwrites your .env.</p>
|
||||
</div>
|
||||
<div class="cmd">
|
||||
<code id="install-cmd">curl -fsSL https://raw.githubusercontent.com/Dvorinka/Containr/main/install.sh | bash</code>
|
||||
<button class="copy-btn" id="copy-btn" aria-label="Copy install command">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section id="features">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<h2>A control plane, not a pile of YAML</h2>
|
||||
<p>Everything a deployment platform needs, running on hardware you already own. One install, one dashboard.</p>
|
||||
</div>
|
||||
<div class="bento">
|
||||
<div class="cell wide reveal">
|
||||
<div>
|
||||
<div class="icon">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/><path d="M10 6.5h5.5a2 2 0 0 1 2 2V14M6.5 10v5.5a2 2 0 0 0 2 2H14"/></svg>
|
||||
</div>
|
||||
<h3>Visual project canvas</h3>
|
||||
<p>Services appear as nodes on a Railway-style canvas. Drag, group, and wire them together; connections and shared variables are inferred automatically. Start, stop, restart, or roll back with a right-click.</p>
|
||||
</div>
|
||||
<div class="cell-media"><img src="screenshots/service-detail.png" alt="Service view with metrics, logs, and configuration tabs" loading="lazy"></div>
|
||||
</div>
|
||||
<div class="cell tinted reveal">
|
||||
<div class="icon">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><ellipse cx="12" cy="5" rx="8" ry="3"/><path d="M4 5v6c0 1.66 3.58 3 8 3s8-1.34 8-3V5"/><path d="M4 11v6c0 1.66 3.58 3 8 3s8-1.34 8-3v-6"/></svg>
|
||||
</div>
|
||||
<h3>Managed databases</h3>
|
||||
<p>Provision engines from the dashboard, with backups and restore built in.</p>
|
||||
<div class="chips">
|
||||
<span>PostgreSQL</span><span>MySQL</span><span>MariaDB</span><span>MongoDB</span><span>Redis</span><span>Dragonfly</span><span>ClickHouse</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cell reveal">
|
||||
<div class="icon">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="6" r="2.5"/><circle cx="18" cy="6" r="2.5"/><circle cx="12" cy="18" r="2.5"/><path d="M8 7.5 10.5 16M16 7.5 13.5 16M8.5 6h7"/></svg>
|
||||
</div>
|
||||
<h3>Private networking</h3>
|
||||
<p>Every project gets an isolated Docker network. Services reach each other by name.</p>
|
||||
<div class="snippet">web:3000 <b>→</b> ${{service.KEY}}</div>
|
||||
</div>
|
||||
<div class="cell reveal">
|
||||
<div class="icon">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3v12"/><circle cx="6" cy="18" r="2.5"/><circle cx="18" cy="6" r="2.5"/><path d="M18 8.5a5 5 0 0 1-5 5H9"/></svg>
|
||||
</div>
|
||||
<h3>Git-push deploys</h3>
|
||||
<p>Connect GitHub, GitLab, Bitbucket, or Gitea. Builds, history, and image rollback included.</p>
|
||||
</div>
|
||||
<div class="cell reveal">
|
||||
<div class="icon">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 17l6-6 4 4 8-8"/><path d="M14 7h7v7"/></svg>
|
||||
</div>
|
||||
<h3>HA and autoscaling</h3>
|
||||
<p>Failover policies, health checks, node agents, and metric-driven replica scaling.</p>
|
||||
</div>
|
||||
<div class="cell reveal">
|
||||
<div class="icon">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2 4 6v6c0 5 3.4 8.6 8 10 4.6-1.4 8-5 8-10V6l-8-4Z"/><path d="m9 12 2 2 4-4"/></svg>
|
||||
</div>
|
||||
<h3>Admin and audit</h3>
|
||||
<p>Approval console, user management, audit logs, and vulnerability scans behind sign-in.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="product">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<h2>The whole fleet on one screen</h2>
|
||||
<p>Logs, metrics, builds, and rollbacks without leaving the dashboard. Host telemetry included.</p>
|
||||
</div>
|
||||
<div class="gallery reveal">
|
||||
<div class="shot-tabs" role="tablist" aria-label="Product screenshots">
|
||||
<button class="shot-tab on" role="tab" aria-selected="true" data-shot="dashboard" data-cap="Projects dashboard with host CPU, memory, and disk telemetry.">Dashboard</button>
|
||||
<button class="shot-tab" role="tab" aria-selected="false" data-shot="canvas" data-cap="Visual service canvas with auto-inferred connections.">Canvas</button>
|
||||
<button class="shot-tab" role="tab" aria-selected="false" data-shot="service-detail" data-cap="Service detail with metrics, logs, and config tabs.">Service detail</button>
|
||||
<button class="shot-tab" role="tab" aria-selected="false" data-shot="databases" data-cap="Managed databases with backup and restore.">Databases</button>
|
||||
</div>
|
||||
<img id="gallery-img" src="screenshots/dashboard.png" alt="Containr dashboard" loading="lazy">
|
||||
<p class="cap" id="gallery-cap">Projects dashboard with host CPU, memory, and disk telemetry.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="templates">
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<h2>A catalog worth browsing</h2>
|
||||
<p>Thirty official templates plus your own. One click from catalog to running service.</p>
|
||||
</div>
|
||||
<div class="marquee reveal" aria-label="Template catalog">
|
||||
<div class="marquee-track" id="marquee-track">
|
||||
<span class="tpl"><img src="https://cdn.simpleicons.org/n8n/8b919e" alt="" loading="lazy" onerror="this.remove()">n8n</span>
|
||||
<span class="tpl"><img src="https://cdn.simpleicons.org/nextcloud/8b919e" alt="" loading="lazy" onerror="this.remove()">Nextcloud</span>
|
||||
<span class="tpl"><img src="https://cdn.simpleicons.org/immich/8b919e" alt="" loading="lazy" onerror="this.remove()">Immich</span>
|
||||
<span class="tpl"><img src="https://cdn.simpleicons.org/vaultwarden/8b919e" alt="" loading="lazy" onerror="this.remove()">Vaultwarden</span>
|
||||
<span class="tpl"><img src="https://cdn.simpleicons.org/grafana/8b919e" alt="" loading="lazy" onerror="this.remove()">Grafana</span>
|
||||
<span class="tpl"><img src="https://cdn.simpleicons.org/supabase/8b919e" alt="" loading="lazy" onerror="this.remove()">Supabase</span>
|
||||
<span class="tpl"><img src="https://cdn.simpleicons.org/gitea/8b919e" alt="" loading="lazy" onerror="this.remove()">Gitea</span>
|
||||
<span class="tpl"><img src="https://cdn.simpleicons.org/uptimekuma/8b919e" alt="" loading="lazy" onerror="this.remove()">Uptime Kuma</span>
|
||||
<span class="tpl"><img src="https://cdn.simpleicons.org/pihole/8b919e" alt="" loading="lazy" onerror="this.remove()">Pi-hole</span>
|
||||
<span class="tpl"><img src="https://cdn.simpleicons.org/jellyfin/8b919e" alt="" loading="lazy" onerror="this.remove()">Jellyfin</span>
|
||||
<span class="tpl"><img src="https://cdn.simpleicons.org/meilisearch/8b919e" alt="" loading="lazy" onerror="this.remove()">Meilisearch</span>
|
||||
<span class="tpl"><img src="https://cdn.simpleicons.org/mastodon/8b919e" alt="" loading="lazy" onerror="this.remove()">Mastodon</span>
|
||||
<span class="tpl"><img src="https://cdn.simpleicons.org/homeassistant/8b919e" alt="" loading="lazy" onerror="this.remove()">Home Assistant</span>
|
||||
<span class="tpl"><img src="https://cdn.simpleicons.org/plausibleanalytics/8b919e" alt="" loading="lazy" onerror="this.remove()">Plausible</span>
|
||||
<span class="tpl"><img src="https://cdn.simpleicons.org/minio/8b919e" alt="" loading="lazy" onerror="this.remove()">MinIO</span>
|
||||
<span class="tpl"><img src="https://cdn.simpleicons.org/appwrite/8b919e" alt="" loading="lazy" onerror="this.remove()">Appwrite</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="wrap">
|
||||
<div class="sec-head reveal">
|
||||
<h2>Open for visitors, locked for owners</h2>
|
||||
<p>The platform doubles as a public showcase. Approved projects are browsable anonymously; secrets never are.</p>
|
||||
</div>
|
||||
<div class="access reveal">
|
||||
<div>
|
||||
<h3>Anonymous</h3>
|
||||
<ul>
|
||||
<li>Browse approved projects</li>
|
||||
<li>Template catalog and docs</li>
|
||||
<li>HA status overview</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Signed in</h3>
|
||||
<ul>
|
||||
<li>Projects, services, databases</li>
|
||||
<li>Logs, variables, builds, deploys</li>
|
||||
<li>Custom templates</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Admin</h3>
|
||||
<ul>
|
||||
<li>Project approval and moderation</li>
|
||||
<li>User flags and agent tokens</li>
|
||||
<li>Platform settings</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="cta">
|
||||
<div class="wrap reveal">
|
||||
<h2>Run your own cloud.</h2>
|
||||
<p>One Docker host is all it takes. Free, open source, and yours to keep.</p>
|
||||
<div class="hero-cta">
|
||||
<a class="btn btn-primary" href="#install">
|
||||
Get started
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
|
||||
</a>
|
||||
<a class="btn btn-ghost" href="https://github.com/Dvorinka/Containr" target="_blank" rel="noopener">Star on GitHub</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<div class="wrap foot">
|
||||
<span class="l"><img src="containr.svg" alt="">Containr - MIT License</span>
|
||||
<nav>
|
||||
<a href="https://github.com/Dvorinka/Containr" target="_blank" rel="noopener">GitHub</a>
|
||||
<a href="https://github.com/Dvorinka/Containr/tree/main/docs" target="_blank" rel="noopener">Docs</a>
|
||||
<a href="https://github.com/Dvorinka/Containr/blob/main/CONTRIBUTING.md" target="_blank" rel="noopener">Contribute</a>
|
||||
<a href="https://github.com/Dvorinka/Containr/blob/main/SECURITY.md" target="_blank" rel="noopener">Security</a>
|
||||
</nav>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
// Copy install command
|
||||
document.getElementById('copy-btn').addEventListener('click', async (e) => {
|
||||
const cmd = document.getElementById('install-cmd').textContent;
|
||||
try {
|
||||
await navigator.clipboard.writeText(cmd);
|
||||
} catch {
|
||||
const r = document.createRange();
|
||||
r.selectNodeContents(document.getElementById('install-cmd'));
|
||||
const s = getSelection();
|
||||
s.removeAllRanges();
|
||||
s.addRange(r);
|
||||
}
|
||||
const btn = e.currentTarget;
|
||||
btn.style.color = 'var(--accent)';
|
||||
setTimeout(() => (btn.style.color = ''), 1200);
|
||||
});
|
||||
|
||||
// Screenshot gallery tabs
|
||||
const img = document.getElementById('gallery-img');
|
||||
const cap = document.getElementById('gallery-cap');
|
||||
document.querySelectorAll('.shot-tab').forEach((tab) => {
|
||||
tab.addEventListener('click', () => {
|
||||
document.querySelectorAll('.shot-tab').forEach((t) => {
|
||||
t.classList.remove('on');
|
||||
t.setAttribute('aria-selected', 'false');
|
||||
});
|
||||
tab.classList.add('on');
|
||||
tab.setAttribute('aria-selected', 'true');
|
||||
img.src = 'screenshots/' + tab.dataset.shot + '.png';
|
||||
img.alt = 'Containr ' + tab.textContent.trim() + ' screenshot';
|
||||
cap.textContent = tab.dataset.cap;
|
||||
});
|
||||
});
|
||||
|
||||
// Duplicate marquee track for seamless loop
|
||||
const track = document.getElementById('marquee-track');
|
||||
track.innerHTML += track.innerHTML;
|
||||
|
||||
// Scroll reveal
|
||||
const io = new IntersectionObserver(
|
||||
(entries) => entries.forEach((en) => en.isIntersecting && en.target.classList.add('in')),
|
||||
{ threshold: 0.12 }
|
||||
);
|
||||
document.querySelectorAll('.reveal').forEach((el) => io.observe(el));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 109 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 85 KiB |
Reference in new issue
Block a user