/* ── Collab buckets plugin ── */ /* Decoration for bucket-bar tabs that represent a collab bucket. The core still renders the tab via its normal `.bucket-tab` rule; the plugin only layers a few extras on top: a small 👥 icon swap (done in JS), an unread dot pinned top-right, and a 3-cycle pulse animation when a fresh update arrives. */ .bucket-tab.bucket-tab-collab { position: relative; /* Subtle left emphasis so collab tabs read differently at a glance */ box-shadow: inset 2px 0 0 0 color-mix(in srgb, var(--t-accent2) 70%, transparent); } .bucket-tab.bucket-tab-collab .collab-tab-dot { position: absolute; top: -3px; right: -3px; width: 8px; height: 8px; border-radius: 50%; background: var(--t-danger); border: 1px solid var(--t-bg); box-shadow: 0 0 6px color-mix(in srgb, var(--t-danger) 60%, transparent); pointer-events: none; } .bucket-tab.bucket-tab-collab.pulsing { animation: collab-bucket-pulse 1.2s ease-in-out 3; } /* Keep the 3-cycle cadence regardless of global animation-speed override */ body.anim-fast .bucket-tab.bucket-tab-collab.pulsing, body.anim-slow .bucket-tab.bucket-tab-collab.pulsing { animation: collab-bucket-pulse 1.2s ease-in-out 3 !important; animation-duration: 1.2s !important; } @keyframes collab-bucket-pulse { 0%, 100% { box-shadow: inset 2px 0 0 0 color-mix(in srgb, var(--t-accent2) 70%, transparent), 0 0 0 0 color-mix(in srgb, var(--t-danger) 60%, transparent); } 50% { box-shadow: inset 2px 0 0 0 color-mix(in srgb, var(--t-accent2) 70%, transparent), 0 0 0 4px color-mix(in srgb, var(--t-danger) 0%, transparent); } } /* ── Create / edit modal (used by the + Bucket button when the plugin is loaded) ── */ .collab-modal-overlay { position: fixed; inset: 0; z-index: 240; background: var(--t-overlay, rgba(0,0,0,0.6)); display: flex; align-items: center; justify-content: center; padding: 24px; backdrop-filter: blur(var(--ui-blur-overlay, 0px)); -webkit-backdrop-filter: blur(var(--ui-blur-overlay, 0px)); } .collab-modal { width: 100%; max-width: 480px; max-height: 85vh; background: var(--t-surface); border: 1px solid var(--t-border); border-radius: var(--ui-radius-modal); box-shadow: var(--ui-shadow-modal); display: flex; flex-direction: column; overflow: hidden; } .collab-modal-header { display: flex; align-items: center; gap: 8px; padding: 12px 16px; border-bottom: 1px solid var(--t-border); flex-shrink: 0; } .collab-modal-close { background: transparent; border: 1px solid var(--t-border); border-radius: var(--ui-radius-button); color: var(--t-muted); cursor: pointer; padding: 4px 8px; display: flex; align-items: center; transition: color 0.12s, border-color 0.12s; } .collab-modal-close:hover { color: var(--t-danger); border-color: var(--t-danger); } .collab-modal-body { padding: 16px; overflow-y: auto; flex: 1; min-height: 0; } .collab-field { display: block; margin-bottom: 14px; } .collab-field:last-child { margin-bottom: 0; } .collab-field.hidden { display: none; } .collab-label { display: block; font-family: var(--t-font-pixel, 'Silkscreen', cursive); font-size: 9px; color: var(--t-dim); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 5px; } .collab-input { width: 100%; background: var(--t-bg); border: 1px solid var(--t-border); border-radius: var(--ui-radius-input); color: var(--t-bright); padding: 7px 10px; font-size: 12px; font-family: var(--t-font-content, 'JetBrains Mono', monospace); outline: none; transition: border-color 0.12s; } .collab-input:focus { border-color: var(--t-accent); } .collab-color { width: 52px; height: 32px; border: 1px solid var(--t-border); border-radius: var(--ui-radius-input); background: transparent; cursor: pointer; padding: 2px; } .collab-toggle-row { display: flex !important; align-items: center; gap: 8px; padding: 8px 10px; background: var(--t-bg); border: 1px solid var(--t-border); border-radius: var(--ui-radius-input); cursor: pointer; font-size: 12px; color: var(--t-text); user-select: none; } .collab-toggle-row input { accent-color: var(--t-accent); cursor: pointer; } .collab-toggle-row:hover { border-color: var(--t-accent); color: var(--t-bright); } .collab-members-wrap { margin-top: 10px; } .collab-members { border: 1px solid var(--t-border); border-radius: var(--ui-radius-input); background: var(--t-bg); max-height: 200px; overflow-y: auto; padding: 4px; } .collab-members-empty { font-size: 11px; color: var(--t-dim); font-style: italic; padding: 8px 10px; } .collab-member-row { display: flex; align-items: center; gap: 8px; padding: 5px 8px; cursor: pointer; font-size: 12px; color: var(--t-text); border-radius: 2px; user-select: none; } .collab-member-row:hover { background: var(--t-raised); color: var(--t-bright); } .collab-member-row input { cursor: pointer; accent-color: var(--t-accent); } .collab-modal-footer { display: flex; align-items: center; gap: 8px; padding: 12px 16px; border-top: 1px solid var(--t-border); flex-shrink: 0; } .collab-modal-status { font-family: var(--t-font-pixel, 'Silkscreen', cursive); font-size: 9px; color: var(--t-dim); min-height: 14px; } .collab-btn-cancel, .collab-btn-submit { padding: 6px 14px; font-family: var(--t-font-pixel, 'Silkscreen', cursive); font-size: 10px; background: transparent; cursor: pointer; border: 1px solid var(--t-border); border-radius: var(--ui-radius-button); color: var(--t-muted); transition: color 0.12s, border-color 0.12s, background 0.12s; } .collab-btn-cancel:hover { color: var(--t-danger); border-color: var(--t-danger); } .collab-btn-submit { color: var(--t-accent); border-color: var(--t-accent); } .collab-btn-submit:hover { background: color-mix(in srgb, var(--t-accent) 12%, transparent); } .collab-readonly { display: block; padding: 7px 10px; background: var(--t-bg); border: 1px solid var(--t-border); border-radius: var(--ui-radius-input); color: var(--t-text); font-family: var(--t-font-content, 'JetBrains Mono', monospace); font-size: 12px; } .collab-hint { margin-top: 4px; font-size: 10px; color: var(--t-dim); font-style: italic; } /* ── Phase 4: exclusive edit-lock indicator on board cards ── Locked cards get a frosted overlay + centered lock icon. The card content underneath stays visible enough to read but is clearly "off-limits" — the cue is intentionally heavier than the old badge so the user can't miss it. */ .board-card.collab-locked { box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--t-accent2) 70%, transparent); } .board-card.collab-locked > *:not(.collab-lock-badge) { filter: blur(2px); opacity: 0.55; transition: filter 0.15s, opacity 0.15s; } .collab-lock-badge { position: absolute; top: 0; left: 0; right: 0; bottom: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; color: var(--t-accent2); background: color-mix(in srgb, var(--t-surface) 60%, transparent); backdrop-filter: blur(1px); -webkit-backdrop-filter: blur(1px); z-index: 6; pointer-events: none; font-family: var(--t-font-pixel, 'Silkscreen', cursive); font-size: 9px; text-align: center; padding: 4px 8px; } .collab-lock-badge i { font-size: 22px; } .collab-lock-badge span { color: var(--t-text); max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* ── Editor takeover (foreign lock arrived while editing) ── */ .collab-readonly-modal #inp-title, .collab-readonly-modal #editor-body { opacity: 0.7; cursor: not-allowed; } .collab-readonly-modal #editor-body { background: color-mix(in srgb, var(--t-danger) 6%, transparent) !important; } .collab-save-blocked { opacity: 0.4 !important; cursor: not-allowed !important; pointer-events: auto; } .collab-takeover-banner { display: flex; align-items: center; gap: 10px; padding: 10px 14px; background: color-mix(in srgb, var(--t-danger) 12%, transparent); border-bottom: 1px solid var(--t-danger); color: var(--t-bright); font-family: var(--t-font-pixel, 'Silkscreen', cursive); font-size: 10px; line-height: 1.5; } .collab-takeover-banner i { color: var(--t-danger); font-size: 16px; flex-shrink: 0; } .collab-takeover-banner b { color: var(--t-danger); } /* ── Conflict toast (LWW losers stashed into the Conflicts bucket) ── */ .collab-conflict-toast { position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%); z-index: 220; display: flex; align-items: center; gap: 10px; padding: 10px 14px 10px 12px; max-width: 480px; font-family: var(--t-font-pixel, 'Silkscreen', cursive); font-size: 10px; color: var(--t-bright); background: var(--t-surface); border: 1px solid var(--t-danger); border-radius: var(--ui-radius-modal); box-shadow: var(--ui-shadow-modal); transition: opacity 0.4s; } .collab-conflict-toast i.ph-warning { color: var(--t-danger); flex-shrink: 0; } .collab-conflict-toast b { color: var(--t-danger); font-weight: 700; } .collab-conflict-toast.fade-out { opacity: 0; } /* Queued variant reuses the layout but switches the danger accents for a neutral info color — used for "relay was down, bucket is queued" and "queued bucket finally synced" messages. */ .collab-conflict-toast.collab-queued-toast { border-color: var(--t-accent2); } .collab-conflict-toast.collab-queued-toast i.ph-cloud-arrow-up { color: var(--t-accent2); flex-shrink: 0; } .collab-conflict-close { background: transparent; border: none; color: var(--t-dim); cursor: pointer; padding: 2px 4px; margin-left: 6px; display: flex; align-items: center; } .collab-conflict-close:hover { color: var(--t-danger); } /* Lock toast: same shape as the conflict toast but accented with the collab/lock color. Reused for both the editor-takeover notification and the "your push was queued because someone else is editing" toast. */ .collab-conflict-toast.collab-lock-toast { border-color: var(--t-accent2); } .collab-conflict-toast.collab-lock-toast i.ph-lock-simple-fill { color: var(--t-accent2); flex-shrink: 0; } .collab-conflict-toast.collab-lock-toast b { color: var(--t-accent2); }