Imported from vaniachow/macro-brief-demo-2 (
.claude/skills/macro-brief/SKILL.md). Install upstream withnpx skills add vaniachow/macro-brief-demo-2 --skill macro-brief. Copyright stays with the author.
Macro Research Dashboard Workflow
When the user requests a macro briefing (e.g., impact of Fed rate decisions on cost of capital and merchant pricing), follow this workflow exactly. Reference the /frontend-design skill for all visual styling decisions.
Step 0: Slack Trigger (conditional)
If the user specifies a Slack channel (e.g., #macro-research or a channel ID like C0123456789), read the channel to find the research request:
- Use
slack_get_channel_historyto fetch recent messages from the specified channel. - Find the most recent message that mentions
@fintech-research— this is the research request. - Extract the request text from that message.
- Note the message
ts(timestamp) — you'll use this later for threading the reply. - React to the message with
:eyes:usingslack_add_reactionto signal the request has been picked up.
If no Slack channel is specified, skip this step entirely and use the request from the user's prompt instead.
Step 1: Acknowledge and Decompose
State your plan clearly:
- "I'll research this by spawning two parallel subagents:"
- Subagent A (FRED Data): Pull rate data from the FRED MCP server
- Subagent B (Web Research): Search for recent FOMC commentary and market analysis
- "I'll then synthesize their findings into an interactive HTML dashboard."
Step 2: Spawn Subagents in Parallel
CRITICAL: Launch BOTH subagents in a SINGLE message with TWO Task tool calls. They MUST run in parallel.
Subagent A — FRED Economic Data
Use the Task tool with subagent_type: "general-purpose" and this prompt:
You are a macro research analyst. Use the FRED MCP tools to gather current rate data. Query these series:
1. FEDFUNDS — Federal Funds Effective Rate
2. DGS2 — 2-Year Treasury Constant Maturity Rate
3. DGS10 — 10-Year Treasury Constant Maturity Rate
4. MORTGAGE30US — 30-Year Fixed Rate Mortgage Average
5. T10Y2Y — 10-Year Treasury Minus 2-Year Treasury (yield curve spread)
For each series, use get_series_info to get the metadata, then use get_rate_data with limit=10 to get recent observations.
Return your findings in TWO parts:
**PART 1 — STRUCTURED DATA TABLE**
Output a pipe-separated table with headers for each series. Include the 10 most recent observations. Format:
SERIES: FEDFUNDS
date|value
2025-04-01|4.33
2025-03-01|4.33
...
SERIES: DGS2
date|value
...
(Repeat for all 5 series)
Also output a CURRENT LEVELS summary line:
CURRENT_LEVELS: FEDFUNDS=X.XX, DGS2=X.XX, DGS10=X.XX, MORTGAGE30US=X.XX, T10Y2Y=X.XX
And a TRENDS summary line (use "rising", "falling", or "flat" based on recent direction):
TRENDS: FEDFUNDS=flat, DGS2=rising, DGS10=rising, MORTGAGE30US=rising, T10Y2Y=rising
**PART 2 — NARRATIVE SUMMARY**
Write a COMPRESSED 3-paragraph summary:
- Paragraph 1: Current rate levels and recent direction of movement
- Paragraph 2: Yield curve shape and what it signals
- Paragraph 3: Key rate trends over the past 3-6 months
Include specific numbers and dates.
At the very end, add a metadata line in this exact format:
[Data processed: X FRED series queried, Y total observations retrieved, approximately Z words of raw API data consumed]
Subagent B — Web Research
Use the Task tool with subagent_type: "general-purpose" and this prompt:
You are a macro research analyst. Search the web for the latest Federal Reserve policy information:
1. Use WebSearch to find the most recent FOMC statement and meeting minutes
2. Use WebSearch to find recent Fed chair (Jerome Powell) commentary on rates
3. Use WebSearch to find market analyst reactions to the latest Fed rate decision
4. Use WebFetch on the most relevant 1-2 URLs to get detailed content
After gathering information, write a COMPRESSED 3-paragraph summary:
- Paragraph 1: What the Fed decided and their stated rationale
- Paragraph 2: Key quotes or signals from Fed officials about future policy direction
- Paragraph 3: Market consensus on next moves and timeline
Include specific dates, quotes where available, and analyst firm names. Do NOT return raw HTML or full articles — only the written summary.
At the very end, add THREE special lines:
1. Source references — list the 2-4 most important sources you used, in this exact format (one per line):
[SOURCE: Title of article or page | https://full-url-here]
2. A key watchpoint line in this exact format:
[KEY WATCHPOINT: one-sentence summary of the single most important thing to watch | https://url-to-relevant-source-if-available]
3. A metadata line in this exact format:
[Data processed: X web searches performed, Y pages fetched, approximately Z words of raw content consumed]
Step 3: Synthesize the Dashboard
Once both subagents return, generate a single self-contained HTML file. Parse the structured data from Subagent A (CURRENT_LEVELS, TRENDS, and the pipe-separated time series) and the narrative + watchpoint + sources from Subagent B.
Follow the /frontend-design skill for all colors, typography, and component styles. Use the Anthropic brand palette (warm charcoal bg #1a1a19, burnt orange accent #d97757, Poppins headings, Lora body text).
Write the HTML file with this structure. Replace ALL placeholder values with real data from the subagents.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Macro Briefing: Fed Rate Decision Impact</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&family=Lora:ital,wght@0,400;0,500;0,600;1,400&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
:root {
--bg: #1a1a19;
--surface: #2a2a28;
--surface-hover: #353533;
--surface-alt: #3d3d3a;
--text: #faf9f5;
--text-secondary: #b0aea5;
--text-muted: #807e76;
--accent: #d97757;
--accent-hover: #e08a6d;
--accent-blue: #6a9bcc;
--accent-green: #788c5d;
--positive: #788c5d;
--negative: #c15f3c;
--warning: #d4a843;
--border: #3d3d3a;
--border-hover: #555550;
--gradient-accent: linear-gradient(90deg, #d97757, #6a9bcc, #788c5d);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Lora', Georgia, serif;
background: var(--bg);
color: var(--text);
line-height: 1.7;
padding: 0;
}
h1, h2, h3, h4, th, .label, .nav-link { font-family: 'Poppins', 'Segoe UI', Arial, sans-serif; }
.mono { font-family: 'JetBrains Mono', Consolas, monospace; }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 3px; }
.container { max-width: 1200px; margin: 0 auto; padding: 0 2rem 2rem; }
/* Sticky Nav */
.sticky-nav {
position: sticky; top: 0; z-index: 100;
background: #1a1a19ee; backdrop-filter: blur(8px);
border-bottom: 1px solid var(--border);
padding: 0.5rem 2rem;
display: flex; gap: 0.25rem; justify-content: center;
}
.sticky-nav a {
font-family: 'Poppins', sans-serif; font-size: 0.8rem;
color: var(--text-secondary); text-decoration: none;
padding: 0.35rem 0.75rem; border-radius: 4px;
transition: color 0.2s, background 0.2s;
}
.sticky-nav a:hover { color: var(--text); background: #3d3d3a44; }
.sticky-nav a.active { color: var(--accent); background: #d9775715; }
header {
text-align: center; padding: 2.5rem 2rem 1.5rem;
}
header h1 { font-size: 1.8rem; font-weight: 700; margin-bottom: 0.25rem; }
header .subtitle { color: var(--text-secondary); font-size: 0.9rem; font-family: 'Poppins', sans-serif; }
.pulse-dot {
display: inline-block; width: 8px; height: 8px;
background: var(--positive); border-radius: 50%;
margin-left: 0.5rem; vertical-align: middle;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { box-shadow: 0 0 0 0 #788c5d88; }
50% { box-shadow: 0 0 0 6px #788c5d00; }
}
.accent-line { height: 3px; background: var(--gradient-accent); border-radius: 2px; margin-bottom: 2rem; }
/* Alert Banner */
.alert-banner {
background: linear-gradient(135deg, #d9775712, #d4a84318);
border: 1px solid #d4a84355; border-radius: 8px;
padding: 1rem 1.5rem; margin-bottom: 2rem;
display: flex; align-items: center; gap: 0.75rem;
}
.alert-banner .icon { font-size: 1.3rem; }
.alert-banner .text { font-size: 0.95rem; color: var(--warning); font-weight: 500; font-family: 'Poppins', sans-serif; }
.alert-banner a { color: var(--warning); text-decoration: underline; text-underline-offset: 3px; }
/* Metric Cards */
.metric-grid {
display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem; margin-bottom: 2rem;
}
.metric-card {
background: var(--surface); border: 1px solid var(--border); border-radius: 8px;
padding: 1.25rem; text-align: center;
transition: transform 0.2s, border-color 0.2s, box-shadow 0.2s;
cursor: pointer; text-decoration: none; display: block; color: inherit;
}
.metric-card:hover {
transform: translateY(-2px); border-color: var(--accent);
box-shadow: 0 4px 20px #d9775722; text-decoration: none;
}
.metric-card .label { color: var(--text-secondary); font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; font-family: 'Poppins', sans-serif; }
.metric-card .value { font-size: 2rem; font-weight: 700; color: var(--accent); font-family: 'Poppins', sans-serif; }
.metric-card .sparkline { margin: 0.5rem auto 0.25rem; display: block; }
.metric-card .trend { font-size: 0.85rem; margin-top: 0.25rem; font-family: 'Poppins', sans-serif; }
.trend-up { color: var(--negative); }
.trend-down { color: var(--positive); }
.trend-flat { color: var(--text-muted); }
/* Charts */
.chart-container {
background: var(--surface); border: 1px solid var(--border); border-radius: 8px;
padding: 1.5rem; margin-bottom: 2rem;
}
.chart-container h2 { font-size: 1.1rem; margin-bottom: 1rem; color: var(--text-secondary); }
.charts-row {
display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 2rem;
}
/* Rate Table */
.rate-table {
width: 100%; border-collapse: collapse; margin-bottom: 2rem;
background: var(--surface); border-radius: 8px; overflow: hidden;
}
.rate-table th {
background: var(--surface-alt); padding: 0.75rem 1rem; text-align: left;
font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-secondary);
}
.rate-table td { padding: 0.75rem 1rem; border-top: 1px solid var(--border); }
.rate-table tr { cursor: pointer; transition: background 0.15s; }
.rate-table tbody tr:hover { background: var(--surface-hover); }
.rate-table td:nth-child(2) { font-weight: 600; color: var(--accent); font-family: 'JetBrains Mono', monospace; }
.rate-table a { color: inherit; text-decoration: none; }
.rate-table a:hover { color: var(--accent); }
/* Sections */
.section {
background: var(--surface); border: 1px solid var(--border); border-radius: 8px;
padding: 1.5rem; margin-bottom: 1.5rem;
}
.section h2 { font-size: 1.1rem; margin-bottom: 1rem; color: var(--accent); display: flex; align-items: center; gap: 0.5rem; }
.section p { color: var(--text); margin-bottom: 0.75rem; font-size: 0.95rem; }
.section p:last-child { margin-bottom: 0; }
.section a { color: var(--accent); }
/* Outlook */
.outlook-grid {
display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem; margin-bottom: 2rem;
}
.outlook-card { background: var(--surface); border-radius: 8px; padding: 1.25rem; }
.outlook-card.base { border: 1px solid var(--accent-blue); }
.outlook-card.upside { border: 1px solid var(--positive); }
.outlook-card.downside { border: 1px solid var(--negative); }
.outlook-card h3 { font-size: 0.95rem; margin-bottom: 0.5rem; }
.outlook-card.base h3 { color: var(--accent-blue); }
.outlook-card.upside h3 { color: var(--positive); }
.outlook-card.downside h3 { color: var(--negative); }
.outlook-card p { color: var(--text-secondary); font-size: 0.9rem; }
footer {
text-align: center; padding-top: 1.5rem; border-top: 1px solid var(--border);
color: var(--text-muted); font-size: 0.8rem; font-family: 'Poppins', sans-serif;
}
footer a { color: var(--text-secondary); }
/* Scroll Fade-In */
.fade-in { opacity: 0; transform: translateY(16px); transition: opacity 0.5s ease, transform 0.5s ease; }
.fade-in.visible { opacity: 1; transform: translateY(0); }
@media (prefers-reduced-motion: reduce) {
.fade-in { opacity: 1; transform: none; transition: none; }
}
@media (max-width: 768px) {
.charts-row { grid-template-columns: 1fr; }
.sticky-nav { padding: 0.5rem 1rem; flex-wrap: wrap; }
}
@media print {
body { background: #fff; color: #141413; padding: 1rem; }
.sticky-nav { display: none; }
.metric-card, .section, .chart-container, .outlook-card, .rate-table { background: #fff; border-color: #e8e6dc; box-shadow: none; }
.metric-card .value, .rate-table td:nth-child(2) { color: #c15f3c; }
.section h2 { color: #c15f3c; }
.accent-line { print-color-adjust: exact; -webkit-print-color-adjust: exact; }
.fade-in { opacity: 1 !important; transform: none !important; }
a { color: #141413; }
a::after { content: " (" attr(href) ")"; font-size: 0.75rem; color: #807e76; }
.pulse-dot { display: none; }
}
</style>
</head>
<body>
<!-- Sticky Navigation -->
<nav class="sticky-nav">
<a href="#overview" class="nav-link active">Overview</a>
<a href="#rates" class="nav-link">Rates</a>
<a href="#analysis" class="nav-link">Analysis</a>
<a href="#outlook" class="nav-link">Outlook</a>
</nav>
<header>
<h1>Macro Briefing: Fed Rate Decision Impact</h1>
<div class="subtitle">{{DATE}} | FinTechCo Research Agent <span class="pulse-dot"></span></div>
</header>
<div class="container">
<div class="accent-line"></div>
<section id="overview">
<div class="alert-banner fade-in">
<span class="icon">⚠</span>
<span class="text"><a href="{{WATCHPOINT_URL}}" target="_blank" rel="noopener">{{KEY_WATCHPOINT}}</a></span>
</div>
<!-- Metric cards: each links to its FRED series page -->
<div class="metric-grid">
<a class="metric-card fade-in" href="https://fred.stlouisfed.org/series/FEDFUNDS" target="_blank" rel="noopener">
<div class="label">Fed Funds Rate</div>
<div class="value" data-target="{{FEDFUNDS_VALUE}}">{{FEDFUNDS_VALUE}}%</div>
<!-- sparkline SVG injected by JS -->
<div class="sparkline" id="spark-fedfunds"></div>
<div class="trend {{FEDFUNDS_TREND_CLASS}}">{{FEDFUNDS_TREND_ARROW}} {{FEDFUNDS_TREND_LABEL}}</div>
</a>
<a class="metric-card fade-in" href="https://fred.stlouisfed.org/series/DGS10" target="_blank" rel="noopener">
<div class="label">10-Year Treasury</div>
<div class="value" data-target="{{DGS10_VALUE}}">{{DGS10_VALUE}}%</div>
<div class="sparkline" id="spark-dgs10"></div>
<div class="trend {{DGS10_TREND_CLASS}}">{{DGS10_TREND_ARROW}} {{DGS10_TREND_LABEL}}</div>
</a>
<a class="metric-card fade-in" href="https://fred.stlouisfed.org/series/DGS2" target="_blank" rel="noopener">
<div class="label">2-Year Treasury</div>
<div class="value" data-target="{{DGS2_VALUE}}">{{DGS2_VALUE}}%</div>
<div class="sparkline" id="spark-dgs2"></div>
<div class="trend {{DGS2_TREND_CLASS}}">{{DGS2_TREND_ARROW}} {{DGS2_TREND_LABEL}}</div>
</a>
<a class="metric-card fade-in" href="https://fred.stlouisfed.org/series/MORTGAGE30US" target="_blank" rel="noopener">
<div class="label">30-Year Mortgage</div>
<div class="value" data-target="{{MORTGAGE30US_VALUE}}">{{MORTGAGE30US_VALUE}}%</div>
<div class="sparkline" id="spark-mortgage"></div>
<div class="trend {{MORTGAGE30US_TREND_CLASS}}">{{MORTGAGE30US_TREND_ARROW}} {{MORTGAGE30US_TREND_LABEL}}</div>
</a>
<a class="metric-card fade-in" href="https://fred.stlouisfed.org/series/T10Y2Y" target="_blank" rel="noopener">
<div class="label">Yield Curve (10Y-2Y)</div>
<div class="value" data-target="{{T10Y2Y_VALUE}}">{{T10Y2Y_VALUE}}%</div>
<div class="sparkline" id="spark-t10y2y"></div>
<div class="trend {{T10Y2Y_TREND_CLASS}}">{{T10Y2Y_TREND_ARROW}} {{T10Y2Y_TREND_LABEL}}</div>
</a>
</div>
</section>
<section id="rates">
<div class="chart-container fade-in">
<h2>Federal Funds Rate — Trend</h2>
<canvas id="fedFundsChart" height="80"></canvas>
</div>
<div class="charts-row">
<div class="chart-container fade-in">
<h2>Treasury Yields — Recent Activity</h2>
<canvas id="treasuryChart" height="120"></canvas>
</div>
<div class="chart-container fade-in">
<h2>Yield Curve Spread (10Y − 2Y)</h2>
<canvas id="yieldCurveChart" height="120"></canvas>
</div>
</div>
<!-- Rate table: each row links to FRED -->
<table class="rate-table fade-in">
<thead>
<tr><th>Indicator</th><th>Current</th><th>Trend</th><th>Signal</th></tr>
</thead>
<tbody>
<tr onclick="window.open('https://fred.stlouisfed.org/series/FEDFUNDS','_blank')">
<td><a href="https://fred.stlouisfed.org/series/FEDFUNDS" target="_blank" rel="noopener">Fed Funds Rate</a></td>
<td>{{FEDFUNDS_VALUE}}%</td><td>{{FEDFUNDS_TREND_LABEL}}</td><td>{{FEDFUNDS_SIGNAL}}</td>
</tr>
<tr onclick="window.open('https://fred.stlouisfed.org/series/DGS2','_blank')">
<td><a href="https://fred.stlouisfed.org/series/DGS2" target="_blank" rel="noopener">2-Year Treasury</a></td>
<td>{{DGS2_VALUE}}%</td><td>{{DGS2_TREND_LABEL}}</td><td>{{DGS2_SIGNAL}}</td>
</tr>
<tr onclick="window.open('https://fred.stlouisfed.org/series/DGS10','_blank')">
<td><a href="https://fred.stlouisfed.org/series/DGS10" target="_blank" rel="noopener">10-Year Treasury</a></td>
<td>{{DGS10_VALUE}}%</td><td>{{DGS10_TREND_LABEL}}</td><td>{{DGS10_SIGNAL}}</td>
</tr>
<tr onclick="window.open('https://fred.stlouisfed.org/series/MORTGAGE30US','_blank')">
<td><a href="https://fred.stlouisfed.org/series/MORTGAGE30US" target="_blank" rel="noopener">30-Year Mortgage</a></td>
<td>{{MORTGAGE30US_VALUE}}%</td><td>{{MORTGAGE30US_TREND_LABEL}}</td><td>{{MORTGAGE30US_SIGNAL}}</td>
</tr>
<tr onclick="window.open('https://fred.stlouisfed.org/series/T10Y2Y','_blank')">
<td><a href="https://fred.stlouisfed.org/series/T10Y2Y" target="_blank" rel="noopener">Yield Curve (10Y-2Y)</a></td>
<td>{{T10Y2Y_VALUE}}%</td><td>{{T10Y2Y_TREND_LABEL}}</td><td>{{T10Y2Y_SIGNAL}}</td>
</tr>
</tbody>
</table>
</section>
<section id="analysis">
<div class="section fade-in">
<h2>🏛 Fed Policy Direction</h2>
<!-- 2 paragraphs synthesizing FOMC decisions, commentary, forward guidance -->
<!-- Embed [SOURCE] links from Subagent B as inline <a> tags -->
</div>
<div class="section fade-in">
<h2>💰 Cost of Capital Impact</h2>
<!-- 2 paragraphs on borrowing costs, credit facilities, warehouse lines, funding costs -->
</div>
<div class="section fade-in">
<h2>🎯 Merchant Pricing Implications</h2>
<!-- 2 paragraphs with actionable insights on MCA pricing, factoring rates, risk premiums -->
</div>
</section>
<section id="outlook">
<div class="outlook-grid">
<div class="outlook-card base fade-in">
<h3>📊 Base Case</h3>
<p><!-- 1-2 sentences --></p>
</div>
<div class="outlook-card upside fade-in">
<h3>⬆ Upside Risk</h3>
<p><!-- 1 sentence --></p>
</div>
<div class="outlook-card downside fade-in">
<h3>⬇ Downside Risk</h3>
<p><!-- 1 sentence --></p>
</div>
</div>
</section>
<footer>
Generated by FinTechCo Research Agent | Sources:
<a href="https://fred.stlouisfed.org/" target="_blank" rel="noopener">FRED</a>,
<a href="https://www.federalreserve.gov/monetarypolicy/fomccalendars.htm" target="_blank" rel="noopener">FOMC</a>,
market research
</footer>
</div>
<script>
/* --- Sparkline generator --- */
function renderSparkline(containerId, values, color) {
const el = document.getElementById(containerId);
if (!el || !values.length) return;
const w = 120, h = 30, pad = 2;
const min = Math.min(...values), max = Math.max(...values);
const range = max - min || 1;
const points = values.map((v, i) =>
`${pad + (i / (values.length - 1)) * (w - 2 * pad)},${pad + (1 - (v - min) / range) * (h - 2 * pad)}`
).join(' ');
const lastX = pad + (w - 2 * pad);
const lastY = pad + (1 - (values[values.length - 1] - min) / range) * (h - 2 * pad);
el.innerHTML = `<svg width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
<polyline points="${points}" fill="none" stroke="${color}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="${lastX}" cy="${lastY}" r="2.5" fill="${color}"/>
</svg>`;
}
/* --- Populate sparklines with FRED data (chronological order) --- */
// REPLACE these arrays with actual values from Subagent A
renderSparkline('spark-fedfunds', [/* chronological FEDFUNDS values */], '#d97757');
renderSparkline('spark-dgs10', [/* chronological DGS10 values */], '#d4a843');
renderSparkline('spark-dgs2', [/* chronological DGS2 values */], '#6a9bcc');
renderSparkline('spark-mortgage', [/* chronological MORTGAGE30US values */], '#c15f3c');
renderSparkline('spark-t10y2y', [/* chronological T10Y2Y values */], '#788c5d');
/* --- Animated number counters --- */
function animateCounters() {
document.querySelectorAll('[data-target]').forEach(el => {
const target = parseFloat(el.dataset.target);
if (isNaN(target)) return;
const decimals = (el.dataset.target.split('.')[1] || '').length;
const duration = 800;
const start = performance.now();
function update(now) {
const elapsed = now - start;
const progress = Math.min(elapsed / duration, 1);
const eased = 1 - Math.pow(1 - progress, 3);
el.textContent = (target * eased).toFixed(decimals) + '%';
if (progress < 1) requestAnimationFrame(update);
}
requestAnimationFrame(update);
});
}
window.addEventListener('load', animateCounters);
/* --- Scroll fade-in --- */
if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
const observer = new IntersectionObserver(entries => {
entries.forEach(e => {
if (e.isIntersecting) { e.target.classList.add('visible'); observer.unobserve(e.target); }
});
}, { threshold: 0.1 });
document.querySelectorAll('.fade-in').forEach(el => observer.observe(el));
} else {
document.querySelectorAll('.fade-in').forEach(el => el.classList.add('visible'));
}
/* --- Sticky nav active state --- */
const navLinks = document.querySelectorAll('.sticky-nav a');
const sections = document.querySelectorAll('section[id]');
const navObserver = new IntersectionObserver(entries => {
entries.forEach(e => {
if (e.isIntersecting) {
navLinks.forEach(a => a.classList.remove('active'));
const link = document.querySelector(`.sticky-nav a[href="#${e.target.id}"]`);
if (link) link.classList.add('active');
}
});
}, { threshold: 0.3 });
sections.forEach(s => navObserver.observe(s));
/* --- Charts (Chart.js) --- */
const chartFont = { family: 'Poppins' };
const gridColor = '#3d3d3a33';
const tickColor = '#b0aea5';
// REPLACE labels and data arrays with actual FRED observations
// Chart 1: Fed Funds Rate trend
new Chart(document.getElementById('fedFundsChart').getContext('2d'), {
type: 'line',
data: {
labels: [/* date labels */],
datasets: [{
label: 'Fed Funds Rate',
data: [/* values */],
borderColor: '#d97757', backgroundColor: '#d9775722',
fill: true, tension: 0.3, pointRadius: 4, pointBackgroundColor: '#d97757', borderWidth: 2.5
}]
},
options: {
responsive: true,
plugins: { legend: { display: false }, tooltip: { callbacks: { label: ctx => ctx.parsed.y + '%' } } },
scales: {
x: { ticks: { color: tickColor, font: chartFont }, grid: { color: gridColor } },
y: { ticks: { color: tickColor, font: chartFont, callback: v => parseFloat(v.toFixed(2)) + '%' }, grid: { color: gridColor } }
}
}
});
// Chart 2: Treasury Yields
new Chart(document.getElementById('treasuryChart').getContext('2d'), {
type: 'line',
data: {
labels: [/* date labels */],
datasets: [
{ label: '2Y Treasury', data: [/* values */], borderColor: '#6a9bcc', backgroundColor: '#6a9bcc22', tension: 0.3, pointRadius: 3, borderWidth: 2 },
{ label: '10Y Treasury', data: [/* values */], borderColor: '#d4a843', backgroundColor: '#d4a84322', tension: 0.3, pointRadius: 3, borderWidth: 2 }
]
},
options: {
responsive: true,
interaction: { mode: 'index', intersect: false },
plugins: { legend: { labels: { color: tickColor, usePointStyle: true, font: chartFont } }, tooltip: { callbacks: { label: ctx => ctx.dataset.label + ': ' + ctx.parsed.y + '%' } } },
scales: {
x: { ticks: { color: tickColor, font: chartFont, maxRotation: 45 }, grid: { color: gridColor } },
y: { ticks: { color: tickColor, font: chartFont, callback: v => parseFloat(v.toFixed(2)) + '%' }, grid: { color: gridColor } }
}
}
});
// Chart 3: Yield Curve Spread
new Chart(document.getElementById('yieldCurveChart').getContext('2d'), {
type: 'line',
data: {
labels: [/* date labels */],
datasets: [{
label: '10Y-2Y Spread',
data: [/* values */],
borderColor: '#788c5d', backgroundColor: '#788c5d33',
fill: true, tension: 0.3, pointRadius: 3, borderWidth: 2
}]
},
options: {
responsive: true,
plugins: { legend: { display: false }, tooltip: { callbacks: { label: ctx => ctx.parsed.y + '%' } } },
scales: {
x: { ticks: { color: tickColor, font: chartFont, maxRotation: 45 }, grid: { color: gridColor } },
y: { ticks: { color: tickColor, font: chartFont, callback: v => parseFloat(v.toFixed(2)) + '%' }, grid: { color: gridColor } }
}
}
});
</script>
</body>
</html>
CRITICAL: When generating the HTML:
- Replace ALL
{{...}}placeholders with real values from the subagent data - Populate ALL Chart.js
labelsanddataarrays with actual FRED observations (chronological order) - Populate ALL
renderSparkline()calls with actual value arrays (chronological order) - For trend arrows: use
↑for rising,↓for falling,→for flat - For trend classes: use
trend-upfor rising,trend-downfor falling,trend-flatfor flat - Write real prose in each
.section— do NOT leave HTML comments as placeholders - Embed source links from Subagent B as inline
<a>tags within the prose paragraphs - The signal column in the table should be a brief interpretation (e.g., "Easing cycle in progress", "Curve normalizing")
- If
[KEY WATCHPOINT]includes a URL, use it in the alert banner link. Otherwise link to the FOMC calendar.
Step 4: Save the Dashboard
Save the completed HTML dashboard to output/macro-briefing-YYYY-MM-DD.html using today's date. Create the output/ directory if it doesn't exist.
Step 5: Post to Slack (conditional)
If the workflow was triggered from a Slack channel (Step 0), post a concise summary to Slack:
- Use
slack_reply_to_threadto post a summary message to the same channel, withthread_tsset to the original message'stsfrom Step 0. Format the message like this:
:chart_with_upwards_trend: *Macro Briefing Complete*
*Key Rates:*
- Fed Funds: X.XX% (trend)
- 10Y Treasury: X.XX% (trend)
- 30Y Mortgage: X.XX% (trend)
*Watchpoint:* [key watchpoint from Subagent B]
:desktop_computer: Full interactive dashboard saved locally — open `output/macro-briefing-YYYY-MM-DD.html` in any browser for charts, metric cards, and detailed analysis.
- React to the original message with
:white_check_mark:usingslack_add_reactionto signal the briefing is complete.
If the workflow was not triggered from Slack, skip this step.
Important Rules
- Subagents MUST be launched in parallel (single message, two Task tool calls)
- Subagents return compressed summaries only — no raw data in the parent context
- The final dashboard must include specific numbers, not placeholders
- Chart.js data arrays must contain the actual FRED observations
- Sparkline arrays must contain the actual FRED observations
- Source links from Subagent B must appear as clickable
<a>tags in the prose - The HTML must be fully self-contained — inline CSS, Chart.js + Google Fonts via CDN
- Follow the
/frontend-designskill for all styling decisions - Always save to the output/ directory
- Slack steps (0 and 5) are conditional — only run when a Slack channel is specified