- Remove web/dist/ from git tracking (build output) - Add web/dist/ to .gitignore - Add Node.js web-builder stage to Dockerfile to compile SPA at build time - Update REPO_POLICIES.md from upstream sneak/prompts (build artifacts policy)
This commit is contained in:
1 parent
78d657111b
commit
4b2888cb90
6 files changed
+29
-484
No files matched your search
@@ -21,6 +21,7 @@ node_modules/
|
||||
*.key
|
||||
|
||||
# Build artifacts
|
||||
web/dist/
|
||||
/neoircd
|
||||
/bin/
|
||||
*.exe
|
||||
|
||||
+12
@@ -1,3 +1,13 @@
|
||||
# Web build stage — compile SPA from source
|
||||
# node:22-alpine, 2026-03-09
|
||||
FROM node@sha256:8094c002d08262dba12645a3b4a15cd6cd627d30bc782f53229a2ec13ee22a00 AS web-builder
|
||||
WORKDIR /web
|
||||
COPY web/package.json web/package-lock.json ./
|
||||
RUN npm ci
|
||||
COPY web/src/ src/
|
||||
COPY web/build.sh build.sh
|
||||
RUN sh build.sh
|
||||
|
||||
# Lint stage — fast feedback on formatting and lint issues
|
||||
# golangci/golangci-lint:v2.1.6, 2026-03-02
|
||||
FROM golangci/golangci-lint@sha256:568ee1c1c53493575fa9494e280e579ac9ca865787bafe4df3023ae59ecf299b AS lint
|
||||
@@ -5,6 +15,7 @@ WORKDIR /src
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
COPY --from=web-builder /web/dist/ web/dist/
|
||||
RUN make fmt-check
|
||||
RUN make lint
|
||||
|
||||
@@ -21,6 +32,7 @@ COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
COPY --from=web-builder /web/dist/ web/dist/
|
||||
|
||||
RUN make test
|
||||
|
||||
|
||||
+16
-3
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Repository Policies
|
||||
last_modified: 2026-02-22
|
||||
last_modified: 2026-03-09
|
||||
---
|
||||
|
||||
This document covers repository structure, tooling, and workflow standards. Code
|
||||
@@ -98,6 +98,13 @@ style conventions are in separate documents:
|
||||
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.gitignore` when setting up
|
||||
a new repo.
|
||||
|
||||
- **No build artifacts in version control.** Code-derived data (compiled
|
||||
bundles, minified output, generated assets) must never be committed to the
|
||||
repository if it can be avoided. The build process (e.g. Dockerfile, Makefile)
|
||||
should generate these at build time. Notable exception: Go protobuf generated
|
||||
files (`.pb.go`) ARE committed because repos need to work with `go get`, which
|
||||
downloads code but does not execute code generation.
|
||||
|
||||
- Never use `git add -A` or `git add .`. Always stage files explicitly by name.
|
||||
|
||||
- Never force-push to `main`.
|
||||
@@ -144,8 +151,14 @@ style conventions are in separate documents:
|
||||
- Use SemVer.
|
||||
|
||||
- Database migrations live in `internal/db/migrations/` and must be embedded in
|
||||
the binary. Pre-1.0.0: modify existing migrations (no installed base assumed).
|
||||
Post-1.0.0: add new migration files.
|
||||
the binary.
|
||||
- `000_migration.sql` — contains ONLY the creation of the migrations
|
||||
tracking table itself. Nothing else.
|
||||
- `001_schema.sql` — the full application schema.
|
||||
- **Pre-1.0.0:** never add additional migration files (002, 003, etc.).
|
||||
There is no installed base to migrate. Edit `001_schema.sql` directly.
|
||||
- **Post-1.0.0:** add new numbered migration files for each schema change.
|
||||
Never edit existing migrations after release.
|
||||
|
||||
- All repos should have an `.editorconfig` enforcing the project's indentation
|
||||
settings.
|
||||
|
||||
Vendored
-2
@@ -1,2 +0,0 @@
|
||||
var ie,T,De,pt,G,$e,He,Re,Le,ve,me,he,mt,ae={},se=[],ht=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,_e=Array.isArray;function J(e,t){for(var n in t)e[n]=t[n];return e}function be(e){e&&e.parentNode&&e.parentNode.removeChild(e)}function m(e,t,n){var a,s,o,_={};for(o in t)o=="key"?a=t[o]:o=="ref"?s=t[o]:_[o]=t[o];if(arguments.length>2&&(_.children=arguments.length>3?ie.call(arguments,2):n),typeof e=="function"&&e.defaultProps!=null)for(o in e.defaultProps)_[o]===void 0&&(_[o]=e.defaultProps[o]);return re(e,_,a,s,null)}function re(e,t,n,a,s){var o={type:e,props:t,key:n,ref:a,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:s??++De,__i:-1,__u:0};return s==null&&T.vnode!=null&&T.vnode(o),o}function le(e){return e.children}function oe(e,t){this.props=e,this.context=t}function Q(e,t){if(t==null)return e.__?Q(e.__,e.__i+1):null;for(var n;t<e.__k.length;t++)if((n=e.__k[t])!=null&&n.__e!=null)return n.__e;return typeof e.type=="function"?Q(e):null}function yt(e){if(e.__P&&e.__d){var t=e.__v,n=t.__e,a=[],s=[],o=J({},t);o.__v=t.__v+1,T.vnode&&T.vnode(o),ke(e.__P,o,t,e.__n,e.__P.namespaceURI,32&t.__u?[n]:null,a,n??Q(t),!!(32&t.__u),s),o.__v=t.__v,o.__.__k[o.__i]=o,je(a,o,s),t.__e=t.__=null,o.__e!=n&&We(o)}}function We(e){if((e=e.__)!=null&&e.__c!=null)return e.__e=e.__c.base=null,e.__k.some(function(t){if(t!=null&&t.__e!=null)return e.__e=e.__c.base=t.__e}),We(e)}function Me(e){(!e.__d&&(e.__d=!0)&&G.push(e)&&!ce.__r++||$e!=T.debounceRendering)&&(($e=T.debounceRendering)||He)(ce)}function ce(){for(var e,t=1;G.length;)G.length>t&&G.sort(Re),e=G.shift(),t=G.length,yt(e);ce.__r=0}function Fe(e,t,n,a,s,o,_,u,y,f,k){var c,h,v,C,$,A,g,w=a&&a.__k||se,D=t.length;for(y=vt(n,t,w,y,D),c=0;c<D;c++)(v=n.__k[c])!=null&&(h=v.__i!=-1&&w[v.__i]||ae,v.__i=c,A=ke(e,v,h,s,o,_,u,y,f,k),C=v.__e,v.ref&&h.ref!=v.ref&&(h.ref&&ge(h.ref,null,v),k.push(v.ref,v.__c||C,v)),$==null&&C!=null&&($=C),(g=!!(4&v.__u))||h.__k===v.__k?y=Je(v,y,e,g):typeof v.type=="function"&&A!==void 0?y=A:C&&(y=C.nextSibling),v.__u&=-7);return n.__e=$,y}function vt(e,t,n,a,s){var o,_,u,y,f,k=n.length,c=k,h=0;for(e.__k=new Array(s),o=0;o<s;o++)(_=t[o])!=null&&typeof _!="boolean"&&typeof _!="function"?(typeof _=="string"||typeof _=="number"||typeof _=="bigint"||_.constructor==String?_=e.__k[o]=re(null,_,null,null,null):_e(_)?_=e.__k[o]=re(le,{children:_},null,null,null):_.constructor===void 0&&_.__b>0?_=e.__k[o]=re(_.type,_.props,_.key,_.ref?_.ref:null,_.__v):e.__k[o]=_,y=o+h,_.__=e,_.__b=e.__b+1,u=null,(f=_.__i=bt(_,n,y,c))!=-1&&(c--,(u=n[f])&&(u.__u|=2)),u==null||u.__v==null?(f==-1&&(s>k?h--:s<k&&h++),typeof _.type!="function"&&(_.__u|=4)):f!=y&&(f==y-1?h--:f==y+1?h++:(f>y?h--:h++,_.__u|=4))):e.__k[o]=null;if(c)for(o=0;o<k;o++)(u=n[o])!=null&&(2&u.__u)==0&&(u.__e==a&&(a=Q(u)),qe(u,u));return a}function Je(e,t,n,a){var s,o;if(typeof e.type=="function"){for(s=e.__k,o=0;s&&o<s.length;o++)s[o]&&(s[o].__=e,t=Je(s[o],t,n,a));return t}e.__e!=t&&(a&&(t&&e.type&&!t.parentNode&&(t=Q(e)),n.insertBefore(e.__e,t||null)),t=e.__e);do t=t&&t.nextSibling;while(t!=null&&t.nodeType==8);return t}function bt(e,t,n,a){var s,o,_,u=e.key,y=e.type,f=t[n],k=f!=null&&(2&f.__u)==0;if(f===null&&u==null||k&&u==f.key&&y==f.type)return n;if(a>(k?1:0)){for(s=n-1,o=n+1;s>=0||o<t.length;)if((f=t[_=s>=0?s--:o++])!=null&&(2&f.__u)==0&&u==f.key&&y==f.type)return _}return-1}function Ee(e,t,n){t[0]=="-"?e.setProperty(t,n??""):e[t]=n==null?"":typeof n!="number"||ht.test(t)?n:n+"px"}function ne(e,t,n,a,s){var o,_;e:if(t=="style")if(typeof n=="string")e.style.cssText=n;else{if(typeof a=="string"&&(e.style.cssText=a=""),a)for(t in a)n&&t in n||Ee(e.style,t,"");if(n)for(t in n)a&&n[t]==a[t]||Ee(e.style,t,n[t])}else if(t[0]=="o"&&t[1]=="n")o=t!=(t=t.replace(Le,"$1")),_=t.toLowerCase(),t=_ in e||t=="onFocusOut"||t=="onFocusIn"?_.slice(2):t.slice(2),e.l||(e.l={}),e.l[t+o]=n,n?a?n.u=a.u:(n.u=ve,e.addEventListener(t,o?he:me,o)):e.removeEventListener(t,o?he:me,o);else{if(s=="http://www.w3.org/2000/svg")t=t.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if(t!="width"&&t!="height"&&t!="href"&&t!="list"&&t!="form"&&t!="tabIndex"&&t!="download"&&t!="rowSpan"&&t!="colSpan"&&t!="role"&&t!="popover"&&t in e)try{e[t]=n??"";break e}catch{}typeof n=="function"||(n==null||n===!1&&t[4]!="-"?e.removeAttribute(t):e.setAttribute(t,t=="popover"&&n==1?"":n))}}function Ue(e){return function(t){if(this.l){var n=this.l[t.type+e];if(t.t==null)t.t=ve++;else if(t.t<n.u)return;return n(T.event?T.event(t):t)}}}function ke(e,t,n,a,s,o,_,u,y,f){var k,c,h,v,C,$,A,g,w,D,M,V,q,L,B,E=t.type;if(t.constructor!==void 0)return null;128&n.__u&&(y=!!(32&n.__u),o=[u=t.__e=n.__e]),(k=T.__b)&&k(t);e:if(typeof E=="function")try{if(g=t.props,w="prototype"in E&&E.prototype.render,D=(k=E.contextType)&&a[k.__c],M=k?D?D.props.value:k.__:a,n.__c?A=(c=t.__c=n.__c).__=c.__E:(w?t.__c=c=new E(g,M):(t.__c=c=new oe(g,M),c.constructor=E,c.render=gt),D&&D.sub(c),c.state||(c.state={}),c.__n=a,h=c.__d=!0,c.__h=[],c._sb=[]),w&&c.__s=Line truncated
|
||||
`):"",p={id:r.id,ts:r.ts,from:r.from,to:r.to,command:r.command};switch(r.command){case"PRIVMSG":case"NOTICE":{let l=i,d=!1;Nt(l)&&(l=Pt(l),d=!0);let b={...p,text:l,system:!1,isAction:d},F=r.to;if(F&&F.startsWith("#"))N(F,b);else{let H=r.from===X.current?r.to:r.from;o(pe=>pe.find(Pe=>Pe.type==="dm"&&Pe.name===H)?pe:[...pe,{type:"dm",name:H}]),N(H,b)}break}case"JOIN":{let l=`${r.from} has joined ${r.to}`;r.to&&N(r.to,{...p,text:l,system:!0}),r.to&&r.to.startsWith("#")&&(r.from===X.current&&o(d=>d.find(b=>b.type==="channel"&&b.name===r.to)?d:[...d,{type:"channel",name:r.to}]),K(r.to));break}case"PART":{let l=i?" ("+i+")":"",d=`${r.from} has parted ${r.to}${l}`;r.to&&N(r.to,{...p,text:d,system:!0}),r.to&&r.to.startsWith("#")&&K(r.to);break}case"QUIT":{let l=i?" ("+i+")":"",d=`${r.from} has quit${l}`;z.current.forEach(b=>{b.type==="channel"&&N(b.name,{...p,text:d,system:!0})});break}case"NICK":{let l=Array.isArray(r.body)?r.body[0]:i,d=`${r.from} is now known as ${l}`;z.current.forEach(b=>{b.type==="channel"&&N(b.name,{...p,text:d,system:!0})}),r.from===X.current&&l&&a(l),z.current.forEach(b=>{b.type==="channel"&&K(b.name)});break}case"TOPIC":{let l=`${r.from} has changed the topic to: ${i}`;r.to&&(N(r.to,{...p,text:l,system:!0}),v(d=>({...d,[r.to]:i})));break}case"353":{if(Array.isArray(r.params)&&r.params.length>=2&&r.body){let l=r.params[1],F=(Array.isArray(r.body)?r.body[0]:String(r.body)).split(/\s+/).filter(Boolean).map(H=>H.startsWith("@")?{nick:H.slice(1),mode:"o"}:H.startsWith("+")?{nick:H.slice(1),mode:"v"}:{nick:H,mode:""});c(H=>({...H,[l]:F}))}break}case"332":{if(Array.isArray(r.params)&&r.params.length>=1){let l=r.params[0],d=Array.isArray(r.body)?r.body[0]:i;d&&v(b=>({...b,[l]:d}))}break}case"322":{if(Array.isArray(r.params)&&r.params.length>=2){let l=r.params[0],d=r.params[1];N("Server",{...p,text:`${l} (${d} users): ${(i||"").trim()}`,system:!0})}break}case"323":N("Server",{...p,text:i||"End of channel list",system:!0});break;case"352":{if(Array.isArray(r.params)&&r.params.length>=5){let l=r.params[0],d=r.params[4],b=r.params.length>5?r.params[5]:"";N("Server",{...p,text:`${l} ${d} ${b}`,system:!0})}break}case"315":N("Server",{...p,text:i||"End of /WHO list",system:!0});break;case"311":{if(Array.isArray(r.params)&&r.params.length>=1){let l=r.params[0];N("Server",{...p,text:`${l} (${i})`,system:!0})}break}case"312":{if(Array.isArray(r.params)&&r.params.length>=2){let l=r.params[0],d=r.params[1];N("Server",{...p,text:`${l} on ${d}`,system:!0})}break}case"319":{if(Array.isArray(r.params)&&r.params.length>=1){let l=r.params[0];N("Server",{...p,text:`${l} is on: ${i}`,system:!0})}break}case"318":N("Server",{...p,text:i||"End of /WHOIS list",system:!0});break;case"375":case"372":case"376":N("Server",{...p,text:i,system:!0});break;default:i&&N("Server",{...p,text:i,system:!0})}},[N,K]);U(()=>{if(!e)return;let r=!0;return(async()=>{for(;r;)try{let p=new AbortController;W.current=p;let l=await x(`/messages?after=${B.current}&timeout=${Ct}`,{signal:p.signal});if(!r)break;if(D(!0),l.messages)for(let d of l.messages)fe(d);l.last_id>B.current&&(B.current=l.last_id)}catch(p){if(!r)break;if(p.name==="AbortError")continue;D(!1),await new Promise(l=>setTimeout(l,It))}})(),()=>{r=!1,W.current?.abort()}},[e,fe]),U(()=>{if(!e)return;let r=s[_];if(!r||r.type!=="channel")return;K(r.name);let i=setInterval(()=>K(r.name),At);return()=>clearInterval(i)},[e,_,s,K]),U(()=>{Oe.current?.scrollIntoView({behavior:"smooth"})},[y,_]),U(()=>{Z.current?.focus()},[_]),U(()=>{let r=i=>{i.key==="/"&&document.activeElement!==Z.current&&!i.ctrlKey&&!i.altKey&&!i.metaKey&&(i.preventDefault(),Z.current?.focus())};return document.addEventListener("keydown",r),Z.current?.focus(),()=>document.removeEventListener("keydown",r)},[]),U(()=>{if(!e)return;let r=s[_];!r||r.type!=="channel"||x("/channels").then(i=>{let p=i.find(l=>l.name===r.name);p&&p.topic&&v(l=>({...l,[r.name]:p.topic}))}).catch(()=>{})},[e,_,s]);let at=Y(async(r,i)=>{if(a(r),t(!0),S("Server",`Connected as ${r}`),i){try{await x("/messages",{method:"POST",body:JSON.stringify({command:"MOTD"})})}catch{}return}let p=JSON.parse(localStorage.getItem("neoirc_channels")||"[]");for(let l of p)try{await x("/messages",{method:"POST",body:JSON.stringify({command:"JOIN",to:l})}),o(d=>d.find(b=>b.type==="channel"&&b.name===l)?d:[...d,{type:"channel",name:l}])}catch{}},[S]),st=async r=>{if(r){r=r.trim(),r.startsWith("#")||(r="#"+r);try{await x("/messages",{method:"POST",body:JSON.stringify({command:"JOIN",to:r})}),o(p=>p.find(l=>l.type==="channel"&&l.name===r)?p:[...p,{type:"channel",name:r}]);let i=z.current.length;u(i);try{let p=await x(`/history?target=${encodeURIComponent(r)}&limit=50`);if(Array.isArray(p))for(let l of p)fe(l)}catch{}}catch(i){S("Server",`Failed to join ${r}: ${i.data?.error||"error"}`)}}},Ne=async(r,i)=>{try{await x("/messages",{method:"POST",body:JSON.stringify(i?{command:"PART",to:r,body:[i]}:{command:"PART",to:r})})}catch{}o(p=>p.filter(l=>!(l.type==="channel"&&l.nam
Line truncated
|
||||
Vendored
-13
@@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>NeoIRC</title>
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Vendored
-466
@@ -1,466 +0,0 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg: #0a0e14;
|
||||
--bg-panel: #0d1117;
|
||||
--bg-input: #0d1117;
|
||||
--bg-tab: #161b22;
|
||||
--bg-tab-active: #0d1117;
|
||||
--bg-topic: #0d1117;
|
||||
--text: #c9d1d9;
|
||||
--text-dim: #6e7681;
|
||||
--text-bright: #e6edf3;
|
||||
--accent: #58a6ff;
|
||||
--accent-dim: #1f6feb;
|
||||
--border: #21262d;
|
||||
--system: #7d8590;
|
||||
--action: #d2a8ff;
|
||||
--warn: #d29922;
|
||||
--error: #f85149;
|
||||
--unread: #f0883e;
|
||||
--nick-brackets: #6e7681;
|
||||
--timestamp: #484f58;
|
||||
--input-bg: #161b22;
|
||||
--prompt: #3fb950;
|
||||
--tab-indicator: #58a6ff;
|
||||
--user-list-bg: #0d1117;
|
||||
--user-list-header: #484f58;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
height: 100%;
|
||||
font-family: "JetBrains Mono", "Cascadia Code", "Fira Code", "SF Mono",
|
||||
"Consolas", "Liberation Mono", "Courier New", monospace;
|
||||
font-size: 13px;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Login Screen
|
||||
============================================ */
|
||||
|
||||
.login-screen {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
.login-box {
|
||||
text-align: center;
|
||||
max-width: 360px;
|
||||
width: 100%;
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
.login-box h1 {
|
||||
color: var(--accent);
|
||||
font-size: 1.8em;
|
||||
margin-bottom: 16px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.login-box .motd {
|
||||
color: var(--accent);
|
||||
font-size: 11px;
|
||||
margin-bottom: 20px;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
font-family: inherit;
|
||||
line-height: 1.2;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.login-box form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.login-box label {
|
||||
color: var(--text-dim);
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.login-box input {
|
||||
padding: 8px 12px;
|
||||
font-family: inherit;
|
||||
font-size: 14px;
|
||||
background: var(--input-bg);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-bright);
|
||||
border-radius: 3px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.login-box input:focus {
|
||||
border-color: var(--accent-dim);
|
||||
}
|
||||
|
||||
.login-box button {
|
||||
padding: 8px 16px;
|
||||
font-family: inherit;
|
||||
font-size: 14px;
|
||||
background: var(--accent-dim);
|
||||
border: none;
|
||||
color: var(--text-bright);
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.login-box button:hover {
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.login-box .error {
|
||||
color: var(--error);
|
||||
font-size: 12px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
IRC App Layout
|
||||
============================================ */
|
||||
|
||||
.irc-app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Tab Bar
|
||||
============================================ */
|
||||
|
||||
.tab-bar {
|
||||
display: flex;
|
||||
background: var(--bg-tab);
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
height: 32px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
flex: 1;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.tabs::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
cursor: pointer;
|
||||
color: var(--text-dim);
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
border-right: 1px solid var(--border);
|
||||
font-size: 12px;
|
||||
gap: 4px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tab:hover {
|
||||
color: var(--text);
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
color: var(--text-bright);
|
||||
background: var(--bg-tab-active);
|
||||
border-bottom: 2px solid var(--tab-indicator);
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.tab.has-unread .tab-label {
|
||||
color: var(--unread);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tab .unread-count {
|
||||
color: var(--unread);
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tab-close {
|
||||
color: var(--text-dim);
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.tab-close:hover {
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
.status-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 0 12px;
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.status-nick {
|
||||
color: var(--accent);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-warn {
|
||||
color: var(--warn);
|
||||
animation: blink 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Topic Bar
|
||||
============================================ */
|
||||
|
||||
.topic-bar {
|
||||
padding: 4px 12px;
|
||||
background: var(--bg-topic);
|
||||
border-bottom: 1px solid var(--border);
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex-shrink: 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.topic-label {
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.topic-text {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Main Content Area
|
||||
============================================ */
|
||||
|
||||
.main-area {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Messages Panel
|
||||
============================================ */
|
||||
|
||||
.messages-panel {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.messages-scroll {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 4px 8px;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--border) transparent;
|
||||
}
|
||||
|
||||
.messages-scroll::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.messages-scroll::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.messages-scroll::-webkit-scrollbar-thumb {
|
||||
background: var(--border);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Message Lines
|
||||
============================================ */
|
||||
|
||||
.message {
|
||||
padding: 1px 0;
|
||||
line-height: 1.4;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.message .timestamp {
|
||||
color: var(--timestamp);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.message .nick {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.message .content {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
/* System messages (joins, parts, quits, etc.) */
|
||||
.system-message {
|
||||
color: var(--system);
|
||||
}
|
||||
|
||||
.system-message .system-text {
|
||||
color: var(--system);
|
||||
}
|
||||
|
||||
/* /me action messages */
|
||||
.action-message .action-text {
|
||||
color: var(--action);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
User List (Right Panel)
|
||||
============================================ */
|
||||
|
||||
.user-list {
|
||||
width: 160px;
|
||||
background: var(--user-list-bg);
|
||||
border-left: 1px solid var(--border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.user-list-header {
|
||||
padding: 6px 10px;
|
||||
color: var(--user-list-header);
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.user-list-entries {
|
||||
overflow-y: auto;
|
||||
padding: 4px 0;
|
||||
flex: 1;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--border) transparent;
|
||||
}
|
||||
|
||||
.nick-entry {
|
||||
padding: 2px 10px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.nick-entry:hover {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
.nick-prefix {
|
||||
color: var(--text-dim);
|
||||
display: inline-block;
|
||||
width: 1ch;
|
||||
text-align: right;
|
||||
margin-right: 1px;
|
||||
}
|
||||
|
||||
.nick-name {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Input Line (Bottom)
|
||||
============================================ */
|
||||
|
||||
.input-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: var(--input-bg);
|
||||
border-top: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
height: 36px;
|
||||
padding: 0 8px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.input-prompt {
|
||||
color: var(--prompt);
|
||||
font-size: 13px;
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.input-line input {
|
||||
flex: 1;
|
||||
padding: 4px 0;
|
||||
font-family: inherit;
|
||||
font-size: 13px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-bright);
|
||||
outline: none;
|
||||
caret-color: var(--accent);
|
||||
}
|
||||
|
||||
.input-line input::placeholder {
|
||||
color: var(--text-dim);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Responsive
|
||||
============================================ */
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.user-list {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 0 8px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.input-prompt {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user