Files
hyprcrd/support-relay/ui/style.css
T
thandClaude Opus 4.7 7bce6a3a36 v2.0.12 — support channel: relay + hyprcrd plugin
Central support desk for the Oumta project. Tickets arrive through a
self-hostable support-relay (public form, SMTP out), hyprcrd pulls
them in as cards, replies go back out through the relay, and KB
fragments are plain hyprcrd cards so the board + search + tags
machinery handles them for free.

support-relay/ (standalone PHP/SQLite service)
- Public form + submit endpoint with IP rate-limit, honeypot, and an
  email-matched reply path so users can follow up by typing their
  OUMTA-n issue id — mismatched email falls back to a fresh ticket
  so a hostile submitter can't hijack someone else's thread.
- One-shot setup wizard configures branding, issue-id prefix, public
  URL, mail footer template, SMTP creds (stored AES-256-GCM at rest),
  and generates the X-Support-Key API token shown exactly once.
- Hyprcrd-facing API: GET /api/hyprcrd/tickets?since=<iso>, POST
  …/reply (sends via SMTP, flips status to answered), PATCH
  …/status, DELETE, GET /ping, plus GET/POST /api/hyprcrd/settings
  exposing brand/public_url/mail_footer for post-setup edits.
- Minimal dependency-free SMTP client covering STARTTLS/TLS/none,
  AUTH LOGIN, RFC 2047 subject encoding, dot-stuffing. Extended to
  emit multipart/alternative (plain+HTML) + multipart/related so
  outbound mails carry the branded logo via Content-ID embedding;
  plain-text fallback stays intact for older clients.
- Mail footer template supports {brand}, {issue_id}, {public_url}
  placeholders. Lines with an empty placeholder are dropped, so the
  "Reply via …" line vanishes cleanly when no public URL is set.
- Favicon on all public pages. Logo (SVG by default, PNG preferred
  when both exist) picked up from logo2026col.* at send time.
- Docker: Dockerfile + docker-compose.yml for a stand-alone test
  container, plus a support-relay service in the main compose
  (profile "support") so hyprcrd can reach it at http://support-relay
  without host-networking shenanigans.

public/plugins/support (hyprcrd client)
- Full-screen Support overlay: ticket list (status-filterable),
  active ticket thread, reply composer with KB-fragment search +
  click-to-insert, "Save as fragment" button, "Close ticket" shortcut.
- KB fragments are regular cards in a configurable bucket. The
  composer just adds a fast search-and-insert path on top.
- Sync-tick hook auto-polls the relay alongside the existing CalDAV
  + relay-mail cycle.
- Settings tab in the main overlay: relay URL, API key, inbox / KB
  bucket pickers, connection test, and a "Relay config" subsection
  that round-trips to /api/hyprcrd/settings so the mail footer and
  branding can be edited without touching the relay's DB.
- Local cache tables support_tickets + support_replies mirror the
  relay's authoritative data keyed by relay_id.

Tooling
- dev.sh now opts into both relay + support profiles.
- dev-down.sh wipes support-relay volumes and bind-mount data for a
  clean re-setup. Hyprcrd + relay data is preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 10:41:19 +02:00

119 lines
3.5 KiB
CSS

/* support-relay public + setup styles. Minimalist, self-contained —
* no font loading, no external deps, passes through whatever the
* system UI stack looks like. Dark + light friendly. */
:root {
--bg: #0f1726;
--surface: #1a2438;
--border: #2a3650;
--text: #cbd5e1;
--bright: #f1f5f9;
--dim: #64748b;
--accent: #38bdf8;
--ok: #4ade80;
--danger: #fb7185;
--radius: 6px;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
font-size: 15px;
line-height: 1.55;
}
main {
max-width: 560px;
margin: 0 auto;
padding: 48px 20px 64px;
}
h1 { color: var(--bright); font-size: 26px; margin-bottom: 6px; }
h2 { color: var(--bright); font-size: 18px; margin: 20px 0 8px; }
.lede { color: var(--dim); margin-bottom: 28px; font-size: 14px; }
form { display: flex; flex-direction: column; gap: 16px; }
.field { display: flex; flex-direction: column; gap: 6px; }
label { font-size: 12px; color: var(--dim); text-transform: uppercase; letter-spacing: 0.5px; font-weight: 600; }
input, textarea, select {
background: var(--surface);
border: 1px solid var(--border);
color: var(--bright);
padding: 10px 12px;
border-radius: var(--radius);
font: inherit;
font-size: 14px;
outline: none;
transition: border-color .15s;
}
input:focus, textarea:focus, select:focus { border-color: var(--accent); }
textarea { resize: vertical; min-height: 140px; font-family: inherit; }
.hint { font-size: 12px; color: var(--dim); }
button[type="submit"], .btn {
appearance: none;
background: var(--accent);
color: #001526;
border: none;
font-size: 14px;
font-weight: 600;
padding: 12px 20px;
border-radius: var(--radius);
cursor: pointer;
transition: filter .15s, transform .05s;
}
button[type="submit"]:hover, .btn:hover { filter: brightness(1.1); }
button[type="submit"]:active, .btn:active { transform: translateY(1px); }
.honey {
position: absolute;
left: -9999px;
width: 1px; height: 1px;
overflow: hidden;
opacity: 0;
}
.banner {
padding: 12px 16px;
border-radius: var(--radius);
background: var(--surface);
border: 1px solid var(--border);
margin-bottom: 18px;
font-size: 13px;
}
.banner.ok { border-color: var(--ok); color: var(--ok); }
.banner.err { border-color: var(--danger); color: var(--danger); }
.issue-id {
font-family: ui-monospace, 'JetBrains Mono', monospace;
background: rgba(56, 189, 248, 0.12);
padding: 2px 6px;
border-radius: 3px;
color: var(--accent);
}
a { color: var(--accent); }
/* Status / reply cards */
.ticket-header { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px 16px; margin-bottom: 16px; }
.ticket-header h2 { margin: 0 0 4px; font-size: 15px; }
.ticket-meta { color: var(--dim); font-size: 12px; }
.reply {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 14px 16px;
margin-bottom: 10px;
white-space: pre-wrap;
}
.reply-time { font-size: 11px; color: var(--dim); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px; }
.setup-note {
background: rgba(74, 222, 128, 0.12);
border: 1px solid var(--ok);
color: var(--ok);
padding: 14px 16px;
border-radius: var(--radius);
font-family: ui-monospace, monospace;
font-size: 13px;
word-break: break-all;
}