diff --git a/frontend/src/App.css b/frontend/src/App.css new file mode 100644 index 0000000..e95632d --- /dev/null +++ b/frontend/src/App.css @@ -0,0 +1,891 @@ +@font-face { + font-family: 'DM Sans'; + font-weight: 400; + font-style: normal; + font-display: swap; + src: url('/fonts/DM-Sans-400.ttf') format('truetype'); +} +@font-face { + font-family: 'DM Sans'; + font-weight: 500; + font-style: normal; + font-display: swap; + src: url('/fonts/DM-Sans-500.ttf') format('truetype'); +} +@font-face { + font-family: 'DM Sans'; + font-weight: 700; + font-style: normal; + font-display: swap; + src: url('/fonts/DM-Sans-700.ttf') format('truetype'); +} +@font-face { + font-family: 'JetBrains Mono'; + font-weight: 400; + font-style: normal; + font-display: swap; + src: url('/fonts/JetBrains-Mono-400.ttf') format('truetype'); +} +@font-face { + font-family: 'JetBrains Mono'; + font-weight: 700; + font-style: normal; + font-display: swap; + src: url('/fonts/JetBrains-Mono-700.ttf') format('truetype'); +} + +:root { + --bg: #0c0c0c; + --bg-raised: #141412; + --bg-input: #1a1a17; + --border: #2a2a25; + --border-strong: #3a3a33; + --text: #e8e4de; + --text-dim: #8a867e; + --text-faint: #5a5750; + --accent: #ff4f6d; + --accent-dim: #cc3f57; + --accent-bg: #ff4f6d08; + --error: #ff5f56; + --error-bg: #ff5f5608; + --serif: 'Instrument Serif', Georgia, serif; + --sans: 'DM Sans', system-ui, sans-serif; + --mono: 'JetBrains Mono', 'Courier New', monospace; +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +::selection { + background: var(--accent); + color: var(--bg); +} + +body { + font-family: var(--sans); + font-size: 14px; + background: var(--bg); + color: var(--text); + line-height: 1.65; + -webkit-font-smoothing: antialiased; +} + +/* ── layout ── */ + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 3rem 2rem; +} + +h1 { + font-family: var(--sans); + font-size: 3.5rem; + font-weight: 400; + font-style: normal; + color: var(--text); + letter-spacing: -0.03em; + margin-bottom: 2.5rem; + line-height: 1; +} + +h1::after { + content: ''; + display: block; + width: 100%; + height: 2px; + background: var(--accent); + margin-top: 1rem; +} + +h2 { + font-family: var(--sans); + font-size: 1.5rem; + font-weight: 400; + color: var(--text); + letter-spacing: -0.01em; + margin-bottom: 1rem; + line-height: 1.2; +} + +.intro-block { + display: flex; + gap: 2rem; + margin-bottom: 2rem; +} + +.intro { + color: var(--text); + font-size: 14px; + line-height: 1.7; + flex: 1; + padding-right: 2rem; + border-right: 1px solid var(--border); +} + +.intro-right { + flex: 1; + display: flex; + flex-direction: column; + gap: 1rem; + padding-left: 2rem; +} + +.instructions { + color: var(--text); + font-size: 14px; + text-align: right; + line-height: 1.7; +} + +.instructions p { + margin-bottom: 0.25rem; +} + +.notes { + color: var(--text-dim); + font-size: 12px; + text-align: right; + line-height: 1.7; + border-top: 1px solid var(--border); + padding-top: 1rem; +} + +.notes p { + margin-bottom: 0.25rem; +} + +a { + color: #ef4444; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +a:visited { + color: #b91c1c; +} + +/* ── topbar ── */ + +.topbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: 0.75rem 0; + border-bottom: 2px solid var(--border); + margin-bottom: 2rem; +} + +.topbar span { + font-size: 15px; + color: var(--text-dim); + letter-spacing: 0.02em; + text-transform: uppercase; +} + +.topbar code { + font-family: var(--mono); + font-size: 15px; + color: var(--accent); + text-transform: none; +} + +/* ── two-column layout ── */ + +.layout { + display: flex; + gap: 2rem; + align-items: flex-start; +} + +.sidebar { + width: 260px; + flex-shrink: 0; + border: 1px solid var(--border); + border-top: 3px solid var(--accent); + padding: 1.25rem; + background: var(--bg-raised); +} + +.main-panel { + flex: 1; + min-width: 0; +} + +/* ── sections ── */ + +section { + margin-bottom: 1.5rem; + padding: 1.25rem; + border: 1px solid var(--border); + border-left: 3px solid var(--border-strong); + background: var(--bg-raised); + transition: border-left-color 0.2s ease; +} + +section:hover { + border-left-color: var(--accent); +} + +section:last-child { + margin-bottom: 0; +} + +/* ── text helpers ── */ + +.muted { + color: var(--text-faint); + font-size: 12px; + letter-spacing: 0.03em; + text-transform: uppercase; +} + +/* ── form fields ── */ + +.field { + display: flex; + gap: 0; +} + +input[type="text"] { + flex: 1; + padding: 0.6rem 0.85rem; + border: 1px solid var(--border); + border-right: none; + background: var(--bg-input); + color: var(--text); + font-family: var(--mono); + font-size: 13px; + transition: border-color 0.15s ease; +} + +input[type="text"]::placeholder { + color: var(--text-dim); + font-style: italic; + font-family: var(--sans); + font-size: 13px; +} + +input[type="text"]:focus { + outline: none; + border-color: var(--accent); + background: var(--bg); +} + +input[type="text"].input-invalid { + border-color: var(--error); + background: var(--error-bg); +} + +.field-hint { + color: var(--error); + font-size: 11px; + margin-top: 0.35rem; + letter-spacing: 0.02em; +} + +button { + padding: 0.6rem 1.2rem; + border: 1px solid var(--border); + background: var(--bg-input); + color: var(--text); + cursor: pointer; + font-family: var(--sans); + font-size: 13px; + font-weight: 700; + letter-spacing: 0.06em; + text-transform: uppercase; + white-space: nowrap; + transition: all 0.15s ease; +} + +button:hover:not(:disabled) { + background: var(--accent); + color: var(--bg); + border-color: var(--accent); +} + +button:disabled { + opacity: 0.35; + cursor: not-allowed; +} + +/* ── topbar logout button ── */ + +.topbar button { + font-size: 11px; + padding: 0.4rem 0.8rem; + border-color: var(--border); +} + +/* ── pre / code output ── */ + +pre { + background: var(--bg); + border: 1px solid var(--border); + border-left: 3px solid var(--text-faint); + padding: 0.85rem 1rem; + overflow-x: auto; + font-family: var(--mono); + font-size: 12px; + margin-top: 0.75rem; + white-space: pre-wrap; + word-break: break-all; + line-height: 1.7; + color: var(--text-dim); +} + +/* ── messages ── */ + +.error { + color: var(--error); + background: var(--error-bg); + border: 1px solid var(--error); + border-left: 3px solid var(--error); + padding: 0.6rem 0.85rem; + margin-top: 0.75rem; + font-size: 12px; +} + +.auth-warning { + color: #b5a13b; + background: #b5a13b18; + border: none; + border-left: 3px solid #b5a13b; + padding: 0.6rem 0.85rem; + margin-top: 0.75rem; + font-size: 12px; + line-height: 1.5; +} + +.auth-warning a { + color: #ef4444; +} + +.auth-warning-details { + margin-top: 0.5rem; + margin-bottom: 0; + margin-left: 0; + border: 1px solid #b5a13b44; + background: #131006; + padding: 0.6rem 0.85rem; + border-radius: 4px; +} + +.auth-warning code { + font-size: 11px; + background: #b5a13b15; + padding: 0.1rem 0.3rem; + border-radius: 2px; +} + +.success { + color: #4caf50; + border-color: #4caf50; + border-left: 3px solid #4caf50; + background: #4caf5008; +} + +/* ── login form ── */ + +form { + margin: 0; +} + +/* ── vouch list ── */ + +.vouch-list { + list-style: none; + margin-top: 0.75rem; + max-height: 60vh; + overflow-y: auto; +} + +.vouch-list li { + display: flex; + justify-content: space-between; + align-items: baseline; + padding: 0.5rem 0; + border-bottom: 1px solid var(--border); + gap: 0.75rem; +} + +.vouch-list li:last-child { + border-bottom: none; +} + + +.vouch-handle { + font-size: 15px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; +} + +.vouch-date { + color: var(--text-dim); + font-size: 11px; + flex-shrink: 0; + font-variant-numeric: tabular-nums; +} + +.vouch-delete { + background: none; + border: none; + cursor: pointer; + padding: 0 0.25rem; + font-size: 14px; + opacity: 0.5; + flex-shrink: 0; +} + +.vouch-delete:hover { + opacity: 1; +} + +/* ── right sidebar (remote vouches) ── */ + +.sidebar-right { + width: 240px; + flex-shrink: 0; + border: 1px solid var(--border); + border-top: 3px solid var(--text-dim); + padding: 1.25rem; + background: var(--bg-raised); +} + +.vouch-mutual { + color: var(--text-faint); + font-size: 10px; + letter-spacing: 0.06em; + text-transform: uppercase; + flex-shrink: 0; +} + + +/* ── pagination ── */ + +.vouch-count { + font-weight: 400; + font-size: 0.85em; + color: var(--text-dim); +} + +.load-more { + width: 100%; + margin-top: 0.5rem; + font-size: 11px; + padding: 0.4rem 0.8rem; +} + +/* ── footer ── */ + +.footer { + margin-top: 3rem; + padding-top: 1rem; + border-top: 1px solid var(--border); + color: var(--text-dim); + font-size: 12px; + text-align: center; +} + +.design-link { + text-align: center; + margin-top: 3rem; + font-size: 12px; + letter-spacing: 0.03em; +} + +/* ── design decisions ── */ + +.design-decisions { + margin-top: 1rem; +} + +.design-decisions-list { + border: 1px solid var(--border); + border-top: 3px solid var(--accent); + background: var(--bg-raised); +} + +.design-decision-item { + padding: 1rem 1.25rem; + border-bottom: 1px solid var(--border); +} + +.design-decision-item:last-child { + border-bottom: none; +} + +.design-decision-item h3 { + font-family: var(--sans); + font-size: 14px; + font-weight: 700; + color: var(--text); + margin-bottom: 0.35rem; +} + +.design-decision-item p { + font-size: 13px; + line-height: 1.7; + color: var(--text-dim); +} + +/* ── bot repositories ── */ + +.bot-repos-section { + margin-top: 2rem; + border-top: 3px solid var(--accent); + border-left: 1px solid var(--border); +} + +.bot-repos-desc { + color: var(--text-dim); + font-size: 12px; + margin-bottom: 0.75rem; + line-height: 1.5; +} + +.bot-repos-list { + list-style: none; + margin: 0; + padding: 0; +} + +.bot-repo-item { + padding: 0.6rem 0; + border-bottom: 1px solid var(--border); +} + +.bot-repo-item:last-child { + border-bottom: none; +} + +.bot-repo-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 0.5rem; +} + +.bot-repo-info { + display: flex; + flex-direction: column; + min-width: 0; + flex: 1; +} + +.bot-repo-name { + font-family: var(--mono); + font-size: 13px; + font-weight: 700; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: inline-flex; + align-items: center; + gap: 4px; +} + +.fork-icon { + opacity: 0.6; + flex-shrink: 0; +} + +.bot-repo-desc { + color: var(--text-dim); + font-size: 11px; + line-height: 1.4; + margin-top: 0.15rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.bot-repo-btn { + font-size: 11px; + padding: 0.3rem 0.7rem; + flex-shrink: 0; +} + +.bot-repo-btn-add:hover:not(:disabled) { + background: #4caf50; + border-color: #4caf50; + color: var(--bg); +} + +.bot-repo-btn-remove:hover:not(:disabled) { + background: var(--error); + border-color: var(--error); + color: var(--bg); +} + +/* ── maintainers ── */ + +.maintainers { + margin-top: 0.4rem; + display: flex; + align-items: flex-start; + gap: 0.4rem; + flex-wrap: wrap; +} + +.maintainers-label { + color: var(--text-faint); + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.04em; + line-height: 22px; + flex-shrink: 0; +} + +.maintainers-list { + display: flex; + align-items: center; + gap: 0.35rem; + flex-wrap: wrap; +} + +.maintainer-chip { + display: inline-flex; + align-items: center; + gap: 0.25rem; + background: var(--bg); + border: 1px solid var(--border); + padding: 0.15rem 0.4rem; + font-size: 11px; + line-height: 1; +} + +.maintainer-chip a { + font-size: 11px; +} + +.maintainer-avatar { + width: 16px; + height: 16px; + border-radius: 50%; + flex-shrink: 0; +} + +.maintainer-remove { + background: none; + border: none; + color: var(--text-faint); + font-size: 10px; + padding: 0 0.15rem; + cursor: pointer; + line-height: 1; + opacity: 0.5; +} + +.maintainer-remove:hover { + opacity: 1; + color: var(--error); + background: none; +} + +.maintainer-add-btn { + background: none; + border: 1px solid var(--border); + color: var(--text-dim); + font-size: 13px; + width: 22px; + height: 22px; + padding: 0; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + line-height: 1; +} + +.maintainer-add-btn:hover:not(:disabled) { + background: var(--accent); + border-color: var(--accent); + color: var(--bg); +} + +.maintainer-input-wrapper { + position: relative; +} + +.maintainer-input { + width: 140px !important; + font-size: 11px !important; + padding: 0.2rem 0.4rem !important; + border: 1px solid var(--border) !important; + border-right: 1px solid var(--border) !important; + background: var(--bg-input); + color: var(--text); + font-family: var(--mono); +} + +.maintainer-input:focus { + border-color: var(--accent) !important; + outline: none; +} + +.maintainer-dropdown { + position: absolute; + top: 100%; + left: 0; + right: 0; + margin: 0; + padding: 0; + list-style: none; + background: var(--bg); + border: 1px solid var(--border); + border-top: none; + z-index: 20; + max-height: 160px; + overflow-y: auto; + min-width: 180px; +} + +.maintainer-dropdown li { + display: flex; + align-items: center; + gap: 0.35rem; + padding: 0.3rem 0.5rem; + cursor: pointer; + font-size: 11px; +} + +.maintainer-dropdown li:hover, +.maintainer-dd-active { + background: var(--border); +} + +.maintainer-dd-avatar { + width: 16px; + height: 16px; + border-radius: 50%; + flex-shrink: 0; +} + +/* ── responsive ── */ + +@media (max-width: 980px) { + .container { + padding: 2rem 1rem; + } + + h1 { + font-size: 2.5rem; + } + + .layout { + flex-direction: column; + } + + .sidebar { + width: 100%; + } + + .sidebar-right { + width: 100%; + } + + .main-panel { + width: 100%; + } + + .intro-block { + flex-direction: column; + } + + .intro { + border-right: none; + border-bottom: 1px solid var(--border); + padding-right: 0; + padding-bottom: 1rem; + } + + .intro-right { + padding-left: 0; + } + + .instructions { + text-align: left; + } + + .design-decisions-list { + width: 100%; + } +} + +/* Typeahead */ +.handle-input-wrapper { + position: relative; + flex: 1; +} + +.handle-input-wrapper input { + width: 100%; + box-sizing: border-box; +} + +.typeahead-dropdown { + position: absolute; + top: 100%; + left: 0; + right: 0; + margin: 0; + padding: 0; + list-style: none; + background: var(--bg); + border: 1px solid var(--border); + border-top: none; + z-index: 10; + max-height: 280px; + overflow-y: auto; +} + +.typeahead-dropdown li { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.4rem 0.6rem; + cursor: pointer; +} + +.typeahead-dropdown li.typeahead-active { + background: var(--border); +} + +.typeahead-avatar { + width: 24px; + height: 24px; + border-radius: 50%; + flex-shrink: 0; +} + +.typeahead-info { + display: flex; + flex-direction: column; + min-width: 0; +} + +.typeahead-name { + font-size: 0.85rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.typeahead-handle { + font-size: 0.75rem; + opacity: 0.6; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +}