955 lines
55 KiB
HTML
955 lines
55 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta name="description" content="Odysseus — a self-hosted AI workspace: chat, agents, tools, model serving, email, research, and more. Your models, your hardware, your data.">
|
|
<title>Odysseus — A Self-Hosted AI Workspace</title>
|
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cpath d='M16 4L16 22L6 22Z' fill='%23e06c75'/%3E%3Cpath d='M16 8L16 22L24 22Z' fill='%23e06c75' opacity='0.6'/%3E%3Cpath d='M4 24Q10 20 16 24Q22 28 28 24' stroke='%23e06c75' stroke-width='2.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E">
|
|
<style>
|
|
:root {
|
|
/* Odysseus default theme — exact app tokens */
|
|
--bg: #282c34;
|
|
--bg2: #1e2228; /* app code/hl background */
|
|
--panel: #111; /* app panel surface */
|
|
--panel2: #1e2228;
|
|
--fg: #9cdef2; /* signature cyan text */
|
|
--heading: #9cdef2;
|
|
--muted: #6b8a94; /* app subheader */
|
|
--border: #355a66; /* teal border */
|
|
--accent: #e06c75; /* app accent (the send-button coral) */
|
|
--accent2: #f0989e; /* lighter coral for gradients */
|
|
--green: #50fa7b;
|
|
--gold: #f0ad4e; /* app --warn */
|
|
--red: #e06c75;
|
|
--radius: 8px;
|
|
}
|
|
* { box-sizing: border-box; }
|
|
html { scroll-behavior: smooth; scroll-snap-type: y mandatory; scroll-padding-top: 60px; }
|
|
/* Each section is a full-viewport "page" with its content centered, so only
|
|
one shows at a time and the snap is obvious. */
|
|
.hero, section {
|
|
scroll-snap-align: start; min-height: 100vh;
|
|
display: flex; flex-direction: column; justify-content: center;
|
|
}
|
|
/* Alternate the page backgrounds: slate (the body) ↔ black, to make each
|
|
page boundary obvious. */
|
|
/* Subtle dot-grid texture across the whole page. */
|
|
section:nth-of-type(odd) {
|
|
background-color: #111111;
|
|
background-image: radial-gradient(circle, rgba(156,222,242,0.075) 1px, transparent 1.4px);
|
|
background-size: 24px 24px;
|
|
}
|
|
section:nth-of-type(even) {
|
|
background-color: var(--bg);
|
|
background-image: radial-gradient(circle, rgba(156,222,242,0.06) 1px, transparent 1.4px);
|
|
background-size: 24px 24px;
|
|
}
|
|
/* Customers section gets a brand-colored gradient glow over the dots. */
|
|
#testimonials {
|
|
background-color: var(--bg);
|
|
background-image:
|
|
radial-gradient(900px 520px at 80% 8%, rgba(224,108,117,0.14), transparent 60%),
|
|
radial-gradient(760px 520px at 8% 96%, rgba(53,90,102,0.32), transparent 58%),
|
|
radial-gradient(circle, rgba(156,222,242,0.06) 1px, transparent 1.4px);
|
|
background-size: cover, cover, 24px 24px;
|
|
}
|
|
/* Domino reveal — each section fades/slides up as it scrolls into view. */
|
|
.hero, section { opacity: 0; transform: translateY(24px); transition: opacity .6s cubic-bezier(.2,.7,.2,1), transform .6s cubic-bezier(.2,.7,.2,1); }
|
|
.hero.in, section.in { opacity: 1; transform: none; }
|
|
@media (prefers-reduced-motion: reduce) {
|
|
html { scroll-snap-type: none; }
|
|
.hero, section { opacity: 1 !important; transform: none !important; transition: none; }
|
|
}
|
|
/* Capabilities cards cascade in like the app's domino expand. */
|
|
#features .feature { opacity: 0; transform: translateY(16px); }
|
|
#features.in .feature { animation: domino-in .5s cubic-bezier(.2,.7,.2,1) forwards; }
|
|
#features.in .feature:nth-child(1) { animation-delay: .04s; }
|
|
#features.in .feature:nth-child(2) { animation-delay: .09s; }
|
|
#features.in .feature:nth-child(3) { animation-delay: .14s; }
|
|
#features.in .feature:nth-child(4) { animation-delay: .19s; }
|
|
#features.in .feature:nth-child(5) { animation-delay: .24s; }
|
|
#features.in .feature:nth-child(6) { animation-delay: .29s; }
|
|
#features.in .feature:nth-child(7) { animation-delay: .34s; }
|
|
#features.in .feature:nth-child(8) { animation-delay: .39s; }
|
|
#features.in .feature:nth-child(9) { animation-delay: .44s; }
|
|
@keyframes domino-in { to { opacity: 1; transform: none; } }
|
|
body {
|
|
margin: 0;
|
|
background:
|
|
radial-gradient(1100px 520px at 82% -10%, rgba(224,108,117,0.12), transparent 60%),
|
|
radial-gradient(900px 520px at 0% 0%, rgba(53,90,102,0.30), transparent 55%),
|
|
var(--bg);
|
|
color: var(--fg);
|
|
font-family: 'Fira Code', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
line-height: 1.6;
|
|
-webkit-font-smoothing: antialiased;
|
|
}
|
|
a { color: var(--accent); text-decoration: none; }
|
|
.wrap { max-width: 1080px; margin: 0 auto; padding: 0 22px; }
|
|
|
|
/* Nav */
|
|
nav {
|
|
position: sticky; top: 0; z-index: 50;
|
|
backdrop-filter: blur(10px);
|
|
background: rgba(17,17,17,0.88);
|
|
border-bottom: 1px solid #9cdef2;
|
|
}
|
|
nav .wrap { display: flex; align-items: center; justify-content: space-between; height: 60px; }
|
|
.brand { display: flex; align-items: center; gap: 8px; font-weight: 700; font-size: 17px; letter-spacing: 0.2px; color: var(--heading); }
|
|
.brand .boat { color: var(--accent); flex-shrink: 0; }
|
|
.nav-links { display: flex; align-items: center; gap: 22px; }
|
|
.nav-links a { color: var(--muted); font-size: 14px; font-weight: 500; }
|
|
.nav-links a:hover { color: var(--fg); }
|
|
.btn {
|
|
display: inline-flex; align-items: center; gap: 8px;
|
|
padding: 9px 16px; border-radius: 10px; font-weight: 600; font-size: 14px;
|
|
border: 1px solid var(--border); color: var(--fg); background: var(--panel);
|
|
transition: transform .12s ease, border-color .12s ease, background .12s ease;
|
|
}
|
|
.btn:hover { transform: translateY(-1px); border-color: var(--accent); }
|
|
.btn.primary {
|
|
background: linear-gradient(135deg, var(--accent), var(--accent2));
|
|
color: #fff; border: none;
|
|
}
|
|
.btn.primary:hover { filter: brightness(1.07); }
|
|
|
|
/* Hero */
|
|
.hero { padding: 86px 0 40px; text-align: center; position: relative; overflow: hidden; }
|
|
#hero-flow { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 0; pointer-events: none; opacity: 0.9; }
|
|
.hero .wrap { position: relative; z-index: 2; }
|
|
.hero h1, .hero .lede, .hero .wordmark { text-shadow: 0 2px 20px rgba(0,0,0,0.45); }
|
|
@media (prefers-reduced-motion: reduce) { #hero-flow { display: none; } }
|
|
.badge {
|
|
display: inline-flex; align-items: center; gap: 7px;
|
|
font-size: 12.5px; color: var(--muted); border: 1px solid var(--border);
|
|
background: var(--panel); padding: 5px 12px; border-radius: 999px; margin-bottom: 22px;
|
|
}
|
|
.badge .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--green); box-shadow: 0 0 8px var(--green); }
|
|
.hero-logo { display: flex; align-items: center; justify-content: center; gap: 14px; color: var(--accent); margin-bottom: 4px; }
|
|
.hero-logo svg { filter: drop-shadow(0 4px 18px rgba(224,108,117,0.35)); }
|
|
.hero-logo .wordmark { font-size: clamp(30px, 6vw, 44px); font-weight: 700; color: var(--heading); letter-spacing: -0.01em; line-height: 1; }
|
|
.hero h1 {
|
|
font-size: clamp(32px, 5.4vw, 52px); line-height: 1.12; margin: 0 0 18px;
|
|
letter-spacing: -0.01em; font-weight: 700; color: var(--heading);
|
|
}
|
|
.hero h1 .grad {
|
|
background: linear-gradient(120deg, var(--accent), var(--accent2));
|
|
-webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent;
|
|
}
|
|
.hero .slogan { font-style: italic; color: var(--accent); font-size: 12px; margin: 0 0 24px; letter-spacing: 0.3px; opacity: 0.9; }
|
|
.hero p.lede { font-size: clamp(16px, 2.4vw, 20px); color: var(--muted); max-width: 680px; margin: 0 auto 30px; }
|
|
.hero-cta { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }
|
|
|
|
/* terminal origin card */
|
|
.term-intro { color: var(--fg); font-size: clamp(13px, 1.8vw, 15px); margin: 34px auto 0; max-width: 560px; }
|
|
.term {
|
|
max-width: 620px; margin: 12px auto 0; text-align: left;
|
|
background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius);
|
|
overflow: hidden; box-shadow: 0 24px 60px rgba(0,0,0,0.4);
|
|
}
|
|
.term-bar { display: flex; align-items: center; justify-content: space-between; padding: 5px 6px 5px 12px; border-bottom: 1px solid var(--border); background: #20242c; }
|
|
.term-bar .ttl { color: var(--muted); font-size: 12px; font-family: 'Fira Code', ui-monospace, monospace; }
|
|
.term-bar .winbtns { display: flex; gap: 1px; }
|
|
.term-bar .winbtns span { cursor: pointer; }
|
|
.term { transition: opacity .18s ease, transform .18s ease; }
|
|
/* Minimized = a rounded "pill", like the app's tab-down dock chip. */
|
|
.term.term-min { max-width: max-content; border-radius: 999px; box-shadow: 0 6px 22px rgba(0,0,0,0.4); }
|
|
.term.term-min .term-bar { border-bottom: none; border-radius: 999px; padding: 7px 10px 7px 16px; gap: 12px; background: var(--panel); }
|
|
.term.term-min pre { display: none; }
|
|
.term.term-closed { opacity: 0; transform: scale(0.96); pointer-events: none; height: 0; margin: 0 auto; border: 0; overflow: hidden; }
|
|
.term-reopen {
|
|
display: none; margin: 14px auto 0; font-family: 'Fira Code', monospace; font-size: 12px;
|
|
color: var(--muted); background: none; border: 1px dashed var(--border); border-radius: 6px;
|
|
padding: 5px 12px; cursor: pointer;
|
|
}
|
|
.term-reopen:hover { color: var(--accent); border-color: var(--accent); }
|
|
.term-reopen.show { display: inline-block; }
|
|
.term-bar .winbtns span {
|
|
width: 28px; height: 20px; display: inline-flex; align-items: center; justify-content: center;
|
|
border-radius: 4px; color: var(--muted); font-size: 12px; line-height: 1;
|
|
}
|
|
.term-bar .winbtns span:hover { background: rgba(156,222,242,0.12); color: var(--fg); }
|
|
.term-bar .winbtns span.x:hover { background: #c0392b; color: #fff; }
|
|
.term pre {
|
|
margin: 0; padding: 18px 16px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
font-size: 13.5px; color: var(--fg); line-height: 1.7; white-space: pre-wrap;
|
|
}
|
|
.term .cs { color: var(--green); } .term .cm { color: #828997; }
|
|
.term-cursor { display: inline-block; color: var(--fg); font-weight: 400; animation: term-blink 1.05s steps(1) infinite; }
|
|
@keyframes term-blink { 50% { opacity: 0; } }
|
|
|
|
/* Sections */
|
|
section { padding: 60px 0; }
|
|
.eyebrow { color: var(--accent); font-weight: 700; font-size: 12px; letter-spacing: 0.12em; text-transform: uppercase; display: inline-flex; align-items: center; gap: 6px; }
|
|
.eyebrow svg { width: 14px; height: 14px; flex-shrink: 0; }
|
|
h2.h { font-size: clamp(19px, 2.7vw, 26px); margin: 8px 0 12px; letter-spacing: -0.01em; color: var(--heading); font-weight: 700; }
|
|
.sub { color: var(--muted); max-width: 620px; }
|
|
.center { text-align: center; }
|
|
.center .sub { margin: 0 auto; }
|
|
|
|
/* Testimonial gag — single featured testimonial, click/swipe to cycle (all sizes) */
|
|
.tcarousel-wrap { position: relative; max-width: 820px; margin: 36px auto 0; }
|
|
.tarrow {
|
|
position: absolute; top: 50%; transform: translateY(-50%); z-index: 4;
|
|
width: 38px; height: 38px; border-radius: 50%;
|
|
background: rgba(17,17,17,0.85); border: 1px solid var(--border); color: var(--fg);
|
|
font-size: 20px; line-height: 1; cursor: pointer;
|
|
display: flex; align-items: center; justify-content: center;
|
|
transition: border-color .12s ease, color .12s ease;
|
|
}
|
|
.tarrow:hover { border-color: var(--accent); color: var(--accent); }
|
|
.tarrow.prev { left: 0; }
|
|
.tarrow.next { right: 0; }
|
|
.tgrid {
|
|
display: block; position: relative; overflow: hidden; cursor: pointer;
|
|
margin: 0 auto; max-width: 740px;
|
|
}
|
|
.tgrid .tcard {
|
|
display: none;
|
|
flex-direction: row-reverse; align-items: center; gap: 24px; text-align: left;
|
|
background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
|
|
padding: 28px;
|
|
}
|
|
.tgrid .tcard.active { display: flex; animation: tslide .25s ease both; }
|
|
.tgrid .tcard.active.shake { animation: tshake .5s ease-in-out 2 both; }
|
|
.tcard .av {
|
|
width: 84px; height: 84px; border-radius: 50%; overflow: hidden;
|
|
border: 1px solid var(--border); background: var(--panel2); flex: 0 0 auto;
|
|
}
|
|
.tcard .av img, .tcard .av svg { width: 100%; height: 100%; object-fit: cover; display: block; }
|
|
.tcard .tmeta { flex: 1 1 auto; }
|
|
.tcard .q { font-size: 18px; color: var(--fg); margin: 0 0 12px; }
|
|
.tcard .stars { font-size: 15px; letter-spacing: 3px; margin: 0 0 8px; color: var(--gold); }
|
|
.tcard .stars.zero { color: var(--muted); opacity: 0.5; }
|
|
.tcard .nm { font-weight: 700; font-size: 14.5px; }
|
|
.tcard .rl { color: var(--muted); font-size: 12.5px; }
|
|
.tcard.cyclops { border-color: rgba(255,90,90,0.45); background: linear-gradient(180deg, rgba(255,80,80,0.06), var(--panel)); }
|
|
.tcard.cyclops .q { color: #ff8a8a; font-weight: 700; letter-spacing: 0.4px; word-break: break-word; }
|
|
.tnav { display: block; text-align: center; margin-top: 18px; }
|
|
.tdot { display: inline-block; width: 9px; height: 9px; border-radius: 50%; background: #39414d; margin: 0 4px; cursor: pointer; }
|
|
.tdot.on { background: var(--accent); }
|
|
.thint { font-size: 12px; color: var(--muted); margin-top: 8px; }
|
|
@keyframes tshake {
|
|
0%,100% { transform: translateX(0) rotate(0); }
|
|
10% { transform: translateX(-9px) rotate(-1.5deg); }
|
|
20% { transform: translateX(9px) rotate(1.5deg); }
|
|
35% { transform: translateX(-7px) rotate(-1deg); }
|
|
50% { transform: translateX(7px) rotate(1deg); }
|
|
65% { transform: translateX(-5px); } 80% { transform: translateX(4px); } 92% { transform: translateX(-2px); }
|
|
}
|
|
@keyframes tslide { from { opacity: 0; transform: translateX(24px); } to { opacity: 1; transform: none; } }
|
|
|
|
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-top: 36px; }
|
|
.feature {
|
|
background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
|
|
padding: 22px; transition: transform .14s ease, border-color .14s ease;
|
|
}
|
|
.feature:hover { transform: translateY(-3px); border-color: var(--accent); }
|
|
.feature .ico {
|
|
width: 40px; height: 40px; border-radius: 10px; display: inline-flex; align-items: center; justify-content: center;
|
|
background: linear-gradient(135deg, rgba(224,108,117,0.18), rgba(53,90,102,0.28));
|
|
border: 1px solid var(--border); color: var(--accent); margin-bottom: 14px;
|
|
}
|
|
.feature h3 { margin: 0 0 6px; font-size: 16.5px; }
|
|
.feature p { margin: 0; color: var(--muted); font-size: 14px; }
|
|
|
|
/* Screenshot strip */
|
|
.shotrow { display: grid; grid-template-columns: 1.4fr 1fr 1fr; gap: 16px; margin-top: 8px; }
|
|
.shot {
|
|
border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden;
|
|
background: linear-gradient(180deg, var(--panel), var(--panel2));
|
|
aspect-ratio: 16/10; display: flex; align-items: center; justify-content: center;
|
|
color: var(--muted); font-size: 13px; position: relative;
|
|
}
|
|
.shot .ph { display: flex; flex-direction: column; align-items: center; gap: 8px; opacity: 0.7; }
|
|
.shot .frame-dots { position: absolute; top: 10px; left: 12px; display: flex; gap: 5px; }
|
|
.shot .frame-dots i { width: 8px; height: 8px; border-radius: 50%; background: #39414d; display: inline-block; }
|
|
|
|
/* Previews — expanding hover carousel that plays a video on hover/tap */
|
|
.previews { display: flex; align-items: center; gap: 12px; height: 480px; max-width: 1000px; margin: 36px auto 0; }
|
|
.preview-panel {
|
|
position: relative; flex: 1 1 0; min-width: 0; height: 360px; overflow: hidden;
|
|
border: 1px solid var(--border); border-radius: var(--radius); cursor: pointer;
|
|
background: linear-gradient(180deg, var(--panel), var(--panel2));
|
|
transition: flex-grow .5s cubic-bezier(.2,.7,.2,1), height .5s cubic-bezier(.2,.7,.2,1), border-color .25s ease;
|
|
}
|
|
.previews:hover .preview-panel { flex-grow: 0.55; height: 300px; }
|
|
.preview-panel:hover, .preview-panel:focus-visible, .preview-panel.is-active { flex-grow: 3.4 !important; height: 480px !important; border-color: var(--accent); }
|
|
.preview-panel .ph {
|
|
position: absolute; inset: 0; display: flex; flex-direction: column;
|
|
align-items: center; justify-content: center; gap: 10px;
|
|
color: var(--muted); font-size: 12.5px; opacity: 0.7; text-align: center; padding: 8px;
|
|
}
|
|
.preview-panel video {
|
|
position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
|
|
z-index: 1; opacity: 0; transition: opacity .3s ease; background: transparent;
|
|
}
|
|
.preview-panel.has-video video { opacity: 1; }
|
|
/* These clips have their action on the left, so show the left edge instead of
|
|
the centered crop. */
|
|
.preview-panel:has(source[src="document.webm"]) video,
|
|
.preview-panel:has(source[src="notes.webm"]) video { object-position: right center; }
|
|
.preview-panel .label {
|
|
position: absolute; z-index: 2; left: 0; right: 0; bottom: 0; padding: 14px 16px;
|
|
background: linear-gradient(0deg, rgba(0,0,0,0.82), transparent);
|
|
color: var(--heading);
|
|
display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
|
|
}
|
|
.preview-panel .label .t { display: flex; align-items: center; gap: 8px; white-space: nowrap; font-weight: 700; font-size: 14px; }
|
|
.preview-panel .label .ico { color: var(--accent); flex-shrink: 0; }
|
|
.preview-panel .label .desc {
|
|
font-weight: 400; font-size: 12.5px; line-height: 1.35; color: rgba(255,255,255,0.82);
|
|
white-space: normal; max-height: 0; opacity: 0; overflow: hidden;
|
|
transition: max-height .4s ease, opacity .4s ease;
|
|
}
|
|
.preview-panel:hover .label .desc, .preview-panel:focus-visible .label .desc, .preview-panel.is-active .label .desc { max-height: 64px; opacity: 1; }
|
|
@media (max-width: 760px) {
|
|
.previews { flex-direction: column; height: auto; touch-action: pan-y; }
|
|
.preview-panel { height: 190px; flex: none; width: 100%; }
|
|
.preview-panel.is-active { height: 280px !important; }
|
|
.previews:hover .preview-panel, .preview-panel:hover { flex: none !important; }
|
|
.preview-panel .label .desc { max-height: 64px; opacity: 1; }
|
|
}
|
|
|
|
/* Fullscreen video background for a section — treated as an ambient, cinematic
|
|
backdrop (soft blur + slow drift) so it sets a mood without fighting the copy. */
|
|
.has-bg-video { position: relative; overflow: hidden; }
|
|
.has-bg-video .sec-bg {
|
|
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
object-fit: cover; z-index: 0; pointer-events: none;
|
|
/* blur softens the busy frame; the extra scale hides the blurred edges */
|
|
filter: blur(4px) saturate(1.08) brightness(0.92);
|
|
transform: scale(1.12);
|
|
transform-origin: 55% 45%;
|
|
animation: bg-drift 36s ease-in-out infinite alternate;
|
|
will-change: transform;
|
|
}
|
|
@keyframes bg-drift {
|
|
from { transform: scale(1.12) translate(0, 0); }
|
|
to { transform: scale(1.2) translate(-2.5%, -1.5%); }
|
|
}
|
|
.has-bg-video .sec-bg-tint {
|
|
position: absolute; inset: 0; z-index: 1; pointer-events: none;
|
|
background:
|
|
radial-gradient(900px 520px at 78% 18%, rgba(224,108,117,0.16), transparent 60%),
|
|
radial-gradient(760px 520px at 8% 88%, rgba(53,90,102,0.30), transparent 58%),
|
|
linear-gradient(180deg, rgba(17,17,17,0.86), rgba(17,17,17,0.62) 42%, rgba(17,17,17,0.92)),
|
|
radial-gradient(1200px 680px at 50% 46%, rgba(17,17,17,0.18), rgba(17,17,17,0.74));
|
|
}
|
|
.has-bg-video .wrap { position: relative; z-index: 2; }
|
|
/* Lift the copy off the moving backdrop. */
|
|
.has-bg-video .eyebrow,
|
|
.has-bg-video .h { text-shadow: 0 2px 22px rgba(0,0,0,0.7); }
|
|
.has-bg-video .sub { color: #b9e6f4; text-shadow: 0 1px 14px rgba(0,0,0,0.75); }
|
|
.hero.has-bg-video h1, .hero.has-bg-video .wordmark,
|
|
.hero.has-bg-video .lede, .hero.has-bg-video .slogan { text-shadow: 0 2px 22px rgba(0,0,0,0.72); }
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.has-bg-video .sec-bg { animation: none; transform: scale(1.12); }
|
|
}
|
|
|
|
/* Get started */
|
|
.start {
|
|
background: linear-gradient(180deg, var(--panel), var(--bg2));
|
|
border: 1px solid var(--border); border-radius: 18px; padding: 40px; text-align: center;
|
|
}
|
|
.codeblock {
|
|
display: inline-flex; align-items: center; gap: 14px; margin: 18px auto 8px;
|
|
background: var(--bg2); border: 1px solid var(--border); border-radius: 10px;
|
|
padding: 12px 16px; font-family: ui-monospace, monospace; font-size: 14px; color: var(--fg);
|
|
text-align: left;
|
|
}
|
|
.codeblock .prompt { color: var(--accent); }
|
|
.codeblock .copy-btn {
|
|
background: none; border: 1px solid var(--border); border-radius: 6px;
|
|
color: var(--muted); cursor: pointer; font-size: 12px; padding: 4px 10px;
|
|
font-family: inherit; transition: border-color .12s ease, color .12s ease;
|
|
}
|
|
.codeblock .copy-btn:hover { border-color: var(--accent); color: var(--fg); }
|
|
.codeblock .copy-btn.copied { border-color: var(--green); color: var(--green); }
|
|
.pill-row { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin-top: 44px; }
|
|
.pill { font-size: 12.5px; color: var(--muted); border: 1px solid var(--border); border-radius: 999px; padding: 5px 12px; background: var(--panel); }
|
|
|
|
footer { border-top: 1px solid var(--border); padding: 30px 0; color: var(--muted); font-size: 13px; scroll-snap-align: end; }
|
|
footer .wrap { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; }
|
|
|
|
@media (max-width: 820px) {
|
|
.grid { grid-template-columns: repeat(2, 1fr); }
|
|
.shotrow { grid-template-columns: 1fr; }
|
|
.nav-links a:not(.btn) { display: none; }
|
|
.codeblock { display: flex; flex-wrap: wrap; gap: 8px; align-items: flex-start; overflow-wrap: anywhere; }
|
|
.codeblock > span { flex: 1 1 auto; min-width: 0; }
|
|
.codeblock .copy-btn { margin-left: auto; }
|
|
}
|
|
@media (max-width: 520px) {
|
|
.grid { grid-template-columns: 1fr; }
|
|
.tgrid .tcard { padding: 20px; gap: 16px; }
|
|
.tcard .av { width: 64px; height: 64px; }
|
|
.tcard .q { font-size: 15px; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<nav>
|
|
<div class="wrap">
|
|
<div class="brand">
|
|
<svg class="boat" viewBox="0 0 32 32" width="24" height="24" aria-hidden="true"><path d="M16 4L16 22L6 22Z" fill="currentColor"/><path d="M16 8L16 22L24 22Z" fill="currentColor" opacity="0.6"/><path d="M4 24Q10 20 16 24Q22 28 28 24" stroke="currentColor" stroke-width="2.5" fill="none" stroke-linecap="round"/></svg>
|
|
Odysseus
|
|
</div>
|
|
<div class="nav-links">
|
|
<a href="#features">Features</a>
|
|
<a href="#testimonials">Testimonials</a>
|
|
<a href="#how">How it started</a>
|
|
<a href="#start">Get started</a>
|
|
<a class="btn" href="https://github.com/pewdiepie-archdaemon/odysseus" target="_blank">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M12 .5C5.7.5.5 5.7.5 12c0 5.1 3.3 9.4 7.9 10.9.6.1.8-.2.8-.6v-2c-3.2.7-3.9-1.5-3.9-1.5-.5-1.3-1.3-1.7-1.3-1.7-1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1 1.8 2.7 1.3 3.4 1 .1-.8.4-1.3.7-1.6-2.6-.3-5.3-1.3-5.3-5.7 0-1.3.5-2.3 1.2-3.1-.1-.3-.5-1.5.1-3.1 0 0 1-.3 3.3 1.2a11.5 11.5 0 0 1 6 0C17.3 4.7 18.3 5 18.3 5c.6 1.6.2 2.8.1 3.1.8.8 1.2 1.8 1.2 3.1 0 4.4-2.7 5.4-5.3 5.7.4.4.8 1.1.8 2.2v3.3c0 .4.2.7.8.6 4.6-1.5 7.9-5.8 7.9-10.9C23.5 5.7 18.3.5 12 .5z"/></svg>
|
|
GitHub
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- HERO -->
|
|
<header class="hero">
|
|
<canvas id="hero-flow" aria-hidden="true"></canvas>
|
|
<div class="wrap">
|
|
<div class="hero-logo">
|
|
<svg viewBox="0 0 32 32" width="48" height="48" aria-hidden="true"><path d="M16 4L16 22L6 22Z" fill="currentColor"/><path d="M16 8L16 22L24 22Z" fill="currentColor" opacity="0.6"/><path d="M4 24Q10 20 16 24Q22 28 28 24" stroke="currentColor" stroke-width="2.5" fill="none" stroke-linecap="round"/></svg>
|
|
<span class="wordmark">Odysseus</span>
|
|
</div>
|
|
<p class="slogan">Yours for the voyage.</p>
|
|
<h1>Your own <span class="grad">AI workspace</span>,<br>running on your hardware.</h1>
|
|
<p class="lede">
|
|
Odysseus is a self-hosted interface for talking to language models — chat,
|
|
autonomous agents, tools, model serving, email, research, and more. Local-first,
|
|
privacy-first, and no telemetry. Just you and your models.
|
|
</p>
|
|
<p style="font-size:11.5px; color:var(--muted); opacity:0.7; max-width:560px; margin:-18px auto 30px;">
|
|
(if you want to add an API that's cool too — I'm not here to tell you how to live your life…)
|
|
</p>
|
|
<div class="hero-cta">
|
|
<a class="btn primary" href="#start">Get started</a>
|
|
<a class="btn" href="https://github.com/pewdiepie-archdaemon/odysseus" target="_blank">View on GitHub</a>
|
|
</div>
|
|
|
|
</div>
|
|
</header>
|
|
|
|
<!-- FEATURES -->
|
|
<section id="features">
|
|
<div class="wrap">
|
|
<div class="center">
|
|
<div class="eyebrow"><svg 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"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/></svg>Everything, self-hosted</div>
|
|
<h2 class="h">One app, a lot of capabilities</h2>
|
|
<p class="sub">Started as an AI chat. Became a workspace. Each piece runs locally against
|
|
whatever endpoints you point it at.</p>
|
|
</div>
|
|
<div class="grid">
|
|
<div class="feature">
|
|
<span class="ico"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></span>
|
|
<h3>Chat & Agents</h3>
|
|
<p>Multi-turn chat plus autonomous agents that plan, call tools, and work through tasks.</p>
|
|
</div>
|
|
<div class="feature">
|
|
<span class="ico"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.8-3.8a6 6 0 0 1-7.9 7.9l-6.9 6.9a2.1 2.1 0 0 1-3-3l6.9-6.9a6 6 0 0 1 7.9-7.9z"/></svg></span>
|
|
<h3>Tools & MCP</h3>
|
|
<p>Built-in tools (bash, files, web, memory) plus any MCP server you connect. Toggle per tool.</p>
|
|
</div>
|
|
<div class="feature">
|
|
<span class="ico"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2 2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg></span>
|
|
<h3>Cookbook</h3>
|
|
<p>Hardware-aware model recommendations and one-click serving across 270+ catalogued models.</p>
|
|
</div>
|
|
<div class="feature">
|
|
<span class="ico"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-10 5L2 7"/></svg></span>
|
|
<h3>Email Assistant</h3>
|
|
<p>AI summaries, style-matched draft replies, auto-tagging and spam triage over IMAP/SMTP.</p>
|
|
</div>
|
|
<div class="feature">
|
|
<span class="ico"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg></span>
|
|
<h3>Deep Research</h3>
|
|
<p>Multi-step research runs that gather, read, and synthesize sources into a written report.</p>
|
|
</div>
|
|
<div class="feature">
|
|
<span class="ico"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="8" height="18" rx="1"/><rect x="14" y="3" width="8" height="18" rx="1"/></svg></span>
|
|
<h3>Compare</h3>
|
|
<p>Send one prompt to several models at once and compare their answers side-by-side.</p>
|
|
</div>
|
|
<div class="feature">
|
|
<span class="ico"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14c0 1.7 4 3 9 3s9-1.3 9-3V5"/><path d="M3 12c0 1.7 4 3 9 3s9-1.3 9-3"/></svg></span>
|
|
<h3>Memory</h3>
|
|
<p>Persistent memory the assistant builds up and recalls across all your conversations.</p>
|
|
</div>
|
|
<div class="feature">
|
|
<span class="ico"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9z"/></svg></span>
|
|
<h3>Skills <span style="font-size:10.5px;font-weight:700;color:var(--accent);border:1px solid var(--border);border-radius:999px;padding:1px 7px;margin-left:4px;vertical-align:middle;">self-evolving</span></h3>
|
|
<p>The assistant writes, refines, and reuses its own skills — getting more capable over time.</p>
|
|
</div>
|
|
<div class="feature">
|
|
<span class="ico"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></span>
|
|
<h3>Private by default</h3>
|
|
<p>Runs on your machine against your own endpoints. No telemetry, with optional external integrations when you choose them.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- TESTIMONIALS (gag) -->
|
|
<section id="testimonials" style="padding-top:30px;">
|
|
<div class="wrap">
|
|
<div class="center">
|
|
<div class="eyebrow"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.8 5.6a5.2 5.2 0 0 0-7.4 0L12 7l-1.4-1.4a5.2 5.2 0 1 0-7.4 7.4L12 21.4l8.8-8.4a5.2 5.2 0 0 0 0-7.4z"/></svg>Loved by enterprises</div>
|
|
<h2 class="h">What our customers are saying</h2>
|
|
</div>
|
|
|
|
<div class="tcarousel-wrap">
|
|
<button class="tarrow prev" type="button" aria-label="Previous testimonial">‹</button>
|
|
<div class="tgrid" id="tcarousel">
|
|
|
|
<!-- Coder guy -->
|
|
<figure class="tcard">
|
|
<span class="av"><img src="https://cdn.prod.website-files.com/66708f90d7e407423093fa76/66708f91d7e407423093fd21_john-carter-testimonial-image-dentistry-x-webflow-template.png" alt="Generic Coder Guy" loading="lazy"></span>
|
|
<div class="tmeta">
|
|
<p class="q">"Odysseus helped us ship more ships while shipping ships. Truly best-in-class shipping."</p>
|
|
<div class="stars">★★★★★</div>
|
|
<div class="nm">Generic Coder Guy</div>
|
|
<div class="rl">Sr. Engineer, ShipShip Inc.</div>
|
|
</div>
|
|
</figure>
|
|
|
|
<!-- Woman -->
|
|
<figure class="tcard">
|
|
<span class="av"><img src="https://images.pexels.com/photos/5876695/pexels-photo-5876695.jpeg?auto=compress&cs=tinysrgb&w=160&h=160&fit=crop" alt="A real woman" loading="lazy"></span>
|
|
<div class="tmeta">
|
|
<p class="q">"I'm a real person. This is a real testimonial. By a real woman."</p>
|
|
<div class="stars">★★★★★</div>
|
|
<div class="nm">Generic Corporate Woman</div>
|
|
<div class="rl">VP of Verticals, Things LLC</div>
|
|
</div>
|
|
</figure>
|
|
|
|
<!-- Cyclops -->
|
|
<figure class="tcard cyclops" data-shake="1">
|
|
<span class="av" style="border-color:rgba(255,90,90,0.6);">
|
|
<svg viewBox="0 0 72 72" width="54" height="54" fill="none" stroke="#cbd5e1" stroke-width="2">
|
|
<rect x="0" y="0" width="72" height="72" fill="#16241a"/>
|
|
<circle cx="36" cy="32" r="18" fill="#7fae7f" stroke="#5a7a5a"/>
|
|
<line x1="29" y1="22" x2="43" y2="34" stroke="#ff5a5a" stroke-width="3"/>
|
|
<line x1="43" y1="22" x2="29" y2="34" stroke="#ff5a5a" stroke-width="3"/>
|
|
<ellipse cx="36" cy="45" rx="7" ry="9" fill="#3a0a0a" stroke="#200"/>
|
|
<path d="M31 51 l-1 4" stroke="#fff" stroke-width="2"/><path d="M41 51 l1 4" stroke="#fff" stroke-width="2"/>
|
|
</svg>
|
|
</span>
|
|
<div class="tmeta">
|
|
<p class="q">"AHHHHHHHHHHHHHHHHHHHHHHHHHHHHH"</p>
|
|
<div class="stars zero">☆☆☆☆☆</div>
|
|
<div class="nm">Polyphemus</div>
|
|
<div class="rl">Cyclops, Cave Solutions (on leave)</div>
|
|
</div>
|
|
</figure>
|
|
|
|
<!-- Corporate -->
|
|
<figure class="tcard">
|
|
<span class="av">
|
|
<svg viewBox="0 0 80 80" aria-hidden="true">
|
|
<rect width="80" height="80" rx="18" fill="#111827"/>
|
|
<circle cx="40" cy="29" r="14" fill="#d1d5db"/>
|
|
<path d="M18 70c4-18 15-27 22-27s18 9 22 27" fill="#374151"/>
|
|
<path d="M28 58h24l-5 12H33z" fill="#e06c75"/>
|
|
<path d="M32 14h16l6 11H26z" fill="#f8fafc"/>
|
|
</svg>
|
|
</span>
|
|
<div class="tmeta">
|
|
<p class="q">"Anyway, as I was saying — best-in-class."</p>
|
|
<div class="stars">★★★★★</div>
|
|
<div class="nm">Chad Corporate</div>
|
|
<div class="rl">Chief Executive Officer</div>
|
|
</div>
|
|
</figure>
|
|
|
|
</div>
|
|
<button class="tarrow next" type="button" aria-label="Next testimonial">›</button>
|
|
</div>
|
|
<div class="tnav" id="tnav"></div>
|
|
</div>
|
|
</section>
|
|
|
|
|
|
<!-- The one-shot prompt it started from (gag) -->
|
|
<section style="padding-top:0;">
|
|
<div class="wrap" style="text-align:center;">
|
|
<p class="term-intro">Odysseus was created by a carefully crafted one-shot AI prompt:</p>
|
|
<div class="term">
|
|
<div class="term-bar">
|
|
<span class="ttl">user@odysseus: ~</span>
|
|
<span class="winbtns"><span data-term="min" title="Minimize">–</span><span class="x" data-term="close" title="Close">✕</span></span>
|
|
</div>
|
|
<pre id="term-pre"><span class="cs">></span> idk what to make come up with something oh make an AI chat but make it good and make it look nice</pre>
|
|
</div>
|
|
<button class="term-reopen" type="button">✕ reopen terminal</button>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- PREVIEWS — hover/tap to expand + play -->
|
|
<section id="previews">
|
|
<div class="wrap">
|
|
<div class="center">
|
|
<div class="eyebrow"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/></svg>See it in action</div>
|
|
<h2 class="h">Hover or tap to take a closer look</h2>
|
|
<p class="sub center">Each panel expands and plays its preview when you hover or tap it. Swipe on mobile to move through them.</p>
|
|
</div>
|
|
<div class="previews">
|
|
<div class="preview-panel" tabindex="0">
|
|
<div class="ph"><svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg><span>[ Chat & Agents ]</span></div>
|
|
<video muted loop playsinline preload="none"><source src="chat.webm" type="video/webm"><source src="chat.mp4" type="video/mp4"></video>
|
|
<div class="label"><span class="t"><svg class="ico" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>Chat & Agents</span><span class="desc">Talk to any local model, or give it tools and let the agent run.</span></div>
|
|
</div>
|
|
<div class="preview-panel" tabindex="0">
|
|
<div class="ph"><svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2 2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg><span>[ Cookbook ]</span></div>
|
|
<div class="label"><span class="t"><svg class="ico" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2 2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg>Cookbook</span><span class="desc">Download, serve, and manage local models across your machines.</span></div>
|
|
</div>
|
|
<div class="preview-panel" tabindex="0">
|
|
<div class="ph"><svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.35-4.35"/></svg><span>[ Deep Research ]</span></div>
|
|
<video muted loop playsinline preload="none"><source src="research.webm" type="video/webm"><source src="research.mp4" type="video/mp4"></video>
|
|
<div class="label"><span class="t"><svg class="ico" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.35-4.35"/></svg>Deep Research</span><span class="desc">Ask once: it searches, reads sources, and writes back a cited report.</span></div>
|
|
</div>
|
|
<div class="preview-panel" tabindex="0">
|
|
<div class="ph"><svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="7" height="16" rx="1"/><rect x="14" y="4" width="7" height="16" rx="1"/></svg><span>[ Compare ]</span></div>
|
|
<video muted loop playsinline preload="none"><source src="compare.webm" type="video/webm"><source src="compare.mp4" type="video/mp4"></video>
|
|
<div class="label"><span class="t"><svg class="ico" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="7" height="16" rx="1"/><rect x="14" y="4" width="7" height="16" rx="1"/></svg>Compare</span><span class="desc">Send one prompt to many models at once and watch them answer side by side.</span></div>
|
|
</div>
|
|
<div class="preview-panel" tabindex="0">
|
|
<div class="ph"><svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M16 13H8M16 17H8M10 9H8"/></svg><span>[ Documents ]</span></div>
|
|
<video muted loop playsinline preload="none"><source src="document.webm" type="video/webm"><source src="document.mp4" type="video/mp4"></video>
|
|
<div class="label"><span class="t"><svg class="ico" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M16 13H8M16 17H8M10 9H8"/></svg>Documents</span><span class="desc">A document editor that puts you first — work on what you want, with AI help when you want it.</span></div>
|
|
</div>
|
|
<div class="preview-panel" tabindex="0">
|
|
<div class="ph"><svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="m3 7 2 2 4-4"/><path d="m3 17 2 2 4-4"/><path d="M13 6h8M13 18h8"/></svg><span>[ Notes & Tasks ]</span></div>
|
|
<video muted loop playsinline preload="none"><source src="notes.webm" type="video/webm"><source src="notes.mp4" type="video/mp4"></video>
|
|
<div class="label"><span class="t"><svg class="ico" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m3 7 2 2 4-4"/><path d="m3 17 2 2 4-4"/><path d="M13 6h8M13 18h8"/></svg>Notes & Tasks</span><span class="desc">Capture notes and to-dos, or let scheduled agents work and brief you after.</span></div>
|
|
</div>
|
|
<div class="preview-panel" tabindex="0">
|
|
<div class="ph"><svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.6-3.6a2 2 0 0 0-2.8 0L6 21"/></svg><span>[ Image Gallery ]</span></div>
|
|
<video muted loop playsinline preload="none"><source src="gallery.webm" type="video/webm"><source src="gallery.mp4" type="video/mp4"></video>
|
|
<div class="label"><span class="t"><svg class="ico" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.6-3.6a2 2 0 0 0-2.8 0L6 21"/></svg>Image Gallery</span><span class="desc">Generate, edit, remove backgrounds, and inpaint in your own gallery.</span></div>
|
|
</div>
|
|
<div class="preview-panel" tabindex="0">
|
|
<div class="ph"><svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2.7 6.3 8.4a8 8 0 1 0 11.4 0z"/></svg><span>[ Themes ]</span></div>
|
|
<video muted loop playsinline preload="none"><source src="theme.webm" type="video/webm"><source src="theme.mp4" type="video/mp4"></video>
|
|
<div class="label"><span class="t"><svg class="ico" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2.7 6.3 8.4a8 8 0 1 0 11.4 0z"/></svg>Themes</span><span class="desc">Restyle and make it yours — edit your own, or ask the agent to make one.</span></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- HOW IT STARTED -->
|
|
<section id="how" class="has-bg-video">
|
|
<video class="sec-bg" autoplay muted loop playsinline preload="auto"><source src="bg.webm" type="video/webm"><source src="bg.mp4" type="video/mp4"></video>
|
|
<div class="sec-bg-tint"></div>
|
|
<div class="wrap">
|
|
<div class="eyebrow"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="m15.6 8.4-2.1 5.1-5.1 2.1 2.1-5.1z"/></svg>How it actually started</div>
|
|
<h2 class="h">Uncompromised local LLM experience.</h2>
|
|
<p class="sub" style="max-width:760px;">
|
|
I started working on the Odysseus project because running local AI felt fun and powerful.
|
|
But the options at the time to engage with LLMs felt like taking steps back. The idea that you
|
|
could just self-host AI and not pay for a subscription wasn't there. All the tools and functions
|
|
that make it all magic were missing.
|
|
</p>
|
|
<p class="sub" style="max-width:760px; margin-top:14px;">
|
|
So I started building Odysseus bit by bit — and the more I gave it to work with, the
|
|
better it served me. Turns out the more your model knows about you, the more useful it gets.
|
|
Which is the other reason to self-host: you get all that context without handing your private
|
|
data to someone else's cloud. </p>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- GET STARTED -->
|
|
<section id="start">
|
|
<div class="wrap">
|
|
<div class="start">
|
|
<div class="eyebrow"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 17l6-5-6-5"/><path d="M12 19h8"/></svg>Get started</div>
|
|
<h2 class="h" style="margin-bottom:6px;">Odysseus is yours.</h2>
|
|
<p class="sub center" style="margin:0 auto;">It's open source and free. No sales team, no demo request, no Trojan horse.</p>
|
|
<div class="codeblock"><span><span class="prompt">$</span> git clone https://github.com/pewdiepie-archdaemon/odysseus.git && cd odysseus</span><button class="copy-btn" data-copy="git clone https://github.com/pewdiepie-archdaemon/odysseus.git && cd odysseus">Copy</button></div>
|
|
<div>
|
|
<a class="btn primary" href="https://github.com/pewdiepie-archdaemon/odysseus" target="_blank" style="margin-top:14px;">View on GitHub</a>
|
|
</div>
|
|
<div class="pill-row">
|
|
<span class="pill">Self-hosted</span>
|
|
<span class="pill">Bring your own models</span>
|
|
<span class="pill">Local-first</span>
|
|
<span class="pill">MCP-ready</span>
|
|
<span class="pill">No telemetry</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<footer>
|
|
<div class="wrap">
|
|
<div>© 2026 Odysseus · Built from one prompt that refused to stop.</div>
|
|
<div>No cyclopes were harmed in production.<sup>*</sup></div>
|
|
</div>
|
|
</footer>
|
|
|
|
<script>
|
|
// Hero background: Perlin flow field — colored particle streams (ported from
|
|
// the app's own background effect, scoped to the hero and brand-colored).
|
|
(function () {
|
|
var canvas = document.getElementById('hero-flow');
|
|
if (!canvas) return;
|
|
if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
|
|
var hero = canvas.parentElement, ctx = canvas.getContext('2d');
|
|
var dpr = Math.min(window.devicePixelRatio || 1, 2);
|
|
var W, H, t = 0, particles = [];
|
|
var COLORS = ['#9cdef2', '#e06c75', '#5fb6cc']; // cyan, coral, teal
|
|
var FADE = 'rgba(40,44,52,0.06)'; // trail fade toward --bg
|
|
function n2(x, y) { var n = Math.sin(x * 12.9898 + y * 78.233) * 43758.5453; return n - Math.floor(n); }
|
|
function noise(x, y) {
|
|
var ix = Math.floor(x), iy = Math.floor(y), fx = x - ix, fy = y - iy;
|
|
var a = n2(ix, iy), b = n2(ix + 1, iy), c = n2(ix, iy + 1), d = n2(ix + 1, iy + 1);
|
|
var ux = fx * fx * (3 - 2 * fx), uy = fy * fy * (3 - 2 * fy);
|
|
return a + (b - a) * ux + (c - a) * uy + (a - b - c + d) * ux * uy;
|
|
}
|
|
function resize() {
|
|
W = hero.clientWidth; H = hero.clientHeight;
|
|
canvas.width = W * dpr; canvas.height = H * dpr;
|
|
canvas.style.width = W + 'px'; canvas.style.height = H + 'px';
|
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
if (!particles.length) {
|
|
for (var i = 0; i < 260; i++) particles.push({ x: Math.random() * W, y: Math.random() * H, life: Math.random(), c: COLORS[i % COLORS.length] });
|
|
}
|
|
}
|
|
resize();
|
|
window.addEventListener('resize', resize);
|
|
function draw() {
|
|
requestAnimationFrame(draw);
|
|
ctx.fillStyle = FADE; ctx.fillRect(0, 0, W, H);
|
|
for (var i = 0; i < particles.length; i++) {
|
|
var p = particles[i];
|
|
var ang = noise(p.x * 0.004 + t * 0.0008, p.y * 0.004 + 100) * Math.PI * 6;
|
|
var sp = 1 + noise(p.x * 0.003, p.y * 0.003 + 50) * 1.5;
|
|
p.x += Math.cos(ang) * sp; p.y += Math.sin(ang) * sp; p.life -= 0.001;
|
|
if (p.life <= 0 || p.x < 0 || p.x > W || p.y < 0 || p.y > H) { p.x = Math.random() * W; p.y = Math.random() * H; p.life = 1; }
|
|
ctx.beginPath(); ctx.arc(p.x, p.y, 1.1, 0, Math.PI * 2);
|
|
ctx.fillStyle = p.c; ctx.globalAlpha = p.life * 0.18; ctx.fill();
|
|
}
|
|
ctx.globalAlpha = 1; t++;
|
|
}
|
|
draw();
|
|
})();
|
|
|
|
// Typewriter for the origin terminal: type line 1, pause 2s, line 2, pause
|
|
// 2s, line 3, hold 4s, then reset and loop. Blinking "|" cursor throughout.
|
|
(function () {
|
|
var pre = document.getElementById('term-pre');
|
|
if (!pre) return;
|
|
var lines = [
|
|
{ p: '<span class="cs">></span> ', t: 'idk what to make come up with something oh make an AI chat but make it good and make it look nice' }
|
|
];
|
|
var CURSOR = '<span class="term-cursor">|</span>';
|
|
var TYPE_MS = 40;
|
|
var done = [], li = 0, timer = null;
|
|
|
|
function render(partial) {
|
|
pre.innerHTML = done.join('\n') + (done.length ? '\n' : '') + partial + CURSOR;
|
|
}
|
|
function typeLine() {
|
|
var ln = lines[li], i = 0;
|
|
(function step() {
|
|
if (i <= ln.t.length) {
|
|
render(ln.p + ln.t.slice(0, i));
|
|
i++; timer = setTimeout(step, TYPE_MS);
|
|
} else {
|
|
done.push(ln.p + ln.t);
|
|
li++;
|
|
if (li >= lines.length) timer = setTimeout(reset, 4000); // hold last line 4s
|
|
else timer = setTimeout(typeLine, 2000); // pause 2s before next
|
|
}
|
|
})();
|
|
}
|
|
function reset() { clearTimeout(timer); done = []; li = 0; typeLine(); }
|
|
|
|
// Start typing only when the terminal scrolls into view (and replay each
|
|
// time you return to it).
|
|
if ('IntersectionObserver' in window) {
|
|
var io2 = new IntersectionObserver(function (entries) {
|
|
entries.forEach(function (e) { if (e.isIntersecting) reset(); });
|
|
}, { threshold: 0.45 });
|
|
io2.observe(pre);
|
|
} else {
|
|
reset();
|
|
}
|
|
})();
|
|
|
|
// Previews: hovering/tapping a panel expands it (CSS) and plays its video; the
|
|
// video only becomes visible once it actually starts playing, so missing
|
|
// files just leave the labeled placeholder.
|
|
(function () {
|
|
var panels = [].slice.call(document.querySelectorAll('.preview-panel'));
|
|
if (!panels.length) return;
|
|
var active = -1;
|
|
function playPanel(p) {
|
|
var v = p.querySelector('video');
|
|
if (!v) return;
|
|
var pr = v.play();
|
|
if (pr && pr.catch) pr.catch(function () {});
|
|
}
|
|
function pausePanel(p) {
|
|
var v = p.querySelector('video');
|
|
if (v) v.pause();
|
|
}
|
|
function setActive(i, shouldPlay) {
|
|
active = (i + panels.length) % panels.length;
|
|
panels.forEach(function (panel, k) {
|
|
var on = k === active;
|
|
panel.classList.toggle('is-active', on);
|
|
panel.setAttribute('aria-expanded', on ? 'true' : 'false');
|
|
if (!on) pausePanel(panel);
|
|
});
|
|
if (shouldPlay !== false) playPanel(panels[active]);
|
|
}
|
|
panels.forEach(function (p, i) {
|
|
var v = p.querySelector('video');
|
|
if (v) {
|
|
v.addEventListener('playing', function () { p.classList.add('has-video'); });
|
|
v.addEventListener('pause', function () { /* keep last frame */ });
|
|
}
|
|
p.setAttribute('aria-expanded', 'false');
|
|
p.addEventListener('mouseenter', function () { setActive(i); });
|
|
p.addEventListener('focus', function () { setActive(i); });
|
|
p.addEventListener('mouseleave', function () {
|
|
if (!window.matchMedia || !window.matchMedia('(hover: none)').matches) {
|
|
p.classList.remove('is-active');
|
|
p.setAttribute('aria-expanded', 'false');
|
|
pausePanel(p);
|
|
}
|
|
});
|
|
p.addEventListener('blur', function () { pausePanel(p); });
|
|
p.addEventListener('click', function () { setActive(i); });
|
|
});
|
|
var strip = document.querySelector('.previews');
|
|
var sx = null, sy = null;
|
|
if (strip) {
|
|
strip.addEventListener('touchstart', function (e) {
|
|
if (!e.touches.length) return;
|
|
sx = e.touches[0].clientX;
|
|
sy = e.touches[0].clientY;
|
|
}, { passive: true });
|
|
strip.addEventListener('touchend', function (e) {
|
|
if (sx === null || sy === null || !e.changedTouches.length) return;
|
|
var dx = e.changedTouches[0].clientX - sx;
|
|
var dy = e.changedTouches[0].clientY - sy;
|
|
if (Math.abs(dx) > 42 && Math.abs(dx) > Math.abs(dy) * 1.25) {
|
|
setActive((active < 0 ? 0 : active) + (dx < 0 ? 1 : -1));
|
|
}
|
|
sx = sy = null;
|
|
}, { passive: true });
|
|
}
|
|
})();
|
|
|
|
// Domino reveal: fade/slide each section in as it scrolls into view.
|
|
(function () {
|
|
var els = document.querySelectorAll('.hero, section');
|
|
if (!('IntersectionObserver' in window)) {
|
|
els.forEach(function (e) { e.classList.add('in'); });
|
|
return;
|
|
}
|
|
var io = new IntersectionObserver(function (entries) {
|
|
entries.forEach(function (e) {
|
|
if (e.isIntersecting) { e.target.classList.add('in'); io.unobserve(e.target); }
|
|
});
|
|
}, { threshold: 0.12, rootMargin: '0px 0px -8% 0px' });
|
|
els.forEach(function (e) { io.observe(e); });
|
|
})();
|
|
|
|
// Fake terminal window buttons — minimize, maximize, close (and reopen).
|
|
(function () {
|
|
var term = document.querySelector('.term');
|
|
var reopen = document.querySelector('.term-reopen');
|
|
if (!term) return;
|
|
term.querySelectorAll('.winbtns [data-term]').forEach(function (b) {
|
|
b.addEventListener('click', function () {
|
|
var act = b.getAttribute('data-term');
|
|
if (act === 'min') term.classList.toggle('term-min');
|
|
else if (act === 'close') {
|
|
term.classList.add('term-closed');
|
|
if (reopen) reopen.classList.add('show');
|
|
}
|
|
});
|
|
});
|
|
if (reopen) reopen.addEventListener('click', function () {
|
|
term.classList.remove('term-closed', 'term-min');
|
|
reopen.classList.remove('show');
|
|
});
|
|
})();
|
|
|
|
// Mobile testimonial carousel: tap or swipe to advance; Polyphemus shakes ~1s.
|
|
(function () {
|
|
var carousel = document.getElementById('tcarousel');
|
|
var nav = document.getElementById('tnav');
|
|
if (!carousel || !nav) return;
|
|
var cards = [].slice.call(carousel.querySelectorAll('.tcard'));
|
|
if (!cards.length) return;
|
|
var idx = 0;
|
|
|
|
var dots = cards.map(function (_, k) {
|
|
var d = document.createElement('span');
|
|
d.className = 'tdot';
|
|
d.addEventListener('click', function (e) { e.stopPropagation(); show(k); });
|
|
nav.appendChild(d);
|
|
return d;
|
|
});
|
|
var hint = document.createElement('div');
|
|
hint.className = 'thint';
|
|
hint.textContent = 'tap or swipe for the next satisfied customer →';
|
|
nav.appendChild(hint);
|
|
|
|
function show(i) {
|
|
idx = (i + cards.length) % cards.length;
|
|
cards.forEach(function (c, k) { c.classList.toggle('active', k === idx); c.classList.remove('shake'); });
|
|
dots.forEach(function (d, k) { d.classList.toggle('on', k === idx); });
|
|
var cur = cards[idx];
|
|
if (cur.getAttribute('data-shake') === '1') {
|
|
void cur.offsetWidth;
|
|
cur.classList.add('shake');
|
|
setTimeout(function () { cur.classList.remove('shake'); }, 1000);
|
|
}
|
|
}
|
|
|
|
carousel.addEventListener('click', function () { show(idx + 1); });
|
|
|
|
var _prev = document.querySelector('.tarrow.prev');
|
|
var _next = document.querySelector('.tarrow.next');
|
|
if (_prev) _prev.addEventListener('click', function (e) { e.stopPropagation(); show(idx - 1); });
|
|
if (_next) _next.addEventListener('click', function (e) { e.stopPropagation(); show(idx + 1); });
|
|
|
|
var sx = null;
|
|
carousel.addEventListener('touchstart', function (e) { sx = e.touches[0].clientX; }, { passive: true });
|
|
carousel.addEventListener('touchend', function (e) {
|
|
if (sx === null) return;
|
|
var dx = e.changedTouches[0].clientX - sx;
|
|
if (Math.abs(dx) > 30) { show(idx + (dx < 0 ? 1 : -1)); }
|
|
sx = null;
|
|
});
|
|
|
|
show(0);
|
|
})();
|
|
|
|
// Copy button for the codeblock command.
|
|
(function () {
|
|
var btn = document.querySelector('.codeblock .copy-btn');
|
|
if (!btn) return;
|
|
btn.addEventListener('click', function () {
|
|
navigator.clipboard.writeText(btn.getAttribute('data-copy')).then(function () {
|
|
btn.textContent = 'Copied!'; btn.classList.add('copied');
|
|
setTimeout(function () { btn.textContent = 'Copy'; btn.classList.remove('copied'); }, 2000);
|
|
});
|
|
});
|
|
})();
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|