Interactive Studies Integration
Align the study generation system’s output with blog requirements, then integrate as Quarto posts.
Strategy
Two-phase approach:
Supra-Phase A: Update
new_studies_format/VISUAL_GUIDE.md— the upstream standard that governs how studies are generated. Make the output inherently compatible with the blog.Supra-Phase B: Integrate studies as Quarto posts — minimal transformation needed since output is already blog-aligned.
This ensures future studies are born compatible, not retrofitted.
Analysis Summary
Current State
Study System Output (new_studies_format/viz/): - Standalone HTML + CSS + JS (D3.js v7) - IBM Plex Mono typography - Blue accent color (#2563eb) for data emphasis - White background (#ffffff) - Rounded corners (2-4px) on bars, tooltips, sparklines, selects - Shadows on tooltips - Global CSS reset (* { margin: 0 }) - :root variable definitions
Blog Visual Identity (docs/VISUAL_IDENTITY.md): - Berkeley Mono typography - Strictly monochrome (no color accents) - Warm paper background (#FAFAF9 light, #1E1D1B dark) - Dot grid background pattern - Zero ornamentation: no shadows, no rounded corners - Dark mode via system preference - CSS variables defined in styles.css
Gap Analysis
| Aspect | Current Studies | Blog Requirement | Change Needed |
|---|---|---|---|
| Font | IBM Plex Mono | Berkeley Mono | Update VISUAL_GUIDE |
| Colors | Blue accent #2563eb | Monochrome only | Update VISUAL_GUIDE |
| Background | Pure white | Warm paper (inherit) | Update VISUAL_GUIDE |
| Corners | Rounded (2-4px) | Sharp (0) | Update VISUAL_GUIDE |
| Shadows | Tooltip shadows | None | Update VISUAL_GUIDE |
| Dark mode | None | System preference | Update VISUAL_GUIDE |
| CSS structure | :root, global reset |
Scoped, use blog vars | Update VISUAL_GUIDE |
| HTML structure | Full document | Fragment only | Update VISUAL_GUIDE |
Detailed Violations in Current Viz
From new_studies_format/viz/style.css:
| Line | Violation | Fix |
|---|---|---|
| 1-18 | :root variable definitions |
Remove, use blog vars |
| 20-24 | Global reset * { margin: 0 } |
Scope to .viz-main * |
| 110-111 | .spark-bar { border-radius: 1px } |
Set to 0 |
| 151 | .paper-row:hover { background: #f9fafb } |
Use var(--code-bg) |
| 179 | .paper-bar-container { border-radius: 2px } |
Set to 0 |
| 186 | .paper-bar { transition: width 0.3s } |
Reduce to 150ms max |
| 247 | #sort-select { border-radius: 4px } |
Set to 0 |
| 267-276 | @keyframes expand 200ms animation |
Reduce to 150ms |
| 316 | .arxiv-link { color: var(--accent) } |
Use var(--black) |
| 330 | #tooltip { box-shadow: ... } |
Remove |
| 330 | #tooltip { border-radius implied } |
Set to 0 |
SUPRA-PHASE A: Update VISUAL_GUIDE.md
Make the study generation system produce blog-compatible output by default.
A1: Proposed VISUAL_GUIDE.md Rewrite
Replace new_studies_format/VISUAL_GUIDE.md with this content:
# Visual Guide
Design principles for publication-ready visualizations that integrate with Linear Content blog.
> **Skills**: Use `/tufte-design` for design decisions and `/d3js-skill` for implementation patterns.
## Philosophy
Follow Tufte + Terminal-Minimalist aesthetic:
- Every pixel must earn its place
- Data speaks; decoration distracts
- Monochrome palette—no color accents
- Sharp edges—no rounded corners
- No shadows or gradients
## Integration Context
Visualizations are embedded in a Quarto blog with existing CSS variables. **Do not define your own variables**—inherit from the host page.
### Available Blog Variables
```css
/* Use these directly - they handle dark mode automatically */
var(--font-mono) /* Berkeley Mono font stack */
var(--paper) /* Background: #FAFAF9 light, #1E1D1B dark */
var(--black) /* Primary: #000000 light, #E6E4DF dark */
var(--mid-gray) /* Secondary text */
var(--border-color) /* Structure, dividers, bar tracks */
var(--code-bg) /* Hover backgrounds */Design Tokens
Use blog variables, with explicit sizing:
/* Typography - inherit font, set sizes */
font-family: var(--font-mono);
font-size: 14px; /* Base */
font-size: 12px; /* Small/labels */
font-size: 1.5rem; /* Headlines (match blog h1) */
line-height: 1.5;
/* Spacing */
--row-height: 32px;
--section-gap: 2rem;
--column-gap: 2rem;Output Structure
HTML Fragment (Not Full Document)
Generate fragments that can be included in Quarto, not standalone pages:
<!-- viz/app.html - NO doctype, html, head, or body -->
<main class="viz-main">
<header class="viz-header">
<h2>Study Title</h2>
<p class="subtitle">Brief description</p>
</header>
<section id="content-section">
<!-- Visualization content -->
</section>
</main>
<div id="tooltip" class="viz-tooltip"></div>Rules: - Use <h2> for study title (Quarto provides <h1> via frontmatter) - Prefix IDs with descriptive names to avoid conflicts - Include tooltip div at end
CSS Structure
/* viz/style.css - scoped, no globals */
/* Container scope - reset only within viz */
.viz-main *,
.viz-main *::before,
.viz-main *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.viz-main {
font-family: var(--font-mono);
font-size: 14px;
color: var(--black);
line-height: 1.5;
max-width: 1200px;
margin: 0 auto;
padding: 0 24px;
}
/* Dark mode handled automatically via var() cascade */Forbidden in CSS: - :root { } definitions - * { margin: 0; padding: 0; } global resets - body { } selectors - Hardcoded colors (use variables) - border-radius > 0 - box-shadow
JavaScript Structure
// viz/main.js
// Determine data path relative to script location
const DATA_PATH = new URL('./viz_data.json', import.meta.url).href;
// Or use explicit path from page root:
// const DATA_PATH = './viz/viz_data.json';
// Wait for DOM
document.addEventListener('DOMContentLoaded', init);
async function init() {
const data = await d3.json(DATA_PATH);
// ... render
}Rules: - Data path must work from Quarto page context (./viz/viz_data.json) - Use DOMContentLoaded or place script at end with defer - Avoid ID conflicts with common names (use descriptive prefixes) - If using ES modules, import.meta.url gives script location
Component Patterns
Bars
.bar {
background: var(--black);
border-radius: 0;
height: 8px;
}
.bar-track {
background: var(--border-color);
border-radius: 0;
}Sparklines
.spark-bar {
background: var(--black);
opacity: 0.6;
border-radius: 0;
}Tooltips
.viz-tooltip {
position: absolute;
pointer-events: none;
background: var(--paper);
border: 1px solid var(--border-color);
padding: 12px;
max-width: 400px;
font-size: 12px;
opacity: 0;
transition: opacity 0.15s;
z-index: 1000;
/* NO box-shadow, NO border-radius */
}Expandable Details
.detail-panel {
border-left: 3px solid var(--black);
padding-left: 1rem;
margin: 8px 0;
background: var(--paper);
}Interactive Rows
.data-row {
padding: 8px 0;
cursor: pointer;
}
.data-row:hover {
background: var(--code-bg);
}Links
a {
color: var(--black);
text-decoration: underline;
text-underline-offset: 2px;
}
a:hover {
text-decoration-thickness: 2px;
/* NO color change */
}Form Controls
select {
font-family: var(--font-mono);
font-size: 12px;
padding: 4px 8px;
border: 1px solid var(--border-color);
border-radius: 0;
background: var(--paper);
color: var(--black);
cursor: pointer;
}
select:focus {
outline: none;
border-color: var(--black);
}Data Emphasis Without Color
Since color accents are forbidden, use:
| Technique | Use Case |
|---|---|
| Opacity (0.4-0.6) | Secondary/background data |
| Bold weight | Emphasis within text |
| Border-left (3px) | Panel/section emphasis |
| Whitespace | Group related items |
| Direct labels | Instead of legends |
| Size variation | Headline stats vs details |
D3.js Patterns
Standard Setup
const ROW_HEIGHT = 32;
const SECTION_GAP = 32;
// Use CSS variables in JS when needed
const style = getComputedStyle(document.documentElement);
const black = style.getPropertyValue('--black').trim();Tooltip Pattern
const tooltip = d3.select('.viz-tooltip');
selection
.on('mouseenter', (e, d) => {
tooltip.style('opacity', 1).html(formatTooltip(d));
})
.on('mousemove', (e) => {
tooltip
.style('left', (e.pageX + 12) + 'px')
.style('top', (e.pageY - 12) + 'px');
})
.on('mouseleave', () => tooltip.style('opacity', 0));Transitions
// Max 150ms, ease-out
selection.transition()
.duration(150)
.ease(d3.easeOut)
.attr('transform', d => `translate(0, ${d.y})`);Checklist
Before generating output:
File Output Structure
Each study should produce:
viz/
├── app.html # HTML fragment
├── style.css # Scoped CSS (no :root, no global reset)
├── main.js # D3 visualization
└── viz_data.json # Data file
---
## A2: Implementation Phases for VISUAL_GUIDE Update
### Phase A.1: Update VISUAL_GUIDE.md
- [x] Replace `new_studies_format/VISUAL_GUIDE.md` with new content
- [x] **Checkpoint**: Document updated
### Phase A.2: Validate Against Current Viz
- [ ] Audit current `viz/style.css` against new rules
- [ ] List all violations (rounded corners, shadows, colors, etc.)
- [ ] **Checkpoint**: Gap list complete
### Phase A.3: Create Reference Implementation
- [ ] Update `viz/style.css` to comply with new VISUAL_GUIDE
- [ ] Update `viz/index.html` → `viz/app.html` (fragment only)
- [ ] Verify no `:root`, no global resets
- [ ] Replace all colors with blog variables
- [ ] Remove all border-radius
- [ ] Remove all box-shadow
- [ ] **Checkpoint**: Reference viz complies with guide
### Phase A.4: Dark Mode Verification
- [ ] Test reference viz with dark mode (will need blog context)
- [ ] Verify all elements adapt correctly
- [ ] **Checkpoint**: Dark mode works
---
# SUPRA-PHASE B: Quarto Integration
Integrate blog-aligned studies as posts.
## B1: Post Structure
Note: Existing posts use underscore separators (e.g., `20160825_sentiment_bukowski`).
posts/ ├── [existing text posts…] └── 20250103_paper_selection/ ├── index.qmd # Quarto wrapper └── viz/ ├── app.html # HTML fragment (from study system) ├── style.css # Scoped CSS ├── main.js # D3 code └── viz_data.json # Data
## B2: Quarto Wrapper Template
```yaml
---
title: "LLM Paper Selection Experiment"
description: "Comparing single-shot vs multi-run consensus in paper selection."
date: "2025-01-03"
categories: [experiments, llm]
format:
html:
page-layout: full
toc: false
---
<div class="viz-container">
{{\< include viz/app.html \>}}
</div>
<link rel="stylesheet" href="viz/style.css">
<script src="https://d3js.org/d3.v7.min.js"></script>
<script type="module">
// Load data relative to page, not script
const DATA_PATH = './viz/viz_data.json';
const { default: init } = await import('./viz/main.js');
// Or if main.js self-initializes, just import it:
// import './viz/main.js';
</script>Alternative (simpler, if main.js handles its own path):
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
// Set data path before main.js runs
window.VIZ_DATA_PATH = './viz/viz_data.json';
</script>
<script src="viz/main.js" defer></script>Then in main.js:
const DATA_PATH = window.VIZ_DATA_PATH || './viz/viz_data.json';B3: Integration CSS (Optional)
If needed, add to styles.css for viz-specific tweaks:
/* Viz container - ensure full width */
.viz-container {
width: 100%;
/* Inherits blog dot grid background */
}
/* Optional: solid background for dense viz */
.viz-container.solid-bg {
background-color: var(--paper);
background-image: none;
}B4: Implementation Phases for Integration
Phase B.1: Create First Study Post
Phase B.2: Verify Visual Alignment
Phase B.3: Test Interactions
Phase B.4: Dark Mode
Phase B.5: Mobile
Phase B.6: Document
File Changes Summary
Supra-Phase A (Upstream)
| File | Action |
|---|---|
new_studies_format/VISUAL_GUIDE.md |
Rewrite (new content) |
new_studies_format/viz/app.html |
Create (fragment from index.html) |
new_studies_format/viz/style.css |
Update (remove :root, radius, shadow) |
new_studies_format/viz/index.html |
Delete or keep as standalone demo |
Supra-Phase B (Integration)
| File | Action |
|---|---|
posts/20250103_paper_selection/index.qmd |
Create |
posts/20250103_paper_selection/viz/* |
Copy from updated new_studies_format |
docs/STRUCTURE.md |
Add viz post section |
styles.css |
Optional: add .viz-container rules |
Backward Compatibility
Old posts remain unchanged. The new viz post type is additive:
- Text posts:
posts/YYYYMMDD_slug/index.qmdorindex.ipynb - Viz posts:
posts/YYYYMMDD_slug/index.qmd+viz/folder
Both work within the same Quarto build. The listing shows all posts sorted by date.
Risks & Mitigations
| Risk | Mitigation |
|---|---|
| Study system generates old format | VISUAL_GUIDE is authoritative; update skills that reference it |
| CSS variable cascade fails | Remove ALL :root from viz CSS |
| ID conflicts in Quarto | Prefix all IDs (e.g., stats-headline not headline) |
| Monochrome limits expressiveness | Use opacity, weight, borders, whitespace per Tufte |
| D3 bundle size | CDN with async; acceptable for interactive content |
Decision Log
| Decision | Choice | Rationale |
|---|---|---|
| Color accent | No | Blog identity is strictly monochrome |
| HTML output | Fragment | Enables Quarto include without conflicts |
| CSS structure | Scoped, no :root | Inherits blog dark mode automatically |
| Border radius | 0 everywhere | Blog forbids rounded corners |
| Shadows | None | Blog forbids decorative effects |