personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

convey: presentation mode for back-row legibility (?present=1)

Toggle adds body.presentation-mode (URL ?present=1, Shift+P, or
window.solstonePresentation.toggle()), persisted in sessionStorage.
Per-app workspaces add scoped CSS overrides; the graph view also
re-renders vis-network with larger node/edge/font scaling on toggle.

Daily-use sizes are unchanged — every override is gated behind the
class. Includes a small floating "PRESENT — Shift+P to exit" badge
so the operator knows the mode is on.

For demo day 5/5 — graph + entities + transcripts all read clearly
when projected on a 1080p HDMI feed at ~50ft.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+279 -42
+48
apps/entities/workspace.html
··· 1252 1252 } 1253 1253 } 1254 1254 1255 + /* ── Presentation mode (?present=1) — back-row legibility on a projector ── */ 1256 + body.presentation-mode .entity-cards-container { padding: 1.5em 2em 4em; } 1257 + body.presentation-mode .entity-type-header { 1258 + font-size: 1.05rem; 1259 + font-weight: 700; 1260 + color: #1f2937; 1261 + letter-spacing: 0.05em; 1262 + text-transform: uppercase; 1263 + padding-bottom: 0.85em; 1264 + border-bottom-width: 2px; 1265 + } 1266 + body.presentation-mode .entity-cards-grid { 1267 + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); 1268 + gap: 1.25em 1.5em; 1269 + } 1270 + body.presentation-mode .entity-card { padding: 1.4em 1.5em; border-width: 2px; } 1271 + body.presentation-mode .entity-card-name { font-size: 22px; font-weight: 600; color: #0b1220; } 1272 + body.presentation-mode .entity-card-meta { font-size: 15px; color: #4b5563; } 1273 + body.presentation-mode .entity-card-date { font-size: 15px; color: #4b5563; } 1274 + body.presentation-mode .entity-card-type-indicator { width: 14px; height: 14px; } 1275 + 1276 + body.presentation-mode .entity-detail-header { padding: 2em 2em 1.5em; } 1277 + body.presentation-mode .entity-detail-name { font-size: 2.2em; color: #0b1220; } 1278 + body.presentation-mode .entity-detail-type, 1279 + body.presentation-mode .entity-detail-detached-badge, 1280 + body.presentation-mode .entity-detail-principal-badge, 1281 + body.presentation-mode .entity-detail-blocked-badge { 1282 + font-size: 15px; 1283 + padding: 0.4em 1em; 1284 + } 1285 + body.presentation-mode .entity-detail-aka, 1286 + body.presentation-mode .entity-detail-indicators { font-size: 1.05em; color: #4b5563; } 1287 + body.presentation-mode .entity-detail-section { padding: 1.75em 2em; } 1288 + body.presentation-mode .entity-detail-section h4 { 1289 + font-size: 1.05rem; 1290 + color: #1f2937; 1291 + letter-spacing: 0.04em; 1292 + text-transform: uppercase; 1293 + } 1294 + body.presentation-mode .entity-desc-display { font-size: 1.15em; line-height: 1.6; color: #0b1220; } 1295 + body.presentation-mode .observation-item { padding: 1em 1.25em; } 1296 + body.presentation-mode .observation-content { font-size: 1.1em; color: #0b1220; line-height: 1.55; } 1297 + body.presentation-mode .observation-meta { font-size: 0.95em; color: #4b5563; } 1298 + body.presentation-mode .entities-table th { font-size: 14px; } 1299 + body.presentation-mode .entities-table td { font-size: 1.05em; padding: 14px 1.5em; } 1300 + body.presentation-mode .entity-name { font-weight: 600; color: #0b1220; } 1301 + body.presentation-mode .entity-desc { color: #4b5563; } 1302 + 1255 1303 </style> 1256 1304 1257 1305 <div class="workspace-content">
+120 -42
apps/graph/workspace.html
··· 526 526 #graph-reload-overlay { display:none; position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); z-index:10; } 527 527 .graph-reloading #graph-reload-overlay { display:block; } 528 528 529 + /* ── Presentation mode (?present=1) — back-row legibility on a projector ── */ 530 + body.presentation-mode .graph-controls { 531 + padding: 0.75rem 1.25rem; 532 + background: #fff; 533 + border-bottom: 2px solid #d1d5db; 534 + } 535 + body.presentation-mode .graph-type-btn, 536 + body.presentation-mode .graph-time-btn, 537 + body.presentation-mode .graph-view-toggle { 538 + font-size: 1.05rem; 539 + font-weight: 600; 540 + padding: 0.5rem 1rem; 541 + border-width: 2px; 542 + } 543 + body.presentation-mode .graph-strength-filter { 544 + font-size: 1rem; 545 + color: #1f2937; 546 + } 547 + body.presentation-mode .graph-strength-filter input[type=range] { width: 120px; height: 8px; } 548 + body.presentation-mode .graph-stats { 549 + font-size: 1rem; 550 + color: #1f2937; 551 + font-weight: 600; 552 + } 553 + body.presentation-mode .graph-detail-panel { 554 + width: 460px; 555 + box-shadow: -4px 0 20px rgba(0, 0, 0, 0.18); 556 + } 557 + body.presentation-mode .graph-detail-header { padding: 1rem 1.25rem; border-bottom: 2px solid #e5e7eb; } 558 + body.presentation-mode .graph-detail-header h2 { font-size: 1.6rem; color: #0b1220; } 559 + body.presentation-mode .graph-detail-close { font-size: 2rem; } 560 + body.presentation-mode .graph-detail-body { font-size: 1.05rem; color: #1f2937; padding: 1rem 1.25rem; } 561 + body.presentation-mode .detail-section-title { font-size: 0.9rem; color: #4b5563; } 562 + body.presentation-mode .detail-type-badge, 563 + body.presentation-mode .detail-principal-badge { font-size: 0.95rem; padding: 0.3rem 0.75rem; } 564 + body.presentation-mode .detail-description { font-size: 1.1rem; line-height: 1.55; color: #1f2937; } 565 + body.presentation-mode .detail-score-item, 566 + body.presentation-mode .detail-connected-item, 567 + body.presentation-mode .detail-activity-item { font-size: 1.05rem; padding: 0.35rem 0; } 568 + body.presentation-mode .detail-connected-rel, 569 + body.presentation-mode .detail-activity-day { font-size: 0.95rem; } 570 + 529 571 /* ── Responsive: Tablet (≤768px) ── */ 530 572 @media (max-width: 768px) { 531 573 .graph-controls-row { gap: 0.75rem; } ··· 630 672 631 673 const escapeHtml = window.AppServices.escapeHtml; 632 674 675 + function isPresenting() { 676 + return document.body.classList.contains('presentation-mode'); 677 + } 678 + 679 + // vis-network options factory — returns presentation-tuned sizes when 680 + // ?present=1 is active so the graph reads from the back row. Daily-use 681 + // sizes are unchanged. 682 + function visOptions() { 683 + const present = isPresenting(); 684 + return { 685 + physics: { 686 + solver: 'forceAtlas2Based', 687 + forceAtlas2Based: { 688 + gravitationalConstant: present ? -260 : -200, 689 + centralGravity: 0.005, 690 + springLength: present ? 280 : 230, 691 + springConstant: 0.015, 692 + damping: 0.4, 693 + avoidOverlap: 0.8, 694 + }, 695 + stabilization: { iterations: 1000 }, 696 + }, 697 + nodes: { 698 + shape: 'dot', 699 + scaling: present 700 + ? { min: 22, max: 75, label: { enabled: true, min: 24, max: 42 } } 701 + : { min: 12, max: 45, label: { enabled: true, min: 14, max: 24 } }, 702 + borderWidth: present ? 3 : 1.5, 703 + shadow: present 704 + ? { enabled: true, size: 8, x: 2, y: 2, color: 'rgba(0,0,0,0.18)' } 705 + : { enabled: true, size: 4, x: 1, y: 1, color: 'rgba(0,0,0,0.1)' }, 706 + font: { 707 + face: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif', 708 + color: present ? '#0b1220' : '#374151', 709 + strokeWidth: present ? 4 : 0, 710 + strokeColor: '#ffffff', 711 + bold: present ? { color: '#0b1220', size: 30, vadjust: 0, mod: 'bold' } : undefined, 712 + }, 713 + }, 714 + edges: { 715 + scaling: present ? { min: 2.5, max: 10 } : { min: 1, max: 5 }, 716 + smooth: { enabled: true, type: 'curvedCW', roundness: 0.15 }, 717 + }, 718 + interaction: { 719 + hover: true, 720 + tooltipDelay: 100, 721 + hideEdgesOnDrag: true, 722 + hideEdgesOnZoom: true, 723 + }, 724 + layout: { 725 + improvedLayout: true, 726 + }, 727 + }; 728 + } 729 + 633 730 function formatDay(d) { 634 731 if (!d || d.length < 8) return d || ''; 635 732 return d.slice(0,4) + '-' + d.slice(4,6) + '-' + d.slice(6,8); ··· 662 759 663 760 // --- Graph rendering --- 664 761 function buildVisData(data) { 762 + const present = isPresenting(); 665 763 const nodes = data.nodes.map(n => { 666 764 const color = TYPE_COLORS[n.type] || TYPE_COLORS.unknown; 667 765 return { ··· 674 772 highlight: { background: color, border: n.is_principal ? '#f59e0b' : '#111827' }, 675 773 hover: { background: color, border: n.is_principal ? '#f59e0b' : '#374151' }, 676 774 }, 677 - borderWidth: n.is_principal ? 3 : 1.5, 775 + borderWidth: n.is_principal ? (present ? 5 : 3) : (present ? 3 : 1.5), 678 776 title: n.name + ' \u00b7 ' + n.type + ' \u00b7 score ' + n.score.toFixed(1), 679 777 _data: n, 680 778 ...(window.selectedFacet ? {} : (() => { ··· 690 788 const edges = data.edges.map((e, i) => { 691 789 if (e.edge_type === 'explicit') { 692 790 const relColor = EDGE_REL_COLORS[e.relationship_type] || '#9ca3af'; 791 + const opacity = present 792 + ? Math.min(1.0, 0.75 + e.frequency * 0.04) 793 + : Math.min(0.9, 0.5 + e.frequency * 0.05); 693 794 return { 694 795 id: 'e' + i, 695 796 from: e.from, 696 797 to: e.to, 697 798 value: e.frequency, 698 - color: { color: relColor, opacity: Math.min(0.9, 0.5 + e.frequency * 0.05), highlight: relColor, hover: relColor }, 699 - arrows: { to: { enabled: true, scaleFactor: 0.5 } }, 799 + color: { color: relColor, opacity: opacity, highlight: relColor, hover: relColor }, 800 + arrows: { to: { enabled: true, scaleFactor: present ? 0.9 : 0.5 } }, 700 801 smooth: { type: 'curvedCW', roundness: 0.15 }, 701 802 title: (e.relationship_type || 'related') + ' (' + e.frequency + ')', 702 803 }; 703 804 } else { 805 + const coColor = present ? '#9ca3af' : '#d1d5db'; 806 + const opacity = present 807 + ? Math.min(0.95, 0.65 + e.frequency * 0.04) 808 + : Math.min(0.8, 0.4 + e.frequency * 0.04); 704 809 return { 705 810 id: 'e' + i, 706 811 from: e.from, 707 812 to: e.to, 708 813 value: e.frequency, 709 - color: { color: '#d1d5db', opacity: Math.min(0.8, 0.4 + e.frequency * 0.04), highlight: '#9ca3af', hover: '#9ca3af' }, 710 - dashes: [4, 4], 814 + color: { color: coColor, opacity: opacity, highlight: '#6b7280', hover: '#6b7280' }, 815 + dashes: present ? [6, 6] : [4, 4], 711 816 smooth: { type: 'curvedCW', roundness: 0.1 }, 712 817 title: 'co-occurrence (' + e.frequency + ')', 713 818 }; ··· 753 858 754 859 const visData = buildVisData(data); 755 860 756 - const options = { 757 - physics: { 758 - solver: 'forceAtlas2Based', 759 - forceAtlas2Based: { 760 - gravitationalConstant: -200, 761 - centralGravity: 0.005, 762 - springLength: 230, 763 - springConstant: 0.015, 764 - damping: 0.4, 765 - avoidOverlap: 0.8, 766 - }, 767 - stabilization: { iterations: 1000 }, 768 - }, 769 - nodes: { 770 - shape: 'dot', 771 - scaling: { min: 12, max: 45, label: { enabled: true, min: 14, max: 24 } }, 772 - borderWidth: 1.5, 773 - shadow: { enabled: true, size: 4, x: 1, y: 1, color: 'rgba(0,0,0,0.1)' }, 774 - font: { 775 - face: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif', 776 - color: '#374151', 777 - }, 778 - }, 779 - edges: { 780 - scaling: { min: 1, max: 5 }, 781 - smooth: { enabled: true, type: 'curvedCW', roundness: 0.15 }, 782 - }, 783 - interaction: { 784 - hover: true, 785 - tooltipDelay: 100, 786 - hideEdgesOnDrag: true, 787 - hideEdgesOnZoom: true, 788 - }, 789 - layout: { 790 - improvedLayout: true, 791 - }, 792 - }; 861 + const options = visOptions(); 793 862 794 863 if (network) { 795 864 network.setData(visData); ··· 1142 1211 // --- Facet awareness --- 1143 1212 window.addEventListener('facet.switch', () => { 1144 1213 reload(); 1214 + }); 1215 + 1216 + // --- Presentation mode awareness — rebuild visData + options on toggle --- 1217 + window.addEventListener('presentation-mode:change', () => { 1218 + if (!network || !graphData) return; 1219 + const visData = buildVisData(graphData); 1220 + network.setOptions(visOptions()); 1221 + network.setData(visData); 1222 + network.fit({ animation: { duration: 300 } }); 1145 1223 }); 1146 1224 1147 1225 // --- Load / reload ---
+33
apps/transcripts/workspace.html
··· 1347 1347 white-space: nowrap; 1348 1348 border: 0; 1349 1349 } 1350 + 1351 + /* ── Presentation mode (?present=1) — back-row legibility on a projector ── */ 1352 + body.presentation-mode .tr-card { --tr-day-col: 240px; --tr-zoom-col: 150px; } 1353 + body.presentation-mode .tr-timeline-label, 1354 + body.presentation-mode .tr-timeline-legend, 1355 + body.presentation-mode .tr-zoom-timeline-label { font-size: 14px; font-weight: 700; } 1356 + body.presentation-mode .tr-label, 1357 + body.presentation-mode .tr-zoom-label { font-size: 14px; color: #1f2937; } 1358 + body.presentation-mode .tr-now-label { font-size: 14px; font-weight: 700; } 1359 + body.presentation-mode .tr-now-marker { border-top-width: 3px; } 1360 + body.presentation-mode .tr-seg { width: 48px; box-shadow: 0 2px 4px rgba(0,0,0,.10); } 1361 + body.presentation-mode .tr-seg-audio { background: rgba(74, 222, 128, 0.85); border-width: 2px; } 1362 + body.presentation-mode .tr-seg-screen { background: rgba(234, 179, 8, 0.85); border-width: 2px; } 1363 + body.presentation-mode .tr-zoom-pill { font-weight: 700; } 1364 + body.presentation-mode .tr-content { padding: 8px 28px 28px; gap: 20px; } 1365 + body.presentation-mode .tr-title { font-size: 28px; color: #0b1220; } 1366 + body.presentation-mode .tr-range-text { font-size: 16px; color: #4b5563; } 1367 + body.presentation-mode .tr-tabs { padding: 12px 0; border-bottom-width: 2px; } 1368 + body.presentation-mode .tr-tab { font-size: 16px; font-weight: 600; padding: 10px 16px; border-width: 2px; } 1369 + body.presentation-mode .tr-tab.active { background: #2563eb; border-color: #2563eb; } 1370 + body.presentation-mode .tr-md-content { 1371 + font-size: 19px; 1372 + line-height: 1.65; 1373 + padding: 20px; 1374 + max-width: 78ch; 1375 + color: #0b1220; 1376 + } 1377 + body.presentation-mode .tr-md-content code { font-size: 17px; } 1378 + body.presentation-mode .tr-panel { font-size: 18px; line-height: 1.6; padding: 20px; } 1379 + body.presentation-mode .tr-entry-time { width: 64px; font-size: 16px; color: #1f2937; font-weight: 600; } 1380 + body.presentation-mode .tr-entry-text { font-size: 19px; line-height: 1.6; color: #0b1220; } 1381 + body.presentation-mode .tr-entry-meta { font-size: 14px; color: #6b7280; margin-top: 4px; } 1382 + body.presentation-mode .tr-screen-text { font-size: 16px; padding: 12px 16px; border-left-width: 4px; color: #1f2937; } 1350 1383 </style> 1351 1384 1352 1385 <div class="tr-wrap">
+46
convey/static/app.css
··· 2843 2843 .skip-link:focus { 2844 2844 top: 0; 2845 2845 } 2846 + 2847 + /* ========================================================================== 2848 + Presentation mode — gated by body.presentation-mode (toggle: ?present=1 2849 + or Shift+P; persists in sessionStorage). Boosts type, contrast, weights 2850 + so views read from the back row of a presentation room on a 1080p 2851 + projector. Per-app overrides live in each app's workspace.html. 2852 + ========================================================================== */ 2853 + 2854 + /* Floating "PRESENT" badge — also a click-to-exit affordance. */ 2855 + body.presentation-mode::after { 2856 + content: 'PRESENT — Shift+P to exit'; 2857 + position: fixed; 2858 + bottom: 12px; 2859 + right: 12px; 2860 + z-index: 9998; 2861 + padding: 6px 12px; 2862 + font-size: 11px; 2863 + font-weight: 700; 2864 + letter-spacing: 0.08em; 2865 + text-transform: uppercase; 2866 + color: #fef3c7; 2867 + background: rgba(180, 83, 9, 0.92); 2868 + border-radius: 4px; 2869 + pointer-events: none; 2870 + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; 2871 + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2); 2872 + opacity: 0.85; 2873 + } 2874 + 2875 + /* Shared chrome boosts — keep these conservative. The big polish lives 2876 + in the app workspaces (graph, entities, transcripts). */ 2877 + body.presentation-mode .facet-bar { 2878 + font-size: 1.05rem; 2879 + } 2880 + body.presentation-mode .facet-pills-container .facet-pill { 2881 + font-size: 1rem; 2882 + padding: 6px 14px; 2883 + } 2884 + 2885 + /* Minor: surface-state empty/error blocks read at distance. */ 2886 + body.presentation-mode .surface-state-heading { 2887 + font-size: 1.5rem; 2888 + } 2889 + body.presentation-mode .surface-state-desc { 2890 + font-size: 1.1rem; 2891 + }
+32
convey/templates/app.html
··· 43 43 <script> 44 44 // Restore sidebar state before first paint to prevent FOUC 45 45 (function(){try{var s=localStorage.getItem('solstone:menu-state');if(s==='full')document.body.classList.add('menu-full');else if(s==='all')document.body.classList.add('menu-all');}catch(e){/* Default collapsed menu state is safe when storage is unavailable. */}})(); 46 + // Presentation mode (?present=1, Shift+P toggle, sessionStorage-persisted). 47 + // Boosts typography + contrast for back-of-room legibility on a projector. 48 + (function(){ 49 + var KEY = 'solstone:presentation-mode'; 50 + function emit(on){ 51 + try { window.dispatchEvent(new CustomEvent('presentation-mode:change', { detail: { on: on } })); } catch(_) {} 52 + } 53 + function set(on){ 54 + try { if (on) sessionStorage.setItem(KEY, '1'); else sessionStorage.removeItem(KEY); } catch(_) {} 55 + document.body.classList.toggle('presentation-mode', !!on); 56 + emit(!!on); 57 + } 58 + function isOn(){ return document.body.classList.contains('presentation-mode'); } 59 + // URL param wins, then sessionStorage. 60 + var on = false; 61 + try { 62 + var p = new URL(window.location.href).searchParams.get('present'); 63 + if (p === '1' || p === 'on' || p === 'true') { on = true; sessionStorage.setItem(KEY, '1'); } 64 + else if (p === '0' || p === 'off' || p === 'false') { on = false; sessionStorage.removeItem(KEY); } 65 + else { on = sessionStorage.getItem(KEY) === '1'; } 66 + } catch(_) {} 67 + if (on) document.body.classList.add('presentation-mode'); 68 + // Shift+P toggles. Skip when typing in an input/textarea/contenteditable. 69 + document.addEventListener('keydown', function(e){ 70 + if (e.key !== 'P' || !e.shiftKey || e.ctrlKey || e.metaKey || e.altKey) return; 71 + var t = e.target; 72 + if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable)) return; 73 + e.preventDefault(); 74 + set(!isOn()); 75 + }); 76 + window.solstonePresentation = { on: function(){ set(true); }, off: function(){ set(false); }, toggle: function(){ set(!isOn()); }, isOn: isOn }; 77 + })(); 46 78 </script> 47 79 <a href="#main-content" class="skip-link">skip to content</a> 48 80 <!-- Corner Tags (screen region detection) -->