Compare commits

...

4 Commits

Author SHA1 Message Date
Jeff Emmett 24543b678d Merge branch 'dev'
CI/CD / deploy (push) Successful in 2m4s Details
2026-04-16 11:04:25 -04:00
Jeff Emmett a2f74faa3e feat(rtime): split-screen layout — commitment form left, pool viz right
Restructured rTime from pool+weaving side-by-side to commitment entry
form (left 50%) and pool orb visualization (right 50%). Inline form
replaces modal for pledging time. Commitments list shows below form.
Weaving SVG moved to separate toggled view via "Weave" button.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 11:04:14 -04:00
Jeff Emmett 8592abd467 feat(rmeets): chat popup notifications + right-side chat panel
- Enable chat notifications (brief popup for all participants)
- Move chat panel to right side via CHAT_PANEL_POSITION
- Applied to both clean room mode and folk-jitsi-room shell mode

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 09:36:29 -04:00
Jeff Emmett 12cc724291 fix(rmeets): add recording button + post-meeting transcript link
- Add "recording" to Jitsi toolbarButtons in both clean room mode
  and folk-jitsi-room shell mode so users can trigger Jibri recording
- Add "View Transcript & Summary" link on meeting-ended screen
- Jibri network connectivity fixed on Netcup (was on wrong Docker
  network, couldn't reach Prosody XMPP)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 09:09:30 -04:00
3 changed files with 304 additions and 98 deletions

View File

@ -143,12 +143,13 @@ class FolkJitsiRoom extends HTMLElement {
hideConferenceSubject: false,
disableVirtualBackground: false,
disableProfile: false,
notifications: ['chat'],
toolbarButtons: [
"camera", "microphone", "desktop", "hangup",
"raisehand", "tileview", "toggle-camera",
"fullscreen", "chat", "settings",
"participants-pane", "select-background",
"sharedvideo",
"sharedvideo", "recording",
],
// Hide panels that add stray close (×) buttons
disableChat: false,
@ -160,6 +161,7 @@ class FolkJitsiRoom extends HTMLElement {
SHOW_BRAND_WATERMARK: false,
CLOSE_PAGE_GUEST_HINT: false,
SHOW_PROMOTIONAL_CLOSE_PAGE: false,
CHAT_PANEL_POSITION: 'right',
SETTINGS_SECTIONS: ['devices', 'language', 'moderator', 'profile', 'sounds', 'more'],
},
});

View File

