just a website
1<!DOCTYPE html>
2<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"><head>
3
4<meta charset="utf-8">
5<meta name="generator" content="quarto-1.9.37">
6
7<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
8
9
10<title>about – Rory Lawless</title>
11<style>
12/* Default styles provided by pandoc.
13** See https://pandoc.org/MANUAL.html#variables-for-html for config info.
14*/
15code{white-space: pre-wrap;}
16span.smallcaps{font-variant: small-caps;}
17div.columns{display: flex; gap: min(4vw, 1.5em);}
18div.column{flex: auto; overflow-x: auto;}
19div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
20ul.task-list{list-style: none;}
21ul.task-list li input[type="checkbox"] {
22 width: 0.8em;
23 margin: 0 0.8em 0.2em -1em; /* quarto-specific, see https://github.com/quarto-dev/quarto-cli/issues/4556 */
24 vertical-align: middle;
25}
26</style>
27
28
29<script src="site_libs/quarto-nav/quarto-nav.js"></script>
30<script src="site_libs/quarto-nav/headroom.min.js"></script>
31<script src="site_libs/clipboard/clipboard.min.js"></script>
32<script src="site_libs/quarto-html/quarto.js" type="module"></script>
33<script src="site_libs/quarto-html/tabsets/tabsets.js" type="module"></script>
34<script src="site_libs/quarto-html/popper.min.js"></script>
35<script src="site_libs/quarto-html/tippy.umd.min.js"></script>
36<link href="site_libs/quarto-html/tippy.css" rel="stylesheet">
37<link href="site_libs/quarto-html/quarto-syntax-highlighting-cad2a2f8a56dfe71ee895a68e79c9b9a.css" rel="stylesheet" id="quarto-text-highlighting-styles">
38<script src="site_libs/bootstrap/bootstrap.min.js"></script>
39<link href="site_libs/bootstrap/bootstrap-icons.css" rel="stylesheet">
40<link href="site_libs/bootstrap/bootstrap-af4d1ddd89e46b498a8a5f4923be9e26.min.css" rel="stylesheet" append-hash="true" id="quarto-bootstrap" data-mode="light">
41<script id="quarto-search-options" type="application/json">{
42 "language": {
43 "search-no-results-text": "No results",
44 "search-matching-documents-text": "matching documents",
45 "search-copy-link-title": "Copy link to search",
46 "search-hide-matches-text": "Hide additional matches",
47 "search-more-match-text": "more match in this document",
48 "search-more-matches-text": "more matches in this document",
49 "search-clear-button-title": "Clear",
50 "search-text-placeholder": "",
51 "search-detached-cancel-button-title": "Cancel",
52 "search-submit-button-title": "Submit",
53 "search-label": "Search"
54 }
55}</script>
56
57
58</head>
59
60<body class="nav-fixed fullcontent quarto-light">
61
62<div id="quarto-search-results"></div>
63 <header id="quarto-header" class="headroom fixed-top">
64 <nav class="navbar navbar-expand-lg " data-bs-theme="dark">
65 <div class="navbar-container container-fluid">
66 <div class="navbar-brand-container mx-auto">
67 <a class="navbar-brand" href="./index.html">
68 <span class="navbar-title">Rory Lawless</span>
69 </a>
70 </div>
71 <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" role="menu" aria-expanded="false" aria-label="Toggle navigation" onclick="if (window.quartoToggleHeadroom) { window.quartoToggleHeadroom(); }">
72 <span class="navbar-toggler-icon"></span>
73</button>
74 <div class="collapse navbar-collapse" id="navbarCollapse">
75 <ul class="navbar-nav navbar-nav-scroll ms-auto">
76 <li class="nav-item">
77 <a class="nav-link" href="./resume.html"> <i class="bi bi-file-earmark-person-fill" role="img" aria-label="Rory's Resume">
78</i>
79<span class="menu-text"> </span></a>
80 </li>
81 <li class="nav-item compact">
82 <a class="nav-link" href="mailto:rory@rorylawless.com"> <i class="bi bi-envelope-at-fill" role="img" aria-label="Email Rory">
83</i>
84<span class="menu-text"></span></a>
85 </li>
86 <li class="nav-item compact">
87 <a class="nav-link" href="https://bsky.app/profile/rorylawless.com"> <i class="bi bi-bluesky" role="img" aria-label="Rory's Bluesky">
88</i>
89<span class="menu-text"></span></a>
90 </li>
91 <li class="nav-item compact">
92 <a class="nav-link" href="https://www.linkedin.com/in/rory-lawless/"> <i class="bi bi-linkedin" role="img" aria-label="Rory's LinkedIn">
93</i>
94<span class="menu-text"></span></a>
95 </li>
96 <li class="nav-item compact">
97 <a class="nav-link" href="https://tangled.org/rorylawless.com"> <i class="bi bi-git" role="img" aria-label="Rory's Tangled">
98</i>
99<span class="menu-text"></span></a>
100 </li>
101</ul>
102 </div> <!-- /navcollapse -->
103 <div class="quarto-navbar-tools">
104</div>
105 </div> <!-- /container-fluid -->
106 </nav>
107</header>
108<!-- content -->
109<div id="quarto-content" class="quarto-container page-columns page-rows-contents page-layout-article page-navbar">
110<!-- sidebar -->
111<!-- margin-sidebar -->
112
113<!-- main -->
114<div class="quarto-about-marquee">
115 <div class="about-image-container">
116 </div>
117 <div class="about-contents">
118 <header id="title-block-header" class="quarto-title-block"></header> <main class="content" id="quarto-document-content">
119<a href="#quarto-document-content" class="skip-to-content">Skip to main content</a>
120<section id="about-me" class="level2">
121<h2 data-anchor-id="about-me">About Me</h2>
122<p>Data analyst based in Washington, DC. I apply a political economy and public policy lens to my work across education policy, financial services regulation, and mental health outcomes, with a focus on how policies and the institutions that design them affect people with the least power to shape them.</p>
123<p>Analysis I have worked on has been picked up by the <a href="https://www.ft.com/content/398fe5be-cf44-459c-83d1-5105adef7337?syn-25a6b1a6=1">Financial Times</a> and I have written about my work on the <a href="https://dme.dc.gov/page/inside-dc-education-blog#10082025-2">Inside DC Education Blog</a>.</p>
124
125
126</section>
127</main>
128 </div>
129</div>
130 <!-- /main -->
131<!-- Accessibility patches for Quarto-generated behaviour -->
132
133<!-- Fix #6: Add tabindex="-1" to the skip link target so Safari moves keyboard
134 focus there when the skip link is activated. Without this, Safari scrolls to
135 the fragment but leaves focus on the skip link itself — the next Tab press
136 returns to the first navbar item instead of the main content. tabindex="-1"
137 makes the element programmatically focusable without inserting it into the
138 natural Tab order. Targets <main id="quarto-document-content">, which is
139 present on every page type (index, about, resume, 404, posts). -->
140<script>
141 function patchSkipLinkTarget() {
142 var main = document.getElementById('quarto-document-content');
143 if (main && !main.hasAttribute('tabindex')) {
144 main.setAttribute('tabindex', '-1');
145 }
146 }
147
148 if (document.readyState === 'loading') {
149 document.addEventListener('DOMContentLoaded', patchSkipLinkTarget);
150 } else {
151 patchSkipLinkTarget();
152 }
153</script>
154
155<!-- Fix #4: Remove incorrect role="menu" from the navbar toggle button.
156 Quarto's internal pandoc template bakes this attribute into the HTML at
157 render time; it cannot be suppressed via _quarto.yml. The button is
158 semantically correct as a plain <button> with aria-expanded and
159 aria-controls already present — no role is needed.
160 readyState guard: if this end-of-body script is reached after
161 DOMContentLoaded has already fired (possible on fast parses), the
162 event listener would never run — so we check first and run immediately
163 if the DOM is already ready. -->
164<script>
165 function removeNavToggleRole() {
166 var toggler = document.querySelector('.navbar-toggler[role="menu"]');
167 if (toggler) {
168 toggler.removeAttribute('role');
169 }
170 }
171
172 if (document.readyState === 'loading') {
173 document.addEventListener('DOMContentLoaded', removeNavToggleRole);
174 } else {
175 removeNavToggleRole();
176 }
177</script>
178
179<!-- Fix #5: Restore keyboard focus after code copy button activation.
180 Quarto's onCopySuccess callback calls button.blur() immediately after a
181 successful copy. This silently drops keyboard focus. requestAnimationFrame
182 defers the re-focus until after the current synchronous call stack
183 (including blur()) has completed, so the focus ring reappears without
184 interfering with the "Copied!" visual feedback.
185 e.detail === 0 guard: the click event fires for both mouse and keyboard
186 activations. e.detail is 0 for keyboard-triggered clicks and ≥1 for
187 mouse clicks. Without this guard, the script would re-focus the button
188 after every mouse click, potentially moving focus away from wherever the
189 user clicked next. -->
190<script>
191 document.addEventListener('click', function (e) {
192 if (e.detail !== 0) { return; }
193 var btn = e.target.closest('.code-copy-button');
194 if (btn) {
195 requestAnimationFrame(function () { btn.focus(); });
196 }
197 });
198</script>
199<script id="quarto-html-after-body" type="application/javascript">
200 window.document.addEventListener("DOMContentLoaded", function (event) {
201 const isCodeAnnotation = (el) => {
202 for (const clz of el.classList) {
203 if (clz.startsWith('code-annotation-')) {
204 return true;
205 }
206 }
207 return false;
208 }
209 const onCopySuccess = function(e) {
210 // button target
211 const button = e.trigger;
212 // don't keep focus
213 button.blur();
214 // flash "checked"
215 button.classList.add('code-copy-button-checked');
216 var currentTitle = button.getAttribute("title");
217 button.setAttribute("title", "Copied!");
218 let tooltip;
219 if (window.bootstrap) {
220 button.setAttribute("data-bs-toggle", "tooltip");
221 button.setAttribute("data-bs-placement", "left");
222 button.setAttribute("data-bs-title", "Copied!");
223 tooltip = new bootstrap.Tooltip(button,
224 { trigger: "manual",
225 customClass: "code-copy-button-tooltip",
226 offset: [0, -8]});
227 tooltip.show();
228 }
229 setTimeout(function() {
230 if (tooltip) {
231 tooltip.hide();
232 button.removeAttribute("data-bs-title");
233 button.removeAttribute("data-bs-toggle");
234 button.removeAttribute("data-bs-placement");
235 }
236 button.setAttribute("title", currentTitle);
237 button.classList.remove('code-copy-button-checked');
238 }, 1000);
239 // clear code selection
240 e.clearSelection();
241 }
242 const getTextToCopy = function(trigger) {
243 const outerScaffold = trigger.parentElement.cloneNode(true);
244 const codeEl = outerScaffold.querySelector('code');
245 for (const childEl of codeEl.children) {
246 if (isCodeAnnotation(childEl)) {
247 childEl.remove();
248 }
249 }
250 return codeEl.innerText;
251 }
252 const clipboard = new window.ClipboardJS('.code-copy-button:not([data-in-quarto-modal])', {
253 text: getTextToCopy
254 });
255 clipboard.on('success', onCopySuccess);
256 if (window.document.getElementById('quarto-embedded-source-code-modal')) {
257 const clipboardModal = new window.ClipboardJS('.code-copy-button[data-in-quarto-modal]', {
258 text: getTextToCopy,
259 container: window.document.getElementById('quarto-embedded-source-code-modal')
260 });
261 clipboardModal.on('success', onCopySuccess);
262 }
263 var localhostRegex = new RegExp(/^(?:http|https):\/\/localhost\:?[0-9]*\//);
264 var mailtoRegex = new RegExp(/^mailto:/);
265 var filterRegex = new RegExp("https:\/\/rorylawless\.com");
266 var isInternal = (href) => {
267 return filterRegex.test(href) || localhostRegex.test(href) || mailtoRegex.test(href);
268 }
269 // Inspect non-navigation links and adorn them if external
270 var links = window.document.querySelectorAll('a[href]:not(.nav-link):not(.navbar-brand):not(.toc-action):not(.sidebar-link):not(.sidebar-item-toggle):not(.pagination-link):not(.no-external):not([aria-hidden]):not(.dropdown-item):not(.quarto-navigation-tool):not(.about-link)');
271 for (var i=0; i<links.length; i++) {
272 const link = links[i];
273 if (!isInternal(link.href)) {
274 // undo the damage that might have been done by quarto-nav.js in the case of
275 // links that we want to consider external
276 if (link.dataset.originalHref !== undefined) {
277 link.href = link.dataset.originalHref;
278 }
279 }
280 }
281 function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) {
282 const config = {
283 allowHTML: true,
284 maxWidth: 500,
285 delay: 100,
286 arrow: false,
287 appendTo: function(el) {
288 return el.parentElement;
289 },
290 interactive: true,
291 interactiveBorder: 10,
292 theme: 'quarto',
293 placement: 'bottom-start',
294 };
295 if (contentFn) {
296 config.content = contentFn;
297 }
298 if (onTriggerFn) {
299 config.onTrigger = onTriggerFn;
300 }
301 if (onUntriggerFn) {
302 config.onUntrigger = onUntriggerFn;
303 }
304 window.tippy(el, config);
305 }
306 const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]');
307 for (var i=0; i<noterefs.length; i++) {
308 const ref = noterefs[i];
309 tippyHover(ref, function() {
310 // use id or data attribute instead here
311 let href = ref.getAttribute('data-footnote-href') || ref.getAttribute('href');
312 try { href = new URL(href).hash; } catch {}
313 const id = href.replace(/^#\/?/, "");
314 const note = window.document.getElementById(id);
315 if (note) {
316 return note.innerHTML;
317 } else {
318 return "";
319 }
320 });
321 }
322 const xrefs = window.document.querySelectorAll('a.quarto-xref');
323 const processXRef = (id, note) => {
324 // Strip column container classes
325 const stripColumnClz = (el) => {
326 el.classList.remove("page-full", "page-columns");
327 if (el.children) {
328 for (const child of el.children) {
329 stripColumnClz(child);
330 }
331 }
332 }
333 stripColumnClz(note)
334 if (id === null || id.startsWith('sec-')) {
335 // Special case sections, only their first couple elements
336 const container = document.createElement("div");
337 if (note.children && note.children.length > 2) {
338 container.appendChild(note.children[0].cloneNode(true));
339 for (let i = 1; i < note.children.length; i++) {
340 const child = note.children[i];
341 if (child.tagName === "P" && child.innerText === "") {
342 continue;
343 } else {
344 container.appendChild(child.cloneNode(true));
345 break;
346 }
347 }
348 if (window.Quarto?.typesetMath) {
349 window.Quarto.typesetMath(container);
350 }
351 return container.innerHTML
352 } else {
353 if (window.Quarto?.typesetMath) {
354 window.Quarto.typesetMath(note);
355 }
356 return note.innerHTML;
357 }
358 } else {
359 // Remove any anchor links if they are present
360 const anchorLink = note.querySelector('a.anchorjs-link');
361 if (anchorLink) {
362 anchorLink.remove();
363 }
364 if (window.Quarto?.typesetMath) {
365 window.Quarto.typesetMath(note);
366 }
367 if (note.classList.contains("callout")) {
368 return note.outerHTML;
369 } else {
370 return note.innerHTML;
371 }
372 }
373 }
374 for (var i=0; i<xrefs.length; i++) {
375 const xref = xrefs[i];
376 tippyHover(xref, undefined, function(instance) {
377 instance.disable();
378 let url = xref.getAttribute('href');
379 let hash = undefined;
380 if (url.startsWith('#')) {
381 hash = url;
382 } else {
383 try { hash = new URL(url).hash; } catch {}
384 }
385 if (hash) {
386 const id = hash.replace(/^#\/?/, "");
387 const note = window.document.getElementById(id);
388 if (note !== null) {
389 try {
390 const html = processXRef(id, note.cloneNode(true));
391 instance.setContent(html);
392 } finally {
393 instance.enable();
394 instance.show();
395 }
396 } else {
397 // See if we can fetch this
398 fetch(url.split('#')[0])
399 .then(res => res.text())
400 .then(html => {
401 const parser = new DOMParser();
402 const htmlDoc = parser.parseFromString(html, "text/html");
403 const note = htmlDoc.getElementById(id);
404 if (note !== null) {
405 const html = processXRef(id, note);
406 instance.setContent(html);
407 }
408 }).finally(() => {
409 instance.enable();
410 instance.show();
411 });
412 }
413 } else {
414 // See if we can fetch a full url (with no hash to target)
415 // This is a special case and we should probably do some content thinning / targeting
416 fetch(url)
417 .then(res => res.text())
418 .then(html => {
419 const parser = new DOMParser();
420 const htmlDoc = parser.parseFromString(html, "text/html");
421 const note = htmlDoc.querySelector('main.content');
422 if (note !== null) {
423 // This should only happen for chapter cross references
424 // (since there is no id in the URL)
425 // remove the first header
426 if (note.children.length > 0 && note.children[0].tagName === "HEADER") {
427 note.children[0].remove();
428 }
429 const html = processXRef(null, note);
430 instance.setContent(html);
431 }
432 }).finally(() => {
433 instance.enable();
434 instance.show();
435 });
436 }
437 }, function(instance) {
438 });
439 }
440 let selectedAnnoteEl;
441 const selectorForAnnotation = ( cell, annotation) => {
442 let cellAttr = 'data-code-cell="' + cell + '"';
443 let lineAttr = 'data-code-annotation="' + annotation + '"';
444 const selector = 'span[' + cellAttr + '][' + lineAttr + ']';
445 return selector;
446 }
447 const selectCodeLines = (annoteEl) => {
448 const doc = window.document;
449 const targetCell = annoteEl.getAttribute("data-target-cell");
450 const targetAnnotation = annoteEl.getAttribute("data-target-annotation");
451 const annoteSpan = window.document.querySelector(selectorForAnnotation(targetCell, targetAnnotation));
452 const lines = annoteSpan.getAttribute("data-code-lines").split(",");
453 const lineIds = lines.map((line) => {
454 return targetCell + "-" + line;
455 })
456 let top = null;
457 let height = null;
458 let parent = null;
459 if (lineIds.length > 0) {
460 //compute the position of the single el (top and bottom and make a div)
461 const el = window.document.getElementById(lineIds[0]);
462 top = el.offsetTop;
463 height = el.offsetHeight;
464 parent = el.parentElement.parentElement;
465 if (lineIds.length > 1) {
466 const lastEl = window.document.getElementById(lineIds[lineIds.length - 1]);
467 const bottom = lastEl.offsetTop + lastEl.offsetHeight;
468 height = bottom - top;
469 }
470 if (top !== null && height !== null && parent !== null) {
471 // cook up a div (if necessary) and position it
472 let div = window.document.getElementById("code-annotation-line-highlight");
473 if (div === null) {
474 div = window.document.createElement("div");
475 div.setAttribute("id", "code-annotation-line-highlight");
476 div.style.position = 'absolute';
477 parent.appendChild(div);
478 }
479 div.style.top = top - 2 + "px";
480 div.style.height = height + 4 + "px";
481 div.style.left = 0;
482 let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter");
483 if (gutterDiv === null) {
484 gutterDiv = window.document.createElement("div");
485 gutterDiv.setAttribute("id", "code-annotation-line-highlight-gutter");
486 gutterDiv.style.position = 'absolute';
487 const codeCell = window.document.getElementById(targetCell);
488 const gutter = codeCell.querySelector('.code-annotation-gutter');
489 gutter.appendChild(gutterDiv);
490 }
491 gutterDiv.style.top = top - 2 + "px";
492 gutterDiv.style.height = height + 4 + "px";
493 }
494 selectedAnnoteEl = annoteEl;
495 }
496 };
497 const unselectCodeLines = () => {
498 const elementsIds = ["code-annotation-line-highlight", "code-annotation-line-highlight-gutter"];
499 elementsIds.forEach((elId) => {
500 const div = window.document.getElementById(elId);
501 if (div) {
502 div.remove();
503 }
504 });
505 selectedAnnoteEl = undefined;
506 };
507 // Handle positioning of the toggle
508 window.addEventListener(
509 "resize",
510 throttle(() => {
511 elRect = undefined;
512 if (selectedAnnoteEl) {
513 selectCodeLines(selectedAnnoteEl);
514 }
515 }, 10)
516 );
517 function throttle(fn, ms) {
518 let throttle = false;
519 let timer;
520 return (...args) => {
521 if(!throttle) { // first call gets through
522 fn.apply(this, args);
523 throttle = true;
524 } else { // all the others get throttled
525 if(timer) clearTimeout(timer); // cancel #2
526 timer = setTimeout(() => {
527 fn.apply(this, args);
528 timer = throttle = false;
529 }, ms);
530 }
531 };
532 }
533 // Attach click handler to the DT
534 const annoteDls = window.document.querySelectorAll('dt[data-target-cell]');
535 for (const annoteDlNode of annoteDls) {
536 annoteDlNode.addEventListener('click', (event) => {
537 const clickedEl = event.target;
538 if (clickedEl !== selectedAnnoteEl) {
539 unselectCodeLines();
540 const activeEl = window.document.querySelector('dt[data-target-cell].code-annotation-active');
541 if (activeEl) {
542 activeEl.classList.remove('code-annotation-active');
543 }
544 selectCodeLines(clickedEl);
545 clickedEl.classList.add('code-annotation-active');
546 } else {
547 // Unselect the line
548 unselectCodeLines();
549 clickedEl.classList.remove('code-annotation-active');
550 }
551 });
552 }
553 const findCites = (el) => {
554 const parentEl = el.parentElement;
555 if (parentEl) {
556 const cites = parentEl.dataset.cites;
557 if (cites) {
558 return {
559 el,
560 cites: cites.split(' ')
561 };
562 } else {
563 return findCites(el.parentElement)
564 }
565 } else {
566 return undefined;
567 }
568 };
569 var bibliorefs = window.document.querySelectorAll('a[role="doc-biblioref"]');
570 for (var i=0; i<bibliorefs.length; i++) {
571 const ref = bibliorefs[i];
572 const citeInfo = findCites(ref);
573 if (citeInfo) {
574 tippyHover(citeInfo.el, function() {
575 var popup = window.document.createElement('div');
576 citeInfo.cites.forEach(function(cite) {
577 var citeDiv = window.document.createElement('div');
578 citeDiv.classList.add('hanging-indent');
579 citeDiv.classList.add('csl-entry');
580 var biblioDiv = window.document.getElementById('ref-' + cite);
581 if (biblioDiv) {
582 citeDiv.innerHTML = biblioDiv.innerHTML;
583 }
584 popup.appendChild(citeDiv);
585 });
586 return popup.innerHTML;
587 });
588 }
589 }
590 });
591 </script>
592</div> <!-- /content -->
593<footer class="footer">
594 <div class="nav-footer">
595 <div class="nav-footer-left">
596
597 </div>
598 <div class="nav-footer-center">
599<p>Made with <a href="https://quarto.org/">Quarto</a> in DC.<br><a href="https://rorylawless.com/">Website</a> © 2026 by <a href="./about.html">Rory Lawless</a> is licensed under <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a>.</p>
600</div>
601 <div class="nav-footer-right">
602
603 </div>
604 </div>
605</footer>
606
607
608
609
610</body></html>