◈
{onClose && (
@@ -558,7 +660,25 @@ export function SourcePanel({
onDeleteNote={onDeleteNote}
/>
) :
- ) : brandMode === 'bookmark' ? (
+ ) : brandMode === 'editor' ? (
+ onSelectDoc && onAddDoc && onDeleteDoc && onRenameDoc && onCreateEditorFolder && onRenameEditorFolder && onDeleteEditorFolder && onMoveDocToFolder && onSelectEditorFolder ? (
+
+ ) :
+ ) : brandMode === 'bookmark' || brandMode === 'draw' ? (
) : (
<>
@@ -595,7 +715,9 @@ export function SourcePanel({
{categories.map((category, catIdx) => {
- const rootFeeds = category.feeds.filter(f => !f.folder);
+ const sq = searchQuery.toLowerCase();
+ const allCatFeeds = sq ? category.feeds.filter(f => f.name.toLowerCase().includes(sq)) : category.feeds;
+ const rootFeeds = allCatFeeds.filter(f => !f.folder);
const folderTree = buildFolderTree(category.folders);
return (
@@ -675,6 +797,29 @@ export function SourcePanel({
)}
+ {/* Mode tabs + Ctrl+K hint */}
+ {onBrandSwitch && (
+
+ {([
+ { mode: 'flux' as const, icon: '◈', label: 'Flux', shortcut: '1' },
+ { mode: 'bookmark' as const, icon: '🔖', label: 'Signets', shortcut: '2' },
+ { mode: 'note' as const, icon: '📝', label: 'Notes', shortcut: '3' },
+ { mode: 'editor' as const, icon: '✏️', label: 'Éditeur', shortcut: '4' },
+ { mode: 'draw' as const, icon: '🎨', label: 'Dessin', shortcut: '5' },
+ ]).map(tab => (
+
+ ))}
+ Ctrl+K
+
+ )}
+
{brandMode === 'flux' && syncError && (
⚠
@@ -682,7 +827,53 @@ export function SourcePanel({
)}
- {brandMode === 'flux' &&
+ {/* ── Bookmark URL input ── */}
+
+ {bookmarkUrlOpen && brandMode === 'bookmark' && (
+
+
+
+ )}
+
+
+ {/* ── Footer ── */}
+
)}
+
+
+ {paletteOpen &&
setPaletteOpen(false)} />}
+
-
}
+
= ({
+ children,
+ className = '',
+ spotlightColor = 'rgba(255, 255, 255, 0.25)'
+}) => {
+ const divRef = useRef(null);
+ const [isFocused, setIsFocused] = useState(false);
+ const [position, setPosition] = useState({ x: 0, y: 0 });
+ const [opacity, setOpacity] = useState(0);
+
+ const handleMouseMove: React.MouseEventHandler = e => {
+ if (!divRef.current || isFocused) return;
+ const rect = divRef.current.getBoundingClientRect();
+ setPosition({ x: e.clientX - rect.left, y: e.clientY - rect.top });
+ };
+
+ const handleFocus = () => {
+ setIsFocused(true);
+ setOpacity(0.6);
+ };
+
+ const handleBlur = () => {
+ setIsFocused(false);
+ setOpacity(0);
+ };
+
+ const handleMouseEnter = () => {
+ setOpacity(0.6);
+ };
+
+ const handleMouseLeave = () => {
+ setOpacity(0);
+ };
+
+ return (
+
+ );
+};
+
+export default SpotlightCard;
diff --git a/src/components/SuperDraw.tsx b/src/components/SuperDraw.tsx
new file mode 100644
index 0000000..e4472b9
--- /dev/null
+++ b/src/components/SuperDraw.tsx
@@ -0,0 +1,585 @@
+import { useState, useCallback, useEffect, useRef, type MouseEvent as RMouseEvent } from 'react';
+
+// ─── Types ───────────────────────────────────────────────────────────
+
+type Tool = 'select' | 'hand' | 'rect' | 'ellipse' | 'diamond' | 'line' | 'arrow' | 'freedraw' | 'text' | 'eraser';
+
+interface DrawElement {
+ id: string;
+ type: 'rect' | 'ellipse' | 'diamond' | 'line' | 'arrow' | 'freedraw' | 'text';
+ x: number; y: number; w: number; h: number;
+ points?: number[][];
+ text?: string;
+ stroke: string;
+ fill: string;
+ strokeWidth: number;
+ opacity: number;
+ fontSize?: number;
+}
+
+interface Camera { x: number; y: number; zoom: number; }
+type Handle = 'nw' | 'ne' | 'sw' | 'se';
+
+const STORAGE_KEY = 'superflux_draw';
+const uid = () => Math.random().toString(36).slice(2, 10);
+
+function loadData(): { elements: DrawElement[]; camera: Camera } {
+ try { const r = localStorage.getItem(STORAGE_KEY); if (r) return JSON.parse(r); } catch { /* */ }
+ return { elements: [], camera: { x: 0, y: 0, zoom: 1 } };
+}
+function saveData(els: DrawElement[], cam: Camera) {
+ try { localStorage.setItem(STORAGE_KEY, JSON.stringify({ elements: els, camera: cam })); } catch { /* */ }
+}
+
+// ─── Geometry ────────────────────────────────────────────────────────
+
+function s2w(sx: number, sy: number, c: Camera): [number, number] {
+ return [(sx - c.x) / c.zoom, (sy - c.y) / c.zoom];
+}
+
+function hitTest(el: DrawElement, wx: number, wy: number): boolean {
+ const m = 6;
+ if (el.type === 'freedraw' && el.points) return el.points.some(([px, py]) => Math.hypot(px - wx, py - wy) < 10);
+ if ((el.type === 'line' || el.type === 'arrow') && el.points && el.points.length >= 2) {
+ for (let i = 0; i < el.points.length - 1; i++) {
+ const [ax, ay] = el.points[i], [bx, by] = el.points[i + 1];
+ const dx = bx - ax, dy = by - ay, l2 = dx * dx + dy * dy;
+ if (l2 === 0) { if (Math.hypot(wx - ax, wy - ay) < 8) return true; continue; }
+ const t = Math.max(0, Math.min(1, ((wx - ax) * dx + (wy - ay) * dy) / l2));
+ if (Math.hypot(wx - (ax + t * dx), wy - (ay + t * dy)) < 8) return true;
+ }
+ return false;
+ }
+ return wx >= el.x - m && wx <= el.x + el.w + m && wy >= el.y - m && wy <= el.y + el.h + m;
+}
+
+function getHandles(el: DrawElement): Record {
+ return { nw: [el.x, el.y], ne: [el.x + el.w, el.y], sw: [el.x, el.y + el.h], se: [el.x + el.w, el.y + el.h] };
+}
+
+function hitHandle(el: DrawElement, wx: number, wy: number): Handle | null {
+ for (const [k, [hx, hy]] of Object.entries(getHandles(el)))
+ if (Math.hypot(wx - hx, wy - hy) < 8) return k as Handle;
+ return null;
+}
+
+// ─── Paint helpers ───────────────────────────────────────────────────
+
+function adaptColor(c: string, isDark: boolean): string {
+ if (isDark && c === '#1e1e1e') return '#ffffff';
+ if (!isDark && c === '#ffffff') return '#1e1e1e';
+ return c;
+}
+
+function paintEl(ctx: CanvasRenderingContext2D, el: DrawElement, cam: Camera, isDark: boolean) {
+ ctx.save();
+ ctx.globalAlpha = el.opacity;
+ ctx.translate(cam.x, cam.y); ctx.scale(cam.zoom, cam.zoom);
+ ctx.strokeStyle = adaptColor(el.stroke, isDark);
+ ctx.fillStyle = el.fill === 'transparent' ? 'transparent' : adaptColor(el.fill, isDark);
+ ctx.lineWidth = el.strokeWidth; ctx.lineJoin = 'round'; ctx.lineCap = 'round';
+ switch (el.type) {
+ case 'rect':
+ if (el.fill !== 'transparent') ctx.fillRect(el.x, el.y, el.w, el.h);
+ ctx.strokeRect(el.x, el.y, el.w, el.h); break;
+ case 'ellipse':
+ ctx.beginPath();
+ ctx.ellipse(el.x + el.w / 2, el.y + el.h / 2, Math.abs(el.w / 2), Math.abs(el.h / 2), 0, 0, Math.PI * 2);
+ if (el.fill !== 'transparent') ctx.fill(); ctx.stroke(); break;
+ case 'diamond': {
+ const cx = el.x + el.w / 2, cy = el.y + el.h / 2;
+ ctx.beginPath(); ctx.moveTo(cx, el.y); ctx.lineTo(el.x + el.w, cy); ctx.lineTo(cx, el.y + el.h); ctx.lineTo(el.x, cy); ctx.closePath();
+ if (el.fill !== 'transparent') ctx.fill(); ctx.stroke(); break;
+ }
+ case 'freedraw':
+ if (!el.points || el.points.length < 2) break;
+ ctx.beginPath(); ctx.moveTo(el.points[0][0], el.points[0][1]);
+ for (let i = 1; i < el.points.length; i++) ctx.lineTo(el.points[i][0], el.points[i][1]);
+ ctx.stroke(); break;
+ case 'line': case 'arrow':
+ if (!el.points || el.points.length < 2) break;
+ ctx.beginPath(); ctx.moveTo(el.points[0][0], el.points[0][1]);
+ for (let i = 1; i < el.points.length; i++) ctx.lineTo(el.points[i][0], el.points[i][1]);
+ ctx.stroke();
+ if (el.type === 'arrow') {
+ const [px, py] = el.points[el.points.length - 2], [ex, ey] = el.points[el.points.length - 1];
+ const a = Math.atan2(ey - py, ex - px), hl = 12 + el.strokeWidth * 2;
+ ctx.beginPath();
+ ctx.moveTo(ex, ey); ctx.lineTo(ex - hl * Math.cos(a - 0.4), ey - hl * Math.sin(a - 0.4));
+ ctx.moveTo(ex, ey); ctx.lineTo(ex - hl * Math.cos(a + 0.4), ey - hl * Math.sin(a + 0.4));
+ ctx.stroke();
+ }
+ break;
+ case 'text':
+ ctx.font = `${el.fontSize ?? 20}px sans-serif`; ctx.fillStyle = adaptColor(el.stroke, isDark); ctx.textBaseline = 'top';
+ (el.text ?? '').split('\n').forEach((l, i) => ctx.fillText(l, el.x, el.y + i * (el.fontSize ?? 20) * 1.2));
+ break;
+ }
+ ctx.restore();
+}
+
+function paintSel(ctx: CanvasRenderingContext2D, el: DrawElement, cam: Camera) {
+ ctx.save(); ctx.translate(cam.x, cam.y); ctx.scale(cam.zoom, cam.zoom);
+ ctx.strokeStyle = '#4a90d9'; ctx.lineWidth = 1.5 / cam.zoom;
+ ctx.setLineDash([4 / cam.zoom, 4 / cam.zoom]);
+ ctx.strokeRect(el.x - 2, el.y - 2, el.w + 4, el.h + 4); ctx.setLineDash([]);
+ const r = 4 / cam.zoom; ctx.fillStyle = '#fff'; ctx.strokeStyle = '#4a90d9'; ctx.lineWidth = 1.5 / cam.zoom;
+ for (const [hx, hy] of Object.values(getHandles(el))) { ctx.beginPath(); ctx.arc(hx, hy, r, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); }
+ ctx.restore();
+}
+
+// ─── Tool config ─────────────────────────────────────────────────────
+
+const TOOLS: { tool: Tool; icon: string; label: string; key: string }[] = [
+ { tool: 'hand', icon: '✋', label: 'Déplacer', key: 'h' },
+ { tool: 'select', icon: '⇱', label: 'Sélection', key: 'v' },
+ { tool: 'rect', icon: '▭', label: 'Rectangle', key: 'r' },
+ { tool: 'ellipse', icon: '◯', label: 'Ellipse', key: 'o' },
+ { tool: 'diamond', icon: '◇', label: 'Losange', key: 'd' },
+ { tool: 'line', icon: '╱', label: 'Ligne', key: 'l' },
+ { tool: 'arrow', icon: '→', label: 'Flèche', key: 'a' },
+ { tool: 'freedraw', icon: '✎', label: 'Crayon', key: 'p' },
+ { tool: 'text', icon: 'T', label: 'Texte', key: 't' },
+ { tool: 'eraser', icon: '⌫', label: 'Gomme', key: 'e' },
+];
+const COLORS = ['#1e1e1e', '#e03131', '#2f9e44', '#1971c2', '#f08c00', '#9c36b5', '#ffffff', 'transparent'];
+const WIDTHS = [1, 2, 3, 5, 8];
+const FONT_SIZES = [12, 16, 20, 28, 36, 48, 64];
+
+// ═════════════════════════════════════════════════════════════════════
+// Component
+// ═════════════════════════════════════════════════════════════════════
+
+export function SuperDraw() {
+ const canvasRef = useRef(null);
+ const wrapRef = useRef(null);
+ const lastPtr = useRef<[number, number]>([0, 0]);
+ const saveTimer = useRef>();
+
+ const init = useRef(loadData());
+ const [els, setEls] = useState(init.current.elements);
+ const [cam, setCam] = useState(init.current.camera);
+ const [tool, setTool] = useState('select');
+ const [sel, setSel] = useState>(new Set());
+ const [sColor, setSColor] = useState('#1e1e1e');
+ const [fColor, setFColor] = useState('transparent');
+ const [sWidth, setSWidth] = useState(2);
+ const [hist, setHist] = useState([init.current.elements]);
+ const [hIdx, setHIdx] = useState(0);
+ const [dark, setDark] = useState(false);
+ const [fontSize, setFontSize] = useState(20);
+ const [textEdit, setTextEdit] = useState<{ wx: number; wy: number; sx: number; sy: number } | null>(null);
+ const [textValue, setTextValue] = useState('');
+ const textAreaRef = useRef(null);
+
+ // Refs mirroring state for imperative paint
+ const R = useRef({ els, cam, sel });
+ R.current = { els, cam, sel };
+
+ const ia = useRef<{
+ type: 'none' | 'draw' | 'move' | 'resize' | 'pan' | 'selbox';
+ sx: number; sy: number;
+ elId?: string; handle?: Handle;
+ origEls?: DrawElement[]; origCam?: Camera;
+ }>({ type: 'none', sx: 0, sy: 0 });
+
+ // ── Switch default stroke color with dark/light mode ──
+ useEffect(() => {
+ setSColor(dark ? '#ffffff' : '#1e1e1e');
+ }, [dark]);
+
+ // ── Auto-focus textarea when it appears ──
+ useEffect(() => {
+ if (textEdit) {
+ // Use rAF + setTimeout to ensure DOM has rendered the textarea
+ requestAnimationFrame(() => {
+ setTimeout(() => textAreaRef.current?.focus(), 0);
+ });
+ }
+ }, [textEdit]);
+
+ // ── Autosave ──
+ useEffect(() => {
+ clearTimeout(saveTimer.current);
+ saveTimer.current = setTimeout(() => saveData(els, cam), 400);
+ }, [els, cam]);
+
+ // ── History ──
+ const push = useCallback((next: DrawElement[]) => {
+ setHist(p => [...p.slice(0, hIdx + 1), next]);
+ setHIdx(p => p + 1);
+ }, [hIdx]);
+ const undo = useCallback(() => { if (hIdx > 0) { setHIdx(hIdx - 1); setEls(hist[hIdx - 1]); setSel(new Set()); } }, [hIdx, hist]);
+ const redo = useCallback(() => { if (hIdx < hist.length - 1) { setHIdx(hIdx + 1); setEls(hist[hIdx + 1]); setSel(new Set()); } }, [hIdx, hist]);
+ const delSel = useCallback(() => { if (sel.size === 0) return; const n = els.filter(e => !sel.has(e.id)); setEls(n); push(n); setSel(new Set()); }, [els, sel, push]);
+ const dupSel = useCallback(() => {
+ if (sel.size === 0) return;
+ const cl: DrawElement[] = [], ids = new Set();
+ els.filter(e => sel.has(e.id)).forEach(e => { const id = uid(); cl.push({ ...e, id, x: e.x + 20, y: e.y + 20 }); ids.add(id); });
+ const n = [...els, ...cl]; setEls(n); push(n); setSel(ids);
+ }, [els, sel, push]);
+
+ // ── Paint (imperative — reads from R.current) ──
+ const paint = useCallback(() => {
+ const canvas = canvasRef.current;
+ const wrap = wrapRef.current;
+ if (!canvas || !wrap) return;
+
+ const w = wrap.clientWidth;
+ const h = wrap.clientHeight;
+ if (w === 0 || h === 0) return;
+
+ const dpr = window.devicePixelRatio || 1;
+ const bw = Math.round(w * dpr);
+ const bh = Math.round(h * dpr);
+
+ // Only resize buffer if CSS size changed
+ if (canvas.width !== bw || canvas.height !== bh) {
+ canvas.width = bw;
+ canvas.height = bh;
+ }
+
+ const ctx = canvas.getContext('2d');
+ if (!ctx) return;
+
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+ ctx.fillStyle = dark ? '#1e1e2e' : '#ffffff';
+ ctx.fillRect(0, 0, w, h);
+
+ const { els: e, cam: c, sel: s } = R.current;
+ e.forEach(el => paintEl(ctx, el, c, dark));
+ e.filter(el => s.has(el.id)).forEach(el => paintSel(ctx, el, c));
+
+ if (ia.current.type === 'selbox') {
+ const [sx, sy] = [ia.current.sx, ia.current.sy];
+ const [lx, ly] = lastPtr.current;
+ ctx.strokeStyle = '#4a90d9'; ctx.lineWidth = 1; ctx.setLineDash([4, 4]);
+ ctx.fillStyle = 'rgba(74,144,217,0.08)';
+ const bx = Math.min(sx, lx), by = Math.min(sy, ly);
+ ctx.fillRect(bx, by, Math.abs(lx - sx), Math.abs(ly - sy));
+ ctx.strokeRect(bx, by, Math.abs(lx - sx), Math.abs(ly - sy));
+ ctx.setLineDash([]);
+ }
+ }, [dark]);
+
+ // Repaint on state change
+ useEffect(() => {
+ const id = requestAnimationFrame(paint);
+ return () => cancelAnimationFrame(id);
+ }, [els, cam, sel, paint]);
+
+ // Resize observer on WRAPPER only
+ useEffect(() => {
+ const wrap = wrapRef.current;
+ if (!wrap) return;
+ const ro = new ResizeObserver(() => requestAnimationFrame(paint));
+ ro.observe(wrap);
+ return () => ro.disconnect();
+ }, [paint]);
+
+ // ── Pointer ──
+ const onDown = useCallback((e: RMouseEvent) => {
+ const canvas = canvasRef.current; if (!canvas) return;
+ const rect = canvas.getBoundingClientRect();
+ const sx = e.clientX - rect.left, sy = e.clientY - rect.top;
+ lastPtr.current = [sx, sy];
+ const [wx, wy] = s2w(sx, sy, cam);
+
+ // Text tool: open textarea at click position
+ if (tool === 'text') {
+ // If there's already a textarea open with text, submit it first
+ if (textEdit && textValue.trim()) {
+ const el: DrawElement = { id: uid(), type: 'text', x: textEdit.wx, y: textEdit.wy, w: 200, h: 30, text: textValue, stroke: sColor, fill: 'transparent', strokeWidth: 1, opacity: 1, fontSize };
+ const n = [...els, el]; setEls(n); push(n);
+ }
+ setTextValue('');
+ setTextEdit({ wx, wy, sx, sy });
+ return;
+ }
+
+ // Use document-level listeners for drag tracking
+
+ if (tool === 'hand') { ia.current = { type: 'pan', sx, sy, origCam: { ...cam } }; return; }
+ if (tool === 'select') {
+ for (const el of els.filter(x => sel.has(x.id))) {
+ const h = hitHandle(el, wx, wy);
+ if (h) { ia.current = { type: 'resize', sx: wx, sy: wy, elId: el.id, handle: h, origEls: els.map(x => ({ ...x })) }; return; }
+ }
+ const hit = [...els].reverse().find(el => hitTest(el, wx, wy));
+ if (hit) {
+ if (!sel.has(hit.id)) setSel(e.shiftKey ? new Set([...sel, hit.id]) : new Set([hit.id]));
+ ia.current = { type: 'move', sx: wx, sy: wy, origEls: els.map(x => ({ ...x })) }; return;
+ }
+ setSel(new Set());
+ ia.current = { type: 'selbox', sx, sy }; return;
+ }
+ if (tool === 'eraser') {
+ const hit = [...els].reverse().find(el => hitTest(el, wx, wy));
+ if (hit) { const n = els.filter(el => el.id !== hit.id); setEls(n); push(n); } return;
+ }
+
+ const newEl: DrawElement = {
+ id: uid(), type: (tool === 'freedraw' ? 'freedraw' : tool === 'line' ? 'line' : tool === 'arrow' ? 'arrow' : tool) as DrawElement['type'],
+ x: wx, y: wy, w: 0, h: 0, stroke: sColor, fill: fColor, strokeWidth: sWidth, opacity: 1,
+ ...(tool === 'freedraw' ? { points: [[wx, wy]] } : {}),
+ ...((tool === 'line' || tool === 'arrow') ? { points: [[wx, wy], [wx, wy]] } : {}),
+ };
+ setEls(prev => [...prev, newEl]);
+ ia.current = { type: 'draw', sx: wx, sy: wy, elId: newEl.id };
+ }, [tool, cam, els, sel, sColor, fColor, sWidth, push, textEdit, textValue]);
+
+ const onMove = useCallback((e: RMouseEvent) => {
+ const canvas = canvasRef.current; if (!canvas) return;
+ const rect = canvas.getBoundingClientRect();
+ const sx = e.clientX - rect.left, sy = e.clientY - rect.top;
+ lastPtr.current = [sx, sy];
+ const [wx, wy] = s2w(sx, sy, cam);
+ const cur = ia.current;
+
+ if (cur.type === 'pan' && cur.origCam) { setCam({ x: cur.origCam.x + (sx - cur.sx), y: cur.origCam.y + (sy - cur.sy), zoom: cur.origCam.zoom }); return; }
+ if (cur.type === 'draw' && cur.elId) {
+ setEls(prev => prev.map(el => {
+ if (el.id !== cur.elId) return el;
+ if (el.type === 'freedraw') return { ...el, points: [...(el.points || []), [wx, wy]] };
+ if (el.type === 'line' || el.type === 'arrow') { const pts = [...(el.points || [])]; pts[pts.length - 1] = [wx, wy]; return { ...el, points: pts }; }
+ return { ...el, x: Math.min(cur.sx, wx), y: Math.min(cur.sy, wy), w: Math.abs(wx - cur.sx), h: Math.abs(wy - cur.sy) };
+ })); return;
+ }
+ if (cur.type === 'move' && cur.origEls) {
+ const dx = wx - cur.sx, dy = wy - cur.sy;
+ setEls(cur.origEls.map(el => {
+ if (!sel.has(el.id)) return el;
+ const m = { ...el, x: el.x + dx, y: el.y + dy };
+ if (el.points) m.points = el.points.map(([px, py]) => [px + dx, py + dy]);
+ return m;
+ })); return;
+ }
+ if (cur.type === 'resize' && cur.origEls && cur.elId && cur.handle) {
+ setEls(cur.origEls.map(el => {
+ if (el.id !== cur.elId) return el;
+ const o = cur.origEls!.find(x => x.id === el.id)!;
+ let { x, y, w, h } = o;
+ const dx = wx - cur.sx, dy = wy - cur.sy;
+ if (cur.handle === 'se') { w += dx; h += dy; }
+ else if (cur.handle === 'nw') { x += dx; y += dy; w -= dx; h -= dy; }
+ else if (cur.handle === 'ne') { y += dy; w += dx; h -= dy; }
+ else { x += dx; w -= dx; h += dy; }
+ return { ...el, x, y, w, h };
+ })); return;
+ }
+ if (cur.type === 'selbox') requestAnimationFrame(paint);
+ }, [cam, sel, paint]);
+
+ const onUp = useCallback(() => {
+ const cur = ia.current;
+ if (cur.type === 'draw' || cur.type === 'move' || cur.type === 'resize') push([...els]);
+ if (cur.type === 'selbox') {
+ const [wx1, wy1] = s2w(Math.min(cur.sx, lastPtr.current[0]), Math.min(cur.sy, lastPtr.current[1]), cam);
+ const [wx2, wy2] = s2w(Math.max(cur.sx, lastPtr.current[0]), Math.max(cur.sy, lastPtr.current[1]), cam);
+ const ids = new Set();
+ els.forEach(el => { if (el.x >= wx1 && el.x + el.w <= wx2 && el.y >= wy1 && el.y + el.h <= wy2) ids.add(el.id); });
+ setSel(ids);
+ }
+ ia.current = { type: 'none', sx: 0, sy: 0 };
+ }, [els, cam, push]);
+
+ // ── Wheel zoom/pan ──
+ useEffect(() => {
+ const canvas = canvasRef.current; if (!canvas) return;
+ const h = (e: WheelEvent) => {
+ e.preventDefault();
+ const rect = canvas.getBoundingClientRect();
+ const sx = e.clientX - rect.left, sy = e.clientY - rect.top;
+ const c = R.current.cam;
+ if (e.ctrlKey || e.metaKey) {
+ const f = e.deltaY > 0 ? 0.92 : 1.08;
+ const z = Math.max(0.1, Math.min(10, c.zoom * f));
+ setCam({ x: sx - (sx - c.x) * (z / c.zoom), y: sy - (sy - c.y) * (z / c.zoom), zoom: z });
+ } else {
+ setCam(p => ({ ...p, x: p.x - e.deltaX, y: p.y - e.deltaY }));
+ }
+ };
+ canvas.addEventListener('wheel', h, { passive: false });
+ return () => canvas.removeEventListener('wheel', h);
+ }, []);
+
+ // ── Keyboard shortcuts ──
+ useEffect(() => {
+ const h = (e: KeyboardEvent) => {
+ if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
+ if (e.ctrlKey && e.key === 'z') { e.preventDefault(); undo(); return; }
+ if (e.ctrlKey && e.key === 'y') { e.preventDefault(); redo(); return; }
+ if (e.ctrlKey && e.key === 'd') { e.preventDefault(); dupSel(); return; }
+ if (e.key === 'Delete' || e.key === 'Backspace') { delSel(); return; }
+ if (e.ctrlKey && e.key === 'a') { e.preventDefault(); setSel(new Set(els.map(el => el.id))); return; }
+ const def = TOOLS.find(t => t.key === e.key.toLowerCase());
+ if (def && !e.ctrlKey && !e.metaKey) { setTool(def.tool); setSel(new Set()); }
+ };
+ window.addEventListener('keydown', h);
+ return () => window.removeEventListener('keydown', h);
+ }, [undo, redo, delSel, dupSel, els]);
+
+
+ const submitText = useCallback(() => {
+ if (!textEdit || !textValue.trim()) return;
+ const el: DrawElement = { id: uid(), type: 'text', x: textEdit.wx, y: textEdit.wy, w: 200, h: 30, text: textValue, stroke: sColor, fill: 'transparent', strokeWidth: 1, opacity: 1, fontSize };
+ const n = [...els, el]; setEls(n); push(n);
+ setTextValue('');
+ setTextEdit(null);
+ }, [textEdit, textValue, els, sColor, push, fontSize]);
+
+ const exportPng = useCallback(() => {
+ const c = canvasRef.current; if (!c) return;
+ const a = document.createElement('a'); a.download = 'superdraw.png'; a.href = c.toDataURL('image/png'); a.click();
+ }, []);
+
+ const clearAll = useCallback(() => { setEls([]); push([]); setSel(new Set()); setCam({ x: 0, y: 0, zoom: 1 }); }, [push]);
+
+ // ─── Styles ────────────────────────────────────────────────────────
+
+ const bg = dark ? 'rgba(30,30,46,0.95)' : '#f0ede8';
+ const border = dark ? 'rgba(255,255,255,0.06)' : '#e8e5df';
+ const txtDim = dark ? '#888' : '#9e9b96';
+ const txtSec = dark ? '#aaa' : '#6b6964';
+ const accent = '#d4a853';
+ const accentBg = 'rgba(212,168,83,0.12)';
+
+ const toolBtn = (active: boolean): React.CSSProperties => ({
+ width: 34, height: 34, display: 'flex', alignItems: 'center', justifyContent: 'center',
+ border: active ? `1.5px solid ${accent}` : 'none', borderRadius: 6,
+ background: active ? accentBg : 'transparent', color: active ? accent : txtSec,
+ fontSize: 16, cursor: 'pointer',
+ });
+
+ const actBtn = (disabled: boolean): React.CSSProperties => ({
+ width: 34, height: 34, display: 'flex', alignItems: 'center', justifyContent: 'center',
+ border: 'none', borderRadius: 6, background: 'transparent', color: txtSec,
+ fontSize: 16, cursor: disabled ? 'default' : 'pointer', opacity: disabled ? 0.35 : 1,
+ });
+
+ const colorBtn = (c: string, active: boolean): React.CSSProperties => ({
+ width: 20, height: 20, borderRadius: '50%', cursor: 'pointer',
+ border: active ? `2px solid ${accent}` : c === '#ffffff' ? '1px solid #ccc' : '2px solid transparent',
+ background: c === 'transparent' ? 'repeating-conic-gradient(#ddd 0% 25%, transparent 0% 50%) 50% / 10px 10px' : c,
+ });
+
+ return (
+
+ {/* Toolbar */}
+
+
+ {TOOLS.map(t => (
+
+ ))}
+
+
+
+
Contour
+
+ {COLORS.filter(c => c !== 'transparent').map(c => (
+
+
Fond
+
+ {COLORS.map(c => (
+
+
+
+
+
Épaisseur
+
+ {WIDTHS.map(w => (
+
+ ))}
+
+
+ {tool === 'text' && (<>
+
+
+
Police
+
+ {FONT_SIZES.map(s => (
+
+ ))}
+
+
+ >)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Canvas wrapper */}
+
+
+
+ {/* Textarea overlay for text tool */}
+ {textEdit && (
+
+
+ );
+}
diff --git a/src/components/SuperEditor.tsx b/src/components/SuperEditor.tsx
new file mode 100644
index 0000000..b80b4a7
--- /dev/null
+++ b/src/components/SuperEditor.tsx
@@ -0,0 +1,483 @@
+import { useCallback, useEffect, useRef, useState, forwardRef, useImperativeHandle } from 'react';
+import { EditorContent, useEditor, ReactRenderer } from '@tiptap/react';
+import StarterKit from '@tiptap/starter-kit';
+import Subscript from '@tiptap/extension-subscript';
+import Superscript from '@tiptap/extension-superscript';
+import Underline from '@tiptap/extension-underline';
+import TextAlign from '@tiptap/extension-text-align';
+import { TextStyleKit } from '@tiptap/extension-text-style';
+import Highlight from '@tiptap/extension-highlight';
+import Color from '@tiptap/extension-color';
+import Link from '@tiptap/extension-link';
+import Image from '@tiptap/extension-image';
+import Placeholder from '@tiptap/extension-placeholder';
+import TaskList from '@tiptap/extension-task-list';
+import TaskItem from '@tiptap/extension-task-item';
+import CharacterCount from '@tiptap/extension-character-count';
+import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight';
+import { Emoji, gitHubEmojis } from '@tiptap/extension-emoji';
+import DragHandle from '@tiptap/extension-drag-handle-react';
+import { all, createLowlight } from 'lowlight';
+import tippy, { type Instance as TippyInstance } from 'tippy.js';
+import type { SuggestionOptions, SuggestionProps } from '@tiptap/suggestion';
+import {
+ Bold, Italic, Underline as UnderlineIcon, Strikethrough, Code, Subscript as SubscriptIcon,
+ Superscript as SuperscriptIcon, Heading1, Heading2, Heading3, Heading4,
+ AlignLeft, AlignCenter, AlignRight, AlignJustify,
+ List, ListOrdered, ListTodo, Quote, Minus, FileCode,
+ Link as LinkIcon, Unlink, Image as ImageIcon,
+ Undo2, Redo2, GripVertical,
+ FilePlus, FolderOpen, Download, ChevronDown, FileInput, FileOutput,
+} from 'lucide-react';
+import { importWithPandoc, exportWithPandoc } from '../services/pandocService';
+
+const lowlight = createLowlight(all);
+
+// ─── Emoji suggestion renderer ───
+interface EmojiItem {
+ name: string;
+ shortcodes: string[];
+ tags: string[];
+ emoji?: string;
+ fallbackImage?: string;
+}
+
+const EmojiList = forwardRef<
+ { onKeyDown: (props: { event: KeyboardEvent }) => boolean },
+ { items: EmojiItem[]; command: (item: EmojiItem) => void }
+>((props, ref) => {
+ const [selectedIndex, setSelectedIndex] = useState(0);
+
+ useEffect(() => { setSelectedIndex(0); }, [props.items]);
+
+ useImperativeHandle(ref, () => ({
+ onKeyDown: ({ event }: { event: KeyboardEvent }) => {
+ if (event.key === 'ArrowUp') {
+ setSelectedIndex(i => (i + props.items.length - 1) % props.items.length);
+ return true;
+ }
+ if (event.key === 'ArrowDown') {
+ setSelectedIndex(i => (i + 1) % props.items.length);
+ return true;
+ }
+ if (event.key === 'Enter') {
+ const item = props.items[selectedIndex];
+ if (item) props.command(item);
+ return true;
+ }
+ return false;
+ },
+ }));
+
+ if (!props.items.length) {
+ return (
+
+ Aucun emoji
+
+ );
+ }
+
+ return (
+
+ {props.items.slice(0, 12).map((item, index) => (
+
+ ))}
+
+ );
+});
+EmojiList.displayName = 'EmojiList';
+
+const emojiSuggestion: Partial> = {
+ render: () => {
+ let component: ReactRenderer | null = null;
+ let popup: TippyInstance[] | null = null;
+ return {
+ onStart: (props: SuggestionProps) => {
+ component = new ReactRenderer(EmojiList, { props, editor: props.editor });
+ if (!props.clientRect) return;
+ popup = tippy('body', {
+ getReferenceClientRect: props.clientRect as () => DOMRect,
+ appendTo: () => document.body,
+ content: component.element,
+ showOnCreate: true,
+ interactive: true,
+ trigger: 'manual',
+ placement: 'bottom-start',
+ });
+ },
+ onUpdate: (props: SuggestionProps) => {
+ component?.updateProps(props);
+ if (!props.clientRect) return;
+ popup?.[0]?.setProps({ getReferenceClientRect: props.clientRect as () => DOMRect });
+ },
+ onKeyDown: (props: { event: KeyboardEvent }) => {
+ if (props.event.key === 'Escape') { popup?.[0]?.hide(); return true; }
+ return (component?.ref as any)?.onKeyDown?.(props) ?? false;
+ },
+ onExit: () => { popup?.[0]?.destroy(); component?.destroy(); },
+ };
+ },
+};
+
+// ─── Toolbar button ───
+function ToolBtn({ icon: Icon, label, active, disabled, onClick }: {
+ icon: any; label: string; active?: boolean; disabled?: boolean; onClick: () => void;
+}) {
+ return (
+
+ );
+}
+
+function ToolSep() {
+ return ;
+}
+
+// ─── File menu dropdown ───
+function FileMenu({ onNew, onOpen, onDownload, onImport, onExport }: {
+ onNew: () => void;
+ onOpen: () => void;
+ onDownload: () => void;
+ onImport: () => void;
+ onExport: (format: 'docx' | 'pdf') => void;
+}) {
+ const [open, setOpen] = useState(false);
+ const [exportSub, setExportSub] = useState(false);
+ const menuRef = useRef(null);
+
+ useEffect(() => {
+ if (!open) return;
+ const handleClick = (e: MouseEvent) => {
+ if (menuRef.current && !menuRef.current.contains(e.target as Node)) setOpen(false);
+ };
+ document.addEventListener('mousedown', handleClick);
+ return () => document.removeEventListener('mousedown', handleClick);
+ }, [open]);
+
+ useEffect(() => {
+ if (!open) setExportSub(false);
+ }, [open]);
+
+ return (
+
+
+ {open && (
+
+
+
+
+
+
setExportSub(true)}
+ onMouseLeave={() => setExportSub(false)}
+ >
+
+
Exporter
+
+ {exportSub && (
+
+
+
+
+ )}
+
+
+
+
+ )}
+
+ );
+}
+
+// ─── Main component ───
+import type { EditorDoc } from './EditorFileList';
+
+interface SuperEditorProps {
+ doc: EditorDoc | null;
+ onUpdateContent?: (id: string, content: string) => void;
+ onAddDoc?: () => void;
+}
+
+export function SuperEditor({ doc, onUpdateContent, onAddDoc }: SuperEditorProps) {
+ const prevDocIdRef = useRef(null);
+
+ const editor = useEditor({
+ extensions: [
+ StarterKit.configure({ heading: { levels: [1, 2, 3, 4] }, codeBlock: false }),
+ Subscript,
+ Superscript,
+ Underline,
+ TextAlign.configure({ types: ['paragraph', 'heading'] }),
+ TextStyleKit,
+ Highlight.configure({ multicolor: true }),
+ Color,
+ Link.configure({ openOnClick: false }),
+ Image.configure({ inline: true }),
+ TaskList,
+ TaskItem.configure({ nested: true }),
+ CharacterCount,
+ CodeBlockLowlight.configure({ lowlight }),
+ Emoji.configure({ emojis: gitHubEmojis, enableEmoticons: true, suggestion: emojiSuggestion }),
+ Placeholder.configure({ placeholder: 'Commencez à écrire...' }),
+ ],
+ content: doc?.content || '',
+ shouldRerenderOnTransaction: true,
+ immediatelyRender: false,
+ onUpdate: ({ editor }) => {
+ if (doc && onUpdateContent) onUpdateContent(doc.id, editor.getHTML());
+ },
+ });
+
+ // Sync editor content when switching between documents
+ useEffect(() => {
+ if (!editor) return;
+ const newId = doc?.id ?? null;
+ if (newId !== prevDocIdRef.current) {
+ prevDocIdRef.current = newId;
+ editor.commands.setContent(doc?.content || '');
+ }
+ }, [doc?.id, doc?.content, editor]);
+
+ const fileInputRef = useRef(null);
+ const importInputRef = useRef(null);
+ const [importError, setImportError] = useState(null);
+
+ const handleNew = useCallback(() => {
+ if (onAddDoc) onAddDoc();
+ }, [onAddDoc]);
+
+ const handleOpen = useCallback(() => {
+ fileInputRef.current?.click();
+ }, []);
+
+ const handleFileChange = useCallback((e: React.ChangeEvent) => {
+ const file = e.target.files?.[0];
+ if (!file || !editor) return;
+ const reader = new FileReader();
+ reader.onload = () => {
+ const text = reader.result as string;
+ let html: string;
+ if (file.name.endsWith('.html') || file.name.endsWith('.htm')) {
+ html = text;
+ } else {
+ html = text.split('\n').map(l => `${l || '
'}
`).join('');
+ }
+ editor.commands.setContent(html);
+ if (doc && onUpdateContent) onUpdateContent(doc.id, editor.getHTML());
+ };
+ reader.readAsText(file);
+ e.target.value = '';
+ }, [editor, doc, onUpdateContent]);
+
+ const handleDownload = useCallback(() => {
+ if (!editor) return;
+ const title = doc?.title || 'supereditor-document';
+ const html = `
+
+${title}
+
+
+${editor.getHTML()}
+`;
+ const blob = new Blob([html], { type: 'text/html' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `${title.replace(/[^a-zA-Z0-9-_ ]/g, '')}.html`;
+ a.click();
+ URL.revokeObjectURL(url);
+ }, [editor, doc?.title]);
+
+ const handleImport = useCallback(() => {
+ importInputRef.current?.click();
+ }, []);
+
+ const handleImportChange = useCallback(async (e: React.ChangeEvent) => {
+ const file = e.target.files?.[0];
+ if (!file || !editor) return;
+ setImportError(null);
+ try {
+ const html = await importWithPandoc(file);
+ editor.commands.setContent(html);
+ if (doc && onUpdateContent) onUpdateContent(doc.id, editor.getHTML());
+ } catch (err: any) {
+ setImportError(err?.message || 'Erreur lors de l\'import');
+ }
+ e.target.value = '';
+ }, [editor, doc, onUpdateContent]);
+
+ const handleExport = useCallback(async (format: 'docx' | 'pdf') => {
+ if (!editor) return;
+ setImportError(null);
+ try {
+ const html = editor.getHTML();
+ const blob = await exportWithPandoc(html, format);
+ const title = doc?.title || 'supereditor-document';
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `${title.replace(/[^a-zA-Z0-9-_ ]/g, '')}.${format}`;
+ a.click();
+ URL.revokeObjectURL(url);
+ } catch (err: any) {
+ setImportError(err?.message || 'Erreur lors de l\'export');
+ }
+ }, [editor, doc?.title]);
+
+ // Keyboard shortcuts for file menu
+ useEffect(() => {
+ const handleKeyDown = (e: KeyboardEvent) => {
+ if (!editor) return;
+ if (e.ctrlKey && e.key === 'n') { e.preventDefault(); handleNew(); }
+ if (e.ctrlKey && e.key === 'o') { e.preventDefault(); handleOpen(); }
+ if (e.ctrlKey && e.key === 's') { e.preventDefault(); handleDownload(); }
+ };
+ window.addEventListener('keydown', handleKeyDown);
+ return () => window.removeEventListener('keydown', handleKeyDown);
+ }, [editor, handleNew, handleOpen, handleDownload]);
+
+ if (!editor) return null;
+
+ const chars = editor.storage.characterCount?.characters?.() ?? 0;
+ const words = editor.storage.characterCount?.words?.() ?? 0;
+
+ return (
+
+
+
+ {/* Toolbar */}
+
+
+
+
+ editor.chain().focus().toggleBold().run()} />
+ editor.chain().focus().toggleItalic().run()} />
+ editor.chain().focus().toggleUnderline().run()} />
+ editor.chain().focus().toggleStrike().run()} />
+ editor.chain().focus().toggleCode().run()} />
+
+
+
+ editor.chain().focus().toggleHeading({ level: 1 }).run()} />
+ editor.chain().focus().toggleHeading({ level: 2 }).run()} />
+ editor.chain().focus().toggleHeading({ level: 3 }).run()} />
+ editor.chain().focus().toggleHeading({ level: 4 }).run()} />
+
+
+
+ editor.chain().focus().setTextAlign('left').run()} />
+ editor.chain().focus().setTextAlign('center').run()} />
+ editor.chain().focus().setTextAlign('right').run()} />
+ editor.chain().focus().setTextAlign('justify').run()} />
+
+
+
+ editor.chain().focus().toggleBulletList().run()} />
+ editor.chain().focus().toggleOrderedList().run()} />
+ editor.chain().focus().toggleTaskList().run()} />
+ editor.chain().focus().toggleBlockquote().run()} />
+ editor.chain().focus().toggleCodeBlock().run()} />
+ editor.chain().focus().setHorizontalRule().run()} />
+
+
+
+ {
+ const url = window.prompt('URL du lien');
+ if (url) editor.chain().focus().extendMarkRange('link').setLink({ href: url }).run();
+ }} />
+ editor.chain().focus().unsetLink().run()} />
+ {
+ const url = window.prompt('URL de l\'image');
+ if (url) editor.chain().focus().setImage({ src: url }).run();
+ }} />
+
+
+
+ editor.chain().focus().toggleSubscript().run()} />
+ editor.chain().focus().toggleSuperscript().run()} />
+
+
+
+ editor.chain().focus().undo().run()} />
+ editor.chain().focus().redo().run()} />
+
+
+
+ {/* Editor content with drag handle */}
+
+
+ {/* Import/Export error toast */}
+ {importError && (
+
setImportError(null)}>
+ ⚠ {importError}
+
+ )}
+
+ {/* Footer */}
+
+ {chars} caractères
+ {words} mots
+
+
+ );
+}
diff --git a/src/components/ui/animated-theme-toggler.tsx b/src/components/ui/animated-theme-toggler.tsx
index 7406d5c..b8f584d 100644
--- a/src/components/ui/animated-theme-toggler.tsx
+++ b/src/components/ui/animated-theme-toggler.tsx
@@ -1,30 +1,32 @@
import { useCallback, useEffect, useRef, useState } from "react"
-import { Moon, Sun, Sunrise } from "lucide-react"
+import { Moon, Sun, Eclipse } from "lucide-react"
import { flushSync } from "react-dom"
import { cn } from "@/lib/utils"
-type Theme = "light" | "sepia" | "dark"
+type Theme = "light" | "dark" | "amoled"
-const THEME_ORDER: Theme[] = ["light", "sepia", "dark"]
+const THEME_ORDER: Theme[] = ["light", "dark", "amoled"]
function getTheme(): Theme {
+ if (document.documentElement.classList.contains("amoled")) return "amoled"
if (document.documentElement.classList.contains("dark")) return "dark"
- if (document.documentElement.classList.contains("sepia")) return "sepia"
return "light"
}
function applyTheme(theme: Theme) {
- document.documentElement.classList.remove("dark", "sepia")
- if (theme !== "light") {
- document.documentElement.classList.add(theme)
+ document.documentElement.classList.remove("dark", "amoled")
+ if (theme === "dark") {
+ document.documentElement.classList.add("dark")
+ } else if (theme === "amoled") {
+ document.documentElement.classList.add("amoled")
}
localStorage.setItem("theme", theme)
}
const ThemeIcon = ({ theme }: { theme: Theme }) => {
+ if (theme === "amoled") return
if (theme === "dark") return
- if (theme === "sepia") return
return
}
@@ -95,6 +97,7 @@ export const AnimatedThemeToggler = ({
ref={buttonRef}
onClick={toggleTheme}
className={cn(className)}
+ title={theme === "light" ? "Clair" : theme === "dark" ? "Sombre" : "AMOLED"}
{...props}
>
diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx
index f8894aa..1597e01 100644
--- a/src/contexts/AuthContext.tsx
+++ b/src/contexts/AuthContext.tsx
@@ -106,6 +106,8 @@ export function AuthProvider({ children }: { children: ReactNode }) {
'superflux_highlights',
'superflux_notes',
'superflux_note_folders',
+ 'superflux_editor_docs',
+ 'superflux_editor_folders',
'superflux_last_sync',
];
userDataKeys.forEach(key => localStorage.removeItem(key));
diff --git a/src/hooks/useCommands.ts b/src/hooks/useCommands.ts
new file mode 100644
index 0000000..1ee5dd9
--- /dev/null
+++ b/src/hooks/useCommands.ts
@@ -0,0 +1,117 @@
+import { useEffect, useCallback, useRef, useState } from 'react';
+
+export interface Command {
+ id: string;
+ label: string;
+ category: string;
+ shortcut?: string; // Display string: "Ctrl+K"
+ keybind?: KeyBind; // Actual key matcher
+ action: () => void;
+ when?: () => boolean; // Only active when this returns true
+}
+
+interface KeyBind {
+ key: string; // e.g. "k", "1", "/", "?"
+ ctrl?: boolean;
+ alt?: boolean;
+ shift?: boolean;
+ meta?: boolean;
+}
+
+function parseShortcut(shortcut: string): KeyBind {
+ const parts = shortcut.toLowerCase().split('+');
+ return {
+ key: parts[parts.length - 1],
+ ctrl: parts.includes('ctrl'),
+ alt: parts.includes('alt'),
+ shift: parts.includes('shift'),
+ meta: parts.includes('meta'),
+ };
+}
+
+function matchesKeybind(e: KeyboardEvent, kb: KeyBind): boolean {
+ const key = e.key.toLowerCase();
+ // Handle special cases
+ const targetKey = kb.key === ',' ? ',' : kb.key === '/' ? '/' : kb.key === '?' ? '?' : kb.key;
+
+ if (key !== targetKey) return false;
+ if (!!kb.ctrl !== (e.ctrlKey || e.metaKey)) return false;
+ if (!!kb.alt !== e.altKey) return false;
+ if (!!kb.shift !== e.shiftKey) return false;
+
+ return true;
+}
+
+function isInInput(e: KeyboardEvent): boolean {
+ const el = e.target as HTMLElement;
+ if (!el) return false;
+ const tag = el.tagName;
+ return tag === 'INPUT' || tag === 'TEXTAREA' || el.isContentEditable;
+}
+
+export function useCommands() {
+ const commandsRef = useRef([]);
+ const [paletteOpen, setPaletteOpen] = useState(false);
+ const [helpOpen, setHelpOpen] = useState(false);
+
+ const registerCommands = useCallback((commands: Command[]) => {
+ // Process shortcuts into keybinds
+ const processed = commands.map(cmd => ({
+ ...cmd,
+ keybind: cmd.shortcut ? parseShortcut(cmd.shortcut) : cmd.keybind,
+ }));
+ commandsRef.current = processed;
+ }, []);
+
+ const openPalette = useCallback(() => setPaletteOpen(true), []);
+ const closePalette = useCallback(() => setPaletteOpen(false), []);
+ const toggleHelp = useCallback(() => setHelpOpen(prev => !prev), []);
+ const closeHelp = useCallback(() => setHelpOpen(false), []);
+
+ useEffect(() => {
+ const handleKeyDown = (e: KeyboardEvent) => {
+ // Command Palette: Ctrl+K
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'k') {
+ e.preventDefault();
+ setPaletteOpen(prev => !prev);
+ return;
+ }
+
+ // If palette is open, don't process other shortcuts
+ if (paletteOpen) return;
+
+ const inInput = isInInput(e);
+
+ for (const cmd of commandsRef.current) {
+ if (!cmd.keybind) continue;
+
+ // Simple keys (no modifier) are blocked in inputs
+ const hasModifier = cmd.keybind.ctrl || cmd.keybind.alt || cmd.keybind.shift || cmd.keybind.meta;
+ if (!hasModifier && inInput) continue;
+
+ if (!matchesKeybind(e, cmd.keybind)) continue;
+
+ // Check "when" condition
+ if (cmd.when && !cmd.when()) continue;
+
+ e.preventDefault();
+ cmd.action();
+ return;
+ }
+ };
+
+ window.addEventListener('keydown', handleKeyDown);
+ return () => window.removeEventListener('keydown', handleKeyDown);
+ }, [paletteOpen]);
+
+ return {
+ commands: commandsRef,
+ registerCommands,
+ paletteOpen,
+ openPalette,
+ closePalette,
+ helpOpen,
+ toggleHelp,
+ closeHelp,
+ };
+}
diff --git a/src/hooks/useFeedStore.ts b/src/hooks/useFeedStore.ts
index d79d777..ca0011a 100644
--- a/src/hooks/useFeedStore.ts
+++ b/src/hooks/useFeedStore.ts
@@ -153,6 +153,7 @@ export interface FeedStore {
renameFolder: (categoryId: string, oldPath: string, newName: string) => void;
deleteFolder: (categoryId: string, path: string) => void;
moveFeedToFolder: (feedId: string, folder: string | undefined) => void;
+ reorderFeed: (feedId: string, targetFeedId: string, position: 'before' | 'after') => void;
}
export function useFeedStore(callbacks?: FeedStoreCallbacks): FeedStore {
@@ -550,6 +551,22 @@ export function useFeedStore(callbacks?: FeedStoreCallbacks): FeedStore {
setFeeds(prev => prev.map(f => f.id === feedId ? { ...f, folder } : f));
}, []);
+ // Reorder a feed before/after another feed in the array
+ const reorderFeed = useCallback((feedId: string, targetFeedId: string, position: 'before' | 'after') => {
+ if (feedId === targetFeedId) return;
+ setFeeds(prev => {
+ const idx = prev.findIndex(f => f.id === feedId);
+ if (idx === -1) return prev;
+ const feed = prev[idx];
+ const without = [...prev.slice(0, idx), ...prev.slice(idx + 1)];
+ const targetIdx = without.findIndex(f => f.id === targetFeedId);
+ if (targetIdx === -1) return prev;
+ const insertAt = position === 'before' ? targetIdx : targetIdx + 1;
+ without.splice(insertAt, 0, feed);
+ return without;
+ });
+ }, []);
+
// ── Favorites / Read Later ordering ──
const getFavoritesOrder = useCallback((): string[] => {
@@ -602,5 +619,6 @@ export function useFeedStore(callbacks?: FeedStoreCallbacks): FeedStore {
renameFolder,
deleteFolder,
moveFeedToFolder,
+ reorderFeed,
};
}
diff --git a/src/hooks/useResizablePanels.ts b/src/hooks/useResizablePanels.ts
index 7a58b61..b28b349 100644
--- a/src/hooks/useResizablePanels.ts
+++ b/src/hooks/useResizablePanels.ts
@@ -96,5 +96,9 @@ export function useResizablePanels({ panels }: UseResizablePanelsOptions) {
};
}, [panels]);
- return { widths, handleMouseDown, containerRef };
+ const setWidthsOverride = useCallback((newWidths: [number, number, number]) => {
+ setWidths(newWidths);
+ }, []);
+
+ return { widths, setWidths: setWidthsOverride, handleMouseDown, containerRef };
}
diff --git a/src/index.css b/src/index.css
index a85cd4d..949f52b 100644
--- a/src/index.css
+++ b/src/index.css
@@ -2,7 +2,7 @@
@import "tw-animate-css";
@import "shadcn/tailwind.css";
-@custom-variant dark (&:is(.dark *));
+@custom-variant dark (&:is(.dark *, .amoled *));
/* ═══════════════════════════════════════════════
FLUX · RSS Reader — Editorial Noir Theme
@@ -27,6 +27,10 @@
--accent-dim: #b8923f;
--accent-glow: rgba(212, 168, 83, 0.12);
--accent-text: #9a7528;
+ --accent-secondary: #c07830;
+ --accent-secondary-glow: rgba(192, 120, 48, 0.10);
+ --accent-tertiary: #e8c06a;
+ --accent-tertiary-glow: rgba(232, 192, 106, 0.10);
/* Text hierarchy */
--text-primary: #1a1917;
@@ -110,6 +114,10 @@
--accent-dim: #b8923f;
--accent-glow: rgba(212, 168, 83, 0.12);
--accent-text: #e8c06a;
+ --accent-secondary: #d4884a;
+ --accent-secondary-glow: rgba(212, 136, 74, 0.10);
+ --accent-tertiary: #f0d080;
+ --accent-tertiary-glow: rgba(240, 208, 128, 0.08);
--text-primary: #e8e6e1;
--text-secondary: #8e8c87;
--text-tertiary: #5a5955;
@@ -151,61 +159,184 @@
--sidebar-ring: oklch(0.75 0.14 75);
}
-/* ── Sepia theme (Aged Parchment) ── */
-.sepia {
- --bg-root: #f0e6d0;
- --bg-surface: #f7f0de;
- --bg-elevated: #eaddc4;
- --bg-hover: #e2d4b8;
- --bg-active: #d9caab;
- --border-subtle: #e2d4b8;
- --border-default: #d0c09e;
- --border-strong: #bba97f;
+/* ── AMOLED theme (Pure Black) ── */
+.amoled {
+ --bg-root: #000000;
+ --bg-surface: #000000;
+ --bg-elevated: #0a0a0a;
+ --bg-hover: #111111;
+ --bg-active: #1a1a1a;
+ --border-subtle: #111111;
+ --border-default: #1a1a1a;
+ --border-strong: #252525;
--accent: #d4a853;
--accent-dim: #b8923f;
--accent-glow: rgba(212, 168, 83, 0.15);
- --accent-text: #8a6520;
- --text-primary: #352a1e;
- --text-secondary: #6d5d48;
- --text-tertiary: #9a8b72;
- --text-inverse: #f0e6d0;
- --red: #c44030;
- --green: #357a45;
- --blue: #4a6d96;
-
- /* shadcn tokens (sepia) */
- --background: oklch(0.93 0.025 80);
- --foreground: oklch(0.22 0.03 65);
- --card: oklch(0.96 0.02 80);
- --card-foreground: oklch(0.22 0.03 65);
- --popover: oklch(0.96 0.02 80);
- --popover-foreground: oklch(0.22 0.03 65);
+ --accent-text: #f0d080;
+ --accent-secondary: #d4884a;
+ --accent-secondary-glow: rgba(212, 136, 74, 0.12);
+ --accent-tertiary: #f0d080;
+ --accent-tertiary-glow: rgba(240, 208, 128, 0.10);
+ --text-primary: #f0eee9;
+ --text-secondary: #929089;
+ --text-tertiary: #5e5c57;
+ --text-inverse: #000000;
+ --red: #f06858;
+ --green: #56f098;
+ --blue: #58a0f0;
+
+ /* shadcn tokens (amoled) */
+ --background: oklch(0 0 0);
+ --foreground: oklch(0.95 0.005 85);
+ --card: oklch(0.04 0.002 85);
+ --card-foreground: oklch(0.95 0.005 85);
+ --popover: oklch(0.04 0.002 85);
+ --popover-foreground: oklch(0.95 0.005 85);
--primary: oklch(0.75 0.14 75);
- --primary-foreground: oklch(0.22 0.03 65);
- --secondary: oklch(0.90 0.025 78);
- --secondary-foreground: oklch(0.28 0.03 65);
- --muted: oklch(0.90 0.025 78);
- --muted-foreground: oklch(0.50 0.03 70);
- --accent-foreground: oklch(0.22 0.03 65);
- --destructive: oklch(0.577 0.245 27.325);
- --border: oklch(0.86 0.025 78);
- --input: oklch(0.86 0.025 78);
+ --primary-foreground: oklch(0 0 0);
+ --secondary: oklch(0.12 0.005 85);
+ --secondary-foreground: oklch(0.95 0.005 85);
+ --muted: oklch(0.12 0.005 85);
+ --muted-foreground: oklch(0.62 0.01 85);
+ --accent-foreground: oklch(0.95 0.005 85);
+ --destructive: oklch(0.704 0.191 22.216);
+ --border: oklch(0.14 0.003 85);
+ --input: oklch(0.14 0.003 85);
--ring: oklch(0.75 0.14 75);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
- --sidebar: oklch(0.94 0.025 80);
- --sidebar-foreground: oklch(0.22 0.03 65);
+ --sidebar: oklch(0 0 0);
+ --sidebar-foreground: oklch(0.95 0.005 85);
--sidebar-primary: oklch(0.75 0.14 75);
- --sidebar-primary-foreground: oklch(0.22 0.03 65);
- --sidebar-accent: oklch(0.90 0.025 78);
- --sidebar-accent-foreground: oklch(0.28 0.03 65);
- --sidebar-border: oklch(0.86 0.025 78);
+ --sidebar-primary-foreground: oklch(0.95 0.005 85);
+ --sidebar-accent: oklch(0.12 0.005 85);
+ --sidebar-accent-foreground: oklch(0.95 0.005 85);
+ --sidebar-border: oklch(0.14 0.003 85);
--sidebar-ring: oklch(0.75 0.14 75);
}
+/* ═══════════════════════════════════════════════
+ Color Palettes — [data-palette="..."]
+ Each palette overrides accent variables.
+ Amber is the default (no override needed).
+ ═══════════════════════════════════════════════ */
+
+/* ── Ocean ── */
+[data-palette="ocean"] {
+ --accent: #3a8fd4; --accent-dim: #2d78b5;
+ --accent-glow: rgba(58, 143, 212, 0.12); --accent-text: #1e5f99;
+ --accent-secondary: #2bb5a0; --accent-secondary-glow: rgba(43, 181, 160, 0.10);
+ --accent-tertiary: #5bc0de; --accent-tertiary-glow: rgba(91, 192, 222, 0.10);
+}
+.dark[data-palette="ocean"], [data-palette="ocean"] .dark, .amoled[data-palette="ocean"], [data-palette="ocean"] .amoled {
+ --accent: #4a9ee8; --accent-dim: #3a8fd4;
+ --accent-glow: rgba(74, 158, 232, 0.12); --accent-text: #6cb4f0;
+ --accent-secondary: #38c9b2; --accent-secondary-glow: rgba(56, 201, 178, 0.10);
+ --accent-tertiary: #6dcce6; --accent-tertiary-glow: rgba(109, 204, 230, 0.08);
+}
+
+/* ── Forest ── */
+[data-palette="forest"] {
+ --accent: #2d9e5a; --accent-dim: #24824a;
+ --accent-glow: rgba(45, 158, 90, 0.12); --accent-text: #1a6b3a;
+ --accent-secondary: #7ab648; --accent-secondary-glow: rgba(122, 182, 72, 0.10);
+ --accent-tertiary: #a3d977; --accent-tertiary-glow: rgba(163, 217, 119, 0.10);
+}
+.dark[data-palette="forest"], [data-palette="forest"] .dark, .amoled[data-palette="forest"], [data-palette="forest"] .amoled {
+ --accent: #3db86c; --accent-dim: #2d9e5a;
+ --accent-glow: rgba(61, 184, 108, 0.12); --accent-text: #5ccf88;
+ --accent-secondary: #8ec854; --accent-secondary-glow: rgba(142, 200, 84, 0.10);
+ --accent-tertiary: #b4e48c; --accent-tertiary-glow: rgba(180, 228, 140, 0.08);
+}
+
+/* ── Sunset ── */
+[data-palette="sunset"] {
+ --accent: #e8734a; --accent-dim: #d05f38;
+ --accent-glow: rgba(232, 115, 74, 0.12); --accent-text: #b84e2a;
+ --accent-secondary: #d94f8c; --accent-secondary-glow: rgba(217, 79, 140, 0.10);
+ --accent-tertiary: #f5a623; --accent-tertiary-glow: rgba(245, 166, 35, 0.10);
+}
+.dark[data-palette="sunset"], [data-palette="sunset"] .dark, .amoled[data-palette="sunset"], [data-palette="sunset"] .amoled {
+ --accent: #f08560; --accent-dim: #e8734a;
+ --accent-glow: rgba(240, 133, 96, 0.12); --accent-text: #f8a080;
+ --accent-secondary: #e868a0; --accent-secondary-glow: rgba(232, 104, 160, 0.10);
+ --accent-tertiary: #f8b840; --accent-tertiary-glow: rgba(248, 184, 64, 0.08);
+}
+
+/* ── Lavender ── */
+[data-palette="lavender"] {
+ --accent: #7c5cbf; --accent-dim: #6648a8;
+ --accent-glow: rgba(124, 92, 191, 0.12); --accent-text: #553899;
+ --accent-secondary: #9b6ddb; --accent-secondary-glow: rgba(155, 109, 219, 0.10);
+ --accent-tertiary: #b794f4; --accent-tertiary-glow: rgba(183, 148, 244, 0.10);
+}
+.dark[data-palette="lavender"], [data-palette="lavender"] .dark, .amoled[data-palette="lavender"], [data-palette="lavender"] .amoled {
+ --accent: #9070d0; --accent-dim: #7c5cbf;
+ --accent-glow: rgba(144, 112, 208, 0.12); --accent-text: #b494f4;
+ --accent-secondary: #ab80e8; --accent-secondary-glow: rgba(171, 128, 232, 0.10);
+ --accent-tertiary: #c8a8f8; --accent-tertiary-glow: rgba(200, 168, 248, 0.08);
+}
+
+/* ── Rosewood ── */
+[data-palette="rosewood"] {
+ --accent: #c44d56; --accent-dim: #a83e48;
+ --accent-glow: rgba(196, 77, 86, 0.12); --accent-text: #922e38;
+ --accent-secondary: #a0522d; --accent-secondary-glow: rgba(160, 82, 45, 0.10);
+ --accent-tertiary: #d4836a; --accent-tertiary-glow: rgba(212, 131, 106, 0.10);
+}
+.dark[data-palette="rosewood"], [data-palette="rosewood"] .dark, .amoled[data-palette="rosewood"], [data-palette="rosewood"] .amoled {
+ --accent: #d86068; --accent-dim: #c44d56;
+ --accent-glow: rgba(216, 96, 104, 0.12); --accent-text: #f08088;
+ --accent-secondary: #b86840; --accent-secondary-glow: rgba(184, 104, 64, 0.10);
+ --accent-tertiary: #e09880; --accent-tertiary-glow: rgba(224, 152, 128, 0.08);
+}
+
+/* ── Mint ── */
+[data-palette="mint"] {
+ --accent: #36b5a0; --accent-dim: #2a9a88;
+ --accent-glow: rgba(54, 181, 160, 0.12); --accent-text: #208070;
+ --accent-secondary: #4ac6b7; --accent-secondary-glow: rgba(74, 198, 183, 0.10);
+ --accent-tertiary: #7edec7; --accent-tertiary-glow: rgba(126, 222, 199, 0.10);
+}
+.dark[data-palette="mint"], [data-palette="mint"] .dark, .amoled[data-palette="mint"], [data-palette="mint"] .amoled {
+ --accent: #44c8b0; --accent-dim: #36b5a0;
+ --accent-glow: rgba(68, 200, 176, 0.12); --accent-text: #68dcc8;
+ --accent-secondary: #58d4c4; --accent-secondary-glow: rgba(88, 212, 196, 0.10);
+ --accent-tertiary: #90e8d4; --accent-tertiary-glow: rgba(144, 232, 212, 0.08);
+}
+
+/* ── Neon (optimized for AMOLED) ── */
+[data-palette="neon"] {
+ --accent: #0097a7; --accent-dim: #00838f;
+ --accent-glow: rgba(0, 151, 167, 0.12); --accent-text: #006064;
+ --accent-secondary: #c2185b; --accent-secondary-glow: rgba(194, 24, 91, 0.10);
+ --accent-tertiary: #558b2f; --accent-tertiary-glow: rgba(85, 139, 47, 0.10);
+}
+.dark[data-palette="neon"], [data-palette="neon"] .dark, .amoled[data-palette="neon"], [data-palette="neon"] .amoled {
+ --accent: #00e5ff; --accent-dim: #00b8d4;
+ --accent-glow: rgba(0, 229, 255, 0.18); --accent-text: #40f0ff;
+ --accent-secondary: #ff2d78; --accent-secondary-glow: rgba(255, 45, 120, 0.15);
+ --accent-tertiary: #76ff03; --accent-tertiary-glow: rgba(118, 255, 3, 0.12);
+}
+
+/* ── Slate ── */
+[data-palette="slate"] {
+ --accent: #6882a8; --accent-dim: #566e92;
+ --accent-glow: rgba(104, 130, 168, 0.12); --accent-text: #455b7c;
+ --accent-secondary: #5a7a9a; --accent-secondary-glow: rgba(90, 122, 154, 0.10);
+ --accent-tertiary: #8fa5c4; --accent-tertiary-glow: rgba(143, 165, 196, 0.10);
+}
+.dark[data-palette="slate"], [data-palette="slate"] .dark, .amoled[data-palette="slate"], [data-palette="slate"] .amoled {
+ --accent: #7c96b8; --accent-dim: #6882a8;
+ --accent-glow: rgba(124, 150, 184, 0.12); --accent-text: #98b0d0;
+ --accent-secondary: #6e8eae; --accent-secondary-glow: rgba(110, 142, 174, 0.10);
+ --accent-tertiary: #a0b8d4; --accent-tertiary-glow: rgba(160, 184, 212, 0.08);
+}
+
+/* ── Sepia theme (Aged Parchment) ── */
*,
*::before,
*::after {
@@ -557,6 +688,65 @@ body {
text-transform: capitalize;
}
+/* ─── MODE TAB BAR ────────────────────────────── */
+
+.mode-tab-bar {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 4px;
+ padding: 4px 8px;
+ flex-shrink: 0;
+}
+
+.mode-tab {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 30px;
+ height: 26px;
+ border: none;
+ border-radius: 6px;
+ background: transparent;
+ color: var(--text-secondary);
+ cursor: pointer;
+ transition: background .15s, color .15s;
+}
+
+.mode-tab:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+}
+
+.mode-tab--active {
+ background: var(--accent-bg, rgba(212, 168, 83, 0.12));
+ color: var(--accent, #d4a853);
+}
+
+.mode-tab--active:hover {
+ background: var(--accent-bg, rgba(212, 168, 83, 0.18));
+ color: var(--accent, #d4a853);
+}
+
+.mode-tab-icon {
+ font-size: 15px;
+ line-height: 1;
+}
+
+.mode-tab-kbd {
+ margin-left: auto;
+ font-size: 10px;
+ color: var(--text-tertiary, #999);
+ background: var(--bg-hover, rgba(128,128,128,0.08));
+ border: 1px solid var(--border-subtle, rgba(128,128,128,0.15));
+ border-radius: 4px;
+ padding: 2px 5px;
+ font-family: inherit;
+ letter-spacing: 0.3px;
+ cursor: default;
+ opacity: 0.7;
+}
+
/* ─── APP LAYOUT ──────────────────────────────── */
.app {
@@ -590,1951 +780,1981 @@ body {
}
/* ═══════════════════════════════════════════════
- SUPERNOTE — Note Panel & Cards
+ SUPERBOOKMARK — Web Bookmarks Panel
═══════════════════════════════════════════════ */
-.note-panel {
- height: 100%;
+.bookmark-panel {
display: flex;
flex-direction: column;
- background: var(--bg-root);
+ height: 100%;
+ overflow: hidden;
}
-.note-panel-header {
- padding: var(--space-lg) var(--space-lg) var(--space-md);
- border-bottom: 1px solid var(--border-subtle);
- flex-shrink: 0;
+.bookmark-panel-header {
display: flex;
align-items: center;
justify-content: space-between;
+ padding: var(--space-lg) var(--space-lg) var(--space-md);
+ border-bottom: 1px solid var(--border-subtle);
+ flex-shrink: 0;
}
-.note-panel-title-row {
+.bookmark-panel-title-row {
display: flex;
align-items: baseline;
- gap: var(--space-md);
+ gap: var(--space-sm);
}
-.note-panel-title {
+.bookmark-panel-title {
font-family: var(--font-display);
- font-size: 20px;
- font-weight: 600;
- color: var(--text-primary);
- letter-spacing: -0.01em;
+ font-size: 18px;
+ font-weight: 700;
+ margin: 0;
}
-.note-panel-count {
+.bookmark-panel-count {
+ font-family: var(--font-body);
font-size: 11px;
font-weight: 500;
- color: var(--accent-dim);
- letter-spacing: 0.02em;
+ color: var(--text-tertiary);
+ background: var(--bg-elevated);
+ padding: 1px 6px;
+ border-radius: 8px;
}
-.note-panel-actions {
+.bookmark-panel-actions {
display: flex;
align-items: center;
gap: 2px;
}
-.note-panel-list {
- flex: 1;
- overflow-y: auto;
- padding: var(--space-lg);
+.bookmark-filter-btn {
+ font-size: 11px;
+ padding: 3px 8px;
+ border-radius: 6px;
+ border: none;
+ background: transparent;
+ color: var(--text-tertiary);
+ cursor: pointer;
+ transition: all 150ms;
}
-/* 2-column staggered grid */
-.note-cards-grid {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- gap: var(--space-xl);
-}
+.bookmark-filter-btn:hover { color: var(--text-secondary); background: var(--bg-hover); }
+.bookmark-filter-btn.active { color: var(--accent-text); background: var(--accent-glow); }
-/* Note card with hover expand */
-.note-card {
- border: 1px solid var(--border-subtle);
- border-radius: 12px;
- padding: var(--space-lg);
- height: 150px;
- overflow: hidden;
+.bookmark-refresh-btn {
+ font-size: 14px;
+ padding: 3px 6px;
+ border-radius: 6px;
+ border: none;
+ background: transparent;
+ color: var(--text-tertiary);
cursor: pointer;
- position: relative;
- background: var(--bg-surface);
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
- transition: all 0.4s cubic-bezier(0.34, 1.7, 0.64, 1);
- display: flex;
- flex-direction: column;
+ transition: all 150ms;
}
-.note-card:nth-child(even) {
- margin-top: 40px;
+.bookmark-refresh-btn:hover { color: var(--text-primary); background: var(--bg-hover); }
+
+.bookmark-panel-list {
+ flex: 1;
+ overflow-y: auto;
+ padding: var(--space-sm) 0;
}
-.note-card:hover {
- height: 260px;
- transform: translateY(-5px);
- border-color: var(--border-strong);
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
- z-index: 10;
+.bookmark-panel-empty {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ color: var(--text-tertiary);
+ font-size: 13px;
+ text-align: center;
+ padding: var(--space-xl);
+ gap: var(--space-sm);
}
-.note-card.active {
- border-color: var(--accent);
- box-shadow: 0 0 0 1px var(--accent), 0 4px 16px var(--accent-glow);
+.bookmark-panel-hint {
+ font-size: 11px;
+ color: var(--text-tertiary);
+ opacity: 0.7;
}
-.note-card-number {
- font-family: var(--font-display);
- font-size: 24px;
- font-weight: 700;
- color: var(--accent);
- opacity: 0.25;
- line-height: 1;
- margin-bottom: var(--space-xs);
+/* ── Bookmark Blob Cards (same pattern as SuperFlux feed-blob-card) ── */
+
+.bookmark-cards-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+ gap: var(--space-md);
+ padding: var(--space-md);
}
-.note-card-title {
- font-family: var(--font-body);
- font-size: 14px;
- font-weight: 600;
- color: var(--text-primary);
- margin-bottom: 2px;
+.bk-blob-card {
+ position: relative;
+ border-radius: 14px;
overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
+ cursor: pointer;
+ height: 220px;
+ box-shadow:
+ 6px 6px 20px rgba(0, 0, 0, 0.06),
+ -4px -4px 16px rgba(255, 255, 255, 0.6);
}
-.note-card-date {
- font-size: 10px;
- color: var(--text-tertiary);
- margin-bottom: var(--space-sm);
+.dark .bk-blob-card {
+ box-shadow:
+ 6px 6px 24px rgba(0, 0, 0, 0.4),
+ -4px -4px 16px rgba(255, 255, 255, 0.03);
}
-.note-card-content {
- font-size: 12px;
- color: var(--text-secondary);
- line-height: 1.5;
- opacity: 0;
- max-height: 0;
- transition: opacity 0.35s ease, max-height 0.35s ease;
- overflow: hidden;
- display: -webkit-box;
- -webkit-line-clamp: 5;
- -webkit-box-orient: vertical;
+.bk-blob-card.active {
+ outline: 2px solid var(--accent);
+ outline-offset: -2px;
}
-.note-card:hover .note-card-content {
- opacity: 1;
- max-height: 120px;
-}
+.bk-blob-card.read { opacity: 0.7; }
+.bk-blob-card.read:hover { opacity: 1; }
-.note-card-actions {
+/* Animated gradient blob */
+.bk-blob-card__blob {
position: absolute;
- top: var(--space-sm);
- right: var(--space-sm);
- opacity: 0;
- transition: opacity 0.2s ease;
+ top: 50%;
+ left: 50%;
+ width: 120px;
+ height: 120px;
+ border-radius: 50%;
+ filter: blur(14px);
+ opacity: 0.85;
+ z-index: 0;
}
-.note-card:hover .note-card-actions {
- opacity: 1;
-}
+.dark .bk-blob-card__blob { opacity: 0.7; }
-.note-card-delete {
+/* Glassy content overlay */
+.bk-blob-card__glass {
+ position: absolute;
+ inset: 4px;
+ background: rgba(255, 255, 255, 0.92);
+ backdrop-filter: blur(24px);
+ -webkit-backdrop-filter: blur(24px);
+ border-radius: 10px;
+ outline: 1.5px solid rgba(255, 255, 255, 0.8);
+ z-index: 1;
display: flex;
- align-items: center;
- justify-content: center;
- width: 22px;
- height: 22px;
- border: none;
- border-radius: 6px;
- background: transparent;
- color: var(--text-tertiary);
- cursor: pointer;
- transition: all var(--duration-fast) var(--ease-out);
+ flex-direction: column;
+ padding: var(--space-md);
+ overflow: hidden;
}
-.note-card-delete:hover {
- background: var(--bg-hover);
- color: var(--red);
+.dark .bk-blob-card__glass {
+ background: rgba(15, 15, 18, 0.82);
+ outline-color: rgba(255, 255, 255, 0.06);
}
-/* Empty state */
-.note-empty {
+/* Meta row */
+.bk-blob-card__meta {
display: flex;
- flex-direction: column;
+ justify-content: space-between;
align-items: center;
- justify-content: center;
- padding: var(--space-3xl) var(--space-xl);
- text-align: center;
- gap: var(--space-sm);
- height: 100%;
+ margin-bottom: var(--space-sm);
+ flex-shrink: 0;
}
-.note-empty-icon {
- font-size: 32px;
- color: var(--accent);
- opacity: 0.3;
- margin-bottom: var(--space-sm);
+.bk-blob-card__source {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ font-family: var(--font-body);
+ font-size: 10px;
+ font-weight: 600;
+ color: var(--accent-dim);
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
-.note-empty-text {
- font-family: var(--font-display);
- font-size: 15px;
- font-weight: 500;
- color: var(--text-secondary);
+.bk-blob-card__favicon {
+ width: 12px;
+ height: 12px;
+ border-radius: 2px;
+ flex-shrink: 0;
}
-.note-empty-hint {
- font-size: 12px;
+.bk-blob-card__time {
+ font-family: var(--font-body);
+ font-size: 10px;
color: var(--text-tertiary);
- max-width: 220px;
- line-height: 1.5;
+ flex-shrink: 0;
}
-/* ═══════════════════════════════════════════════
- SUPERNOTE — Source List (panel 1)
- ═══════════════════════════════════════════════ */
-
-.nsrc {
- display: flex;
- flex-direction: column;
- gap: 2px;
- padding: var(--space-sm) 0;
+/* Body */
+.bk-blob-card__body {
+ flex: 1;
+ min-height: 0;
+ overflow: hidden;
}
-.nsrc-add-folder-btn {
+.bk-blob-card__title-row {
display: flex;
- align-items: center;
- gap: var(--space-sm);
- width: 100%;
- padding: 6px var(--space-lg);
- background: none;
- border: none;
- color: var(--text-tertiary);
- font-size: 12px;
- cursor: pointer;
- transition: color var(--duration-fast) var(--ease-out);
+ align-items: flex-start;
+ gap: 6px;
+ margin-bottom: var(--space-xs);
}
-.nsrc-add-folder-btn:hover {
- color: var(--accent);
+.bk-blob-card__unread {
+ flex-shrink: 0;
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background: var(--blue);
+ margin-top: 5px;
}
-.nsrc-add-folder-icon {
- font-size: 14px;
+.bk-blob-card__title {
+ font-family: var(--font-display);
+ font-size: 13px;
font-weight: 600;
- width: 16px;
- text-align: center;
+ line-height: 1.3;
+ color: var(--text-primary);
+ display: -webkit-box;
+ -webkit-line-clamp: 3;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ margin: 0;
}
-.nsrc-add-folder-label {
- font-weight: 500;
+.bk-blob-card:hover .bk-blob-card__title {
+ color: var(--accent-text);
}
-.nsrc-folder-input-row {
- padding: 2px var(--space-lg);
+.bk-blob-card__excerpt {
+ font-family: var(--font-body);
+ font-size: 11px;
+ line-height: 1.45;
+ color: var(--text-secondary);
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
overflow: hidden;
+ margin: 0;
}
-.nsrc-folder-input {
- width: 100%;
- padding: 4px 8px;
- font-size: 12px;
- font-family: var(--font-body);
- background: var(--bg-elevated);
- border: 1px solid var(--border-default);
- border-radius: 6px;
- color: var(--text-primary);
- outline: none;
+/* Footer */
+.bk-blob-card__footer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-top: auto;
+ padding-top: var(--space-xs);
+ flex-shrink: 0;
}
-.nsrc-folder-input:focus {
- border-color: var(--accent);
+.bk-blob-card__tags {
+ display: flex;
+ gap: 4px;
+ overflow: hidden;
}
-.nsrc-folder-input.inline {
- padding: 2px 6px;
- font-size: 12px;
- flex: 1;
+.bk-blob-card__tag {
+ font-size: 9px;
+ font-weight: 500;
+ color: var(--text-tertiary);
+ background: var(--bg-hover);
+ padding: 1px 6px;
+ border-radius: 3px;
+ white-space: nowrap;
}
-.nsrc-root-zone {
- display: flex;
- flex-direction: column;
+.dark .bk-blob-card__tag {
+ background: rgba(255, 255, 255, 0.06);
}
-.nsrc-note {
+.bk-blob-card__tag--chrome { background: var(--accent-glow); color: var(--accent-text); }
+.bk-blob-card__tag--desktop { background: rgba(74, 142, 232, 0.12); color: var(--blue); }
+.bk-blob-card__tag--mobile { background: rgba(249, 115, 22, 0.12); color: #f97316; }
+
+.bk-blob-card__actions {
display: flex;
align-items: center;
- gap: var(--space-sm);
- padding: 5px var(--space-lg) 5px calc(var(--space-lg) + 8px);
- margin: 2px var(--space-sm);
- cursor: pointer;
- font-size: 12px;
- color: var(--text-secondary);
- transition: all var(--duration-fast) var(--ease-out);
- border-radius: 6px;
- user-select: none;
- background: var(--bg-surface);
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
+ gap: 4px;
+ opacity: 0;
+ transition: opacity 150ms;
}
-.nsrc-note:hover {
- background: var(--bg-hover);
- color: var(--text-primary);
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.06);
-}
+.bk-blob-card:hover .bk-blob-card__actions { opacity: 1; }
-.nsrc-note.active {
- background: var(--accent-glow);
- color: var(--accent-text);
+.bk-blob-card__action-btn {
+ font-size: 11px;
+ width: 22px;
+ height: 22px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 5px;
+ border: none;
+ background: rgba(0, 0, 0, 0.05);
+ color: var(--text-tertiary);
+ cursor: pointer;
+ transition: all 150ms;
}
+.dark .bk-blob-card__action-btn { background: rgba(255, 255, 255, 0.06); }
+.bk-blob-card__action-btn:hover { background: var(--bg-active); color: var(--text-primary); }
+.bk-blob-card__action-btn--delete:hover { color: var(--red); }
-.nsrc-note-icon {
- font-size: 11px;
- opacity: 0.5;
- flex-shrink: 0;
-}
+/* ── View toggle buttons ── */
-.nsrc-note-title {
- flex: 1;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- font-weight: 500;
+.bookmark-view-btn {
+ font-size: 13px;
+ padding: 3px 6px;
+ border-radius: 6px;
+ border: none;
+ background: transparent;
+ color: var(--text-tertiary);
+ cursor: pointer;
+ transition: all 150ms;
}
-.nsrc-folder {
+.bookmark-view-btn:hover { color: var(--text-secondary); background: var(--bg-hover); }
+.bookmark-view-btn.active { color: var(--accent-text); }
+
+/* ── Compact List View ── */
+
+.bk-compact-list {
display: flex;
flex-direction: column;
}
-.nsrc-folder-header {
+.bk-compact-item {
display: flex;
align-items: center;
+ justify-content: space-between;
gap: var(--space-sm);
- padding: 6px var(--space-lg);
+ padding: var(--space-sm) var(--space-lg);
+ border-bottom: 1px solid var(--border-subtle);
cursor: pointer;
- font-size: 12px;
- color: var(--text-secondary);
- transition: all var(--duration-fast) var(--ease-out);
- border-radius: 4px;
- border: 1px solid transparent;
+ transition: background 150ms;
}
-.nsrc-folder-header:hover {
- background: var(--bg-hover);
- color: var(--text-primary);
-}
+.bk-compact-item:hover { background: var(--bg-hover); }
+.bk-compact-item.active { background: var(--accent-glow); }
+.bk-compact-item.read { opacity: 0.6; }
+.bk-compact-item.read:hover { opacity: 0.85; }
-.nsrc-folder-header.active {
- background: var(--accent-glow);
- color: var(--accent-text);
+.bk-compact-item__left {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ min-width: 0;
+ flex: 1;
}
+.bk-compact-item__dot {
+ flex-shrink: 0;
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background: var(--blue);
+}
-
-.nsrc-folder-icon {
- font-size: 13px;
+.bk-compact-item__favicon {
+ width: 14px;
+ height: 14px;
+ border-radius: 3px;
flex-shrink: 0;
}
-.nsrc-folder-name {
+.bk-compact-item__text {
+ min-width: 0;
flex: 1;
+}
+
+.bk-compact-item__title {
+ font-family: var(--font-display);
+ font-size: 13px;
font-weight: 600;
+ line-height: 1.3;
+ color: var(--text-primary);
+ margin: 0;
+ white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
- white-space: nowrap;
}
-.nsrc-folder-count {
+.bk-compact-item:hover .bk-compact-item__title { color: var(--accent-text); }
+.bk-compact-item.active .bk-compact-item__title { color: var(--accent-text); }
+
+.bk-compact-item__meta {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ margin-top: 1px;
font-size: 10px;
color: var(--text-tertiary);
- background: var(--bg-elevated);
- padding: 1px 6px;
- border-radius: 8px;
- min-width: 18px;
- text-align: center;
-}
-
-.nsrc-folder-children {
+ white-space: nowrap;
overflow: hidden;
}
-.nsrc-folder-children .nsrc-note {
- padding-left: calc(var(--space-lg) + 20px);
-}
+.bk-compact-item__site { font-weight: 500; }
+.bk-compact-item__sep { opacity: 0.5; }
-.nsrc-folder-empty {
- padding: 4px calc(var(--space-lg) + 20px);
- font-size: 11px;
- color: var(--text-tertiary);
- font-style: italic;
+.bk-compact-item__source {
+ padding: 0px 4px;
+ border-radius: 3px;
+ font-size: 8px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.3px;
+ margin-left: 2px;
}
-/* ── Sticky Board (panel 2 – board view) ── */
+.bk-compact-item__source--chrome { background: var(--accent-glow); color: var(--accent-text); }
+.bk-compact-item__source--desktop { background: rgba(74, 142, 232, 0.12); color: var(--blue); }
+.bk-compact-item__source--mobile { background: rgba(249, 115, 22, 0.12); color: #f97316; }
-.sticky-board-wrapper {
- flex: 1;
- display: flex;
- flex-direction: column;
- overflow: hidden;
-}
-
-.sticky-board-toolbar {
+.bk-compact-item__actions {
display: flex;
align-items: center;
- justify-content: space-between;
- padding: 6px var(--space-md);
- border-bottom: 1px solid var(--border-subtle);
+ gap: 2px;
+ opacity: 0;
+ transition: opacity 150ms;
flex-shrink: 0;
}
-.sticky-color-picker {
- display: flex;
- align-items: center;
- gap: 6px;
-}
+.bk-compact-item:hover .bk-compact-item__actions { opacity: 1; }
+.bk-compact-item.active .bk-compact-item__actions { opacity: 1; }
-.sticky-color-swatch {
+.bk-compact-item__btn {
+ font-size: 11px;
width: 22px;
height: 22px;
- border-radius: 50%;
- border: 2px solid transparent;
- cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
- transition: transform 0.15s, border-color 0.15s;
- font-size: 10px;
- line-height: 1;
+ border-radius: 5px;
+ border: none;
+ background: transparent;
+ color: var(--text-tertiary);
+ cursor: pointer;
+ transition: all 150ms;
}
-.sticky-color-swatch:hover {
- transform: scale(1.15);
+.bk-compact-item__btn:hover { background: var(--bg-active); color: var(--text-primary); }
+.bk-compact-item__btn--delete:hover { color: var(--red); }
+
+/* ═══════════════════════════════════════════════
+ BOOKMARK READER — Internal article viewer
+ ═══════════════════════════════════════════════ */
+
+.bk-reader {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ overflow: hidden;
+ background: var(--bg-root);
}
-.sticky-color-swatch.active {
- transform: scale(1.15);
- box-shadow: 0 1px 4px rgba(0,0,0,0.15);
+.bk-reader-empty {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ gap: var(--space-md);
+ color: var(--text-tertiary);
+ font-size: 13px;
}
-.sticky-color-check {
- font-size: 11px;
- font-weight: 700;
- color: rgba(0,0,0,0.4);
+.bk-reader-empty-icon { font-size: 32px; opacity: 0.5; }
+
+/* Toolbar */
+.bk-reader-toolbar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: var(--space-sm) var(--space-lg);
+ border-bottom: 1px solid var(--border-subtle);
+ flex-shrink: 0;
+ gap: var(--space-md);
}
-.sticky-add-btn {
- width: 28px;
- height: 28px;
- border-radius: 8px;
- border: none;
- background: var(--accent);
- color: #fff;
- font-size: 16px;
- font-weight: 600;
- cursor: pointer;
+.bk-reader-toolbar-left {
display: flex;
align-items: center;
- justify-content: center;
- transition: background 0.15s;
+ gap: var(--space-sm);
+ min-width: 0;
}
-.sticky-add-btn:hover {
- filter: brightness(1.1);
+.bk-reader-favicon {
+ width: 14px;
+ height: 14px;
+ border-radius: 2px;
+ flex-shrink: 0;
}
-.sticky-board {
- flex: 1;
- position: relative;
+.bk-reader-site {
+ font-size: 12px;
+ font-weight: 500;
+ color: var(--text-secondary);
+ white-space: nowrap;
overflow: hidden;
- background-image: radial-gradient(circle at 1px 1px, var(--border-subtle) 1px, transparent 0);
- background-size: 28px 28px;
+ text-overflow: ellipsis;
}
-.sticky-board-empty {
- position: absolute;
- inset: 0;
+.bk-reader-toolbar-right {
display: flex;
- flex-direction: column;
align-items: center;
- justify-content: center;
- gap: 4px;
- color: var(--text-tertiary);
+ gap: var(--space-sm);
+ flex-shrink: 0;
}
-.sticky-board-empty-icon {
- font-size: 32px;
- opacity: 0.4;
+.bk-reader-viewtoggle {
+ display: flex;
+ align-items: center;
+ background: var(--bg-elevated);
+ border-radius: 6px;
+ padding: 1px;
}
-.sticky-board-empty p {
- margin: 0;
+.bk-reader-viewbtn {
font-size: 13px;
+ padding: 3px 8px;
+ border-radius: 5px;
+ border: none;
+ background: transparent;
+ color: var(--text-tertiary);
+ cursor: pointer;
+ transition: all 150ms;
}
-.sticky-board-empty-hint {
- font-size: 11px !important;
- opacity: 0.6;
+.bk-reader-viewbtn:hover { color: var(--text-secondary); }
+.bk-reader-viewbtn.active {
+ background: var(--bg-surface);
+ color: var(--text-primary);
+ box-shadow: 0 1px 2px rgba(0,0,0,.08);
}
-/* ── Single sticky note ── */
-
-.sticky-note {
- position: absolute;
- width: 200px;
- min-height: 180px;
+.bk-reader-fontsize {
display: flex;
- flex-direction: column;
- border-radius: 2px;
- user-select: none;
- touch-action: none;
- cursor: grab;
+ align-items: center;
+ gap: 1px;
}
-.sticky-note--dragging {
- cursor: grabbing;
- z-index: 9999 !important;
+.bk-reader-fontbtn {
+ font-size: 11px;
+ padding: 3px 6px;
+ border-radius: 5px;
+ border: none;
+ background: transparent;
+ color: var(--text-tertiary);
+ cursor: pointer;
+ transition: all 150ms;
}
-.sticky-note--selected {
- outline: 2px solid var(--accent);
- outline-offset: 2px;
-}
+.bk-reader-fontbtn:hover { color: var(--text-primary); background: var(--bg-hover); }
-.sticky-note:not(.sticky-note--dragging):hover {
- box-shadow: 0 8px 24px rgba(0,0,0,0.18) !important;
+.bk-reader-extbtn {
+ font-size: 14px;
+ padding: 3px 6px;
+ border-radius: 5px;
+ border: none;
+ background: transparent;
+ color: var(--text-tertiary);
+ cursor: pointer;
+ transition: all 150ms;
}
-.sticky-note-tape {
- position: absolute;
- top: -10px;
- left: 50%;
- width: 56px;
- height: 20px;
- border-radius: 2px;
- opacity: 0.75;
- pointer-events: none;
+.bk-reader-extbtn:hover { color: var(--accent-text); background: var(--accent-glow); }
+
+/* Content area */
+.bk-reader-content {
+ flex: 1;
+ overflow-y: auto;
+ padding: var(--space-xl) 0;
}
-.sticky-note-header {
+/* Loading */
+.bk-reader-loading {
display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 16px 10px 2px;
+ flex-direction: column;
+ height: 100%;
}
-.sticky-note-grip {
- font-size: 12px;
- color: rgba(0,0,0,0.2);
- cursor: grab;
- letter-spacing: 1px;
+.bk-reader-loading-bar {
+ height: 2px;
+ background: linear-gradient(90deg, transparent, var(--accent), transparent);
+ animation: loadingSlide 1.5s ease-in-out infinite;
}
-.sticky-note-actions {
+.bk-reader-loading-body {
+ flex: 1;
display: flex;
+ flex-direction: column;
align-items: center;
- gap: 2px;
+ justify-content: center;
+ gap: var(--space-md);
+ font-size: 12px;
+ color: var(--text-tertiary);
}
-.sticky-note-btn {
- width: 22px;
- height: 22px;
- border: none;
- background: none;
+.bk-reader-spinner {
+ width: 24px;
+ height: 24px;
+ border: 2px solid var(--border-subtle);
+ border-top-color: var(--accent);
border-radius: 50%;
- cursor: pointer;
+ animation: spin 0.8s linear infinite;
+}
+
+@keyframes spin { to { transform: rotate(360deg); } }
+
+/* Error */
+.bk-reader-error {
display: flex;
+ flex-direction: column;
align-items: center;
justify-content: center;
- font-size: 12px;
- color: rgba(0,0,0,0.35);
- transition: background 0.15s, color 0.15s;
+ height: 100%;
+ gap: var(--space-md);
+ padding: var(--space-xl);
+ text-align: center;
+ color: var(--text-secondary);
}
-.sticky-note-btn:hover {
- background: rgba(0,0,0,0.08);
- color: rgba(0,0,0,0.7);
+.bk-reader-error-icon { font-size: 40px; color: var(--text-tertiary); opacity: 0.6; }
+.bk-reader-error h3 {
+ font-family: var(--font-display);
+ font-size: 18px;
+ color: var(--text-primary);
+ margin: 0;
}
+.bk-reader-error p { font-size: 13px; margin: 0; }
-.sticky-note-btn--save {
- color: #15803d;
- font-weight: 700;
+.bk-reader-error-actions {
+ display: flex;
+ gap: var(--space-sm);
+ margin-top: var(--space-sm);
}
-.sticky-note-btn--save:hover {
- background: rgba(21,128,61,0.1);
+.bk-reader-error-btn {
+ padding: var(--space-sm) var(--space-lg);
+ border: 1px solid var(--border-default);
+ border-radius: 8px;
+ background: transparent;
+ color: var(--text-secondary);
+ font-size: 12px;
+ cursor: pointer;
+ transition: all 150ms;
}
-.sticky-note-btn--delete:hover {
- background: rgba(220,38,38,0.1);
- color: #dc2626;
+.bk-reader-error-btn:hover { background: var(--bg-hover); color: var(--text-primary); }
+.bk-reader-error-btn.primary {
+ background: var(--accent-glow);
+ border-color: var(--accent-dim);
+ color: var(--accent-text);
}
+.bk-reader-error-btn.primary:hover { background: var(--accent); color: var(--text-inverse); }
-.sticky-note-title {
- padding: 0 12px;
- font-size: 11px;
- font-weight: 600;
- color: rgba(0,0,0,0.45);
- text-transform: uppercase;
- letter-spacing: 0.5px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
+/* Article */
+.bk-reader-article {
+ max-width: 720px;
+ margin: 0 auto;
+ padding: 0 var(--space-xl);
+ line-height: 1.7;
+ color: var(--text-primary);
}
-.sticky-note-content {
- flex: 1;
- padding: 6px 12px 12px;
+.bk-reader-hero {
+ margin-bottom: var(--space-xl);
+ border-radius: 10px;
+ overflow: hidden;
}
-.sticky-note-text {
- font-family: 'Caveat', cursive;
- font-size: 18px;
- line-height: 1.4;
- color: rgba(0,0,0,0.8);
- white-space: pre-wrap;
- word-break: break-word;
- cursor: grab;
- margin: 0;
-}
-
-.sticky-note-textarea {
+.bk-reader-hero img {
width: 100%;
- height: 110px;
- background: transparent;
- border: none;
- outline: none;
- resize: none;
- font-family: 'Caveat', cursive;
- font-size: 18px;
- line-height: 1.4;
- color: rgba(0,0,0,0.8);
-}
-
-.sticky-note-textarea::placeholder {
- color: rgba(0,0,0,0.25);
+ height: auto;
+ display: block;
+ object-fit: cover;
+ max-height: 360px;
}
-.sticky-note-corner {
- position: absolute;
- bottom: 0;
- right: 0;
- width: 20px;
- height: 20px;
- background: linear-gradient(135deg, transparent 50%, rgba(0,0,0,0.06) 50%);
- pointer-events: none;
+.bk-reader-title {
+ font-family: var(--font-display);
+ font-size: 1.8em;
+ font-weight: 700;
+ line-height: 1.25;
+ color: var(--text-primary);
+ margin: 0 0 var(--space-md);
}
-/* ── Note Editor (3rd panel) ── */
-
-.note-editor {
- height: 100%;
+.bk-reader-meta {
display: flex;
- flex-direction: column;
- background: var(--bg-surface);
- border-left: 1px solid var(--border-subtle);
-}
-
-.note-editor-header {
- padding: var(--space-lg);
+ flex-wrap: wrap;
+ gap: var(--space-sm);
+ font-size: 0.8em;
+ color: var(--text-tertiary);
+ margin-bottom: var(--space-xl);
+ padding-bottom: var(--space-lg);
border-bottom: 1px solid var(--border-subtle);
- flex-shrink: 0;
- display: flex;
- flex-direction: column;
- gap: var(--space-xs);
}
-.note-editor-title-row {
- display: flex;
- align-items: center;
- gap: var(--space-sm);
-}
+.bk-reader-byline { font-weight: 500; color: var(--text-secondary); }
+.bk-reader-sitename { color: var(--accent-text); }
-.note-editor-title {
- flex: 1;
- font-family: var(--font-display);
- font-size: 20px;
- font-weight: 600;
- color: var(--text-primary);
- background: transparent;
- border: none;
- outline: none;
- padding: 0;
- letter-spacing: -0.01em;
+.bk-reader-body {
+ font-size: 1em;
+ line-height: 1.75;
}
-.note-editor-title::placeholder {
- color: var(--text-tertiary);
+.bk-reader-body img {
+ max-width: 100%;
+ height: auto;
+ border-radius: 8px;
+ margin: var(--space-lg) 0;
}
-.note-editor-date {
- font-size: 11px;
- color: var(--text-tertiary);
+.bk-reader-body a {
+ color: var(--accent-text);
+ text-decoration: underline;
+ text-decoration-color: var(--accent-dim);
+ text-underline-offset: 2px;
}
-.note-editor-body {
- flex: 1;
- overflow-y: auto;
- padding: var(--space-lg);
-}
+.bk-reader-body a:hover { color: var(--accent); }
-.note-editor-content {
- width: 100%;
- height: 100%;
- min-height: 300px;
- font-family: var(--font-body);
- font-size: 14px;
- line-height: 1.7;
- color: var(--text-primary);
- background: transparent;
- border: none;
- outline: none;
- resize: none;
- padding: 0;
-}
+.bk-reader-body p { margin: 0 0 1em; }
-.note-editor-content::placeholder {
- color: var(--text-tertiary);
+.bk-reader-body h1,
+.bk-reader-body h2,
+.bk-reader-body h3,
+.bk-reader-body h4 {
+ font-family: var(--font-display);
+ color: var(--text-primary);
+ margin: 1.5em 0 0.5em;
+ line-height: 1.3;
}
-.note-editor-empty {
- height: 100%;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- background: var(--bg-surface);
- border-left: 1px solid var(--border-subtle);
- gap: var(--space-sm);
+.bk-reader-body blockquote {
+ border-left: 3px solid var(--accent-dim);
+ margin: var(--space-lg) 0;
+ padding: var(--space-sm) var(--space-lg);
+ color: var(--text-secondary);
+ font-style: italic;
+ background: var(--bg-elevated);
+ border-radius: 0 8px 8px 0;
}
-/* Dark / Sepia shadow overrides */
-.dark .note-card {
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
+.bk-reader-body pre {
+ background: var(--bg-elevated);
+ border: 1px solid var(--border-subtle);
+ border-radius: 8px;
+ padding: var(--space-lg);
+ overflow-x: auto;
+ font-size: 0.85em;
}
-.dark .note-card:hover {
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
+.bk-reader-body code {
+ font-family: var(--font-mono, monospace);
+ font-size: 0.9em;
+ background: var(--bg-elevated);
+ padding: 1px 4px;
+ border-radius: 3px;
}
-.sepia .note-card {
- box-shadow: 0 2px 8px rgba(80, 60, 30, 0.06);
+.bk-reader-body pre code {
+ background: none;
+ padding: 0;
}
-.sepia .note-card:hover {
- box-shadow: 0 8px 24px rgba(80, 60, 30, 0.12);
+.bk-reader-body figure {
+ margin: var(--space-lg) 0;
}
-.panel {
- height: 100%;
- overflow: hidden;
- display: flex;
- flex-direction: column;
- flex-shrink: 0;
+.bk-reader-body figcaption {
+ font-size: 0.85em;
+ color: var(--text-tertiary);
+ text-align: center;
+ margin-top: var(--space-sm);
}
-/* ─── RESIZE HANDLE ───────────────────────────── */
-
-.resize-handle {
- width: 5px;
- height: 100%;
- cursor: col-resize;
+/* Webview mode */
+.bk-reader-webview {
+ flex: 1;
position: relative;
- flex-shrink: 0;
- z-index: 10;
+ overflow: hidden;
}
-.resize-handle-line {
- position: absolute;
- top: 0;
- left: 2px;
- width: 1px;
+.bk-reader-iframe {
+ width: 100%;
height: 100%;
- background: var(--border-subtle);
- transition:
- background var(--duration-fast) var(--ease-out),
- width var(--duration-fast) var(--ease-out),
- left var(--duration-fast) var(--ease-out);
-}
-
-.resize-handle:hover .resize-handle-line,
-.resize-handle.hovered .resize-handle-line {
- width: 3px;
- left: 1px;
- background: var(--accent);
- box-shadow: 0 0 8px var(--accent-glow);
+ border: none;
+ background: #fff;
}
-/* ─── SOURCE PANEL (Left) ─────────────────────── */
+/* ═══════════════════════════════════════════════
+ SUPERNOTE — Note Panel & Cards
+ ═══════════════════════════════════════════════ */
-.source-panel {
+.note-panel {
height: 100%;
display: flex;
flex-direction: column;
- background: var(--bg-surface);
- border-right: 1px solid var(--border-subtle);
+ background: var(--bg-root);
}
-.source-panel-header {
- padding: var(--space-xl) var(--space-lg) var(--space-lg);
+.note-panel-header {
+ padding: var(--space-lg) var(--space-lg) var(--space-md);
border-bottom: 1px solid var(--border-subtle);
flex-shrink: 0;
-}
-
-.source-panel-brand {
display: flex;
align-items: center;
- gap: var(--space-sm);
+ justify-content: space-between;
}
-.brand-icon {
- font-size: 22px;
- color: var(--accent);
- line-height: 1;
+.note-panel-title-row {
+ display: flex;
+ align-items: baseline;
+ gap: var(--space-md);
}
-.brand-name {
+.note-panel-title {
font-family: var(--font-display);
font-size: 20px;
font-weight: 600;
- letter-spacing: 0.02em;
color: var(--text-primary);
- flex: 1;
+ letter-spacing: -0.01em;
}
-.theme-toggle-btn {
+.note-panel-count {
+ font-size: 11px;
+ font-weight: 500;
+ color: var(--accent-dim);
+ letter-spacing: 0.02em;
+}
+
+.note-panel-actions {
display: flex;
align-items: center;
- justify-content: center;
- width: 30px;
- height: 30px;
- border: none;
- border-radius: var(--radius-md);
- background: transparent;
- color: var(--text-tertiary);
- cursor: pointer;
- transition: all var(--duration-fast) var(--ease-out);
+ gap: 2px;
}
-.theme-toggle-btn:hover {
- background: var(--bg-hover);
- color: var(--accent);
+.note-panel-list {
+ flex: 1;
+ overflow-y: auto;
+ padding: var(--space-lg);
}
-.theme-toggle-btn svg {
- width: 16px;
- height: 16px;
+/* 2-column staggered grid */
+.note-cards-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: var(--space-xl);
}
-.source-panel-content {
- flex: 1;
- overflow-y: auto;
- padding: var(--space-md) var(--space-sm);
+/* Note card with hover expand */
+.note-card {
+ border: 1px solid var(--border-subtle);
+ border-radius: 12px;
+ padding: var(--space-lg);
+ height: 150px;
+ overflow: hidden;
+ cursor: pointer;
+ position: relative;
+ background: var(--bg-surface);
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
+ transition: all 0.4s cubic-bezier(0.34, 1.7, 0.64, 1);
+ display: flex;
+ flex-direction: column;
}
-/* All feeds button */
-.source-all-btn {
- display: flex;
- align-items: center;
- gap: var(--space-sm);
- width: 100%;
- padding: var(--space-sm) var(--space-md);
- border: none;
- border-radius: var(--radius-md);
- background: transparent;
- color: var(--text-secondary);
- font-family: var(--font-body);
- font-size: 13px;
- font-weight: 500;
- cursor: pointer;
- transition: all var(--duration-fast) var(--ease-out);
- margin-bottom: var(--space-md);
+.note-card:nth-child(even) {
+ margin-top: 40px;
}
-.source-all-btn:hover {
- background: var(--bg-hover);
- color: var(--text-primary);
+.note-card:hover {
+ height: 260px;
+ transform: translateY(-5px);
+ border-color: var(--border-strong);
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
+ z-index: 10;
}
-.source-all-btn.active {
- background: var(--accent-glow);
- color: var(--accent-text);
+.note-card.active {
+ border-color: var(--accent);
+ box-shadow: 0 0 0 1px var(--accent), 0 4px 16px var(--accent-glow);
}
-.source-favorites-btn {
+.note-card-number {
+ font-family: var(--font-display);
+ font-size: 24px;
+ font-weight: 700;
+ color: var(--accent);
+ opacity: 0.25;
+ line-height: 1;
margin-bottom: var(--space-xs);
}
-.source-favorites-btn .source-all-icon {
- color: #f59e0b;
-}
-
-.source-favorites-btn.active {
- background: rgba(245, 158, 11, 0.1);
+.note-card-title {
+ font-family: var(--font-body);
+ font-size: 14px;
+ font-weight: 600;
color: var(--text-primary);
+ margin-bottom: 2px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
-.source-favorites-btn.active .source-all-count {
- color: #f59e0b;
- background: rgba(245, 158, 11, 0.15);
+.note-card-date {
+ font-size: 10px;
+ color: var(--text-tertiary);
+ margin-bottom: var(--space-sm);
}
-.source-readlater-btn {
- margin-bottom: var(--space-md);
+.note-card-content {
+ font-size: 12px;
+ color: var(--text-secondary);
+ line-height: 1.5;
+ opacity: 0;
+ max-height: 0;
+ transition: opacity 0.35s ease, max-height 0.35s ease;
+ overflow: hidden;
+ display: -webkit-box;
+ -webkit-line-clamp: 5;
+ -webkit-box-orient: vertical;
}
-.source-readlater-btn .source-all-icon {
- font-size: 14px;
+.note-card:hover .note-card-content {
+ opacity: 1;
+ max-height: 120px;
}
-.source-readlater-btn.active {
- background: rgba(59, 130, 246, 0.1);
- color: var(--text-primary);
+.note-card-empty {
+ color: var(--text-tertiary);
+ font-style: italic;
}
-.source-readlater-btn.active .source-all-count {
- color: #3b82f6;
- background: rgba(59, 130, 246, 0.15);
+/* Markdown inside note cards */
+.note-card-md p { margin: 0 0 4px; }
+.note-card-md p:last-child { margin-bottom: 0; }
+.note-card-md h1, .note-card-md h2, .note-card-md h3 {
+ font-size: 1em;
+ font-weight: 600;
+ margin: 0 0 2px;
}
-
-.source-all-icon {
- font-size: 15px;
- opacity: 0.7;
+.note-card-md ul, .note-card-md ol {
+ margin: 2px 0;
+ padding-left: 16px;
}
-
-.source-all-label {
- flex: 1;
- text-align: left;
+.note-card-md li { margin-bottom: 1px; }
+.note-card-md code {
+ background: var(--bg-hover);
+ border-radius: 3px;
+ padding: 1px 3px;
+ font-family: monospace;
+ font-size: 0.85em;
}
-
-.source-all-count {
- font-size: 11px;
- font-weight: 600;
- color: var(--accent);
- background: var(--accent-glow);
- padding: 1px 7px;
- border-radius: 10px;
- min-width: 20px;
- text-align: center;
+.note-card-md pre {
+ background: var(--bg-hover);
+ border-radius: 3px;
+ padding: 4px 6px;
+ overflow: hidden;
+ font-size: 0.82em;
+ margin: 2px 0;
}
+.note-card-md strong { font-weight: 600; }
-/* Category sections */
-.source-categories {
- display: flex;
- flex-direction: column;
- gap: 2px;
+.note-card-actions {
+ position: absolute;
+ top: var(--space-sm);
+ right: var(--space-sm);
+ opacity: 0;
+ transition: opacity 0.2s ease;
}
-.source-category {
- margin-bottom: var(--space-xs);
+.note-card:hover .note-card-actions {
+ opacity: 1;
}
-.category-header {
+.note-card-delete {
display: flex;
align-items: center;
- gap: var(--space-sm);
- width: 100%;
- padding: var(--space-sm) var(--space-md);
+ justify-content: center;
+ width: 22px;
+ height: 22px;
border: none;
- border-radius: var(--radius-md);
+ border-radius: 6px;
background: transparent;
- color: var(--text-secondary);
- font-family: var(--font-body);
- font-size: 12px;
- font-weight: 600;
- text-transform: uppercase;
- letter-spacing: 0.06em;
+ color: var(--text-tertiary);
cursor: pointer;
transition: all var(--duration-fast) var(--ease-out);
}
-.category-header:hover {
+.note-card-delete:hover {
background: var(--bg-hover);
- color: var(--text-primary);
-}
-
-.category-header.active {
- color: var(--accent-text);
- background: var(--accent-glow);
+ color: var(--red);
}
-.category-icon {
- font-size: 11px;
- width: 16px;
+/* Empty state */
+.note-empty {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: var(--space-3xl) var(--space-xl);
text-align: center;
- opacity: 0.6;
-}
-
-.category-label {
- flex: 1;
- text-align: left;
+ gap: var(--space-sm);
+ height: 100%;
}
-.category-count {
- font-size: 10px;
- font-weight: 600;
- color: var(--text-tertiary);
- min-width: 16px;
- text-align: center;
+.note-empty-icon {
+ font-size: 32px;
+ color: var(--accent);
+ opacity: 0.3;
+ margin-bottom: var(--space-sm);
}
-.category-header.active .category-count {
- color: var(--accent-dim);
+.note-empty-text {
+ font-family: var(--font-display);
+ font-size: 15px;
+ font-weight: 500;
+ color: var(--text-secondary);
}
-.category-chevron {
- font-size: 14px;
- width: 16px;
- text-align: center;
- transition: transform var(--duration-fast) var(--ease-out);
+.note-empty-hint {
+ font-size: 12px;
color: var(--text-tertiary);
- line-height: 1;
+ max-width: 220px;
+ line-height: 1.5;
}
-.category-chevron.expanded {
- transform: rotate(90deg);
-}
+/* ═══════════════════════════════════════════════
+ SUPERNOTE — Source List (panel 1)
+ ═══════════════════════════════════════════════ */
-/* Feed items in category */
-.category-feeds {
- overflow: hidden;
+.nsrc {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ padding: var(--space-sm) 0;
}
-.feed-item-btn {
+.nsrc-add-folder-btn {
display: flex;
align-items: center;
gap: var(--space-sm);
width: 100%;
- padding: 6px var(--space-md) 6px 28px;
+ padding: 6px var(--space-lg);
+ background: none;
border: none;
- border-radius: var(--radius-sm);
- background: transparent;
- color: var(--text-secondary);
- font-family: var(--font-body);
- font-size: 12.5px;
- font-weight: 400;
+ color: var(--text-tertiary);
+ font-size: 12px;
cursor: pointer;
- transition: all var(--duration-fast) var(--ease-out);
-}
-
-.feed-item-btn:hover {
- background: var(--bg-hover);
- color: var(--text-primary);
+ transition: color var(--duration-fast) var(--ease-out);
}
-.feed-item-btn.active {
- background: var(--bg-active);
- color: var(--text-primary);
+.nsrc-add-folder-btn:hover {
+ color: var(--accent-secondary);
}
-.feed-item-btn.active::before {
- content: "";
- position: absolute;
- left: 0;
- top: 50%;
- transform: translateY(-50%);
- width: 2px;
- height: 14px;
- background: var(--accent);
- border-radius: 1px;
+.nsrc-add-folder-icon {
+ font-size: 14px;
+ font-weight: 600;
+ width: 16px;
+ text-align: center;
}
-.feed-item-btn {
- position: relative;
+.nsrc-add-folder-label {
+ font-weight: 500;
}
-.feed-icon {
- font-size: 10px;
- width: 14px;
- text-align: center;
- flex-shrink: 0;
-}
-
-.feed-name {
- flex: 1;
- text-align: left;
- white-space: nowrap;
+.nsrc-folder-input-row {
+ padding: 2px var(--space-lg);
overflow: hidden;
- text-overflow: ellipsis;
}
-.feed-unread {
- font-size: 10px;
- font-weight: 600;
- color: var(--text-tertiary);
+.nsrc-folder-input {
+ width: 100%;
+ padding: 4px 8px;
+ font-size: 12px;
+ font-family: var(--font-body);
background: var(--bg-elevated);
- padding: 0 5px;
- border-radius: 8px;
- min-width: 16px;
- text-align: center;
- line-height: 16px;
+ border: 1px solid var(--border-default);
+ border-radius: 6px;
+ color: var(--text-primary);
+ outline: none;
}
-.feed-item-btn.active .feed-unread,
-.feed-item-btn:hover .feed-unread {
- color: var(--text-secondary);
+.nsrc-folder-input:focus {
+ border-color: var(--accent);
}
-/* Sync error banner */
-.sync-error-banner {
+.nsrc-folder-input.inline {
+ padding: 2px 6px;
+ font-size: 12px;
+ flex: 1;
+}
+
+.nsrc-root-zone {
+ display: flex;
+ flex-direction: column;
+}
+
+.nsrc-note {
display: flex;
align-items: center;
- gap: 6px;
- padding: 6px var(--space-md);
- background: color-mix(in srgb, var(--accent-red, #e74c3c) 15%, transparent);
- color: var(--accent-red, #e74c3c);
- font-size: 11px;
- line-height: 1.3;
- border-top: 1px solid color-mix(in srgb, var(--accent-red, #e74c3c) 25%, transparent);
+ gap: var(--space-sm);
+ padding: 5px var(--space-lg) 5px calc(var(--space-lg) + 8px);
+ margin: 2px var(--space-sm);
+ cursor: pointer;
+ font-size: 12px;
+ color: var(--text-secondary);
+ transition: all var(--duration-fast) var(--ease-out);
+ border-radius: 6px;
+ user-select: none;
+ background: var(--bg-surface);
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
}
-.sync-error-icon {
+.nsrc-note:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.06);
+}
+
+.nsrc-note.active {
+ background: var(--accent-glow);
+ color: var(--accent-text);
+}
+
+
+.nsrc-note-icon {
+ font-size: 11px;
+ opacity: 0.5;
flex-shrink: 0;
}
-.sync-error-text {
+.nsrc-note.active .nsrc-note-icon {
+ opacity: 1;
+ color: var(--accent-secondary);
+}
+
+.nsrc-note-title {
+ flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
+ font-weight: 500;
}
-/* Footer */
-.source-panel-footer {
+.nsrc-folder {
display: flex;
- align-items: center;
- gap: 2px;
- padding: var(--space-sm) var(--space-md);
- border-top: 1px solid var(--border-subtle);
- flex-shrink: 0;
+ flex-direction: column;
}
-.footer-btn {
+.nsrc-folder-header {
display: flex;
align-items: center;
- justify-content: center;
- width: 32px;
- height: 32px;
- border: none;
- border-radius: var(--radius-md);
- background: transparent;
- color: var(--text-tertiary);
- font-size: 15px;
+ gap: var(--space-sm);
+ padding: 6px var(--space-lg);
cursor: pointer;
+ font-size: 12px;
+ color: var(--text-secondary);
transition: all var(--duration-fast) var(--ease-out);
+ border-radius: 4px;
+ border: 1px solid transparent;
}
-.footer-btn:hover {
+.nsrc-folder-header:hover {
background: var(--bg-hover);
- color: var(--text-secondary);
+ color: var(--text-primary);
}
-/* Bouton + agrandi pour correspondre au SyncButton */
-.footer-btn-add {
- width: 36px;
- height: 36px;
- font-size: 20px;
- font-weight: 300;
+.nsrc-folder-header.active {
+ background: var(--accent-glow);
+ color: var(--accent-text);
+ border-color: var(--accent-tertiary-glow);
}
-.footer-btn-add:hover {
- color: var(--accent);
+.nsrc-folder-icon {
+ font-size: 13px;
+ flex-shrink: 0;
}
-.upgrade-btn {
- color: #f59e0b;
+.nsrc-folder-header.active .nsrc-folder-icon {
+ color: var(--accent-tertiary);
}
-.upgrade-btn:hover {
- color: #d97706;
- background: rgba(245, 158, 11, 0.1);
+.nsrc-folder-name {
+ flex: 1;
+ font-weight: 600;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
-/* ─── ADD FEED MODAL ─────────────────────────── */
+.nsrc-folder-count {
+ font-size: 10px;
+ color: var(--accent-secondary);
+ background: var(--accent-secondary-glow);
+ padding: 1px 6px;
+ border-radius: 8px;
+ min-width: 18px;
+ text-align: center;
+}
-.modal-backdrop {
- position: fixed;
- inset: 0;
- background: rgba(0, 0, 0, 0.6);
- backdrop-filter: blur(4px);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 1000;
+.nsrc-folder-children {
+ overflow: hidden;
}
-.modal-content {
- background: var(--bg-surface);
- border: 1px solid var(--border-default);
- border-radius: var(--radius-lg);
- width: 100%;
- max-width: 420px;
- max-height: 90vh;
- overflow-y: auto;
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
+.nsrc-folder-children .nsrc-note {
+ padding-left: calc(var(--space-lg) + 20px);
}
-.modal-header {
+.nsrc-folder-empty {
+ padding: 4px calc(var(--space-lg) + 20px);
+ font-size: 11px;
+ color: var(--text-tertiary);
+ font-style: italic;
+}
+
+/* ── Sticky Board (panel 2 – board view) ── */
+
+.sticky-board-wrapper {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
+
+.sticky-board-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
- padding: var(--space-lg) var(--space-xl);
+ padding: 6px var(--space-md);
border-bottom: 1px solid var(--border-subtle);
+ flex-shrink: 0;
}
-.modal-title {
- font-family: var(--font-display);
- font-size: 18px;
- font-weight: 600;
- color: var(--text-primary);
- letter-spacing: -0.01em;
+.sticky-color-picker {
+ display: flex;
+ align-items: center;
+ gap: 6px;
}
-.modal-close {
+.sticky-color-swatch {
+ width: 22px;
+ height: 22px;
+ border-radius: 50%;
+ border: 2px solid transparent;
+ cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
- width: 28px;
- height: 28px;
- border: none;
- border-radius: var(--radius-sm);
- background: transparent;
- color: var(--text-tertiary);
- font-size: 20px;
- cursor: pointer;
- transition: all var(--duration-fast) var(--ease-out);
-}
-
-.modal-close:hover {
- background: var(--bg-hover);
- color: var(--text-primary);
+ transition: transform 0.15s, border-color 0.15s;
+ font-size: 10px;
+ line-height: 1;
}
-.modal-form {
- padding: var(--space-xl);
- display: flex;
- flex-direction: column;
- gap: var(--space-lg);
+.sticky-color-swatch:hover {
+ transform: scale(1.15);
}
-.form-group {
- display: flex;
- flex-direction: column;
- gap: var(--space-sm);
+.sticky-color-swatch.active {
+ transform: scale(1.15);
+ box-shadow: 0 1px 4px rgba(0,0,0,0.15);
}
-.form-label {
- font-size: 12px;
- font-weight: 600;
- color: var(--text-secondary);
- letter-spacing: 0.02em;
+.sticky-color-check {
+ font-size: 11px;
+ font-weight: 700;
+ color: rgba(0,0,0,0.4);
}
-.form-hint {
- font-weight: 400;
- color: var(--text-tertiary);
- font-style: italic;
-}
-
-.form-resolved {
+.sticky-add-btn {
+ width: 28px;
+ height: 28px;
+ border-radius: 8px;
+ border: none;
+ background: var(--accent);
+ color: #fff;
+ font-size: 16px;
+ font-weight: 600;
+ cursor: pointer;
display: flex;
align-items: center;
- gap: var(--space-xs);
- padding: var(--space-xs) var(--space-sm);
- font-size: 11px;
- color: var(--accent-dim);
+ justify-content: center;
+ transition: background 0.15s;
}
-.resolved-icon {
- font-size: 10px;
- color: var(--accent);
+.sticky-add-btn:hover {
+ filter: brightness(1.1);
}
-.resolved-url {
- font-family: var(--font-body);
- letter-spacing: 0.01em;
+.sticky-board {
+ flex: 1;
+ position: relative;
+ overflow: hidden;
+ background-image: radial-gradient(circle at 1px 1px, var(--border-subtle) 1px, transparent 0);
+ background-size: 28px 28px;
}
-/* RSSHub suggestion badge */
-.rsshub-suggestion {
+.sticky-board-empty {
+ position: absolute;
+ inset: 0;
display: flex;
+ flex-direction: column;
align-items: center;
- gap: var(--space-xs);
- padding: var(--space-xs) var(--space-sm);
- margin-top: 4px;
- font-size: 11px;
- color: var(--text-secondary);
- background: var(--bg-elevated);
- border: 1px solid var(--border-default);
- border-radius: var(--radius-sm);
- cursor: pointer;
- transition: all 0.15s ease;
- text-align: left;
- width: 100%;
-}
-
-.rsshub-suggestion:hover {
- color: #f5712c;
- border-color: #f5712c40;
- background: #f5712c08;
+ justify-content: center;
+ gap: 4px;
+ color: var(--text-tertiary);
}
-.rsshub-logo {
- flex-shrink: 0;
- color: #f5712c;
+.sticky-board-empty-icon {
+ font-size: 32px;
+ opacity: 0.4;
}
-.rsshub-suggestion-text {
- font-family: var(--font-body);
- letter-spacing: 0.01em;
+.sticky-board-empty p {
+ margin: 0;
+ font-size: 13px;
}
-/* RSSHub badge on feed items in SourcePanel */
-.feed-rsshub-badge {
- display: inline-flex;
- align-items: center;
- margin-left: auto;
- flex-shrink: 0;
- color: #f5712c;
+.sticky-board-empty-hint {
+ font-size: 11px !important;
opacity: 0.6;
}
-.feed-item-btn:hover .feed-rsshub-badge {
- opacity: 1;
-}
-
-.form-input {
- padding: var(--space-md) var(--space-lg);
- border: 1px solid var(--border-default);
- border-radius: var(--radius-md);
- background: var(--bg-elevated);
- color: var(--text-primary);
- font-family: var(--font-body);
- font-size: 13px;
- transition: all var(--duration-fast) var(--ease-out);
-}
-
-.form-input:focus {
- outline: none;
- border-color: var(--accent);
- background: var(--bg-surface);
-}
+/* ── Single sticky note ── */
-.form-input::placeholder {
- color: var(--text-tertiary);
+.sticky-note {
+ position: absolute;
+ width: 200px;
+ min-height: 180px;
+ display: flex;
+ flex-direction: column;
+ border-radius: 2px;
+ user-select: none;
+ touch-action: none;
+ cursor: grab;
+ overflow: hidden;
}
-.source-selector {
- display: flex;
- flex-wrap: wrap;
- gap: var(--space-sm);
+.sticky-note--dragging {
+ cursor: grabbing;
+ z-index: 9999 !important;
}
-.source-option {
- display: flex;
- align-items: center;
- gap: var(--space-sm);
- padding: var(--space-sm) var(--space-md);
- border: 1px solid var(--border-default);
- border-radius: var(--radius-md);
- background: transparent;
- color: var(--text-secondary);
- font-family: var(--font-body);
- font-size: 12px;
- cursor: pointer;
- transition: all var(--duration-fast) var(--ease-out);
+.sticky-note--resizing {
+ z-index: 9999 !important;
}
-.source-option:hover {
- background: var(--bg-hover);
- color: var(--text-primary);
+.sticky-note--selected {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
}
-.source-option.active {
- background: var(--accent-glow);
- border-color: var(--accent);
- color: var(--accent-text);
+.sticky-note:not(.sticky-note--dragging):hover {
+ box-shadow: 0 8px 24px rgba(0,0,0,0.18) !important;
}
-.source-option-icon {
- font-size: 14px;
+.sticky-note-tape {
+ position: absolute;
+ top: -10px;
+ left: 50%;
+ width: 56px;
+ height: 20px;
+ border-radius: 2px;
+ opacity: 0.75;
+ pointer-events: none;
}
-.source-option-label {
- font-weight: 500;
+.sticky-note-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px 10px 2px;
}
-.form-error {
- padding: var(--space-md);
- background: rgba(217, 69, 58, 0.1);
- border: 1px solid rgba(217, 69, 58, 0.3);
- border-radius: var(--radius-md);
- color: var(--red);
+.sticky-note-grip {
font-size: 12px;
- font-weight: 500;
+ color: rgba(0,0,0,0.2);
+ cursor: grab;
+ letter-spacing: 1px;
}
-.modal-actions {
+.sticky-note-actions {
display: flex;
- justify-content: flex-end;
- gap: var(--space-md);
- margin-top: var(--space-md);
+ align-items: center;
+ gap: 2px;
}
-.btn-secondary,
-.btn-primary {
+.sticky-note-btn {
+ width: 22px;
+ height: 22px;
+ border: none;
+ background: none;
+ border-radius: 50%;
+ cursor: pointer;
display: flex;
align-items: center;
- gap: var(--space-sm);
- padding: var(--space-md) var(--space-lg);
- border-radius: var(--radius-md);
- font-family: var(--font-body);
- font-size: 13px;
- font-weight: 500;
- cursor: pointer;
- transition: all var(--duration-fast) var(--ease-out);
+ justify-content: center;
+ font-size: 12px;
+ color: rgba(0,0,0,0.35);
+ transition: background 0.15s, color 0.15s;
}
-.btn-secondary {
- background: transparent;
- border: 1px solid var(--border-default);
- color: var(--text-secondary);
+.sticky-note-btn:hover {
+ background: rgba(0,0,0,0.08);
+ color: rgba(0,0,0,0.7);
}
-.btn-secondary:hover {
- background: var(--bg-hover);
- color: var(--text-primary);
+.sticky-note-btn--save {
+ color: #15803d;
+ font-weight: 700;
}
-.btn-primary {
- background: var(--accent);
- border: 1px solid var(--accent);
- color: var(--text-inverse);
+.sticky-note-btn--save:hover {
+ background: rgba(21,128,61,0.1);
}
-.btn-primary:hover {
- background: var(--accent-dim);
- border-color: var(--accent-dim);
+.sticky-note-btn--delete:hover {
+ background: rgba(220,38,38,0.1);
+ color: #dc2626;
}
-.btn-primary:disabled {
- opacity: 0.6;
- cursor: not-allowed;
+.sticky-note-title {
+ padding: 0 12px;
+ font-size: 11px;
+ font-weight: 600;
+ color: rgba(0,0,0,0.45);
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
-.btn-spinner {
- width: 14px;
- height: 14px;
- border: 2px solid transparent;
- border-top-color: currentColor;
- border-radius: 50%;
- animation: spin 0.6s linear infinite;
+.sticky-note-content {
+ flex: 1;
+ padding: 6px 12px 12px;
}
-/* ─── Feed search dropdown ─── */
-
-.feed-search-results {
- position: absolute;
- top: 100%;
- left: 0;
- right: 0;
- margin-top: 4px;
- max-height: 240px;
- overflow-y: auto;
- background: var(--bg-surface);
- border: 1px solid var(--border-default);
- border-radius: var(--radius-md);
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
- z-index: 10;
+.sticky-note-text {
+ font-family: 'Caveat', cursive;
+ font-size: 18px;
+ line-height: 1.4;
+ color: rgba(0,0,0,0.8);
+ word-break: break-word;
+ cursor: grab;
+ margin: 0;
}
-.feed-search-item {
- display: flex;
- align-items: center;
- gap: var(--space-md);
- width: 100%;
- padding: var(--space-md);
- border: none;
- border-bottom: 1px solid var(--border-subtle);
- background: transparent;
- cursor: pointer;
- text-align: left;
- font-family: var(--font-body);
- transition: background var(--duration-fast) var(--ease-out);
+.sticky-note-placeholder {
+ color: rgba(0,0,0,0.3);
+ font-style: italic;
+ margin: 0;
}
-.feed-search-item:last-child {
- border-bottom: none;
+/* Markdown inside sticky notes */
+.sticky-note-md p { margin: 0 0 6px; }
+.sticky-note-md p:last-child { margin-bottom: 0; }
+.sticky-note-md h1, .sticky-note-md h2, .sticky-note-md h3 {
+ font-size: 1.1em;
+ font-weight: 700;
+ margin: 0 0 4px;
+ line-height: 1.3;
}
-
-.feed-search-item:hover {
- background: var(--bg-hover);
+.sticky-note-md ul, .sticky-note-md ol {
+ margin: 2px 0 6px;
+ padding-left: 18px;
}
-
-.feed-search-artwork {
- width: 40px;
- height: 40px;
- border-radius: 6px;
- object-fit: cover;
- flex-shrink: 0;
+.sticky-note-md li { margin-bottom: 2px; }
+.sticky-note-md code {
+ background: rgba(0,0,0,0.06);
+ border-radius: 3px;
+ padding: 1px 4px;
+ font-family: monospace;
+ font-size: 0.85em;
}
-
-.feed-search-artwork-placeholder {
- background: var(--bg-elevated);
- border: 1px solid var(--border-subtle);
+.sticky-note-md pre {
+ background: rgba(0,0,0,0.06);
+ border-radius: 4px;
+ padding: 6px 8px;
+ overflow-x: auto;
+ font-size: 0.82em;
+ margin: 4px 0;
}
-
-.feed-search-info {
- flex: 1;
- min-width: 0;
- display: flex;
- flex-direction: column;
- gap: 2px;
+.sticky-note-md pre code {
+ background: none;
+ padding: 0;
}
-
-.feed-search-name {
- font-size: 13px;
- font-weight: 600;
- color: var(--text-primary);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
+.sticky-note-md blockquote {
+ border-left: 3px solid rgba(0,0,0,0.2);
+ margin: 4px 0;
+ padding-left: 8px;
+ color: rgba(0,0,0,0.6);
}
+.sticky-note-md strong { font-weight: 700; }
+.sticky-note-md a { color: #1971c2; text-decoration: underline; }
-.feed-search-description {
- font-size: 11px;
- color: var(--text-secondary);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
+.sticky-note-textarea {
+ width: 100%;
+ min-height: 60px;
+ height: 100%;
+ background: transparent;
+ border: none;
+ outline: none;
+ resize: none;
+ font-family: 'Caveat', cursive;
+ font-size: 18px;
+ line-height: 1.4;
+ color: rgba(0,0,0,0.8);
}
-.feed-search-meta {
- flex-shrink: 0;
- font-size: 11px;
- color: var(--text-tertiary);
- white-space: nowrap;
+.sticky-note-textarea::placeholder {
+ color: rgba(0,0,0,0.25);
}
-.feed-search-loading,
-.feed-search-empty {
+.sticky-note-resize {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ width: 20px;
+ height: 20px;
+ cursor: nwse-resize;
display: flex;
align-items: center;
justify-content: center;
- gap: var(--space-sm);
- padding: var(--space-lg);
- font-size: 12px;
- color: var(--text-tertiary);
+ opacity: 0.4;
+ transition: opacity 0.15s;
+ touch-action: none;
}
-/* Settings modal */
-.settings-body {
- padding: var(--space-md) 0;
+.sticky-note-resize:hover {
+ opacity: 0.8;
}
-.settings-section {
- padding: var(--space-md) var(--space-xl);
-}
+/* ── Note Editor (3rd panel) ── */
-.settings-section-title {
- font-size: 14px;
- font-weight: 600;
- color: var(--text-primary);
- margin: 0 0 var(--space-xs) 0;
+.note-editor {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ background: var(--bg-surface);
+ border-left: 1px solid var(--border-subtle);
}
-.settings-section-desc {
- font-size: 12px;
- color: var(--text-secondary);
- margin: 0 0 var(--space-lg) 0;
- line-height: 1.5;
+.note-editor-header {
+ padding: var(--space-lg);
+ border-bottom: 1px solid var(--border-subtle);
+ flex-shrink: 0;
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-xs);
}
-.opml-dropzone {
+.note-editor-title-row {
display: flex;
- flex-direction: column;
align-items: center;
- justify-content: center;
gap: var(--space-sm);
- padding: var(--space-xl) var(--space-lg);
- border: 2px dashed var(--border-default);
- border-radius: var(--radius-lg);
- cursor: pointer;
- transition: all var(--duration-fast) var(--ease-out);
- background: transparent;
}
-.opml-dropzone:hover {
- border-color: var(--accent);
- background: var(--accent-glow);
+.note-editor-title {
+ flex: 1;
+ font-family: var(--font-display);
+ font-size: 20px;
+ font-weight: 600;
+ color: var(--text-primary);
+ background: transparent;
+ border: none;
+ outline: none;
+ padding: 0;
+ letter-spacing: -0.01em;
}
-.opml-dropzone.dragging {
- border-color: var(--accent);
- background: var(--accent-glow);
- transform: scale(1.01);
+.note-editor-title::placeholder {
+ color: var(--text-tertiary);
}
-.opml-file-input {
- display: none;
+.note-editor-date {
+ font-size: 11px;
+ color: var(--text-tertiary);
}
-.opml-dropzone-icon {
- font-size: 28px;
- opacity: 0.7;
+.note-editor-body {
+ flex: 1;
+ overflow-y: auto;
+ padding: var(--space-lg);
}
-.opml-dropzone-text {
- font-size: 13px;
- font-weight: 500;
+.note-editor-content {
+ width: 100%;
+ height: 100%;
+ min-height: 300px;
+ font-family: var(--font-body);
+ font-size: 14px;
+ line-height: 1.7;
color: var(--text-primary);
+ background: transparent;
+ border: none;
+ outline: none;
+ resize: none;
+ padding: 0;
}
-.opml-dropzone-hint {
- font-size: 11px;
+.note-editor-content::placeholder {
color: var(--text-tertiary);
}
-.opml-result {
+.note-editor-empty {
+ height: 100%;
display: flex;
+ flex-direction: column;
align-items: center;
+ justify-content: center;
+ background: var(--bg-surface);
+ border-left: 1px solid var(--border-subtle);
gap: var(--space-sm);
- padding: var(--space-md);
- margin-top: var(--space-md);
- background: rgba(52, 199, 89, 0.1);
- border: 1px solid rgba(52, 199, 89, 0.3);
- border-radius: var(--radius-md);
- font-size: 13px;
- color: var(--text-primary);
-}
-
-.opml-result-icon {
- color: #34c759;
- font-weight: 700;
- font-size: 15px;
}
-.opml-result-text strong {
- font-weight: 600;
-}
-
-.opml-result-skipped {
- color: var(--text-secondary);
- font-size: 12px;
+/* Dark / Sepia shadow overrides */
+.dark .note-card {
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
-@keyframes spin {
- to { transform: rotate(360deg); }
+.dark .note-card:hover {
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
}
-/* ─── FEED PANEL (Center) ─────────────────────── */
-
-.feed-panel {
+.panel {
height: 100%;
+ overflow: hidden;
display: flex;
flex-direction: column;
- background: var(--bg-root);
+ flex-shrink: 0;
}
-.feed-panel-header {
- padding: var(--space-lg) var(--space-lg) var(--space-md);
- border-bottom: 1px solid var(--border-subtle);
+/* ─── RESIZE HANDLE ───────────────────────────── */
+
+.resize-handle {
+ width: 5px;
+ height: 100%;
+ cursor: col-resize;
+ position: relative;
flex-shrink: 0;
+ z-index: 10;
}
-.feed-panel-title-row {
- display: flex;
- align-items: baseline;
- gap: var(--space-md);
- margin-bottom: var(--space-sm);
+.resize-handle-line {
+ position: absolute;
+ top: 0;
+ left: 2px;
+ width: 1px;
+ height: 100%;
+ background: var(--border-subtle);
+ transition:
+ background var(--duration-fast) var(--ease-out),
+ width var(--duration-fast) var(--ease-out),
+ left var(--duration-fast) var(--ease-out);
}
-.feed-panel-title {
- font-family: var(--font-display);
- font-size: 20px;
- font-weight: 600;
- color: var(--text-primary);
- letter-spacing: -0.01em;
+.resize-handle:hover .resize-handle-line,
+.resize-handle.hovered .resize-handle-line {
+ width: 3px;
+ left: 1px;
+ background: var(--accent);
+ box-shadow: 0 0 8px var(--accent-glow);
}
-.feed-panel-unread {
- font-size: 11px;
- font-weight: 500;
- color: var(--accent-dim);
- letter-spacing: 0.02em;
+/* ─── SOURCE PANEL (Left) ─────────────────────── */
+
+.source-panel {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ background: var(--bg-surface);
+ border-right: 1px solid var(--border-subtle);
}
-.feed-panel-actions {
+.source-panel-header {
+ padding: var(--space-xl) var(--space-lg) var(--space-lg);
+ border-bottom: 1px solid var(--border-subtle);
+ flex-shrink: 0;
+}
+
+.source-panel-brand {
display: flex;
align-items: center;
- gap: 2px;
+ gap: var(--space-sm);
}
-.feed-action-btn {
+.brand-icon {
+ font-size: 22px;
+ color: var(--accent);
+ line-height: 1;
+}
+
+.brand-name {
+ font-family: var(--font-display);
+ font-size: 20px;
+ font-weight: 600;
+ letter-spacing: 0.02em;
+ color: var(--text-primary);
+ flex: 1;
+}
+
+.theme-toggle-btn {
display: flex;
align-items: center;
justify-content: center;
- width: 28px;
- height: 28px;
+ width: 30px;
+ height: 30px;
border: none;
- border-radius: var(--radius-sm);
+ border-radius: var(--radius-md);
background: transparent;
color: var(--text-tertiary);
- font-size: 14px;
cursor: pointer;
transition: all var(--duration-fast) var(--ease-out);
}
-.feed-action-btn:hover {
+.theme-toggle-btn:hover {
background: var(--bg-hover);
- color: var(--text-secondary);
+ color: var(--accent);
}
-/* Feed list */
-.feed-panel-list {
+.theme-toggle-btn svg {
+ width: 16px;
+ height: 16px;
+}
+
+.source-panel-content {
flex: 1;
overflow-y: auto;
- padding: var(--space-sm) var(--space-sm);
+ padding: var(--space-md) var(--space-sm);
}
-/* Empty state */
-.feed-empty {
+/* All feeds button */
+.source-all-btn {
display: flex;
- flex-direction: column;
align-items: center;
- justify-content: center;
- padding: var(--space-3xl) var(--space-xl);
- text-align: center;
gap: var(--space-sm);
-}
-
-.feed-empty-icon {
- font-size: 32px;
- color: var(--accent);
- opacity: 0.3;
- margin-bottom: var(--space-sm);
-}
-
-.feed-empty-text {
- font-family: var(--font-display);
- font-size: 15px;
- font-weight: 500;
+ width: 100%;
+ padding: var(--space-sm) var(--space-md);
+ border: none;
+ border-radius: var(--radius-md);
+ background: transparent;
color: var(--text-secondary);
+ font-family: var(--font-body);
+ font-size: 13px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all var(--duration-fast) var(--ease-out);
+ margin-bottom: var(--space-md);
}
-.feed-empty-hint {
- font-size: 12px;
- color: var(--text-tertiary);
- max-width: 220px;
- line-height: 1.5;
+.source-all-btn:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
}
-.feed-group {
- margin-bottom: var(--space-md);
+.source-all-btn.active {
+ background: var(--accent-glow);
+ color: var(--accent-text);
}
-.feed-group-label {
- padding: var(--space-sm) var(--space-md);
- font-size: 10px;
- font-weight: 600;
- text-transform: uppercase;
- letter-spacing: 0.1em;
- color: var(--text-tertiary);
+.source-favorites-btn {
+ margin-bottom: var(--space-xs);
}
-/* Feed card */
-.feed-card {
- padding: var(--space-md) var(--space-lg);
- margin: 1px 0;
- border-radius: var(--radius-md);
- cursor: pointer;
- transition: all var(--duration-fast) var(--ease-out);
- position: relative;
- border: 1px solid transparent;
+.source-favorites-btn .source-all-icon {
+ color: #f59e0b;
}
-.feed-card:hover {
- background: var(--bg-elevated);
+.source-favorites-btn.active {
+ background: rgba(245, 158, 11, 0.1);
+ color: var(--text-primary);
}
-.feed-card.active {
- background: var(--bg-elevated);
- border-color: var(--border-default);
+.source-favorites-btn.active .source-all-count {
+ color: #f59e0b;
+ background: rgba(245, 158, 11, 0.15);
}
-.feed-card.active::after {
- content: "";
- position: absolute;
- left: 0;
- top: 12px;
- bottom: 12px;
- width: 2px;
- background: var(--accent);
- border-radius: 1px;
+.source-readlater-btn {
+ margin-bottom: var(--space-md);
}
-.feed-card.read .feed-card-title {
- color: var(--text-secondary);
- opacity: 0.5;
+.source-readlater-btn .source-all-icon {
+ font-size: 14px;
}
-.feed-card.read .feed-card-excerpt {
- color: var(--text-tertiary);
+.source-readlater-btn.active {
+ background: rgba(59, 130, 246, 0.1);
+ color: var(--text-primary);
}
-/* Drag-and-drop reorder (favorites / read later) */
-.feed-card[draggable="true"] {
- cursor: grab;
+.source-readlater-btn.active .source-all-count {
+ color: #3b82f6;
+ background: rgba(59, 130, 246, 0.15);
}
-.feed-card[draggable="true"]:active {
- cursor: grabbing;
+.source-all-icon {
+ font-size: 15px;
+ opacity: 0.7;
}
-.feed-card.dragging {
- opacity: 0.4;
+.source-all-label {
+ flex: 1;
+ text-align: left;
}
-.feed-card.drop-over {
- border-top: 2px solid var(--accent);
+.source-all-count {
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--accent-tertiary);
+ background: var(--accent-tertiary-glow);
+ padding: 1px 7px;
+ border-radius: 10px;
+ min-width: 20px;
+ text-align: center;
}
-.feed-card-drag-handle {
- color: var(--text-tertiary);
- font-size: 12px;
- cursor: grab;
- user-select: none;
- letter-spacing: 1px;
- opacity: 0.4;
- transition: opacity 0.15s;
+/* Category sections */
+.source-categories {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
}
-.feed-card:hover .feed-card-drag-handle {
- opacity: 1;
+.source-category {
+ margin-bottom: var(--space-xs);
}
-/* Unread dot */
-.feed-card-title-row {
+.category-header {
display: flex;
- align-items: flex-start;
+ align-items: center;
gap: var(--space-sm);
+ width: 100%;
+ padding: var(--space-sm) var(--space-md);
+ border: none;
+ border-radius: var(--radius-md);
+ background: transparent;
+ color: var(--text-secondary);
+ font-family: var(--font-body);
+ font-size: 12px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ cursor: pointer;
+ transition: all var(--duration-fast) var(--ease-out);
}
-.feed-card-title-row .feed-card-title {
- flex: 1;
- min-width: 0;
+.category-header:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
}
-.feed-card-unread-dot {
- flex-shrink: 0;
- width: 6px;
- height: 6px;
- border-radius: 50%;
- background: var(--blue);
- margin-top: 7px;
-}
-
-/* Context menu */
-.feed-context-menu {
- position: fixed;
- z-index: 999;
- min-width: 180px;
- background: var(--bg-surface);
- border: 1px solid var(--border-default);
- border-radius: var(--radius-md);
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
- padding: var(--space-xs) 0;
+.category-header.active {
+ color: var(--accent-text);
+ background: var(--accent-glow);
}
-.feed-context-menu-item {
- display: flex;
- align-items: center;
- gap: var(--space-sm);
- width: 100%;
- padding: var(--space-sm) var(--space-md);
- border: none;
- background: transparent;
- color: var(--text-primary);
- font-family: var(--font-body);
- font-size: 12.5px;
- cursor: pointer;
- transition: background var(--duration-fast) var(--ease-out);
+.category-icon {
+ font-size: 11px;
+ width: 16px;
+ text-align: center;
+ opacity: 0.6;
}
-.feed-context-menu-item:hover {
- background: var(--bg-hover);
+.category-label {
+ flex: 1;
+ text-align: left;
}
-.feed-context-menu-icon {
+.category-count {
font-size: 10px;
- color: var(--blue);
- width: 14px;
+ font-weight: 600;
+ color: var(--text-tertiary);
+ min-width: 16px;
text-align: center;
}
-.feed-context-menu-item--danger {
- color: var(--red);
+.category-header.active .category-count {
+ color: var(--accent-dim);
}
-.feed-context-menu-item--danger .feed-context-menu-icon {
- color: var(--red);
+.category-chevron {
+ font-size: 14px;
+ width: 16px;
+ text-align: center;
+ transition: transform var(--duration-fast) var(--ease-out);
+ color: var(--text-tertiary);
+ line-height: 1;
}
-.feed-context-menu-item--danger:hover {
- background: rgba(217, 69, 58, 0.1);
+.category-chevron.expanded {
+ transform: rotate(90deg);
}
-/* ── Subfolder styles ── */
-
-.subfolder {
- margin: 0;
+/* Feed items in category */
+.category-feeds {
+ overflow: hidden;
}
-.subfolder-header {
+.feed-item-btn {
display: flex;
align-items: center;
- gap: var(--space-xs);
+ gap: var(--space-sm);
width: 100%;
- padding: 5px var(--space-md) 5px 24px; /* left padding overridden by inline style for nesting */
+ padding: 6px var(--space-md) 6px 28px;
border: none;
border-radius: var(--radius-sm);
background: transparent;
color: var(--text-secondary);
font-family: var(--font-body);
- font-size: 12px;
- font-weight: 500;
+ font-size: 12.5px;
+ font-weight: 400;
cursor: pointer;
transition: all var(--duration-fast) var(--ease-out);
}
-.subfolder-header:hover {
+.feed-item-btn:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
-.subfolder-chevron {
- font-size: 12px;
- width: 12px;
- text-align: center;
- transition: transform var(--duration-fast) var(--ease-out);
- color: var(--text-tertiary);
- line-height: 1;
+.feed-item-btn.active {
+ background: var(--bg-active);
+ color: var(--text-primary);
}
-.subfolder-chevron.expanded {
- transform: rotate(90deg);
+.feed-item-btn.active::before {
+ content: "";
+ position: absolute;
+ left: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 2px;
+ height: 14px;
+ background: var(--accent);
+ border-radius: 1px;
}
-.subfolder-icon {
- font-size: 11px;
+.feed-item-btn {
+ position: relative;
+}
+
+.feed-icon {
+ font-size: 10px;
width: 14px;
text-align: center;
flex-shrink: 0;
}
-.subfolder-name {
+.feed-name {
flex: 1;
text-align: left;
white-space: nowrap;
@@ -2542,2122 +2762,3105 @@ body {
text-overflow: ellipsis;
}
-.subfolder-count {
+.feed-unread {
font-size: 10px;
+ font-weight: 600;
color: var(--text-tertiary);
- min-width: 14px;
+ background: var(--bg-elevated);
+ padding: 0 5px;
+ border-radius: 8px;
+ min-width: 16px;
text-align: center;
+ line-height: 16px;
}
-/* Folder inline input */
+.feed-item-btn.active .feed-unread,
+.feed-item-btn:hover .feed-unread {
+ color: var(--text-secondary);
+}
-.folder-inline-input-wrapper {
- padding: 2px var(--space-md) 2px 24px; /* left padding overridden by inline style for nesting */
+/* Sync error banner */
+.sync-error-banner {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 6px var(--space-md);
+ background: color-mix(in srgb, var(--accent-red, #e74c3c) 15%, transparent);
+ color: var(--accent-red, #e74c3c);
+ font-size: 11px;
+ line-height: 1.3;
+ border-top: 1px solid color-mix(in srgb, var(--accent-red, #e74c3c) 25%, transparent);
}
-.folder-inline-input {
- width: 100%;
- padding: 4px 8px;
- border: 1px solid var(--accent);
- border-radius: var(--radius-sm);
- background: var(--bg-surface);
- color: var(--text-primary);
- font-family: var(--font-body);
- font-size: 12px;
- outline: none;
+.sync-error-icon {
+ flex-shrink: 0;
}
-.folder-inline-input::placeholder {
- color: var(--text-tertiary);
+.sync-error-text {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
-/* Context submenu (move to) */
+/* Footer */
+/* ── Source panel search bar ── */
-.feed-context-menu-submenu-wrapper {
- position: relative;
+.source-panel-search {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 6px var(--space-md);
+ border-top: 1px solid var(--border-subtle);
+ flex-shrink: 0;
}
-.feed-context-menu-arrow {
- margin-left: auto;
- font-size: 12px;
+.source-panel-search-icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
color: var(--text-tertiary);
+ flex-shrink: 0;
}
-.context-submenu {
- position: absolute;
- left: 100%;
- top: 0;
- min-width: 150px;
- background: var(--bg-surface);
- border: 1px solid var(--border-default);
+.source-panel-search-input {
+ flex: 1;
+ min-width: 0;
+ height: 28px;
+ padding: 0 8px;
+ border: 1px solid var(--border-subtle);
border-radius: var(--radius-md);
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
- padding: var(--space-xs) 0;
- z-index: 1000;
-}
-
-/* ── Drag-and-drop ── */
-
-.feed-item-btn[draggable="true"] {
- cursor: grab;
-}
-
-.feed-item-btn[draggable="true"]:active {
- cursor: grabbing;
+ background: var(--bg-surface);
+ color: var(--text-primary);
+ font-size: 12px;
+ font-family: var(--font-body);
+ outline: none;
+ transition: border-color var(--duration-fast) var(--ease-out),
+ box-shadow var(--duration-fast) var(--ease-out);
}
-.feed-item-btn.dragging {
- opacity: 0.4;
+.source-panel-search-input::placeholder {
+ color: var(--text-tertiary);
}
-.category-root-zone {
- min-height: 4px;
- border-radius: var(--radius-sm);
- transition: background var(--duration-fast) var(--ease-out),
- outline var(--duration-fast) var(--ease-out);
+.source-panel-search-input:focus,
+.source-panel-search-input[data-focus] {
+ border-color: var(--accent);
+ box-shadow: 0 0 0 2px var(--accent-glow);
}
-.category-root-zone.drop-over {
- background: var(--accent-glow);
- outline: 1.5px dashed var(--accent);
- outline-offset: -1.5px;
+.source-panel-search-clear {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 20px;
+ height: 20px;
+ border: none;
+ background: none;
+ color: var(--text-tertiary);
+ font-size: 10px;
+ cursor: pointer;
+ border-radius: 50%;
+ flex-shrink: 0;
+ transition: background var(--duration-fast), color var(--duration-fast);
}
-.subfolder-header.drop-over {
- background: var(--accent-glow);
- color: var(--accent-text);
- outline: 1.5px dashed var(--accent);
- outline-offset: -1.5px;
+.source-panel-search-clear:hover {
+ background: var(--bg-hover);
+ color: var(--text-secondary);
}
-.subfolder.drop-over > .subfolder-feeds {
- background: var(--accent-glow);
- border-radius: var(--radius-sm);
+/* ── Bookmark URL input bar ── */
+.bk-url-input-bar {
+ overflow: hidden;
+ border-top: 1px solid var(--border-subtle);
+ flex-shrink: 0;
}
-
-.feed-card-meta {
+.bk-url-input-bar form {
display: flex;
align-items: center;
- gap: var(--space-xs);
- margin-bottom: var(--space-xs);
+ gap: 6px;
+ padding: 6px var(--space-md);
}
-
-.feed-card-source {
- font-size: 11px;
- font-weight: 600;
- color: var(--accent-dim);
- letter-spacing: 0.02em;
+.bk-url-input {
+ flex: 1;
+ min-width: 0;
+ height: 30px;
+ padding: 0 10px;
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-md);
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ font-size: 12px;
+ outline: none;
+ transition: border-color 0.15s;
}
-
-.feed-card-dot {
- color: var(--text-tertiary);
- font-size: 10px;
+.bk-url-input:focus {
+ border-color: var(--accent);
}
-
-.feed-card-time {
- font-size: 11px;
+.bk-url-input::placeholder {
color: var(--text-tertiary);
}
+.bk-url-submit {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
+ border: none;
+ border-radius: var(--radius-md);
+ background: var(--accent);
+ color: #fff;
+ cursor: pointer;
+ flex-shrink: 0;
+ transition: opacity 0.15s;
+}
+.bk-url-submit:disabled {
+ opacity: 0.35;
+ cursor: default;
+}
+.bk-url-submit:not(:disabled):hover {
+ opacity: 0.85;
+}
-.feed-card-star {
- color: var(--accent);
- font-size: 11px;
- margin-left: auto;
+.source-panel-footer {
+ display: flex;
+ align-items: center;
+ gap: 2px;
+ padding: var(--space-sm) var(--space-md);
+ border-top: 1px solid var(--border-subtle);
+ flex-shrink: 0;
}
-.feed-card-bookmark {
- background: none;
+.footer-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
border: none;
+ border-radius: var(--radius-md);
+ background: transparent;
+ color: var(--text-tertiary);
+ font-size: 15px;
cursor: pointer;
- font-size: 13px;
- padding: 0 2px;
- line-height: 1;
- opacity: 0;
- transition: opacity var(--duration-fast) var(--ease-out);
- margin-left: auto;
+ transition: all var(--duration-fast) var(--ease-out);
}
-.feed-card-star + .feed-card-bookmark {
- margin-left: 0;
+.footer-btn:hover {
+ background: var(--bg-hover);
+ color: var(--text-secondary);
}
-.feed-card:hover .feed-card-bookmark,
-.feed-card-bookmark.active {
- opacity: 1;
+/* Bouton + agrandi pour correspondre au SyncButton */
+.footer-btn-add {
+ width: 36px;
+ height: 36px;
+ font-size: 20px;
+ font-weight: 300;
}
-.feed-card-bookmark:hover {
- transform: scale(1.15);
+.footer-btn-add:hover {
+ color: var(--accent);
}
-.feed-card-title {
- font-family: var(--font-display);
- font-size: 14.5px;
- font-weight: 500;
- line-height: 1.35;
- color: var(--text-primary);
- margin-bottom: var(--space-xs);
- transition: color var(--duration-fast) var(--ease-out);
+.upgrade-btn {
+ color: #f59e0b;
}
-.feed-card:hover .feed-card-title {
- color: var(--accent-text);
+.upgrade-btn:hover {
+ color: #d97706;
+ background: rgba(245, 158, 11, 0.1);
}
-.feed-card-excerpt {
- font-size: 12px;
- line-height: 1.5;
- color: var(--text-secondary);
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- overflow: hidden;
- margin-bottom: var(--space-sm);
-}
+/* ─── ADD FEED MODAL ─────────────────────────── */
-.feed-card-footer {
+.modal-backdrop {
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.6);
+ backdrop-filter: blur(4px);
display: flex;
align-items: center;
- gap: var(--space-sm);
+ justify-content: center;
+ z-index: 1000;
}
-.feed-card-tag {
- font-size: 10px;
- font-weight: 500;
- color: var(--text-tertiary);
- background: var(--bg-hover);
- padding: 1px 7px;
- border-radius: 3px;
- letter-spacing: 0.02em;
+.modal-content {
+ background: var(--bg-surface);
+ border: 1px solid var(--border-default);
+ border-radius: var(--radius-lg);
+ width: 100%;
+ max-width: 420px;
+ max-height: 90vh;
+ overflow-y: auto;
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
}
-.feed-card-readtime {
- font-size: 10px;
- color: var(--text-tertiary);
- margin-left: auto;
+.modal-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: var(--space-lg) var(--space-xl);
+ border-bottom: 1px solid var(--border-subtle);
}
-.feed-card-comments {
- font-size: 10px;
- font-weight: 500;
- color: var(--accent-dim);
- background: var(--accent-glow);
- padding: 1px 7px;
- border-radius: 3px;
- letter-spacing: 0.02em;
+.modal-title {
+ font-family: var(--font-display);
+ font-size: 18px;
+ font-weight: 600;
+ color: var(--text-primary);
+ letter-spacing: -0.01em;
}
-/* ─── Compact mode ─── */
-
-.feed-panel.compact .feed-card {
- padding: var(--space-sm) var(--space-lg);
- margin: 0;
+.modal-close {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
+ border: none;
+ border-radius: var(--radius-sm);
+ background: transparent;
+ color: var(--text-tertiary);
+ font-size: 20px;
+ cursor: pointer;
+ transition: all var(--duration-fast) var(--ease-out);
}
-.feed-panel.compact .feed-card-meta {
- margin-bottom: 2px;
+.modal-close:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
}
-.feed-panel.compact .feed-card-title {
- font-size: 13px;
- margin-bottom: 0;
+.modal-form {
+ padding: var(--space-xl);
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-lg);
}
-.feed-panel.compact .feed-card-unread-dot {
- width: 5px;
- height: 5px;
- margin-top: 6px;
+.form-group {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-sm);
}
-.feed-panel.compact .feed-group {
- margin-bottom: var(--space-sm);
+.form-label {
+ font-size: 12px;
+ font-weight: 600;
+ color: var(--text-secondary);
+ letter-spacing: 0.02em;
}
-.feed-action-btn.active {
- color: var(--accent-text);
+.form-hint {
+ font-weight: 400;
+ color: var(--text-tertiary);
+ font-style: italic;
}
-/* ─── Feed Panel Pagination (MorphingPageDots) ─── */
-
-.feed-panel-pagination {
- flex-shrink: 0;
- border-top: 1px solid var(--border-subtle);
+.form-resolved {
display: flex;
- flex-direction: column;
align-items: center;
- padding: var(--space-xs) var(--space-md);
-}
-
-.feed-panel-pagination > div {
- padding-top: var(--space-xs);
- padding-bottom: var(--space-xs);
- gap: var(--space-md);
-}
-
-.feed-panel-pagination svg {
- width: 16px;
- height: 16px;
+ gap: var(--space-xs);
+ padding: var(--space-xs) var(--space-sm);
+ font-size: 11px;
+ color: var(--accent-dim);
}
-.feed-panel-pagination__info {
- font-family: var(--font-body);
+.resolved-icon {
font-size: 10px;
- color: var(--text-tertiary);
- letter-spacing: 0.03em;
+ color: var(--accent);
}
-/* ─── Cards Grid View (Gradient Blob) ─────────── */
-
-.feed-cards-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
- gap: var(--space-md);
- padding: var(--space-md);
+.resolved-url {
+ font-family: var(--font-body);
+ letter-spacing: 0.01em;
}
-.feed-blob-card {
- position: relative;
- border-radius: 14px;
- overflow: hidden;
+/* RSSHub suggestion badge */
+.rsshub-suggestion {
+ display: flex;
+ align-items: center;
+ gap: var(--space-xs);
+ padding: var(--space-xs) var(--space-sm);
+ margin-top: 4px;
+ font-size: 11px;
+ color: var(--text-secondary);
+ background: var(--bg-elevated);
+ border: 1px solid var(--border-default);
+ border-radius: var(--radius-sm);
cursor: pointer;
- height: 220px;
- box-shadow:
- 6px 6px 20px rgba(0, 0, 0, 0.06),
- -4px -4px 16px rgba(255, 255, 255, 0.6);
+ transition: all 0.15s ease;
+ text-align: left;
+ width: 100%;
}
-.dark .feed-blob-card {
- box-shadow:
- 6px 6px 24px rgba(0, 0, 0, 0.4),
- -4px -4px 16px rgba(255, 255, 255, 0.03);
+.rsshub-suggestion:hover {
+ color: #f5712c;
+ border-color: #f5712c40;
+ background: #f5712c08;
}
-.sepia .feed-blob-card {
- box-shadow:
- 6px 6px 20px rgba(80, 60, 30, 0.1),
- -4px -4px 16px rgba(255, 245, 220, 0.5);
+.rsshub-logo {
+ flex-shrink: 0;
+ color: #f5712c;
}
-.feed-blob-card.active {
- outline: 2px solid var(--accent);
- outline-offset: -2px;
+.rsshub-suggestion-text {
+ font-family: var(--font-body);
+ letter-spacing: 0.01em;
}
-.feed-blob-card.read {
- opacity: 0.7;
+/* RSSHub badge on feed items in SourcePanel */
+.feed-rsshub-badge {
+ display: inline-flex;
+ align-items: center;
+ margin-left: auto;
+ flex-shrink: 0;
+ color: #f5712c;
+ opacity: 0.6;
}
-.feed-blob-card.read:hover {
+.feed-item-btn:hover .feed-rsshub-badge {
opacity: 1;
}
-/* Animated gradient blob */
-.feed-blob-card__blob {
- position: absolute;
- top: 50%;
- left: 50%;
- width: 120px;
- height: 120px;
- border-radius: 50%;
- filter: blur(14px);
- opacity: 0.85;
- z-index: 0;
+.form-input {
+ padding: var(--space-md) var(--space-lg);
+ border: 1px solid var(--border-default);
+ border-radius: var(--radius-md);
+ background: var(--bg-elevated);
+ color: var(--text-primary);
+ font-family: var(--font-body);
+ font-size: 13px;
+ transition: all var(--duration-fast) var(--ease-out);
}
-.dark .feed-blob-card__blob {
- opacity: 0.7;
+.form-input:focus {
+ outline: none;
+ border-color: var(--accent);
+ background: var(--bg-surface);
}
-.sepia .feed-blob-card__blob {
- opacity: 0.75;
+.form-input::placeholder {
+ color: var(--text-tertiary);
}
-/* Glassy content overlay */
-.feed-blob-card__glass {
- position: absolute;
- inset: 4px;
- background: rgba(255, 255, 255, 0.92);
- backdrop-filter: blur(24px);
- -webkit-backdrop-filter: blur(24px);
- border-radius: 10px;
- outline: 1.5px solid rgba(255, 255, 255, 0.8);
- z-index: 1;
+.source-selector {
display: flex;
- flex-direction: column;
- padding: var(--space-md);
- overflow: hidden;
-}
-
-.dark .feed-blob-card__glass {
- background: rgba(15, 15, 18, 0.82);
- outline-color: rgba(255, 255, 255, 0.06);
-}
-
-.sepia .feed-blob-card__glass {
- background: rgba(247, 240, 222, 0.90);
- outline-color: rgba(210, 195, 160, 0.5);
+ flex-wrap: wrap;
+ gap: var(--space-sm);
}
-/* Meta row */
-.feed-blob-card__meta {
+.source-option {
display: flex;
- justify-content: space-between;
align-items: center;
- margin-bottom: var(--space-sm);
- flex-shrink: 0;
-}
-
-.feed-blob-card__source {
- font-family: var(--font-body);
- font-size: 10px;
- font-weight: 600;
- color: var(--accent-dim);
- letter-spacing: 0.04em;
- text-transform: uppercase;
-}
-
-.feed-blob-card__time {
+ gap: var(--space-sm);
+ padding: var(--space-sm) var(--space-md);
+ border: 1px solid var(--border-default);
+ border-radius: var(--radius-md);
+ background: transparent;
+ color: var(--text-secondary);
font-family: var(--font-body);
- font-size: 10px;
- color: var(--text-tertiary);
+ font-size: 12px;
+ cursor: pointer;
+ transition: all var(--duration-fast) var(--ease-out);
}
-/* Body */
-.feed-blob-card__body {
- flex: 1;
- min-height: 0;
- overflow: hidden;
+.source-option:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
}
-.feed-blob-card__title-row {
- display: flex;
- align-items: flex-start;
- gap: 6px;
- margin-bottom: var(--space-xs);
+.source-option.active {
+ background: var(--accent-glow);
+ border-color: var(--accent);
+ color: var(--accent-text);
}
-.feed-blob-card__unread {
- flex-shrink: 0;
- width: 6px;
- height: 6px;
- border-radius: 50%;
- background: var(--blue);
- margin-top: 5px;
+.source-option-icon {
+ font-size: 14px;
}
-.feed-blob-card__title {
- font-family: var(--font-display);
- font-size: 13px;
- font-weight: 600;
- line-height: 1.3;
- color: var(--text-primary);
- display: -webkit-box;
- -webkit-line-clamp: 3;
- -webkit-box-orient: vertical;
- overflow: hidden;
+.source-option-label {
+ font-weight: 500;
}
-.feed-blob-card:hover .feed-blob-card__title {
- color: var(--accent-text);
+.form-error {
+ padding: var(--space-md);
+ background: rgba(217, 69, 58, 0.1);
+ border: 1px solid rgba(217, 69, 58, 0.3);
+ border-radius: var(--radius-md);
+ color: var(--red);
+ font-size: 12px;
+ font-weight: 500;
}
-.feed-blob-card__excerpt {
- font-family: var(--font-body);
- font-size: 11px;
- line-height: 1.45;
- color: var(--text-secondary);
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- overflow: hidden;
+.modal-actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: var(--space-md);
+ margin-top: var(--space-md);
}
-/* Footer */
-.feed-blob-card__footer {
+.btn-secondary,
+.btn-primary {
display: flex;
- justify-content: space-between;
align-items: center;
- margin-top: auto;
- padding-top: var(--space-xs);
- flex-shrink: 0;
+ gap: var(--space-sm);
+ padding: var(--space-md) var(--space-lg);
+ border-radius: var(--radius-md);
+ font-family: var(--font-body);
+ font-size: 13px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all var(--duration-fast) var(--ease-out);
}
-.feed-blob-card__tags {
- display: flex;
- gap: 4px;
- overflow: hidden;
+.btn-secondary {
+ background: transparent;
+ border: 1px solid var(--border-default);
+ color: var(--text-secondary);
}
-.feed-blob-card__tag {
- font-size: 9px;
- font-weight: 500;
- color: var(--text-tertiary);
+.btn-secondary:hover {
background: var(--bg-hover);
- padding: 1px 6px;
- border-radius: 3px;
- white-space: nowrap;
+ color: var(--text-primary);
}
-.dark .feed-blob-card__tag {
- background: rgba(255, 255, 255, 0.06);
+.btn-primary {
+ background: var(--accent);
+ border: 1px solid var(--accent);
+ color: var(--text-inverse);
}
-.sepia .feed-blob-card__tag {
- background: rgba(180, 160, 120, 0.12);
+.btn-primary:hover {
+ background: var(--accent-dim);
+ border-color: var(--accent-dim);
}
-.feed-blob-card__actions {
- display: flex;
- align-items: center;
- gap: 6px;
+.btn-primary:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
}
-.feed-blob-card__star {
- color: var(--accent);
- font-size: 12px;
+.btn-spinner {
+ width: 14px;
+ height: 14px;
+ border: 2px solid transparent;
+ border-top-color: currentColor;
+ border-radius: 50%;
+ animation: spin 0.6s linear infinite;
}
-.feed-blob-card__readtime {
- font-size: 9px;
- color: var(--text-tertiary);
-}
+/* ─── Feed search dropdown ─── */
-/* ─── READER PANEL (Right) ────────────────────── */
-
-.reader-panel {
- height: 100%;
- display: flex;
- flex-direction: column;
+.feed-search-results {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ right: 0;
+ margin-top: 4px;
+ max-height: 240px;
+ overflow-y: auto;
background: var(--bg-surface);
- border-left: 1px solid var(--border-subtle);
+ border: 1px solid var(--border-default);
+ border-radius: var(--radius-md);
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
+ z-index: 10;
}
-/* Breadcrumb bar */
-.reader-breadcrumb {
- padding: var(--space-sm) var(--space-lg);
+.feed-search-item {
+ display: flex;
+ align-items: center;
+ gap: var(--space-md);
+ width: 100%;
+ padding: var(--space-md);
+ border: none;
border-bottom: 1px solid var(--border-subtle);
- flex-shrink: 0;
-}
-
-.reader-breadcrumb [data-slot="breadcrumb-list"] {
- font-size: 12px;
- gap: var(--space-xs);
- flex-wrap: nowrap;
-}
-
-.reader-breadcrumb [data-slot="breadcrumb-link"] {
- color: var(--text-tertiary);
- text-decoration: none;
+ background: transparent;
cursor: pointer;
- white-space: nowrap;
-}
-
-.reader-breadcrumb [data-slot="breadcrumb-link"]:hover {
- color: var(--accent-text);
+ text-align: left;
+ font-family: var(--font-body);
+ transition: background var(--duration-fast) var(--ease-out);
}
-.reader-breadcrumb [data-slot="breadcrumb-page"] {
- color: var(--text-primary);
- font-weight: 500;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- max-width: 300px;
+.feed-search-item:last-child {
+ border-bottom: none;
}
-.reader-breadcrumb [data-slot="breadcrumb-separator"] {
- color: var(--text-tertiary);
+.feed-search-item:hover {
+ background: var(--bg-hover);
}
-.reader-breadcrumb [data-slot="breadcrumb-separator"] svg {
- width: 12px;
- height: 12px;
+.feed-search-artwork {
+ width: 40px;
+ height: 40px;
+ border-radius: 6px;
+ object-fit: cover;
+ flex-shrink: 0;
}
-/* Empty state */
-.reader-empty {
- align-items: center;
- justify-content: center;
+.feed-search-artwork-placeholder {
+ background: var(--bg-elevated);
+ border: 1px solid var(--border-subtle);
}
-.reader-empty-content {
+.feed-search-info {
+ flex: 1;
+ min-width: 0;
display: flex;
flex-direction: column;
- align-items: center;
- gap: var(--space-lg);
- opacity: 0.5;
+ gap: 2px;
}
-.reader-empty-icon {
- font-size: 48px;
- color: var(--accent);
- opacity: 0.3;
+.feed-search-name {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--text-primary);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
-.reader-empty-text {
- font-family: var(--font-display);
- font-size: 15px;
- font-style: italic;
- color: var(--text-tertiary);
- text-align: center;
- max-width: 240px;
- line-height: 1.5;
+.feed-search-description {
+ font-size: 11px;
+ color: var(--text-secondary);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
-.reader-empty-shortcuts {
- display: flex;
- flex-direction: column;
- gap: var(--space-sm);
- margin-top: var(--space-md);
+.feed-search-meta {
+ flex-shrink: 0;
+ font-size: 11px;
+ color: var(--text-tertiary);
+ white-space: nowrap;
}
-.shortcut-row {
+.feed-search-loading,
+.feed-search-empty {
display: flex;
align-items: center;
+ justify-content: center;
gap: var(--space-sm);
- font-size: 11px;
+ padding: var(--space-lg);
+ font-size: 12px;
color: var(--text-tertiary);
}
-kbd {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- min-width: 22px;
- height: 20px;
- padding: 0 5px;
- font-family: var(--font-body);
- font-size: 10px;
- font-weight: 500;
- color: var(--text-secondary);
- background: var(--bg-elevated);
- border: 1px solid var(--border-default);
- border-radius: 3px;
+/* Settings modal */
+.settings-body {
+ padding: var(--space-md) 0;
}
-/* Toolbar */
-.reader-toolbar {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: var(--space-sm) var(--space-lg);
- border-bottom: 1px solid var(--border-subtle);
- flex-shrink: 0;
- min-height: 44px;
- position: relative;
- z-index: 10;
- background: var(--bg-surface);
+.settings-section {
+ padding: var(--space-md) var(--space-xl);
}
-.reader-toolbar-left,
-.reader-toolbar-right {
- display: flex;
- align-items: center;
- gap: 2px;
+.settings-section-title {
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--text-primary);
+ margin: 0 0 var(--space-xs) 0;
}
-.reader-tool-btn {
+.settings-section-desc {
+ font-size: 12px;
+ color: var(--text-secondary);
+ margin: 0 0 var(--space-lg) 0;
+ line-height: 1.5;
+}
+
+.opml-dropzone {
display: flex;
+ flex-direction: column;
align-items: center;
justify-content: center;
- width: 32px;
- height: 32px;
- border: none;
- border-radius: var(--radius-sm);
- background: transparent;
- color: var(--text-tertiary);
- font-size: 15px;
+ gap: var(--space-sm);
+ padding: var(--space-xl) var(--space-lg);
+ border: 2px dashed var(--border-default);
+ border-radius: var(--radius-lg);
cursor: pointer;
transition: all var(--duration-fast) var(--ease-out);
+ background: transparent;
}
-.reader-tool-btn:hover {
- background: var(--bg-hover);
- color: var(--text-secondary);
-}
-
-.reader-tool-btn.back-btn {
- font-size: 18px;
- width: 36px;
- height: 36px;
- color: var(--text-secondary);
- font-weight: 600;
+.opml-dropzone:hover {
+ border-color: var(--accent);
+ background: var(--accent-glow);
}
-.reader-tool-btn.back-btn:hover {
- background: var(--bg-active);
- color: var(--text-primary);
+.opml-dropzone.dragging {
+ border-color: var(--accent);
+ background: var(--accent-glow);
+ transform: scale(1.01);
}
-.reader-tool-btn.back-feeds-btn {
- font-size: 14px;
- width: 36px;
- height: 36px;
- color: var(--text-secondary);
+.opml-file-input {
+ display: none;
}
-.reader-tool-btn.back-feeds-btn:hover {
- background: var(--bg-active);
- color: var(--accent);
+.opml-dropzone-icon {
+ font-size: 28px;
+ opacity: 0.7;
}
-.reader-tool-btn .starred {
- color: var(--accent);
+.opml-dropzone-text {
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--text-primary);
}
-/* Toolbar divider */
-.reader-toolbar-divider {
- width: 1px;
- height: 18px;
- background: var(--border-default);
- margin: 0 var(--space-sm);
+.opml-dropzone-hint {
+ font-size: 11px;
+ color: var(--text-tertiary);
}
-/* View mode toggle */
-.view-mode-toggle {
+.opml-result {
display: flex;
align-items: center;
- background: var(--bg-root);
+ gap: var(--space-sm);
+ padding: var(--space-md);
+ margin-top: var(--space-md);
+ background: rgba(52, 199, 89, 0.1);
+ border: 1px solid rgba(52, 199, 89, 0.3);
border-radius: var(--radius-md);
- padding: 2px;
- gap: 1px;
+ font-size: 13px;
+ color: var(--text-primary);
}
-.view-mode-btn {
- display: flex;
- align-items: center;
- gap: 4px;
- padding: 4px 10px;
- border: none;
- border-radius: var(--radius-sm);
- background: transparent;
- color: var(--text-tertiary);
- font-family: var(--font-body);
- font-size: 11px;
- font-weight: 500;
- cursor: pointer;
- transition: all var(--duration-fast) var(--ease-out);
- white-space: nowrap;
+.opml-result-icon {
+ color: #34c759;
+ font-weight: 700;
+ font-size: 15px;
}
-.view-mode-btn:hover:not(.disabled) {
- color: var(--text-secondary);
+.opml-result-text strong {
+ font-weight: 600;
}
-.view-mode-btn.active {
- background: var(--bg-elevated);
- color: var(--accent-text);
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
+.opml-result-skipped {
+ color: var(--text-secondary);
+ font-size: 12px;
}
-.view-mode-btn.disabled {
- opacity: 0.35;
- cursor: not-allowed;
+@keyframes spin {
+ to { transform: rotate(360deg); }
}
-.view-mode-icon {
- font-size: 12px;
- line-height: 1;
-}
+/* ─── FEED PANEL (Center) ─────────────────────── */
-.view-mode-label {
- letter-spacing: 0.01em;
+.feed-panel {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ background: var(--bg-root);
}
-/* Reader content */
-.reader-content {
- flex: 1;
- overflow-y: auto;
- padding: var(--space-2xl) var(--space-3xl);
+.feed-panel-header {
+ padding: var(--space-lg) var(--space-lg) var(--space-md);
+ border-bottom: 1px solid var(--border-subtle);
+ flex-shrink: 0;
}
-.reader-article-meta {
+.feed-panel-title-row {
display: flex;
- align-items: center;
- gap: var(--space-sm);
- margin-bottom: var(--space-lg);
+ align-items: baseline;
+ gap: var(--space-md);
+ margin-bottom: var(--space-sm);
}
-.reader-source {
- font-size: 12px;
+.feed-panel-title {
+ font-family: var(--font-display);
+ font-size: 20px;
font-weight: 600;
- color: var(--accent);
- letter-spacing: 0.04em;
- text-transform: uppercase;
-}
-
-.reader-meta-dot {
- color: var(--text-tertiary);
- font-size: 10px;
+ color: var(--text-primary);
+ letter-spacing: -0.01em;
}
-.reader-author {
- font-size: 12px;
- color: var(--text-secondary);
+.feed-panel-unread {
+ font-size: 11px;
font-weight: 500;
+ color: var(--accent-dim);
+ letter-spacing: 0.02em;
}
-.reader-title {
- font-family: var(--font-display);
- font-size: 28px;
- font-weight: 700;
- line-height: 1.2;
- color: var(--text-primary);
- letter-spacing: -0.02em;
- margin-bottom: var(--space-lg);
-}
-
-.reader-info {
+.feed-panel-actions {
display: flex;
align-items: center;
- gap: var(--space-sm);
- margin-bottom: var(--space-md);
+ gap: 2px;
}
-.reader-date {
- font-size: 12px;
+.feed-action-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
+ border: none;
+ border-radius: var(--radius-sm);
+ background: transparent;
color: var(--text-tertiary);
+ font-size: 14px;
+ cursor: pointer;
+ transition: all var(--duration-fast) var(--ease-out);
}
-.reader-readtime {
- font-size: 12px;
- color: var(--text-tertiary);
+.feed-action-btn:hover {
+ background: var(--bg-hover);
+ color: var(--text-secondary);
}
-.reader-comments-count {
- font-size: 12px;
- color: var(--accent-dim);
- font-weight: 500;
+/* Feed list */
+.feed-panel-list {
+ flex: 1;
+ overflow-y: auto;
+ padding: var(--space-sm) var(--space-sm);
}
-.reader-tags {
+/* Empty state */
+.feed-empty {
display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: var(--space-3xl) var(--space-xl);
+ text-align: center;
gap: var(--space-sm);
- margin-bottom: var(--space-xl);
- flex-wrap: wrap;
}
-.reader-tag {
- font-size: 11px;
+.feed-empty-icon {
+ font-size: 32px;
+ color: var(--accent);
+ opacity: 0.3;
+ margin-bottom: var(--space-sm);
+}
+
+.feed-empty-text {
+ font-family: var(--font-display);
+ font-size: 15px;
font-weight: 500;
- color: var(--accent-dim);
- background: var(--accent-glow);
- padding: 2px 10px;
- border-radius: 3px;
- letter-spacing: 0.02em;
+ color: var(--text-secondary);
}
-.reader-divider {
- height: 1px;
- background: linear-gradient(
- 90deg,
- var(--accent) 0%,
- var(--border-subtle) 40%,
- transparent 100%
- );
- margin-bottom: var(--space-2xl);
- opacity: 0.5;
+.feed-empty-hint {
+ font-size: 12px;
+ color: var(--text-tertiary);
+ max-width: 220px;
+ line-height: 1.5;
}
-/* Article body content */
-.reader-body {
- font-size: 14.5px;
- line-height: 1.75;
- color: var(--text-secondary);
+.feed-group {
+ margin-bottom: var(--space-md);
}
-.reader-body h2 {
- font-family: var(--font-display);
- font-size: 20px;
+.feed-group-label {
+ padding: var(--space-sm) var(--space-md);
+ font-size: 10px;
font-weight: 600;
- color: var(--text-primary);
- margin: var(--space-2xl) 0 var(--space-md);
- letter-spacing: -0.01em;
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+ color: var(--text-tertiary);
}
-.reader-body h3 {
- font-family: var(--font-display);
- font-size: 17px;
- font-weight: 600;
- color: var(--text-primary);
- margin: var(--space-xl) 0 var(--space-sm);
+/* Feed card */
+.feed-card {
+ padding: var(--space-md) var(--space-lg);
+ margin: 1px 0;
+ border-radius: var(--radius-md);
+ cursor: pointer;
+ transition: all var(--duration-fast) var(--ease-out);
+ position: relative;
+ border: 1px solid transparent;
}
-.reader-body p {
- margin-bottom: var(--space-lg);
+.feed-card:hover {
+ background: var(--bg-elevated);
}
-.reader-body strong {
- color: var(--text-primary);
- font-weight: 600;
+.feed-card.active {
+ background: var(--bg-elevated);
+ border-color: var(--border-default);
}
-.reader-body em {
- font-style: italic;
- color: var(--text-primary);
+.feed-card.active::after {
+ content: "";
+ position: absolute;
+ left: 0;
+ top: 12px;
+ bottom: 12px;
+ width: 2px;
+ background: var(--accent);
+ border-radius: 1px;
}
-.reader-body a {
- color: var(--accent);
- text-decoration: none;
- border-bottom: 1px solid var(--accent-glow);
- transition: border-color var(--duration-fast) var(--ease-out);
+.feed-card.read .feed-card-title {
+ color: var(--text-secondary);
+ opacity: 0.5;
}
-.reader-body a:hover {
- border-color: var(--accent);
+.feed-card.read .feed-card-excerpt {
+ color: var(--text-tertiary);
}
-.reader-body ul,
-.reader-body ol {
- margin-bottom: var(--space-lg);
- padding-left: var(--space-xl);
+/* Drag-and-drop reorder (favorites / read later) */
+.feed-card[draggable="true"] {
+ cursor: grab;
}
-.reader-body li {
- margin-bottom: var(--space-sm);
+.feed-card[draggable="true"]:active {
+ cursor: grabbing;
}
-.reader-body li::marker {
- color: var(--accent-dim);
+.feed-card.dragging {
+ opacity: 0.4;
}
-.reader-body blockquote {
- margin: var(--space-xl) 0;
- padding: var(--space-lg) var(--space-xl);
- border-left: 2px solid var(--accent);
- background: var(--accent-glow);
- border-radius: 0 var(--radius-md) var(--radius-md) 0;
- font-style: italic;
- color: var(--text-primary);
- font-size: 14px;
- line-height: 1.6;
-}
-
-.reader-body pre {
- margin: var(--space-xl) 0;
- padding: var(--space-lg);
- background: var(--bg-root);
- border: 1px solid var(--border-default);
- border-radius: var(--radius-md);
- overflow-x: auto;
-}
-
-.reader-body code {
- font-family: "JetBrains Mono", "Fira Code", monospace;
- font-size: 13px;
- line-height: 1.6;
+.feed-card.drop-over {
+ border-top: 2px solid var(--accent);
}
-.reader-body p code {
- background: var(--bg-root);
- padding: 1px 6px;
- border-radius: 3px;
- font-size: 12.5px;
- color: var(--accent-text);
+.feed-card-drag-handle {
+ color: var(--text-tertiary);
+ font-size: 12px;
+ cursor: grab;
+ user-select: none;
+ letter-spacing: 1px;
+ opacity: 0.4;
+ transition: opacity 0.15s;
}
-.reader-comments-section {
- margin-top: var(--space-2xl);
+.feed-card:hover .feed-card-drag-handle {
+ opacity: 1;
}
-.reader-comments-header {
+/* Unread dot */
+.feed-card-title-row {
display: flex;
- align-items: center;
+ align-items: flex-start;
gap: var(--space-sm);
- margin-bottom: var(--space-md);
-}
-
-.reader-comments-title {
- font-family: var(--font-display);
- font-size: 20px;
- font-weight: 600;
- color: var(--text-primary);
- letter-spacing: -0.01em;
-}
-
-.reader-comments-badge {
- font-size: 11px;
- font-weight: 600;
- color: var(--accent-dim);
- background: var(--accent-glow);
- border: 1px solid rgba(212, 168, 83, 0.25);
- border-radius: 999px;
- padding: 2px 10px;
}
-.reader-comments-status {
- font-size: 11px;
- color: var(--text-tertiary);
- letter-spacing: 0.02em;
+.feed-card-title-row .feed-card-title {
+ flex: 1;
+ min-width: 0;
}
-.reader-comments-list {
- display: flex;
- flex-direction: column;
- gap: var(--space-md);
+.feed-card-unread-dot {
+ flex-shrink: 0;
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background: var(--blue);
+ margin-top: 7px;
}
-.reader-comment-card {
- border: 1px solid var(--border-subtle);
- background: var(--bg-elevated);
+/* Context menu */
+.feed-context-menu {
+ position: fixed;
+ z-index: 999;
+ min-width: 180px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border-default);
border-radius: var(--radius-md);
- padding: var(--space-md);
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
+ padding: var(--space-xs) 0;
}
-.reader-comment-meta {
+.feed-context-menu-item {
display: flex;
align-items: center;
- gap: var(--space-xs);
- margin-bottom: var(--space-sm);
+ gap: var(--space-sm);
+ width: 100%;
+ padding: var(--space-sm) var(--space-md);
+ border: none;
+ background: transparent;
+ color: var(--text-primary);
+ font-family: var(--font-body);
+ font-size: 12.5px;
+ cursor: pointer;
+ transition: background var(--duration-fast) var(--ease-out);
}
-.reader-comment-author {
- font-size: 12px;
- font-weight: 600;
- color: var(--accent-dim);
+.feed-context-menu-item:hover {
+ background: var(--bg-hover);
}
-.reader-comment-score,
-.reader-comment-time {
- font-size: 11px;
- color: var(--text-tertiary);
+.feed-context-menu-icon {
+ font-size: 10px;
+ color: var(--blue);
+ width: 14px;
+ text-align: center;
}
-.reader-comment-body {
- font-size: 13px;
- line-height: 1.65;
- color: var(--text-secondary);
- white-space: pre-wrap;
+.feed-context-menu-item--danger {
+ color: var(--red);
}
-.reader-comments-error {
- font-size: 12px;
+.feed-context-menu-item--danger .feed-context-menu-icon {
color: var(--red);
- margin-bottom: var(--space-sm);
}
-.reader-comments-empty {
- font-size: 12px;
- color: var(--text-tertiary);
- font-style: italic;
+.feed-context-menu-item--danger:hover {
+ background: rgba(217, 69, 58, 0.1);
}
-.reader-original-link {
- display: inline-flex;
- align-items: center;
- gap: var(--space-xs);
- font-size: 12px;
- font-weight: 500;
- color: var(--accent);
- text-decoration: none;
- padding: var(--space-sm) var(--space-lg);
- border: 1px solid var(--border-default);
- border-radius: var(--radius-md);
- transition: all var(--duration-fast) var(--ease-out);
-}
+/* ── Subfolder styles ── */
-.reader-original-link:hover {
- background: var(--accent-glow);
- border-color: var(--accent-dim);
+.subfolder {
+ margin: 0;
}
-.reader-comments-link {
- display: inline-flex;
+.subfolder-header {
+ display: flex;
align-items: center;
gap: var(--space-xs);
+ width: 100%;
+ padding: 5px var(--space-md) 5px 24px; /* left padding overridden by inline style for nesting */
+ border: none;
+ border-radius: var(--radius-sm);
+ background: transparent;
+ color: var(--text-secondary);
font-family: var(--font-body);
font-size: 12px;
font-weight: 500;
- color: var(--accent-text);
- text-decoration: none;
- padding: var(--space-sm) var(--space-lg);
- border: 1px solid var(--accent-dim);
- border-radius: var(--radius-md);
- background: var(--accent-glow);
cursor: pointer;
transition: all var(--duration-fast) var(--ease-out);
}
-.reader-comments-link:hover {
- background: var(--accent);
- border-color: var(--accent);
- color: var(--text-inverse);
-}
-
-/* Webview link in reader footer */
-.reader-webview-link {
- display: inline-flex;
- align-items: center;
- gap: var(--space-sm);
- font-family: var(--font-body);
- font-size: 12px;
- font-weight: 500;
+.subfolder-header:hover {
+ background: var(--bg-hover);
color: var(--text-primary);
- background: var(--accent-glow);
- border: 1px solid var(--accent-dim);
- padding: var(--space-sm) var(--space-lg);
- border-radius: var(--radius-md);
- cursor: pointer;
- transition: all var(--duration-fast) var(--ease-out);
-}
-
-.reader-webview-link:hover {
- background: var(--accent);
- color: var(--text-inverse);
- border-color: var(--accent);
-}
-
-.webview-link-icon {
- font-size: 14px;
-}
-
-.reader-footer {
- margin-top: var(--space-3xl);
- padding-top: var(--space-xl);
- border-top: 1px solid var(--border-subtle);
- display: flex;
- align-items: center;
- gap: var(--space-md);
- flex-wrap: wrap;
}
-/* ─── WEBVIEW ─────────────────────────────────── */
-
-/* URL bar */
-.webview-urlbar {
- flex-shrink: 0;
- border-bottom: 1px solid var(--border-subtle);
- overflow: hidden;
+.subfolder-chevron {
+ font-size: 12px;
+ width: 12px;
+ text-align: center;
+ transition: transform var(--duration-fast) var(--ease-out);
+ color: var(--text-tertiary);
+ line-height: 1;
}
-.urlbar-inner {
- display: flex;
- align-items: center;
- gap: var(--space-sm);
- padding: var(--space-sm) var(--space-lg);
- background: var(--bg-root);
+.subfolder-chevron.expanded {
+ transform: rotate(90deg);
}
-.urlbar-lock {
+.subfolder-icon {
font-size: 11px;
- color: var(--accent-dim);
+ width: 14px;
+ text-align: center;
flex-shrink: 0;
}
-.urlbar-url {
+.subfolder-name {
flex: 1;
- font-family: var(--font-body);
- font-size: 11.5px;
- color: var(--text-secondary);
+ text-align: left;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
- letter-spacing: 0.01em;
}
-.urlbar-open {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 24px;
- height: 24px;
- border: none;
- border-radius: var(--radius-sm);
- background: transparent;
+.subfolder-count {
+ font-size: 10px;
color: var(--text-tertiary);
- font-size: 13px;
- cursor: pointer;
- flex-shrink: 0;
- transition: all var(--duration-fast) var(--ease-out);
+ min-width: 14px;
+ text-align: center;
}
-.urlbar-open:hover {
- background: var(--bg-hover);
- color: var(--text-secondary);
-}
+/* Folder inline input */
-/* Webview container */
-.webview-container {
- flex: 1;
- position: relative;
- overflow: hidden;
- background: #fff;
+.folder-inline-input-wrapper {
+ padding: 2px var(--space-md) 2px 24px; /* left padding overridden by inline style for nesting */
}
-.webview-iframe {
+.folder-inline-input {
width: 100%;
- height: 100%;
- border: none;
- background: #fff;
-}
-
-/* Loading state */
-.webview-loading {
- position: absolute;
- inset: 0;
- z-index: 5;
- display: flex;
- flex-direction: column;
+ padding: 4px 8px;
+ border: 1px solid var(--accent);
+ border-radius: var(--radius-sm);
background: var(--bg-surface);
+ color: var(--text-primary);
+ font-family: var(--font-body);
+ font-size: 12px;
+ outline: none;
}
-.webview-loading-bar {
- height: 2px;
- background: linear-gradient(90deg, transparent, var(--accent), transparent);
- animation: webview-loading-slide 1.5s ease-in-out infinite;
-}
-
-@keyframes webview-loading-slide {
- 0% {
- transform: translateX(-100%);
- }
- 100% {
- transform: translateX(100%);
- }
-}
-
-.webview-loading-content {
- flex: 1;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: var(--space-lg);
+.folder-inline-input::placeholder {
+ color: var(--text-tertiary);
}
-.webview-loading-spinner {
- width: 24px;
- height: 24px;
- border: 2px solid var(--border-default);
- border-top-color: var(--accent);
- border-radius: 50%;
- animation: webview-spin 0.8s linear infinite;
-}
+/* Context submenu (move to) */
-@keyframes webview-spin {
- to {
- transform: rotate(360deg);
- }
+.feed-context-menu-submenu-wrapper {
+ position: relative;
}
-.webview-loading-text {
+.feed-context-menu-arrow {
+ margin-left: auto;
font-size: 12px;
color: var(--text-tertiary);
- letter-spacing: 0.02em;
}
-/* Error state */
-.webview-error {
+.context-submenu {
position: absolute;
- inset: 0;
- z-index: 5;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: var(--space-md);
+ left: 100%;
+ top: 0;
+ min-width: 150px;
background: var(--bg-surface);
+ border: 1px solid var(--border-default);
+ border-radius: var(--radius-md);
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
+ padding: var(--space-xs) 0;
+ z-index: 1000;
}
-.webview-error-icon {
- font-size: 40px;
- color: var(--text-tertiary);
- opacity: 0.5;
+/* ── Drag-and-drop ── */
+
+.feed-item-btn[draggable="true"] {
+ cursor: grab;
}
-.webview-error-title {
- font-family: var(--font-display);
- font-size: 18px;
- font-weight: 600;
- color: var(--text-primary);
+.feed-item-btn[draggable="true"]:active {
+ cursor: grabbing;
}
-.webview-error-text {
- font-size: 13px;
- color: var(--text-secondary);
- text-align: center;
- max-width: 280px;
- line-height: 1.5;
+.feed-item-btn.dragging {
+ opacity: 0.4;
}
-.webview-error-actions {
- display: flex;
- gap: var(--space-sm);
- margin-top: var(--space-md);
+.feed-item-btn.drop-before {
+ box-shadow: 0 -2px 0 0 var(--accent);
}
-.webview-error-btn {
- padding: var(--space-sm) var(--space-lg);
- border: 1px solid var(--border-default);
- border-radius: var(--radius-md);
- background: transparent;
- color: var(--text-secondary);
- font-family: var(--font-body);
- font-size: 12px;
- font-weight: 500;
- cursor: pointer;
- transition: all var(--duration-fast) var(--ease-out);
+.feed-item-btn.drop-after {
+ box-shadow: 0 2px 0 0 var(--accent);
}
-.webview-error-btn:hover {
- background: var(--bg-hover);
- color: var(--text-primary);
- border-color: var(--border-strong);
+.category-root-zone {
+ min-height: 4px;
+ border-radius: var(--radius-sm);
+ transition: background var(--duration-fast) var(--ease-out),
+ outline var(--duration-fast) var(--ease-out);
}
-.webview-error-btn.primary {
+.category-root-zone.drop-over {
+ background: var(--accent-glow);
+ outline: 1.5px dashed var(--accent);
+ outline-offset: -1.5px;
+}
+
+.subfolder-header.drop-over {
background: var(--accent-glow);
- border-color: var(--accent-dim);
color: var(--accent-text);
+ outline: 1.5px dashed var(--accent);
+ outline-offset: -1.5px;
}
-.webview-error-btn.primary:hover {
- background: var(--accent);
- color: var(--text-inverse);
+.subfolder.drop-over > .subfolder-feeds {
+ background: var(--accent-glow);
+ border-radius: var(--radius-sm);
}
-/* Floating action bar */
-.webview-floating-bar {
- position: absolute;
- bottom: var(--space-lg);
- left: 50%;
- transform: translateX(-50%);
- z-index: 6;
+.feed-card-meta {
display: flex;
align-items: center;
- gap: var(--space-sm);
- padding: var(--space-sm) var(--space-md);
- background: var(--bg-elevated);
- border: 1px solid var(--border-default);
- border-radius: var(--radius-lg);
- box-shadow: 0 4px 24px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(0, 0, 0, 0.2);
- backdrop-filter: blur(12px);
+ gap: var(--space-xs);
+ margin-bottom: var(--space-xs);
}
-.webview-floating-text {
+.feed-card-source {
font-size: 11px;
+ font-weight: 600;
+ color: var(--accent-dim);
+ letter-spacing: 0.02em;
+}
+
+.feed-card-dot {
color: var(--text-tertiary);
- white-space: nowrap;
- padding: 0 var(--space-xs);
+ font-size: 10px;
}
-.webview-floating-btn {
- padding: 4px 12px;
- border: none;
- border-radius: var(--radius-sm);
- background: var(--accent);
- color: var(--text-inverse);
- font-family: var(--font-body);
+.feed-card-time {
font-size: 11px;
- font-weight: 600;
- cursor: pointer;
- white-space: nowrap;
- transition: all var(--duration-fast) var(--ease-out);
+ color: var(--text-tertiary);
}
-.webview-floating-btn:hover {
- background: var(--accent-text);
+.feed-card-star {
+ color: var(--accent);
+ font-size: 11px;
+ margin-left: auto;
}
-.webview-floating-btn.secondary {
- background: transparent;
- color: var(--text-secondary);
- border: 1px solid var(--border-default);
+.feed-card-bookmark {
+ background: none;
+ border: none;
+ cursor: pointer;
+ font-size: 13px;
+ padding: 0 2px;
+ line-height: 1;
+ opacity: 0;
+ transition: opacity var(--duration-fast) var(--ease-out);
+ margin-left: auto;
}
-.webview-floating-btn.secondary:hover {
- background: var(--bg-hover);
- color: var(--text-primary);
- border-color: var(--border-strong);
+.feed-card-star + .feed-card-bookmark {
+ margin-left: 0;
}
-/* ─── AI SUMMARY ─────────────────────────────── */
+.feed-card:hover .feed-card-bookmark,
+.feed-card-bookmark.active {
+ opacity: 1;
+}
-.reader-summary {
- margin-bottom: var(--space-xl);
- border: 1px solid var(--accent-dim);
- border-radius: var(--radius-md);
- background: var(--accent-glow);
- overflow: hidden;
+.feed-card-bookmark:hover {
+ transform: scale(1.15);
}
-.reader-summary-header {
- display: flex;
- align-items: center;
- gap: var(--space-sm);
- width: 100%;
- padding: var(--space-md) var(--space-lg);
- border: none;
- background: transparent;
- cursor: pointer;
- font-family: var(--font-body);
- transition: background var(--duration-fast) var(--ease-out);
-}
-
-.reader-summary-header:hover {
- background: rgba(212, 168, 83, 0.08);
-}
-
-.reader-summary-icon {
- font-size: 14px;
- color: var(--accent);
+.feed-card-title {
+ font-family: var(--font-display);
+ font-size: 14.5px;
+ font-weight: 500;
+ line-height: 1.35;
+ color: var(--text-primary);
+ margin-bottom: var(--space-xs);
+ transition: color var(--duration-fast) var(--ease-out);
}
-.reader-summary-title {
- font-size: 12px;
- font-weight: 600;
+.feed-card:hover .feed-card-title {
color: var(--accent-text);
- letter-spacing: 0.04em;
- text-transform: uppercase;
- flex: 1;
- text-align: left;
-}
-
-.reader-summary-chevron {
- font-size: 16px;
- color: var(--accent-dim);
- transition: transform var(--duration-fast) var(--ease-out);
- line-height: 1;
-}
-
-.reader-summary-chevron.open {
- transform: rotate(90deg);
}
-.reader-summary-content {
- padding: 0 var(--space-lg) var(--space-lg);
- font-size: 13.5px;
- line-height: 1.7;
- color: var(--text-primary);
- white-space: pre-wrap;
+.feed-card-excerpt {
+ font-size: 12px;
+ line-height: 1.5;
+ color: var(--text-secondary);
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ margin-bottom: var(--space-sm);
}
-.reader-summary-loading {
- padding: var(--space-sm) var(--space-lg) var(--space-lg);
+.feed-card-footer {
display: flex;
- flex-direction: column;
+ align-items: center;
gap: var(--space-sm);
}
-.reader-summary-pulse {
- height: 12px;
- border-radius: 6px;
- background: var(--border-default);
- animation: summary-pulse 1.2s ease-in-out infinite;
-}
-
-.reader-summary-pulse.short {
- width: 60%;
+.feed-card-tag {
+ font-size: 10px;
+ font-weight: 500;
+ color: var(--text-tertiary);
+ background: var(--bg-hover);
+ padding: 1px 7px;
+ border-radius: 3px;
+ letter-spacing: 0.02em;
}
-@keyframes summary-pulse {
- 0%, 100% { opacity: 0.3; }
- 50% { opacity: 0.7; }
+.feed-card-readtime {
+ font-size: 10px;
+ color: var(--text-tertiary);
+ margin-left: auto;
}
-.reader-summary-error {
- padding: 0 var(--space-lg) var(--space-lg);
- font-size: 12px;
- color: var(--red);
+.feed-card-comments {
+ font-size: 10px;
font-weight: 500;
-}
-
-/* ── Feed Digest IA ── */
-.feed-digest {
- margin: 0 var(--space-md) var(--space-sm);
- border: 1px solid var(--accent-dim);
- border-radius: var(--radius-md);
+ color: var(--accent-dim);
background: var(--accent-glow);
- overflow: hidden;
+ padding: 1px 7px;
+ border-radius: 3px;
+ letter-spacing: 0.02em;
}
-.feed-digest-header {
- display: flex;
- align-items: center;
- gap: var(--space-sm);
- width: 100%;
- padding: var(--space-md) var(--space-lg);
- border: none;
- background: transparent;
- cursor: pointer;
- font-family: var(--font-body);
- transition: background var(--duration-fast) var(--ease-out);
-}
+/* ─── Compact mode ─── */
-.feed-digest-header:hover {
- background: rgba(212, 168, 83, 0.08);
+.feed-panel.compact .feed-card {
+ padding: var(--space-sm) var(--space-lg);
+ margin: 0;
}
-.feed-digest-icon {
- font-size: 14px;
- color: var(--accent);
+.feed-panel.compact .feed-card-meta {
+ margin-bottom: 2px;
}
-.feed-digest-title {
- font-size: 12px;
- font-weight: 600;
- color: var(--accent-text);
- letter-spacing: 0.04em;
- text-transform: uppercase;
- flex: 1;
- text-align: left;
+.feed-panel.compact .feed-card-title {
+ font-size: 13px;
+ margin-bottom: 0;
}
-.feed-digest-chevron {
- font-size: 16px;
- color: var(--accent-dim);
- transition: transform var(--duration-fast) var(--ease-out);
- line-height: 1;
+.feed-panel.compact .feed-card-unread-dot {
+ width: 5px;
+ height: 5px;
+ margin-top: 6px;
}
-.feed-digest-chevron.open {
- transform: rotate(90deg);
+.feed-panel.compact .feed-group {
+ margin-bottom: var(--space-sm);
}
-.feed-digest-content {
- padding: 0 var(--space-lg) var(--space-lg);
- font-size: 13.5px;
- line-height: 1.7;
- color: var(--text-primary);
- white-space: pre-wrap;
+.feed-action-btn.active {
+ color: var(--accent-text);
}
-.feed-digest-loading {
- padding: var(--space-sm) var(--space-lg) var(--space-lg);
+/* ─── Feed Panel Pagination (MorphingPageDots) ─── */
+
+.feed-panel-pagination {
+ flex-shrink: 0;
+ border-top: 1px solid var(--border-subtle);
display: flex;
flex-direction: column;
- gap: var(--space-sm);
+ align-items: center;
+ padding: var(--space-xs) var(--space-md);
}
-.feed-digest-pulse {
- height: 12px;
- border-radius: 6px;
- background: var(--border-default);
- animation: summary-pulse 1.2s ease-in-out infinite;
+.feed-panel-pagination > div {
+ padding-top: var(--space-xs);
+ padding-bottom: var(--space-xs);
+ gap: var(--space-md);
}
-.feed-digest-pulse.short {
- width: 60%;
+.feed-panel-pagination svg {
+ width: 16px;
+ height: 16px;
}
-.feed-digest-error {
- padding: 0 var(--space-lg) var(--space-lg);
- font-size: 12px;
- color: var(--red);
- font-weight: 500;
+.feed-panel-pagination__info {
+ font-family: var(--font-body);
+ font-size: 10px;
+ color: var(--text-tertiary);
+ letter-spacing: 0.03em;
}
-.reader-tool-btn.summarize {
- width: auto;
- padding: 0 10px;
- gap: 6px;
- font-size: 12px;
- font-weight: 500;
- color: var(--accent);
- background: var(--accent-glow, rgba(212, 168, 83, 0.08));
- border: 1px solid color-mix(in srgb, var(--accent) 25%, transparent);
- border-radius: var(--radius-full, 999px);
-}
+/* ─── Cards Grid View (Gradient Blob) ─────────── */
-.reader-tool-btn.summarize:hover {
- color: var(--bg-surface);
- background: var(--accent);
- border-color: var(--accent);
+.feed-cards-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+ gap: var(--space-md);
+ padding: var(--space-md);
}
-.reader-tool-btn.summarize .summarize-label {
- font-size: 12px;
- letter-spacing: 0.01em;
+.feed-blob-card {
+ position: relative;
+ border-radius: 14px;
+ overflow: hidden;
+ cursor: pointer;
+ height: 220px;
+ box-shadow:
+ 6px 6px 20px rgba(0, 0, 0, 0.06),
+ -4px -4px 16px rgba(255, 255, 255, 0.6);
}
-.reader-tool-btn.summarize.loading {
- cursor: wait;
- opacity: 0.7;
+.dark .feed-blob-card {
+ box-shadow:
+ 6px 6px 24px rgba(0, 0, 0, 0.4),
+ -4px -4px 16px rgba(255, 255, 255, 0.03);
}
-/* TTS button */
-.reader-tool-btn.tts {
- width: auto;
- padding: 0 10px;
- font-size: 12px;
- font-weight: 500;
- color: var(--text-tertiary);
- background: transparent;
- border: 1px solid var(--border-default);
- border-radius: var(--radius-full, 999px);
+.feed-blob-card.active {
+ outline: 2px solid var(--accent);
+ outline-offset: -2px;
}
-.reader-tool-btn.tts:hover {
- color: var(--accent);
- border-color: var(--accent-dim);
- background: var(--accent-glow);
+.feed-blob-card.read {
+ opacity: 0.7;
}
-.reader-tool-btn.tts.active {
- color: var(--bg-surface);
- background: var(--accent);
- border-color: var(--accent);
+.feed-blob-card.read:hover {
+ opacity: 1;
}
-.reader-tool-btn.tts-stop {
- width: 28px;
- height: 28px;
- font-size: 10px;
- color: var(--text-tertiary);
- border: 1px solid var(--border-default);
- border-radius: var(--radius-full, 999px);
+/* Animated gradient blob */
+.feed-blob-card__blob {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 120px;
+ height: 120px;
+ border-radius: 50%;
+ filter: blur(14px);
+ opacity: 0.85;
+ z-index: 0;
}
-.reader-tool-btn.tts-stop:hover {
- color: var(--red);
- border-color: var(--red);
- background: rgba(217, 69, 58, 0.1);
+.dark .feed-blob-card__blob {
+ opacity: 0.7;
}
-/* ─── FULL CONTENT EXTRACTION ────────────────── */
-
-.reader-fullcontent-banner {
+/* Glassy content overlay */
+.feed-blob-card__glass {
+ position: absolute;
+ inset: 4px;
+ background: rgba(255, 255, 255, 0.92);
+ backdrop-filter: blur(24px);
+ -webkit-backdrop-filter: blur(24px);
+ border-radius: 10px;
+ outline: 1.5px solid rgba(255, 255, 255, 0.8);
+ z-index: 1;
display: flex;
- align-items: center;
- gap: var(--space-sm);
- padding: var(--space-md) var(--space-lg);
- margin-bottom: var(--space-lg);
- border-radius: var(--radius-md);
- font-size: 13px;
- color: var(--text-secondary);
-}
-
-.reader-fullcontent-banner.loading {
- background: var(--accent-glow);
- border: 1px solid var(--accent-dim);
- color: var(--accent-text);
+ flex-direction: column;
+ padding: var(--space-md);
+ overflow: hidden;
}
-.reader-fullcontent-banner.loading .btn-spinner {
- width: 14px;
- height: 14px;
- flex-shrink: 0;
+.dark .feed-blob-card__glass {
+ background: rgba(15, 15, 18, 0.82);
+ outline-color: rgba(255, 255, 255, 0.06);
}
-.reader-fullcontent-banner.error {
- background: rgba(220, 80, 80, 0.08);
- border: 1px solid rgba(220, 80, 80, 0.2);
- color: var(--text-secondary);
+/* Meta row */
+.feed-blob-card__meta {
+ display: flex;
justify-content: space-between;
+ align-items: center;
+ margin-bottom: var(--space-sm);
+ flex-shrink: 0;
}
-.reader-fullcontent-btn {
- display: inline-flex;
- align-items: center;
- gap: var(--space-xs);
- padding: var(--space-xs) var(--space-md);
- border: 1px solid var(--border-default);
- border-radius: var(--radius-sm);
- background: var(--bg-surface);
- color: var(--text-secondary);
- font-size: 12px;
+.feed-blob-card__source {
font-family: var(--font-body);
- cursor: pointer;
- transition: all var(--duration-fast) var(--ease-out);
- white-space: nowrap;
+ font-size: 10px;
+ font-weight: 600;
+ color: var(--accent-dim);
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
}
-.reader-fullcontent-btn:hover {
- background: var(--bg-elevated);
- border-color: var(--border-strong);
- color: var(--text-primary);
+.feed-blob-card__time {
+ font-family: var(--font-body);
+ font-size: 10px;
+ color: var(--text-tertiary);
}
-.reader-fullcontent-fetch {
- margin-top: var(--space-xl);
- padding-top: var(--space-lg);
- border-top: 1px solid var(--border-subtle);
- text-align: center;
+/* Body */
+.feed-blob-card__body {
+ flex: 1;
+ min-height: 0;
+ overflow: hidden;
}
-/* ─── SETTINGS FORMAT TOGGLE ─────────────────── */
-
-.settings-format-toggle {
+.feed-blob-card__title-row {
display: flex;
- gap: var(--space-sm);
- margin-bottom: var(--space-md);
+ align-items: flex-start;
+ gap: 6px;
+ margin-bottom: var(--space-xs);
}
-.format-option {
- display: flex;
- align-items: center;
- gap: var(--space-sm);
- flex: 1;
- padding: var(--space-md);
- border: 1px solid var(--border-default);
- border-radius: var(--radius-md);
- background: transparent;
- color: var(--text-secondary);
- font-family: var(--font-body);
- font-size: 13px;
- font-weight: 500;
- cursor: pointer;
- transition: all var(--duration-fast) var(--ease-out);
+.feed-blob-card__unread {
+ flex-shrink: 0;
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background: var(--blue);
+ margin-top: 5px;
}
-.format-option:hover {
- background: var(--bg-hover);
+.feed-blob-card__title {
+ font-family: var(--font-display);
+ font-size: 13px;
+ font-weight: 600;
+ line-height: 1.3;
color: var(--text-primary);
+ display: -webkit-box;
+ -webkit-line-clamp: 3;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
}
-.format-option.active {
- background: var(--accent-glow);
- border-color: var(--accent);
+.feed-blob-card:hover .feed-blob-card__title {
color: var(--accent-text);
}
-.format-option-icon {
- font-size: 16px;
- width: 20px;
- text-align: center;
+.feed-blob-card__excerpt {
+ font-family: var(--font-body);
+ font-size: 11px;
+ line-height: 1.45;
+ color: var(--text-secondary);
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
}
-.settings-api-status {
+/* Footer */
+.feed-blob-card__footer {
display: flex;
+ justify-content: space-between;
align-items: center;
- gap: var(--space-sm);
- padding: var(--space-sm) 0;
-}
-
-.api-status-dot {
- width: 8px;
- height: 8px;
- border-radius: 50%;
+ margin-top: auto;
+ padding-top: var(--space-xs);
flex-shrink: 0;
}
-.api-status-dot.configured {
- background: var(--green);
-}
-
-.api-status-dot.missing {
- background: var(--red);
+.feed-blob-card__tags {
+ display: flex;
+ gap: 4px;
+ overflow: hidden;
}
-.api-status-text {
- font-size: 12px;
- color: var(--text-secondary);
+.feed-blob-card__tag {
+ font-size: 9px;
+ font-weight: 500;
+ color: var(--text-tertiary);
+ background: var(--bg-hover);
+ padding: 1px 6px;
+ border-radius: 3px;
+ white-space: nowrap;
}
-.settings-label {
- display: block;
- font-size: 12px;
- font-weight: 600;
- color: var(--text-secondary);
- text-transform: uppercase;
- letter-spacing: 0.04em;
- margin-bottom: var(--space-sm);
+.dark .feed-blob-card__tag {
+ background: rgba(255, 255, 255, 0.06);
}
-.settings-ollama-status {
+.feed-blob-card__actions {
display: flex;
align-items: center;
- gap: var(--space-sm);
- padding: var(--space-sm) var(--space-md);
- margin-bottom: var(--space-md);
- border-radius: var(--radius-md);
- background: var(--bg-secondary);
- font-size: 12px;
- color: var(--text-secondary);
- flex-wrap: wrap;
+ gap: 6px;
}
-.settings-ollama-status code {
- font-family: var(--font-mono, monospace);
- font-size: 11px;
- padding: 1px 5px;
- border-radius: var(--radius-sm);
- background: var(--bg-hover);
- color: var(--text-primary);
+.feed-blob-card__star {
+ color: var(--accent);
+ font-size: 12px;
}
-.ollama-status-dot {
- width: 8px;
- height: 8px;
- border-radius: 50%;
- flex-shrink: 0;
+.feed-blob-card__readtime {
+ font-size: 9px;
+ color: var(--text-tertiary);
}
-.ollama-status-dot.connected {
- background: var(--green);
+/* ─── READER PANEL (Right) ────────────────────── */
+
+.reader-panel {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ background: var(--bg-surface);
+ border-left: 1px solid var(--border-subtle);
}
-.ollama-status-dot.disconnected {
- background: var(--red);
+/* Breadcrumb bar */
+.reader-breadcrumb {
+ padding: var(--space-sm) var(--space-lg);
+ border-bottom: 1px solid var(--border-subtle);
+ flex-shrink: 0;
}
-.ollama-status-text {
+.reader-breadcrumb [data-slot="breadcrumb-list"] {
font-size: 12px;
- color: var(--text-secondary);
+ gap: var(--space-xs);
+ flex-wrap: nowrap;
}
-.ollama-model-select {
- margin-left: auto;
- padding: 4px 8px;
- border: 1px solid var(--border-default);
- border-radius: var(--radius-sm);
- background: var(--bg-primary);
- color: var(--text-primary);
- font-family: var(--font-body);
- font-size: 12px;
+.reader-breadcrumb [data-slot="breadcrumb-link"] {
+ color: var(--text-tertiary);
+ text-decoration: none;
cursor: pointer;
- max-width: 200px;
-}
-
-.ollama-model-select:focus {
- outline: none;
- border-color: var(--accent);
-}
-
-.settings-ollama-block {
- margin-bottom: var(--space-md);
+ white-space: nowrap;
}
-.ollama-setup-card {
- padding: var(--space-md);
- border-radius: var(--radius-md);
- background: var(--bg-secondary);
- margin-bottom: var(--space-sm);
+.reader-breadcrumb [data-slot="breadcrumb-link"]:hover {
+ color: var(--accent-text);
}
-.ollama-setup-text {
- font-size: 12px;
- color: var(--text-secondary);
- margin: var(--space-sm) 0;
- line-height: 1.5;
+.reader-breadcrumb [data-slot="breadcrumb-page"] {
+ color: var(--text-primary);
+ font-weight: 500;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ max-width: 300px;
}
-.ollama-setup-actions {
- display: flex;
- gap: var(--space-sm);
- margin-top: var(--space-md);
+.reader-breadcrumb [data-slot="breadcrumb-separator"] {
+ color: var(--text-tertiary);
}
-.ollama-setup-btn {
- padding: 6px 14px;
- border: 1px solid var(--accent);
- border-radius: var(--radius-md);
- background: var(--accent);
- color: var(--accent-contrast, #fff);
- font-family: var(--font-body);
- font-size: 12px;
- font-weight: 600;
- cursor: pointer;
- transition: all var(--duration-fast) var(--ease-out);
+.reader-breadcrumb [data-slot="breadcrumb-separator"] svg {
+ width: 12px;
+ height: 12px;
}
-.ollama-setup-btn:hover {
- opacity: 0.9;
+/* Empty state */
+.reader-empty {
+ align-items: center;
+ justify-content: center;
}
-.ollama-setup-btn:disabled {
+.reader-empty-content {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: var(--space-lg);
opacity: 0.5;
- cursor: not-allowed;
-}
-
-.ollama-setup-btn.secondary {
- background: transparent;
- color: var(--text-secondary);
- border-color: var(--border-default);
}
-.ollama-setup-btn.secondary:hover {
- background: var(--bg-hover);
- color: var(--text-primary);
+.reader-empty-icon {
+ font-size: 48px;
+ color: var(--accent);
+ opacity: 0.3;
}
-.ollama-setup-hint {
- font-size: 11px;
+.reader-empty-text {
+ font-family: var(--font-display);
+ font-size: 15px;
+ font-style: italic;
color: var(--text-tertiary);
- margin-top: var(--space-sm);
+ text-align: center;
+ max-width: 240px;
line-height: 1.5;
}
-.ollama-setup-hint code {
- font-family: var(--font-mono, monospace);
- font-size: 11px;
- padding: 1px 5px;
- border-radius: var(--radius-sm);
- background: var(--bg-hover);
- color: var(--text-primary);
+.reader-empty-shortcuts {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-sm);
+ margin-top: var(--space-md);
}
-.ollama-pull-progress {
+.shortcut-row {
display: flex;
align-items: center;
gap: var(--space-sm);
- padding: var(--space-sm) var(--space-md);
- margin-top: var(--space-sm);
- border-radius: var(--radius-md);
- background: var(--bg-secondary);
- font-size: 12px;
+ font-size: 11px;
+ color: var(--text-tertiary);
+}
+
+kbd {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 22px;
+ height: 20px;
+ padding: 0 5px;
+ font-family: var(--font-body);
+ font-size: 10px;
+ font-weight: 500;
color: var(--text-secondary);
+ background: var(--bg-elevated);
+ border: 1px solid var(--border-default);
+ border-radius: 3px;
}
-.ollama-pull-status {
+/* Toolbar */
+.reader-toolbar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: var(--space-sm) var(--space-lg);
+ border-bottom: 1px solid var(--border-subtle);
flex-shrink: 0;
- max-width: 160px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
+ min-height: 44px;
+ position: relative;
+ z-index: 10;
+ background: var(--bg-surface);
}
-.ollama-pull-bar {
- flex: 1;
- height: 6px;
- background: var(--bg-hover);
- border-radius: 3px;
- overflow: hidden;
+.reader-toolbar-left,
+.reader-toolbar-right {
+ display: flex;
+ align-items: center;
+ gap: 2px;
}
-.ollama-pull-bar-fill {
- height: 100%;
- background: var(--accent);
- border-radius: 3px;
- transition: width 0.3s ease-out;
+.reader-tool-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ border: none;
+ border-radius: var(--radius-sm);
+ background: transparent;
+ color: var(--text-tertiary);
+ font-size: 15px;
+ cursor: pointer;
+ transition: all var(--duration-fast) var(--ease-out);
}
-.ollama-pull-percent {
- flex-shrink: 0;
- font-variant-numeric: tabular-nums;
- min-width: 36px;
- text-align: right;
+.reader-tool-btn:hover {
+ background: var(--bg-hover);
+ color: var(--text-secondary);
}
-/* ─── NOISE OVERLAY (subtle texture) ──────────── */
-
-.app::before {
- content: "";
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- pointer-events: none;
- z-index: 1000;
- opacity: 0.015;
- background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
- background-repeat: repeat;
- background-size: 256px 256px;
+.reader-tool-btn.back-btn {
+ font-size: 18px;
+ width: 36px;
+ height: 36px;
+ color: var(--text-secondary);
+ font-weight: 600;
}
-/* ─── HIGHLIGHTS ──────────────────────────────── */
-
-mark.highlight {
- border-radius: 2px;
- padding: 1px 0;
- cursor: pointer;
+.reader-tool-btn.back-btn:hover {
+ background: var(--bg-active);
+ color: var(--text-primary);
}
-mark.highlight-yellow { background: rgba(250, 204, 21, 0.35); }
-mark.highlight-green { background: rgba(74, 222, 128, 0.30); }
-mark.highlight-blue { background: rgba(96, 165, 250, 0.30); }
-mark.highlight-pink { background: rgba(244, 114, 182, 0.30); }
-mark.highlight-orange { background: rgba(251, 146, 60, 0.35); }
-
-.dark mark.highlight-yellow { background: rgba(250, 204, 21, 0.30); }
-.dark mark.highlight-green { background: rgba(74, 222, 128, 0.25); }
-.dark mark.highlight-blue { background: rgba(96, 165, 250, 0.25); }
-.dark mark.highlight-pink { background: rgba(244, 114, 182, 0.25); }
-.dark mark.highlight-orange { background: rgba(251, 146, 60, 0.30); }
-
-.sepia mark.highlight-yellow { background: rgba(250, 204, 21, 0.30); }
-.sepia mark.highlight-green { background: rgba(74, 222, 128, 0.25); }
-.sepia mark.highlight-blue { background: rgba(96, 165, 250, 0.25); }
-.sepia mark.highlight-pink { background: rgba(244, 114, 182, 0.25); }
-.sepia mark.highlight-orange { background: rgba(251, 146, 60, 0.25); }
+.reader-tool-btn.back-feeds-btn {
+ font-size: 14px;
+ width: 36px;
+ height: 36px;
+ color: var(--text-secondary);
+}
-@keyframes highlight-pulse {
- 0%, 100% { filter: brightness(1); }
- 50% { filter: brightness(1.4); }
+.reader-tool-btn.back-feeds-btn:hover {
+ background: var(--bg-active);
+ color: var(--accent);
}
-mark.highlight-pulse {
- animation: highlight-pulse 0.75s ease-in-out 2;
+.reader-tool-btn .starred {
+ color: var(--accent);
}
-/* Color picker */
-.highlight-color-picker {
- position: fixed;
- z-index: 999;
+/* Toolbar divider */
+.reader-toolbar-divider {
+ width: 1px;
+ height: 18px;
+ background: var(--border-default);
+ margin: 0 var(--space-sm);
+}
+
+/* View mode toggle */
+.view-mode-toggle {
display: flex;
align-items: center;
- gap: 6px;
- padding: 6px 10px;
- background: var(--bg-surface);
- border: 1px solid var(--border-default);
- border-radius: var(--radius-lg);
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
+ background: var(--bg-root);
+ border-radius: var(--radius-md);
+ padding: 2px;
+ gap: 1px;
}
-.highlight-color-swatch {
- width: 22px;
- height: 22px;
- border-radius: 50%;
- border: 2px solid transparent;
+.view-mode-btn {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ padding: 4px 10px;
+ border: none;
+ border-radius: var(--radius-sm);
+ background: transparent;
+ color: var(--text-tertiary);
+ font-family: var(--font-body);
+ font-size: 11px;
+ font-weight: 500;
cursor: pointer;
transition: all var(--duration-fast) var(--ease-out);
+ white-space: nowrap;
}
-.highlight-color-swatch:hover {
- transform: scale(1.15);
- border-color: var(--text-primary);
+.view-mode-btn:hover:not(.disabled) {
+ color: var(--text-secondary);
}
-.highlight-color-swatch.yellow { background: rgba(250, 204, 21, 0.7); }
-.highlight-color-swatch.green { background: rgba(74, 222, 128, 0.6); }
-.highlight-color-swatch.blue { background: rgba(96, 165, 250, 0.6); }
-.highlight-color-swatch.pink { background: rgba(244, 114, 182, 0.6); }
-.highlight-color-swatch.orange { background: rgba(251, 146, 60, 0.7); }
+.view-mode-btn.active {
+ background: var(--bg-elevated);
+ color: var(--accent-text);
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
+}
-/* Highlights menu button */
-.reader-tool-btn.highlights-btn {
- width: auto;
- padding: 0 10px;
- gap: 4px;
- font-size: 12px;
- font-weight: 500;
- color: var(--text-tertiary);
- background: transparent;
- border: 1px solid var(--border-default);
- border-radius: var(--radius-full, 999px);
- position: relative;
+.view-mode-btn.disabled {
+ opacity: 0.35;
+ cursor: not-allowed;
}
-.reader-tool-btn.highlights-btn:hover {
- color: var(--accent);
- border-color: var(--accent-dim);
- background: var(--accent-glow);
+.view-mode-icon {
+ font-size: 12px;
+ line-height: 1;
}
-.highlights-badge {
- font-size: 10px;
- font-weight: 600;
- color: var(--accent);
- background: var(--accent-glow);
- padding: 0 5px;
- border-radius: 8px;
- min-width: 16px;
- text-align: center;
- line-height: 16px;
+.view-mode-label {
+ letter-spacing: 0.01em;
}
-/* Highlights menu dropdown */
-.highlights-menu-dropdown {
- position: absolute;
- top: calc(100% + 8px);
- right: 0;
- min-width: 280px;
- max-width: 340px;
- max-height: 400px;
- background: var(--bg-surface);
- border: 1px solid var(--border-default);
- border-radius: var(--radius-md);
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
- z-index: 100;
- overflow: hidden;
- display: flex;
- flex-direction: column;
+/* Reader content */
+.reader-content {
+ flex: 1;
+ overflow-y: auto;
+ padding: var(--space-2xl) var(--space-3xl);
}
-.highlights-menu-header {
+.reader-article-meta {
display: flex;
align-items: center;
gap: var(--space-sm);
- padding: var(--space-sm) var(--space-md);
- border-bottom: 1px solid var(--border-subtle);
- flex-shrink: 0;
+ margin-bottom: var(--space-lg);
}
-.highlights-menu-title {
+.reader-source {
font-size: 12px;
font-weight: 600;
- color: var(--text-primary);
- flex: 1;
+ color: var(--accent);
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
}
-.highlights-menu-list {
- overflow-y: auto;
- padding: var(--space-xs) 0;
+.reader-meta-dot {
+ color: var(--text-tertiary);
+ font-size: 10px;
}
-.highlight-menu-item {
- display: flex;
- align-items: flex-start;
- gap: var(--space-sm);
- padding: var(--space-sm) var(--space-md);
- cursor: pointer;
- transition: background var(--duration-fast) var(--ease-out);
- position: relative;
+.reader-author {
+ font-size: 12px;
+ color: var(--text-secondary);
+ font-weight: 500;
}
-.highlight-menu-item:hover {
- background: var(--bg-hover);
+.reader-title {
+ font-family: var(--font-display);
+ font-size: 28px;
+ font-weight: 700;
+ line-height: 1.2;
+ color: var(--text-primary);
+ letter-spacing: -0.02em;
+ margin-bottom: var(--space-lg);
}
-.highlight-menu-dot {
- width: 10px;
- height: 10px;
- border-radius: 50%;
- flex-shrink: 0;
- margin-top: 3px;
+.reader-info {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ margin-bottom: var(--space-md);
}
-.highlight-menu-dot.yellow { background: rgba(250, 204, 21, 0.7); }
-.highlight-menu-dot.green { background: rgba(74, 222, 128, 0.6); }
-.highlight-menu-dot.blue { background: rgba(96, 165, 250, 0.6); }
-.highlight-menu-dot.pink { background: rgba(244, 114, 182, 0.6); }
-.highlight-menu-dot.orange { background: rgba(251, 146, 60, 0.7); }
+.reader-date {
+ font-size: 12px;
+ color: var(--text-tertiary);
+}
-.highlight-menu-content {
- flex: 1;
- min-width: 0;
+.reader-readtime {
+ font-size: 12px;
+ color: var(--text-tertiary);
}
-.highlight-menu-text {
+.reader-comments-count {
font-size: 12px;
- color: var(--text-primary);
- line-height: 1.4;
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- overflow: hidden;
+ color: var(--accent-dim);
+ font-weight: 500;
}
-.highlight-menu-note {
- font-size: 11px;
- font-style: italic;
- color: var(--text-tertiary);
- margin-top: 2px;
- display: -webkit-box;
- -webkit-line-clamp: 1;
- -webkit-box-orient: vertical;
- overflow: hidden;
- cursor: text;
+.reader-tags {
+ display: flex;
+ gap: var(--space-sm);
+ margin-bottom: var(--space-xl);
+ flex-wrap: wrap;
}
-.highlight-menu-note-input {
- width: 100%;
+.reader-tag {
font-size: 11px;
- font-family: var(--font-body);
+ font-weight: 500;
+ color: var(--accent-dim);
+ background: var(--accent-glow);
+ padding: 2px 10px;
+ border-radius: 3px;
+ letter-spacing: 0.02em;
+}
+
+.reader-divider {
+ height: 1px;
+ background: linear-gradient(
+ 90deg,
+ var(--accent) 0%,
+ var(--border-subtle) 40%,
+ transparent 100%
+ );
+ margin-bottom: var(--space-2xl);
+ opacity: 0.5;
+}
+
+/* Article body content */
+.reader-body {
+ font-size: 14.5px;
+ line-height: 1.75;
+ color: var(--text-secondary);
+}
+
+.reader-body h2 {
+ font-family: var(--font-display);
+ font-size: 20px;
+ font-weight: 600;
color: var(--text-primary);
- background: var(--bg-elevated);
- border: 1px solid var(--border-default);
- border-radius: var(--radius-sm);
- padding: 2px 6px;
- margin-top: 2px;
- outline: none;
- transition: border-color var(--duration-fast) var(--ease-out);
+ margin: var(--space-2xl) 0 var(--space-md);
+ letter-spacing: -0.01em;
}
-.highlight-menu-note-input:focus {
- border-color: var(--accent);
+.reader-body h3 {
+ font-family: var(--font-display);
+ font-size: 17px;
+ font-weight: 600;
+ color: var(--text-primary);
+ margin: var(--space-xl) 0 var(--space-sm);
+}
+
+.reader-body p {
+ margin-bottom: var(--space-lg);
+}
+
+.reader-body strong {
+ color: var(--text-primary);
+ font-weight: 600;
+}
+
+.reader-body em {
+ font-style: italic;
+ color: var(--text-primary);
+}
+
+.reader-body a {
+ color: var(--accent);
+ text-decoration: none;
+ border-bottom: 1px solid var(--accent-glow);
+ transition: border-color var(--duration-fast) var(--ease-out);
+}
+
+.reader-body a:hover {
+ border-color: var(--accent);
+}
+
+.reader-body ul,
+.reader-body ol {
+ margin-bottom: var(--space-lg);
+ padding-left: var(--space-xl);
+}
+
+.reader-body li {
+ margin-bottom: var(--space-sm);
+}
+
+.reader-body li::marker {
+ color: var(--accent-dim);
+}
+
+.reader-body blockquote {
+ margin: var(--space-xl) 0;
+ padding: var(--space-lg) var(--space-xl);
+ border-left: 2px solid var(--accent);
+ background: var(--accent-glow);
+ border-radius: 0 var(--radius-md) var(--radius-md) 0;
+ font-style: italic;
+ color: var(--text-primary);
+ font-size: 14px;
+ line-height: 1.6;
+}
+
+.reader-body pre {
+ margin: var(--space-xl) 0;
+ padding: var(--space-lg);
+ background: var(--bg-root);
+ border: 1px solid var(--border-default);
+ border-radius: var(--radius-md);
+ overflow-x: auto;
+}
+
+.reader-body code {
+ font-family: "JetBrains Mono", "Fira Code", monospace;
+ font-size: 13px;
+ line-height: 1.6;
+}
+
+.reader-body p code {
+ background: var(--bg-root);
+ padding: 1px 6px;
+ border-radius: 3px;
+ font-size: 12.5px;
+ color: var(--accent-text);
+}
+
+.reader-comments-section {
+ margin-top: var(--space-2xl);
+}
+
+.reader-comments-header {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ margin-bottom: var(--space-md);
+}
+
+.reader-comments-title {
+ font-family: var(--font-display);
+ font-size: 20px;
+ font-weight: 600;
+ color: var(--text-primary);
+ letter-spacing: -0.01em;
+}
+
+.reader-comments-badge {
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--accent-dim);
+ background: var(--accent-glow);
+ border: 1px solid rgba(212, 168, 83, 0.25);
+ border-radius: 999px;
+ padding: 2px 10px;
+}
+
+.reader-comments-status {
+ font-size: 11px;
+ color: var(--text-tertiary);
+ letter-spacing: 0.02em;
+}
+
+.reader-comments-list {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-md);
+}
+
+.reader-comment-card {
+ border: 1px solid var(--border-subtle);
+ background: var(--bg-elevated);
+ border-radius: var(--radius-md);
+ padding: var(--space-md);
+}
+
+.reader-comment-meta {
+ display: flex;
+ align-items: center;
+ gap: var(--space-xs);
+ margin-bottom: var(--space-sm);
+}
+
+.reader-comment-author {
+ font-size: 12px;
+ font-weight: 600;
+ color: var(--accent-dim);
+}
+
+.reader-comment-score,
+.reader-comment-time {
+ font-size: 11px;
+ color: var(--text-tertiary);
+}
+
+.reader-comment-body {
+ font-size: 13px;
+ line-height: 1.65;
+ color: var(--text-secondary);
+ white-space: pre-wrap;
+}
+
+.reader-comments-error {
+ font-size: 12px;
+ color: var(--red);
+ margin-bottom: var(--space-sm);
+}
+
+.reader-comments-empty {
+ font-size: 12px;
+ color: var(--text-tertiary);
+ font-style: italic;
+}
+
+.reader-original-link {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--space-xs);
+ font-size: 12px;
+ font-weight: 500;
+ color: var(--accent);
+ text-decoration: none;
+ padding: var(--space-sm) var(--space-lg);
+ border: 1px solid var(--border-default);
+ border-radius: var(--radius-md);
+ transition: all var(--duration-fast) var(--ease-out);
+}
+
+.reader-original-link:hover {
+ background: var(--accent-glow);
+ border-color: var(--accent-dim);
+}
+
+.reader-comments-link {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--space-xs);
+ font-family: var(--font-body);
+ font-size: 12px;
+ font-weight: 500;
+ color: var(--accent-text);
+ text-decoration: none;
+ padding: var(--space-sm) var(--space-lg);
+ border: 1px solid var(--accent-dim);
+ border-radius: var(--radius-md);
+ background: var(--accent-glow);
+ cursor: pointer;
+ transition: all var(--duration-fast) var(--ease-out);
+}
+
+.reader-comments-link:hover {
+ background: var(--accent);
+ border-color: var(--accent);
+ color: var(--text-inverse);
+}
+
+/* Webview link in reader footer */
+.reader-webview-link {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--space-sm);
+ font-family: var(--font-body);
+ font-size: 12px;
+ font-weight: 500;
+ color: var(--text-primary);
+ background: var(--accent-glow);
+ border: 1px solid var(--accent-dim);
+ padding: var(--space-sm) var(--space-lg);
+ border-radius: var(--radius-md);
+ cursor: pointer;
+ transition: all var(--duration-fast) var(--ease-out);
+}
+
+.reader-webview-link:hover {
+ background: var(--accent);
+ color: var(--text-inverse);
+ border-color: var(--accent);
+}
+
+.webview-link-icon {
+ font-size: 14px;
+}
+
+.reader-footer {
+ margin-top: var(--space-3xl);
+ padding-top: var(--space-xl);
+ border-top: 1px solid var(--border-subtle);
+ display: flex;
+ align-items: center;
+ gap: var(--space-md);
+ flex-wrap: wrap;
+}
+
+/* ─── WEBVIEW ─────────────────────────────────── */
+
+/* URL bar */
+.webview-urlbar {
+ flex-shrink: 0;
+ border-bottom: 1px solid var(--border-subtle);
+ overflow: hidden;
+}
+
+.urlbar-inner {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ padding: var(--space-sm) var(--space-lg);
+ background: var(--bg-root);
+}
+
+.urlbar-lock {
+ font-size: 11px;
+ color: var(--accent-dim);
+ flex-shrink: 0;
+}
+
+.urlbar-url {
+ flex: 1;
+ font-family: var(--font-body);
+ font-size: 11.5px;
+ color: var(--text-secondary);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ letter-spacing: 0.01em;
+}
+
+.urlbar-open {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ border: none;
+ border-radius: var(--radius-sm);
+ background: transparent;
+ color: var(--text-tertiary);
+ font-size: 13px;
+ cursor: pointer;
+ flex-shrink: 0;
+ transition: all var(--duration-fast) var(--ease-out);
+}
+
+.urlbar-open:hover {
+ background: var(--bg-hover);
+ color: var(--text-secondary);
+}
+
+/* Webview container */
+.webview-container {
+ flex: 1;
+ position: relative;
+ overflow: hidden;
+ background: #fff;
+}
+
+.webview-iframe {
+ width: 100%;
+ height: 100%;
+ border: none;
+ background: #fff;
+}
+
+/* Loading state */
+.webview-loading {
+ position: absolute;
+ inset: 0;
+ z-index: 5;
+ display: flex;
+ flex-direction: column;
+ background: var(--bg-surface);
+}
+
+.webview-loading-bar {
+ height: 2px;
+ background: linear-gradient(90deg, transparent, var(--accent), transparent);
+ animation: webview-loading-slide 1.5s ease-in-out infinite;
+}
+
+@keyframes webview-loading-slide {
+ 0% {
+ transform: translateX(-100%);
+ }
+ 100% {
+ transform: translateX(100%);
+ }
+}
+
+.webview-loading-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: var(--space-lg);
+}
+
+.webview-loading-spinner {
+ width: 24px;
+ height: 24px;
+ border: 2px solid var(--border-default);
+ border-top-color: var(--accent);
+ border-radius: 50%;
+ animation: webview-spin 0.8s linear infinite;
+}
+
+@keyframes webview-spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+.webview-loading-text {
+ font-size: 12px;
+ color: var(--text-tertiary);
+ letter-spacing: 0.02em;
+}
+
+/* Error state */
+.webview-error {
+ position: absolute;
+ inset: 0;
+ z-index: 5;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: var(--space-md);
+ background: var(--bg-surface);
+}
+
+.webview-error-icon {
+ font-size: 40px;
+ color: var(--text-tertiary);
+ opacity: 0.5;
+}
+
+.webview-error-title {
+ font-family: var(--font-display);
+ font-size: 18px;
+ font-weight: 600;
+ color: var(--text-primary);
+}
+
+.webview-error-text {
+ font-size: 13px;
+ color: var(--text-secondary);
+ text-align: center;
+ max-width: 280px;
+ line-height: 1.5;
+}
+
+.webview-error-actions {
+ display: flex;
+ gap: var(--space-sm);
+ margin-top: var(--space-md);
+}
+
+.webview-error-btn {
+ padding: var(--space-sm) var(--space-lg);
+ border: 1px solid var(--border-default);
+ border-radius: var(--radius-md);
+ background: transparent;
+ color: var(--text-secondary);
+ font-family: var(--font-body);
+ font-size: 12px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all var(--duration-fast) var(--ease-out);
+}
+
+.webview-error-btn:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+ border-color: var(--border-strong);
+}
+
+.webview-error-btn.primary {
+ background: var(--accent-glow);
+ border-color: var(--accent-dim);
+ color: var(--accent-text);
+}
+
+.webview-error-btn.primary:hover {
+ background: var(--accent);
+ color: var(--text-inverse);
+}
+
+/* Floating action bar */
+.webview-floating-bar {
+ position: absolute;
+ bottom: var(--space-lg);
+ left: 50%;
+ transform: translateX(-50%);
+ z-index: 6;
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ padding: var(--space-sm) var(--space-md);
+ background: var(--bg-elevated);
+ border: 1px solid var(--border-default);
+ border-radius: var(--radius-lg);
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(0, 0, 0, 0.2);
+ backdrop-filter: blur(12px);
+}
+
+.webview-floating-text {
+ font-size: 11px;
+ color: var(--text-tertiary);
+ white-space: nowrap;
+ padding: 0 var(--space-xs);
+}
+
+.webview-floating-btn {
+ padding: 4px 12px;
+ border: none;
+ border-radius: var(--radius-sm);
+ background: var(--accent);
+ color: var(--text-inverse);
+ font-family: var(--font-body);
+ font-size: 11px;
+ font-weight: 600;
+ cursor: pointer;
+ white-space: nowrap;
+ transition: all var(--duration-fast) var(--ease-out);
+}
+
+.webview-floating-btn:hover {
+ background: var(--accent-text);
+}
+
+.webview-floating-btn.secondary {
+ background: transparent;
+ color: var(--text-secondary);
+ border: 1px solid var(--border-default);
+}
+
+.webview-floating-btn.secondary:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+ border-color: var(--border-strong);
+}
+
+/* ─── AI SUMMARY ─────────────────────────────── */
+
+.reader-summary {
+ margin-bottom: var(--space-xl);
+ border: 1px solid var(--accent-dim);
+ border-radius: var(--radius-md);
+ background: var(--accent-glow);
+ overflow: hidden;
+}
+
+.reader-summary-header {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ width: 100%;
+ padding: var(--space-md) var(--space-lg);
+ border: none;
+ background: transparent;
+ cursor: pointer;
+ font-family: var(--font-body);
+ transition: background var(--duration-fast) var(--ease-out);
+}
+
+.reader-summary-header:hover {
+ background: rgba(212, 168, 83, 0.08);
+}
+
+.reader-summary-icon {
+ font-size: 14px;
+ color: var(--accent);
+}
+
+.reader-summary-title {
+ font-size: 12px;
+ font-weight: 600;
+ color: var(--accent-text);
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
+ flex: 1;
+ text-align: left;
+}
+
+.reader-summary-chevron {
+ font-size: 16px;
+ color: var(--accent-dim);
+ transition: transform var(--duration-fast) var(--ease-out);
+ line-height: 1;
+}
+
+.reader-summary-chevron.open {
+ transform: rotate(90deg);
+}
+
+.reader-summary-content {
+ padding: 0 var(--space-lg) var(--space-lg);
+ font-size: 13.5px;
+ line-height: 1.7;
+ color: var(--text-primary);
+ white-space: pre-wrap;
+}
+
+.reader-summary-loading {
+ padding: var(--space-sm) var(--space-lg) var(--space-lg);
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-sm);
+}
+
+.reader-summary-pulse {
+ height: 12px;
+ border-radius: 6px;
+ background: var(--border-default);
+ animation: summary-pulse 1.2s ease-in-out infinite;
+}
+
+.reader-summary-pulse.short {
+ width: 60%;
+}
+
+@keyframes summary-pulse {
+ 0%, 100% { opacity: 0.3; }
+ 50% { opacity: 0.7; }
+}
+
+.reader-summary-error {
+ padding: 0 var(--space-lg) var(--space-lg);
+ font-size: 12px;
+ color: var(--red);
+ font-weight: 500;
+}
+
+/* ── Feed Digest IA ── */
+.feed-digest {
+ margin: 0 var(--space-md) var(--space-sm);
+ border: 1px solid var(--accent-dim);
+ border-radius: var(--radius-md);
+ background: var(--accent-glow);
+ overflow: hidden;
+}
+
+.feed-digest-header {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ width: 100%;
+ padding: var(--space-md) var(--space-lg);
+ border: none;
+ background: transparent;
+ cursor: pointer;
+ font-family: var(--font-body);
+ transition: background var(--duration-fast) var(--ease-out);
+}
+
+.feed-digest-header:hover {
+ background: rgba(212, 168, 83, 0.08);
+}
+
+.feed-digest-icon {
+ font-size: 14px;
+ color: var(--accent);
+}
+
+.feed-digest-title {
+ font-size: 12px;
+ font-weight: 600;
+ color: var(--accent-text);
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
+ flex: 1;
+ text-align: left;
+}
+
+.feed-digest-chevron {
+ font-size: 16px;
+ color: var(--accent-dim);
+ transition: transform var(--duration-fast) var(--ease-out);
+ line-height: 1;
+}
+
+.feed-digest-chevron.open {
+ transform: rotate(90deg);
+}
+
+.feed-digest-content {
+ padding: 0 var(--space-lg) var(--space-lg);
+ font-size: 13.5px;
+ line-height: 1.7;
+ color: var(--text-primary);
+ white-space: pre-wrap;
+}
+
+.feed-digest-loading {
+ padding: var(--space-sm) var(--space-lg) var(--space-lg);
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-sm);
+}
+
+.feed-digest-pulse {
+ height: 12px;
+ border-radius: 6px;
+ background: var(--border-default);
+ animation: summary-pulse 1.2s ease-in-out infinite;
+}
+
+.feed-digest-pulse.short {
+ width: 60%;
+}
+
+.feed-digest-error {
+ padding: 0 var(--space-lg) var(--space-lg);
+ font-size: 12px;
+ color: var(--red);
+ font-weight: 500;
+}
+
+.reader-tool-btn.summarize {
+ width: auto;
+ padding: 0 10px;
+ gap: 6px;
+ font-size: 12px;
+ font-weight: 500;
+ color: var(--accent);
+ background: var(--accent-glow, rgba(212, 168, 83, 0.08));
+ border: 1px solid color-mix(in srgb, var(--accent) 25%, transparent);
+ border-radius: var(--radius-full, 999px);
+}
+
+.reader-tool-btn.summarize:hover {
+ color: var(--bg-surface);
+ background: var(--accent);
+ border-color: var(--accent);
+}
+
+.reader-tool-btn.summarize .summarize-label {
+ font-size: 12px;
+ letter-spacing: 0.01em;
+}
+
+.reader-tool-btn.summarize.loading {
+ cursor: wait;
+ opacity: 0.7;
+}
+
+/* TTS button */
+.reader-tool-btn.tts {
+ width: auto;
+ padding: 0 10px;
+ font-size: 12px;
+ font-weight: 500;
+ color: var(--text-tertiary);
+ background: transparent;
+ border: 1px solid var(--border-default);
+ border-radius: var(--radius-full, 999px);
+}
+
+.reader-tool-btn.tts:hover {
+ color: var(--accent);
+ border-color: var(--accent-dim);
+ background: var(--accent-glow);
+}
+
+.reader-tool-btn.tts.active {
+ color: var(--bg-surface);
+ background: var(--accent);
+ border-color: var(--accent);
+}
+
+.reader-tool-btn.tts-stop {
+ width: 28px;
+ height: 28px;
+ font-size: 10px;
+ color: var(--text-tertiary);
+ border: 1px solid var(--border-default);
+ border-radius: var(--radius-full, 999px);
+}
+
+.reader-tool-btn.tts-stop:hover {
+ color: var(--red);
+ border-color: var(--red);
+ background: rgba(217, 69, 58, 0.1);
+}
+
+/* ─── FULL CONTENT EXTRACTION ────────────────── */
+
+.reader-fullcontent-banner {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ padding: var(--space-md) var(--space-lg);
+ margin-bottom: var(--space-lg);
+ border-radius: var(--radius-md);
+ font-size: 13px;
+ color: var(--text-secondary);
+}
+
+.reader-fullcontent-banner.loading {
+ background: var(--accent-glow);
+ border: 1px solid var(--accent-dim);
+ color: var(--accent-text);
+}
+
+.reader-fullcontent-banner.loading .btn-spinner {
+ width: 14px;
+ height: 14px;
+ flex-shrink: 0;
+}
+
+.reader-fullcontent-banner.error {
+ background: rgba(220, 80, 80, 0.08);
+ border: 1px solid rgba(220, 80, 80, 0.2);
+ color: var(--text-secondary);
+ justify-content: space-between;
+}
+
+.reader-fullcontent-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--space-xs);
+ padding: var(--space-xs) var(--space-md);
+ border: 1px solid var(--border-default);
+ border-radius: var(--radius-sm);
+ background: var(--bg-surface);
+ color: var(--text-secondary);
+ font-size: 12px;
+ font-family: var(--font-body);
+ cursor: pointer;
+ transition: all var(--duration-fast) var(--ease-out);
+ white-space: nowrap;
+}
+
+.reader-fullcontent-btn:hover {
+ background: var(--bg-elevated);
+ border-color: var(--border-strong);
+ color: var(--text-primary);
+}
+
+.reader-fullcontent-fetch {
+ margin-top: var(--space-xl);
+ padding-top: var(--space-lg);
+ border-top: 1px solid var(--border-subtle);
+ text-align: center;
+}
+
+/* ─── SETTINGS FORMAT TOGGLE ─────────────────── */
+
+.settings-format-toggle {
+ display: flex;
+ gap: var(--space-sm);
+ margin-bottom: var(--space-md);
+}
+
+.format-option {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ flex: 1;
+ padding: var(--space-md);
+ border: 1px solid var(--border-default);
+ border-radius: var(--radius-md);
+ background: transparent;
+ color: var(--text-secondary);
+ font-family: var(--font-body);
+ font-size: 13px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all var(--duration-fast) var(--ease-out);
+}
+
+.format-option:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+}
+
+.format-option.active {
+ background: var(--accent-glow);
+ border-color: var(--accent);
+ color: var(--accent-text);
+}
+
+.format-option-icon {
+ font-size: 16px;
+ width: 20px;
+ text-align: center;
+}
+
+.settings-api-status {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ padding: var(--space-sm) 0;
+}
+
+.api-status-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ flex-shrink: 0;
+}
+
+.api-status-dot.configured {
+ background: var(--green);
+}
+
+.api-status-dot.missing {
+ background: var(--red);
+}
+
+.api-status-text {
+ font-size: 12px;
+ color: var(--text-secondary);
+}
+
+.settings-label {
+ display: block;
+ font-size: 12px;
+ font-weight: 600;
+ color: var(--text-secondary);
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+ margin-bottom: var(--space-sm);
+}
+
+.settings-ollama-status {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ padding: var(--space-sm) var(--space-md);
+ margin-bottom: var(--space-md);
+ border-radius: var(--radius-md);
+ background: var(--bg-secondary);
+ font-size: 12px;
+ color: var(--text-secondary);
+ flex-wrap: wrap;
+}
+
+.settings-ollama-status code {
+ font-family: var(--font-mono, monospace);
+ font-size: 11px;
+ padding: 1px 5px;
+ border-radius: var(--radius-sm);
+ background: var(--bg-hover);
+ color: var(--text-primary);
+}
+
+.ollama-status-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ flex-shrink: 0;
+}
+
+.ollama-status-dot.connected {
+ background: var(--green);
+}
+
+.ollama-status-dot.disconnected {
+ background: var(--red);
+}
+
+.ollama-status-text {
+ font-size: 12px;
+ color: var(--text-secondary);
+}
+
+.ollama-model-select {
+ margin-left: auto;
+ padding: 4px 8px;
+ border: 1px solid var(--border-default);
+ border-radius: var(--radius-sm);
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ font-family: var(--font-body);
+ font-size: 12px;
+ cursor: pointer;
+ max-width: 200px;
+}
+
+.ollama-model-select:focus {
+ outline: none;
+ border-color: var(--accent);
+}
+
+.settings-ollama-block {
+ margin-bottom: var(--space-md);
+}
+
+.ollama-setup-card {
+ padding: var(--space-md);
+ border-radius: var(--radius-md);
+ background: var(--bg-secondary);
+ margin-bottom: var(--space-sm);
+}
+
+.ollama-setup-text {
+ font-size: 12px;
+ color: var(--text-secondary);
+ margin: var(--space-sm) 0;
+ line-height: 1.5;
+}
+
+.ollama-setup-actions {
+ display: flex;
+ gap: var(--space-sm);
+ margin-top: var(--space-md);
+}
+
+.ollama-setup-btn {
+ padding: 6px 14px;
+ border: 1px solid var(--accent);
+ border-radius: var(--radius-md);
+ background: var(--accent);
+ color: var(--accent-contrast, #fff);
+ font-family: var(--font-body);
+ font-size: 12px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all var(--duration-fast) var(--ease-out);
+}
+
+.ollama-setup-btn:hover {
+ opacity: 0.9;
+}
+
+.ollama-setup-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.ollama-setup-btn.secondary {
+ background: transparent;
+ color: var(--text-secondary);
+ border-color: var(--border-default);
+}
+
+.ollama-setup-btn.secondary:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+}
+
+.ollama-setup-hint {
+ font-size: 11px;
+ color: var(--text-tertiary);
+ margin-top: var(--space-sm);
+ line-height: 1.5;
+}
+
+.ollama-setup-hint code {
+ font-family: var(--font-mono, monospace);
+ font-size: 11px;
+ padding: 1px 5px;
+ border-radius: var(--radius-sm);
+ background: var(--bg-hover);
+ color: var(--text-primary);
+}
+
+.ollama-pull-progress {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ padding: var(--space-sm) var(--space-md);
+ margin-top: var(--space-sm);
+ border-radius: var(--radius-md);
+ background: var(--bg-secondary);
+ font-size: 12px;
+ color: var(--text-secondary);
+}
+
+.ollama-pull-status {
+ flex-shrink: 0;
+ max-width: 160px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.ollama-pull-bar {
+ flex: 1;
+ height: 6px;
+ background: var(--bg-hover);
+ border-radius: 3px;
+ overflow: hidden;
+}
+
+.ollama-pull-bar-fill {
+ height: 100%;
+ background: var(--accent);
+ border-radius: 3px;
+ transition: width 0.3s ease-out;
+}
+
+.ollama-pull-percent {
+ flex-shrink: 0;
+ font-variant-numeric: tabular-nums;
+ min-width: 36px;
+ text-align: right;
+}
+
+/* ─── NOISE OVERLAY (subtle texture) ──────────── */
+
+.app::before {
+ content: "";
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ z-index: 1000;
+ opacity: 0.015;
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
+ background-repeat: repeat;
+ background-size: 256px 256px;
+}
+
+/* ─── HIGHLIGHTS ──────────────────────────────── */
+
+mark.highlight {
+ border-radius: 2px;
+ padding: 1px 0;
+ cursor: pointer;
+}
+
+mark.highlight-yellow { background: rgba(250, 204, 21, 0.35); }
+mark.highlight-green { background: rgba(74, 222, 128, 0.30); }
+mark.highlight-blue { background: rgba(96, 165, 250, 0.30); }
+mark.highlight-pink { background: rgba(244, 114, 182, 0.30); }
+mark.highlight-orange { background: rgba(251, 146, 60, 0.35); }
+
+.dark mark.highlight-yellow { background: rgba(250, 204, 21, 0.30); }
+.dark mark.highlight-green { background: rgba(74, 222, 128, 0.25); }
+.dark mark.highlight-blue { background: rgba(96, 165, 250, 0.25); }
+.dark mark.highlight-pink { background: rgba(244, 114, 182, 0.25); }
+.dark mark.highlight-orange { background: rgba(251, 146, 60, 0.30); }
+
+@keyframes highlight-pulse {
+ 0%, 100% { filter: brightness(1); }
+ 50% { filter: brightness(1.4); }
+}
+
+mark.highlight-pulse {
+ animation: highlight-pulse 0.75s ease-in-out 2;
+}
+
+/* Color picker */
+.highlight-color-picker {
+ position: fixed;
+ z-index: 999;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 6px 10px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border-default);
+ border-radius: var(--radius-lg);
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
+}
+
+.highlight-color-swatch {
+ width: 22px;
+ height: 22px;
+ border-radius: 50%;
+ border: 2px solid transparent;
+ cursor: pointer;
+ transition: all var(--duration-fast) var(--ease-out);
+}
+
+.highlight-color-swatch:hover {
+ transform: scale(1.15);
+ border-color: var(--text-primary);
+}
+
+.highlight-color-swatch.yellow { background: rgba(250, 204, 21, 0.7); }
+.highlight-color-swatch.green { background: rgba(74, 222, 128, 0.6); }
+.highlight-color-swatch.blue { background: rgba(96, 165, 250, 0.6); }
+.highlight-color-swatch.pink { background: rgba(244, 114, 182, 0.6); }
+.highlight-color-swatch.orange { background: rgba(251, 146, 60, 0.7); }
+
+/* Highlights menu button */
+.reader-tool-btn.highlights-btn {
+ width: auto;
+ padding: 0 10px;
+ gap: 4px;
+ font-size: 12px;
+ font-weight: 500;
+ color: var(--text-tertiary);
+ background: transparent;
+ border: 1px solid var(--border-default);
+ border-radius: var(--radius-full, 999px);
+ position: relative;
+}
+
+.reader-tool-btn.highlights-btn:hover {
+ color: var(--accent);
+ border-color: var(--accent-dim);
+ background: var(--accent-glow);
+}
+
+.highlights-badge {
+ font-size: 10px;
+ font-weight: 600;
+ color: var(--accent);
+ background: var(--accent-glow);
+ padding: 0 5px;
+ border-radius: 8px;
+ min-width: 16px;
+ text-align: center;
+ line-height: 16px;
+}
+
+/* Highlights menu dropdown */
+.highlights-menu-dropdown {
+ position: absolute;
+ top: calc(100% + 8px);
+ right: 0;
+ min-width: 280px;
+ max-width: 340px;
+ max-height: 400px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border-default);
+ border-radius: var(--radius-md);
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
+ z-index: 100;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+}
+
+.highlights-menu-header {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ padding: var(--space-sm) var(--space-md);
+ border-bottom: 1px solid var(--border-subtle);
+ flex-shrink: 0;
+}
+
+.highlights-menu-title {
+ font-size: 12px;
+ font-weight: 600;
+ color: var(--text-primary);
+ flex: 1;
+}
+
+.highlights-menu-list {
+ overflow-y: auto;
+ padding: var(--space-xs) 0;
+}
+
+.highlight-menu-item {
+ display: flex;
+ align-items: flex-start;
+ gap: var(--space-sm);
+ padding: var(--space-sm) var(--space-md);
+ cursor: pointer;
+ transition: background var(--duration-fast) var(--ease-out);
+ position: relative;
+}
+
+.highlight-menu-item:hover {
+ background: var(--bg-hover);
+}
+
+.highlight-menu-dot {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ flex-shrink: 0;
+ margin-top: 3px;
+}
+
+.highlight-menu-dot.yellow { background: rgba(250, 204, 21, 0.7); }
+.highlight-menu-dot.green { background: rgba(74, 222, 128, 0.6); }
+.highlight-menu-dot.blue { background: rgba(96, 165, 250, 0.6); }
+.highlight-menu-dot.pink { background: rgba(244, 114, 182, 0.6); }
+.highlight-menu-dot.orange { background: rgba(251, 146, 60, 0.7); }
+
+.highlight-menu-content {
+ flex: 1;
+ min-width: 0;
+}
+
+.highlight-menu-text {
+ font-size: 12px;
+ color: var(--text-primary);
+ line-height: 1.4;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+
+.highlight-menu-note {
+ font-size: 11px;
+ font-style: italic;
+ color: var(--text-tertiary);
+ margin-top: 2px;
+ display: -webkit-box;
+ -webkit-line-clamp: 1;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ cursor: text;
+}
+
+.highlight-menu-note-input {
+ width: 100%;
+ font-size: 11px;
+ font-family: var(--font-body);
+ color: var(--text-primary);
+ background: var(--bg-elevated);
+ border: 1px solid var(--border-default);
+ border-radius: var(--radius-sm);
+ padding: 2px 6px;
+ margin-top: 2px;
+ outline: none;
+ transition: border-color var(--duration-fast) var(--ease-out);
+}
+
+.highlight-menu-note-input:focus {
+ border-color: var(--accent);
}
.highlight-menu-note-action {
@@ -4666,1104 +5869,2324 @@ mark.highlight-pulse {
justify-content: center;
background: none;
border: none;
- color: var(--text-tertiary);
- font-size: 11px;
+ color: var(--text-tertiary);
+ font-size: 11px;
+ cursor: pointer;
+ padding: 2px;
+ margin-top: 2px;
+ transition: color var(--duration-fast) var(--ease-out);
+}
+
+.highlight-menu-note-action:hover {
+ color: var(--accent);
+}
+
+.highlight-menu-remove {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 20px;
+ height: 20px;
+ border: none;
+ border-radius: var(--radius-sm);
+ background: transparent;
+ color: var(--text-tertiary);
+ font-size: 14px;
+ cursor: pointer;
+ opacity: 0;
+ transition: all var(--duration-fast) var(--ease-out);
+ flex-shrink: 0;
+ margin-top: 1px;
+}
+
+.highlight-menu-item:hover .highlight-menu-remove {
+ opacity: 1;
+}
+
+.highlight-menu-remove:hover {
+ color: var(--red);
+ background: rgba(217, 69, 58, 0.1);
+}
+
+.highlights-menu-empty {
+ padding: var(--space-lg) var(--space-md);
+ text-align: center;
+ font-size: 12px;
+ color: var(--text-tertiary);
+ font-style: italic;
+}
+
+/* ─── SELECTION ────────────────────────────────── */
+
+::selection {
+ background: var(--accent);
+ color: var(--text-inverse);
+}
+
+/* ─── SYNC COMPONENTS ──────────────────────────── */
+
+.sync-button-container {
+ position: relative;
+}
+
+.sync-button:hover {
+ background: var(--bg-hover) !important;
+}
+
+.sync-button.syncing {
+ cursor: wait;
+}
+
+.sync-button.success svg {
+ color: var(--green);
+}
+
+.sync-button.error svg {
+ color: var(--red);
+}
+
+.sync-progress {
+ transition: width 0.3s ease-out;
+}
+
+.sync-status {
+ position: relative;
+}
+
+.sync-status.compact {
+ font-family: var(--font-body);
+}
+
+.sync-status:not(.compact) {
+ border: 1px solid var(--border-subtle);
+}
+
+/* ─── AUTH / USER MENU ─────────────────────────── */
+
+.auth-divider {
+ display: flex;
+ align-items: center;
+ gap: var(--space-md);
+ margin: var(--space-lg) 0;
+ color: var(--text-tertiary);
+ font-size: 11px;
+}
+
+.auth-divider::before,
+.auth-divider::after {
+ content: "";
+ flex: 1;
+ height: 1px;
+ background: var(--border-default);
+}
+
+.auth-oauth-buttons {
+ display: flex;
+ gap: var(--space-sm);
+}
+
+.auth-oauth-btn {
+ flex: 1;
+ text-align: center;
+}
+
+.auth-switch {
+ text-align: center;
+ font-size: 12px;
+ color: var(--text-secondary);
+ margin-top: var(--space-md);
+}
+
+.auth-switch-link {
+ background: none;
+ border: none;
+ color: var(--accent);
+ font-family: var(--font-body);
+ font-size: 12px;
+ cursor: pointer;
+ text-decoration: underline;
+ padding: 0;
+}
+
+.auth-switch-link:hover {
+ color: var(--accent-text);
+}
+
+.auth-not-configured {
+ padding: var(--space-md) 0;
+}
+
+.auth-not-configured-text {
+ font-size: 13px;
+ color: var(--text-primary);
+ margin: 0 0 var(--space-sm) 0;
+}
+
+.auth-not-configured-hint {
+ font-size: 12px;
+ color: var(--text-secondary);
+ line-height: 1.6;
+ margin: 0 0 var(--space-lg) 0;
+}
+
+.auth-not-configured-hint code {
+ font-size: 11px;
+ background: var(--bg-root);
+ padding: 1px 5px;
+ border-radius: 3px;
+ color: var(--accent-text);
+}
+
+.auth-success {
+ padding: var(--space-md);
+ background: rgba(52, 199, 89, 0.1);
+ border: 1px solid rgba(52, 199, 89, 0.3);
+ border-radius: var(--radius-md);
+ font-size: 12px;
+ color: #34c759;
+}
+
+/* User menu */
+.user-menu {
+ position: relative;
+ margin-left: auto;
+}
+
+.user-menu-trigger {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 26px;
+ height: 26px;
+ border: none;
+ border-radius: 50%;
+ background: var(--bg-hover);
+ cursor: pointer;
+ padding: 0;
+ overflow: hidden;
+ transition: all var(--duration-fast) var(--ease-out);
+}
+
+.user-menu-trigger:hover {
+ background: var(--bg-elevated);
+ box-shadow: 0 0 0 2px var(--accent-dim);
+}
+
+.user-menu-avatar {
+ width: 26px;
+ height: 26px;
+ border-radius: 50%;
+ object-fit: cover;
+}
+
+.user-menu-avatar-fallback {
+ font-size: 12px;
+ font-weight: 600;
+ color: var(--accent);
+}
+
+.user-menu-login-btn {
+ margin-left: auto;
+}
+
+.user-menu-dropdown {
+ position: absolute;
+ bottom: calc(100% + var(--space-sm));
+ right: 0;
+ min-width: 200px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border-default);
+ border-radius: var(--radius-md);
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
+ padding: var(--space-xs) 0;
+ z-index: 100;
+}
+
+.user-menu-info {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ padding: var(--space-sm) var(--space-md);
+}
+
+.user-menu-name {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--text-primary);
+}
+
+.user-menu-email {
+ font-size: 11px;
+ color: var(--text-tertiary);
+}
+
+.user-menu-divider {
+ height: 1px;
+ background: var(--border-default);
+ margin: var(--space-xs) 0;
+}
+
+.user-menu-item {
+ display: block;
+ width: 100%;
+ padding: var(--space-sm) var(--space-md);
+ border: none;
+ background: transparent;
+ color: var(--text-primary);
+ font-family: var(--font-body);
+ font-size: 12.5px;
+ cursor: pointer;
+ text-align: left;
+ transition: background var(--duration-fast) var(--ease-out);
+}
+
+.user-menu-item:hover {
+ background: var(--bg-hover);
+}
+
+.user-menu-item--danger {
+ color: var(--red);
+}
+
+.user-menu-item--danger:hover {
+ background: rgba(217, 69, 58, 0.1);
+}
+
+/* Settings account section */
+.settings-account {
+ display: flex;
+ align-items: center;
+ gap: var(--space-md);
+ padding: var(--space-md);
+ background: var(--bg-elevated);
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-md);
+ margin-bottom: var(--space-lg);
+}
+
+.settings-account-info {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ flex: 1;
+}
+
+.settings-account-email {
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--text-primary);
+}
+
+.settings-account-status {
+ font-size: 11px;
+ color: var(--text-tertiary);
+}
+
+/* ── Provider settings ── */
+
+.provider-input {
+ width: 100%;
+ padding: 8px 12px;
+ border: 1px solid var(--border-default);
+ border-radius: 6px;
+ background: var(--bg-surface);
+ color: var(--text-primary);
+ font-size: 13px;
+ font-family: var(--font-body);
+ margin-top: 4px;
+ margin-bottom: 8px;
+ transition: border-color var(--duration-fast) var(--ease-out);
+}
+
+.provider-input:focus {
+ outline: none;
+ border-color: var(--accent);
+}
+
+.provider-input::placeholder {
+ color: var(--text-tertiary);
+}
+
+.provider-actions {
+ display: flex;
+ gap: 8px;
+ margin-top: 8px;
+ margin-bottom: 8px;
+}
+
+.provider-sync-toggle {
+ margin-top: 12px;
+}
+
+.provider-connected {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+@theme inline {
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+ --radius-2xl: calc(var(--radius) + 8px);
+ --radius-3xl: calc(var(--radius) + 12px);
+ --radius-4xl: calc(var(--radius) + 16px);
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+ --color-chart-1: var(--chart-1);
+ --color-chart-2: var(--chart-2);
+ --color-chart-3: var(--chart-3);
+ --color-chart-4: var(--chart-4);
+ --color-chart-5: var(--chart-5);
+ --color-sidebar: var(--sidebar);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-ring: var(--sidebar-ring);
+}
+
+@layer base {
+ * {
+ @apply border-border outline-ring/50;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
+
+::view-transition-old(root), ::view-transition-new(root) {
+ animation: none;
+ mix-blend-mode: normal;
+}
+
+/* ═══════════════════════════════════════════════
+ Audio Player — Podcast episode player
+ ═══════════════════════════════════════════════ */
+
+.audio-player {
+ display: flex;
+ gap: var(--space-md);
+ padding: var(--space-lg);
+ margin: var(--space-lg) 0;
+ background: var(--bg-elevated);
+ border: 1px solid var(--border-subtle);
+ border-radius: 12px;
+}
+
+.audio-player-artwork {
+ width: 80px;
+ height: 80px;
+ border-radius: 8px;
+ object-fit: cover;
+ flex-shrink: 0;
+}
+
+.audio-player-body {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-sm);
+}
+
+.audio-player-info {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.audio-player-feed {
+ font-family: var(--font-body);
+ font-size: 11px;
+ font-weight: 500;
+ color: var(--accent-text);
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+}
+
+.audio-player-title {
+ font-family: var(--font-body);
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--text-primary);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.audio-player-controls {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+}
+
+.audio-player-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+ color: var(--text-secondary);
+ font-family: var(--font-body);
+ font-size: 12px;
+ font-weight: 600;
+ padding: var(--space-xs) var(--space-sm);
+ border-radius: 6px;
+ transition: all var(--duration-fast) var(--ease-out);
+}
+
+.audio-player-btn:hover {
+ color: var(--text-primary);
+ background: var(--bg-hover);
+}
+
+.audio-player-btn-play {
+ width: 36px;
+ height: 36px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--accent);
+ color: var(--text-inverse);
+ border-radius: 50%;
+ font-size: 14px;
+ padding: 0;
+}
+
+.audio-player-btn-play:hover {
+ background: var(--accent-dim);
+ color: var(--text-inverse);
+}
+
+.audio-player-speed {
+ background: none;
+ border: 1px solid var(--border-default);
+ cursor: pointer;
+ color: var(--text-secondary);
+ font-family: var(--font-body);
+ font-size: 11px;
+ font-weight: 600;
+ padding: 2px 8px;
+ border-radius: 10px;
+ transition: all var(--duration-fast) var(--ease-out);
+}
+
+.audio-player-speed:hover {
+ color: var(--text-primary);
+ border-color: var(--border-strong);
+}
+
+.audio-player-progress {
+ width: 100%;
+ height: 4px;
+ background: var(--border-subtle);
+ border-radius: 2px;
+ cursor: pointer;
+ position: relative;
+ transition: height var(--duration-fast) var(--ease-out);
+}
+
+.audio-player-progress:hover {
+ height: 6px;
+}
+
+.audio-player-progress-fill {
+ height: 100%;
+ background: var(--accent);
+ border-radius: 2px;
+ transition: width 0.1s linear;
+}
+
+.audio-player-time {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-family: var(--font-body);
+ font-size: 11px;
+ color: var(--text-tertiary);
+ font-variant-numeric: tabular-nums;
+}
+
+.audio-player-volume {
+ width: 60px;
+ height: 3px;
+ accent-color: var(--accent);
+ cursor: pointer;
+}
+
+/* ─── PANEL STRIP (collapsed panel toggle) ──── */
+
+.panel-strip {
+ width: 32px;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--bg-surface);
+ border-right: 1px solid var(--border-subtle);
+ cursor: pointer;
+ flex-shrink: 0;
+ transition: background var(--duration-fast) var(--ease-out),
+ width var(--duration-normal) var(--ease-out);
+}
+
+.panel-strip:last-child {
+ border-right: none;
+ border-left: 1px solid var(--border-subtle);
+}
+
+.panel-strip:hover {
+ background: var(--bg-hover);
+ width: 40px;
+}
+
+.panel-strip-icon {
+ font-size: 16px;
+ color: var(--text-tertiary);
+ transition: color var(--duration-fast) var(--ease-out),
+ transform var(--duration-fast) var(--ease-out);
+ user-select: none;
+}
+
+.panel-strip:hover .panel-strip-icon {
+ color: var(--accent);
+ transform: scale(1.15);
+}
+
+/* ─── PANEL CLOSE BUTTON ───────────────────── */
+
+.panel-close-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
+ border: none;
+ border-radius: var(--radius-sm);
+ background: transparent;
+ color: var(--text-tertiary);
+ font-size: 14px;
+ cursor: pointer;
+ transition: all var(--duration-fast) var(--ease-out);
+ flex-shrink: 0;
+ margin-left: 2px;
+}
+
+.panel-close-btn:hover {
+ background: var(--bg-hover);
+ color: var(--text-secondary);
+}
+
+/* ═══════════════════════════════════════════════
+ Window Effects — Acrylic / Mica / Blur
+ ═══════════════════════════════════════════════ */
+
+:root {
+ --window-opacity-pct: 100%;
+}
+
+/* Entire background chain must be transparent for the native effect to show */
+html.window-effect-active,
+html.window-effect-active body,
+html.window-effect-active #root {
+ background: transparent !important;
+}
+
+html.window-effect-active .app-wrapper {
+ background: transparent;
+}
+
+html.window-effect-active .app {
+ background: color-mix(in srgb, var(--bg-root) var(--window-opacity-pct), transparent);
+}
+
+html.window-effect-active .titlebar {
+ background: color-mix(in srgb, var(--bg-surface) var(--window-opacity-pct), transparent);
+}
+
+html.window-effect-active .titlebar--collapsed {
+ background: color-mix(in srgb, var(--bg-surface) var(--window-opacity-pct), transparent);
+}
+
+html.window-effect-active .source-panel {
+ background: color-mix(in srgb, var(--bg-surface) var(--window-opacity-pct), transparent);
+}
+
+html.window-effect-active .feed-panel {
+ background: color-mix(in srgb, var(--bg-root) var(--window-opacity-pct), transparent);
+}
+
+html.window-effect-active .reader-panel {
+ background: color-mix(in srgb, var(--bg-surface) var(--window-opacity-pct), transparent);
+}
+
+html.window-effect-active .panel-strip {
+ background: color-mix(in srgb, var(--bg-surface) var(--window-opacity-pct), transparent);
+}
+
+html.window-effect-active .panel-strip:hover {
+ background: color-mix(in srgb, var(--bg-hover) var(--window-opacity-pct), transparent);
+}
+
+/* ── Opacity slider in settings ── */
+
+.settings-opacity-slider {
+ display: flex;
+ align-items: center;
+ gap: var(--space-md);
+ margin-top: var(--space-sm);
+}
+
+.settings-opacity-slider input[type="range"] {
+ flex: 1;
+ height: 4px;
+ appearance: none;
+ -webkit-appearance: none;
+ background: var(--border-default);
+ border-radius: 2px;
+ outline: none;
+ cursor: pointer;
+}
+
+.settings-opacity-slider input[type="range"]::-webkit-slider-thumb {
+ appearance: none;
+ -webkit-appearance: none;
+ width: 14px;
+ height: 14px;
+ border-radius: 50%;
+ background: var(--accent);
+ border: 2px solid var(--bg-surface);
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
+ cursor: pointer;
+ transition: transform var(--duration-fast) var(--ease-out);
+}
+
+.settings-opacity-slider input[type="range"]::-webkit-slider-thumb:hover {
+ transform: scale(1.2);
+}
+
+.settings-opacity-value {
+ font-family: var(--font-body);
+ font-size: 12px;
+ font-variant-numeric: tabular-nums;
+ color: var(--text-secondary);
+ min-width: 36px;
+ text-align: right;
+}
+
+/* ─── Stats modal ─────────────────────────────── */
+
+.stats-modal {
+ max-width: 520px;
+}
+
+.stats-body {
+ padding: var(--space-lg) var(--space-xl);
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-lg);
+}
+
+.stats-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: var(--space-md);
+}
+
+.stats-card {
+ background: var(--bg-elevated);
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-md);
+ padding: var(--space-md);
+ text-align: center;
+}
+
+.stats-card-value {
+ font-family: var(--font-display);
+ font-size: 22px;
+ font-weight: 700;
+ color: var(--text-primary);
+ letter-spacing: -0.02em;
+ line-height: 1.2;
+}
+
+.stats-card-total {
+ font-size: 14px;
+ font-weight: 400;
+ color: var(--text-tertiary);
+}
+
+.stats-card-label {
+ font-family: var(--font-body);
+ font-size: 11px;
+ color: var(--text-secondary);
+ margin-top: var(--space-xs);
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+}
+
+.stats-bar {
+ height: 4px;
+ background: var(--border-subtle);
+ border-radius: 2px;
+ margin-top: var(--space-sm);
+ overflow: hidden;
+}
+
+.stats-bar-fill {
+ height: 100%;
+ background: var(--accent);
+ border-radius: 2px;
+ transition: width 0.4s ease;
+}
+
+.stats-bar-label {
+ font-family: var(--font-body);
+ font-size: 10px;
+ color: var(--text-tertiary);
+ margin-top: 2px;
+ text-align: right;
+}
+
+.stats-section {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-sm);
+}
+
+.stats-section-title {
+ font-family: var(--font-display);
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--text-primary);
+ letter-spacing: -0.01em;
+ padding-bottom: var(--space-xs);
+ border-bottom: 1px solid var(--border-subtle);
+}
+
+.stats-source-row {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+}
+
+.stats-source-label {
+ font-family: var(--font-body);
+ font-size: 12px;
+ color: var(--text-secondary);
+ min-width: 72px;
+ flex-shrink: 0;
+}
+
+.stats-source-bar {
+ flex: 1;
+ height: 6px;
+ background: var(--border-subtle);
+ border-radius: 3px;
+ overflow: hidden;
+}
+
+.stats-source-bar-fill {
+ height: 100%;
+ background: var(--accent);
+ border-radius: 3px;
+ transition: width 0.4s ease;
+}
+
+.stats-source-count {
+ font-family: var(--font-body);
+ font-size: 12px;
+ font-variant-numeric: tabular-nums;
+ color: var(--text-tertiary);
+ min-width: 28px;
+ text-align: right;
+}
+
+.stats-feed-row {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ padding: var(--space-xs) 0;
+}
+
+.stats-feed-rank {
+ font-family: var(--font-body);
+ font-size: 12px;
+ color: var(--text-tertiary);
+ min-width: 18px;
+}
+
+.stats-feed-name {
+ font-family: var(--font-body);
+ font-size: 12px;
+ color: var(--text-primary);
+ flex: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.stats-feed-count {
+ font-family: var(--font-body);
+ font-size: 12px;
+ font-variant-numeric: tabular-nums;
+ color: var(--accent-text);
+ font-weight: 600;
+}
+
+.stats-close-btn {
+ align-self: center;
+ padding: var(--space-sm) var(--space-xl);
+ border: 1px solid var(--border-default);
+ border-radius: var(--radius-md);
+ background: transparent;
+ color: var(--text-secondary);
+ font-family: var(--font-body);
+ font-size: 13px;
+ cursor: pointer;
+ transition: all var(--duration-fast) var(--ease-out);
+}
+
+.stats-close-btn:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
+}
+
+/* ── Expanding Panel ── */
+
+.expanding-panel-overlay {
+ position: fixed;
+ inset: 0;
+ z-index: 900;
+ background: rgba(0, 0, 0, 0.25);
+ backdrop-filter: blur(12px);
+ -webkit-backdrop-filter: blur(12px);
+}
+
+.expanding-panel {
+ position: fixed;
+ inset: 0;
+ z-index: 910;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ background: var(--bg-surface);
+ color: var(--text-primary);
+}
+
+.expanding-panel-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: var(--space-lg) var(--space-xl);
+ border-bottom: 1px solid var(--border-subtle);
+ flex-shrink: 0;
+}
+
+.expanding-panel-title {
+ font-family: var(--font-display);
+ font-size: 18px;
+ font-weight: 600;
+ letter-spacing: 0.02em;
+}
+
+.expanding-panel-close {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ border-radius: var(--radius-md);
+ border: 1px solid var(--border-default);
+ background: var(--bg-elevated);
+ color: var(--text-secondary);
+ cursor: pointer;
+ font-size: 14px;
+ transition: all 0.2s ease;
+ margin-left: auto;
+}
+
+.expanding-panel-close:hover {
+ background: var(--text-primary);
+ color: var(--bg-surface);
+ border-color: var(--text-primary);
+}
+
+.expanding-panel-body {
+ flex: 1;
+ overflow-y: auto;
+ overscroll-behavior: contain;
+ padding: var(--space-xl);
+}
+
+/* Panel content sections */
+.panel-about {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-xl);
+ max-width: 640px;
+ margin: 0 auto;
+}
+
+.panel-about-hero {
+ text-align: center;
+ padding: var(--space-xxl) 0;
+}
+
+.panel-about-logo {
+ font-size: 48px;
+ color: var(--accent);
+ margin-bottom: var(--space-md);
+ line-height: 1;
+}
+
+.panel-about-appname {
+ font-family: var(--font-display);
+ font-size: 32px;
+ font-weight: 700;
+ letter-spacing: 0.04em;
+ margin-bottom: var(--space-xs);
+}
+
+.panel-about-version {
+ font-size: 13px;
+ color: var(--text-tertiary);
+ letter-spacing: 0.02em;
+}
+
+.panel-about-desc {
+ font-size: 14px;
+ color: var(--text-secondary);
+ line-height: 1.6;
+ margin-top: var(--space-md);
+}
+
+.panel-section {
+ padding: var(--space-lg);
+ background: var(--bg-elevated);
+ border-radius: var(--radius-lg);
+ border: 1px solid var(--border-subtle);
+}
+
+.panel-section-title {
+ font-family: var(--font-display);
+ font-size: 13px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--text-tertiary);
+ margin-bottom: var(--space-md);
+}
+
+.panel-shortcuts {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-sm);
+}
+
+.panel-shortcut-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: var(--space-xs) 0;
+}
+
+.panel-shortcut-label {
+ font-size: 13px;
+ color: var(--text-secondary);
+}
+
+.panel-shortcut-key {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 24px;
+ height: 24px;
+ padding: 0 var(--space-sm);
+ font-size: 11px;
+ font-family: var(--font-body);
+ font-weight: 600;
+ color: var(--text-secondary);
+ background: var(--bg-surface);
+ border: 1px solid var(--border-default);
+ border-radius: var(--radius-sm);
+}
+
+.panel-features-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: var(--space-md);
+}
+
+.panel-feature {
+ display: flex;
+ align-items: flex-start;
+ gap: var(--space-sm);
+ font-size: 13px;
+ color: var(--text-secondary);
+ line-height: 1.4;
+}
+
+.panel-feature-icon {
+ flex-shrink: 0;
+ font-size: 16px;
+ line-height: 1.2;
+}
+
+/* Brand name clickable */
+.brand-name-btn {
+ font-family: var(--font-display);
+ font-size: 20px;
+ font-weight: 600;
+ letter-spacing: 0.02em;
+ color: var(--text-primary);
+ flex: 1;
+ background: none;
+ border: none;
cursor: pointer;
- padding: 2px;
- margin-top: 2px;
- transition: color var(--duration-fast) var(--ease-out);
+ padding: 0;
+ text-align: left;
+ transition: color 0.15s ease;
+}
+
+.brand-name-btn:hover {
+ color: var(--accent);
+}
+
+/* ═══════════════════════════════════════════════
+ SUPEREDITOR — Rich Text Editor
+ ═══════════════════════════════════════════════ */
+
+/* ── SuperDraw ── */
+.sd {
+ width: 100%;
+ height: 100%;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ background: var(--bg-surface);
+ font-family: var(--font-body);
+}
+
+/* ─ Top toolbar ─ */
+.sd-toolbar {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 6px 12px;
+ background: var(--bg-elevated);
+ border-bottom: 1px solid var(--border-subtle);
+ flex-shrink: 0;
+ overflow-x: auto;
+ z-index: 10;
}
-
-.highlight-menu-note-action:hover {
- color: var(--accent);
+.sd-tools {
+ display: flex;
+ gap: 2px;
}
-
-.highlight-menu-remove {
+.sd-tool-btn {
+ width: 34px;
+ height: 34px;
display: flex;
align-items: center;
justify-content: center;
- width: 20px;
- height: 20px;
border: none;
- border-radius: var(--radius-sm);
+ border-radius: 6px;
background: transparent;
- color: var(--text-tertiary);
- font-size: 14px;
+ color: var(--text-secondary);
+ font-size: 16px;
cursor: pointer;
- opacity: 0;
- transition: all var(--duration-fast) var(--ease-out);
- flex-shrink: 0;
- margin-top: 1px;
-}
-
-.highlight-menu-item:hover .highlight-menu-remove {
- opacity: 1;
-}
-
-.highlight-menu-remove:hover {
- color: var(--red);
- background: rgba(217, 69, 58, 0.1);
-}
-
-.highlights-menu-empty {
- padding: var(--space-lg) var(--space-md);
- text-align: center;
- font-size: 12px;
- color: var(--text-tertiary);
- font-style: italic;
-}
-
-/* ─── SELECTION ────────────────────────────────── */
-
-::selection {
- background: var(--accent);
- color: var(--text-inverse);
+ transition: background 0.15s, color 0.15s;
}
-
-/* ─── SYNC COMPONENTS ──────────────────────────── */
-
-.sync-button-container {
- position: relative;
+.sd-tool-btn:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
}
-
-.sync-button:hover {
- background: var(--bg-hover) !important;
+.sd-tool-btn.active {
+ background: var(--accent-glow);
+ color: var(--accent);
+ box-shadow: inset 0 0 0 1.5px var(--accent);
}
-.sync-button.syncing {
- cursor: wait;
+.sd-separator {
+ width: 1px;
+ height: 28px;
+ background: var(--border-subtle);
+ flex-shrink: 0;
}
-.sync-button.success svg {
- color: var(--green);
+/* ─ Colors ─ */
+.sd-colors {
+ display: flex;
+ align-items: center;
+ gap: 6px;
}
-
-.sync-button.error svg {
- color: var(--red);
+.sd-label {
+ font-size: 10px;
+ color: var(--text-tertiary);
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ white-space: nowrap;
}
-
-.sync-progress {
- transition: width 0.3s ease-out;
+.sd-color-row {
+ display: flex;
+ gap: 3px;
}
-
-.sync-status {
- position: relative;
+.sd-color-btn {
+ width: 20px;
+ height: 20px;
+ border-radius: 50%;
+ border: 2px solid transparent;
+ cursor: pointer;
+ transition: border-color 0.15s, transform 0.15s;
}
-
-.sync-status.compact {
- font-family: var(--font-body);
+.sd-color-btn:hover {
+ transform: scale(1.15);
}
-
-.sync-status:not(.compact) {
- border: 1px solid var(--border-subtle);
+.sd-color-btn.active {
+ border-color: var(--accent);
+ box-shadow: 0 0 0 2px var(--accent-glow);
}
-/* ─── AUTH / USER MENU ─────────────────────────── */
-
-.auth-divider {
+/* ─ Stroke widths ─ */
+.sd-widths {
display: flex;
align-items: center;
- gap: var(--space-md);
- margin: var(--space-lg) 0;
- color: var(--text-tertiary);
- font-size: 11px;
+ gap: 6px;
}
-
-.auth-divider::before,
-.auth-divider::after {
- content: "";
- flex: 1;
- height: 1px;
- background: var(--border-default);
+.sd-width-row {
+ display: flex;
+ gap: 3px;
}
-
-.auth-oauth-buttons {
+.sd-width-btn {
+ width: 28px;
+ height: 28px;
display: flex;
- gap: var(--space-sm);
+ align-items: center;
+ justify-content: center;
+ border: none;
+ border-radius: 5px;
+ background: transparent;
+ cursor: pointer;
+ transition: background 0.15s;
}
-
-.auth-oauth-btn {
- flex: 1;
- text-align: center;
+.sd-width-btn:hover {
+ background: var(--bg-hover);
}
-
-.auth-switch {
- text-align: center;
- font-size: 12px;
- color: var(--text-secondary);
- margin-top: var(--space-md);
+.sd-width-btn.active {
+ background: var(--accent-glow);
+ box-shadow: inset 0 0 0 1.5px var(--accent);
}
-.auth-switch-link {
- background: none;
+/* ─ Actions ─ */
+.sd-actions {
+ display: flex;
+ gap: 2px;
+ margin-left: auto;
+}
+.sd-action-btn {
+ width: 34px;
+ height: 34px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
border: none;
- color: var(--accent);
- font-family: var(--font-body);
- font-size: 12px;
+ border-radius: 6px;
+ background: transparent;
+ color: var(--text-secondary);
+ font-size: 16px;
cursor: pointer;
- text-decoration: underline;
- padding: 0;
-}
-
-.auth-switch-link:hover {
- color: var(--accent-text);
-}
-
-.auth-not-configured {
- padding: var(--space-md) 0;
+ transition: background 0.15s, color 0.15s;
}
-
-.auth-not-configured-text {
- font-size: 13px;
+.sd-action-btn:hover:not(:disabled) {
+ background: var(--bg-hover);
color: var(--text-primary);
- margin: 0 0 var(--space-sm) 0;
}
-
-.auth-not-configured-hint {
- font-size: 12px;
- color: var(--text-secondary);
- line-height: 1.6;
- margin: 0 0 var(--space-lg) 0;
+.sd-action-btn:disabled {
+ opacity: 0.35;
+ cursor: default;
}
-.auth-not-configured-hint code {
- font-size: 11px;
- background: var(--bg-root);
- padding: 1px 5px;
- border-radius: 3px;
- color: var(--accent-text);
+/* ─ Canvas wrapper (takes all remaining space) ─ */
+.sd-canvas-wrap {
+ flex: 1 1 0;
+ min-height: 0;
+ position: relative;
+ overflow: hidden;
}
-.auth-success {
- padding: var(--space-md);
- background: rgba(52, 199, 89, 0.1);
- border: 1px solid rgba(52, 199, 89, 0.3);
- border-radius: var(--radius-md);
- font-size: 12px;
- color: #34c759;
+/* ─ Canvas (fills wrapper via absolute) ─ */
+.sd-canvas {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: block;
+ touch-action: none;
}
-/* User menu */
-.user-menu {
- position: relative;
- margin-left: auto;
+/* ─ Text input overlay ─ */
+.sd-text-input {
+ position: absolute;
+ min-width: 120px;
+ min-height: 30px;
+ background: transparent;
+ border: 1.5px dashed var(--accent);
+ border-radius: 4px;
+ color: var(--text-primary);
+ font-family: sans-serif;
+ outline: none;
+ resize: none;
+ padding: 2px 4px;
+ z-index: 20;
}
-.user-menu-trigger {
+/* ─ Zoom controls ─ */
+.sd-zoom {
+ position: absolute;
+ bottom: 12px;
+ left: 12px;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ background: var(--bg-elevated);
+ border: 1px solid var(--border-subtle);
+ border-radius: 8px;
+ padding: 4px 8px;
+ z-index: 10;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.08);
+}
+.sd-zoom-btn {
+ width: 28px;
+ height: 28px;
display: flex;
align-items: center;
justify-content: center;
- width: 26px;
- height: 26px;
border: none;
- border-radius: 50%;
- background: var(--bg-hover);
+ border-radius: 5px;
+ background: transparent;
+ color: var(--text-secondary);
+ font-size: 16px;
cursor: pointer;
- padding: 0;
- overflow: hidden;
- transition: all var(--duration-fast) var(--ease-out);
-}
-
-.user-menu-trigger:hover {
- background: var(--bg-elevated);
- box-shadow: 0 0 0 2px var(--accent-dim);
+ transition: background 0.15s;
}
-
-.user-menu-avatar {
- width: 26px;
- height: 26px;
- border-radius: 50%;
- object-fit: cover;
+.sd-zoom-btn:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
}
-
-.user-menu-avatar-fallback {
+.sd-zoom-label {
font-size: 12px;
- font-weight: 600;
- color: var(--accent);
+ color: var(--text-secondary);
+ min-width: 36px;
+ text-align: center;
}
-.user-menu-login-btn {
- margin-left: auto;
+/* ─ Dark mode overrides ─ */
+.dark .sd,
+.amoled .sd {
+ background: #1e1e2e;
+}
+.dark .sd-toolbar,
+.amoled .sd-toolbar {
+ background: rgba(30,30,46,0.95);
+ border-bottom-color: rgba(255,255,255,0.06);
+}
+.dark .sd-zoom,
+.amoled .sd-zoom {
+ background: rgba(30,30,46,0.95);
+ border-color: rgba(255,255,255,0.08);
}
-
-.user-menu-dropdown {
- position: absolute;
- bottom: calc(100% + var(--space-sm));
- right: 0;
- min-width: 200px;
- background: var(--bg-surface);
- border: 1px solid var(--border-default);
- border-radius: var(--radius-md);
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
- padding: var(--space-xs) 0;
- z-index: 100;
+.dark .sd-text-input,
+.amoled .sd-text-input {
+ color: #e0e0e0;
}
-.user-menu-info {
+.super-editor-root {
display: flex;
flex-direction: column;
- gap: 2px;
- padding: var(--space-sm) var(--space-md);
-}
-
-.user-menu-name {
- font-size: 13px;
- font-weight: 600;
- color: var(--text-primary);
+ height: 100%;
+ background: var(--bg-surface);
+ font-family: var(--font-body);
}
-.user-menu-email {
- font-size: 11px;
- color: var(--text-tertiary);
+.super-editor-toolbar {
+ border-bottom: 1px solid var(--border-subtle);
+ background: var(--bg-elevated);
+ padding: 6px 10px;
+ flex-shrink: 0;
}
-.user-menu-divider {
- height: 1px;
- background: var(--border-default);
- margin: var(--space-xs) 0;
+.super-editor-toolbar-row {
+ display: flex;
+ align-items: center;
+ gap: 2px;
+ flex-wrap: wrap;
}
-.user-menu-item {
- display: block;
- width: 100%;
- padding: var(--space-sm) var(--space-md);
+.super-editor-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
border: none;
+ border-radius: 4px;
background: transparent;
- color: var(--text-primary);
- font-family: var(--font-body);
- font-size: 12.5px;
+ color: var(--text-secondary);
cursor: pointer;
- text-align: left;
- transition: background var(--duration-fast) var(--ease-out);
+ transition: all var(--duration-fast) var(--ease-out);
}
-.user-menu-item:hover {
+.super-editor-btn:hover {
background: var(--bg-hover);
+ color: var(--text-primary);
}
-.user-menu-item--danger {
- color: var(--red);
+.super-editor-btn.active {
+ background: var(--accent-glow);
+ color: var(--accent);
}
-.user-menu-item--danger:hover {
- background: rgba(217, 69, 58, 0.1);
+.super-editor-btn:disabled {
+ opacity: 0.3;
+ cursor: not-allowed;
}
-/* Settings account section */
-.settings-account {
- display: flex;
- align-items: center;
- gap: var(--space-md);
- padding: var(--space-md);
- background: var(--bg-elevated);
- border: 1px solid var(--border-subtle);
- border-radius: var(--radius-md);
- margin-bottom: var(--space-lg);
+.super-editor-sep {
+ width: 1px;
+ height: 20px;
+ background: var(--border-subtle);
+ margin: 0 4px;
+ flex-shrink: 0;
}
-.settings-account-info {
- display: flex;
- flex-direction: column;
- gap: 2px;
+.super-editor-content-wrapper {
flex: 1;
+ overflow-y: auto;
+ position: relative;
}
-.settings-account-email {
- font-size: 13px;
- font-weight: 500;
- color: var(--text-primary);
-}
-
-.settings-account-status {
- font-size: 11px;
- color: var(--text-tertiary);
+.super-editor-content {
+ height: 100%;
}
-/* ── Provider settings ── */
-
-.provider-input {
- width: 100%;
- padding: 8px 12px;
- border: 1px solid var(--border-default);
- border-radius: 6px;
- background: var(--bg-surface);
- color: var(--text-primary);
- font-size: 13px;
+.super-editor-content .tiptap {
+ outline: none;
+ min-height: 100%;
+ padding: 24px 40px;
font-family: var(--font-body);
- margin-top: 4px;
- margin-bottom: 8px;
- transition: border-color var(--duration-fast) var(--ease-out);
+ font-size: 15px;
+ line-height: 1.7;
+ color: var(--text-primary);
}
-.provider-input:focus {
- outline: none;
- border-color: var(--accent);
+.super-editor-content .tiptap > * + * {
+ margin-top: 0.75em;
}
-.provider-input::placeholder {
- color: var(--text-tertiary);
+.super-editor-content .tiptap h1 {
+ font-family: var(--font-display);
+ font-size: 2.1em;
+ font-weight: 700;
+ letter-spacing: -0.02em;
+ line-height: 1.2;
+ color: var(--text-primary);
}
-.provider-actions {
- display: flex;
- gap: 8px;
- margin-top: 8px;
- margin-bottom: 8px;
+.super-editor-content .tiptap h2 {
+ font-family: var(--font-display);
+ font-size: 1.6em;
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ line-height: 1.3;
+ color: var(--text-primary);
}
-.provider-sync-toggle {
- margin-top: 12px;
+.super-editor-content .tiptap h3 {
+ font-family: var(--font-display);
+ font-size: 1.3em;
+ font-weight: 600;
+ line-height: 1.4;
+ color: var(--text-primary);
}
-.provider-connected {
- display: flex;
- flex-direction: column;
- gap: 4px;
+.super-editor-content .tiptap h4 {
+ font-size: 1.1em;
+ font-weight: 600;
+ line-height: 1.5;
+ color: var(--text-primary);
}
-@theme inline {
- --radius-sm: calc(var(--radius) - 4px);
- --radius-md: calc(var(--radius) - 2px);
- --radius-lg: var(--radius);
- --radius-xl: calc(var(--radius) + 4px);
- --radius-2xl: calc(var(--radius) + 8px);
- --radius-3xl: calc(var(--radius) + 12px);
- --radius-4xl: calc(var(--radius) + 16px);
- --color-background: var(--background);
- --color-foreground: var(--foreground);
- --color-card: var(--card);
- --color-card-foreground: var(--card-foreground);
- --color-popover: var(--popover);
- --color-popover-foreground: var(--popover-foreground);
- --color-primary: var(--primary);
- --color-primary-foreground: var(--primary-foreground);
- --color-secondary: var(--secondary);
- --color-secondary-foreground: var(--secondary-foreground);
- --color-muted: var(--muted);
- --color-muted-foreground: var(--muted-foreground);
- --color-accent: var(--accent);
- --color-accent-foreground: var(--accent-foreground);
- --color-destructive: var(--destructive);
- --color-border: var(--border);
- --color-input: var(--input);
- --color-ring: var(--ring);
- --color-chart-1: var(--chart-1);
- --color-chart-2: var(--chart-2);
- --color-chart-3: var(--chart-3);
- --color-chart-4: var(--chart-4);
- --color-chart-5: var(--chart-5);
- --color-sidebar: var(--sidebar);
- --color-sidebar-foreground: var(--sidebar-foreground);
- --color-sidebar-primary: var(--sidebar-primary);
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
- --color-sidebar-accent: var(--sidebar-accent);
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
- --color-sidebar-border: var(--sidebar-border);
- --color-sidebar-ring: var(--sidebar-ring);
+.super-editor-content .tiptap p.is-editor-empty:first-child::before {
+ content: attr(data-placeholder);
+ color: var(--text-tertiary);
+ pointer-events: none;
+ float: left;
+ height: 0;
}
-@layer base {
- * {
- @apply border-border outline-ring/50;
- }
- body {
- @apply bg-background text-foreground;
- }
+.super-editor-content .tiptap code {
+ background: var(--bg-elevated);
+ padding: 2px 5px;
+ border-radius: 3px;
+ font-family: "JetBrains Mono", "Fira Code", monospace;
+ font-size: 0.88em;
+ border: 1px solid var(--border-subtle);
+ color: var(--accent-text);
}
-::view-transition-old(root), ::view-transition-new(root) {
- animation: none;
- mix-blend-mode: normal;
+.super-editor-content .tiptap pre {
+ background: #1e1e2e;
+ color: #cdd6f4;
+ padding: 16px 20px;
+ border-radius: 8px;
+ overflow-x: auto;
+ font-size: 13px;
+ line-height: 1.6;
+ border: 1px solid #313244;
}
-/* ═══════════════════════════════════════════════
- Audio Player — Podcast episode player
- ═══════════════════════════════════════════════ */
+.super-editor-content .tiptap pre code {
+ background: transparent;
+ padding: 0;
+ border: none;
+ color: inherit;
+ font-size: inherit;
+}
-.audio-player {
- display: flex;
- gap: var(--space-md);
- padding: var(--space-lg);
- margin: var(--space-lg) 0;
- background: var(--bg-elevated);
- border: 1px solid var(--border-subtle);
- border-radius: 12px;
+.super-editor-content .tiptap blockquote {
+ border-left: 3px solid var(--accent-secondary);
+ padding-left: 16px;
+ color: var(--text-secondary);
+ font-style: italic;
}
-.audio-player-artwork {
- width: 80px;
- height: 80px;
- border-radius: 8px;
- object-fit: cover;
- flex-shrink: 0;
+.super-editor-content .tiptap ul:not([data-type="taskList"]) {
+ padding-left: 1.25rem;
+ list-style-type: disc;
}
-.audio-player-body {
- flex: 1;
- min-width: 0;
- display: flex;
- flex-direction: column;
- gap: var(--space-sm);
+.super-editor-content .tiptap ol {
+ padding-left: 1.25rem;
+ list-style-type: decimal;
}
-.audio-player-info {
- display: flex;
- flex-direction: column;
- gap: 2px;
+.super-editor-content .tiptap ul ul {
+ list-style-type: circle;
}
-.audio-player-feed {
- font-family: var(--font-body);
- font-size: 11px;
- font-weight: 500;
- color: var(--accent-text);
- text-transform: uppercase;
- letter-spacing: 0.04em;
+.super-editor-content .tiptap ul ul ul {
+ list-style-type: square;
}
-.audio-player-title {
- font-family: var(--font-body);
- font-size: 13px;
- font-weight: 600;
- color: var(--text-primary);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
+.super-editor-content .tiptap ul[data-type="taskList"] li {
+ list-style: none;
+ display: flex;
+ align-items: flex-start;
+ gap: 8px;
}
-.audio-player-controls {
- display: flex;
- align-items: center;
- gap: var(--space-sm);
+.super-editor-content .tiptap ul[data-type="taskList"] li input[type="checkbox"] {
+ accent-color: var(--accent);
+ margin-top: 5px;
}
-.audio-player-btn {
- background: none;
+.super-editor-content .tiptap hr {
border: none;
- cursor: pointer;
- color: var(--text-secondary);
- font-family: var(--font-body);
- font-size: 12px;
- font-weight: 600;
- padding: var(--space-xs) var(--space-sm);
- border-radius: 6px;
- transition: all var(--duration-fast) var(--ease-out);
+ border-top: 1px solid var(--border-default);
+ margin: 20px 0;
}
-.audio-player-btn:hover {
- color: var(--text-primary);
- background: var(--bg-hover);
+.super-editor-content .tiptap a {
+ color: var(--blue);
+ text-decoration: underline;
+ text-underline-offset: 2px;
}
-.audio-player-btn-play {
- width: 36px;
- height: 36px;
- display: flex;
- align-items: center;
- justify-content: center;
- background: var(--accent);
- color: var(--text-inverse);
- border-radius: 50%;
- font-size: 14px;
- padding: 0;
+.super-editor-content .tiptap a:hover {
+ color: var(--accent-tertiary);
}
-.audio-player-btn-play:hover {
- background: var(--accent-dim);
- color: var(--text-inverse);
+.super-editor-content .tiptap em {
+ font-style: italic;
}
-.audio-player-speed {
- background: none;
- border: 1px solid var(--border-default);
- cursor: pointer;
- color: var(--text-secondary);
- font-family: var(--font-body);
- font-size: 11px;
- font-weight: 600;
- padding: 2px 8px;
- border-radius: 10px;
- transition: all var(--duration-fast) var(--ease-out);
+.super-editor-content .tiptap strong {
+ font-weight: 700;
}
-.audio-player-speed:hover {
- color: var(--text-primary);
- border-color: var(--border-strong);
+.super-editor-content .tiptap img {
+ max-width: 100%;
+ border-radius: 8px;
+ margin: 8px 0;
}
-.audio-player-progress {
- width: 100%;
- height: 4px;
- background: var(--border-subtle);
+.super-editor-content .tiptap mark {
border-radius: 2px;
- cursor: pointer;
- position: relative;
- transition: height var(--duration-fast) var(--ease-out);
+ padding: 1px 2px;
}
-.audio-player-progress:hover {
- height: 6px;
+.super-editor-drag-handle {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 18px;
+ height: 18px;
+ border-radius: 3px;
+ color: var(--text-tertiary);
+ cursor: grab;
+ transition: all var(--duration-fast) var(--ease-out);
}
-.audio-player-progress-fill {
- height: 100%;
- background: var(--accent);
- border-radius: 2px;
- transition: width 0.1s linear;
+.super-editor-drag-handle:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
}
-.audio-player-time {
+.super-editor-footer {
display: flex;
align-items: center;
- justify-content: space-between;
- font-family: var(--font-body);
+ gap: 16px;
+ padding: 6px 16px;
+ border-top: 1px solid var(--border-subtle);
font-size: 11px;
color: var(--text-tertiary);
- font-variant-numeric: tabular-nums;
+ flex-shrink: 0;
}
-.audio-player-volume {
- width: 60px;
- height: 3px;
- accent-color: var(--accent);
- cursor: pointer;
+/* Emoji suggestion popup */
+.super-editor-emoji-popup {
+ background: var(--bg-surface);
+ border: 1px solid var(--border-default);
+ border-radius: 8px;
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
+ max-height: 200px;
+ overflow-y: auto;
+ padding: 4px;
+ min-width: 200px;
}
-/* ─── PANEL STRIP (collapsed panel toggle) ──── */
-
-.panel-strip {
- width: 32px;
- height: 100%;
+.super-editor-emoji-item {
display: flex;
align-items: center;
- justify-content: center;
- background: var(--bg-surface);
- border-right: 1px solid var(--border-subtle);
+ gap: 8px;
+ width: 100%;
+ padding: 5px 8px;
+ border: none;
+ border-radius: 4px;
+ background: transparent;
cursor: pointer;
- flex-shrink: 0;
- transition: background var(--duration-fast) var(--ease-out),
- width var(--duration-normal) var(--ease-out);
-}
-
-.panel-strip:last-child {
- border-right: none;
- border-left: 1px solid var(--border-subtle);
+ font-size: 13px;
+ color: var(--text-primary);
+ text-align: left;
}
-.panel-strip:hover {
+.super-editor-emoji-item:hover,
+.super-editor-emoji-item.active {
background: var(--bg-hover);
- width: 40px;
}
-.panel-strip-icon {
- font-size: 16px;
+.super-editor-emoji-name {
color: var(--text-tertiary);
- transition: color var(--duration-fast) var(--ease-out),
- transform var(--duration-fast) var(--ease-out);
- user-select: none;
+ font-size: 11px;
}
-.panel-strip:hover .panel-strip-icon {
- color: var(--accent);
- transform: scale(1.15);
+.super-editor-emoji-empty {
+ padding: 8px;
+ font-size: 12px;
+ color: var(--text-tertiary);
}
-/* ─── PANEL CLOSE BUTTON ───────────────────── */
+/* File menu */
+.super-editor-filemenu {
+ position: relative;
+}
-.panel-close-btn {
+.super-editor-filemenu-trigger {
display: flex;
align-items: center;
- justify-content: center;
- width: 28px;
- height: 28px;
+ gap: 3px;
+ padding: 4px 10px;
border: none;
- border-radius: var(--radius-sm);
+ border-radius: 4px;
background: transparent;
- color: var(--text-tertiary);
- font-size: 14px;
+ color: var(--text-secondary);
+ font-family: var(--font-body);
+ font-size: 13px;
+ font-weight: 500;
cursor: pointer;
transition: all var(--duration-fast) var(--ease-out);
- flex-shrink: 0;
- margin-left: 2px;
}
-.panel-close-btn:hover {
+.super-editor-filemenu-trigger:hover {
background: var(--bg-hover);
- color: var(--text-secondary);
+ color: var(--text-primary);
}
-/* ═══════════════════════════════════════════════
- Window Effects — Acrylic / Mica / Blur
- ═══════════════════════════════════════════════ */
-
-:root {
- --window-opacity-pct: 100%;
+.super-editor-filemenu-dropdown {
+ position: absolute;
+ top: calc(100% + 4px);
+ left: 0;
+ min-width: 200px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border-default);
+ border-radius: 8px;
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
+ padding: 4px;
+ z-index: 100;
+ animation: se-menu-in 0.12s var(--ease-out);
}
-/* Entire background chain must be transparent for the native effect to show */
-html.window-effect-active,
-html.window-effect-active body,
-html.window-effect-active #root {
- background: transparent !important;
+@keyframes se-menu-in {
+ from { opacity: 0; transform: translateY(-4px); }
+ to { opacity: 1; transform: translateY(0); }
}
-html.window-effect-active .app-wrapper {
+.super-editor-filemenu-item {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ width: 100%;
+ padding: 7px 10px;
+ border: none;
+ border-radius: 5px;
background: transparent;
+ color: var(--text-primary);
+ font-family: var(--font-body);
+ font-size: 13px;
+ cursor: pointer;
+ text-align: left;
+ transition: background var(--duration-fast) var(--ease-out);
}
-html.window-effect-active .app {
- background: color-mix(in srgb, var(--bg-root) var(--window-opacity-pct), transparent);
+.super-editor-filemenu-item:hover {
+ background: var(--bg-hover);
}
-html.window-effect-active .titlebar {
- background: color-mix(in srgb, var(--bg-surface) var(--window-opacity-pct), transparent);
+.super-editor-filemenu-item span {
+ flex: 1;
}
-html.window-effect-active .titlebar--collapsed {
- background: color-mix(in srgb, var(--bg-surface) var(--window-opacity-pct), transparent);
+.super-editor-filemenu-item kbd {
+ font-family: var(--font-body);
+ font-size: 11px;
+ color: var(--text-tertiary);
+ background: var(--bg-elevated);
+ padding: 1px 6px;
+ border-radius: 3px;
+ border: 1px solid var(--border-subtle);
}
-html.window-effect-active .source-panel {
- background: color-mix(in srgb, var(--bg-surface) var(--window-opacity-pct), transparent);
+.super-editor-filemenu-divider {
+ height: 1px;
+ background: var(--border-subtle);
+ margin: 4px 6px;
}
-html.window-effect-active .feed-panel {
- background: color-mix(in srgb, var(--bg-root) var(--window-opacity-pct), transparent);
+.super-editor-filemenu-submenu-trigger {
+ position: relative;
}
-html.window-effect-active .reader-panel {
- background: color-mix(in srgb, var(--bg-surface) var(--window-opacity-pct), transparent);
+.super-editor-filemenu-submenu {
+ position: absolute;
+ left: 100%;
+ top: -4px;
+ min-width: 160px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border-default);
+ border-radius: 8px;
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
+ padding: 4px;
+ z-index: 110;
+ animation: se-menu-in 0.12s var(--ease-out);
}
-html.window-effect-active .panel-strip {
- background: color-mix(in srgb, var(--bg-surface) var(--window-opacity-pct), transparent);
+.super-editor-toast {
+ position: absolute;
+ bottom: 40px;
+ left: 50%;
+ transform: translateX(-50%);
+ background: #d32f2f;
+ color: #fff;
+ padding: 8px 16px;
+ border-radius: 8px;
+ font-size: 12px;
+ font-family: var(--font-body);
+ cursor: pointer;
+ z-index: 200;
+ animation: se-menu-in 0.15s var(--ease-out);
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
}
-html.window-effect-active .panel-strip:hover {
- background: color-mix(in srgb, var(--bg-hover) var(--window-opacity-pct), transparent);
+/* ── Palette Picker ── */
+
+.palette-picker {
+ position: absolute;
+ bottom: 100%;
+ right: 0;
+ z-index: 1000;
+ min-width: 180px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border-default);
+ border-radius: 8px;
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
+ padding: 6px;
+ margin-bottom: 4px;
+ animation: palette-fade-in 0.15s ease-out;
}
-/* ── Opacity slider in settings ── */
+@keyframes palette-fade-in {
+ from { opacity: 0; transform: translateY(4px); }
+ to { opacity: 1; transform: translateY(0); }
+}
-.settings-opacity-slider {
+.palette-picker-title {
+ padding: 4px 8px 6px;
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--text-tertiary);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+.palette-picker-list {
+ display: flex;
+ flex-direction: column;
+ gap: 1px;
+}
+
+.palette-picker-item {
display: flex;
align-items: center;
- gap: var(--space-md);
- margin-top: var(--space-sm);
+ gap: 8px;
+ padding: 6px 8px;
+ border: none;
+ border-radius: 5px;
+ background: transparent;
+ color: var(--text-secondary);
+ font-size: 12px;
+ font-family: var(--font-body);
+ cursor: pointer;
+ transition: all var(--duration-fast) var(--ease-out);
}
-.settings-opacity-slider input[type="range"] {
- flex: 1;
- height: 4px;
- appearance: none;
- -webkit-appearance: none;
- background: var(--border-default);
- border-radius: 2px;
- outline: none;
- cursor: pointer;
+.palette-picker-item:hover {
+ background: var(--bg-hover);
+ color: var(--text-primary);
}
-.settings-opacity-slider input[type="range"]::-webkit-slider-thumb {
- appearance: none;
- -webkit-appearance: none;
- width: 14px;
- height: 14px;
- border-radius: 50%;
- background: var(--accent);
- border: 2px solid var(--bg-surface);
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
- cursor: pointer;
- transition: transform var(--duration-fast) var(--ease-out);
+.palette-picker-item.active {
+ background: var(--accent-glow);
+ color: var(--accent-text);
}
-.settings-opacity-slider input[type="range"]::-webkit-slider-thumb:hover {
- transform: scale(1.2);
+.palette-picker-dots {
+ display: flex;
+ gap: 3px;
+ flex-shrink: 0;
}
-.settings-opacity-value {
- font-family: var(--font-body);
- font-size: 12px;
- font-variant-numeric: tabular-nums;
- color: var(--text-secondary);
- min-width: 36px;
- text-align: right;
+.palette-dot {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ border: 1px solid rgba(0, 0, 0, 0.08);
}
-/* ─── Stats modal ─────────────────────────────── */
+.dark .palette-dot,
+.amoled .palette-dot {
+ border-color: rgba(255, 255, 255, 0.08);
+}
-.stats-modal {
- max-width: 520px;
+.palette-picker-name {
+ flex: 1;
+ font-weight: 500;
}
-.stats-body {
- padding: var(--space-lg) var(--space-xl);
- display: flex;
- flex-direction: column;
- gap: var(--space-lg);
+.palette-picker-check {
+ font-size: 11px;
+ color: var(--accent);
+ font-weight: 700;
}
-.stats-grid {
+/* Inline version (for SettingsModal) */
+.palette-picker-inline {
display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: var(--space-md);
+ grid-template-columns: 1fr 1fr;
+ gap: 4px;
}
-.stats-card {
- background: var(--bg-elevated);
+.palette-picker-inline .palette-picker-item {
+ padding: 8px 10px;
border: 1px solid var(--border-subtle);
- border-radius: var(--radius-md);
- padding: var(--space-md);
- text-align: center;
+ border-radius: 6px;
}
-.stats-card-value {
- font-family: var(--font-display);
- font-size: 22px;
- font-weight: 700;
- color: var(--text-primary);
- letter-spacing: -0.02em;
- line-height: 1.2;
+.palette-picker-inline .palette-picker-item.active {
+ border-color: var(--accent);
}
-.stats-card-total {
- font-size: 14px;
- font-weight: 400;
- color: var(--text-tertiary);
+/* Palette button in SourcePanel header */
+.palette-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 26px;
+ height: 26px;
+ border: none;
+ border-radius: 5px;
+ background: transparent;
+ cursor: pointer;
+ transition: all var(--duration-fast) var(--ease-out);
+ position: relative;
}
-.stats-card-label {
- font-family: var(--font-body);
- font-size: 11px;
- color: var(--text-secondary);
- margin-top: var(--space-xs);
- text-transform: uppercase;
- letter-spacing: 0.04em;
+.palette-btn:hover {
+ background: var(--bg-hover);
}
-.stats-bar {
- height: 4px;
- background: var(--border-subtle);
- border-radius: 2px;
- margin-top: var(--space-sm);
- overflow: hidden;
+.palette-btn-dots {
+ display: flex;
+ gap: 2px;
}
-.stats-bar-fill {
- height: 100%;
- background: var(--accent);
- border-radius: 2px;
- transition: width 0.4s ease;
+.palette-btn-dots .palette-dot {
+ width: 6px;
+ height: 6px;
}
-.stats-bar-label {
- font-family: var(--font-body);
- font-size: 10px;
- color: var(--text-tertiary);
- margin-top: 2px;
- text-align: right;
-}
+/* ── Editor file list (panel 1) ── */
-.stats-section {
+.editor-filelist {
display: flex;
flex-direction: column;
- gap: var(--space-sm);
+ height: 100%;
}
-.stats-section-title {
+.editor-filelist-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 10px 12px 8px;
+ border-bottom: 1px solid var(--border-subtle);
+}
+
+.editor-filelist-title {
font-family: var(--font-display);
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
letter-spacing: -0.01em;
- padding-bottom: var(--space-xs);
- border-bottom: 1px solid var(--border-subtle);
}
-.stats-source-row {
+.editor-filelist-add {
display: flex;
align-items: center;
- gap: var(--space-sm);
-}
-
-.stats-source-label {
- font-family: var(--font-body);
- font-size: 12px;
+ justify-content: center;
+ width: 26px;
+ height: 26px;
+ border: none;
+ border-radius: 5px;
+ background: transparent;
color: var(--text-secondary);
- min-width: 72px;
- flex-shrink: 0;
+ cursor: pointer;
+ transition: all var(--duration-fast) var(--ease-out);
}
-.stats-source-bar {
- flex: 1;
- height: 6px;
- background: var(--border-subtle);
- border-radius: 3px;
- overflow: hidden;
+.editor-filelist-add:hover {
+ background: var(--accent-glow);
+ color: var(--accent);
}
-.stats-source-bar-fill {
- height: 100%;
- background: var(--accent);
- border-radius: 3px;
- transition: width 0.4s ease;
+.editor-filelist-items {
+ flex: 1;
+ overflow-y: auto;
+ padding: 6px;
}
-.stats-source-count {
- font-family: var(--font-body);
+.editor-filelist-empty {
+ padding: 24px 12px;
+ text-align: center;
font-size: 12px;
- font-variant-numeric: tabular-nums;
color: var(--text-tertiary);
- min-width: 28px;
- text-align: right;
}
-.stats-feed-row {
+.editor-filelist-item {
display: flex;
align-items: center;
- gap: var(--space-sm);
- padding: var(--space-xs) 0;
+ gap: 8px;
+ padding: 8px 10px;
+ border-radius: 6px;
+ cursor: pointer;
+ transition: all var(--duration-fast) var(--ease-out);
}
-.stats-feed-rank {
- font-family: var(--font-body);
- font-size: 12px;
+.editor-filelist-item:hover {
+ background: var(--bg-hover);
+}
+
+.editor-filelist-item.active {
+ background: var(--accent-glow);
+}
+
+.editor-filelist-item.active .editor-filelist-name {
+ color: var(--accent);
+}
+
+.editor-filelist-icon {
+ flex-shrink: 0;
color: var(--text-tertiary);
- min-width: 18px;
}
-.stats-feed-name {
- font-family: var(--font-body);
- font-size: 12px;
- color: var(--text-primary);
+.editor-filelist-item.active .editor-filelist-icon {
+ color: var(--accent);
+}
+
+.editor-filelist-info {
flex: 1;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 1px;
+}
+
+.editor-filelist-name {
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--text-primary);
+ white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
- white-space: nowrap;
}
-.stats-feed-count {
- font-family: var(--font-body);
- font-size: 12px;
- font-variant-numeric: tabular-nums;
- color: var(--accent-text);
- font-weight: 600;
+.editor-filelist-date {
+ font-size: 10px;
+ color: var(--text-tertiary);
}
-.stats-close-btn {
- align-self: center;
- padding: var(--space-sm) var(--space-xl);
- border: 1px solid var(--border-default);
- border-radius: var(--radius-md);
+.editor-filelist-actions {
+ display: flex;
+ gap: 2px;
+ opacity: 0;
+ transition: opacity var(--duration-fast) var(--ease-out);
+}
+
+.editor-filelist-item:hover .editor-filelist-actions {
+ opacity: 1;
+}
+
+.editor-filelist-action {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 22px;
+ height: 22px;
+ border: none;
+ border-radius: 4px;
background: transparent;
- color: var(--text-secondary);
+ color: var(--text-tertiary);
+ cursor: pointer;
+ transition: all var(--duration-fast) var(--ease-out);
+}
+
+.editor-filelist-action:hover {
+ background: var(--bg-active);
+ color: var(--text-primary);
+}
+
+.editor-filelist-action-delete:hover {
+ color: var(--red);
+}
+
+.editor-filelist-rename {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ flex: 1;
+}
+
+.editor-filelist-rename-input {
+ flex: 1;
+ padding: 3px 6px;
+ border: 1px solid var(--accent);
+ border-radius: 4px;
+ background: var(--bg-surface);
+ color: var(--text-primary);
font-family: var(--font-body);
- font-size: 13px;
+ font-size: 12px;
+ outline: none;
+}
+
+.editor-filelist-rename-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 20px;
+ height: 20px;
+ border: none;
+ border-radius: 3px;
+ background: transparent;
+ color: var(--text-tertiary);
cursor: pointer;
- transition: all var(--duration-fast) var(--ease-out);
}
-.stats-close-btn:hover {
- background: var(--bg-hover);
+.editor-filelist-rename-btn:hover {
+ background: var(--bg-active);
color: var(--text-primary);
}
-/* ── Expanding Panel ── */
+/* ═══════════════════════════════════════════════
+ COMMAND PALETTE
+ ═══════════════════════════════════════════════ */
-.expanding-panel-overlay {
+.cmd-palette-backdrop {
position: fixed;
inset: 0;
- z-index: 900;
- background: rgba(0, 0, 0, 0.25);
- backdrop-filter: blur(12px);
- -webkit-backdrop-filter: blur(12px);
+ z-index: 9999;
+ background: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: flex-start;
+ justify-content: center;
+ padding-top: 15vh;
+ animation: fadeIn 100ms ease-out;
}
-.expanding-panel {
- position: fixed;
- inset: 0;
- z-index: 910;
+@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
+
+.cmd-palette {
+ width: 520px;
+ max-height: 420px;
+ background: var(--bg-surface);
+ border: 1px solid var(--border-default);
+ border-radius: 12px;
+ box-shadow: 0 24px 64px rgba(0,0,0,0.4), 0 0 0 1px var(--border-subtle);
display: flex;
flex-direction: column;
overflow: hidden;
- background: var(--bg-surface);
- color: var(--text-primary);
+ animation: slideDown 150ms ease-out;
}
-.expanding-panel-header {
+@keyframes slideDown {
+ from { opacity: 0; transform: translateY(-8px) scale(0.98); }
+ to { opacity: 1; transform: translateY(0) scale(1); }
+}
+
+.cmd-palette-input-wrap {
display: flex;
align-items: center;
- justify-content: space-between;
- padding: var(--space-lg) var(--space-xl);
+ gap: 8px;
+ padding: 12px 16px;
border-bottom: 1px solid var(--border-subtle);
- flex-shrink: 0;
}
-.expanding-panel-title {
- font-family: var(--font-display);
- font-size: 18px;
- font-weight: 600;
- letter-spacing: 0.02em;
+.cmd-palette-icon {
+ color: var(--accent);
+ font-size: 14px;
+ flex-shrink: 0;
}
-.expanding-panel-close {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 32px;
- height: 32px;
- border-radius: var(--radius-md);
- border: 1px solid var(--border-default);
- background: var(--bg-elevated);
- color: var(--text-secondary);
- cursor: pointer;
+.cmd-palette-input {
+ flex: 1;
+ background: transparent;
+ border: none;
+ outline: none;
+ color: var(--text-primary);
+ font-family: var(--font-body);
font-size: 14px;
- transition: all 0.2s ease;
- margin-left: auto;
+ caret-color: var(--accent);
}
-.expanding-panel-close:hover {
- background: var(--text-primary);
- color: var(--bg-surface);
- border-color: var(--text-primary);
-}
+.cmd-palette-input::placeholder { color: var(--text-tertiary); }
-.expanding-panel-body {
- flex: 1;
- overflow-y: auto;
- overscroll-behavior: contain;
- padding: var(--space-xl);
+.cmd-palette-esc {
+ font-size: 10px;
+ padding: 2px 6px;
+ border-radius: 4px;
+ background: var(--bg-elevated);
+ border: 1px solid var(--border-default);
+ color: var(--text-tertiary);
+ font-family: var(--font-body);
}
-/* Panel content sections */
-.panel-about {
- display: flex;
- flex-direction: column;
- gap: var(--space-xl);
- max-width: 640px;
- margin: 0 auto;
+.cmd-palette-list {
+ overflow-y: auto;
+ padding: 4px 0;
+ flex: 1;
}
-.panel-about-hero {
+.cmd-palette-empty {
+ padding: 24px;
text-align: center;
- padding: var(--space-xxl) 0;
+ color: var(--text-tertiary);
+ font-size: 13px;
}
-.panel-about-logo {
- font-size: 48px;
- color: var(--accent);
- margin-bottom: var(--space-md);
- line-height: 1;
+.cmd-palette-group {
+ padding: 8px 16px 4px;
+ font-size: 10px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ color: var(--text-tertiary);
}
-.panel-about-appname {
- font-family: var(--font-display);
- font-size: 32px;
- font-weight: 700;
- letter-spacing: 0.04em;
- margin-bottom: var(--space-xs);
+.cmd-palette-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+ padding: 6px 16px;
+ border: none;
+ background: transparent;
+ color: var(--text-primary);
+ font-family: var(--font-body);
+ font-size: 13px;
+ cursor: pointer;
+ text-align: left;
+ transition: background 80ms;
}
-.panel-about-version {
- font-size: 13px;
- color: var(--text-tertiary);
- letter-spacing: 0.02em;
+.cmd-palette-item:hover,
+.cmd-palette-item--selected {
+ background: var(--bg-hover);
}
-.panel-about-desc {
- font-size: 14px;
- color: var(--text-secondary);
- line-height: 1.6;
- margin-top: var(--space-md);
+.cmd-palette-item--selected {
+ background: var(--accent-glow);
}
-.panel-section {
- padding: var(--space-lg);
+.cmd-palette-item-label { flex: 1; }
+
+.cmd-palette-item-shortcut {
+ font-size: 11px;
+ padding: 2px 6px;
+ border-radius: 4px;
background: var(--bg-elevated);
- border-radius: var(--radius-lg);
border: 1px solid var(--border-subtle);
+ color: var(--text-secondary);
+ font-family: var(--font-body);
+ margin-left: 12px;
+ white-space: nowrap;
}
-.panel-section-title {
- font-family: var(--font-display);
- font-size: 13px;
- font-weight: 600;
- letter-spacing: 0.06em;
- text-transform: uppercase;
- color: var(--text-tertiary);
- margin-bottom: var(--space-md);
+/* ═══════════════════════════════════════════════
+ SHORTCUTS OVERLAY
+ ═══════════════════════════════════════════════ */
+
+.shortcuts-backdrop {
+ position: fixed;
+ inset: 0;
+ z-index: 9998;
+ background: rgba(0, 0, 0, 0.6);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ animation: fadeIn 100ms ease-out;
}
-.panel-shortcuts {
+.shortcuts-overlay {
+ width: 600px;
+ max-height: 80vh;
+ background: var(--bg-surface);
+ border: 1px solid var(--border-default);
+ border-radius: 12px;
+ box-shadow: 0 24px 64px rgba(0,0,0,0.4);
+ overflow: hidden;
display: flex;
flex-direction: column;
- gap: var(--space-sm);
}
-.panel-shortcut-row {
+.shortcuts-header {
display: flex;
align-items: center;
justify-content: space-between;
- padding: var(--space-xs) 0;
+ padding: 16px 20px;
+ border-bottom: 1px solid var(--border-subtle);
}
-.panel-shortcut-label {
- font-size: 13px;
- color: var(--text-secondary);
+.shortcuts-title {
+ font-family: var(--font-display);
+ font-size: 16px;
+ font-weight: 600;
+ color: var(--text-primary);
}
-.panel-shortcut-key {
- display: inline-flex;
+.shortcuts-close {
+ width: 28px;
+ height: 28px;
+ display: flex;
align-items: center;
justify-content: center;
- min-width: 24px;
- height: 24px;
- padding: 0 var(--space-sm);
- font-size: 11px;
- font-family: var(--font-body);
- font-weight: 600;
- color: var(--text-secondary);
- background: var(--bg-surface);
- border: 1px solid var(--border-default);
- border-radius: var(--radius-sm);
+ border-radius: 6px;
+ border: none;
+ background: transparent;
+ color: var(--text-tertiary);
+ cursor: pointer;
+ font-size: 14px;
+ transition: all 150ms;
}
-.panel-features-grid {
+.shortcuts-close:hover { background: var(--bg-hover); color: var(--text-primary); }
+
+.shortcuts-grid {
display: grid;
- grid-template-columns: repeat(2, 1fr);
- gap: var(--space-md);
+ grid-template-columns: 1fr 1fr;
+ gap: 4px 24px;
+ padding: 16px 20px;
+ overflow-y: auto;
}
-.panel-feature {
- display: flex;
- align-items: flex-start;
- gap: var(--space-sm);
- font-size: 13px;
- color: var(--text-secondary);
- line-height: 1.4;
+.shortcuts-section { margin-bottom: 12px; }
+
+.shortcuts-category {
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.4px;
+ color: var(--accent-text);
+ margin-bottom: 6px;
}
-.panel-feature-icon {
- flex-shrink: 0;
- font-size: 16px;
- line-height: 1.2;
+.shortcuts-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 4px 0;
}
-/* Brand name clickable */
-.brand-name-btn {
- font-family: var(--font-display);
- font-size: 20px;
- font-weight: 600;
- letter-spacing: 0.02em;
- color: var(--text-primary);
- flex: 1;
- background: none;
- border: none;
- cursor: pointer;
- padding: 0;
- text-align: left;
- transition: color 0.15s ease;
+.shortcuts-label {
+ font-size: 12px;
+ color: var(--text-secondary);
}
-.brand-name-btn:hover {
- color: var(--accent);
+.shortcuts-kbd {
+ font-size: 11px;
+ padding: 2px 7px;
+ border-radius: 4px;
+ background: var(--bg-elevated);
+ border: 1px solid var(--border-default);
+ color: var(--text-primary);
+ font-family: var(--font-body);
+ min-width: 24px;
+ text-align: center;
}
diff --git a/src/main.tsx b/src/main.tsx
index e3f5f9e..4c8a359 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -12,11 +12,11 @@ import { ProProvider } from './contexts/ProContext'
const opacity = Number(localStorage.getItem('superflux_window_opacity') || '85');
if (effect !== 'none') {
- const isDark = document.documentElement.classList.contains('dark');
- const isSepia = document.documentElement.classList.contains('sepia');
+ const isAmoled = document.documentElement.classList.contains('amoled');
+ const isDark = isAmoled || document.documentElement.classList.contains('dark');
let r: number, g: number, b: number;
- if (isDark) { r = 20; g = 20; b = 20; }
- else if (isSepia) { r = 210; g = 195; b = 170; }
+ if (isAmoled) { r = 0; g = 0; b = 0; }
+ else if (isDark) { r = 20; g = 20; b = 20; }
else { r = 240; g = 240; b = 240; }
const alpha = Math.round((opacity / 100) * 200);
diff --git a/src/services/bookmarkService.ts b/src/services/bookmarkService.ts
new file mode 100644
index 0000000..c8324db
--- /dev/null
+++ b/src/services/bookmarkService.ts
@@ -0,0 +1,103 @@
+import { supabase, isSupabaseConfigured } from '../lib/supabase';
+
+export interface WebBookmark {
+ id: string;
+ url: string;
+ title: string;
+ excerpt: string | null;
+ image: string | null;
+ favicon: string | null;
+ author: string | null;
+ site_name: string | null;
+ tags: string[];
+ note: string | null;
+ is_read: boolean;
+ source: 'chrome' | 'desktop' | 'mobile';
+ created_at: string;
+ updated_at: string;
+}
+
+export async function fetchBookmarks(userId: string): Promise {
+ if (!isSupabaseConfigured) return [];
+
+ const { data, error } = await supabase
+ .from('bookmarks')
+ .select('*')
+ .eq('user_id', userId)
+ .order('created_at', { ascending: false });
+
+ if (error) {
+ console.error('[bookmarks] fetch error:', error);
+ return [];
+ }
+
+ return data ?? [];
+}
+
+export async function addBookmark(
+ userId: string,
+ bookmark: Omit
+): Promise {
+ if (!isSupabaseConfigured) return null;
+
+ const { data, error } = await supabase
+ .from('bookmarks')
+ .upsert({
+ ...bookmark,
+ user_id: userId,
+ }, { onConflict: 'id,user_id' })
+ .select()
+ .single();
+
+ if (error) {
+ console.error('[bookmarks] add error:', error);
+ return null;
+ }
+
+ return data;
+}
+
+export async function removeBookmark(userId: string, bookmarkId: string): Promise {
+ if (!isSupabaseConfigured) return false;
+
+ const { error } = await supabase
+ .from('bookmarks')
+ .delete()
+ .eq('id', bookmarkId)
+ .eq('user_id', userId);
+
+ if (error) {
+ console.error('[bookmarks] remove error:', error);
+ return false;
+ }
+
+ return true;
+}
+
+export async function toggleBookmarkRead(userId: string, bookmarkId: string, isRead: boolean): Promise {
+ if (!isSupabaseConfigured) return false;
+
+ const { error } = await supabase
+ .from('bookmarks')
+ .update({ is_read: isRead })
+ .eq('id', bookmarkId)
+ .eq('user_id', userId);
+
+ if (error) {
+ console.error('[bookmarks] toggle read error:', error);
+ return false;
+ }
+
+ return true;
+}
+
+export function generateBookmarkId(url: string): string {
+ // Simple hash for desktop (sync compatible with Chrome extension's bk- prefix)
+ let hash = 0;
+ for (let i = 0; i < url.length; i++) {
+ const chr = url.charCodeAt(i);
+ hash = ((hash << 5) - hash) + chr;
+ hash |= 0;
+ }
+ return `bk-${Math.abs(hash).toString(16).padStart(16, '0').slice(0, 16)}`;
+}
diff --git a/src/services/editorDocService.ts b/src/services/editorDocService.ts
new file mode 100644
index 0000000..4a132bb
--- /dev/null
+++ b/src/services/editorDocService.ts
@@ -0,0 +1,112 @@
+import { supabase, isSupabaseConfigured } from '../lib/supabase';
+
+export interface EditorDocRow {
+ id: string;
+ title: string;
+ content: string;
+ folder: string | null;
+ created_at: string;
+ updated_at: string;
+}
+
+export async function fetchEditorDocs(userId: string): Promise {
+ if (!isSupabaseConfigured) return [];
+
+ const { data, error } = await supabase
+ .from('editor_documents')
+ .select('*')
+ .eq('user_id', userId)
+ .order('updated_at', { ascending: false });
+
+ if (error) {
+ console.error('[editor-docs] fetch error:', error);
+ return [];
+ }
+
+ return data ?? [];
+}
+
+export async function upsertEditorDoc(
+ userId: string,
+ doc: { id: string; title: string; content: string; folder?: string | null }
+): Promise {
+ if (!isSupabaseConfigured) return null;
+
+ const { data, error } = await supabase
+ .from('editor_documents')
+ .upsert({
+ id: doc.id,
+ user_id: userId,
+ title: doc.title,
+ content: doc.content,
+ folder: doc.folder ?? null,
+ }, { onConflict: 'id,user_id' })
+ .select()
+ .single();
+
+ if (error) {
+ console.error('[editor-docs] upsert error:', error);
+ return null;
+ }
+
+ return data;
+}
+
+export async function removeEditorDoc(userId: string, docId: string): Promise {
+ if (!isSupabaseConfigured) return false;
+
+ const { error } = await supabase
+ .from('editor_documents')
+ .delete()
+ .eq('id', docId)
+ .eq('user_id', userId);
+
+ if (error) {
+ console.error('[editor-docs] remove error:', error);
+ return false;
+ }
+
+ return true;
+}
+
+export async function updateEditorDocContent(
+ userId: string,
+ docId: string,
+ content: string
+): Promise {
+ if (!isSupabaseConfigured) return false;
+
+ const { error } = await supabase
+ .from('editor_documents')
+ .update({ content })
+ .eq('id', docId)
+ .eq('user_id', userId);
+
+ if (error) {
+ console.error('[editor-docs] update content error:', error);
+ return false;
+ }
+
+ return true;
+}
+
+export async function updateEditorDocMeta(
+ userId: string,
+ docId: string,
+ meta: { title?: string; folder?: string | null }
+): Promise {
+ if (!isSupabaseConfigured) return false;
+
+ const { error } = await supabase
+ .from('editor_documents')
+ .update(meta)
+ .eq('id', docId)
+ .eq('user_id', userId);
+
+ if (error) {
+ console.error('[editor-docs] update meta error:', error);
+ return false;
+ }
+
+ return true;
+}
diff --git a/src/services/noteService.ts b/src/services/noteService.ts
new file mode 100644
index 0000000..a170093
--- /dev/null
+++ b/src/services/noteService.ts
@@ -0,0 +1,151 @@
+import { supabase, isSupabaseConfigured } from '../lib/supabase';
+
+export interface NoteRow {
+ id: string;
+ title: string;
+ content: string;
+ folder: string | null;
+ sticky_x: number | null;
+ sticky_y: number | null;
+ sticky_rotation: number | null;
+ sticky_z_index: number | null;
+ sticky_color: string | null;
+ sticky_width: number | null;
+ sticky_height: number | null;
+ created_at: string;
+ updated_at: string;
+}
+
+export async function fetchNotes(userId: string): Promise {
+ console.log('[notes] isSupabaseConfigured:', isSupabaseConfigured, 'userId:', userId);
+ if (!isSupabaseConfigured) { console.warn('[notes] Supabase not configured'); return []; }
+
+ const { data, error, status } = await supabase
+ .from('notes')
+ .select('*')
+ .eq('user_id', userId)
+ .order('updated_at', { ascending: false });
+
+ console.log('[notes] fetch response — status:', status, 'data:', data?.length, 'error:', error);
+
+ if (error) {
+ console.error('[notes] fetch error:', error);
+ return [];
+ }
+
+ return data ?? [];
+}
+
+export async function upsertNote(
+ userId: string,
+ note: {
+ id: string;
+ title: string;
+ content: string;
+ folder?: string | null;
+ sticky_x?: number | null;
+ sticky_y?: number | null;
+ sticky_rotation?: number | null;
+ sticky_z_index?: number | null;
+ sticky_color?: string | null;
+ sticky_width?: number | null;
+ sticky_height?: number | null;
+ }
+): Promise {
+ if (!isSupabaseConfigured) return null;
+
+ const { data, error } = await supabase
+ .from('notes')
+ .upsert({
+ id: note.id,
+ user_id: userId,
+ title: note.title,
+ content: note.content,
+ folder: note.folder ?? null,
+ sticky_x: note.sticky_x ?? null,
+ sticky_y: note.sticky_y ?? null,
+ sticky_rotation: note.sticky_rotation ?? null,
+ sticky_z_index: note.sticky_z_index ?? null,
+ sticky_color: note.sticky_color ?? null,
+ sticky_width: note.sticky_width ?? null,
+ sticky_height: note.sticky_height ?? null,
+ }, { onConflict: 'id,user_id' })
+ .select()
+ .single();
+
+ if (error) {
+ console.error('[notes] upsert error:', error);
+ return null;
+ }
+
+ return data;
+}
+
+export async function removeNote(userId: string, noteId: string): Promise {
+ if (!isSupabaseConfigured) return false;
+
+ const { error } = await supabase
+ .from('notes')
+ .delete()
+ .eq('id', noteId)
+ .eq('user_id', userId);
+
+ if (error) {
+ console.error('[notes] remove error:', error);
+ return false;
+ }
+
+ return true;
+}
+
+export async function updateNoteContent(
+ userId: string,
+ noteId: string,
+ content: string
+): Promise {
+ if (!isSupabaseConfigured) return false;
+
+ const { error } = await supabase
+ .from('notes')
+ .update({ content })
+ .eq('id', noteId)
+ .eq('user_id', userId);
+
+ if (error) {
+ console.error('[notes] update content error:', error);
+ return false;
+ }
+
+ return true;
+}
+
+export async function updateNoteMeta(
+ userId: string,
+ noteId: string,
+ meta: {
+ title?: string;
+ folder?: string | null;
+ sticky_x?: number | null;
+ sticky_y?: number | null;
+ sticky_rotation?: number | null;
+ sticky_z_index?: number | null;
+ sticky_color?: string | null;
+ sticky_width?: number | null;
+ sticky_height?: number | null;
+ }
+): Promise {
+ if (!isSupabaseConfigured) return false;
+
+ const { error } = await supabase
+ .from('notes')
+ .update(meta)
+ .eq('id', noteId)
+ .eq('user_id', userId);
+
+ if (error) {
+ console.error('[notes] update meta error:', error);
+ return false;
+ }
+
+ return true;
+}
diff --git a/src/services/pandocService.ts b/src/services/pandocService.ts
new file mode 100644
index 0000000..e43355f
--- /dev/null
+++ b/src/services/pandocService.ts
@@ -0,0 +1,76 @@
+import { invoke } from '@tauri-apps/api/core';
+import { isTauri } from '../lib/tauriFetch';
+
+/** Check if pandoc is available (Tauri only) */
+export async function isPandocAvailable(): Promise {
+ if (!isTauri()) return false;
+ try {
+ await invoke('pandoc_check');
+ return true;
+ } catch {
+ return false;
+ }
+}
+
+/** Convert a File (docx/pdf) to HTML via pandoc */
+export async function importWithPandoc(file: File): Promise {
+ const buffer = await file.arrayBuffer();
+ const bytes = new Uint8Array(buffer);
+ const base64 = uint8ToBase64(bytes);
+
+ if (isTauri()) {
+ return invoke('pandoc_import', {
+ base64Data: base64,
+ filename: file.name,
+ });
+ }
+
+ // Web fallback: only .docx via mammoth
+ if (file.name.endsWith('.docx')) {
+ const mammoth = await import('mammoth');
+ const result = await mammoth.convertToHtml({ arrayBuffer: buffer });
+ return result.value;
+ }
+
+ throw new Error('Import PDF nécessite l\'application desktop avec pandoc installé.');
+}
+
+/** Export HTML to docx/pdf via pandoc, returns a downloadable Blob */
+export async function exportWithPandoc(
+ html: string,
+ format: 'docx' | 'pdf',
+): Promise {
+ if (!isTauri()) {
+ throw new Error('L\'export nécessite l\'application desktop avec pandoc installé.');
+ }
+
+ const base64 = await invoke('pandoc_export', {
+ htmlContent: html,
+ format,
+ });
+
+ const bytes = base64ToUint8(base64);
+ const mime = format === 'docx'
+ ? 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
+ : 'application/pdf';
+ return new Blob([bytes.buffer as ArrayBuffer], { type: mime });
+}
+
+// ── helpers ──
+
+function uint8ToBase64(bytes: Uint8Array): string {
+ let binary = '';
+ for (let i = 0; i < bytes.length; i++) {
+ binary += String.fromCharCode(bytes[i]);
+ }
+ return btoa(binary);
+}
+
+function base64ToUint8(b64: string): Uint8Array {
+ const binary = atob(b64);
+ const bytes = new Uint8Array(binary.length);
+ for (let i = 0; i < binary.length; i++) {
+ bytes[i] = binary.charCodeAt(i);
+ }
+ return bytes;
+}
diff --git a/src/services/rssService.ts b/src/services/rssService.ts
index b18ca6b..58b92c7 100644
--- a/src/services/rssService.ts
+++ b/src/services/rssService.ts
@@ -356,6 +356,9 @@ export async function fetchAndParseFeed(
feed: Feed,
existingItemIds: Set
): Promise {
+ // Skip feeds without a valid URL (e.g. virtual feeds like chrome-extension bookmarks)
+ if (!feed.url || feed.url.trim() === '') return [];
+
const channel = await fetchFeed(feed.url);
// Auto-detect podcast: if feed has audio enclosures, treat as podcast
diff --git a/src/services/syncService.ts b/src/services/syncService.ts
index 894a8f3..e4e4a75 100644
--- a/src/services/syncService.ts
+++ b/src/services/syncService.ts
@@ -13,6 +13,8 @@ function dispatchSyncEvent() {
const STORAGE_KEYS = {
FEEDS: 'superflux_feeds',
ITEMS: 'superflux_items',
+ SYNCED_FEED_IDS: 'superflux_synced_feed_ids',
+ SYNCED_ITEM_IDS: 'superflux_synced_item_ids',
};
function loadLocal(key: string, fallback: T): T {
@@ -219,12 +221,32 @@ export const SyncService = {
.eq('user_id', userId);
if (itemsErr) throw itemsErr;
+ // ---- Detect remote deletions (feeds/items previously synced but no longer in Supabase) ----
+ const prevSyncedFeedIds = new Set(loadLocal(STORAGE_KEYS.SYNCED_FEED_IDS, []));
+ const prevSyncedItemIds = new Set(loadLocal(STORAGE_KEYS.SYNCED_ITEM_IDS, []));
+ const remoteFeedIdSet = new Set((remoteFeeds ?? []).map((r: Record) => r.id as string));
+ const remoteItemIdSet = new Set((remoteItems ?? []).map((r: Record) => r.id as string));
+ const remoteItemUrlSet = new Set((remoteItems ?? []).map((r: Record) => r.url as string).filter(Boolean));
+
+ // Feeds previously synced but now missing from Supabase → deleted remotely
+ const deletedFeedIds = new Set();
+ for (const id of prevSyncedFeedIds) {
+ if (!remoteFeedIdSet.has(id)) deletedFeedIds.add(id);
+ }
+ // Items previously synced but now missing from Supabase → deleted remotely
+ const deletedItemIds = new Set();
+ for (const id of prevSyncedItemIds) {
+ if (!remoteItemIdSet.has(id)) deletedItemIds.add(id);
+ }
+
// ---- Merge feeds (last-write-wins on updated_at, match by id OR url) ----
const localFeeds: Feed[] = loadLocal(STORAGE_KEYS.FEEDS, []);
const feedMap = new Map();
const urlToFeedId = new Map(); // URL → canonical local ID
for (const lf of localFeeds) {
+ // Skip feeds that were deleted remotely
+ if (deletedFeedIds.has(lf.id)) continue;
feedMap.set(lf.id, lf);
if (lf.url) urlToFeedId.set(lf.url, lf.id);
}
@@ -269,6 +291,9 @@ export const SyncService = {
const urlToId = new Map(); // URL → canonical ID for dedup
for (const li of localItems) {
+ // Skip items deleted remotely or belonging to deleted feeds
+ if (deletedItemIds.has(li.id)) continue;
+ if (deletedFeedIds.has(li.feedId)) continue;
itemMap.set(li.id, li);
if (li.url) urlToId.set(li.url, li.id);
}
@@ -312,9 +337,8 @@ export const SyncService = {
saveLocal(STORAGE_KEYS.FEEDS, mergedFeeds);
saveLocal(STORAGE_KEYS.ITEMS, mergedItems);
- // ---- Push local-only feeds to remote (skip feeds already synced by URL) ----
- const remoteFeedIds = new Set((remoteFeeds ?? []).map((r: Record) => r.id as string));
- const feedsToPush = mergedFeeds.filter(f => !remoteFeedIds.has(f.id) && !remoteFeedUrls.has(f.url));
+ // ---- Push local-only feeds to remote (skip feeds already synced by URL or deleted remotely) ----
+ const feedsToPush = mergedFeeds.filter(f => !remoteFeedIdSet.has(f.id) && !remoteFeedUrls.has(f.url) && !deletedFeedIds.has(f.id));
if (feedsToPush.length > 0) {
// Use insert for new feeds (they don't exist remotely yet)
for (const feed of feedsToPush) {
@@ -325,9 +349,7 @@ export const SyncService = {
}
// ---- Push local-only items to remote (batch 500) ----
- const remoteItemIds = new Set((remoteItems ?? []).map((r: Record) => r.id as string));
- const remoteItemUrls = new Set((remoteItems ?? []).map((r: Record) => r.url as string).filter(Boolean));
- const itemsToPush = mergedItems.filter(i => !remoteItemIds.has(i.id) && !(i.url && remoteItemUrls.has(i.url)));
+ const itemsToPush = mergedItems.filter(i => !remoteItemIdSet.has(i.id) && !(i.url && remoteItemUrlSet.has(i.url)) && !deletedItemIds.has(i.id) && !deletedFeedIds.has(i.feedId));
// Ensure every referenced feed exists before inserting items
// Populate cache so ensureFeedInSupabase can find them
for (const f of mergedFeeds) _feedsCache.set(f.id, f);
@@ -347,6 +369,12 @@ export const SyncService = {
if (error) dispatchSyncError('fullSync push items batch', error);
}
+ // ---- Track synced IDs for future orphan detection ----
+ const allSyncedFeedIds = [...remoteFeedIdSet, ...feedsToPush.map(f => f.id)];
+ const allSyncedItemIds = [...remoteItemIdSet, ...safeItemsToPush.map(i => i.id)];
+ saveLocal(STORAGE_KEYS.SYNCED_FEED_IDS, allSyncedFeedIds);
+ saveLocal(STORAGE_KEYS.SYNCED_ITEM_IDS, allSyncedItemIds);
+
// ---- Notify useFeedStore to reload ----
dispatchSyncEvent();
},
diff --git a/src/themes/palettes.ts b/src/themes/palettes.ts
new file mode 100644
index 0000000..b3935bf
--- /dev/null
+++ b/src/themes/palettes.ts
@@ -0,0 +1,262 @@
+export interface PaletteColors {
+ accent: string;
+ accentDim: string;
+ accentGlow: string;
+ accentText: string;
+ secondary: string;
+ secondaryGlow: string;
+ tertiary: string;
+ tertiaryGlow: string;
+}
+
+export interface Palette {
+ id: string;
+ name: string;
+ light: PaletteColors;
+ dark: PaletteColors;
+}
+
+export const palettes: Palette[] = [
+ {
+ id: 'amber',
+ name: 'Amber',
+ light: {
+ accent: '#d4a853',
+ accentDim: '#b8923f',
+ accentGlow: 'rgba(212, 168, 83, 0.12)',
+ accentText: '#9a7528',
+ secondary: '#c07830',
+ secondaryGlow: 'rgba(192, 120, 48, 0.10)',
+ tertiary: '#e8c06a',
+ tertiaryGlow: 'rgba(232, 192, 106, 0.10)',
+ },
+ dark: {
+ accent: '#d4a853',
+ accentDim: '#b8923f',
+ accentGlow: 'rgba(212, 168, 83, 0.12)',
+ accentText: '#e8c06a',
+ secondary: '#d4884a',
+ secondaryGlow: 'rgba(212, 136, 74, 0.10)',
+ tertiary: '#f0d080',
+ tertiaryGlow: 'rgba(240, 208, 128, 0.08)',
+ },
+ },
+ {
+ id: 'ocean',
+ name: 'Ocean',
+ light: {
+ accent: '#3a8fd4',
+ accentDim: '#2d78b5',
+ accentGlow: 'rgba(58, 143, 212, 0.12)',
+ accentText: '#1e5f99',
+ secondary: '#2bb5a0',
+ secondaryGlow: 'rgba(43, 181, 160, 0.10)',
+ tertiary: '#5bc0de',
+ tertiaryGlow: 'rgba(91, 192, 222, 0.10)',
+ },
+ dark: {
+ accent: '#4a9ee8',
+ accentDim: '#3a8fd4',
+ accentGlow: 'rgba(74, 158, 232, 0.12)',
+ accentText: '#6cb4f0',
+ secondary: '#38c9b2',
+ secondaryGlow: 'rgba(56, 201, 178, 0.10)',
+ tertiary: '#6dcce6',
+ tertiaryGlow: 'rgba(109, 204, 230, 0.08)',
+ },
+ },
+ {
+ id: 'forest',
+ name: 'Forest',
+ light: {
+ accent: '#2d9e5a',
+ accentDim: '#24824a',
+ accentGlow: 'rgba(45, 158, 90, 0.12)',
+ accentText: '#1a6b3a',
+ secondary: '#7ab648',
+ secondaryGlow: 'rgba(122, 182, 72, 0.10)',
+ tertiary: '#a3d977',
+ tertiaryGlow: 'rgba(163, 217, 119, 0.10)',
+ },
+ dark: {
+ accent: '#3db86c',
+ accentDim: '#2d9e5a',
+ accentGlow: 'rgba(61, 184, 108, 0.12)',
+ accentText: '#5ccf88',
+ secondary: '#8ec854',
+ secondaryGlow: 'rgba(142, 200, 84, 0.10)',
+ tertiary: '#b4e48c',
+ tertiaryGlow: 'rgba(180, 228, 140, 0.08)',
+ },
+ },
+ {
+ id: 'sunset',
+ name: 'Sunset',
+ light: {
+ accent: '#e8734a',
+ accentDim: '#d05f38',
+ accentGlow: 'rgba(232, 115, 74, 0.12)',
+ accentText: '#b84e2a',
+ secondary: '#d94f8c',
+ secondaryGlow: 'rgba(217, 79, 140, 0.10)',
+ tertiary: '#f5a623',
+ tertiaryGlow: 'rgba(245, 166, 35, 0.10)',
+ },
+ dark: {
+ accent: '#f08560',
+ accentDim: '#e8734a',
+ accentGlow: 'rgba(240, 133, 96, 0.12)',
+ accentText: '#f8a080',
+ secondary: '#e868a0',
+ secondaryGlow: 'rgba(232, 104, 160, 0.10)',
+ tertiary: '#f8b840',
+ tertiaryGlow: 'rgba(248, 184, 64, 0.08)',
+ },
+ },
+ {
+ id: 'lavender',
+ name: 'Lavender',
+ light: {
+ accent: '#7c5cbf',
+ accentDim: '#6648a8',
+ accentGlow: 'rgba(124, 92, 191, 0.12)',
+ accentText: '#553899',
+ secondary: '#9b6ddb',
+ secondaryGlow: 'rgba(155, 109, 219, 0.10)',
+ tertiary: '#b794f4',
+ tertiaryGlow: 'rgba(183, 148, 244, 0.10)',
+ },
+ dark: {
+ accent: '#9070d0',
+ accentDim: '#7c5cbf',
+ accentGlow: 'rgba(144, 112, 208, 0.12)',
+ accentText: '#b494f4',
+ secondary: '#ab80e8',
+ secondaryGlow: 'rgba(171, 128, 232, 0.10)',
+ tertiary: '#c8a8f8',
+ tertiaryGlow: 'rgba(200, 168, 248, 0.08)',
+ },
+ },
+ {
+ id: 'rosewood',
+ name: 'Rosewood',
+ light: {
+ accent: '#c44d56',
+ accentDim: '#a83e48',
+ accentGlow: 'rgba(196, 77, 86, 0.12)',
+ accentText: '#922e38',
+ secondary: '#a0522d',
+ secondaryGlow: 'rgba(160, 82, 45, 0.10)',
+ tertiary: '#d4836a',
+ tertiaryGlow: 'rgba(212, 131, 106, 0.10)',
+ },
+ dark: {
+ accent: '#d86068',
+ accentDim: '#c44d56',
+ accentGlow: 'rgba(216, 96, 104, 0.12)',
+ accentText: '#f08088',
+ secondary: '#b86840',
+ secondaryGlow: 'rgba(184, 104, 64, 0.10)',
+ tertiary: '#e09880',
+ tertiaryGlow: 'rgba(224, 152, 128, 0.08)',
+ },
+ },
+ {
+ id: 'mint',
+ name: 'Mint',
+ light: {
+ accent: '#36b5a0',
+ accentDim: '#2a9a88',
+ accentGlow: 'rgba(54, 181, 160, 0.12)',
+ accentText: '#208070',
+ secondary: '#4ac6b7',
+ secondaryGlow: 'rgba(74, 198, 183, 0.10)',
+ tertiary: '#7edec7',
+ tertiaryGlow: 'rgba(126, 222, 199, 0.10)',
+ },
+ dark: {
+ accent: '#44c8b0',
+ accentDim: '#36b5a0',
+ accentGlow: 'rgba(68, 200, 176, 0.12)',
+ accentText: '#68dcc8',
+ secondary: '#58d4c4',
+ secondaryGlow: 'rgba(88, 212, 196, 0.10)',
+ tertiary: '#90e8d4',
+ tertiaryGlow: 'rgba(144, 232, 212, 0.08)',
+ },
+ },
+ {
+ id: 'neon',
+ name: 'Neon',
+ light: {
+ accent: '#0097a7',
+ accentDim: '#00838f',
+ accentGlow: 'rgba(0, 151, 167, 0.12)',
+ accentText: '#006064',
+ secondary: '#c2185b',
+ secondaryGlow: 'rgba(194, 24, 91, 0.10)',
+ tertiary: '#558b2f',
+ tertiaryGlow: 'rgba(85, 139, 47, 0.10)',
+ },
+ dark: {
+ accent: '#00e5ff',
+ accentDim: '#00b8d4',
+ accentGlow: 'rgba(0, 229, 255, 0.18)',
+ accentText: '#40f0ff',
+ secondary: '#ff2d78',
+ secondaryGlow: 'rgba(255, 45, 120, 0.15)',
+ tertiary: '#76ff03',
+ tertiaryGlow: 'rgba(118, 255, 3, 0.12)',
+ },
+ },
+ {
+ id: 'slate',
+ name: 'Slate',
+ light: {
+ accent: '#6882a8',
+ accentDim: '#566e92',
+ accentGlow: 'rgba(104, 130, 168, 0.12)',
+ accentText: '#455b7c',
+ secondary: '#5a7a9a',
+ secondaryGlow: 'rgba(90, 122, 154, 0.10)',
+ tertiary: '#8fa5c4',
+ tertiaryGlow: 'rgba(143, 165, 196, 0.10)',
+ },
+ dark: {
+ accent: '#7c96b8',
+ accentDim: '#6882a8',
+ accentGlow: 'rgba(124, 150, 184, 0.12)',
+ accentText: '#98b0d0',
+ secondary: '#6e8eae',
+ secondaryGlow: 'rgba(110, 142, 174, 0.10)',
+ tertiary: '#a0b8d4',
+ tertiaryGlow: 'rgba(160, 184, 212, 0.08)',
+ },
+ },
+];
+
+const PALETTE_KEY = 'superflux_palette';
+
+export function getStoredPaletteId(): string {
+ try {
+ return localStorage.getItem(PALETTE_KEY) || 'amber';
+ } catch {
+ return 'amber';
+ }
+}
+
+export function getPaletteById(id: string): Palette {
+ return palettes.find(p => p.id === id) || palettes[0];
+}
+
+export function applyPalette(id: string) {
+ document.documentElement.setAttribute('data-palette', id);
+ try {
+ localStorage.setItem(PALETTE_KEY, id);
+ } catch { /* ignore */ }
+}
+
+export function initPalette() {
+ const id = getStoredPaletteId();
+ document.documentElement.setAttribute('data-palette', id);
+}
diff --git a/supabase/migrations/20250225000005_bookmarks.sql b/supabase/migrations/20250225000005_bookmarks.sql
new file mode 100644
index 0000000..5d7d906
--- /dev/null
+++ b/supabase/migrations/20250225000005_bookmarks.sql
@@ -0,0 +1,40 @@
+-- ============================================================
+-- SuperFlux — Bookmarks table (shared between desktop & Chrome extension)
+-- ============================================================
+
+create table bookmarks (
+ id text not null,
+ user_id uuid not null references profiles on delete cascade,
+ url text not null,
+ title text not null,
+ excerpt text,
+ image text,
+ favicon text,
+ author text,
+ site_name text,
+ tags text[] default '{}',
+ note text,
+ is_read boolean default false,
+ source text not null default 'chrome' check (source in ('chrome', 'desktop', 'mobile')),
+ created_at timestamptz default now(),
+ updated_at timestamptz default now(),
+ primary key (id, user_id)
+);
+
+create index bookmarks_user_updated_idx on bookmarks (user_id, updated_at desc);
+create index bookmarks_user_url_idx on bookmarks (user_id, url);
+
+alter table bookmarks enable row level security;
+
+create policy "Users can view own bookmarks"
+ on bookmarks for select using (auth.uid() = user_id);
+create policy "Users can insert own bookmarks"
+ on bookmarks for insert with check (auth.uid() = user_id);
+create policy "Users can update own bookmarks"
+ on bookmarks for update using (auth.uid() = user_id);
+create policy "Users can delete own bookmarks"
+ on bookmarks for delete using (auth.uid() = user_id);
+
+create trigger bookmarks_updated_at
+ before update on bookmarks
+ for each row execute function update_updated_at();
diff --git a/supabase/migrations/20250225000006_editor_documents.sql b/supabase/migrations/20250225000006_editor_documents.sql
new file mode 100644
index 0000000..d64b244
--- /dev/null
+++ b/supabase/migrations/20250225000006_editor_documents.sql
@@ -0,0 +1,32 @@
+-- ============================================================
+-- SuperFlux — Editor documents (SuperEditor)
+-- ============================================================
+
+create table editor_documents (
+ id text not null,
+ user_id uuid not null references profiles on delete cascade,
+ title text not null default 'Sans titre',
+ content text not null default '',
+ folder text,
+ created_at timestamptz default now(),
+ updated_at timestamptz default now(),
+ primary key (id, user_id)
+);
+
+create index editor_documents_user_updated_idx on editor_documents (user_id, updated_at desc);
+create index editor_documents_user_folder_idx on editor_documents (user_id, folder);
+
+alter table editor_documents enable row level security;
+
+create policy "Users can view own editor documents"
+ on editor_documents for select using (auth.uid() = user_id);
+create policy "Users can insert own editor documents"
+ on editor_documents for insert with check (auth.uid() = user_id);
+create policy "Users can update own editor documents"
+ on editor_documents for update using (auth.uid() = user_id);
+create policy "Users can delete own editor documents"
+ on editor_documents for delete using (auth.uid() = user_id);
+
+create trigger editor_documents_updated_at
+ before update on editor_documents
+ for each row execute function update_updated_at();
diff --git a/supabase/migrations/20250225000007_notes.sql b/supabase/migrations/20250225000007_notes.sql
new file mode 100644
index 0000000..7895a68
--- /dev/null
+++ b/supabase/migrations/20250225000007_notes.sql
@@ -0,0 +1,52 @@
+-- ============================================================
+-- SuperFlux — Notes (SuperNote)
+-- Idempotent: safe to re-run if table already exists
+-- ============================================================
+
+create table if not exists notes (
+ id text not null,
+ user_id uuid not null references profiles on delete cascade,
+ title text not null default 'Sans titre',
+ content text not null default '',
+ folder text,
+ created_at timestamptz default now(),
+ updated_at timestamptz default now(),
+ primary key (id, user_id)
+);
+
+-- Add sticky board columns if missing
+alter table notes add column if not exists sticky_x double precision;
+alter table notes add column if not exists sticky_y double precision;
+alter table notes add column if not exists sticky_rotation double precision;
+alter table notes add column if not exists sticky_z_index integer;
+alter table notes add column if not exists sticky_color text;
+alter table notes add column if not exists sticky_width double precision;
+alter table notes add column if not exists sticky_height double precision;
+
+create index if not exists notes_user_updated_idx on notes (user_id, updated_at desc);
+create index if not exists notes_user_folder_idx on notes (user_id, folder);
+
+alter table notes enable row level security;
+
+do $$ begin
+ if not exists (select 1 from pg_policies where tablename = 'notes' and policyname = 'Users can view own notes') then
+ create policy "Users can view own notes" on notes for select using (auth.uid() = user_id);
+ end if;
+ if not exists (select 1 from pg_policies where tablename = 'notes' and policyname = 'Users can insert own notes') then
+ create policy "Users can insert own notes" on notes for insert with check (auth.uid() = user_id);
+ end if;
+ if not exists (select 1 from pg_policies where tablename = 'notes' and policyname = 'Users can update own notes') then
+ create policy "Users can update own notes" on notes for update using (auth.uid() = user_id);
+ end if;
+ if not exists (select 1 from pg_policies where tablename = 'notes' and policyname = 'Users can delete own notes') then
+ create policy "Users can delete own notes" on notes for delete using (auth.uid() = user_id);
+ end if;
+end $$;
+
+do $$ begin
+ if not exists (select 1 from pg_trigger where tgname = 'notes_updated_at') then
+ create trigger notes_updated_at
+ before update on notes
+ for each row execute function update_updated_at();
+ end if;
+end $$;