@ -750,12 +750,13 @@ routes.get("/:room", (c) => {
enableClosePage: false,
disableVirtualBackground: false,
disableProfile: false,
notifications: ['chat'],
toolbarButtons: [
"microphone","camera","desktop","hangup",
"raisehand","tileview","toggle-camera",
"fullscreen","chat","settings",
"participants-pane","select-background",
"sharedvideo","shareaudio",
"sharedvideo","shareaudio","recording",
],
},
customToolbarButtons: [
@ -768,6 +769,7 @@ routes.get("/:room", (c) => {
MOBILE_APP_PROMO: false,
HIDE_DEEP_LINKING_LOGO: true,
DISABLE_JOIN_LEAVE_NOTIFICATIONS: false,
CHAT_PANEL_POSITION: 'right',
SETTINGS_SECTIONS: ['devices', 'language', 'moderator', 'profile', 'sounds', 'more'],
},
});
@ -784,7 +786,8 @@ routes.get("/:room", (c) => {
try { window.close(); } catch(e) {}
document.getElementById("jitsi-container").innerHTML =
'<div class="ended"><span>Meeting ended</span>'
+ '<a href="${escapeHtml(`/${space}/rmeets`)}">Back to rMeets</a></div>';
+ '<a href="${meetsBase}/recordings">View Transcript &amp; Summary</a>'
+ '<a href="${meetsBase}">Back to rMeets</a></div>';
});
} catch(e) {
document.getElementById("jitsi-container").innerHTML =

View File

@ -287,7 +287,7 @@ class FolkTimebankApp extends HTMLElement {
private poolPointerId: number | null = null;
private poolPointerStart: { x: number; y: number; cx: number; cy: number } | null = null;
private poolPanelCollapsed = false;
private _panelSplitPct = 35;
private _panelSplitPct = 50;
private _dividerDragging = false;
private _dividerDragStartX = 0;
private _dividerDragStartPct = 0;
@ -375,12 +375,14 @@ class FolkTimebankApp extends HTMLElement {
this._history.push(view);
this.currentView = view;
const canvasView = this.shadow.getElementById('canvas-view');
const weaveView = this.shadow.getElementById('weave-view');
const collabView = this.shadow.getElementById('collaborate-view');
const dashView = this.shadow.getElementById('dashboard-view');
if (canvasView) canvasView.style.display = view === 'canvas' ? 'flex' : 'none';
if (weaveView) weaveView.style.display = 'none';
if (collabView) collabView.style.display = view === 'collaborate' ? 'flex' : 'none';
if (dashView) dashView.style.display = view === 'dashboard' ? 'flex' : 'none';
if (view === 'canvas') { this.resizePoolCanvas(); this.rebuildSidebar(); }
if (view === 'canvas') { this.resizePoolCanvas(); this.rebuildSidebar(); this.renderCommitmentsList(); }
if (view === 'collaborate') this.refreshCollaborate();
if (view === 'dashboard') this.refreshDashboard();
}
@ -395,7 +397,7 @@ class FolkTimebankApp extends HTMLElement {
else this.currentView = 'canvas';
this.dpr = window.devicePixelRatio || 1;
this._theme = (localStorage.getItem('rtime-theme') as 'dark' | 'light') || 'dark';
this._panelSplitPct = parseFloat(localStorage.getItem('rtime-split-pct') || '35');
this._panelSplitPct = parseFloat(localStorage.getItem('rtime-split-pct') || '50');
this.render();
this.applyTheme();
this.setupPoolPanel();
@ -524,6 +526,7 @@ class FolkTimebankApp extends HTMLElement {
this.buildOrbs();
this.updateStats();
this.renderCommitmentsList();
this.rebuildSidebar();
this.applyRestoredConnections();
@ -544,13 +547,52 @@ class FolkTimebankApp extends HTMLElement {
<style>${CSS_TEXT}</style>
<div class="main">
<div id="canvas-view">
<!-- Left pool panel -->
<!-- Left: commitment entry form -->
<div class="form-panel" id="formPanel">
<div class="form-panel-header">
<span>Pledge Time</span>
<button class="theme-toggle" id="themeToggle" title="Toggle light/dark theme">\u2600</button>
</div>
<div class="inline-form" id="inlineForm">
<div class="inline-form-field">
<label>Your Name</label>
<input type="text" id="inlineName" placeholder="e.g. Dana Lee">
</div>
<div class="inline-form-field">
<label>Skill Category</label>
<select id="inlineSkill">
<option value="facilitation">Facilitation</option>
<option value="design">Design</option>
<option value="tech">Tech</option>
<option value="outreach">Outreach</option>
<option value="logistics">Logistics</option>
</select>
</div>
<div class="inline-form-row">
<div class="inline-form-field" style="flex:1">
<label>Hours</label>
<input type="number" id="inlineHours" min="1" max="10" value="2">
</div>
</div>
<div class="inline-form-field">
<label>Description</label>
<input type="text" id="inlineDesc" placeholder="What will you contribute?">
</div>
<button class="pledge-btn" id="pledgeBtn">Pledge to Pool</button>
</div>
<div class="commitments-list-header">
<span>Commitments</span>
<span class="commitments-count" id="commitmentsCount">0</span>
</div>
<div class="commitments-list" id="commitmentsList"></div>
</div>
<!-- Resizable divider -->
<div class="panel-divider" id="panelDivider"><div class="divider-pip"></div></div>
<!-- Right: pool visualization -->
<div class="pool-panel" id="poolPanel">
<div class="pool-panel-header">
<span>Commitment Pool</span>
<span class="pool-hint">drag orb to weave \u2192</span>
<button class="theme-toggle" id="themeToggle" title="Toggle light/dark theme"></button>
<button id="poolPanelToggle" title="Collapse panel">\u27E8</button>
<button id="poolPanelToggle" title="Toggle weaving view" class="weave-toggle-btn">\u2694 Weave</button>
</div>
<canvas id="pool-canvas"></canvas>
<div class="pool-legend" id="poolLegend">
@ -570,22 +612,17 @@ class FolkTimebankApp extends HTMLElement {
<div class="pool-detail-hours" id="detailHours"></div>
<div class="pool-detail-desc" id="detailDesc"></div>
<div class="pool-detail-woven" id="detailWoven"></div>
<button class="pool-detail-drag-btn" id="detailDragBtn">Drag to Weave \u2192</button>
</div>
<div class="pool-panel-sidebar" id="poolSidebar">
<div class="sidebar-section">Woven Tasks</div>
<div id="sidebarTasks"></div>
</div>
<button class="add-btn" id="addBtn">+ Pledge Time</button>
</div>
<!-- Resizable divider -->
<div class="panel-divider" id="panelDivider"><div class="divider-pip"></div></div>
<!-- SVG infinite canvas -->
<div class="canvas-wrap" id="canvasWrap">
<div class="canvas-wrap-header">
<!-- Weaving view (hidden by default, toggled via button) -->
<div id="weave-view" style="display:none">
<div class="weave-view-header">
<button class="weave-back-btn" id="weaveBackBtn">\u2190 Back to Pool</button>
<span>Commitment Weaving</span>
<span class="canvas-wrap-hint">connect commitments to projects</span>
</div>
<div class="canvas-wrap" id="canvasWrap">
<svg id="weave-svg" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse">
@ -799,31 +836,19 @@ class FolkTimebankApp extends HTMLElement {
this.applyTheme();
});
// Pool panel toggle
// Weave toggle — switches to weaving SVG view
this.shadow.getElementById('poolPanelToggle')!.addEventListener('click', () => {
this.poolPanelCollapsed = !this.poolPanelCollapsed;
const panel = this.shadow.getElementById('poolPanel')!;
panel.classList.toggle('collapsed', this.poolPanelCollapsed);
const divider = this.shadow.getElementById('panelDivider');
if (divider) divider.style.display = this.poolPanelCollapsed ? 'none' : '';
const btn = this.shadow.getElementById('poolPanelToggle')!;
btn.textContent = this.poolPanelCollapsed ? '\u27E9' : '\u27E8';
btn.title = this.poolPanelCollapsed ? 'Expand panel' : 'Collapse panel';
if (!this.poolPanelCollapsed) setTimeout(() => { this.applyPanelSplit(); }, 50);
this.shadow.getElementById('canvas-view')!.style.display = 'none';
this.shadow.getElementById('weave-view')!.style.display = 'flex';
});
this.shadow.getElementById('weaveBackBtn')!.addEventListener('click', () => {
this.shadow.getElementById('weave-view')!.style.display = 'none';
this.shadow.getElementById('canvas-view')!.style.display = 'flex';
this.resizePoolCanvas();
});
// Add commitment modal
const modal = this.shadow.getElementById('modalOverlay')!;
this.shadow.getElementById('addBtn')!.addEventListener('click', () => {
modal.classList.add('visible');
(this.shadow.getElementById('modalName') as HTMLInputElement).value = '';
(this.shadow.getElementById('modalHours') as HTMLInputElement).value = '2';
(this.shadow.getElementById('modalDesc') as HTMLInputElement).value = '';
(this.shadow.getElementById('modalName') as HTMLInputElement).focus();
});
this.shadow.getElementById('modalCancel')!.addEventListener('click', () => modal.classList.remove('visible'));
modal.addEventListener('click', (e) => { if (e.target === modal) modal.classList.remove('visible'); });
this.shadow.getElementById('modalSubmit')!.addEventListener('click', () => this.submitCommitment());
// Inline commitment form
this.shadow.getElementById('pledgeBtn')!.addEventListener('click', () => this.submitCommitment());
// Pool pointer events — including long-press drag-to-canvas
this.canvas.addEventListener('pointerdown', (e) => this.onPoolPointerDown(e));
@ -944,10 +969,9 @@ class FolkTimebankApp extends HTMLElement {
}
private applyPanelSplit() {
const panel = this.shadow.getElementById('poolPanel');
const panel = this.shadow.getElementById('formPanel');
const divider = this.shadow.getElementById('panelDivider');
if (!panel) return;
if (this.poolPanelCollapsed) return;
panel.style.width = this._panelSplitPct + '%';
if (divider) divider.style.display = '';
this.resizePoolCanvas();
@ -1175,11 +1199,11 @@ class FolkTimebankApp extends HTMLElement {
// ── Submit commitment ──
private async submitCommitment() {
const name = (this.shadow.getElementById('modalName') as HTMLInputElement).value.trim();
const skill = (this.shadow.getElementById('modalSkill') as HTMLSelectElement).value;
const hours = Math.max(1, Math.min(10, parseInt((this.shadow.getElementById('modalHours') as HTMLInputElement).value) || 2));
const desc = (this.shadow.getElementById('modalDesc') as HTMLInputElement).value.trim() || (SKILL_LABELS[skill] || skill) + ' contribution';
if (!name) { (this.shadow.getElementById('modalName') as HTMLInputElement).focus(); return; }
const name = (this.shadow.getElementById('inlineName') as HTMLInputElement).value.trim();
const skill = (this.shadow.getElementById('inlineSkill') as HTMLSelectElement).value;
const hours = Math.max(1, Math.min(10, parseInt((this.shadow.getElementById('inlineHours') as HTMLInputElement).value) || 2));
const desc = (this.shadow.getElementById('inlineDesc') as HTMLInputElement).value.trim() || (SKILL_LABELS[skill] || skill) + ' contribution';
if (!name) { (this.shadow.getElementById('inlineName') as HTMLInputElement).focus(); return; }
const c: Commitment = { id: 'local-' + Date.now(), memberName: name, skill, hours, desc };
@ -1206,7 +1230,33 @@ class FolkTimebankApp extends HTMLElement {
this.ripples.push(new Ripple(this.basketCX - 15, this.basketCY + 10, SKILL_COLORS[skill] || '#8b5cf6'));
}, 400);
this.updateStats();
this.shadow.getElementById('modalOverlay')!.classList.remove('visible');
this.renderCommitmentsList();
// Clear form fields
(this.shadow.getElementById('inlineDesc') as HTMLInputElement).value = '';
(this.shadow.getElementById('inlineHours') as HTMLInputElement).value = '2';
}
private renderCommitmentsList() {
const list = this.shadow.getElementById('commitmentsList');
const countEl = this.shadow.getElementById('commitmentsCount');
if (!list) return;
if (countEl) countEl.textContent = String(this.commitments.length);
list.innerHTML = '';
for (const c of [...this.commitments].reverse()) {
const color = SKILL_COLORS[c.skill] || '#8b5cf6';
const avail = this.availableHours(c.id);
const wovenLabel = avail < c.hours ? ` <span class="cl-woven">${c.hours - avail}h woven</span>` : '';
const item = document.createElement('div');
item.className = 'cl-item';
item.innerHTML = `<div class="cl-dot" style="background:${color}"></div>
<div class="cl-info">
<div class="cl-name">${c.memberName}</div>
<div class="cl-meta">${SKILL_LABELS[c.skill] || c.skill} \u00b7 ${c.hours}h${wovenLabel}</div>
<div class="cl-desc">${c.desc}</div>
</div>`;
list.appendChild(item);
}
}
// ── Stats bar ──
@ -3028,9 +3078,9 @@ class FolkTimebankApp extends HTMLElement {
if (this._tour) return;
const { TourEngine } = await import('../../../shared/tour-engine');
this._tour = new TourEngine(this.shadow as unknown as ShadowRoot, [
{ target: '.pool-panel-header', title: 'Canvas & Collaborate', message: 'The Canvas shows commitment orbs alongside the weaving SVG. Collaborate matches intents via the solver.' },
{ target: '#pool-canvas', title: 'Commitment Pool', message: 'Each floating orb represents a time commitment — sized by hours, colored by skill. Long-press to drag onto the canvas.' },
{ target: '#addBtn', title: 'Add a Commitment', message: 'Pledge your hours with a skill category. Your commitment joins the pool for others to see.', advanceOnClick: true },
{ target: '.form-panel-header', title: 'Pledge Time', message: 'Use the form to pledge your hours with a skill category. Commitments appear in the pool.' },
{ target: '#inlineForm', title: 'Commitment Form', message: 'Fill in your name, skill, hours, and description, then click Pledge to Pool.' },
{ target: '#pool-canvas', title: 'Commitment Pool', message: 'Each floating orb represents a time commitment — sized by hours, colored by skill.' },
{ target: '.pool-legend', title: 'Community Stats', message: 'See total hours available and how many contributors are in the pool at a glance.' },
], 'rtime_tour_done', () => this.shadow.querySelector('.main') as HTMLElement);
}
@ -3590,7 +3640,7 @@ const CSS_TEXT = `
.main { flex: 1; position: relative; overflow: hidden; }
/* Unified canvas view: pool panel + SVG canvas side by side */
/* Unified canvas view: form panel + pool visualization side by side */
#canvas-view {
display: flex;
width: 100%; height: 100%;
@ -3598,9 +3648,34 @@ const CSS_TEXT = `
overflow: hidden;
}
/* Pool panel (left side) */
.pool-panel {
min-width: 180px;
/* Weave view (full screen SVG canvas, toggled from pool) */
#weave-view {
display: none;
flex-direction: column;
width: 100%; height: 100%;
position: absolute; top: 0; left: 0;
overflow: hidden;
}
.weave-view-header {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.5rem 0.75rem;
font-size: 0.82rem; font-weight: 600; color: #94a3b8;
text-transform: uppercase; letter-spacing: 0.04em;
border-bottom: 1px solid #334155; flex-shrink: 0;
background: #1e293b;
}
.weave-back-btn {
background: none; border: 1px solid #475569; border-radius: 0.375rem;
color: #94a3b8; font-size: 0.82rem; cursor: pointer; padding: 0.3rem 0.75rem;
transition: all 0.15s; text-transform: none; letter-spacing: normal;
}
.weave-back-btn:hover { border-color: #8b5cf6; color: #e2e8f0; }
/* Form panel (left side) — commitment entry */
.form-panel {
min-width: 240px;
flex-shrink: 0;
background: #1e293b;
border-right: 1px solid #334155;
@ -3610,12 +3685,7 @@ const CSS_TEXT = `
position: relative;
transition: width 0.2s ease;
}
.pool-panel.collapsed { width: 40px; }
.pool-panel.collapsed #pool-canvas,
.pool-panel.collapsed .pool-detail,
.pool-panel.collapsed .pool-panel-sidebar,
.pool-panel.collapsed .add-btn { display: none; }
.pool-panel-header {
.form-panel-header {
display: flex;
justify-content: space-between;
align-items: center;
@ -3628,12 +3698,147 @@ const CSS_TEXT = `
border-bottom: 1px solid #334155;
flex-shrink: 0;
}
.pool-panel-header button {
.form-panel-header button {
background: none; border: 1px solid #475569; border-radius: 0.25rem;
color: #94a3b8; font-size: 0.85rem; cursor: pointer; padding: 0.1rem 0.4rem;
line-height: 1; transition: border-color 0.15s;
}
.pool-panel-header button:hover { border-color: #8b5cf6; color: #e2e8f0; }
.form-panel-header button:hover { border-color: #8b5cf6; color: #e2e8f0; }
/* Inline commitment form */
.inline-form {
padding: 0.75rem;
border-bottom: 1px solid #334155;
flex-shrink: 0;
}
.inline-form-field {
margin-bottom: 0.5rem;
}
.inline-form-field label {
display: block;
font-size: 0.72rem;
font-weight: 600;
color: #94a3b8;
text-transform: uppercase;
letter-spacing: 0.03em;
margin-bottom: 0.2rem;
}
.inline-form-field input,
.inline-form-field select {
width: 100%;
padding: 0.4rem 0.55rem;
background: #0f172a;
border: 1px solid #334155;
border-radius: 0.375rem;
color: #e2e8f0;
font-size: 0.82rem;
transition: border-color 0.15s;
box-sizing: border-box;
}
.inline-form-field input:focus,
.inline-form-field select:focus {
outline: none;
border-color: #8b5cf6;
}
.inline-form-row {
display: flex;
gap: 0.5rem;
}
.pledge-btn {
width: 100%;
padding: 0.5rem 0.75rem;
margin-top: 0.25rem;
background: linear-gradient(135deg, #8b5cf6, #ec4899);
color: #fff;
border: none;
border-radius: 0.5rem;
font-size: 0.82rem;
font-weight: 600;
cursor: pointer;
box-shadow: 0 4px 16px rgba(139,92,246,0.3);
transition: all 0.2s;
}
.pledge-btn:hover { transform: translateY(-1px); box-shadow: 0 6px 20px rgba(139,92,246,0.4); }
/* Commitments list */
.commitments-list-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem 0.75rem;
font-size: 0.72rem;
font-weight: 600;
color: #64748b;
text-transform: uppercase;
letter-spacing: 0.04em;
border-bottom: 1px solid #334155;
flex-shrink: 0;
}
.commitments-count {
background: #334155;
color: #94a3b8;
font-size: 0.68rem;
padding: 0.1rem 0.4rem;
border-radius: 0.25rem;
min-width: 1.2rem;
text-align: center;
}
.commitments-list {
flex: 1;
overflow-y: auto;
padding: 0.25rem;
}
.cl-item {
display: flex;
align-items: flex-start;
gap: 0.5rem;
padding: 0.5rem 0.5rem;
border-radius: 0.375rem;
transition: background 0.15s;
border-bottom: 1px solid rgba(51,65,85,0.4);
}
.cl-item:hover { background: rgba(51,65,85,0.3); }
.cl-dot {
width: 8px; height: 8px;
border-radius: 50%;
flex-shrink: 0;
margin-top: 0.35rem;
}
.cl-info { flex: 1; min-width: 0; }
.cl-name { font-size: 0.82rem; font-weight: 600; color: #f1f5f9; }
.cl-meta { font-size: 0.72rem; color: #94a3b8; }
.cl-woven { color: #8b5cf6; font-weight: 600; }
.cl-desc { font-size: 0.72rem; color: #64748b; margin-top: 0.15rem; line-height: 1.4; }
/* Pool panel (right side) — visualization */
.pool-panel {
flex: 1;
background: #0f172a;
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
}
.pool-panel-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem 0.75rem;
font-size: 0.82rem;
font-weight: 600;
color: #94a3b8;
text-transform: uppercase;
letter-spacing: 0.04em;
border-bottom: 1px solid #334155;
flex-shrink: 0;
background: #1e293b;
}
.weave-toggle-btn {
background: none; border: 1px solid #475569; border-radius: 0.375rem;
color: #94a3b8; font-size: 0.78rem; cursor: pointer; padding: 0.25rem 0.6rem;
transition: all 0.15s; text-transform: none; letter-spacing: normal;
}
.weave-toggle-btn:hover { border-color: #8b5cf6; color: #e2e8f0; }
#pool-canvas { flex: 1; min-height: 0; display: block; cursor: default; touch-action: none; }
.pool-detail {
@ -3662,16 +3867,6 @@ const CSS_TEXT = `
background: rgba(139,92,246,0.1); border-radius: 0.25rem;
display: inline-block;
}
.pool-detail-drag-btn {
margin-top: 0.5rem; padding: 0.35rem 0.75rem; width: 100%;
background: linear-gradient(135deg, #8b5cf6, #ec4899);
color: #fff; border: none; border-radius: 0.375rem;
font-size: 0.78rem; font-weight: 600; cursor: grab;
transition: opacity 0.15s; touch-action: none;
}
.pool-detail-drag-btn:hover { opacity: 0.85; }
.pool-detail-drag-btn:active { cursor: grabbing; }
/* Pool header hint */
.pool-hint {
font-size: 0.68rem; font-weight: 400; color: #64748b;
@ -3709,21 +3904,7 @@ const CSS_TEXT = `
.pool-panel-sidebar { flex-shrink: 0; overflow-y: auto; max-height: 200px; border-top: 1px solid #334155; }
.add-btn {
padding: 0.5rem 0.75rem;
margin: 0.5rem;
background: linear-gradient(135deg, #8b5cf6, #ec4899);
color: #fff;
border: none;
border-radius: 0.5rem;
font-size: 0.82rem;
font-weight: 500;
cursor: pointer;
box-shadow: 0 4px 16px rgba(139,92,246,0.3);
transition: all 0.2s;
flex-shrink: 0;
}
.add-btn:hover { transform: translateY(-1px); box-shadow: 0 6px 20px rgba(139,92,246,0.4); }
/* Legacy .add-btn kept for modal fallback */
.sidebar-item-info { flex: 1; min-width: 0; }
.sidebar-item-name { font-size: 0.82rem; font-weight: 600; color: #f1f5f9; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@ -4309,6 +4490,23 @@ const CSS_TEXT = `
:host([data-theme="light"]) .dep-port-diamond { fill: #e2e8f0; stroke: #94a3b8; }
:host([data-theme="light"]) .project-frame-rect { stroke: #8b5cf644; }
:host([data-theme="light"]) .pool-detail-woven { background: rgba(139,92,246,0.08); }
:host([data-theme="light"]) .form-panel { background: #fff; border-right-color: #e2e8f0; }
:host([data-theme="light"]) .form-panel-header { color: #64748b; border-bottom-color: #e2e8f0; }
:host([data-theme="light"]) .inline-form { border-bottom-color: #e2e8f0; }
:host([data-theme="light"]) .inline-form-field input,
:host([data-theme="light"]) .inline-form-field select { background: #f8fafc; border-color: #e2e8f0; color: #1e293b; }
:host([data-theme="light"]) .inline-form-field label { color: #64748b; }
:host([data-theme="light"]) .commitments-list-header { color: #94a3b8; border-bottom-color: #e2e8f0; }
:host([data-theme="light"]) .commitments-count { background: #e2e8f0; color: #64748b; }
:host([data-theme="light"]) .cl-item { border-bottom-color: rgba(226,232,240,0.4); }
:host([data-theme="light"]) .cl-item:hover { background: rgba(241,245,249,0.6); }
:host([data-theme="light"]) .cl-name { color: #1e293b; }
:host([data-theme="light"]) .cl-meta { color: #64748b; }
:host([data-theme="light"]) .cl-desc { color: #94a3b8; }
:host([data-theme="light"]) .pool-panel { background: #f8fafc; }
:host([data-theme="light"]) .weave-view-header { background: #fff; color: #64748b; border-bottom-color: #e2e8f0; }
:host([data-theme="light"]) .weave-back-btn { border-color: #e2e8f0; color: #64748b; }
:host([data-theme="light"]) .weave-toggle-btn { border-color: #e2e8f0; color: #64748b; }
/* Hex hover stroke */
.hex-hover-stroke { transition: stroke-width 0.15s; }
@ -4376,12 +4574,15 @@ const CSS_TEXT = `
.skill-legend { gap: 0.5rem; }
.skill-legend-item { font-size: 0.68rem; }
#canvas-view { flex-direction: column; }
.pool-panel {
.form-panel {
width: 100% !important; min-width: unset;
border-right: none; border-bottom: 1px solid #334155;
flex: 1; min-height: 0; max-height: none;
flex: 0 0 auto; max-height: 50%;
}
.pool-panel {
flex: 1; min-height: 0;
border-top: 1px solid #334155;
}
.pool-panel.collapsed { flex: 0 0 36px; min-height: 36px; max-height: 36px; }
.canvas-wrap {
flex: 1; min-height: 0;
border-top: 1px solid #334155;
@ -4389,13 +4590,13 @@ const CSS_TEXT = `
.canvas-wrap-header { display: flex; }
.panel-divider { display: none !important; }
.pool-hint { display: none; }
.pool-panel-sidebar { max-height: 80px; }
.exec-panel { width: 95vw; }
.task-edit-panel { width: 95vw; }
}
@media (max-width: 640px) {
.pool-legend-skills { display: none; }
.pool-panel.collapsed { width: 100% !important; }
.inline-form-field label { font-size: 0.68rem; }
.inline-form-field input, .inline-form-field select { font-size: 0.78rem; padding: 0.35rem 0.45rem; }
}
`;