Merge branch 'dev'

This commit is contained in:
Jeff Emmett 2026-03-04 12:55:16 -08:00
commit 659e203d6a
9 changed files with 482 additions and 456 deletions

View File

@ -366,8 +366,8 @@ export class FolkBookReader extends HTMLElement {
width: 100%;
height: 100%;
min-height: calc(100vh - 52px);
background: #0f172a;
color: #f1f5f9;
background: var(--rs-bg-page);
color: var(--rs-text-primary);
}
.loading {
@ -382,7 +382,7 @@ export class FolkBookReader extends HTMLElement {
.loading-spinner {
width: 40px;
height: 40px;
border: 3px solid #334155;
border: 3px solid var(--rs-border-strong);
border-top-color: #60a5fa;
border-radius: 50%;
animation: spin 0.8s linear infinite;
@ -390,14 +390,14 @@ export class FolkBookReader extends HTMLElement {
@keyframes spin { to { transform: rotate(360deg); } }
.loading-status {
color: #94a3b8;
color: var(--rs-text-secondary);
font-size: 0.9rem;
}
.loading-bar {
width: 200px;
height: 4px;
background: #1e293b;
background: var(--rs-bg-surface);
border-radius: 2px;
overflow: hidden;
}
@ -417,15 +417,15 @@ export class FolkBookReader extends HTMLElement {
height: calc(100vh - 52px);
gap: 0.5rem;
}
.error h3 { color: #f87171; margin: 0; }
.error p { color: #94a3b8; margin: 0; }
.error h3 { color: var(--rs-error); margin: 0; }
.error p { color: var(--rs-text-secondary); margin: 0; }
.error button {
margin-top: 1rem;
padding: 0.5rem 1.5rem;
border: 1px solid #334155;
border: 1px solid var(--rs-border-strong);
border-radius: 0.5rem;
background: #1e293b;
color: #f1f5f9;
background: var(--rs-bg-surface);
color: var(--rs-text-primary);
cursor: pointer;
}
@ -446,23 +446,23 @@ export class FolkBookReader extends HTMLElement {
min-height: 36px;
}
.rapp-nav__back {
padding: 4px 10px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.1);
background: transparent; color: #94a3b8; cursor: pointer; font-size: 13px;
padding: 4px 10px; border-radius: 6px; border: 1px solid var(--rs-border-subtle);
background: transparent; color: var(--rs-text-secondary); cursor: pointer; font-size: 13px;
text-decoration: none; transition: color 0.15s, border-color 0.15s;
}
.rapp-nav__back:hover { color: #e2e8f0; border-color: rgba(255,255,255,0.2); }
.rapp-nav__back:hover { color: var(--rs-text-primary); border-color: var(--rs-border-strong); }
.rapp-nav__title {
font-size: 15px; font-weight: 600; color: #e2e8f0;
font-size: 15px; font-weight: 600; color: var(--rs-text-primary);
flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rapp-nav__subtitle {
font-size: 0.8rem;
color: #94a3b8;
color: var(--rs-text-secondary);
margin-left: 4px;
}
.rapp-nav__meta {
font-size: 0.85rem;
color: #94a3b8;
color: var(--rs-text-secondary);
white-space: nowrap;
}
@ -481,10 +481,10 @@ export class FolkBookReader extends HTMLElement {
.nav-btn {
width: 44px;
height: 80px;
border: 1px solid #334155;
border: 1px solid var(--rs-border-strong);
border-radius: 0.5rem;
background: #1e293b;
color: #f1f5f9;
background: var(--rs-bg-surface);
color: var(--rs-text-primary);
font-size: 1.5rem;
cursor: pointer;
display: flex;
@ -492,7 +492,7 @@ export class FolkBookReader extends HTMLElement {
justify-content: center;
transition: background 0.15s;
}
.nav-btn:hover { background: #334155; }
.nav-btn:hover { background: var(--rs-border-strong); }
.reader-footer {
display: flex;
@ -501,14 +501,14 @@ export class FolkBookReader extends HTMLElement {
.nav-text-btn {
padding: 0.375rem 1rem;
border: 1px solid #334155;
border: 1px solid var(--rs-border-strong);
border-radius: 0.375rem;
background: transparent;
color: #94a3b8;
color: var(--rs-text-secondary);
font-size: 0.8rem;
cursor: pointer;
}
.nav-text-btn:hover { border-color: #60a5fa; color: #f1f5f9; }
.nav-text-btn:hover { border-color: #60a5fa; color: var(--rs-text-primary); }
</style>`;
}

View File

@ -810,7 +810,7 @@ class FolkCalendarView extends HTMLElement {
${dayEvents.length > 0 ? `
<div class="dots">
${dayEvents.slice(0, 5).map(e => `<span class="dot" style="background:${e.source_color || "#6366f1"}"></span>`).join("")}
${dayEvents.length > 5 ? `<span style="font-size:8px;color:#888">+${dayEvents.length - 5}</span>` : ""}
${dayEvents.length > 5 ? `<span style="font-size:8px;color:var(--rs-text-muted)">+${dayEvents.length - 5}</span>` : ""}
</div>
${dayEvents.slice(0, 2).map(e => {
const evColor = e.source_color || "#6366f1";
@ -1366,10 +1366,10 @@ class FolkCalendarView extends HTMLElement {
${e.description ? `<div class="modal-field">${this.esc(e.description)}</div>` : ""}
<div class="modal-field">${new Date(e.start_time).toLocaleString()}${e.end_time ? ` \u2013 ${new Date(e.end_time).toLocaleString()}` : ""}</div>
${e.location_name ? `<div class="modal-field">\u{1F4CD} ${this.esc(e.location_name)}</div>` : ""}
${e.location_breadcrumb ? `<div class="modal-field" style="font-size:11px;color:#64748b">${this.esc(e.location_breadcrumb)}</div>` : ""}
${e.location_breadcrumb ? `<div class="modal-field" style="font-size:11px;color:var(--rs-text-muted)">${this.esc(e.location_breadcrumb)}</div>` : ""}
${e.source_name ? `<div class="modal-field" style="margin-top:8px"><span class="src-badge" style="border-color:${e.source_color || "#666"};color:${e.source_color || "#aaa"}">${this.esc(e.source_name)}</span></div>` : ""}
${e.is_virtual ? `<div class="modal-field">\u{1F4BB} ${this.esc(e.virtual_platform || "Virtual")} ${e.virtual_url ? `<a href="${e.virtual_url}" target="_blank" style="color:#6366f1">Join</a>` : ""}</div>` : ""}
${e.latitude != null ? `<div class="modal-field" style="font-size:11px;color:#64748b">\u{1F4CD} ${e.latitude.toFixed(4)}, ${e.longitude.toFixed(4)}</div>` : ""}
${e.is_virtual ? `<div class="modal-field">\u{1F4BB} ${this.esc(e.virtual_platform || "Virtual")} ${e.virtual_url ? `<a href="${e.virtual_url}" target="_blank" style="color:var(--rs-primary-hover)">Join</a>` : ""}</div>` : ""}
${e.latitude != null ? `<div class="modal-field" style="font-size:11px;color:var(--rs-text-muted)">\u{1F4CD} ${e.latitude.toFixed(4)}, ${e.longitude.toFixed(4)}</div>` : ""}
</div>
</div>`;
}
@ -1738,52 +1738,52 @@ class FolkCalendarView extends HTMLElement {
private getStyles(): string {
return `
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: #e0e0e0; padding: 0.5rem; }
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: var(--rs-text-primary); padding: 0.5rem; }
* { box-sizing: border-box; }
.error { color: #ef5350; text-align: center; padding: 8px; }
.error { color: var(--rs-error); text-align: center; padding: 8px; }
/* ── Nav ── */
.nav { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; min-height: 36px; flex-wrap: wrap; }
.nav-btn { padding: 6px 12px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.12); background: transparent; color: #94a3b8; cursor: pointer; font-size: 14px; -webkit-tap-highlight-color: transparent; }
.nav-btn:hover { color: #e2e8f0; border-color: rgba(255,255,255,0.2); }
.nav-btn.active { border-color: #6366f1; color: #6366f1; }
.nav-title { font-size: 15px; font-weight: 600; flex: 1; text-align: center; color: #e2e8f0; }
.nav-primary { padding: 6px 14px; border-radius: 6px; border: none; background: #4f46e5; color: #fff; font-weight: 600; cursor: pointer; font-size: 12px; }
.nav-btn { padding: 6px 12px; border-radius: 6px; border: 1px solid var(--rs-border); background: transparent; color: var(--rs-text-secondary); cursor: pointer; font-size: 14px; -webkit-tap-highlight-color: transparent; }
.nav-btn:hover { color: var(--rs-text-primary); border-color: var(--rs-border-strong); }
.nav-btn.active { border-color: var(--rs-primary-hover); color: var(--rs-primary-hover); }
.nav-title { font-size: 15px; font-weight: 600; flex: 1; text-align: center; color: var(--rs-text-primary); }
.nav-primary { padding: 6px 14px; border-radius: 6px; border: none; background: var(--rs-primary); color: #fff; font-weight: 600; cursor: pointer; font-size: 12px; }
/* ── Lunar Overlay ── */
.lunar-overlay { background: #16161e; border: 1px solid #222; border-radius: 8px; padding: 0; margin-bottom: 12px; overflow: hidden; }
.lunar-overlay { background: var(--rs-bg-surface); border: 1px solid var(--rs-border); border-radius: 8px; padding: 0; margin-bottom: 12px; overflow: hidden; }
.lunar-summary { display: flex; align-items: center; gap: 12px; padding: 8px 12px; cursor: pointer; user-select: none; transition: background 0.15s; }
.lunar-summary:hover { background: rgba(255,255,255,0.04); }
.lunar-summary-phase { font-size: 13px; font-weight: 600; color: #e2e8f0; text-transform: capitalize; white-space: nowrap; }
.lunar-summary-stats { font-size: 11px; color: #94a3b8; white-space: nowrap; }
.lunar-summary-chevron { margin-left: auto; font-size: 10px; color: #64748b; }
.lunar-expanded { border-top: 1px solid #222; padding: 12px; }
.lunar-summary:hover { background: var(--rs-bg-hover); }
.lunar-summary-phase { font-size: 13px; font-weight: 600; color: var(--rs-text-primary); text-transform: capitalize; white-space: nowrap; }
.lunar-summary-stats { font-size: 11px; color: var(--rs-text-secondary); white-space: nowrap; }
.lunar-summary-chevron { margin-left: auto; font-size: 10px; color: var(--rs-text-muted); }
.lunar-expanded { border-top: 1px solid var(--rs-border); padding: 12px; }
.phase-chips { display: flex; gap: 6px; overflow-x: auto; padding: 8px 0 4px; }
.phase-chip { display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px; border-radius: 16px; border: 1px solid #333; font-size: 11px; color: #94a3b8; white-space: nowrap; flex-shrink: 0; transition: all 0.15s; }
.phase-chip.current { border-color: #6366f1; color: #818cf8; background: rgba(99,102,241,0.08); }
.phase-chip { display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px; border-radius: 16px; border: 1px solid var(--rs-border-strong); font-size: 11px; color: var(--rs-text-secondary); white-space: nowrap; flex-shrink: 0; transition: all 0.15s; }
.phase-chip.current { border-color: var(--rs-primary-hover); color: #818cf8; background: var(--rs-bg-active); }
.phase-chip.past { opacity: 0.4; }
.phase-chip-label { text-transform: capitalize; }
/* ── Zoom Controller ── */
.zoom-ctrl { display: flex; align-items: center; gap: 6px; margin-bottom: 12px; padding: 8px 10px; background: #16161e; border: 1px solid #222; border-radius: 8px; flex-wrap: wrap; }
.zoom-btn { width: 28px; height: 28px; border-radius: 50%; border: 1px solid #333; background: transparent; color: #94a3b8; cursor: pointer; font-size: 16px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.zoom-btn:hover { border-color: #6366f1; color: #e2e8f0; }
.zoom-ctrl { display: flex; align-items: center; gap: 6px; margin-bottom: 12px; padding: 8px 10px; background: var(--rs-bg-surface); border: 1px solid var(--rs-border); border-radius: 8px; flex-wrap: wrap; }
.zoom-btn { width: 28px; height: 28px; border-radius: 50%; border: 1px solid var(--rs-border-strong); background: transparent; color: var(--rs-text-secondary); cursor: pointer; font-size: 16px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.zoom-btn:hover { border-color: var(--rs-primary-hover); color: var(--rs-text-primary); }
.zoom-btn:disabled { opacity: 0.3; cursor: not-allowed; }
.zoom-btn:disabled:hover { border-color: #333; color: #94a3b8; }
.zoom-btn:disabled:hover { border-color: var(--rs-border-strong); color: var(--rs-text-secondary); }
.zoom-track { flex: 1; display: flex; align-items: center; gap: 0; min-width: 200px; }
.zoom-tick { flex: 1; text-align: center; cursor: pointer; padding: 4px 0; }
.zoom-tick-dot { width: 12px; height: 12px; border-radius: 50%; border: 2px solid #333; background: #16161e; margin: 0 auto; transition: all 0.15s; }
.zoom-tick-dot.active { border-color: #6366f1; background: #4f46e5; transform: scale(1.2); }
.zoom-tick-label { font-size: 9px; color: #4a5568; margin-top: 3px; transition: color 0.15s; }
.zoom-tick-dot { width: 12px; height: 12px; border-radius: 50%; border: 2px solid var(--rs-border-strong); background: var(--rs-bg-surface); margin: 0 auto; transition: all 0.15s; }
.zoom-tick-dot.active { border-color: var(--rs-primary-hover); background: var(--rs-primary); transform: scale(1.2); }
.zoom-tick-label { font-size: 9px; color: var(--rs-text-muted); margin-top: 3px; transition: color 0.15s; }
.zoom-tick-label.active { color: #818cf8; font-weight: 600; }
.coupling-btn { padding: 4px 10px; border-radius: 12px; border: 1px solid #333; background: transparent; color: #64748b; cursor: pointer; font-size: 11px; transition: all 0.15s; white-space: nowrap; flex-shrink: 0; }
.coupling-btn:hover { border-color: #555; color: #94a3b8; }
.coupling-btn.coupled { border-color: #6366f1; color: #818cf8; background: rgba(99,102,241,0.08); }
.coupling-btn { padding: 4px 10px; border-radius: 12px; border: 1px solid var(--rs-border-strong); background: transparent; color: var(--rs-text-muted); cursor: pointer; font-size: 11px; transition: all 0.15s; white-space: nowrap; flex-shrink: 0; }
.coupling-btn:hover { border-color: var(--rs-border-strong); color: var(--rs-text-secondary); }
.coupling-btn.coupled { border-color: var(--rs-primary-hover); color: #818cf8; background: var(--rs-bg-active); }
/* ── Sources ── */
.sources { display: flex; gap: 6px; margin-bottom: 10px; flex-wrap: wrap; }
.src-badge { font-size: 10px; padding: 3px 8px; border-radius: 10px; border: 1px solid #333; cursor: pointer; transition: opacity 0.15s; user-select: none; }
.src-badge { font-size: 10px; padding: 3px 8px; border-radius: 10px; border: 1px solid var(--rs-border-strong); cursor: pointer; transition: opacity 0.15s; user-select: none; }
.src-badge:hover { filter: brightness(1.2); }
.src-badge.filtered { opacity: 0.3; text-decoration: line-through; }
@ -1793,137 +1793,137 @@ class FolkCalendarView extends HTMLElement {
.calendar-pane { overflow: auto; min-width: 0; }
/* ── Map Panel ── */
.map-panel { background: #0d1117; border: 1px solid #333; border-radius: 12px; overflow: hidden; display: flex; flex-direction: column; }
.map-panel--floating { position: absolute; bottom: 8px; right: 8px; width: 380px; height: 320px; resize: both; z-index: 100; box-shadow: 0 8px 32px rgba(0,0,0,0.5); }
.map-panel { background: var(--rs-bg-surface-sunken); border: 1px solid var(--rs-border-strong); border-radius: 12px; overflow: hidden; display: flex; flex-direction: column; }
.map-panel--floating { position: absolute; bottom: 8px; right: 8px; width: 380px; height: 320px; resize: both; z-index: 100; box-shadow: var(--rs-shadow-lg); }
.map-panel--docked { min-height: 400px; }
.map-panel-header { display: flex; align-items: center; justify-content: space-between; padding: 6px 10px; background: #16161e; border-bottom: 1px solid #222; cursor: default; flex-shrink: 0; }
.map-panel-title { font-size: 12px; font-weight: 500; color: #94a3b8; }
.map-panel-header { display: flex; align-items: center; justify-content: space-between; padding: 6px 10px; background: var(--rs-bg-surface); border-bottom: 1px solid var(--rs-border); cursor: default; flex-shrink: 0; }
.map-panel-title { font-size: 12px; font-weight: 500; color: var(--rs-text-secondary); }
.map-panel-controls { display: flex; gap: 4px; }
.map-ctrl-btn { width: 24px; height: 24px; border-radius: 4px; border: 1px solid #333; background: transparent; color: #94a3b8; cursor: pointer; font-size: 12px; display: flex; align-items: center; justify-content: center; }
.map-ctrl-btn:hover { border-color: #6366f1; color: #e2e8f0; }
.map-ctrl-btn { width: 24px; height: 24px; border-radius: 4px; border: 1px solid var(--rs-border-strong); background: transparent; color: var(--rs-text-secondary); cursor: pointer; font-size: 12px; display: flex; align-items: center; justify-content: center; }
.map-ctrl-btn:hover { border-color: var(--rs-primary-hover); color: var(--rs-text-primary); }
.map-body { flex: 1; position: relative; min-height: 200px; }
.map-overlay-label { position: absolute; top: 8px; right: 8px; z-index: 500; background: rgba(22,22,30,0.9); border: 1px solid #333; border-radius: 6px; padding: 4px 10px; font-size: 11px; color: #94a3b8; pointer-events: none; }
.map-fab { position: absolute; bottom: 16px; right: 16px; width: 44px; height: 44px; border-radius: 50%; border: 1px solid #333; background: #16161e; color: #e2e8f0; cursor: pointer; font-size: 20px; display: flex; align-items: center; justify-content: center; z-index: 100; box-shadow: 0 4px 16px rgba(0,0,0,0.4); transition: all 0.15s; }
.map-fab:hover { border-color: #6366f1; background: #1e1e2e; transform: scale(1.1); }
.map-overlay-label { position: absolute; top: 8px; right: 8px; z-index: 500; background: var(--rs-bg-overlay); border: 1px solid var(--rs-border-strong); border-radius: 6px; padding: 4px 10px; font-size: 11px; color: var(--rs-text-secondary); pointer-events: none; }
.map-fab { position: absolute; bottom: 16px; right: 16px; width: 44px; height: 44px; border-radius: 50%; border: 1px solid var(--rs-border-strong); background: var(--rs-bg-surface); color: var(--rs-text-primary); cursor: pointer; font-size: 20px; display: flex; align-items: center; justify-content: center; z-index: 100; box-shadow: var(--rs-shadow-md); transition: all 0.15s; }
.map-fab:hover { border-color: var(--rs-primary-hover); background: var(--rs-bg-surface-raised); transform: scale(1.1); }
/* ── Synodic (reused in overlay) ── */
.synodic-section { margin: 0 0 8px; }
.synodic-labels { display: flex; justify-content: space-between; font-size: 11px; color: #64748b; margin-bottom: 6px; }
.synodic-bar { height: 14px; background: #222; border-radius: 7px; overflow: visible; position: relative; }
.synodic-fill { height: 100%; background: linear-gradient(to right, #1a1a2e, #e2e8f0, #1a1a2e); border-radius: 7px; transition: width 0.3s; }
.synodic-labels { display: flex; justify-content: space-between; font-size: 11px; color: var(--rs-text-muted); margin-bottom: 6px; }
.synodic-bar { height: 14px; background: var(--rs-border); border-radius: 7px; overflow: visible; position: relative; }
.synodic-fill { height: 100%; background: linear-gradient(to right, var(--rs-bg-surface), var(--rs-text-primary), var(--rs-bg-surface)); border-radius: 7px; transition: width 0.3s; }
.synodic-marker { position: absolute; top: -2px; font-size: 12px; transform: translateX(-50%); pointer-events: none; }
/* ── Month Grid ── */
.weekdays { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; margin-bottom: 4px; }
.wd { text-align: center; font-size: 11px; color: #64748b; padding: 4px; font-weight: 600; }
.wd { text-align: center; font-size: 11px; color: var(--rs-text-muted); padding: 4px; font-weight: 600; }
.grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.day { background: #16161e; border: 1px solid #222; border-radius: 6px; min-height: 80px; padding: 6px; cursor: pointer; position: relative; -webkit-tap-highlight-color: transparent; }
.day:hover { border-color: #444; }
.day.today { border-color: #6366f1; background: rgba(99,102,241,0.06); }
.day.expanded { border-color: #6366f1; background: rgba(99,102,241,0.1); }
.day { background: var(--rs-bg-surface); border: 1px solid var(--rs-border); border-radius: 6px; min-height: 80px; padding: 6px; cursor: pointer; position: relative; -webkit-tap-highlight-color: transparent; }
.day:hover { border-color: var(--rs-border-strong); }
.day.today { border-color: var(--rs-primary-hover); background: var(--rs-bg-active); }
.day.expanded { border-color: var(--rs-primary-hover); background: rgba(99,102,241,0.1); }
.day.other { opacity: 0.3; }
.day-num { font-size: 12px; font-weight: 600; margin-bottom: 2px; display: flex; justify-content: space-between; }
.moon { font-size: 10px; opacity: 0.7; }
.dots { display: flex; flex-wrap: wrap; gap: 1px; }
.dot { width: 6px; height: 6px; border-radius: 50%; display: inline-block; margin: 1px; }
.ev-label { font-size: 9px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: #aaa; line-height: 1.4; padding: 1px 3px; border-radius: 3px; cursor: pointer; }
.ev-label:hover { background: rgba(255,255,255,0.08); }
.ev-time { color: #666; font-size: 8px; margin-right: 2px; }
.ev-label { font-size: 9px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--rs-text-secondary); line-height: 1.4; padding: 1px 3px; border-radius: 3px; cursor: pointer; }
.ev-label:hover { background: var(--rs-bg-hover); }
.ev-time { color: var(--rs-text-muted); font-size: 8px; margin-right: 2px; }
.ev-bell { margin-right: 2px; font-size: 8px; }
.ev-loc { color: #64748b; font-size: 7px; margin-left: 3px; }
.ev-loc { color: var(--rs-text-muted); font-size: 7px; margin-left: 3px; }
.ev-virtual { font-size: 8px; margin-right: 2px; vertical-align: middle; }
/* ── Drop Target ── */
.day.drop-target { background: rgba(245,158,11,0.15); border: 2px dashed #f59e0b; }
.day.drop-target { background: rgba(245,158,11,0.15); border: 2px dashed var(--rs-warning); }
/* ── Day Detail Panel ── */
.day-detail { grid-column: 1 / -1; background: #1a1a2e; border: 1px solid #334155; border-radius: 8px; padding: 12px; }
.day-detail { grid-column: 1 / -1; background: var(--rs-bg-surface); border: 1px solid var(--rs-bg-surface-raised); border-radius: 8px; padding: 12px; }
.dd-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
.dd-date { font-size: 14px; font-weight: 600; color: #e2e8f0; }
.dd-close { background: none; border: none; color: #64748b; font-size: 18px; cursor: pointer; padding: 4px 8px; }
.dd-date { font-size: 14px; font-weight: 600; color: var(--rs-text-primary); }
.dd-close { background: none; border: none; color: var(--rs-text-muted); font-size: 18px; cursor: pointer; padding: 4px 8px; }
.dd-event { display: flex; gap: 8px; align-items: flex-start; padding: 8px; border-radius: 6px; margin-bottom: 4px; cursor: pointer; -webkit-tap-highlight-color: transparent; }
.dd-event:hover { background: rgba(255,255,255,0.05); }
.dd-event:hover { background: var(--rs-bg-hover); }
.dd-color { width: 4px; border-radius: 2px; align-self: stretch; flex-shrink: 0; }
.dd-info { flex: 1; min-width: 0; }
.dd-title { font-size: 13px; font-weight: 500; color: #e2e8f0; }
.dd-meta { font-size: 11px; color: #94a3b8; margin-top: 2px; }
.dd-empty { font-size: 12px; color: #64748b; padding: 8px 0; }
.dd-desc { font-size: 11px; color: #64748b; margin-top: 3px; line-height: 1.4; }
.dd-title { font-size: 13px; font-weight: 500; color: var(--rs-text-primary); }
.dd-meta { font-size: 11px; color: var(--rs-text-secondary); margin-top: 2px; }
.dd-empty { font-size: 12px; color: var(--rs-text-muted); padding: 8px 0; }
.dd-desc { font-size: 11px; color: var(--rs-text-muted); margin-top: 3px; line-height: 1.4; }
.dd-source { font-size: 9px; padding: 1px 6px; border-radius: 8px; border: 1px solid; margin-left: 6px; vertical-align: middle; }
/* ── Event Modal ── */
.modal-bg { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.6); display: flex; align-items: center; justify-content: center; z-index: 1000; }
.modal { background: #1e1e2e; border: 1px solid #333; border-radius: 12px; padding: 20px; max-width: 400px; width: 90%; }
.modal { background: var(--rs-bg-surface); border: 1px solid var(--rs-border-strong); border-radius: 12px; padding: 20px; max-width: 400px; width: 90%; }
.modal-title { font-size: 16px; font-weight: 600; margin-bottom: 12px; }
.modal-field { font-size: 13px; color: #aaa; margin-bottom: 6px; }
.modal-close { float: right; background: none; border: none; color: #888; font-size: 18px; cursor: pointer; }
.modal-field { font-size: 13px; color: var(--rs-text-secondary); margin-bottom: 6px; }
.modal-close { float: right; background: none; border: none; color: var(--rs-text-muted); font-size: 18px; cursor: pointer; }
/* ── Day View ── */
.day-view { position: relative; }
.day-view-header { font-size: 13px; color: #94a3b8; margin-bottom: 8px; font-weight: 500; }
.day-allday { background: #16161e; border: 1px solid #222; border-radius: 8px; padding: 8px; margin-bottom: 8px; }
.day-allday-label { font-size: 10px; color: #64748b; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 4px; }
.timeline { position: relative; border-left: 1px solid #222; margin-left: 44px; }
.hour-row { display: flex; min-height: 48px; border-bottom: 1px solid rgba(255,255,255,0.04); position: relative; }
.hour-label { position: absolute; left: -48px; top: -7px; width: 40px; text-align: right; font-size: 10px; color: #4a5568; font-variant-numeric: tabular-nums; }
.day-view-header { font-size: 13px; color: var(--rs-text-secondary); margin-bottom: 8px; font-weight: 500; }
.day-allday { background: var(--rs-bg-surface); border: 1px solid var(--rs-border); border-radius: 8px; padding: 8px; margin-bottom: 8px; }
.day-allday-label { font-size: 10px; color: var(--rs-text-muted); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 4px; }
.timeline { position: relative; border-left: 1px solid var(--rs-border); margin-left: 44px; }
.hour-row { display: flex; min-height: 48px; border-bottom: 1px solid var(--rs-border-subtle); position: relative; }
.hour-label { position: absolute; left: -48px; top: -7px; width: 40px; text-align: right; font-size: 10px; color: var(--rs-text-muted); font-variant-numeric: tabular-nums; }
.hour-content { flex: 1; position: relative; padding-left: 8px; }
.tl-event { position: absolute; left: 8px; right: 8px; border-radius: 6px; padding: 4px 8px; font-size: 11px; overflow: hidden; cursor: pointer; border-left: 3px solid; z-index: 1; transition: opacity 0.15s; }
.tl-event:hover { opacity: 0.85; }
.tl-event-title { font-weight: 600; color: #e2e8f0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tl-event-time { font-size: 10px; color: #94a3b8; }
.tl-event-loc { font-size: 10px; color: #64748b; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tl-event-desc { font-size: 9px; color: #64748b; margin-top: 2px; line-height: 1.3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tl-breadcrumb { font-size: 8px; color: #4a5568; background: rgba(255,255,255,0.04); padding: 1px 5px; border-radius: 3px; margin-top: 2px; display: inline-block; }
.tl-event-title { font-weight: 600; color: var(--rs-text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tl-event-time { font-size: 10px; color: var(--rs-text-secondary); }
.tl-event-loc { font-size: 10px; color: var(--rs-text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tl-event-desc { font-size: 9px; color: var(--rs-text-muted); margin-top: 2px; line-height: 1.3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tl-breadcrumb { font-size: 8px; color: var(--rs-text-muted); background: var(--rs-bg-hover); padding: 1px 5px; border-radius: 3px; margin-top: 2px; display: inline-block; }
.tl-virtual { font-size: 9px; color: #818cf8; margin-left: 6px; }
.now-line { position: absolute; left: 0; right: 0; height: 2px; background: #ef4444; z-index: 5; }
.now-dot { position: absolute; left: -5px; top: -3px; width: 8px; height: 8px; border-radius: 50%; background: #ef4444; }
.now-line { position: absolute; left: 0; right: 0; height: 2px; background: var(--rs-error); z-index: 5; }
.now-dot { position: absolute; left: -5px; top: -3px; width: 8px; height: 8px; border-radius: 50%; background: var(--rs-error); }
/* ── Week View ── */
.week-view { overflow-x: auto; }
.week-header { display: grid; grid-template-columns: 44px repeat(7, 1fr); gap: 0; margin-bottom: 0; }
.week-day-header { text-align: center; padding: 8px 4px; font-size: 11px; color: #64748b; font-weight: 600; border-bottom: 1px solid #222; cursor: pointer; }
.week-day-header:hover { color: #e2e8f0; }
.week-day-header.today { color: #6366f1; border-bottom-color: #6366f1; }
.week-day-header { text-align: center; padding: 8px 4px; font-size: 11px; color: var(--rs-text-muted); font-weight: 600; border-bottom: 1px solid var(--rs-border); cursor: pointer; }
.week-day-header:hover { color: var(--rs-text-primary); }
.week-day-header.today { color: var(--rs-primary-hover); border-bottom-color: var(--rs-primary-hover); }
.week-day-num { font-size: 16px; font-weight: 700; display: block; }
.week-day-name { font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em; }
.week-grid { display: grid; grid-template-columns: 44px repeat(7, 1fr); }
.week-time-label { font-size: 10px; color: #4a5568; text-align: right; padding-right: 6px; font-variant-numeric: tabular-nums; height: 48px; }
.week-cell { border-left: 1px solid rgba(255,255,255,0.04); border-bottom: 1px solid rgba(255,255,255,0.04); min-height: 48px; position: relative; }
.week-time-label { font-size: 10px; color: var(--rs-text-muted); text-align: right; padding-right: 6px; font-variant-numeric: tabular-nums; height: 48px; }
.week-cell { border-left: 1px solid var(--rs-border-subtle); border-bottom: 1px solid var(--rs-border-subtle); min-height: 48px; position: relative; }
.week-cell.today { background: rgba(99,102,241,0.04); }
.week-event { position: absolute; left: 2px; right: 2px; border-radius: 4px; padding: 2px 4px; font-size: 10px; overflow: hidden; cursor: pointer; border-left: 2px solid; z-index: 1; }
.week-event:hover { opacity: 0.85; }
.week-event-title { font-weight: 600; color: #e2e8f0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.week-event-meta { font-size: 9px; color: #94a3b8; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.week-event-time { color: #64748b; }
.week-event-loc { margin-left: 3px; color: #4a5568; }
.week-event-title { font-weight: 600; color: var(--rs-text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.week-event-meta { font-size: 9px; color: var(--rs-text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.week-event-time { color: var(--rs-text-muted); }
.week-event-loc { margin-left: 3px; color: var(--rs-text-muted); }
.wk-virtual { font-size: 9px; margin-right: 2px; vertical-align: middle; }
/* ── Season View ── */
.season-header { text-align: center; margin-bottom: 12px; font-size: 16px; font-weight: 600; color: #e2e8f0; }
.season-q { font-size: 12px; color: #64748b; font-weight: 400; margin-left: 4px; }
.season-header { text-align: center; margin-bottom: 12px; font-size: 16px; font-weight: 600; color: var(--rs-text-primary); }
.season-q { font-size: 12px; color: var(--rs-text-muted); font-weight: 400; margin-left: 4px; }
.season-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.season-month-wrap { display: flex; flex-direction: column; gap: 4px; }
.season-cities { display: flex; flex-wrap: wrap; gap: 3px; padding: 2px 4px; }
.season-city-chip { font-size: 9px; color: #94a3b8; background: rgba(255,255,255,0.04); border: 1px solid #333; border-radius: 8px; padding: 1px 6px; white-space: nowrap; }
.season-city-chip { font-size: 9px; color: var(--rs-text-secondary); background: var(--rs-bg-hover); border: 1px solid var(--rs-border-strong); border-radius: 8px; padding: 1px 6px; white-space: nowrap; }
/* ── Year View ── */
.year-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
/* ── Mini-Month (shared) ── */
.mini-month { background: #16161e; border: 1px solid #222; border-radius: 8px; padding: 8px; cursor: pointer; transition: border-color 0.15s; }
.mini-month:hover { border-color: #444; }
.mini-month.current { border-color: #6366f1; background: rgba(99,102,241,0.06); }
.mini-month-title { font-size: 12px; font-weight: 600; text-align: center; color: #e2e8f0; margin-bottom: 4px; }
.mini-month { background: var(--rs-bg-surface); border: 1px solid var(--rs-border); border-radius: 8px; padding: 8px; cursor: pointer; transition: border-color 0.15s; }
.mini-month:hover { border-color: var(--rs-border-strong); }
.mini-month.current { border-color: var(--rs-primary-hover); background: var(--rs-bg-active); }
.mini-month-title { font-size: 12px; font-weight: 600; text-align: center; color: var(--rs-text-primary); margin-bottom: 4px; }
.mini-wd-row { display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px; margin-bottom: 2px; }
.mini-wd { text-align: center; font-size: 8px; color: #4a5568; font-weight: 600; }
.mini-wd { text-align: center; font-size: 8px; color: var(--rs-text-muted); font-weight: 600; }
.mini-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px; }
.mini-day { position: relative; display: flex; align-items: center; justify-content: center; font-size: 10px; color: #94a3b8; border-radius: 3px; aspect-ratio: 1; cursor: pointer; }
.mini-day:hover { background: rgba(255,255,255,0.08); }
.mini-day.today { background: #4f46e5; color: #fff; font-weight: 700; }
.mini-day { position: relative; display: flex; align-items: center; justify-content: center; font-size: 10px; color: var(--rs-text-secondary); border-radius: 3px; aspect-ratio: 1; cursor: pointer; }
.mini-day:hover { background: var(--rs-bg-hover); }
.mini-day.today { background: var(--rs-primary); color: #fff; font-weight: 700; }
.mini-day.empty { cursor: default; }
.mini-dots { position: absolute; bottom: 1px; left: 50%; transform: translateX(-50%); display: flex; gap: 1px; }
.mini-dot { width: 3px; height: 3px; border-radius: 50%; flex-shrink: 0; }
.mini-hint { text-align: center; font-size: 11px; color: #4a5568; margin-top: 8px; }
.mini-hint { text-align: center; font-size: 11px; color: var(--rs-text-muted); margin-top: 8px; }
/* ── Transition Animations ── */
.calendar-pane { position: relative; overflow: hidden; }
@ -1958,68 +1958,68 @@ class FolkCalendarView extends HTMLElement {
/* ── Variant Indicator ── */
.variant-indicator { display: flex; gap: 4px; align-items: center; padding: 0 6px; }
.variant-dot { width: 6px; height: 6px; border-radius: 50%; border: 1px solid #555; background: transparent; transition: all 0.15s; }
.variant-dot.active { background: #6366f1; border-color: #6366f1; }
.variant-dot { width: 6px; height: 6px; border-radius: 50%; border: 1px solid var(--rs-border-strong); background: transparent; transition: all 0.15s; }
.variant-dot.active { background: var(--rs-primary-hover); border-color: var(--rs-primary-hover); }
/* ── Horizontal Day View ── */
.dh-container { overflow-x: auto; overflow-y: hidden; position: relative; min-height: 180px; padding: 0 0 8px; }
.dh-hours { position: relative; height: 24px; border-bottom: 1px solid #222; }
.dh-hour { position: absolute; top: 0; height: 24px; font-size: 10px; color: #4a5568; text-align: center; border-left: 1px solid rgba(255,255,255,0.04); line-height: 24px; }
.dh-hours { position: relative; height: 24px; border-bottom: 1px solid var(--rs-border); }
.dh-hour { position: absolute; top: 0; height: 24px; font-size: 10px; color: var(--rs-text-muted); text-align: center; border-left: 1px solid var(--rs-border-subtle); line-height: 24px; }
.dh-events { position: relative; min-height: 140px; padding-top: 8px; }
.dh-event { position: absolute; top: 32px; height: auto; min-height: 48px; border-radius: 6px; padding: 4px 8px; font-size: 11px; overflow: hidden; cursor: pointer; border-top: 3px solid; z-index: 1; }
.dh-event:hover { opacity: 0.85; }
.dh-now { position: absolute; top: 0; bottom: 0; width: 2px; background: #ef4444; z-index: 5; }
.dh-now::before { content: ""; position: absolute; top: -3px; left: -3px; width: 8px; height: 8px; border-radius: 50%; background: #ef4444; }
.dh-event-loc { font-size: 9px; color: #64748b; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dh-now { position: absolute; top: 0; bottom: 0; width: 2px; background: var(--rs-error); z-index: 5; }
.dh-now::before { content: ""; position: absolute; top: -3px; left: -3px; width: 8px; height: 8px; border-radius: 50%; background: var(--rs-error); }
.dh-event-loc { font-size: 9px; color: var(--rs-text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dh-virtual { font-size: 9px; margin-right: 2px; vertical-align: middle; }
/* ── Month Transposed View ── */
.month-transposed { overflow-x: auto; }
.mt-header-row { display: flex; gap: 2px; margin-bottom: 4px; }
.mt-week-header { flex: 1; min-width: 48px; text-align: center; font-size: 10px; color: #4a5568; font-weight: 600; padding: 4px; }
.mt-week-header { flex: 1; min-width: 48px; text-align: center; font-size: 10px; color: var(--rs-text-muted); font-weight: 600; padding: 4px; }
.mt-row { display: flex; gap: 2px; margin-bottom: 2px; }
.mt-day-name { width: 40px; flex-shrink: 0; font-size: 11px; color: #64748b; font-weight: 600; display: flex; align-items: center; justify-content: flex-end; padding-right: 6px; }
.mt-cell { flex: 1; min-width: 48px; min-height: 36px; background: #16161e; border: 1px solid #222; border-radius: 4px; display: flex; align-items: center; justify-content: center; gap: 4px; cursor: pointer; position: relative; }
.mt-cell:hover { border-color: #444; }
.mt-cell.today { border-color: #6366f1; background: rgba(99,102,241,0.06); }
.mt-cell.weekend { background: rgba(255,255,255,0.02); }
.mt-day-name { width: 40px; flex-shrink: 0; font-size: 11px; color: var(--rs-text-muted); font-weight: 600; display: flex; align-items: center; justify-content: flex-end; padding-right: 6px; }
.mt-cell { flex: 1; min-width: 48px; min-height: 36px; background: var(--rs-bg-surface); border: 1px solid var(--rs-border); border-radius: 4px; display: flex; align-items: center; justify-content: center; gap: 4px; cursor: pointer; position: relative; }
.mt-cell:hover { border-color: var(--rs-border-strong); }
.mt-cell.today { border-color: var(--rs-primary-hover); background: var(--rs-bg-active); }
.mt-cell.weekend { background: var(--rs-bg-hover); }
.mt-cell.empty { background: transparent; border-color: transparent; cursor: default; }
.mt-num { font-size: 12px; color: #94a3b8; font-weight: 500; }
.mt-num { font-size: 12px; color: var(--rs-text-secondary); font-weight: 500; }
.mt-color-bar { display: flex; height: 3px; border-radius: 2px; overflow: hidden; width: 100%; position: absolute; bottom: 2px; left: 0; }
.mt-seg { height: 100%; min-width: 2px; }
.mt-count-num { font-size: 9px; color: #94a3b8; font-weight: 600; }
.mt-count-num { font-size: 9px; color: var(--rs-text-secondary); font-weight: 600; }
/* ── Year Vertical View ── */
.year-vertical { max-height: 600px; overflow-y: auto; }
.yv-month { display: flex; align-items: flex-start; gap: 8px; padding: 6px 0; border-bottom: 1px solid #1a1a2e; cursor: pointer; }
.yv-month:hover { background: rgba(255,255,255,0.02); }
.yv-label { width: 36px; flex-shrink: 0; font-size: 11px; font-weight: 600; color: #94a3b8; text-align: right; padding-top: 2px; }
.yv-month { display: flex; align-items: flex-start; gap: 8px; padding: 6px 0; border-bottom: 1px solid var(--rs-border-subtle); cursor: pointer; }
.yv-month:hover { background: var(--rs-bg-hover); }
.yv-label { width: 36px; flex-shrink: 0; font-size: 11px; font-weight: 600; color: var(--rs-text-secondary); text-align: right; padding-top: 2px; }
.yv-days { display: flex; flex-wrap: wrap; gap: 2px; flex: 1; }
.yv-day { position: relative; width: 22px; height: 22px; display: flex; align-items: center; justify-content: center; font-size: 9px; color: #64748b; border-radius: 3px; cursor: pointer; }
.yv-day:hover { background: rgba(255,255,255,0.08); color: #e2e8f0; }
.yv-day.today { background: #4f46e5; color: #fff; font-weight: 700; }
.yv-day { position: relative; width: 22px; height: 22px; display: flex; align-items: center; justify-content: center; font-size: 9px; color: var(--rs-text-muted); border-radius: 3px; cursor: pointer; }
.yv-day:hover { background: var(--rs-bg-hover); color: var(--rs-text-primary); }
.yv-day.today { background: var(--rs-primary); color: #fff; font-weight: 700; }
.yv-day.weekend { opacity: 0.5; }
.yv-dots { position: absolute; bottom: 1px; left: 50%; transform: translateX(-50%); display: flex; gap: 1px; }
.yv-dot { width: 3px; height: 3px; border-radius: 50%; flex-shrink: 0; }
.yv-country { font-size: 9px; color: #4a5568; margin-left: 6px; font-weight: 400; }
.yv-country { font-size: 9px; color: var(--rs-text-muted); margin-left: 6px; font-weight: 400; }
/* ── Multi-Year View ── */
.multi-year-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.my-year { background: #16161e; border: 1px solid #222; border-radius: 8px; padding: 8px; cursor: pointer; transition: border-color 0.15s; }
.my-year:hover { border-color: #444; }
.my-year.current { border-color: #6366f1; background: rgba(99,102,241,0.06); }
.my-year-label { font-size: 14px; font-weight: 700; text-align: center; color: #e2e8f0; margin-bottom: 6px; }
.my-year { background: var(--rs-bg-surface); border: 1px solid var(--rs-border); border-radius: 8px; padding: 8px; cursor: pointer; transition: border-color 0.15s; }
.my-year:hover { border-color: var(--rs-border-strong); }
.my-year.current { border-color: var(--rs-primary-hover); background: var(--rs-bg-active); }
.my-year-label { font-size: 14px; font-weight: 700; text-align: center; color: var(--rs-text-primary); margin-bottom: 6px; }
.my-months { display: grid; grid-template-columns: repeat(4, 1fr); gap: 2px; }
.micro-month { display: flex; align-items: center; gap: 2px; padding: 2px 3px; border-radius: 3px; cursor: pointer; overflow: hidden; }
.micro-month:hover { background: rgba(255,255,255,0.08); }
.micro-label { font-size: 8px; color: #4a5568; font-weight: 600; width: 8px; flex-shrink: 0; }
.micro-month:hover { background: var(--rs-bg-hover); }
.micro-label { font-size: 8px; color: var(--rs-text-muted); font-weight: 600; width: 8px; flex-shrink: 0; }
.micro-bar-stack { height: 3px; border-radius: 2px; flex-shrink: 0; display: flex; overflow: hidden; }
.micro-seg { height: 100%; min-width: 1px; }
.micro-count { font-size: 7px; color: #64748b; flex-shrink: 0; }
.micro-count { font-size: 7px; color: var(--rs-text-muted); flex-shrink: 0; }
/* ── Keyboard Hint ── */
.kbd-hint { text-align: center; font-size: 10px; color: #333; margin-top: 12px; padding-top: 8px; border-top: 1px solid #1a1a2e; }
.kbd-hint kbd { padding: 1px 4px; background: #16161e; border: 1px solid #222; border-radius: 3px; font-family: inherit; font-size: 9px; }
.kbd-hint { text-align: center; font-size: 10px; color: var(--rs-text-muted); margin-top: 12px; padding-top: 8px; border-top: 1px solid var(--rs-border-subtle); }
.kbd-hint kbd { padding: 1px 4px; background: var(--rs-bg-surface); border: 1px solid var(--rs-border); border-radius: 3px; font-family: inherit; font-size: 9px; }
/* ── Mobile ── */
@media (max-width: 768px) {

View File

@ -11,11 +11,11 @@
.flows-detail ::-webkit-scrollbar-track,
.flows-landing ::-webkit-scrollbar-track { background: transparent; }
.flows-detail ::-webkit-scrollbar-thumb,
.flows-landing ::-webkit-scrollbar-thumb { background: #475569; border-radius: 3px; }
.flows-landing ::-webkit-scrollbar-thumb { background: var(--rs-bg-surface-raised); border-radius: 3px; }
/* ── Shared utility classes ──────────────────────────── */
.flows-loading { text-align: center; color: #64748b; padding: 48px 16px; font-size: 14px; }
.flows-error { text-align: center; color: #ef4444; padding: 20px 16px; font-size: 14px; }
.flows-loading { text-align: center; color: var(--rs-text-muted); padding: 48px 16px; font-size: 14px; }
.flows-error { text-align: center; color: var(--rs-error); padding: 20px 16px; font-size: 14px; }
/* ── Landing page ────────────────────────────────────── */
.flows-landing { max-width: 960px; margin: 0 auto; padding: 24px 20px 64px; }
@ -26,63 +26,63 @@
display: grid; grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); gap: 12px;
}
.flows-features__card {
background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 20px;
background: var(--rs-bg-surface); border: 1px solid var(--rs-border-strong); border-radius: 8px; padding: 20px;
transition: border-color 0.2s;
}
.flows-features__card:hover { border-color: #475569; }
.flows-features__card:hover { border-color: var(--rs-bg-surface-raised); }
.flows-features__icon { font-size: 24px; margin-bottom: 8px; }
.flows-features__card h3 { font-size: 14px; font-weight: 600; color: #e2e8f0; margin: 0 0 6px; }
.flows-features__card p { font-size: 12px; color: #94a3b8; line-height: 1.6; margin: 0; }
.flows-features__card h3 { font-size: 14px; font-weight: 600; color: var(--rs-text-primary); margin: 0 0 6px; }
.flows-features__card p { font-size: 12px; color: var(--rs-text-secondary); line-height: 1.6; margin: 0; }
/* Flow list */
.flows-flows { margin-bottom: 48px; }
.flows-flows__header { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 16px; gap: 12px; flex-wrap: wrap; }
.flows-flows__heading { font-size: 18px; font-weight: 600; color: #e2e8f0; margin: 0; }
.flows-flows__user { font-size: 12px; color: #64748b; }
.flows-flows__heading { font-size: 18px; font-weight: 600; color: var(--rs-text-primary); margin: 0; }
.flows-flows__user { font-size: 12px; color: var(--rs-text-muted); }
.flows-flows__grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 12px; }
.flows-flows__empty {
text-align: center; color: #64748b; padding: 32px 16px; font-size: 14px;
background: #1e293b; border: 1px solid #334155; border-radius: 8px;
text-align: center; color: var(--rs-text-muted); padding: 32px 16px; font-size: 14px;
background: var(--rs-bg-surface); border: 1px solid var(--rs-border-strong); border-radius: 8px;
}
.flows-flows__empty a { color: #6366f1; text-decoration: none; }
.flows-flows__empty a { color: var(--rs-primary-hover); text-decoration: none; }
.flows-flows__empty a:hover { text-decoration: underline; }
.flows-flow-card {
display: block; text-decoration: none;
background: #1e293b; border: 1px solid #334155; border-radius: 8px;
background: var(--rs-bg-surface); border: 1px solid var(--rs-border-strong); border-radius: 8px;
padding: 16px; cursor: pointer; transition: border-color 0.2s, transform 0.15s;
}
.flows-flow-card:hover { border-color: #6366f1; transform: translateY(-1px); }
.flows-flow-card__name { font-size: 15px; font-weight: 600; color: #e2e8f0; margin-bottom: 4px; }
.flows-flow-card:hover { border-color: var(--rs-primary-hover); transform: translateY(-1px); }
.flows-flow-card__name { font-size: 15px; font-weight: 600; color: var(--rs-text-primary); margin-bottom: 4px; }
.flows-flow-card__value { font-size: 20px; font-weight: 700; color: #0ea5e9; margin-bottom: 4px; }
.flows-flow-card__meta { font-size: 12px; color: #64748b; }
.flows-flow-card__meta { font-size: 12px; color: var(--rs-text-muted); }
/* About / how-it-works section */
.flows-about { margin-bottom: 48px; }
.flows-about__heading { font-size: 18px; font-weight: 600; color: #e2e8f0; margin: 0 0 20px; }
.flows-about__heading { font-size: 18px; font-weight: 600; color: var(--rs-text-primary); margin: 0 0 20px; }
/* Steps layout (replaces the old card grid for "how it works") */
.flows-about__steps { display: flex; flex-direction: column; gap: 16px; }
.flows-about__step {
display: flex; gap: 16px; align-items: flex-start;
background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 20px;
background: var(--rs-bg-surface); border: 1px solid var(--rs-border-strong); border-radius: 8px; padding: 20px;
}
.flows-about__step-num {
width: 32px; height: 32px; border-radius: 50%; flex-shrink: 0;
background: #4f46e5; color: #fff; font-weight: 700; font-size: 14px;
background: var(--rs-primary); color: #fff; font-weight: 700; font-size: 14px;
display: flex; align-items: center; justify-content: center;
}
.flows-about__step h3 { font-size: 14px; font-weight: 600; color: #e2e8f0; margin: 0 0 4px; }
.flows-about__step p { font-size: 13px; color: #94a3b8; line-height: 1.6; margin: 0; }
.flows-about__step h3 { font-size: 14px; font-weight: 600; color: var(--rs-text-primary); margin: 0 0 4px; }
.flows-about__step p { font-size: 13px; color: var(--rs-text-secondary); line-height: 1.6; margin: 0; }
/* Legacy about grid (kept for compat) */
.flows-about__grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 16px; }
.flows-about__card {
background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 20px;
background: var(--rs-bg-surface); border: 1px solid var(--rs-border-strong); border-radius: 8px; padding: 20px;
}
.flows-about__icon { font-size: 28px; margin-bottom: 8px; }
.flows-about__card h3 { font-size: 15px; font-weight: 600; color: #e2e8f0; margin: 0 0 8px; }
.flows-about__card p { font-size: 13px; color: #94a3b8; line-height: 1.6; margin: 0; }
.flows-about__card h3 { font-size: 15px; font-weight: 600; color: var(--rs-text-primary); margin: 0 0 8px; }
.flows-about__card p { font-size: 13px; color: var(--rs-text-secondary); line-height: 1.6; margin: 0; }
/* ── Detail view ─────────────────────────────────────── */
.flows-detail { max-width: 1100px; margin: 0 auto; padding: 16px 20px 64px; }
@ -91,27 +91,27 @@
/* ── Table tab — card grid ───────────────────────────── */
.flows-table { }
.flows-section { margin-bottom: 28px; }
.flows-section__title { font-size: 14px; font-weight: 600; color: #94a3b8; margin: 0 0 12px; text-transform: uppercase; letter-spacing: 0.05em; }
.flows-section__title { font-size: 14px; font-weight: 600; color: var(--rs-text-secondary); margin: 0 0 12px; text-transform: uppercase; letter-spacing: 0.05em; }
.flows-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; }
.flows-card {
background: #1e293b; border: 1px solid #334155; border-radius: 8px; padding: 16px;
background: var(--rs-bg-surface); border: 1px solid var(--rs-border-strong); border-radius: 8px; padding: 16px;
}
.flows-card__header { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.flows-card__icon { font-size: 18px; }
.flows-card__label { font-size: 14px; font-weight: 600; color: #e2e8f0; flex: 1; }
.flows-card__type { font-size: 11px; color: #64748b; text-transform: uppercase; }
.flows-card__label { font-size: 14px; font-weight: 600; color: var(--rs-text-primary); flex: 1; }
.flows-card__type { font-size: 11px; color: var(--rs-text-muted); text-transform: uppercase; }
.flows-card__status { font-size: 11px; font-weight: 600; text-transform: capitalize; }
.flows-card__desc { font-size: 12px; color: #94a3b8; margin-bottom: 10px; line-height: 1.5; }
.flows-card__desc { font-size: 12px; color: var(--rs-text-secondary); margin-bottom: 10px; line-height: 1.5; }
.flows-card__stat { margin-bottom: 10px; }
.flows-card__stat-value { font-size: 18px; font-weight: 700; color: #e2e8f0; }
.flows-card__stat-label { font-size: 12px; color: #64748b; margin-left: 4px; }
.flows-card__stat-value { font-size: 18px; font-weight: 700; color: var(--rs-text-primary); }
.flows-card__stat-label { font-size: 12px; color: var(--rs-text-muted); margin-left: 4px; }
.flows-card__stats { display: flex; justify-content: space-between; margin-bottom: 8px; }
/* Progress bar */
.flows-card__bar-container {
position: relative; height: 6px; background: #334155; border-radius: 3px;
position: relative; height: 6px; background: var(--rs-border-strong); border-radius: 3px;
margin-bottom: 10px; overflow: visible;
}
.flows-card__bar {
@ -121,29 +121,29 @@
.flows-card__bar--outcome { opacity: 0.8; }
.flows-card__bar-threshold {
position: absolute; top: -3px; width: 2px; height: 12px;
background: #fbbf24; border-radius: 1px;
background: var(--rs-warning); border-radius: 1px;
}
.flows-card__thresholds {
display: flex; gap: 12px; font-size: 11px; color: #64748b; margin-bottom: 8px;
display: flex; gap: 12px; font-size: 11px; color: var(--rs-text-muted); margin-bottom: 8px;
}
/* Allocation lists */
.flows-card__allocs { margin-top: 8px; padding-top: 8px; border-top: 1px solid #334155; }
.flows-card__alloc-title { font-size: 11px; color: #64748b; font-weight: 600; margin-bottom: 4px; text-transform: uppercase; }
.flows-card__alloc { font-size: 12px; color: #94a3b8; display: flex; align-items: center; gap: 6px; margin: 2px 0; }
.flows-card__allocs { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--rs-border-strong); }
.flows-card__alloc-title { font-size: 11px; color: var(--rs-text-muted); font-weight: 600; margin-bottom: 4px; text-transform: uppercase; }
.flows-card__alloc { font-size: 12px; color: var(--rs-text-secondary); display: flex; align-items: center; gap: 6px; margin: 2px 0; }
.flows-card__alloc-dot { width: 8px; height: 8px; border-radius: 2px; flex-shrink: 0; }
/* Status colors */
.flows-status--abundant { color: #fbbf24; }
.flows-status--sufficient { color: #10b981; }
.flows-status--abundant { color: var(--rs-warning); }
.flows-status--sufficient { color: var(--rs-success); }
.flows-status--seeking { color: #0ea5e9; }
.flows-status--critical { color: #ef4444; }
.flows-status--critical { color: var(--rs-error); }
/* ── Interactive canvas (Diagram tab) ───────────────── */
.flows-canvas-container {
position: relative; height: 70vh; min-height: 400px;
background: #0f172a; border-radius: 12px; border: 1px solid #334155;
background: var(--rs-bg-page); border-radius: 12px; border: 1px solid var(--rs-border-strong);
overflow: hidden; user-select: none; touch-action: none;
}
.flows-canvas-container--fullpage {
@ -154,14 +154,14 @@
.flows-nav-overlay {
position: absolute; top: 0; left: 0; right: 0; z-index: 15;
height: 44px; display: flex; align-items: center; padding: 0 16px; gap: 12px;
background: linear-gradient(to bottom, rgba(15,23,42,0.9) 0%, transparent 100%);
background: linear-gradient(to bottom, var(--rs-bg-overlay) 0%, transparent 100%);
pointer-events: none;
}
.flows-nav-overlay > * { pointer-events: auto; }
.flows-nav-overlay .rapp-nav__back { color: #94a3b8; text-decoration: none; font-size: 13px; }
.flows-nav-overlay .rapp-nav__back:hover { color: #e2e8f0; }
.flows-nav-overlay .rapp-nav__title { color: #e2e8f0; font-size: 14px; font-weight: 600; }
.flows-nav-overlay .rapp-nav__badge { font-size: 10px; color: #fbbf24; background: rgba(251,191,36,0.15); padding: 2px 8px; border-radius: 4px; }
.flows-nav-overlay .rapp-nav__back { color: var(--rs-text-secondary); text-decoration: none; font-size: 13px; }
.flows-nav-overlay .rapp-nav__back:hover { color: var(--rs-text-primary); }
.flows-nav-overlay .rapp-nav__title { color: var(--rs-text-primary); font-size: 14px; font-weight: 600; }
.flows-nav-overlay .rapp-nav__badge { font-size: 10px; color: var(--rs-warning); background: rgba(251,191,36,0.15); padding: 2px 8px; border-radius: 4px; }
/* Badge offset when nav overlay present */
.flows-canvas-container--fullpage .flows-canvas-badge { top: 54px; }
@ -179,32 +179,32 @@
display: flex; gap: 4px; flex-wrap: wrap; align-items: center;
}
.flows-canvas-btn {
padding: 5px 10px; border: 1px solid #475569; border-radius: 6px;
background: #1e293b; color: #e2e8f0; font-size: 11px; font-weight: 500;
padding: 5px 10px; border: 1px solid var(--rs-bg-surface-raised); border-radius: 6px;
background: var(--rs-bg-surface); color: var(--rs-text-primary); font-size: 11px; font-weight: 500;
cursor: pointer; white-space: nowrap; transition: background 0.15s, border-color 0.15s;
}
.flows-canvas-btn:hover { background: #334155; border-color: #64748b; }
.flows-canvas-btn--source { border-color: #10b981; color: #6ee7b7; }
.flows-canvas-btn--source:hover { background: #064e3b; }
.flows-canvas-btn--funnel { border-color: #3b82f6; color: #93c5fd; }
.flows-canvas-btn--funnel:hover { background: #1e3a5f; }
.flows-canvas-btn--outcome { border-color: #ec4899; color: #f9a8d4; }
.flows-canvas-btn--outcome:hover { background: #4a1942; }
.flows-canvas-btn--active { background: #4f46e5; border-color: #6366f1; color: #fff; }
.flows-canvas-btn:hover { background: var(--rs-border-strong); border-color: var(--rs-text-muted); }
.flows-canvas-btn--source { border-color: #10b981; color: var(--rflows-source-text, #6ee7b7); }
.flows-canvas-btn--source:hover { background: var(--rflows-source-hover-bg, #064e3b); }
.flows-canvas-btn--funnel { border-color: #3b82f6; color: var(--rflows-funnel-text, #93c5fd); }
.flows-canvas-btn--funnel:hover { background: var(--rflows-funnel-hover-bg, #1e3a5f); }
.flows-canvas-btn--outcome { border-color: #ec4899; color: var(--rflows-outcome-text, #f9a8d4); }
.flows-canvas-btn--outcome:hover { background: var(--rflows-outcome-hover-bg, #4a1942); }
.flows-canvas-btn--active { background: var(--rs-primary); border-color: var(--rs-primary-hover); color: #fff; }
.flows-canvas-sep {
width: 1px; height: 20px; background: #334155; margin: 0 4px;
width: 1px; height: 20px; background: var(--rs-border-strong); margin: 0 4px;
}
/* SVG node styles */
.flow-node { cursor: pointer; }
.flow-node:hover .node-bg { filter: brightness(1.15); }
.flow-node.selected .node-bg { stroke: #6366f1; stroke-width: 3; }
.flow-node.selected .node-bg { stroke: var(--rs-primary-hover); stroke-width: 3; }
.node-glow { filter: drop-shadow(0 0 6px rgba(251,191,36,0.5)); }
/* Editor panel — right side slide-in */
.flows-editor-panel {
position: absolute; top: 0; right: 0; bottom: 0; width: 320px; z-index: 20;
background: #1e293b; border-left: 1px solid #334155;
background: var(--rs-bg-surface); border-left: 1px solid var(--rs-border-strong);
transform: translateX(100%); transition: transform 0.25s ease;
overflow-y: auto; padding: 16px;
display: flex; flex-direction: column; gap: 12px;
@ -214,16 +214,16 @@
.editor-header {
display: flex; align-items: center; justify-content: space-between;
}
.editor-title { font-size: 14px; font-weight: 600; color: #e2e8f0; }
.editor-title { font-size: 14px; font-weight: 600; color: var(--rs-text-primary); }
.editor-close {
background: none; border: none; color: #94a3b8; font-size: 18px; cursor: pointer; padding: 2px 6px;
background: none; border: none; color: var(--rs-text-secondary); font-size: 18px; cursor: pointer; padding: 2px 6px;
}
.editor-close:hover { color: #e2e8f0; }
.editor-close:hover { color: var(--rs-text-primary); }
/* Analytics popout panel (left side) */
.flows-analytics-panel {
position: absolute; top: 0; left: 0; bottom: 0; width: 380px; z-index: 20;
background: #1e293b; border-right: 1px solid #334155;
background: var(--rs-bg-surface); border-right: 1px solid var(--rs-border-strong);
transform: translateX(-100%); transition: transform 0.25s ease;
overflow-y: auto; display: flex; flex-direction: column;
}
@ -231,68 +231,68 @@
.analytics-header {
display: flex; align-items: center; gap: 8px; padding: 12px 16px;
border-bottom: 1px solid #334155; flex-shrink: 0;
border-bottom: 1px solid var(--rs-border-strong); flex-shrink: 0;
}
.analytics-title { font-size: 14px; font-weight: 600; color: #e2e8f0; flex: 1; }
.analytics-title { font-size: 14px; font-weight: 600; color: var(--rs-text-primary); flex: 1; }
.analytics-close {
background: none; border: none; color: #94a3b8; font-size: 18px; cursor: pointer;
background: none; border: none; color: var(--rs-text-secondary); font-size: 18px; cursor: pointer;
}
.analytics-close:hover { color: #e2e8f0; }
.analytics-close:hover { color: var(--rs-text-primary); }
.analytics-tabs { display: flex; gap: 4px; }
.analytics-tab {
padding: 4px 10px; border: 1px solid #334155; border-radius: 4px;
background: transparent; color: #94a3b8; font-size: 11px; cursor: pointer;
padding: 4px 10px; border: 1px solid var(--rs-border-strong); border-radius: 4px;
background: transparent; color: var(--rs-text-secondary); font-size: 11px; cursor: pointer;
}
.analytics-tab:hover { background: #334155; color: #e2e8f0; }
.analytics-tab--active { background: #334155; color: #e2e8f0; }
.analytics-tab:hover { background: var(--rs-border-strong); color: var(--rs-text-primary); }
.analytics-tab--active { background: var(--rs-border-strong); color: var(--rs-text-primary); }
.analytics-content { padding: 16px; flex: 1; overflow-y: auto; }
.editor-field { display: flex; flex-direction: column; gap: 4px; }
.editor-label { font-size: 11px; color: #94a3b8; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; }
.editor-label { font-size: 11px; color: var(--rs-text-secondary); font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; }
.editor-input {
padding: 6px 10px; border: 1px solid #334155; border-radius: 6px;
background: #0f172a; color: #e2e8f0; font-size: 13px;
padding: 6px 10px; border: 1px solid var(--rs-border-strong); border-radius: 6px;
background: var(--rs-bg-page); color: var(--rs-text-primary); font-size: 13px;
}
.editor-input:focus { outline: none; border-color: #6366f1; }
.editor-input:focus { outline: none; border-color: var(--rs-primary-hover); }
.editor-select {
padding: 6px 10px; border: 1px solid #334155; border-radius: 6px;
background: #0f172a; color: #e2e8f0; font-size: 13px;
padding: 6px 10px; border: 1px solid var(--rs-border-strong); border-radius: 6px;
background: var(--rs-bg-page); color: var(--rs-text-primary); font-size: 13px;
}
.editor-alloc-row {
display: flex; align-items: center; gap: 6px; font-size: 12px; color: #94a3b8;
display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--rs-text-secondary);
}
.editor-alloc-row .editor-input { width: 60px; text-align: right; }
.editor-alloc-dot { width: 8px; height: 8px; border-radius: 2px; flex-shrink: 0; }
.editor-section {
padding-top: 8px; border-top: 1px solid #334155;
padding-top: 8px; border-top: 1px solid var(--rs-border-strong);
}
.editor-section-title { font-size: 12px; font-weight: 600; color: #64748b; margin-bottom: 6px; }
.editor-section-title { font-size: 12px; font-weight: 600; color: var(--rs-text-muted); margin-bottom: 6px; }
.editor-btn {
padding: 6px 14px; border: 1px solid #475569; border-radius: 6px;
background: #334155; color: #e2e8f0; font-size: 12px; cursor: pointer;
padding: 6px 14px; border: 1px solid var(--rs-bg-surface-raised); border-radius: 6px;
background: var(--rs-border-strong); color: var(--rs-text-primary); font-size: 12px; cursor: pointer;
transition: background 0.15s;
}
.editor-btn:hover { background: #475569; }
.editor-btn--danger { border-color: #ef4444; color: #fca5a5; }
.editor-btn--danger:hover { background: #7f1d1d; }
.editor-btn:hover { background: var(--rs-bg-surface-raised); }
.editor-btn--danger { border-color: var(--rs-error); color: var(--rflows-danger-text, #fca5a5); }
.editor-btn--danger:hover { background: var(--rflows-danger-hover-bg, #7f1d1d); }
/* Edge +/- controls */
.edge-controls {
display: flex; align-items: center; gap: 2px; font-size: 11px;
}
.edge-btn {
width: 18px; height: 18px; border: 1px solid #475569; border-radius: 4px;
background: #1e293b; color: #e2e8f0; font-size: 12px; cursor: pointer;
width: 18px; height: 18px; border: 1px solid var(--rs-bg-surface-raised); border-radius: 4px;
background: var(--rs-bg-surface); color: var(--rs-text-primary); font-size: 12px; cursor: pointer;
display: flex; align-items: center; justify-content: center; padding: 0;
}
.edge-btn:hover { background: #334155; }
.edge-pct { color: #e2e8f0; font-weight: 600; min-width: 30px; text-align: center; }
.edge-btn:hover { background: var(--rs-border-strong); }
.edge-pct { color: var(--rs-text-primary); font-weight: 600; min-width: 30px; text-align: center; }
/* Legend — bottom-left */
.flows-canvas-legend {
position: absolute; bottom: 10px; left: 10px; z-index: 10;
display: flex; flex-wrap: wrap; gap: 12px;
font-size: 11px; color: #94a3b8; background: rgba(15,23,42,0.85);
font-size: 11px; color: var(--rs-text-secondary); background: var(--rs-glass-bg);
padding: 6px 10px; border-radius: 8px;
}
.flows-canvas-legend-item { display: flex; align-items: center; gap: 4px; }
@ -307,18 +307,18 @@
/* Sufficiency badge — top-left */
.flows-canvas-badge {
position: absolute; top: 10px; left: 10px; z-index: 10;
background: rgba(15,23,42,0.85); border-radius: 8px; padding: 8px 14px;
background: var(--rs-glass-bg); border-radius: 8px; padding: 8px 14px;
display: flex; align-items: center; gap: 8px;
}
.flows-canvas-badge__score { font-size: 20px; font-weight: 700; }
.flows-canvas-badge__label { font-size: 10px; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.05em; }
.flows-canvas-badge__label { font-size: 10px; color: var(--rs-text-secondary); text-transform: uppercase; letter-spacing: 0.05em; }
/* Legacy diagram (kept for compat) */
.flows-diagram { overflow-x: auto; }
.flows-diagram svg { display: block; margin: 0 auto; }
.flows-diagram__legend {
display: flex; flex-wrap: wrap; gap: 16px; justify-content: center;
margin-top: 12px; font-size: 12px; color: #94a3b8;
margin-top: 12px; font-size: 12px; color: var(--rs-text-secondary);
}
.flows-diagram__legend-item { display: flex; align-items: center; gap: 5px; }
.flows-diagram__dot { width: 10px; height: 10px; border-radius: 3px; flex-shrink: 0; }
@ -328,20 +328,20 @@
/* ── Transactions tab ────────────────────────────────── */
.flows-tx-list { display: flex; flex-direction: column; gap: 4px; }
.flows-tx-empty { text-align: center; color: #64748b; padding: 48px 16px; font-size: 14px; }
.flows-tx-empty { text-align: center; color: var(--rs-text-muted); padding: 48px 16px; font-size: 14px; }
.flows-tx {
display: flex; align-items: center; gap: 12px; padding: 12px 16px;
background: #1e293b; border: 1px solid #334155; border-radius: 8px;
background: var(--rs-bg-surface); border: 1px solid var(--rs-border-strong); border-radius: 8px;
}
.flows-tx__icon { font-size: 16px; flex-shrink: 0; }
.flows-tx__body { flex: 1; min-width: 0; }
.flows-tx__desc { font-size: 13px; color: #e2e8f0; font-weight: 500; }
.flows-tx__meta { font-size: 11px; color: #64748b; margin-top: 2px; }
.flows-tx__desc { font-size: 13px; color: var(--rs-text-primary); font-weight: 500; }
.flows-tx__meta { font-size: 11px; color: var(--rs-text-muted); margin-top: 2px; }
.flows-tx__amount { font-size: 14px; font-weight: 600; white-space: nowrap; }
.flows-tx__amount--positive { color: #10b981; }
.flows-tx__amount--negative { color: #ef4444; }
.flows-tx__time { font-size: 11px; color: #64748b; white-space: nowrap; }
.flows-tx__amount--positive { color: var(--rs-success); }
.flows-tx__amount--negative { color: var(--rs-error); }
.flows-tx__time { font-size: 11px; color: var(--rs-text-muted); white-space: nowrap; }
/* ── Port & wiring ──────────────────────────────────── */
.port-group { pointer-events: all; }
@ -354,7 +354,7 @@
.port-group--wiring-dimmed { opacity: 0.15; pointer-events: none; }
.wiring-temp-path {
fill: none; stroke: #94a3b8; stroke-width: 2; stroke-dasharray: 8 4;
fill: none; stroke: var(--rs-text-secondary); stroke-width: 2; stroke-dasharray: 8 4;
stroke-linecap: round; animation: wire-dash 0.6s linear infinite;
}
@ -390,77 +390,77 @@
/* ── Node detail modals ──────────────────────────────── */
.flows-modal-backdrop {
position: fixed; inset: 0; z-index: 50;
background: rgba(0,0,0,0.6); display: flex;
background: var(--rs-bg-overlay); display: flex;
align-items: center; justify-content: center;
animation: modalFadeIn 0.15s ease-out;
}
@keyframes modalFadeIn { from { opacity: 0; } to { opacity: 1; } }
.flows-modal {
background: #1e293b; border-radius: 16px; padding: 24px;
background: var(--rs-bg-surface); border-radius: 16px; padding: 24px;
width: 440px; max-height: 85vh; overflow-y: auto;
border: 1px solid #334155; box-shadow: 0 20px 60px rgba(0,0,0,0.5);
border: 1px solid var(--rs-border-strong); box-shadow: var(--rs-shadow-lg);
animation: modalSlideIn 0.2s ease-out;
}
@keyframes modalSlideIn { from { transform: translateY(12px); opacity: 0; } to { transform: none; opacity: 1; } }
.flows-modal::-webkit-scrollbar { width: 6px; }
.flows-modal::-webkit-scrollbar-track { background: transparent; }
.flows-modal::-webkit-scrollbar-thumb { background: #475569; border-radius: 3px; }
.flows-modal::-webkit-scrollbar-thumb { background: var(--rs-bg-surface-raised); border-radius: 3px; }
.flows-modal__header {
display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px;
}
.flows-modal__close {
background: none; border: none; color: #94a3b8; font-size: 24px; cursor: pointer;
background: none; border: none; color: var(--rs-text-secondary); font-size: 24px; cursor: pointer;
padding: 2px 8px; border-radius: 4px; transition: color 0.15s;
}
.flows-modal__close:hover { color: #e2e8f0; }
.flows-modal__close:hover { color: var(--rs-text-primary); }
.flows-modal__progress-bar {
height: 8px; background: #334155; border-radius: 4px; overflow: hidden; margin-top: 8px;
height: 8px; background: var(--rs-border-strong); border-radius: 4px; overflow: hidden; margin-top: 8px;
}
.flows-modal__progress-fill { height: 100%; border-radius: 4px; transition: width 0.3s; }
/* Phase accordion */
.phase-tier-bar { display: flex; gap: 1px; height: 8px; border-radius: 4px; overflow: hidden; margin-bottom: 16px; }
.phase-tier-segment { flex: 1; transition: background 0.3s; }
.phase-card { border: 1px solid #334155; border-radius: 8px; overflow: hidden; margin-bottom: 8px; }
.phase-card { border: 1px solid var(--rs-border-strong); border-radius: 8px; overflow: hidden; margin-bottom: 8px; }
.phase-card--locked { opacity: 0.5; }
.phase-header {
padding: 10px 14px; cursor: pointer; display: flex; align-items: center; gap: 8px;
background: #0f172a; transition: background 0.15s;
background: var(--rs-bg-page); transition: background 0.15s;
}
.phase-header:hover { background: #1e293b; }
.phase-content { padding: 8px 14px 14px; border-top: 1px solid #334155; }
.phase-header:hover { background: var(--rs-bg-surface); }
.phase-content { padding: 8px 14px 14px; border-top: 1px solid var(--rs-border-strong); }
.phase-task {
display: flex; align-items: center; gap: 8px; font-size: 13px; color: #94a3b8; padding: 4px 0;
display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--rs-text-secondary); padding: 4px 0;
}
.phase-task input[type="checkbox"] { accent-color: #10b981; cursor: pointer; }
.phase-task--done { color: #64748b; text-decoration: line-through; }
.phase-task--done { color: var(--rs-text-muted); text-decoration: line-through; }
.phase-add-btn {
display: flex; align-items: center; gap: 4px; font-size: 12px; color: #64748b;
background: none; border: 1px dashed #334155; border-radius: 6px;
display: flex; align-items: center; gap: 4px; font-size: 12px; color: var(--rs-text-muted);
background: none; border: 1px dashed var(--rs-border-strong); border-radius: 6px;
padding: 4px 10px; cursor: pointer; margin-top: 6px; transition: all 0.15s;
}
.phase-add-btn:hover { color: #94a3b8; border-color: #475569; }
.phase-add-btn:hover { color: var(--rs-text-secondary); border-color: var(--rs-bg-surface-raised); }
/* Source type picker */
.source-type-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 16px; }
.source-type-btn {
display: flex; flex-direction: column; align-items: center; gap: 6px;
padding: 14px 8px; border-radius: 8px; border: 2px solid #334155;
background: #0f172a; color: #94a3b8; cursor: pointer; transition: all 0.15s;
padding: 14px 8px; border-radius: 8px; border: 2px solid var(--rs-border-strong);
background: var(--rs-bg-page); color: var(--rs-text-secondary); cursor: pointer; transition: all 0.15s;
font-size: 12px; font-weight: 500;
}
.source-type-btn:hover { border-color: #475569; background: #1e293b; }
.source-type-btn--active { border-color: #10b981; background: #064e3b; color: #6ee7b7; }
.source-type-btn:hover { border-color: var(--rs-bg-surface-raised); background: var(--rs-bg-surface); }
.source-type-btn--active { border-color: #10b981; background: var(--rflows-source-hover-bg, #064e3b); color: var(--rflows-source-text, #6ee7b7); }
/* Node hover tooltip */
.flows-node-tooltip {
position: absolute; z-index: 30; pointer-events: none;
background: rgba(15,23,42,0.95); border: 1px solid #475569; border-radius: 8px;
padding: 8px 12px; font-size: 12px; color: #e2e8f0;
box-shadow: 0 4px 12px rgba(0,0,0,0.4); white-space: nowrap;
background: var(--rs-glass-bg); border: 1px solid var(--rs-bg-surface-raised); border-radius: 8px;
padding: 8px 12px; font-size: 12px; color: var(--rs-text-primary);
box-shadow: var(--rs-shadow-md); white-space: nowrap;
}
.flows-node-tooltip__label { font-weight: 600; margin-bottom: 2px; }
.flows-node-tooltip__stat { color: #94a3b8; font-size: 11px; }
.flows-node-tooltip__stat { color: var(--rs-text-secondary); font-size: 11px; }
/* Sufficiency glow on funnel status text */
@keyframes sufficiencyPulse {
@ -478,8 +478,8 @@
/* Inline edit inputs (foreignObject) */
.inline-edit-input {
background: transparent; border: none; border-bottom: 1px solid #6366f1;
color: #e2e8f0; font-size: 13px; font-weight: 600; width: 100%;
background: transparent; border: none; border-bottom: 1px solid var(--rs-primary-hover);
color: var(--rs-text-primary); font-size: 13px; font-weight: 600; width: 100%;
outline: none; padding: 2px 4px; box-sizing: border-box;
font-family: system-ui, -apple-system, sans-serif;
}
@ -512,6 +512,30 @@
.port-group[data-port-side="left"] .port-arrow { /* horizontal arrow left handled inline */ }
.port-group[data-port-side="right"] .port-arrow { /* horizontal arrow right handled inline */ }
/* ── Light theme overrides ──────────────────────────── */
[data-theme="light"] {
--rflows-source-text: #059669;
--rflows-source-hover-bg: #d1fae5;
--rflows-funnel-text: #2563eb;
--rflows-funnel-hover-bg: #dbeafe;
--rflows-outcome-text: #db2777;
--rflows-outcome-hover-bg: #fce7f3;
--rflows-danger-text: #dc2626;
--rflows-danger-hover-bg: #fee2e2;
}
@media (prefers-color-scheme: light) {
:root:not([data-theme]) {
--rflows-source-text: #059669;
--rflows-source-hover-bg: #d1fae5;
--rflows-funnel-text: #2563eb;
--rflows-funnel-hover-bg: #dbeafe;
--rflows-outcome-text: #db2777;
--rflows-outcome-hover-bg: #fce7f3;
--rflows-danger-text: #dc2626;
--rflows-danger-hover-bg: #fee2e2;
}
}
/* ── Mobile responsive ──────────────────────────────── */
@media (max-width: 768px) {
.flows-diagram { overflow-x: auto; -webkit-overflow-scrolling: touch; }

View File

@ -50,9 +50,11 @@ const COLORS = {
spendingWaterfall: ["#8b5cf6", "#ec4899", "#06b6d4", "#3b82f6", "#10b981", "#6366f1"],
outcomePool: "#3b82f6",
goldenGlow: "#fbbf24",
bg: "#0f172a",
text: "#e2e8f0",
textMuted: "#94a3b8",
bg: "var(--rs-bg-page)",
surface: "var(--rs-bg-surface)",
surfaceRaised: "var(--rs-bg-surface-raised)",
text: "var(--rs-text-primary)",
textMuted: "var(--rs-text-secondary)",
};
function distributeWidths(percentages: number[], totalAvailable: number, minWidth: number): number[] {
@ -314,14 +316,14 @@ function renderBranch(b: BranchLayout): string {
return `
<path d="M ${b.x1} ${b.y1 - halfW} C ${cpx} ${b.y1 - halfW}, ${cpx} ${b.y2 - halfW}, ${b.x2} ${b.y2 - halfW} L ${b.x2} ${b.y2 + halfW} C ${cpx} ${b.y2 + halfW}, ${cpx} ${b.y1 + halfW}, ${b.x1} ${b.y1 + halfW} Z" fill="${b.color}" opacity="0.35"/>
<text x="${(b.x1 + b.x2) / 2}" y="${(b.y1 + b.y2) / 2 - 8}" text-anchor="middle" fill="${COLORS.textMuted}" font-size="10">${b.percentage}%</text>`;
<text x="${(b.x1 + b.x2) / 2}" y="${(b.y1 + b.y2) / 2 - 8}" text-anchor="middle" style="fill:${COLORS.textMuted}" font-size="10">${b.percentage}%</text>`;
}
function renderSource(s: SourceLayout): string {
return `
<rect x="${s.x}" y="${s.y}" width="${s.width}" height="${SOURCE_HEIGHT}" rx="8" fill="#1e293b" stroke="#334155"/>
<text x="${s.x + s.width / 2}" y="${s.y + 16}" text-anchor="middle" fill="${COLORS.text}" font-size="11" font-weight="600">${esc(s.label)}</text>
<text x="${s.x + s.width / 2}" y="${s.y + 30}" text-anchor="middle" fill="${COLORS.textMuted}" font-size="9">$${s.flowRate.toLocaleString()}/mo</text>`;
<rect x="${s.x}" y="${s.y}" width="${s.width}" height="${SOURCE_HEIGHT}" rx="8" style="fill:${COLORS.surface};stroke:${COLORS.surfaceRaised}"/>
<text x="${s.x + s.width / 2}" y="${s.y + 16}" text-anchor="middle" style="fill:${COLORS.text}" font-size="11" font-weight="600">${esc(s.label)}</text>
<text x="${s.x + s.width / 2}" y="${s.y + 30}" text-anchor="middle" style="fill:${COLORS.textMuted}" font-size="9">$${s.flowRate.toLocaleString()}/mo</text>`;
}
function renderFunnel(f: FunnelLayout): string {
@ -342,9 +344,9 @@ function renderFunnel(f: FunnelLayout): string {
${isSufficient ? `<rect x="${f.x - 4}" y="${f.y - 4}" width="${f.segmentLength + 8}" height="${f.riverWidth + 8}" rx="6" fill="none" stroke="${COLORS.goldenGlow}" stroke-width="2" opacity="0.6" style="animation:shimmer 2s ease-in-out infinite"/>` : ""}
<rect x="${f.x}" y="${f.y}" width="${f.segmentLength}" height="${f.riverWidth}" rx="4" fill="url(#${gradId})"/>
${[0, 1, 2].map((i) => `<rect x="${f.x}" y="${f.y + (f.riverWidth / 4) * i}" width="${f.segmentLength}" height="${f.riverWidth / 4}" fill="${colors[0]}" opacity="0.08" style="animation:waterFlow ${2 + i * 0.5}s linear infinite;animation-delay:${i * -0.6}s"/>`).join("")}
<text x="${f.x + f.segmentLength / 2}" y="${f.y - 12}" text-anchor="middle" fill="${COLORS.text}" font-size="13" font-weight="600">${esc(f.label)}</text>
<text x="${f.x + f.segmentLength / 2}" y="${f.y - 2}" text-anchor="middle" fill="${COLORS.textMuted}" font-size="10">$${Math.floor(f.data.currentValue).toLocaleString()} / $${Math.floor(threshold).toLocaleString()} ${isSufficient ? "✨" : ""}</text>
<rect x="${f.x}" y="${f.y + f.riverWidth + 4}" width="${f.segmentLength}" height="3" rx="1.5" fill="#334155"/>
<text x="${f.x + f.segmentLength / 2}" y="${f.y - 12}" text-anchor="middle" style="fill:${COLORS.text}" font-size="13" font-weight="600">${esc(f.label)}</text>
<text x="${f.x + f.segmentLength / 2}" y="${f.y - 2}" text-anchor="middle" style="fill:${COLORS.textMuted}" font-size="10">$${Math.floor(f.data.currentValue).toLocaleString()} / $${Math.floor(threshold).toLocaleString()} ${isSufficient ? "✨" : ""}</text>
<rect x="${f.x}" y="${f.y + f.riverWidth + 4}" width="${f.segmentLength}" height="3" rx="1.5" style="fill:${COLORS.surfaceRaised}"/>
<rect x="${f.x}" y="${f.y + f.riverWidth + 4}" width="${f.segmentLength * fillRatio}" height="3" rx="1.5" fill="${colors[0]}"/>`;
}
@ -353,11 +355,11 @@ function renderOutcome(o: OutcomeLayout): string {
const color = o.data.status === "completed" ? "#10b981" : o.data.status === "blocked" ? "#ef4444" : "#3b82f6";
return `
<rect x="${o.x}" y="${o.y}" width="${o.poolWidth}" height="${POOL_HEIGHT}" rx="8" fill="#1e293b" stroke="#334155"/>
<rect x="${o.x}" y="${o.y}" width="${o.poolWidth}" height="${POOL_HEIGHT}" rx="8" style="fill:${COLORS.surface};stroke:${COLORS.surfaceRaised}"/>
<rect x="${o.x + 2}" y="${o.y + POOL_HEIGHT - filled}" width="${o.poolWidth - 4}" height="${filled}" rx="6" fill="${color}" opacity="0.4"/>
${filled > 5 ? `<rect x="${o.x + 2}" y="${o.y + POOL_HEIGHT - filled}" width="${o.poolWidth - 4}" height="3" rx="1.5" fill="${color}" opacity="0.6" style="animation:waveFloat 2s ease-in-out infinite"/>` : ""}
<text x="${o.x + o.poolWidth / 2}" y="${o.y + POOL_HEIGHT + 14}" text-anchor="middle" fill="${COLORS.text}" font-size="10" font-weight="500">${esc(o.label)}</text>
<text x="${o.x + o.poolWidth / 2}" y="${o.y + POOL_HEIGHT + 26}" text-anchor="middle" fill="${COLORS.textMuted}" font-size="9">${Math.round(o.fillPercent)}%</text>`;
<text x="${o.x + o.poolWidth / 2}" y="${o.y + POOL_HEIGHT + 14}" text-anchor="middle" style="fill:${COLORS.text}" font-size="10" font-weight="500">${esc(o.label)}</text>
<text x="${o.x + o.poolWidth / 2}" y="${o.y + POOL_HEIGHT + 26}" text-anchor="middle" style="fill:${COLORS.textMuted}" font-size="9">${Math.round(o.fillPercent)}%</text>`;
}
function renderSufficiencyBadge(score: number, x: number, y: number): string {
@ -368,11 +370,11 @@ function renderSufficiencyBadge(score: number, x: number, y: number): string {
return `
<g transform="translate(${x}, ${y})">
<circle cx="24" cy="24" r="22" fill="#1e293b" stroke="#334155" stroke-width="1.5"/>
<circle cx="24" cy="24" r="18" fill="none" stroke="#334155" stroke-width="3"/>
<circle cx="24" cy="24" r="22" style="fill:${COLORS.surface};stroke:${COLORS.surfaceRaised}" stroke-width="1.5"/>
<circle cx="24" cy="24" r="18" fill="none" style="stroke:${COLORS.surfaceRaised}" stroke-width="3"/>
<circle cx="24" cy="24" r="18" fill="none" stroke="${color}" stroke-width="3" stroke-dasharray="${circumference}" stroke-dashoffset="${dashoffset}" transform="rotate(-90 24 24)" stroke-linecap="round"/>
<text x="24" y="22" text-anchor="middle" fill="${color}" font-size="11" font-weight="700">${pct}%</text>
<text x="24" y="34" text-anchor="middle" fill="${COLORS.textMuted}" font-size="7">ENOUGH</text>
<text x="24" y="34" text-anchor="middle" style="fill:${COLORS.textMuted}" font-size="7">ENOUGH</text>
</g>`;
}
@ -445,14 +447,14 @@ class FolkFlowRiver extends HTMLElement {
this.shadow.innerHTML = `
<style>
:host { display: block; }
.container { position: relative; overflow: auto; background: ${COLORS.bg}; border-radius: 12px; border: 1px solid #334155; max-height: 85vh; cursor: grab; }
.container { position: relative; overflow: auto; background: ${COLORS.bg}; border-radius: 12px; border: 1px solid var(--rs-bg-surface-raised); max-height: 85vh; cursor: grab; }
.container.dragging { cursor: grabbing; user-select: none; }
svg { display: block; }
.controls { position: absolute; top: 12px; left: 12px; display: flex; gap: 8px; }
.controls button { padding: 6px 12px; border-radius: 6px; border: 1px solid #334155; background: #1e293b; color: #94a3b8; cursor: pointer; font-size: 12px; }
.controls button:hover { border-color: #6366f1; color: #f1f5f9; }
.controls button.active { background: #4f46e5; border-color: #6366f1; color: #fff; }
.legend { position: absolute; bottom: 12px; left: 12px; background: rgba(15,23,42,0.9); border: 1px solid #334155; border-radius: 8px; padding: 8px 12px; font-size: 10px; color: #94a3b8; }
.controls button { padding: 6px 12px; border-radius: 6px; border: 1px solid var(--rs-bg-surface-raised); background: var(--rs-bg-surface); color: var(--rs-text-secondary); cursor: pointer; font-size: 12px; }
.controls button:hover { border-color: var(--rs-primary-hover); color: var(--rs-text-primary); }
.controls button.active { background: var(--rs-primary); border-color: var(--rs-primary-hover); color: #fff; }
.legend { position: absolute; bottom: 12px; left: 12px; background: var(--rs-glass-bg); border: 1px solid var(--rs-bg-surface-raised); border-radius: 8px; padding: 8px 12px; font-size: 10px; color: var(--rs-text-secondary); }
.legend-item { display: flex; align-items: center; gap: 6px; margin: 2px 0; }
.legend-dot { width: 8px; height: 8px; border-radius: 2px; }
@keyframes waterFlow { 0% { transform: translateY(0); } 100% { transform: translateY(100%); } }

View File

@ -215,7 +215,7 @@ class FolkFlowsApp extends HTMLElement {
private render() {
this.shadow.innerHTML = `
<style>
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: #e2e8f0; }
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: var(--rs-text-primary); }
*, *::before, *::after { box-sizing: border-box; }
</style>
<link rel="stylesheet" href="${this.getCssPath()}">
@ -246,15 +246,15 @@ class FolkFlowsApp extends HTMLElement {
<a href="${this.esc(demoUrl)}" class="rapp-nav__btn rapp-nav__btn--secondary">Demo</a>
${authed
? `<button class="rapp-nav__btn" data-action="create-flow">+ Create Flow</button>`
: `<span style="font-size:12px;color:#64748b">Sign in to create flows</span>`
: `<span style="font-size:12px;color:var(--rs-text-muted)">Sign in to create flows</span>`
}
</div>
</div>
<div class="flows-desc" style="color:#94a3b8;font-size:14px;line-height:1.6;max-width:600px;margin-bottom:24px">
<div class="flows-desc" style="color:var(--rs-text-secondary);font-size:14px;line-height:1.6;max-width:600px;margin-bottom:24px">
Design transparent resource flows with sufficiency-based cascading.
Funnels fill to their threshold, then overflow routes surplus to the next layer &mdash;
ensuring every level has <em style="color:#fbbf24;font-style:normal;font-weight:600">enough</em> before abundance cascades forward.
ensuring every level has <em style="color:var(--rs-warning);font-style:normal;font-weight:600">enough</em> before abundance cascades forward.
</div>
<div class="flows-features">
@ -1060,8 +1060,8 @@ class FolkFlowsApp extends HTMLElement {
const icon = icons[d.sourceType] || "\u{1F4B0}";
return `<g class="flow-node ${selected ? "selected" : ""}" data-node-id="${n.id}" transform="translate(${x},${y})">
<rect class="node-bg" x="0" y="0" width="${w}" height="${h}" rx="8" fill="#064e3b" stroke="${selected ? "#6366f1" : "#10b981"}" stroke-width="${selected ? 3 : 2}"/>
<text x="14" y="24" fill="#e2e8f0" font-size="15">${icon}</text>
<text x="36" y="24" fill="#e2e8f0" font-size="13" font-weight="600">${this.esc(d.label)}</text>
<text x="14" y="24" style="fill:var(--rs-text-primary)" font-size="15">${icon}</text>
<text x="36" y="24" style="fill:var(--rs-text-primary)" font-size="13" font-weight="600">${this.esc(d.label)}</text>
<text x="${w / 2}" y="46" text-anchor="middle" fill="#6ee7b7" font-size="11">$${d.flowRate.toLocaleString()}/mo</text>
${this.renderAllocBar(d.targetAllocations, w, h - 6)}
${this.renderPortsSvg(n)}
@ -1148,22 +1148,22 @@ class FolkFlowsApp extends HTMLElement {
<clipPath id="${clipId}"><path d="${funnelPath}"/></clipPath>
</defs>
${isOverflow ? `<path d="${funnelPath}" fill="none" stroke="#10b981" stroke-width="2" opacity="0.4" transform="translate(-2,-2) scale(${(w + 4) / w},${(h + 4) / h})"/>` : ""}
<path class="node-bg" d="${funnelPath}" fill="#1e293b" stroke="${selected ? "#6366f1" : borderColor}" stroke-width="${selected ? 3 : 2}"/>
<path class="node-bg" d="${funnelPath}" style="fill:var(--rs-bg-surface)" stroke="${selected ? "#6366f1" : borderColor}" stroke-width="${selected ? 3 : 2}"/>
<g clip-path="url(#${clipId})">
<rect x="${-lipW}" y="${zoneTop + overflowH + healthyH}" width="${w + lipW * 2}" height="${drainH}" fill="#ef4444" opacity="0.08"/>
<rect x="${-lipW}" y="${zoneTop + overflowH}" width="${w + lipW * 2}" height="${healthyH}" fill="#0ea5e9" opacity="0.06"/>
<rect x="${-lipW}" y="${zoneTop}" width="${w + lipW * 2}" height="${overflowH}" fill="#f59e0b" opacity="0.06"/>
<rect x="${-lipW}" y="${fillY}" width="${w + lipW * 2}" height="${totalFillH}" fill="${fillColor}" opacity="0.25"/>
</g>
<rect class="funnel-lip ${isOverflow ? "funnel-lip--active" : ""}" x="${-lipW}" y="${lipH}" width="${lipW}" height="${lipNotch}" rx="2" fill="${isOverflow ? "#10b981" : "#334155"}" opacity="${isOverflow ? 0.8 : 0.3}"/>
<rect class="funnel-lip ${isOverflow ? "funnel-lip--active" : ""}" x="${w}" y="${lipH}" width="${lipW}" height="${lipNotch}" rx="2" fill="${isOverflow ? "#10b981" : "#334155"}" opacity="${isOverflow ? 0.8 : 0.3}"/>
<text x="${w / 2}" y="${lipH + 6}" text-anchor="middle" fill="#e2e8f0" font-size="13" font-weight="600">${this.esc(d.label)}</text>
<rect class="funnel-lip ${isOverflow ? "funnel-lip--active" : ""}" x="${-lipW}" y="${lipH}" width="${lipW}" height="${lipNotch}" rx="2" style="fill:${isOverflow ? "#10b981" : "var(--rs-bg-surface-raised)"}" opacity="${isOverflow ? 0.8 : 0.3}"/>
<rect class="funnel-lip ${isOverflow ? "funnel-lip--active" : ""}" x="${w}" y="${lipH}" width="${lipW}" height="${lipNotch}" rx="2" style="fill:${isOverflow ? "#10b981" : "var(--rs-bg-surface-raised)"}" opacity="${isOverflow ? 0.8 : 0.3}"/>
<text x="${w / 2}" y="${lipH + 6}" text-anchor="middle" style="fill:var(--rs-text-primary)" font-size="13" font-weight="600">${this.esc(d.label)}</text>
<text x="${w - 10}" y="${lipH + 6}" text-anchor="end" fill="${borderColor}" font-size="10" font-weight="500" class="${!isCritical ? "sufficiency-glow" : ""}">${statusLabel}</text>
<rect x="20" y="${satBarY}" width="${satBarW}" height="6" rx="3" fill="#334155" opacity="0.3" class="satisfaction-bar-bg"/>
<rect x="20" y="${satBarY}" width="${satBarW}" height="6" rx="3" style="fill:var(--rs-bg-surface-raised)" opacity="0.3" class="satisfaction-bar-bg"/>
<rect x="20" y="${satBarY}" width="${satFillW}" height="6" rx="3" fill="#10b981" class="satisfaction-bar-fill" ${satBarBorder}/>
<text x="${w / 2}" y="${satBarY + 16}" text-anchor="middle" fill="#64748b" font-size="9">${satLabel}</text>
<text x="${w / 2}" y="${h - insetPx - 8}" text-anchor="middle" fill="#94a3b8" font-size="11">$${Math.floor(d.currentValue).toLocaleString()} / $${Math.floor(threshold).toLocaleString()}</text>
<rect x="${insetPx + 4}" y="${h - 10}" width="${w - insetPx * 2 - 8}" height="4" rx="2" fill="#334155"/>
<text x="${w / 2}" y="${satBarY + 16}" text-anchor="middle" style="fill:var(--rs-text-muted)" font-size="9">${satLabel}</text>
<text x="${w / 2}" y="${h - insetPx - 8}" text-anchor="middle" style="fill:var(--rs-text-secondary)" font-size="11">$${Math.floor(d.currentValue).toLocaleString()} / $${Math.floor(threshold).toLocaleString()}</text>
<rect x="${insetPx + 4}" y="${h - 10}" width="${w - insetPx * 2 - 8}" height="4" rx="2" style="fill:var(--rs-bg-surface-raised)"/>
<rect x="${insetPx + 4}" y="${h - 10}" width="${(w - insetPx * 2 - 8) * fillPct}" height="4" rx="2" fill="${fillColor}"/>
${this.renderPortsSvg(n)}
</g>`;
@ -1182,9 +1182,9 @@ class FolkFlowsApp extends HTMLElement {
const phaseW = (w - 20) / d.phases.length;
phaseBars = d.phases.map((p, i) => {
const unlocked = d.fundingReceived >= p.fundingThreshold;
return `<rect x="${10 + i * phaseW}" y="65" width="${phaseW - 2}" height="6" rx="2" fill="${unlocked ? "#10b981" : "#334155"}" opacity="${unlocked ? 0.8 : 0.5}"/>`;
return `<rect x="${10 + i * phaseW}" y="65" width="${phaseW - 2}" height="6" rx="2" style="fill:${unlocked ? "#10b981" : "var(--rs-bg-surface-raised)"}" opacity="${unlocked ? 0.8 : 0.5}"/>`;
}).join("");
phaseBars += `<text x="${w / 2}" y="83" text-anchor="middle" fill="#64748b" font-size="9">${d.phases.filter((p) => d.fundingReceived >= p.fundingThreshold).length}/${d.phases.length} phases</text>`;
phaseBars += `<text x="${w / 2}" y="83" text-anchor="middle" style="fill:var(--rs-text-muted)" font-size="9">${d.phases.filter((p) => d.fundingReceived >= p.fundingThreshold).length}/${d.phases.length} phases</text>`;
}
// Enhanced progress bar (8px height, green funded portion + grey gap)
@ -1194,12 +1194,12 @@ class FolkFlowsApp extends HTMLElement {
const dollarLabel = `${this.formatDollar(d.fundingReceived)} / ${this.formatDollar(d.fundingTarget)}`;
return `<g class="flow-node ${selected ? "selected" : ""}" data-node-id="${n.id}" transform="translate(${x},${y})">
<rect class="node-bg" x="0" y="0" width="${w}" height="${h}" rx="8" fill="#1e293b" stroke="${selected ? "#6366f1" : statusColor}" stroke-width="${selected ? 3 : 1.5}"/>
<rect class="node-bg" x="0" y="0" width="${w}" height="${h}" rx="8" style="fill:var(--rs-bg-surface)" stroke="${selected ? "#6366f1" : statusColor}" stroke-width="${selected ? 3 : 1.5}"/>
<circle cx="14" cy="18" r="5" fill="${statusColor}" opacity="0.7"/>
<text x="26" y="22" fill="#e2e8f0" font-size="12" font-weight="600">${this.esc(d.label)}</text>
<rect x="10" y="${barY}" width="${barW}" height="${barH}" rx="4" fill="#334155" class="satisfaction-bar-bg"/>
<text x="26" y="22" style="fill:var(--rs-text-primary)" font-size="12" font-weight="600">${this.esc(d.label)}</text>
<rect x="10" y="${barY}" width="${barW}" height="${barH}" rx="4" style="fill:var(--rs-bg-surface-raised)" class="satisfaction-bar-bg"/>
<rect x="10" y="${barY}" width="${barW * fillPct}" height="${barH}" rx="4" fill="${statusColor}" opacity="0.8" class="satisfaction-bar-fill"/>
<text x="${w / 2}" y="${barY + barH + 12}" text-anchor="middle" fill="#94a3b8" font-size="10">${Math.round(fillPct * 100)}% ${dollarLabel}</text>
<text x="${w / 2}" y="${barY + barH + 12}" text-anchor="middle" style="fill:var(--rs-text-secondary)" font-size="10">${Math.round(fillPct * 100)}% ${dollarLabel}</text>
${phaseBars}
${this.renderPortsSvg(n)}
</g>`;
@ -1361,15 +1361,15 @@ class FolkFlowsApp extends HTMLElement {
return `<g class="edge-group" data-from="${fromId}" data-to="${toId}">
<path d="${d}" fill="none" stroke="${color}" stroke-width="1" stroke-opacity="0.2" stroke-dasharray="4 6" class="edge-ghost"/>
<g class="edge-ctrl-group" transform="translate(${midX},${midY})">
<rect x="-34" y="-12" width="68" height="24" rx="6" fill="#1e293b" stroke="#475569" stroke-width="1" opacity="0.5"/>
<rect x="-34" y="-12" width="68" height="24" rx="6" style="fill:var(--rs-bg-surface);stroke:var(--rs-bg-surface-raised)" stroke-width="1" opacity="0.5"/>
<text x="-14" y="5" fill="${color}" font-size="11" font-weight="600" text-anchor="middle" opacity="0.5">${label}</text>
<g data-edge-action="dec" data-edge-from="${fromId}" data-edge-to="${toId}" data-edge-type="${edgeType}" style="cursor:pointer">
<rect x="-32" y="-9" width="16" height="18" rx="3" fill="#334155"/>
<text x="-24" y="5" fill="#e2e8f0" font-size="13" text-anchor="middle">&minus;</text>
<rect x="-32" y="-9" width="16" height="18" rx="3" style="fill:var(--rs-bg-surface-raised)"/>
<text x="-24" y="5" style="fill:var(--rs-text-primary)" font-size="13" text-anchor="middle">&minus;</text>
</g>
<g data-edge-action="inc" data-edge-from="${fromId}" data-edge-to="${toId}" data-edge-type="${edgeType}" style="cursor:pointer">
<rect x="16" y="-9" width="16" height="18" rx="3" fill="#334155"/>
<text x="24" y="5" fill="#e2e8f0" font-size="13" text-anchor="middle">+</text>
<rect x="16" y="-9" width="16" height="18" rx="3" style="fill:var(--rs-bg-surface-raised)"/>
<text x="24" y="5" style="fill:var(--rs-text-primary)" font-size="13" text-anchor="middle">+</text>
</g>
</g>
</g>`;
@ -1383,15 +1383,15 @@ class FolkFlowsApp extends HTMLElement {
<path d="${d}" fill="none" stroke="${color}" stroke-width="${strokeW * 2.5}" stroke-opacity="0.12" class="edge-glow"/>
<path d="${d}" fill="none" stroke="${color}" stroke-width="${strokeW}" stroke-opacity="0.8" class="${animClass}"/>
<g class="edge-ctrl-group" transform="translate(${midX},${midY})">
<rect x="${-halfW}" y="-12" width="${labelW}" height="24" rx="6" fill="#1e293b" stroke="#475569" stroke-width="1" opacity="0.9"/>
<rect x="${-halfW}" y="-12" width="${labelW}" height="24" rx="6" style="fill:var(--rs-bg-surface);stroke:var(--rs-bg-surface-raised)" stroke-width="1" opacity="0.9"/>
<text x="0" y="5" fill="${color}" font-size="10" font-weight="600" text-anchor="middle">${label}</text>
<g data-edge-action="dec" data-edge-from="${fromId}" data-edge-to="${toId}" data-edge-type="${edgeType}" style="cursor:pointer">
<rect x="${-halfW + 2}" y="-9" width="16" height="18" rx="3" fill="#334155"/>
<text x="${-halfW + 10}" y="5" fill="#e2e8f0" font-size="13" text-anchor="middle">&minus;</text>
<rect x="${-halfW + 2}" y="-9" width="16" height="18" rx="3" style="fill:var(--rs-bg-surface-raised)"/>
<text x="${-halfW + 10}" y="5" style="fill:var(--rs-text-primary)" font-size="13" text-anchor="middle">&minus;</text>
</g>
<g data-edge-action="inc" data-edge-from="${fromId}" data-edge-to="${toId}" data-edge-type="${edgeType}" style="cursor:pointer">
<rect x="${halfW - 18}" y="-9" width="16" height="18" rx="3" fill="#334155"/>
<text x="${halfW - 10}" y="5" fill="#e2e8f0" font-size="13" text-anchor="middle">+</text>
<rect x="${halfW - 18}" y="-9" width="16" height="18" rx="3" style="fill:var(--rs-bg-surface-raised)"/>
<text x="${halfW - 10}" y="5" style="fill:var(--rs-text-primary)" font-size="13" text-anchor="middle">+</text>
</g>
</g>
</g>`;
@ -1777,7 +1777,7 @@ class FolkFlowsApp extends HTMLElement {
<div xmlns="http://www.w3.org/1999/xhtml" class="inline-edit-toolbar">
<button class="iet-done" style="background:#10b981;color:white">Done</button>
<button class="iet-delete" style="background:#ef4444;color:white">Delete</button>
<button class="iet-panel" style="background:#334155;color:#e2e8f0">...</button>
<button class="iet-panel" style="background:var(--rs-border-strong);color:var(--rs-text-primary)">...</button>
</div>
</foreignObject>`;
@ -2000,7 +2000,7 @@ class FolkFlowsApp extends HTMLElement {
<div xmlns="http://www.w3.org/1999/xhtml" class="inline-edit-toolbar">
<button class="iet-done" style="background:#10b981;color:white">Done</button>
<button class="iet-delete" style="background:#ef4444;color:white">Delete</button>
<button class="iet-panel" style="background:#334155;color:#e2e8f0">...</button>
<button class="iet-panel" style="background:var(--rs-border-strong);color:var(--rs-text-primary)">...</button>
</div>
</foreignObject>`;
@ -2037,7 +2037,7 @@ class FolkFlowsApp extends HTMLElement {
if (d.sourceType === "card") {
html += `<div class="editor-field" style="margin-top:12px">
<button class="editor-btn fund-card-btn" data-action="fund-with-card"
style="width:100%;padding:10px;background:#6366f1;color:white;border:none;border-radius:8px;cursor:pointer;font-weight:600">
style="width:100%;padding:10px;background:var(--rs-primary);color:white;border:none;border-radius:8px;cursor:pointer;font-weight:600">
Fund with Card
</button>
</div>`;
@ -2086,9 +2086,9 @@ class FolkFlowsApp extends HTMLElement {
html += `<div class="editor-section"><div class="editor-section-title">Phases</div>`;
for (const p of d.phases) {
const unlocked = d.fundingReceived >= p.fundingThreshold;
html += `<div style="margin-bottom:6px;padding:6px;background:#0f172a;border-radius:6px;border-left:3px solid ${unlocked ? "#10b981" : "#334155"}">
<div style="font-size:12px;font-weight:600;color:${unlocked ? "#6ee7b7" : "#64748b"}">${this.esc(p.name)} $${p.fundingThreshold.toLocaleString()}</div>
${p.tasks.map((t) => `<div style="font-size:11px;color:#94a3b8;margin-top:2px">${t.completed ? "&#x2705;" : "&#x2B1C;"} ${this.esc(t.label)}</div>`).join("")}
html += `<div style="margin-bottom:6px;padding:6px;background:var(--rs-bg-surface-sunken);border-radius:6px;border-left:3px solid ${unlocked ? "#10b981" : "var(--rs-border-strong)"}">
<div style="font-size:12px;font-weight:600;color:${unlocked ? "var(--rs-success)" : "var(--rs-text-muted)"}">${this.esc(p.name)} $${p.fundingThreshold.toLocaleString()}</div>
${p.tasks.map((t) => `<div style="font-size:11px;color:var(--rs-text-secondary);margin-top:2px">${t.completed ? "&#x2705;" : "&#x2B1C;"} ${this.esc(t.label)}</div>`).join("")}
</div>`;
}
html += `</div>`;
@ -2106,7 +2106,7 @@ class FolkFlowsApp extends HTMLElement {
html += `<div class="editor-alloc-row">
<span class="editor-alloc-dot" style="background:${a.color}"></span>
<span style="flex:1">${this.esc(this.getNodeLabel(a.targetId))}</span>
<span style="font-weight:600;color:#e2e8f0">${a.percentage}%</span>
<span style="font-weight:600;color:var(--rs-text-primary)">${a.percentage}%</span>
</div>`;
}
html += `</div>`;
@ -2147,7 +2147,7 @@ class FolkFlowsApp extends HTMLElement {
modal.id = "transak-modal";
modal.style.cssText = `position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,0.7);display:flex;align-items:center;justify-content:center;`;
modal.innerHTML = `
<div style="position:relative;width:450px;height:680px;border-radius:16px;overflow:hidden;background:#1e1e2e">
<div style="position:relative;width:450px;height:680px;border-radius:16px;overflow:hidden;background:var(--rs-bg-surface)">
<button id="transak-close" style="position:absolute;top:8px;right:12px;z-index:10;
background:none;border:none;color:white;font-size:24px;cursor:pointer">&times;</button>
<iframe src="${baseUrl}?${params}" style="width:100%;height:100%;border:none"
@ -2223,7 +2223,7 @@ class FolkFlowsApp extends HTMLElement {
const d = node.data as FunnelNodeData;
const suf = computeSufficiencyState(d);
html += `<div class="flows-node-tooltip__stat">$${Math.floor(d.currentValue).toLocaleString()} / $${Math.floor(d.sufficientThreshold ?? d.maxThreshold).toLocaleString()}</div>`;
html += `<div class="flows-node-tooltip__stat" style="text-transform:capitalize;color:${suf === "sufficient" || suf === "abundant" ? "#fbbf24" : "#94a3b8"}">${suf}</div>`;
html += `<div class="flows-node-tooltip__stat" style="text-transform:capitalize;color:${suf === "sufficient" || suf === "abundant" ? "#fbbf24" : "var(--rs-text-secondary)"}">${suf}</div>`;
} else {
const d = node.data as OutcomeNodeData;
const pct = d.fundingTarget > 0 ? Math.round((d.fundingReceived / d.fundingTarget) * 100) : 0;
@ -2287,7 +2287,7 @@ class FolkFlowsApp extends HTMLElement {
phasesHtml += `<div class="phase-tier-bar">`;
for (const p of d.phases) {
const unlocked = d.fundingReceived >= p.fundingThreshold;
phasesHtml += `<div class="phase-tier-segment" style="background:${unlocked ? "#10b981" : "#334155"}"></div>`;
phasesHtml += `<div class="phase-tier-segment" style="background:${unlocked ? "#10b981" : "var(--rs-bg-surface-raised)"}"></div>`;
}
phasesHtml += `</div>`;
@ -2300,14 +2300,14 @@ class FolkFlowsApp extends HTMLElement {
phasesHtml += `<div class="phase-card ${unlocked ? "" : "phase-card--locked"}">
<div class="phase-header" data-phase-idx="${i}">
<span style="font-size:14px">${unlocked ? "&#x1F513;" : "&#x1F512;"}</span>
<span style="flex:1;font-size:13px;font-weight:600;color:${unlocked ? "#e2e8f0" : "#64748b"}">${this.esc(p.name)}</span>
<span style="font-size:11px;color:#64748b">${completedTasks}/${p.tasks.length}</span>
<span style="font-size:11px;color:#64748b">$${p.fundingThreshold.toLocaleString()}</span>
<span style="font-size:12px;color:#64748b;transition:transform 0.2s" data-phase-chevron="${i}">&#x25B6;</span>
<span style="flex:1;font-size:13px;font-weight:600;color:${unlocked ? "var(--rs-text-primary)" : "var(--rs-text-muted)"}">${this.esc(p.name)}</span>
<span style="font-size:11px;color:var(--rs-text-muted)">${completedTasks}/${p.tasks.length}</span>
<span style="font-size:11px;color:var(--rs-text-muted)">$${p.fundingThreshold.toLocaleString()}</span>
<span style="font-size:12px;color:var(--rs-text-muted);transition:transform 0.2s" data-phase-chevron="${i}">&#x25B6;</span>
</div>
<div class="phase-content" data-phase-content="${i}" style="display:none">
<div style="margin-bottom:8px">
<div style="display:flex;justify-content:space-between;font-size:11px;color:#64748b;margin-bottom:4px">
<div style="display:flex;justify-content:space-between;font-size:11px;color:var(--rs-text-muted);margin-bottom:4px">
<span>${Math.min(phasePct, 100)}% funded</span>
<span>$${Math.floor(Math.min(d.fundingReceived, p.fundingThreshold)).toLocaleString()} / $${p.fundingThreshold.toLocaleString()}</span>
</div>
@ -2336,20 +2336,20 @@ class FolkFlowsApp extends HTMLElement {
<div class="flows-modal__header">
<div style="display:flex;align-items:center;gap:10px">
<span style="display:inline-block;padding:3px 10px;border-radius:12px;font-size:11px;font-weight:600;background:${statusColor}22;color:${statusColor}">${statusLabel}</span>
<span style="font-size:16px;font-weight:700;color:#e2e8f0">${this.esc(d.label)}</span>
<span style="font-size:16px;font-weight:700;color:var(--rs-text-primary)">${this.esc(d.label)}</span>
</div>
<button class="flows-modal__close" data-modal-action="close">&times;</button>
</div>
${d.description ? `<div style="font-size:13px;color:#94a3b8;line-height:1.6;margin-bottom:16px;padding:10px 12px;background:#0f172a;border-radius:8px;border-left:3px solid ${statusColor}">${this.esc(d.description)}</div>` : ""}
${d.description ? `<div style="font-size:13px;color:var(--rs-text-secondary);line-height:1.6;margin-bottom:16px;padding:10px 12px;background:var(--rs-bg-surface-sunken);border-radius:8px;border-left:3px solid ${statusColor}">${this.esc(d.description)}</div>` : ""}
<div style="margin-bottom:20px">
<div style="font-size:28px;font-weight:700;color:#e2e8f0">$${Math.floor(d.fundingReceived).toLocaleString()}</div>
<div style="font-size:13px;color:#64748b;margin-top:2px">of $${Math.floor(d.fundingTarget).toLocaleString()} (${Math.round(fillPct)}%)</div>
<div style="font-size:28px;font-weight:700;color:var(--rs-text-primary)">$${Math.floor(d.fundingReceived).toLocaleString()}</div>
<div style="font-size:13px;color:var(--rs-text-muted);margin-top:2px">of $${Math.floor(d.fundingTarget).toLocaleString()} (${Math.round(fillPct)}%)</div>
<div class="flows-modal__progress-bar" style="margin-top:10px;height:10px">
<div class="flows-modal__progress-fill" style="width:${fillPct}%;background:${statusColor}"></div>
</div>
</div>
${d.phases && d.phases.length > 0 ? `<div style="margin-bottom:16px">
<div style="font-size:13px;font-weight:600;color:#94a3b8;text-transform:uppercase;letter-spacing:0.05em;margin-bottom:10px">Phases</div>
<div style="font-size:13px;font-weight:600;color:var(--rs-text-secondary);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:10px">Phases</div>
${phasesHtml}
</div>` : ""}
</div>`;
@ -2465,8 +2465,8 @@ class FolkFlowsApp extends HTMLElement {
} else if (d.sourceType === "ridentity") {
configHtml = `<div style="text-align:center;padding:16px 0;margin-top:12px">
<div style="font-size:32px;margin-bottom:8px">&#x1F464;</div>
<div style="font-size:13px;color:#94a3b8;margin-bottom:12px">${isAuthenticated() ? "Connected" : "Not connected"}</div>
${!isAuthenticated() ? `<button class="editor-btn" data-action="connect-ridentity" style="background:#6366f1;color:white;border:none;padding:8px 20px;border-radius:8px;font-weight:600;cursor:pointer">Connect with EncryptID</button>` : `<div style="font-size:12px;color:#6ee7b7">&#x2705; ${this.esc(getUsername() || "Connected")}</div>`}
<div style="font-size:13px;color:var(--rs-text-secondary);margin-bottom:12px">${isAuthenticated() ? "Connected" : "Not connected"}</div>
${!isAuthenticated() ? `<button class="editor-btn" data-action="connect-ridentity" style="background:var(--rs-primary);color:white;border:none;padding:8px 20px;border-radius:8px;font-weight:600;cursor:pointer">Connect with EncryptID</button>` : `<div style="font-size:12px;color:var(--rs-success)">&#x2705; ${this.esc(getUsername() || "Connected")}</div>`}
</div>`;
}
@ -2477,12 +2477,12 @@ class FolkFlowsApp extends HTMLElement {
<div class="flows-modal__header">
<div style="display:flex;align-items:center;gap:10px">
<span style="font-size:20px">${icons[d.sourceType] || "&#x1F4B0;"}</span>
<span style="font-size:16px;font-weight:700;color:#e2e8f0">${this.esc(d.label)}</span>
<span style="font-size:16px;font-weight:700;color:var(--rs-text-primary)">${this.esc(d.label)}</span>
</div>
<button class="flows-modal__close" data-modal-action="close">&times;</button>
</div>
<div style="margin-bottom:16px">
<div style="font-size:11px;font-weight:600;color:#94a3b8;text-transform:uppercase;letter-spacing:0.05em;margin-bottom:8px">Source Type</div>
<div style="font-size:11px;font-weight:600;color:var(--rs-text-secondary);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:8px">Source Type</div>
<div class="source-type-grid">
${["card", "safe_wallet", "ridentity"].map((t) => `
<button class="source-type-btn ${d.sourceType === t ? "source-type-btn--active" : ""}" data-source-type="${t}">

View File

@ -154,27 +154,27 @@ class FolkRemindersWidget extends HTMLElement {
private render() {
const styles = `
<style>
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: #e2e8f0; }
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: var(--rs-text-primary); }
.rw-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
.rw-title { font-size: 14px; font-weight: 600; color: #f59e0b; }
.rw-btn { padding: 4px 10px; border-radius: 6px; border: none; cursor: pointer; font-size: 12px; font-weight: 600; transition: all 0.15s; }
.rw-btn-primary { background: linear-gradient(135deg, #f59e0b, #f97316); color: #0f172a; }
.rw-btn-primary:hover { opacity: 0.9; }
.rw-btn-sm { padding: 2px 6px; font-size: 10px; border-radius: 4px; border: none; cursor: pointer; }
.rw-btn-ghost { background: transparent; color: #64748b; }
.rw-btn-ghost:hover { color: #e2e8f0; }
.rw-btn-ghost { background: transparent; color: var(--rs-text-muted); }
.rw-btn-ghost:hover { color: var(--rs-text-primary); }
.rw-card { display: flex; align-items: flex-start; gap: 8px; padding: 8px; border-radius: 6px; margin-bottom: 4px; transition: background 0.15s; }
.rw-card:hover { background: rgba(255,255,255,0.05); }
.rw-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; margin-top: 4px; }
.rw-info { flex: 1; min-width: 0; }
.rw-card-title { font-size: 13px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rw-card-meta { font-size: 11px; color: #64748b; margin-top: 2px; }
.rw-card-source { font-size: 10px; color: #94a3b8; }
.rw-card-meta { font-size: 11px; color: var(--rs-text-muted); margin-top: 2px; }
.rw-card-source { font-size: 10px; color: var(--rs-text-secondary); }
.rw-actions { display: flex; gap: 2px; flex-shrink: 0; }
.rw-empty { text-align: center; padding: 20px; color: #64748b; font-size: 13px; }
.rw-loading { text-align: center; padding: 20px; color: #94a3b8; font-size: 13px; }
.rw-form { background: rgba(15,23,42,0.6); border: 1px solid rgba(30,41,59,0.8); border-radius: 8px; padding: 10px; margin-bottom: 8px; }
.rw-input { width: 100%; padding: 6px 8px; border-radius: 6px; border: 1px solid #334155; background: rgba(30,41,59,0.8); color: #e2e8f0; font-size: 13px; margin-bottom: 6px; box-sizing: border-box; outline: none; font-family: inherit; }
.rw-empty { text-align: center; padding: 20px; color: var(--rs-text-muted); font-size: 13px; }
.rw-loading { text-align: center; padding: 20px; color: var(--rs-text-secondary); font-size: 13px; }
.rw-form { background: var(--rs-bg-surface-sunken); border: 1px solid rgba(30,41,59,0.8); border-radius: 8px; padding: 10px; margin-bottom: 8px; }
.rw-input { width: 100%; padding: 6px 8px; border-radius: 6px; border: 1px solid var(--rs-border-strong); background: rgba(30,41,59,0.8); color: var(--rs-text-primary); font-size: 13px; margin-bottom: 6px; box-sizing: border-box; outline: none; font-family: inherit; }
.rw-input:focus { border-color: #f59e0b; }
.rw-form-actions { display: flex; gap: 6px; }
.widget-drop-active { background: rgba(245,158,11,0.1); border: 2px dashed #f59e0b; border-radius: 8px; }

View File

@ -404,7 +404,7 @@ class FolkScheduleApp extends HTMLElement {
<label class="s-label">To <input type="email" class="s-input" data-config="to" value="${this.esc(this.formConfig.to || "")}" placeholder="user@example.com"></label>
`;
default:
return `<p style="color:#94a3b8">No configuration needed for this action type.</p>`;
return `<p style="color:var(--rs-text-secondary)">No configuration needed for this action type.</p>`;
}
}
@ -415,50 +415,50 @@ class FolkScheduleApp extends HTMLElement {
private render() {
const styles = `
<style>
:host { display:block; font-family:system-ui,-apple-system,sans-serif; color:#e2e8f0; }
:host { display:block; font-family:system-ui,-apple-system,sans-serif; color:var(--rs-text-primary); }
.s-header { display:flex; align-items:center; justify-content:space-between; margin-bottom:24px; flex-wrap:wrap; gap:12px; }
.s-title { font-size:1.5rem; font-weight:700; margin:0; }
.s-tabs { display:flex; gap:4px; background:rgba(30,41,59,0.6); border-radius:8px; padding:3px; }
.s-tab { padding:6px 16px; border-radius:6px; border:none; background:transparent; color:#94a3b8; cursor:pointer; font-size:13px; transition:all 0.15s; }
.s-tab:hover { color:#e2e8f0; }
.s-tab { padding:6px 16px; border-radius:6px; border:none; background:transparent; color:var(--rs-text-secondary); cursor:pointer; font-size:13px; transition:all 0.15s; }
.s-tab:hover { color:var(--rs-text-primary); }
.s-tab.active { background:rgba(245,158,11,0.15); color:#f59e0b; }
.s-btn { padding:8px 16px; border-radius:8px; border:none; cursor:pointer; font-size:13px; font-weight:600; transition:all 0.15s; }
.s-btn-primary { background:linear-gradient(135deg,#f59e0b,#f97316); color:#0f172a; }
.s-btn-primary:hover { opacity:0.9; }
.s-btn-secondary { background:rgba(51,65,85,0.6); color:#e2e8f0; }
.s-btn-secondary { background:rgba(51,65,85,0.6); color:var(--rs-text-primary); }
.s-btn-secondary:hover { background:rgba(71,85,105,0.6); }
.s-btn-danger { background:rgba(239,68,68,0.15); color:#ef4444; }
.s-btn-danger { background:rgba(239,68,68,0.15); color:var(--rs-error); }
.s-btn-danger:hover { background:rgba(239,68,68,0.25); }
.s-btn-sm { padding:4px 10px; font-size:12px; }
.s-btn:disabled { opacity:0.5; cursor:not-allowed; }
.s-table { width:100%; border-collapse:collapse; }
.s-table th { text-align:left; padding:10px 12px; font-size:11px; text-transform:uppercase; letter-spacing:0.05em; color:#64748b; border-bottom:2px solid #1e293b; }
.s-table th { text-align:left; padding:10px 12px; font-size:11px; text-transform:uppercase; letter-spacing:0.05em; color:var(--rs-text-muted); border-bottom:2px solid var(--rs-bg-surface); }
.s-table td { padding:10px 12px; border-bottom:1px solid rgba(30,41,59,0.6); font-size:14px; vertical-align:middle; }
.s-table tr:hover td { background:rgba(30,41,59,0.3); }
.s-status { display:inline-block; width:8px; height:8px; border-radius:50%; margin-right:6px; }
.s-status-success { background:#22c55e; }
.s-status-error { background:#ef4444; }
.s-status-null { background:#475569; }
.s-status-success { background:var(--rs-success); }
.s-status-error { background:var(--rs-error); }
.s-status-null { background:var(--rs-bg-surface-raised); }
.s-toggle { position:relative; width:36px; height:20px; cursor:pointer; }
.s-toggle input { opacity:0; width:0; height:0; position:absolute; }
.s-toggle .slider { position:absolute; inset:0; background:#334155; border-radius:20px; transition:0.2s; }
.s-toggle .slider::before { content:''; position:absolute; height:14px; width:14px; left:3px; bottom:3px; background:#94a3b8; border-radius:50%; transition:0.2s; }
.s-toggle .slider { position:absolute; inset:0; background:var(--rs-border-strong); border-radius:20px; transition:0.2s; }
.s-toggle .slider::before { content:''; position:absolute; height:14px; width:14px; left:3px; bottom:3px; background:var(--rs-text-secondary); border-radius:50%; transition:0.2s; }
.s-toggle input:checked + .slider { background:rgba(245,158,11,0.3); }
.s-toggle input:checked + .slider::before { transform:translateX(16px); background:#f59e0b; }
.s-card { background:rgba(15,23,42,0.6); border:1px solid rgba(30,41,59,0.8); border-radius:12px; padding:24px; margin-bottom:16px; }
.s-card { background:var(--rs-bg-surface-sunken); border:1px solid rgba(30,41,59,0.8); border-radius:12px; padding:24px; margin-bottom:16px; }
.s-form-grid { display:grid; grid-template-columns:1fr 1fr; gap:16px; }
.s-form-full { grid-column:1/-1; }
.s-label { display:flex; flex-direction:column; gap:6px; font-size:13px; color:#94a3b8; font-weight:500; }
.s-input { padding:8px 12px; background:rgba(30,41,59,0.8); border:1px solid #334155; border-radius:6px; color:#e2e8f0; font-size:14px; font-family:inherit; }
.s-label { display:flex; flex-direction:column; gap:6px; font-size:13px; color:var(--rs-text-secondary); font-weight:500; }
.s-input { padding:8px 12px; background:rgba(30,41,59,0.8); border:1px solid var(--rs-border-strong); border-radius:6px; color:var(--rs-text-primary); font-size:14px; font-family:inherit; }
.s-input:focus { outline:none; border-color:#f59e0b; }
.s-textarea { min-height:80px; resize:vertical; }
.s-actions { display:flex; gap:6px; flex-wrap:wrap; }
.s-empty { text-align:center; padding:48px 20px; color:#64748b; }
.s-loading { text-align:center; padding:48px; color:#94a3b8; }
.s-empty { text-align:center; padding:48px 20px; color:var(--rs-text-muted); }
.s-loading { text-align:center; padding:48px; color:var(--rs-text-secondary); }
.s-log-item { display:flex; gap:12px; padding:10px 0; border-bottom:1px solid rgba(30,41,59,0.6); font-size:13px; align-items:flex-start; }
.s-log-time { color:#64748b; white-space:nowrap; min-width:120px; }
.s-log-msg { color:#cbd5e1; flex:1; word-break:break-word; }
.s-log-dur { color:#64748b; white-space:nowrap; }
.s-log-time { color:var(--rs-text-muted); white-space:nowrap; min-width:120px; }
.s-log-msg { color:var(--rs-text-primary); flex:1; word-break:break-word; }
.s-log-dur { color:var(--rs-text-muted); white-space:nowrap; }
@media (max-width:768px) {
.s-form-grid { grid-template-columns:1fr; }
.s-table { font-size:12px; }
@ -522,17 +522,17 @@ class FolkScheduleApp extends HTMLElement {
</label>
</td>
<td>
<strong style="color:#e2e8f0">${this.esc(j.name)}</strong>
${j.description ? `<br><span style="color:#64748b;font-size:12px">${this.esc(j.description)}</span>` : ""}
<strong style="color:var(--rs-text-primary)">${this.esc(j.name)}</strong>
${j.description ? `<br><span style="color:var(--rs-text-muted);font-size:12px">${this.esc(j.description)}</span>` : ""}
</td>
<td style="font-family:monospace;font-size:12px;color:#94a3b8" title="${this.esc(j.cronExpression)}">${this.esc(j.cronHuman || j.cronExpression)}</td>
<td style="color:#94a3b8;font-size:13px">${this.esc(j.timezone)}</td>
<td style="font-family:monospace;font-size:12px;color:var(--rs-text-secondary)" title="${this.esc(j.cronExpression)}">${this.esc(j.cronHuman || j.cronExpression)}</td>
<td style="color:var(--rs-text-secondary);font-size:13px">${this.esc(j.timezone)}</td>
<td><span style="background:rgba(245,158,11,0.1);color:#f59e0b;padding:2px 8px;border-radius:4px;font-size:12px">${this.esc(j.actionType)}</span></td>
<td>
<span class="s-status s-status-${j.lastRunStatus || "null"}"></span>
<span style="color:#94a3b8;font-size:13px">${this.formatTime(j.lastRunAt)}</span>
<span style="color:var(--rs-text-secondary);font-size:13px">${this.formatTime(j.lastRunAt)}</span>
</td>
<td style="color:#94a3b8;font-size:13px">${this.formatFuture(j.nextRunAt)}</td>
<td style="color:var(--rs-text-secondary);font-size:13px">${this.formatFuture(j.nextRunAt)}</td>
<td>
<div class="s-actions">
<button class="s-btn s-btn-secondary s-btn-sm" data-run="${j.id}" ${this.runningJobId === j.id ? "disabled" : ""}>${this.runningJobId === j.id ? "Running..." : "Run Now"}</button>
@ -566,7 +566,7 @@ class FolkScheduleApp extends HTMLElement {
private renderLog(): string {
if (this.log.length === 0) {
return `<div class="s-empty"><p>No execution log entries yet.</p><p style="color:#64748b;margin-top:8px">Jobs will log their results here after they run.</p></div>`;
return `<div class="s-empty"><p>No execution log entries yet.</p><p style="color:var(--rs-text-muted);margin-top:8px">Jobs will log their results here after they run.</p></div>`;
}
const jobNames = new Map(this.jobs.map((j) => [j.id, j.name]));
@ -614,8 +614,8 @@ class FolkScheduleApp extends HTMLElement {
<label class="s-label">Action Type
<select class="s-input" id="f-action">${actionOptions}</select>
</label>
<div class="s-form-full" style="border-top:1px solid #1e293b;padding-top:16px;margin-top:8px">
<h3 style="font-size:14px;color:#94a3b8;margin:0 0 12px">Action Configuration</h3>
<div class="s-form-full" style="border-top:1px solid var(--rs-bg-surface);padding-top:16px;margin-top:8px">
<h3 style="font-size:14px;color:var(--rs-text-secondary);margin:0 0 12px">Action Configuration</h3>
<div class="s-form-grid" id="f-config-fields">
${this.renderActionConfigFields()}
</div>
@ -648,13 +648,13 @@ class FolkScheduleApp extends HTMLElement {
<div style="display:flex;align-items:center;gap:8px">
<span style="width:8px;height:8px;border-radius:50%;background:${r.sourceColor || "#f59e0b"};flex-shrink:0"></span>
<div>
<strong style="color:#e2e8f0">${this.esc(r.title)}</strong>
${r.description ? `<br><span style="color:#64748b;font-size:12px">${this.esc(r.description.slice(0, 60))}</span>` : ""}
<strong style="color:var(--rs-text-primary)">${this.esc(r.title)}</strong>
${r.description ? `<br><span style="color:var(--rs-text-muted);font-size:12px">${this.esc(r.description.slice(0, 60))}</span>` : ""}
</div>
</div>
</td>
<td style="color:#94a3b8;font-size:13px">${dateStr}<br><span style="font-size:11px;color:#64748b">${timeStr}</span></td>
<td>${r.sourceLabel ? `<span style="color:${r.sourceColor || "#94a3b8"};font-size:12px">${this.esc(r.sourceLabel)}</span>` : '<span style="color:#64748b;font-size:12px">Free-form</span>'}</td>
<td style="color:var(--rs-text-secondary);font-size:13px">${dateStr}<br><span style="font-size:11px;color:var(--rs-text-muted)">${timeStr}</span></td>
<td>${r.sourceLabel ? `<span style="color:${r.sourceColor || "var(--rs-text-secondary)"};font-size:12px">${this.esc(r.sourceLabel)}</span>` : '<span style="color:var(--rs-text-muted);font-size:12px">Free-form</span>'}</td>
<td>${statusBadge}</td>
<td>
<div class="s-actions">

View File

@ -355,83 +355,83 @@ class FolkTripsPlanner extends HTMLElement {
private render() {
this.shadow.innerHTML = `
<style>
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: #e0e0e0; }
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: var(--rs-text-primary); }
* { box-sizing: border-box; }
.rapp-nav { display: flex; gap: 8px; margin-bottom: 16px; align-items: center; min-height: 36px; }
.rapp-nav__back { padding: 4px 10px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.1); background: transparent; color: #94a3b8; cursor: pointer; font-size: 13px; }
.rapp-nav__back:hover { color: #e2e8f0; border-color: rgba(255,255,255,0.2); }
.rapp-nav__title { font-size: 15px; font-weight: 600; flex: 1; color: #e2e8f0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rapp-nav__back { padding: 4px 10px; border-radius: 6px; border: 1px solid var(--rs-border-subtle); background: transparent; color: var(--rs-text-secondary); cursor: pointer; font-size: 13px; }
.rapp-nav__back:hover { color: var(--rs-text-primary); border-color: rgba(255,255,255,0.2); }
.rapp-nav__title { font-size: 15px; font-weight: 600; flex: 1; color: var(--rs-text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rapp-nav__btn { padding: 6px 14px; border-radius: 6px; border: none; background: #14b8a6; color: #fff; font-weight: 600; cursor: pointer; font-size: 13px; }
.rapp-nav__btn:hover { background: #0d9488; }
.trip-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 12px; }
.trip-card {
background: #1e1e2e; border: 1px solid #333; border-radius: 12px; padding: 16px;
background: var(--rs-bg-surface); border: 1px solid var(--rs-border-strong); border-radius: 12px; padding: 16px;
cursor: pointer; transition: border-color 0.2s, transform 0.15s;
}
.trip-card:hover { border-color: #555; transform: translateY(-1px); }
.trip-card:hover { border-color: var(--rs-bg-surface-raised); transform: translateY(-1px); }
.trip-card-header { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.trip-name { font-size: 15px; font-weight: 600; flex: 1; }
.trip-status {
display: inline-block; font-size: 10px; font-weight: 600; padding: 3px 10px;
border-radius: 12px; text-transform: uppercase; letter-spacing: 0.04em;
}
.trip-chain { font-size: 12px; color: #94a3b8; margin-bottom: 8px; display: flex; align-items: center; gap: 4px; }
.trip-chain-icon { color: #64748b; }
.trip-dates { font-size: 12px; color: #64748b; margin-bottom: 6px; }
.trip-stats { display: flex; gap: 12px; font-size: 11px; color: #64748b; margin-bottom: 8px; }
.trip-chain { font-size: 12px; color: var(--rs-text-secondary); margin-bottom: 8px; display: flex; align-items: center; gap: 4px; }
.trip-chain-icon { color: var(--rs-text-muted); }
.trip-dates { font-size: 12px; color: var(--rs-text-muted); margin-bottom: 6px; }
.trip-stats { display: flex; gap: 12px; font-size: 11px; color: var(--rs-text-muted); margin-bottom: 8px; }
.trip-stat { display: flex; align-items: center; gap: 3px; }
.trip-budget-bar { height: 4px; background: #2a2a3a; border-radius: 2px; overflow: hidden; }
.trip-budget-bar { height: 4px; background: var(--rs-bg-surface-sunken); border-radius: 2px; overflow: hidden; }
.trip-budget-fill { height: 100%; border-radius: 2px; transition: width 0.3s; }
.trip-budget-label { display: flex; justify-content: space-between; font-size: 10px; color: #64748b; margin-top: 4px; }
.trip-budget-label { display: flex; justify-content: space-between; font-size: 10px; color: var(--rs-text-muted); margin-top: 4px; }
.tabs { display: flex; gap: 4px; margin-bottom: 16px; flex-wrap: wrap; }
.tab { padding: 6px 14px; border-radius: 6px; border: 1px solid #333; background: #16161e; color: #888; cursor: pointer; font-size: 12px; }
.tab:hover { border-color: #555; }
.tab { padding: 6px 14px; border-radius: 6px; border: 1px solid var(--rs-border-strong); background: var(--rs-bg-surface-sunken); color: var(--rs-text-muted); cursor: pointer; font-size: 12px; }
.tab:hover { border-color: var(--rs-bg-surface-raised); }
.tab.active { border-color: #14b8a6; color: #14b8a6; }
.section-title { font-size: 14px; font-weight: 600; margin: 16px 0 8px; color: #aaa; }
.item-row { background: #1e1e2e; border: 1px solid #333; border-radius: 8px; padding: 10px 12px; margin-bottom: 6px; display: flex; gap: 10px; align-items: center; }
.section-title { font-size: 14px; font-weight: 600; margin: 16px 0 8px; color: var(--rs-text-secondary); }
.item-row { background: var(--rs-bg-surface); border: 1px solid var(--rs-border-strong); border-radius: 8px; padding: 10px 12px; margin-bottom: 6px; display: flex; gap: 10px; align-items: center; }
.item-title { font-size: 13px; font-weight: 500; flex: 1; }
.item-meta { font-size: 11px; color: #888; }
.badge { font-size: 10px; padding: 2px 6px; border-radius: 4px; background: #2a2a3a; color: #aaa; }
.item-meta { font-size: 11px; color: var(--rs-text-muted); }
.badge { font-size: 10px; padding: 2px 6px; border-radius: 4px; background: var(--rs-bg-surface-sunken); color: var(--rs-text-secondary); }
.budget-bar { height: 8px; background: #2a2a3a; border-radius: 4px; margin: 8px 0; overflow: hidden; }
.budget-bar { height: 8px; background: var(--rs-bg-surface-sunken); border-radius: 4px; margin: 8px 0; overflow: hidden; }
.budget-fill { height: 100%; background: #14b8a6; border-radius: 4px; transition: width 0.3s; }
/* Collaborators */
.collab-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.collab {
display: flex; align-items: center; gap: 6px; padding: 6px 10px;
background: #16161e; border: 1px solid #222; border-radius: 8px; font-size: 12px;
background: var(--rs-bg-surface-sunken); border: 1px solid var(--rs-border-strong); border-radius: 8px; font-size: 12px;
}
.collab-avatar { font-size: 16px; }
.collab-name { font-weight: 500; color: #e2e8f0; }
.collab-role { font-size: 10px; color: #64748b; }
.collab-name { font-weight: 500; color: var(--rs-text-primary); }
.collab-role { font-size: 10px; color: var(--rs-text-muted); }
/* Itinerary with emoji categories */
.itin-row { display: flex; gap: 10px; align-items: flex-start; padding: 10px 12px; margin-bottom: 4px; background: #1e1e2e; border: 1px solid #333; border-radius: 8px; }
.itin-row { display: flex; gap: 10px; align-items: flex-start; padding: 10px 12px; margin-bottom: 4px; background: var(--rs-bg-surface); border: 1px solid var(--rs-border-strong); border-radius: 8px; }
.itin-emoji { font-size: 18px; flex-shrink: 0; width: 28px; text-align: center; }
.itin-body { flex: 1; min-width: 0; }
.itin-title { font-size: 13px; font-weight: 500; color: #e2e8f0; }
.itin-meta { font-size: 11px; color: #64748b; margin-top: 2px; }
.itin-title { font-size: 13px; font-weight: 500; color: var(--rs-text-primary); }
.itin-meta { font-size: 11px; color: var(--rs-text-muted); margin-top: 2px; }
/* Destination cards */
.dest-card { background: #1e1e2e; border: 1px solid #333; border-radius: 10px; padding: 14px; margin-bottom: 8px; display: flex; gap: 12px; align-items: center; }
.dest-card { background: var(--rs-bg-surface); border: 1px solid var(--rs-border-strong); border-radius: 10px; padding: 14px; margin-bottom: 8px; display: flex; gap: 12px; align-items: center; }
.dest-pin { font-size: 24px; }
.dest-info { flex: 1; }
.dest-name { font-size: 14px; font-weight: 600; color: #e2e8f0; }
.dest-country { font-size: 12px; color: #94a3b8; }
.dest-date { font-size: 11px; color: #64748b; }
.dest-name { font-size: 14px; font-weight: 600; color: var(--rs-text-primary); }
.dest-country { font-size: 12px; color: var(--rs-text-secondary); }
.dest-date { font-size: 11px; color: var(--rs-text-muted); }
.packing-item { display: flex; align-items: center; gap: 8px; padding: 6px 0; border-bottom: 1px solid #222; }
.packing-item { display: flex; align-items: center; gap: 8px; padding: 6px 0; border-bottom: 1px solid var(--rs-border-strong); }
.packing-check { width: 16px; height: 16px; cursor: pointer; }
.empty { text-align: center; color: #666; padding: 40px; }
.empty { text-align: center; color: var(--rs-text-muted); padding: 40px; }
</style>
${this.error ? `<div style="color:#ef5350;text-align:center;padding:8px">${this.esc(this.error)}</div>` : ""}
${this.error ? `<div style="color:var(--rs-error);text-align:center;padding:8px">${this.esc(this.error)}</div>` : ""}
${this.view === "list" ? this.renderList() : this.renderDetail()}
`;
this.attachListeners();

View File

@ -179,74 +179,74 @@ class FolkWorkBoard extends HTMLElement {
private render() {
this.shadow.innerHTML = `
<style>
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: #e0e0e0; }
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: var(--rs-text-primary); }
* { box-sizing: border-box; }
.rapp-nav { display: flex; gap: 8px; margin-bottom: 16px; align-items: center; min-height: 36px; }
.rapp-nav__back { padding: 4px 10px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.1); background: transparent; color: #94a3b8; cursor: pointer; font-size: 13px; text-decoration: none; }
.rapp-nav__back:hover { color: #e2e8f0; border-color: rgba(255,255,255,0.2); }
.rapp-nav__title { font-size: 15px; font-weight: 600; flex: 1; color: #e2e8f0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rapp-nav__btn { padding: 6px 14px; border-radius: 6px; border: none; background: #4f46e5; color: #fff; font-weight: 600; cursor: pointer; font-size: 13px; }
.rapp-nav__btn:hover { background: #6366f1; }
.rapp-nav__back { padding: 4px 10px; border-radius: 6px; border: 1px solid var(--rs-border-subtle); background: transparent; color: var(--rs-text-secondary); cursor: pointer; font-size: 13px; text-decoration: none; }
.rapp-nav__back:hover { color: var(--rs-text-primary); border-color: var(--rs-border-strong); }
.rapp-nav__title { font-size: 15px; font-weight: 600; flex: 1; color: var(--rs-text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rapp-nav__btn { padding: 6px 14px; border-radius: 6px; border: none; background: var(--rs-primary); color: #fff; font-weight: 600; cursor: pointer; font-size: 13px; }
.rapp-nav__btn:hover { background: var(--rs-primary-hover); }
.workspace-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; }
.workspace-card {
background: #1e1e2e; border: 1px solid #333; border-radius: 10px;
background: var(--rs-bg-surface); border: 1px solid var(--rs-border-strong); border-radius: 10px;
padding: 16px; cursor: pointer; transition: border-color 0.2s;
}
.workspace-card:hover { border-color: #555; }
.workspace-card:hover { border-color: var(--rs-border-strong); }
.ws-name { font-size: 15px; font-weight: 600; margin-bottom: 4px; }
.ws-meta { font-size: 12px; color: #888; }
.ws-meta { font-size: 12px; color: var(--rs-text-muted); }
.board { display: flex; gap: 12px; overflow-x: auto; min-height: 400px; padding-bottom: 12px; }
.column {
min-width: 240px; max-width: 280px; flex-shrink: 0;
background: #16161e; border: 1px solid #2a2a3a; border-radius: 10px; padding: 12px;
background: var(--rs-bg-surface-sunken); border: 1px solid var(--rs-border); border-radius: 10px; padding: 12px;
}
.col-header { font-size: 13px; font-weight: 600; text-transform: uppercase; color: #888; margin-bottom: 10px; display: flex; justify-content: space-between; }
.col-count { background: #2a2a3a; border-radius: 10px; padding: 0 8px; font-size: 11px; }
.col-header { font-size: 13px; font-weight: 600; text-transform: uppercase; color: var(--rs-text-muted); margin-bottom: 10px; display: flex; justify-content: space-between; }
.col-count { background: var(--rs-border); border-radius: 10px; padding: 0 8px; font-size: 11px; }
.column.drag-over { background: #1a1a2e; border-color: #4f46e5; }
.column.drag-over { background: var(--rs-bg-surface); border-color: var(--rs-primary); }
.task-card {
background: #1e1e2e; border: 1px solid #333; border-radius: 8px;
background: var(--rs-bg-surface); border: 1px solid var(--rs-border-strong); border-radius: 8px;
padding: 10px 12px; margin-bottom: 8px; cursor: grab;
transition: opacity 0.2s;
}
.task-card.dragging { opacity: 0.4; }
.task-card:hover { border-color: #555; }
.task-card:hover { border-color: var(--rs-border-strong); }
.task-title { font-size: 13px; font-weight: 500; margin-bottom: 4px; }
.task-meta { display: flex; gap: 6px; flex-wrap: wrap; }
.badge { font-size: 10px; padding: 2px 6px; border-radius: 4px; background: #2a2a3a; color: #aaa; }
.badge { font-size: 10px; padding: 2px 6px; border-radius: 4px; background: var(--rs-border); color: var(--rs-text-muted); }
.badge-urgent { background: #3b1111; color: #f87171; }
.badge-high { background: #3b2611; color: #fb923c; }
.badge-medium { background: #3b3511; color: #facc15; }
.badge-low { background: #112a3b; color: #60a5fa; }
.move-btns { display: flex; gap: 4px; margin-top: 6px; }
.move-btn { font-size: 10px; padding: 2px 6px; border-radius: 4px; border: 1px solid #333; background: #16161e; color: #888; cursor: pointer; }
.move-btn:hover { border-color: #555; color: #ccc; }
.move-btn { font-size: 10px; padding: 2px 6px; border-radius: 4px; border: 1px solid var(--rs-border-strong); background: var(--rs-bg-surface-sunken); color: var(--rs-text-muted); cursor: pointer; }
.move-btn:hover { border-color: var(--rs-border-strong); color: var(--rs-text-secondary); }
.create-form { background: #1a1a2e; border: 1px solid #4f46e5; border-radius: 8px; padding: 10px; margin-bottom: 10px; }
.create-form { background: var(--rs-bg-surface); border: 1px solid var(--rs-primary); border-radius: 8px; padding: 10px; margin-bottom: 10px; }
.create-form input, .create-form select, .create-form textarea {
width: 100%; padding: 6px 8px; border-radius: 6px; border: 1px solid #333;
background: #16161e; color: #e0e0e0; font-size: 13px; margin-bottom: 6px; outline: none; font-family: inherit;
width: 100%; padding: 6px 8px; border-radius: 6px; border: 1px solid var(--rs-border-strong);
background: var(--rs-bg-surface-sunken); color: var(--rs-text-primary); font-size: 13px; margin-bottom: 6px; outline: none; font-family: inherit;
}
.create-form input:focus, .create-form select:focus, .create-form textarea:focus { border-color: #6366f1; }
.create-form input:focus, .create-form select:focus, .create-form textarea:focus { border-color: var(--rs-primary-hover); }
.create-form textarea { resize: vertical; min-height: 40px; }
.create-form-actions { display: flex; gap: 6px; }
.create-form-actions button { padding: 4px 12px; border-radius: 6px; border: none; font-size: 12px; cursor: pointer; font-weight: 600; }
.cf-submit { background: #4f46e5; color: #fff; }
.cf-cancel { background: transparent; color: #888; border: 1px solid #333 !important; }
.cf-submit { background: var(--rs-primary); color: #fff; }
.cf-cancel { background: transparent; color: var(--rs-text-muted); border: 1px solid var(--rs-border-strong) !important; }
.task-title-input {
width: 100%; padding: 4px 6px; border-radius: 4px; border: 1px solid #6366f1;
background: #16161e; color: #e0e0e0; font-size: 13px; font-weight: 500; outline: none; font-family: inherit;
width: 100%; padding: 4px 6px; border-radius: 4px; border: 1px solid var(--rs-primary-hover);
background: var(--rs-bg-surface-sunken); color: var(--rs-text-primary); font-size: 13px; font-weight: 500; outline: none; font-family: inherit;
}
.badge.clickable { cursor: pointer; transition: all 0.15s; }
.badge.clickable:hover { filter: brightness(1.3); transform: scale(1.1); }
.empty { text-align: center; color: #666; padding: 40px; }
.empty { text-align: center; color: var(--rs-text-muted); padding: 40px; }
@media (max-width: 768px) {
.board { flex-direction: column; overflow-x: visible; }
@ -254,7 +254,7 @@ class FolkWorkBoard extends HTMLElement {
}
</style>
${this.error ? `<div style="color:#ef5350;text-align:center;padding:8px">${this.esc(this.error)}</div>` : ""}
${this.error ? `<div style="color:var(--rs-error);text-align:center;padding:8px">${this.esc(this.error)}</div>` : ""}
${this.view === "list" ? this.renderList() : this.renderBoard()}
`;
this.attachListeners();
@ -340,7 +340,7 @@ class FolkWorkBoard extends HTMLElement {
${priorityBadge(task.priority || "")}
${(task.labels || []).map((l: string) => `<span class="badge">${this.esc(l)}</span>`).join("")}
</div>
${task.assignee ? `<div style="font-size:11px;color:#888;margin-top:4px">${this.esc(task.assignee)}</div>` : ""}
${task.assignee ? `<div style="font-size:11px;color:var(--rs-text-muted);margin-top:4px">${this.esc(task.assignee)}</div>` : ""}
<div class="move-btns">
${otherStatuses.map(s => `<button class="move-btn" data-move="${task.id}" data-to="${s}">\u2192 ${this.esc(s.replace(/_/g, " ").substring(0, 8))}</button>`).join("")}
</div>