Relay:
- Inbox/shared/sent split into own view-inbox (nav button + poll).
Compose is now single-column, centered.
- Header logo links to compose. Inbox badge stays fresh on all
views via a quiet loadInbox on every view switch.
- Mobile menu: full-height sheet below header, larger buttons
(15px font, 14px padding), full width.
- Login form wrapped in <form> with name attributes + for/id on
labels so password managers can autofill.
- Admin user list shows "last seen <relative time>" per user.
last_seen_at column on tenants, updated on every authenticated
request (session + client API).
- Cache-busting on style.css + app.js via filemtime.
- View persistence in localStorage (survives reload).
- Shared-bucket cards show title only (preview line removed).
Favicon:
- favicon.svg added to project root + copied to public/, relay/,
website/. Linked via <link rel="icon" type="image/svg+xml"> in
all three <head> sections.
Settings:
- New toggle: "Show shopping list items in task collector"
(default off). collectTasks skips #einkauf/#shopping cards when
disabled.
Fixes:
- card-spinner gets display:inline-block so it rotates outside
flex containers (shopping save spinner).
- Tim tab test opens the plugin before checking active state.
- Compose view centered with margin:auto on desktop.
Bumps to 1.9.29.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
704 lines
33 KiB
HTML
704 lines
33 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>hyprcrd</title>
|
|
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
|
<meta name="description" content="Self-hosted card manager. Markdown, tags, wiki-links, calendar, encrypted relay. Find everything.">
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Silkscreen&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://unpkg.com/@phosphor-icons/web@2.1.1/src/regular/style.css">
|
|
<style>
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
:root {
|
|
--bg: #06080f;
|
|
--s1: #0d1117;
|
|
--s2: #161b22;
|
|
--bdr: #21262d;
|
|
--tx: #8b949e;
|
|
--hi: #e6edf3;
|
|
--ac: #38bdf8;
|
|
--ac2: #a78bfa;
|
|
--rd: #fb7185;
|
|
--gn: #34d399;
|
|
--yl: #fbbf24;
|
|
--pk: #f472b6;
|
|
--pixel: 'Silkscreen', monospace;
|
|
--mono: 'JetBrains Mono', monospace;
|
|
}
|
|
html { scroll-behavior: smooth; }
|
|
body { font-family: var(--mono); background: var(--bg); color: var(--tx); }
|
|
::selection { background: rgba(56,189,248,0.25); }
|
|
|
|
/* ── Top bar ── */
|
|
header {
|
|
position: fixed; top: 0; left: 0; right: 0; z-index: 100;
|
|
background: rgba(6,8,15,0.88); backdrop-filter: blur(16px);
|
|
border-bottom: 1px solid var(--bdr); padding: 0 48px;
|
|
}
|
|
header .inner {
|
|
display: flex; align-items: center; height: 48px; gap: 20px;
|
|
}
|
|
header .brand { font-family: var(--pixel); font-size: 12px; color: var(--ac); letter-spacing: 2px; }
|
|
header nav { margin-left: auto; display: flex; gap: 20px; align-items: center; }
|
|
header nav a { font-size: 11px; color: var(--tx); font-family: var(--pixel); text-decoration: none; }
|
|
header nav a:hover { color: var(--hi); }
|
|
.gh-btn {
|
|
font-family: var(--pixel); font-size: 10px; color: var(--bg);
|
|
background: var(--ac); padding: 5px 16px; border-radius: 4px;
|
|
}
|
|
.gh-btn:hover { background: #7dd3fc; }
|
|
|
|
/* ── Sections ── */
|
|
.section {
|
|
min-height: 100vh; padding: 80px 48px;
|
|
display: flex; flex-direction: column; justify-content: center;
|
|
border-bottom: 1px solid var(--bdr);
|
|
position: relative; overflow: hidden;
|
|
}
|
|
|
|
/* ── Section label — big watermark word ── */
|
|
.section-label {
|
|
font-family: var(--pixel); font-size: clamp(60px, 12vw, 140px);
|
|
color: var(--bdr); letter-spacing: 4px; text-transform: uppercase;
|
|
line-height: 1; margin-bottom: 48px; user-select: none;
|
|
opacity: 0.7;
|
|
}
|
|
.section-label .accent { color: rgba(56,189,248,0.15); }
|
|
|
|
/* ── Hero ── */
|
|
.hero { padding-top: 100px; }
|
|
.hero-brand {
|
|
display: flex; align-items: center; gap: clamp(16px, 3vw, 32px);
|
|
margin-bottom: 48px;
|
|
}
|
|
.hero-brand i {
|
|
font-size: clamp(60px, 12vw, 140px); color: var(--ac);
|
|
line-height: 1; display: block;
|
|
}
|
|
.hero-brand span {
|
|
font-family: var(--pixel); font-size: clamp(60px, 12vw, 140px);
|
|
color: var(--ac); letter-spacing: 4px; line-height: 1;
|
|
}
|
|
.hero-brand .typed { display: inline; }
|
|
.hero-brand .cursor {
|
|
display: inline-block; width: 0.55em; height: 1em;
|
|
background: var(--ac); vertical-align: text-bottom;
|
|
margin-left: 2px;
|
|
animation: cursor-blink 530ms steps(1) infinite;
|
|
}
|
|
.cursor-done { animation: cursor-blink 530ms steps(1) 3 forwards !important; }
|
|
@keyframes cursor-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
|
|
.hero-title {
|
|
font-size: clamp(48px, 8vw, 100px); font-weight: 700;
|
|
color: var(--hi); line-height: 1.05; margin-bottom: 32px; letter-spacing: -3px;
|
|
}
|
|
.hero-title .accent { color: var(--ac); }
|
|
.hero-title .dim { color: var(--bdr); }
|
|
.hero-sub { font-size: 16px; line-height: 1.8; max-width: 560px; margin-bottom: 48px; }
|
|
.hero-terminal {
|
|
background: var(--s1); border: 1px solid var(--bdr); border-radius: 8px;
|
|
padding: 20px 28px; font-size: 13px; line-height: 2.2; display: inline-block;
|
|
}
|
|
.hero-terminal .p { color: var(--gn); }
|
|
.hero-terminal .c { color: var(--hi); }
|
|
.hero-terminal .d { opacity: 0.3; }
|
|
|
|
/* ── Feature layout ── */
|
|
.feat {
|
|
display: grid; grid-template-columns: 1fr 1fr; gap: 80px;
|
|
width: 100%; align-items: center;
|
|
}
|
|
.feat.reverse .feat-text { order: 2; }
|
|
.feat.reverse .feat-visual { order: 1; }
|
|
|
|
.feat-text h3 {
|
|
font-size: 28px; color: var(--hi); font-weight: 700;
|
|
line-height: 1.3; margin-bottom: 20px; letter-spacing: -0.5px;
|
|
}
|
|
.feat-text p { font-size: 14px; line-height: 1.9; margin-bottom: 16px; }
|
|
.feat-text ul { list-style: none; margin-top: 16px; }
|
|
.feat-text li {
|
|
font-size: 13px; padding: 6px 0; display: flex; align-items: flex-start; gap: 10px; line-height: 1.6;
|
|
}
|
|
.feat-text li::before {
|
|
content: ''; width: 6px; height: 6px; border-radius: 50%;
|
|
background: var(--ac); flex-shrink: 0; margin-top: 8px;
|
|
}
|
|
|
|
.feat-visual {
|
|
background: var(--s1); border: 1px solid var(--bdr); border-radius: 10px; overflow: hidden;
|
|
}
|
|
|
|
/* ── Visual panels ── */
|
|
.vis-code {
|
|
padding: 28px 32px; font-size: 13px; line-height: 2; white-space: pre-wrap;
|
|
}
|
|
.vis-code .kw { color: var(--ac); }
|
|
.vis-code .str { color: var(--gn); }
|
|
.vis-code .tag { color: var(--yl); }
|
|
.vis-code .link { color: var(--ac2); }
|
|
.vis-code .cm { opacity: 0.3; }
|
|
.vis-code .hi { color: var(--hi); }
|
|
|
|
.vis-tags { padding: 32px; display: flex; flex-wrap: wrap; gap: 10px; align-content: flex-start; }
|
|
.vtag {
|
|
font-family: var(--pixel); font-size: 11px; padding: 8px 18px;
|
|
border-radius: 6px; border: 1px solid;
|
|
}
|
|
|
|
.vis-keys { padding: 32px; display: flex; flex-wrap: wrap; gap: 10px; }
|
|
.vkey {
|
|
font-family: var(--pixel); font-size: 12px; padding: 10px 16px;
|
|
background: var(--bg); border: 1px solid var(--bdr); border-radius: 6px;
|
|
color: var(--hi); min-width: 40px; text-align: center;
|
|
box-shadow: 0 3px 0 var(--bdr);
|
|
}
|
|
|
|
.vis-cards { padding: 24px; display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
|
.vcard {
|
|
background: var(--bg); border: 1px solid var(--bdr); border-radius: 6px;
|
|
padding: 16px; border-top: 3px solid;
|
|
}
|
|
.vcard-t { font-family: var(--pixel); font-size: 10px; color: var(--hi); margin-bottom: 8px; }
|
|
|
|
.vis-gitea { padding: 28px 32px; }
|
|
.vis-gitea .ghdr {
|
|
font-family: var(--pixel); font-size: 10px; color: var(--tx);
|
|
margin-bottom: 16px; display: flex; align-items: center; gap: 8px;
|
|
}
|
|
.vis-gitea .issue-row {
|
|
display: flex; align-items: center; gap: 12px;
|
|
padding: 12px 0; border-bottom: 1px solid var(--bdr); font-size: 12px;
|
|
}
|
|
.vis-gitea .issue-row:last-child { border-bottom: none; }
|
|
.vis-gitea .issue-state { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
|
.vis-gitea .issue-title { color: var(--hi); flex: 1; }
|
|
.vis-gitea .issue-labels { display: flex; gap: 4px; }
|
|
.vis-gitea .issue-label {
|
|
font-family: var(--pixel); font-size: 8px; padding: 2px 8px; border-radius: 3px;
|
|
}
|
|
.vis-gitea .issue-num { font-family: var(--pixel); font-size: 10px; color: var(--tx); }
|
|
|
|
.vis-relay { padding: 28px 32px; font-size: 12px; line-height: 2.2; }
|
|
.vis-relay .node {
|
|
display: inline-block; padding: 4px 14px; border-radius: 4px;
|
|
font-family: var(--pixel); font-size: 10px; border: 1px solid; margin: 2px;
|
|
}
|
|
.vis-relay .arrow { color: var(--bdr); margin: 0 6px; }
|
|
|
|
/* ── Screenshot ── */
|
|
.hero-screenshot {
|
|
margin-top: 56px; border-radius: 10px; overflow: hidden;
|
|
border: 1px solid var(--bdr);
|
|
box-shadow: 0 24px 80px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.03);
|
|
}
|
|
.hero-screenshot img { display: block; width: 100%; height: auto; }
|
|
|
|
/* ── Email visual ── */
|
|
.vis-inbox { padding: 24px 32px; }
|
|
.vis-inbox .inbox-row {
|
|
display: flex; align-items: center; gap: 12px;
|
|
padding: 10px 0; border-bottom: 1px solid var(--bdr); font-size: 12px;
|
|
}
|
|
.vis-inbox .inbox-row:last-child { border-bottom: none; }
|
|
.vis-inbox .inbox-from { width: 120px; color: var(--hi); flex-shrink: 0; font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
.vis-inbox .inbox-subject { flex: 1; color: var(--tx); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
.vis-inbox .inbox-badge {
|
|
font-family: var(--pixel); font-size: 8px; padding: 2px 8px;
|
|
border-radius: 3px; flex-shrink: 0;
|
|
}
|
|
.vis-inbox .inbox-row.spam { opacity: 0.4; }
|
|
.vis-inbox .inbox-row.spam .inbox-from { text-decoration: line-through; }
|
|
|
|
/* ── CTA ── */
|
|
.cta-section {
|
|
min-height: 60vh; display: flex; align-items: center; justify-content: center;
|
|
text-align: center; padding: 80px 48px;
|
|
}
|
|
.cta-section h2 {
|
|
font-family: var(--pixel); font-size: clamp(24px, 4vw, 48px);
|
|
color: var(--hi); margin-bottom: 16px; letter-spacing: -1px;
|
|
}
|
|
.cta-section p { font-size: 14px; margin-bottom: 40px; max-width: 440px; margin-left: auto; margin-right: auto; }
|
|
.cta-links { display: flex; gap: 16px; justify-content: center; flex-wrap: wrap; }
|
|
.cta-btn {
|
|
font-family: var(--pixel); font-size: 11px; padding: 12px 28px;
|
|
border-radius: 6px; transition: all 0.2s; text-decoration: none;
|
|
}
|
|
.cta-btn.primary { background: var(--ac); color: var(--bg); }
|
|
.cta-btn.primary:hover { background: #7dd3fc; }
|
|
.cta-btn.outline { border: 1px solid var(--bdr); color: var(--tx); }
|
|
.cta-btn.outline:hover { border-color: var(--ac); color: var(--ac); }
|
|
|
|
footer {
|
|
padding: 24px 48px; border-top: 1px solid var(--bdr);
|
|
font-family: var(--pixel); font-size: 9px; color: var(--tx); opacity: 0.3;
|
|
text-align: center;
|
|
}
|
|
|
|
@media (max-width: 900px) {
|
|
.section { padding: 60px 24px; }
|
|
.feat { grid-template-columns: 1fr; gap: 40px; }
|
|
.feat.reverse .feat-text, .feat.reverse .feat-visual { order: unset; }
|
|
.section-label { font-size: clamp(36px, 10vw, 80px); }
|
|
header { padding: 0 20px; }
|
|
header nav a:not(.gh-btn) { display: none; }
|
|
footer { padding: 24px; }
|
|
.cta-section { padding: 60px 24px; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<header>
|
|
<div class="inner">
|
|
<span class="brand">hyprcrd</span>
|
|
<nav>
|
|
<a href="#search">search</a>
|
|
<a href="#tags">tags</a>
|
|
<a href="#editor">editor</a>
|
|
<a href="#gitea">gitea</a>
|
|
<a href="#relay">relay</a>
|
|
<a href="#email">email</a>
|
|
<a href="#deploy">deploy</a>
|
|
<a href="https://git.uesome.de/tanztee/hyprcrd" class="gh-btn">source</a>
|
|
</nav>
|
|
</div>
|
|
</header>
|
|
|
|
<main>
|
|
|
|
<section class="section hero">
|
|
<div class="hero-brand">
|
|
<i class="ph ph-terminal-window"></i>
|
|
<span><span class="typed" id="hero-typed"></span><span class="cursor" id="hero-cursor"></span></span>
|
|
</div>
|
|
<h1 class="hero-title">
|
|
stop losing<br>
|
|
<span class="accent">information</span><span class="dim">.</span>
|
|
</h1>
|
|
<p class="hero-sub">
|
|
Cards. Tags. Wiki-links. Markdown. Calendar. Encrypted sync between devices.
|
|
One SQLite file. Zero cloud. Built for finding things, not filing them.
|
|
</p>
|
|
<div class="hero-terminal">
|
|
<span class="p">$</span> <span class="c">git clone https://git.uesome.de/tanztee/hyprcrd</span><br>
|
|
<span class="p">$</span> <span class="c">cd hyprcrd && docker compose up -d</span><br>
|
|
<span class="d"># open localhost:8088 — set password — done</span>
|
|
</div>
|
|
<div class="hero-screenshot">
|
|
<img src="screenshot.png" alt="hyprcrd board view with cards, tags, and buckets">
|
|
</div>
|
|
</section>
|
|
|
|
<section class="section" id="search">
|
|
<div class="section-label">SEARCH</div>
|
|
<div class="feat">
|
|
<div class="feat-text">
|
|
<h3>Type to find. Instantly.</h3>
|
|
<p>Full-text search hits titles, card bodies, and tags at the same time. Combine <code>#tag</code> filters with free text — <code>#project deploy script</code> finds exactly what you think it does.</p>
|
|
<ul>
|
|
<li>Search + replace across every card in the database</li>
|
|
<li><code>#tag1 #tag2</code> for AND filtering</li>
|
|
<li>Scope to a specific bucket</li>
|
|
<li>Drag search results onto each other to group cards</li>
|
|
<li>Preview panel shows rendered markdown</li>
|
|
</ul>
|
|
</div>
|
|
<div class="feat-visual">
|
|
<div class="vis-code"><span class="cm">></span> <span class="hi">#project deploy</span>
|
|
|
|
<span class="cm"> 3 results in 2ms</span>
|
|
|
|
<span class="kw">API redesign</span>
|
|
<span class="cm">...the <mark style="background:rgba(56,189,248,0.15);color:var(--ac)">deploy</mark> script handles
|
|
staging and prod environments...</span>
|
|
|
|
<span class="kw">Infra checklist</span>
|
|
<span class="cm">...<mark style="background:rgba(56,189,248,0.15);color:var(--ac)">deploy</mark> to k8s cluster
|
|
after running integration tests...</span>
|
|
|
|
<span class="kw">Sprint retro</span>
|
|
<span class="cm">...discussed <mark style="background:rgba(56,189,248,0.15);color:var(--ac)">deploy</mark>
|
|
frequency and rollback strategy...</span></div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="section" id="tags">
|
|
<div class="section-label">TAGS</div>
|
|
<div class="feat reverse">
|
|
<div class="feat-text">
|
|
<h3>Write a hashtag.<br>It becomes structure.</h3>
|
|
<p>No separate tag dialog. No filing step. Write <code>#project</code> in the card body and it extracts automatically into a clickable, filterable tag.</p>
|
|
<ul>
|
|
<li>Hierarchical: <code>#project/frontend/auth</code> — each segment is independently clickable</li>
|
|
<li>Click to filter, Ctrl+click to combine (AND logic)</li>
|
|
<li>Tag quick-pick with <kbd>T</kbd> — searchable overlay of all existing tags</li>
|
|
<li>Assign via right-click context menu</li>
|
|
<li>Super-buckets: nest buckets into collapsible groups</li>
|
|
</ul>
|
|
</div>
|
|
<div class="feat-visual">
|
|
<div class="vis-tags">
|
|
<span class="vtag" style="background:rgba(56,189,248,0.06);color:var(--ac);border-color:rgba(56,189,248,0.2);">#project</span>
|
|
<span class="vtag" style="background:rgba(167,139,250,0.06);color:var(--ac2);border-color:rgba(167,139,250,0.2);">#project/api</span>
|
|
<span class="vtag" style="background:rgba(167,139,250,0.06);color:var(--ac2);border-color:rgba(167,139,250,0.2);">#project/frontend</span>
|
|
<span class="vtag" style="background:rgba(167,139,250,0.06);color:var(--ac2);border-color:rgba(167,139,250,0.2);">#project/frontend/auth</span>
|
|
<span class="vtag" style="background:rgba(251,113,133,0.06);color:var(--rd);border-color:rgba(251,113,133,0.2);">#bug</span>
|
|
<span class="vtag" style="background:rgba(251,113,133,0.06);color:var(--rd);border-color:rgba(251,113,133,0.2);">#urgent</span>
|
|
<span class="vtag" style="background:rgba(52,211,153,0.06);color:var(--gn);border-color:rgba(52,211,153,0.2);">#ops</span>
|
|
<span class="vtag" style="background:rgba(52,211,153,0.06);color:var(--gn);border-color:rgba(52,211,153,0.2);">#done</span>
|
|
<span class="vtag" style="background:rgba(251,191,36,0.06);color:var(--yl);border-color:rgba(251,191,36,0.2);">#meeting</span>
|
|
<span class="vtag" style="background:rgba(251,191,36,0.06);color:var(--yl);border-color:rgba(251,191,36,0.2);">#termin</span>
|
|
<span class="vtag" style="background:rgba(56,189,248,0.06);color:var(--ac);border-color:rgba(56,189,248,0.2);">#research</span>
|
|
<span class="vtag" style="background:rgba(244,114,182,0.06);color:var(--pk);border-color:rgba(244,114,182,0.2);">#idea</span>
|
|
<span class="vtag" style="background:rgba(244,114,182,0.06);color:var(--pk);border-color:rgba(244,114,182,0.2);">#geburtstag</span>
|
|
<span class="vtag" style="background:rgba(56,189,248,0.06);color:var(--ac);border-color:rgba(56,189,248,0.2);">#personal</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="section" id="editor">
|
|
<div class="section-label">EDITOR</div>
|
|
<div class="feat">
|
|
<div class="feat-text">
|
|
<h3>Markdown that does more.</h3>
|
|
<p>Every card is a markdown document with live preview. Enriched with tools that make it a knowledge system, not just a text file.</p>
|
|
<ul>
|
|
<li>Wiki-links: <code>[[Card Title]]</code> — auto-complete, backlinks, split view</li>
|
|
<li>Snippets: <code>?dat</code> <code>?table</code> <code>?code</code> <code>?todo</code></li>
|
|
<li>Inline calculator: <code>?3+5</code> space → 8</li>
|
|
<li>Line operations: Alt+Up/Down to move, Shift+Alt to duplicate</li>
|
|
<li>Upload images, audio, PDFs — paste or drag</li>
|
|
<li>Asset picker: thumbnail grid with search</li>
|
|
<li>Extract selection to new card with auto-link</li>
|
|
<li>Find & replace within the card (Ctrl+H)</li>
|
|
</ul>
|
|
</div>
|
|
<div class="feat-visual">
|
|
<div class="vis-code"><span class="hi">## Sprint planning Q3</span>
|
|
|
|
<span class="cm">Goals for next quarter:</span>
|
|
|
|
- [x] Finish API redesign
|
|
- [x] Migrate auth to <span class="link">[[Architecture RFC]]</span>
|
|
- [ ] Load testing
|
|
- [ ] Deploy to prod
|
|
|
|
<span class="kw">**Deadline:**</span> 2026-07-01
|
|
<span class="kw">**Owner:**</span> @alice
|
|
|
|
See also: <span class="link">[[Infra checklist]]</span>
|
|
|
|
<span class="tag">#project/api</span> <span class="tag">#meeting</span></div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="section" id="gitea">
|
|
<div class="section-label"><span class="accent">GITEA</span></div>
|
|
<div class="feat reverse">
|
|
<div class="feat-text">
|
|
<h3>Cards become tickets.<br>Tickets stay linked.</h3>
|
|
<p>Connect any number of Gitea instances. Browse issues, create tickets directly from cards, and keep your project management in sync without leaving hyprcrd.</p>
|
|
<ul>
|
|
<li>Multi-account: connect unlimited Gitea servers with token auth</li>
|
|
<li>Default repo per account — pre-fills create form, stays editable</li>
|
|
<li>Right-click any card → <em>Make ticket</em>: title + body pre-fill the issue form</li>
|
|
<li>Full issue browser: filter by state, scope (assigned/created/mentioned)</li>
|
|
<li>Label picker, milestone selector, assignee multi-select</li>
|
|
<li>Repo autocomplete with fuzzy search across all repos</li>
|
|
<li>Inline issue detail: view body, labels, assignees, edit title, toggle state</li>
|
|
<li>Pagination for large repos</li>
|
|
</ul>
|
|
</div>
|
|
<div class="feat-visual">
|
|
<div class="vis-gitea">
|
|
<div class="ghdr">
|
|
<span style="color:var(--ac);">git.example.com</span>
|
|
<span style="opacity:0.3">/</span>
|
|
<span style="color:var(--hi);">team/hyprcrd</span>
|
|
<span style="margin-left:auto;padding:2px 10px;border-radius:3px;background:rgba(52,211,153,0.08);color:var(--gn);border:1px solid rgba(52,211,153,0.15);font-family:var(--pixel);">open</span>
|
|
</div>
|
|
<div class="issue-row">
|
|
<span class="issue-state" style="background:var(--gn);"></span>
|
|
<span class="issue-title">API redesign: migrate to v3 endpoints</span>
|
|
<span class="issue-labels">
|
|
<span class="issue-label" style="background:rgba(56,189,248,0.1);color:var(--ac);">enhancement</span>
|
|
<span class="issue-label" style="background:rgba(167,139,250,0.1);color:var(--ac2);">api</span>
|
|
</span>
|
|
<span class="issue-num">#142</span>
|
|
</div>
|
|
<div class="issue-row">
|
|
<span class="issue-state" style="background:var(--gn);"></span>
|
|
<span class="issue-title">Auth timeout on mobile devices</span>
|
|
<span class="issue-labels">
|
|
<span class="issue-label" style="background:rgba(251,113,133,0.1);color:var(--rd);">bug</span>
|
|
<span class="issue-label" style="background:rgba(251,191,36,0.1);color:var(--yl);">priority</span>
|
|
</span>
|
|
<span class="issue-num">#139</span>
|
|
</div>
|
|
<div class="issue-row">
|
|
<span class="issue-state" style="background:var(--gn);"></span>
|
|
<span class="issue-title">Add CalDAV recurring event support</span>
|
|
<span class="issue-labels">
|
|
<span class="issue-label" style="background:rgba(52,211,153,0.1);color:var(--gn);">feature</span>
|
|
</span>
|
|
<span class="issue-num">#137</span>
|
|
</div>
|
|
<div class="issue-row">
|
|
<span class="issue-state" style="background:var(--ac2);"></span>
|
|
<span class="issue-title">Relay: per-pair key exchange for strict E2E</span>
|
|
<span class="issue-labels">
|
|
<span class="issue-label" style="background:rgba(244,114,182,0.1);color:var(--pk);">security</span>
|
|
<span class="issue-label" style="background:rgba(167,139,250,0.1);color:var(--ac2);">relay</span>
|
|
</span>
|
|
<span class="issue-num">#131</span>
|
|
</div>
|
|
<div class="issue-row">
|
|
<span class="issue-state" style="background:var(--tx);opacity:0.3;"></span>
|
|
<span class="issue-title" style="opacity:0.4;">Export: include plugin settings in backup</span>
|
|
<span class="issue-labels">
|
|
<span class="issue-label" style="background:rgba(56,189,248,0.05);color:var(--tx);opacity:0.5;">enhancement</span>
|
|
</span>
|
|
<span class="issue-num" style="opacity:0.4;">#128</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="section" id="relay">
|
|
<div class="section-label">RELAY</div>
|
|
<div class="feat">
|
|
<div class="feat-text">
|
|
<h3>Sync between devices.<br>Share with people.</h3>
|
|
<p>A self-hosted message relay that connects multiple hyprcrd instances. Per-user encryption. Collaborative buckets with exclusive card locking. Encrypted chat with read receipts.</p>
|
|
<ul>
|
|
<li>Multi-tenant: each user gets their own login + encryption secret</li>
|
|
<li>Cards sync between devices — push from one, poll from another</li>
|
|
<li>Collaborative buckets: shared editing with exclusive locks</li>
|
|
<li>Chat: encrypted messages with delivery + read receipts</li>
|
|
<li>Compose + inbox UI works on mobile</li>
|
|
<li>24h ephemeral — the relay routes, it doesn't archive</li>
|
|
<li>AES-256-CBC at every hop, bcrypt passwords, master key at rest</li>
|
|
</ul>
|
|
</div>
|
|
<div class="feat-visual">
|
|
<div class="vis-relay">
|
|
<div style="margin-bottom:20px;">
|
|
<span class="node" style="border-color:var(--ac);color:var(--ac);">alice</span>
|
|
<span class="arrow">→</span>
|
|
<span style="font-family:var(--pixel);font-size:10px;color:var(--tx);">encrypts with own secret</span>
|
|
<span class="arrow">→</span>
|
|
<span class="node" style="border-color:var(--yl);color:var(--yl);">relay</span>
|
|
</div>
|
|
<div style="margin-bottom:20px;padding-left:24px;border-left:2px solid var(--bdr);">
|
|
<span style="font-size:11px;color:var(--tx);">
|
|
1. authenticate sender<br>
|
|
2. decrypt with alice's secret<br>
|
|
3. re-encrypt with inter-tenant key<br>
|
|
4. store (24h TTL)<br>
|
|
5. on bob's poll: decrypt<br>
|
|
6. re-encrypt with bob's secret<br>
|
|
7. deliver
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<span class="node" style="border-color:var(--yl);color:var(--yl);">relay</span>
|
|
<span class="arrow">→</span>
|
|
<span style="font-family:var(--pixel);font-size:10px;color:var(--tx);">re-encrypted for recipient</span>
|
|
<span class="arrow">→</span>
|
|
<span class="node" style="border-color:var(--gn);color:var(--gn);">bob</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="section" id="email">
|
|
<div class="section-label">EMAIL</div>
|
|
<div class="feat">
|
|
<div class="feat-text">
|
|
<h3>Built-in mail client.<br>Spam dies on arrival.</h3>
|
|
<p>A focused IMAP mail client built into hyprcrd. Not a replacement for your full email workflow — a triage tool. See what's real, nuke what's not, move on.</p>
|
|
<ul>
|
|
<li>Multi-account IMAP with encrypted credential storage</li>
|
|
<li>Automatic spam scoring — sender reputation, header analysis, pattern matching</li>
|
|
<li>One-click spam/ham training per sender — builds your personal allowlist/blocklist</li>
|
|
<li>Bulk select all flagged spam and delete in one action</li>
|
|
<li>Suspicious messages marked with warning badge and score breakdown</li>
|
|
<li>HTML rendering with external images blocked by default (tracking pixel protection)</li>
|
|
<li>Plain text fallback — always available, always safe</li>
|
|
<li>Pagination for large mailboxes, keyboard-friendly navigation</li>
|
|
</ul>
|
|
</div>
|
|
<div class="feat-visual">
|
|
<div class="vis-inbox">
|
|
<div class="inbox-row">
|
|
<span class="inbox-from">@alice</span>
|
|
<span class="inbox-subject">Re: Sprint planning — updated timeline attached</span>
|
|
<span class="inbox-badge" style="background:rgba(52,211,153,0.1);color:var(--gn);">inbox</span>
|
|
</div>
|
|
<div class="inbox-row">
|
|
<span class="inbox-from">GitHub</span>
|
|
<span class="inbox-subject">[hyprcrd] PR #142 merged: API v3 migration</span>
|
|
<span class="inbox-badge" style="background:rgba(56,189,248,0.1);color:var(--ac);">notification</span>
|
|
</div>
|
|
<div class="inbox-row">
|
|
<span class="inbox-from">@carol</span>
|
|
<span class="inbox-subject">Relay docs draft — please review before Friday</span>
|
|
<span class="inbox-badge" style="background:rgba(52,211,153,0.1);color:var(--gn);">inbox</span>
|
|
</div>
|
|
<div class="inbox-row spam">
|
|
<span class="inbox-from">prince.ng@mail.ru</span>
|
|
<span class="inbox-subject">URGENT: $4.5M inheritance awaiting your claim</span>
|
|
<span class="inbox-badge" style="background:rgba(251,113,133,0.1);color:var(--rd);">spam 94%</span>
|
|
</div>
|
|
<div class="inbox-row spam">
|
|
<span class="inbox-from">deals@sz-wholesale.cn</span>
|
|
<span class="inbox-subject">Limited offer!!! Best prices on electronics!!!</span>
|
|
<span class="inbox-badge" style="background:rgba(251,113,133,0.1);color:var(--rd);">spam 87%</span>
|
|
</div>
|
|
<div class="inbox-row">
|
|
<span class="inbox-from">Sentry</span>
|
|
<span class="inbox-subject">New issue: TypeError in /api/relay/push</span>
|
|
<span class="inbox-badge" style="background:rgba(251,191,36,0.1);color:var(--yl);">? suspicious</span>
|
|
</div>
|
|
<div class="inbox-row spam">
|
|
<span class="inbox-from">crypto-gains@proton.me</span>
|
|
<span class="inbox-subject">Make 500% returns with this one trick</span>
|
|
<span class="inbox-badge" style="background:rgba(251,113,133,0.1);color:var(--rd);">spam 98%</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="section" id="keys">
|
|
<div class="section-label">KEYBOARD</div>
|
|
<div class="feat reverse">
|
|
<div class="feat-text">
|
|
<h3>40+ shortcuts.<br>Zero mouse required.</h3>
|
|
<p>Every action has a key binding. Arrow keys navigate the board. Number keys switch buckets. Quick capture creates cards without leaving the keyboard.</p>
|
|
<ul>
|
|
<li>Press <kbd>?</kbd> for the full shortcut reference</li>
|
|
<li>Shortcuts auto-suppress in input fields</li>
|
|
<li>Context-aware: editor shortcuts only fire in the editor</li>
|
|
<li>Styled tooltips and inline help across the UI</li>
|
|
</ul>
|
|
</div>
|
|
<div class="feat-visual">
|
|
<div class="vis-keys">
|
|
<span class="vkey">?</span>
|
|
<span class="vkey">N</span>
|
|
<span class="vkey">J</span>
|
|
<span class="vkey">T</span>
|
|
<span class="vkey">C</span>
|
|
<span class="vkey">W</span>
|
|
<span class="vkey">M</span>
|
|
<span class="vkey">/</span>
|
|
<span class="vkey">Esc</span>
|
|
<span class="vkey">F1</span>
|
|
<span class="vkey">Ctrl K</span>
|
|
<span class="vkey">Ctrl N</span>
|
|
<span class="vkey">Ctrl H</span>
|
|
<span class="vkey">Ctrl S</span>
|
|
<span class="vkey">Ctrl Z</span>
|
|
<span class="vkey">↑</span>
|
|
<span class="vkey">↓</span>
|
|
<span class="vkey">←</span>
|
|
<span class="vkey">→</span>
|
|
<span class="vkey">Enter</span>
|
|
<span class="vkey">Tab</span>
|
|
<span class="vkey">1</span>
|
|
<span class="vkey">2</span>
|
|
<span class="vkey">3</span>
|
|
<span class="vkey">...</span>
|
|
<span class="vkey">9</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="section" id="deploy">
|
|
<div class="section-label">DEPLOY</div>
|
|
<div class="feat">
|
|
<div class="feat-text">
|
|
<h3>Docker. PHP host. Desktop app.</h3>
|
|
<p>Three deployment paths, same app. The Electron build bundles a static PHP binary — no PHP installation needed. Data lives outside the app bundle and survives updates.</p>
|
|
<ul>
|
|
<li>One SQLite file — back up by copying <code>data/hyprcrd.db</code></li>
|
|
<li>Export as JSON+media ZIP or Obsidian/Logseq markdown vault</li>
|
|
<li>Plugin system: drop a folder, reload, feature appears</li>
|
|
<li>Bundled: chat, email, Gitea, canvas, slideshows, Logseq import, watch</li>
|
|
<li>Fully themed: design tokens, presets, build your own</li>
|
|
<li>MIT licensed</li>
|
|
</ul>
|
|
</div>
|
|
<div class="feat-visual">
|
|
<div class="vis-cards">
|
|
<div class="vcard" style="border-color:var(--ac);">
|
|
<div class="vcard-t">Docker</div>
|
|
<div style="font-size:11px;color:var(--gn);line-height:1.8;">docker compose up -d<br><span style="opacity:0.3">→ localhost:8088</span></div>
|
|
</div>
|
|
<div class="vcard" style="border-color:var(--ac2);">
|
|
<div class="vcard-t">PHP host</div>
|
|
<div style="font-size:11px;color:var(--tx);line-height:1.8;">Upload to webspace<br>Set docroot → public/</div>
|
|
</div>
|
|
<div class="vcard" style="border-color:var(--gn);">
|
|
<div class="vcard-t">Electron</div>
|
|
<div style="font-size:11px;color:var(--gn);line-height:1.8;">npm run dist<br><span style="opacity:0.3">→ AppImage / DMG / NSIS</span></div>
|
|
</div>
|
|
<div class="vcard" style="border-color:var(--yl);">
|
|
<div class="vcard-t">Data</div>
|
|
<div style="font-size:11px;color:var(--tx);line-height:1.8;">data/hyprcrd.db<br>public/uploads/</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="cta-section">
|
|
<div>
|
|
<h2>your brain, self-hosted.</h2>
|
|
<p>Set a password. Start writing. Find everything.</p>
|
|
<div class="cta-links">
|
|
<a href="https://git.uesome.de/tanztee/hyprcrd" class="cta-btn primary">get the source</a>
|
|
<a href="https://git.uesome.de/tanztee/hyprcrd/src/branch/master/README.md" class="cta-btn outline">readme</a>
|
|
<a href="https://git.uesome.de/tanztee/hyprcrd/src/branch/master/relay/README.md" class="cta-btn outline">relay docs</a>
|
|
<a href="https://git.uesome.de/tanztee/hyprcrd/src/branch/master/electron/README.md" class="cta-btn outline">electron</a>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
</main>
|
|
|
|
<footer>hyprcrd · MIT license · v1.3.3</footer>
|
|
|
|
<script>
|
|
(function() {
|
|
const text = 'hyprcrd';
|
|
const el = document.getElementById('hero-typed');
|
|
const cursor = document.getElementById('hero-cursor');
|
|
if (!el) return;
|
|
let i = 0;
|
|
function type() {
|
|
if (i < text.length) {
|
|
el.textContent += text[i++];
|
|
setTimeout(type, 90 + Math.random() * 80);
|
|
} else {
|
|
setTimeout(() => cursor.classList.add('cursor-done'), 1500);
|
|
}
|
|
}
|
|
setTimeout(type, 400);
|
|
})();
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|