example-content plugin, project website with screenshot
Example-content: footer button creates 20 realistic demo cards in a "Project Alpha" bucket for screenshots and first-time exploration. Website: full-page sections with large watermark labels, screenshot hero with typing animation, email/spam section, Gitea detail, relay encryption flow, keyboard keycaps, deploy options. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
b9345a410f
commit
9f85e484c4
8 files changed
+1207
-3
No files matched your search
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
define('APP_NAME', 'hyprcrd');
|
define('APP_NAME', 'hyprcrd');
|
||||||
define('APP_VERSION', '1.3.3');
|
define('APP_VERSION', '1.3.4');
|
||||||
|
|
||||||
// Database path — writable directory, outside webroot
|
// Database path — writable directory, outside webroot
|
||||||
// DB_PATH / UPLOAD_DIR can be pre-defined by the Electron router for app isolation
|
// DB_PATH / UPLOAD_DIR can be pre-defined by the Electron router for app isolation
|
||||||
|
|||||||
+2
-1
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"disabled": [
|
"disabled": [
|
||||||
"logseq-import"
|
"logseq-import",
|
||||||
|
"example-content"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hyprcrd",
|
"name": "hyprcrd",
|
||||||
"version": "1.3.3",
|
"version": "1.3.4",
|
||||||
"description": "hyprcrd — self-hosted card manager",
|
"description": "hyprcrd — self-hosted card manager",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -0,0 +1,487 @@
|
|||||||
|
// ── Example content plugin — one-click demo data ──
|
||||||
|
|
||||||
|
const h = window.hyprcrd;
|
||||||
|
const api = h?.api || window._exampleApi;
|
||||||
|
|
||||||
|
const COLORS = {
|
||||||
|
blue: '#38bdf8', purple: '#a78bfa', pink: '#f472b6', red: '#fb7185',
|
||||||
|
green: '#34d399', yellow: '#fbbf24', orange: '#fb923c', slate: '#64748b',
|
||||||
|
};
|
||||||
|
|
||||||
|
const CARDS = [
|
||||||
|
{
|
||||||
|
title: 'Architecture RFC',
|
||||||
|
color: COLORS.blue,
|
||||||
|
body: `## System Architecture
|
||||||
|
|
||||||
|
The new microservice layout separates auth, data, and notification layers.
|
||||||
|
|
||||||
|
### Key decisions
|
||||||
|
- PostgreSQL for persistent state
|
||||||
|
- Redis for session cache + pub/sub
|
||||||
|
- gRPC between internal services
|
||||||
|
- REST + WebSocket for client API
|
||||||
|
|
||||||
|
### Open questions
|
||||||
|
- [ ] Rate limiting strategy
|
||||||
|
- [ ] Multi-region failover
|
||||||
|
- [x] Auth token rotation scheme
|
||||||
|
- [x] Database migration tooling
|
||||||
|
|
||||||
|
See also: [[Deploy checklist]]
|
||||||
|
|
||||||
|
#project/api #architecture`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Sprint Planning Q3',
|
||||||
|
color: COLORS.purple,
|
||||||
|
body: `## Goals
|
||||||
|
|
||||||
|
1. Finish API v3 migration
|
||||||
|
2. Launch mobile auth flow
|
||||||
|
3. Load testing at 10k concurrent
|
||||||
|
4. Cut staging→prod deploy time to <5min
|
||||||
|
|
||||||
|
**Deadline:** 2026-07-01
|
||||||
|
**Owner:** @alice
|
||||||
|
|
||||||
|
### Capacity
|
||||||
|
| Team | Points | Allocated |
|
||||||
|
|----------|--------|-----------|
|
||||||
|
| Backend | 34 | 30 |
|
||||||
|
| Frontend | 21 | 18 |
|
||||||
|
| Infra | 13 | 13 |
|
||||||
|
|
||||||
|
#meeting #project`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Auth timeout on mobile',
|
||||||
|
color: COLORS.red,
|
||||||
|
body: `Users on iOS Safari report being logged out after ~3 minutes of inactivity.
|
||||||
|
|
||||||
|
### Repro
|
||||||
|
1. Login on iPhone 15 (Safari 18)
|
||||||
|
2. Switch to another app for 3 min
|
||||||
|
3. Return → "session expired"
|
||||||
|
|
||||||
|
### Investigation
|
||||||
|
- Cookie \`SameSite=Strict\` might be too aggressive
|
||||||
|
- Safari's ITP may be purging the session cookie
|
||||||
|
- Backend session TTL is 24h — not the issue
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
Try \`SameSite=Lax\` + short-lived refresh token.
|
||||||
|
|
||||||
|
#bug #urgent #project/frontend/auth`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Deploy checklist',
|
||||||
|
color: COLORS.green,
|
||||||
|
body: `- [x] Run full test suite
|
||||||
|
- [x] Update CHANGELOG.md
|
||||||
|
- [x] Bump version in config
|
||||||
|
- [ ] Tag release in git
|
||||||
|
- [ ] Build Docker image
|
||||||
|
- [ ] Push to registry
|
||||||
|
- [ ] Roll out to staging
|
||||||
|
- [ ] Smoke test staging
|
||||||
|
- [ ] Promote to production
|
||||||
|
- [ ] Monitor Grafana for 30min
|
||||||
|
|
||||||
|
> Last deploy: 2026-04-08 by @bob
|
||||||
|
> Rollback: \`git revert HEAD && ./deploy.sh\`
|
||||||
|
|
||||||
|
#ops #deploy`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Meeting 2026-04-15 — Retro',
|
||||||
|
color: COLORS.yellow,
|
||||||
|
due_date: '2026-04-15',
|
||||||
|
body: `**Zeit:** 14:00-15:00
|
||||||
|
**Ort:** Room 42
|
||||||
|
**Teilnehmer:** Alice, Bob, Carol
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### What went well
|
||||||
|
- Deploy frequency improved (3x/week → daily)
|
||||||
|
- Zero P1 incidents this sprint
|
||||||
|
- New search feature got good feedback
|
||||||
|
|
||||||
|
### What to improve
|
||||||
|
- PR review turnaround still >24h
|
||||||
|
- Flaky integration tests causing false alarms
|
||||||
|
- Documentation gaps in relay module
|
||||||
|
|
||||||
|
### Action items
|
||||||
|
- [ ] @alice: set up PR review SLA
|
||||||
|
- [ ] @bob: fix top 5 flaky tests
|
||||||
|
- [ ] @carol: write relay onboarding doc
|
||||||
|
|
||||||
|
#meeting #termin #retro`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Reading list',
|
||||||
|
color: COLORS.slate,
|
||||||
|
body: `Papers and articles to read:
|
||||||
|
|
||||||
|
- [Designing Data-Intensive Applications](https://dataintensive.net/) — Ch. 7-9
|
||||||
|
- [The Twelve-Factor App](https://12factor.net/)
|
||||||
|
- \`CAP theorem revisited\` — Brewer 2012
|
||||||
|
- Local-first software — Kleppmann et al.
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
> "The truth is the log. The database is a cache."
|
||||||
|
> — Pat Helland
|
||||||
|
|
||||||
|
#personal #research`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'API v3 migration',
|
||||||
|
color: COLORS.blue,
|
||||||
|
body: `### Endpoints to migrate
|
||||||
|
|
||||||
|
| v2 | v3 | Status |
|
||||||
|
|----|-----|--------|
|
||||||
|
| GET /users | GET /api/v3/users | done |
|
||||||
|
| POST /auth | POST /api/v3/auth/token | done |
|
||||||
|
| GET /cards | GET /api/v3/cards | in progress |
|
||||||
|
| WS /stream | WS /api/v3/events | planned |
|
||||||
|
|
||||||
|
### Breaking changes
|
||||||
|
- Auth header: \`Bearer\` instead of \`X-Token\`
|
||||||
|
- Pagination: cursor-based instead of offset
|
||||||
|
- Error format: \`{"error": {"code": ..., "message": ...}}\`
|
||||||
|
|
||||||
|
[[Architecture RFC]]
|
||||||
|
|
||||||
|
#project/api #migration`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Infra cost review',
|
||||||
|
color: COLORS.orange,
|
||||||
|
body: `### Monthly breakdown (April)
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
Compute (k8s) €340 ████████░░ 68%
|
||||||
|
Storage (S3) €85 ██░░░░░░░░ 17%
|
||||||
|
Database (RDS) €52 █░░░░░░░░░ 10%
|
||||||
|
CDN + DNS €18 ░░░░░░░░░░ 4%
|
||||||
|
Monitoring €8 ░░░░░░░░░░ 2%
|
||||||
|
─────────────────────────────────────
|
||||||
|
Total €503
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
Budget: €600 → **€97 headroom**
|
||||||
|
|
||||||
|
Next action: evaluate spot instances for batch workers.
|
||||||
|
|
||||||
|
#ops #infra #finance`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Birthday Alice',
|
||||||
|
color: COLORS.pink,
|
||||||
|
due_date: '2026-06-12',
|
||||||
|
body: `#geburtstag`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Onboarding: new developer',
|
||||||
|
color: COLORS.green,
|
||||||
|
body: `## First week checklist
|
||||||
|
|
||||||
|
- [ ] Laptop + accounts provisioned
|
||||||
|
- [ ] SSH key added to Gitea
|
||||||
|
- [ ] Access to staging cluster
|
||||||
|
- [x] Slack channels joined
|
||||||
|
- [x] 1:1 with team lead
|
||||||
|
- [ ] Read [[Architecture RFC]]
|
||||||
|
- [ ] First PR: fix a good-first-issue
|
||||||
|
|
||||||
|
### Contacts
|
||||||
|
| Role | Person |
|
||||||
|
|------|--------|
|
||||||
|
| Buddy | @carol |
|
||||||
|
| Team lead | @alice |
|
||||||
|
| Infra | @dave |
|
||||||
|
|
||||||
|
#onboarding #ops`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Competitive analysis',
|
||||||
|
color: COLORS.purple,
|
||||||
|
body: `### Landscape
|
||||||
|
|
||||||
|
| Product | Self-hosted | Offline | E2E encryption |
|
||||||
|
|---------|:-----------:|:-------:|:--------------:|
|
||||||
|
| Notion | no | no | no |
|
||||||
|
| Obsidian| yes | yes | paid |
|
||||||
|
| Logseq | yes | yes | no |
|
||||||
|
| **hyprcrd** | **yes** | **yes** | **yes** |
|
||||||
|
|
||||||
|
### Differentiators
|
||||||
|
- Tag extraction from body (zero-friction categorization)
|
||||||
|
- Relay model: multi-tenant sync without cloud dependency
|
||||||
|
- Plugin system: extensible without forking
|
||||||
|
- CalDAV native (not just ical export)
|
||||||
|
|
||||||
|
#research #strategy`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Security audit notes',
|
||||||
|
color: COLORS.red,
|
||||||
|
body: `## Audit 2026-Q1 findings
|
||||||
|
|
||||||
|
### Critical
|
||||||
|
~~1. Session token in URL parameter~~ → fixed in v1.2.1
|
||||||
|
|
||||||
|
### High
|
||||||
|
2. CSRF token not rotated on privilege change → [[Auth timeout on mobile]]
|
||||||
|
|
||||||
|
### Medium
|
||||||
|
3. Upload directory allows .svg (XSS vector)
|
||||||
|
4. Rate limiter not applied to /api/export
|
||||||
|
|
||||||
|
### Low
|
||||||
|
5. Server version exposed in HTTP headers
|
||||||
|
6. No CSP header on main app
|
||||||
|
|
||||||
|
> Next audit scheduled: 2026-07-15
|
||||||
|
|
||||||
|
#security #audit #urgent`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Design system tokens',
|
||||||
|
color: COLORS.purple,
|
||||||
|
body: `### Color palette
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
--t-bg: #0f172a base background
|
||||||
|
--t-surface: #1e293b card/panel surface
|
||||||
|
--t-border: #334155 borders
|
||||||
|
--t-text: #94a3b8 body text
|
||||||
|
--t-bright: #e2e8f0 headlines
|
||||||
|
--t-accent: #38bdf8 primary accent
|
||||||
|
--t-accent2: #a78bfa secondary accent
|
||||||
|
--t-danger: #fb7185 errors/destructive
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Typography
|
||||||
|
- **UI:** Silkscreen (pixel font)
|
||||||
|
- **Content:** JetBrains Mono
|
||||||
|
- **Sizes:** 9px labels, 11px body, 13px headings
|
||||||
|
|
||||||
|
### Radius scale
|
||||||
|
\`--ui-radius-card\`, \`--ui-radius-modal\`, \`--ui-radius-input\`
|
||||||
|
|
||||||
|
All overridable per theme preset.
|
||||||
|
|
||||||
|
#design #frontend`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Customer feedback digest',
|
||||||
|
color: COLORS.yellow,
|
||||||
|
body: `### Week of 2026-04-07
|
||||||
|
|
||||||
|
**Positive:**
|
||||||
|
- "Search is blazing fast" — 3 mentions
|
||||||
|
- "CalDAV sync just works" — 2 mentions
|
||||||
|
- "Love the keyboard shortcuts" — @user42
|
||||||
|
|
||||||
|
**Requests:**
|
||||||
|
- Mobile app (→ Electron PWA?)
|
||||||
|
- Shared boards without relay (→ collab-buckets?)
|
||||||
|
- Dark mode for relay UI (→ already shipped!)
|
||||||
|
|
||||||
|
**Issues:**
|
||||||
|
- Image upload fails >5MB on shared hosting
|
||||||
|
- Calendar popup overlaps on 13" screens
|
||||||
|
|
||||||
|
#feedback #product`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Database schema notes',
|
||||||
|
color: COLORS.slate,
|
||||||
|
body: `### Core tables
|
||||||
|
|
||||||
|
\`\`\`sql
|
||||||
|
cards (id, title, body, color, tags,
|
||||||
|
bucket_id, parent_id, position,
|
||||||
|
due_date, relay_uid,
|
||||||
|
created_at, updated_at)
|
||||||
|
|
||||||
|
buckets (id, name, color, parent_id,
|
||||||
|
collab_uid, position)
|
||||||
|
|
||||||
|
settings (key, value)
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Indexes
|
||||||
|
- \`cards.bucket_id\` — bucket filter
|
||||||
|
- \`cards.parent_id\` — group lookup
|
||||||
|
- \`buckets.collab_uid UNIQUE\` — relay sync
|
||||||
|
|
||||||
|
SQLite WAL mode for concurrent reads.
|
||||||
|
|
||||||
|
#architecture #database`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Release notes v1.3',
|
||||||
|
color: COLORS.green,
|
||||||
|
body: `## What's new
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- **Tooltip system** — custom styled tooltips replace all native titles
|
||||||
|
- **Asset picker** — thumbnail grid in the editor toolbar
|
||||||
|
- **Super-bucket split** — click activates, caret opens dropdown
|
||||||
|
- **Watch plugin** — analog clock overlay with SVG watchface
|
||||||
|
- **Card peek** — hover preview with rendered markdown
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- Shortcuts no longer fire when typing in inputs
|
||||||
|
- Gallery opens even if API fetch fails
|
||||||
|
- Relay poll suppressed during user actions
|
||||||
|
- Find/replace integrated into search overlay
|
||||||
|
|
||||||
|
### Breaking
|
||||||
|
- All \`title=""\` attributes migrated to \`data-tip=""\`
|
||||||
|
|
||||||
|
#release #changelog`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '2026-04-12',
|
||||||
|
color: COLORS.slate,
|
||||||
|
body: `## Journal
|
||||||
|
|
||||||
|
Started the day reviewing PRs. Merged the tooltip system and the asset picker.
|
||||||
|
|
||||||
|
Spent the afternoon on the Electron build — static PHP binary works but the WAL/SHM files caused issues. Fixed by deleting stale lock files.
|
||||||
|
|
||||||
|
Had a good session on the watch plugin. The wobble animation on the second hand is a nice touch.
|
||||||
|
|
||||||
|
**Tomorrow:**
|
||||||
|
- [ ] Test relay collab-buckets with two instances
|
||||||
|
- [ ] Write release notes
|
||||||
|
- [ ] Screenshot for website
|
||||||
|
|
||||||
|
#journal`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Backup strategy',
|
||||||
|
color: COLORS.orange,
|
||||||
|
body: `### Current
|
||||||
|
|
||||||
|
Daily cron:
|
||||||
|
\`\`\`bash
|
||||||
|
#!/bin/bash
|
||||||
|
DATE=$(date +%Y-%m-%d)
|
||||||
|
cp data/hyprcrd.db "/backup/hyprcrd-$DATE.db"
|
||||||
|
tar czf "/backup/uploads-$DATE.tar.gz" public/uploads/
|
||||||
|
find /backup -mtime +30 -delete
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Planned
|
||||||
|
- [ ] Automated export ZIP via API
|
||||||
|
- [ ] Offsite copy to S3-compatible storage
|
||||||
|
- [ ] Test restore monthly
|
||||||
|
- [ ] Document in runbook
|
||||||
|
|
||||||
|
> WAL checkpoint before copy:
|
||||||
|
> \`sqlite3 data/hyprcrd.db "PRAGMA wal_checkpoint(TRUNCATE);"\`
|
||||||
|
|
||||||
|
#ops #backup #infra`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Plugin ideas',
|
||||||
|
color: COLORS.pink,
|
||||||
|
body: `### Brainstorm
|
||||||
|
|
||||||
|
- **Kanban columns** — cards as swimlanes per bucket
|
||||||
|
- **Time tracking** — start/stop timer per card, weekly report
|
||||||
|
- **AI summary** — auto-generate card summaries with local LLM
|
||||||
|
- **RSS reader** — fetch feeds into cards
|
||||||
|
- **Webhook triggers** — POST on card create/update/delete
|
||||||
|
- **PDF export** — render card as styled PDF
|
||||||
|
- **Voice memo** — record audio, attach to card, transcribe
|
||||||
|
|
||||||
|
### Criteria for inclusion
|
||||||
|
1. Does it help *find* information?
|
||||||
|
2. Can it be a drop-in plugin (no core changes)?
|
||||||
|
3. Is it useful without an internet connection?
|
||||||
|
|
||||||
|
#idea #plugins #brainstorm`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Monitoring setup',
|
||||||
|
color: COLORS.green,
|
||||||
|
body: `### Stack
|
||||||
|
|
||||||
|
- **Uptime:** UptimeRobot (free tier, 5min interval)
|
||||||
|
- **Errors:** Sentry self-hosted → \`sentry.internal\`
|
||||||
|
- **Metrics:** Grafana + Prometheus
|
||||||
|
- **Logs:** Loki + promtail on each node
|
||||||
|
|
||||||
|
### Alerts
|
||||||
|
| Metric | Threshold | Channel |
|
||||||
|
|--------|-----------|---------|
|
||||||
|
| HTTP 5xx rate | >1% for 5min | Slack #ops-alerts |
|
||||||
|
| Response time p95 | >2s for 10min | Slack #ops-alerts |
|
||||||
|
| Disk usage | >85% | Email + Slack |
|
||||||
|
| SSL expiry | <14 days | Email |
|
||||||
|
|
||||||
|
Dashboard: \`grafana.internal/d/hyprcrd\`
|
||||||
|
|
||||||
|
#ops #monitoring #infra`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
async function generate() {
|
||||||
|
const bucket = await api('api/buckets', { method: 'POST', body: { name: 'Project Alpha' } });
|
||||||
|
const bid = bucket?.id;
|
||||||
|
if (!bid) return false;
|
||||||
|
await api(`api/buckets/${bid}`, { method: 'PATCH', body: { color: COLORS.blue } });
|
||||||
|
|
||||||
|
for (const c of CARDS) {
|
||||||
|
await api('api/cards', {
|
||||||
|
method: 'POST',
|
||||||
|
body: { title: c.title, body: c.body, color: c.color, bucket_id: bid, due_date: c.due_date || null },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
const footer = document.getElementById('main-footer');
|
||||||
|
if (!footer) return;
|
||||||
|
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.className = 'flex items-center gap-1.5 px-2.5 py-1 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors';
|
||||||
|
btn.innerHTML = '<i class="ph ph-flask text-sm"></i><span class="font-pixel text-[10px]">Demo</span>';
|
||||||
|
btn.dataset.tip = 'Create example bucket with demo cards';
|
||||||
|
|
||||||
|
btn.addEventListener('click', async () => {
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.innerHTML = '<i class="ph ph-spinner text-sm"></i><span class="font-pixel text-[10px]">creating...</span>';
|
||||||
|
const ok = await generate();
|
||||||
|
if (ok) {
|
||||||
|
btn.innerHTML = '<i class="ph ph-check text-sm"></i><span class="font-pixel text-[10px]">Done</span>';
|
||||||
|
await window._reloadAll?.();
|
||||||
|
} else {
|
||||||
|
btn.innerHTML = '<i class="ph ph-x text-sm"></i><span class="font-pixel text-[10px]">Failed</span>';
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
btn.innerHTML = '<i class="ph ph-flask text-sm"></i><span class="font-pixel text-[10px]">Demo</span>';
|
||||||
|
btn.disabled = false;
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
const firstChild = footer.querySelector('div');
|
||||||
|
if (firstChild) firstChild.appendChild(btn);
|
||||||
|
|
||||||
|
h?.registerPlugin({ name: 'example-content', version: '1.0.0' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
|
} else {
|
||||||
|
init();
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"name": "example-content",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "One-click demo content for screenshots and first-time exploration.",
|
||||||
|
"frontend": {
|
||||||
|
"js": ["example-content.js"]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,702 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>hyprcrd</title>
|
||||||
|
<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>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 367 KiB |
Executable
+6
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Ad hoc server for the hyprcrd website
|
||||||
|
PORT=${1:-3000}
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
echo " hyprcrd website: http://localhost:$PORT"
|
||||||
|
python3 -m http.server "$PORT"
|
||||||
Reference in new issue
Block a user